# SDK (TypeScript & Go)

OmniStream punya dua SDK resmi: **TypeScript** (`@omnistreams/sdk`) dan **Go**
(`omnistream-go`). Keduanya menambahkan auth, retry, pagination, idempotency,
typed errors, dan verifikasi webhook di atas REST API.

## TypeScript

`@omnistreams/sdk` adalah client TypeScript resmi. Tipe di-generate dari OpenAPI
spec; runtime menambahkan auth, retry, pagination, idempotency, typed errors,
dan helper verifikasi webhook.

### Instalasi

```bash
npm install @omnistreams/sdk
```

Butuh Node.js ≥ 18 (memakai `fetch` global dan Web Crypto).

### Penggunaan

```ts
import { OmnistreamClient } from "@omnistreams/sdk";

const client = new OmnistreamClient({
  apiKey: process.env.OMNISTREAM_API_KEY!,
  baseUrl: "https://api.omnistream.example", // default http://localhost:3000
});

// Resource helper yang typed
const open = await client.conversations.list({ status: "open" });

await client.conversations.sendMessage(open[0].id, {
  type: "text",
  content: { body: "Halo! Ada yang bisa kami bantu?" },
});
```

Untuk **membaca** teks pesan, pakai `content.body`: semua channel engine
menormalkan konten masuk ke `{ body }` sebelum menyimpannya. Di jalur kirim
kedua kunci diterima (`text` didahulukan, jatuh ke `body`), dan apa pun yang
Anda kirim disimpan apa adanya — jadi mengirim `body` membuat baca dan tulis
memakai kunci yang sama.

### Pagination otomatis

Ada dua bentuk di API, dan cara paginasinya berbeda.

**Array polos** (`/api/conversations`, `/api/wa-templates`, …) — `paginate()`
menelusurinya secara lazy:

```ts
import type { Conversation } from "@omnistreams/sdk";

for await (const c of client.paginate<Conversation>("/api/conversations", { status: "open" })) {
  console.log(c.id);
}
```

**Envelope pagination** (`/api/contacts`, `/api/messages/search`) — endpoint ini
mengembalikan `{ data | results, total, page, per_page, total_pages }`, jadi
`paginate()` tidak berlaku. Pakai helper resource-nya:

```ts
const rows = await client.contacts.list({ search: "acme" });        // Contact[]
const page = await client.contacts.listPage({ search: "acme", page: 2 });
console.log(`${page.data.length} dari ${page.total}`);
```

### Pencarian pesan

`messages.search` menerima objek params dan mengembalikan envelope berisi
`MessageSearchResult` — hit membawa `snippet` ber-`<mark>` dan konteks kontak,
dan sengaja tidak punya `status`, `external_id`, atau field error. Ukuran
halaman memakai `per_page` (di-clamp 1..50 oleh server); tidak ada `limit`.

```ts
const hits = await client.messages.search({
  q: "invoice",
  channel: "whatsapp",
  direction: "inbound",
  per_page: 50,
});
for (const hit of hits.results) {
  console.log(hit.contact_name, hit.snippet); // snippet berupa HTML — escape sebelum render
}
```

### Idempotency

```ts
import { generateIdempotencyKey } from "@omnistreams/sdk";

await client.conversations.sendMessage(
  id,
  { type: "text", content: { text: "hi" } },
  { idempotencyKey: generateIdempotencyKey() }, // aman di-retry
);
```

### Typed errors

```ts
import { OmnistreamApiError } from "@omnistreams/sdk";

try {
  await client.conversations.get("tidak-ada");
} catch (err) {
  if (err instanceof OmnistreamApiError && err.isNotFound) {
    // 404
  }
}
```

`OmnistreamApiError` mengekspos `status`, `code`, `body`, dan helper:
`isAuthError` (401), `isForbidden` (403), `isNotFound` (404),
`isValidationError` (422), `isRateLimited` (429), `isServerError` (5xx).

### Retry & rate limit

Request yang gagal di-retry otomatis dengan exponential backoff + jitter;
respons `429` menghormati `Retry-After`. GET di-retry pada error jaringan/5xx;
method lain hanya pada `429` kecuali diberi `Idempotency-Key`. Atur dengan
`maxRetries` dan `timeoutMs` di konstruktor.

Lihat juga: [Verifikasi webhook](/developer/verifikasi-webhook).

## Go

[`omnistream-go`](https://github.com/Cepat-Kilat-Teknologi/omnistream-go) adalah
client Go resmi. Cakupannya sama secara konsep (auth, retry, idempotency,
pagination, verifikasi webhook) tapi tanpa dependency pihak ketiga; lima
resource punya helper typed (`Conversations`, `Contacts`, `Messages`,
`Templates`, `APIKeys`), endpoint lain dijangkau lewat generic escape hatch
(`Get`/`Post`/`Delete`/`Do`). Belum ada tag rilis — pending `v0.1.0`.

### Instalasi

```bash
go get github.com/Cepat-Kilat-Teknologi/omnistream-go
```

Butuh Go 1.23 atau lebih baru. Tanpa dependency pihak ketiga.

### Penggunaan

```go
import (
    "context"
    "log"
    "os"

    omnistream "github.com/Cepat-Kilat-Teknologi/omnistream-go"
)

client, err := omnistream.NewClient(
    os.Getenv("OMNISTREAM_API_KEY"),
    omnistream.WithBaseURL("https://api.omnistream.example"), // default http://localhost:3000
)
if err != nil {
    log.Fatal(err)
}

ctx := context.Background()
open, err := client.Conversations.List(ctx, &omnistream.ConversationListParams{
    Status: omnistream.ConversationOpen,
})
```

### Verifikasi webhook

```go
import "github.com/Cepat-Kilat-Teknologi/omnistream-go/webhook"

// rawBody harus body mentah persis seperti diterima — verifikasi SEBELUM json.Unmarshal.
ok := webhook.Verify(rawBody, r.Header.Get(webhook.SignatureHeader), secret)
```

Selama rotasi secret, terima salah satu signature dengan
`webhook.VerifyWithRotation(rawBody, secret, currentHeader, previousHeader)`.
Detail rotasi dan contoh tanpa SDK: [Verifikasi webhook](/developer/verifikasi-webhook).

Source dan dokumentasi lengkap: https://github.com/Cepat-Kilat-Teknologi/omnistream-go
