# TypeScript SDK — Patterns & Recipes

Common patterns for timeouts, cancellation, pagination, heartbeat, and advanced client configuration.

## Timeouts and cancellation

Every SDK method accepts `timeoutMs` and `signal` for deadline control:

```ts
// Per-request timeout
const symbols = await client.marketData.listSymbols({
  timeoutMs: 5_000,
});

// Cancellation via AbortController
const controller = new AbortController();
const events = client.predictions.listEvents(
  { status: ["active"] },
  { signal: controller.signal },
);

// Cancel from elsewhere
controller.abort();
```

Set a default timeout for all operations on the client:

```ts
const client = await createClient({
  auth,
  timeoutMs: 10_000, // 10s default for all REST and WebSocket waits
});
```

WebSocket stream listeners also support signal-based cleanup:

```ts
const trades = client.websocket.trades("BTCUSD");
const controller = new AbortController();

trades.on("message", (trade) => {
  console.log(trade);
}, { signal: controller.signal });

// Later: remove the listener without manually calling off()
controller.abort();
```

## Pagination

List endpoints that return many results use offset pagination. The SDK provides an async generator that walks pages automatically:

```ts
import { HttpTransport, HmacAuth } from "gemini-markets/server";

const transport = new HttpTransport({
  env: "sandbox",
  auth: new HmacAuth({ apiKey, apiSecret }),
});

// Walk prediction market positions, 100 per page, up to 500 total
for await (const position of transport.paginate({
  method: "POST",
  path: "/v1/prediction-markets/positions",
  limit: 100,
  maxItems: 500,
  itemsKey: "positions",           // response is { positions, total }, not a bare array
  parameterLocation: "query",      // limit/offset go as query params, not in signed payload
})) {
  console.log(position);
}
```

### Deduplication

Offset pagination is not snapshot-consistent — records can shift between pages while you're iterating. Use `dedupeKey` to fail loudly if the same record appears twice:

```ts
for await (const position of transport.paginate({
  method: "POST",
  path: "/v1/prediction-markets/positions",
  limit: 100,
  itemsKey: "positions",
  parameterLocation: "query",
  dedupeKey: (item: unknown) => {
    const pos = item as Record<string, unknown>;
    return `${pos.instrumentId}:${pos.outcome}`; // instrumentId alone is not unique — YES and NO positions share the same ID
  },
})) {
  // Throws SdkError if a duplicate instrumentId:outcome pair is seen
}
```

## Heartbeat

Keep a session alive with an explicit heartbeat. The heartbeat is stopped by default — you control its lifecycle:

```ts
const heartbeat = client.createHeartbeat({
  intervalMs: 15_000, // default
  onError: (err) => console.error("Heartbeat failed:", err),
});

heartbeat.start();

// Later
heartbeat.stop();
```

The heartbeat sends a `POST /v1/heartbeat` request at the configured interval. It requires an authenticated client (HMAC or OAuth).

## File responses

Some endpoints return binary files (XLSX, CSV) instead of JSON:

```ts
const report = await client.marketData.getFundingAmountReportFile({
  symbol: "BTCGUSDPERP",
});

// report.bytes: Uint8Array — the raw file content
// report.contentType: string | undefined (e.g. "text/csv" or XLSX MIME type)
// report.contentDisposition: string | undefined (Content-Disposition header)

// Write to disk (Node.js)
import { writeFileSync } from "node:fs";
const ext = report.contentType.includes("csv") ? "csv" : "xlsx";
writeFileSync(`funding-report.${ext}`, report.bytes);
```

## Prediction markets — terms acceptance

Prediction market order placement requires accepting the current terms. The SDK enforces this:

```ts
// Check terms status
const terms = await client.predictions.getPredictionMarketsTermsStatus();

if (!terms.hasAcceptedLatest) {
  // Show terms to the user, get explicit consent, then:
  await client.predictions.acceptTerms();
}

// Now orders will work
await client.predictions.placeOrder({ /* ... */ });
```

If you skip this, the SDK throws `AcceptTermsRequired`.

## Advanced client options

The full set of options available on `createClient()`:

```ts
const client = await createClient({
  // Environment
  env: "sandbox",                    // "sandbox" | "production"

  // Authentication
  auth: new HmacAuth({ apiKey, apiSecret }),

  // Timeouts and retries
  timeoutMs: 30_000,                 // default: 30s
  maxRetries: 5,                     // default: 5 (safe reads only)
  backoff: {
    baseMs: 500,                     // initial backoff (default: 500)
    capMs: 30_000,                   // maximum backoff
    factor: 2,                       // exponential factor
  },

  // WebSocket
  skipWsInit: false,                 // true to skip ws preloading (REST-only)
  webSocketFactory: customFactory,   // custom socket factory
  webSocketLiveness: {
    intervalMs: 30_000,              // liveness check interval
    timeoutMs: 5_000,               // liveness check timeout
  },
  webSocketMaxMessageSizeBytes: 1_048_576, // 1MB frame limit

  // Custom fetch (for proxies, instrumentation, or testing)
  fetch: customFetchImpl,

  // Observability
  logger: new ConsoleLogger({ minLevel: "debug" }),
  onDiagnostic: (event) => telemetry.record(event),
});
```

## Runtime compatibility

The SDK runs on any runtime with Web Crypto and standard `fetch`:

| Runtime | Browser entry | Server entry | Notes |
| --- | --- | --- | --- |
| **Node.js** 22+ | Yes | Yes | Full support |
| **Node.js** 18–21 | Yes | Yes | Partial — no lossless JSON (throws on responses containing unsafe integers) |
| **Bun** | Yes | Yes | Partial — no lossless JSON (Bun lacks `JSON.parse` source access as of v1.x) |
| **Deno** | Yes | Yes | Partial — no lossless JSON (Deno lacks `JSON.parse` source access as of v2.x) |
| **Cloudflare Workers** | Yes | No | No `ws` package — browser entry only; no lossless JSON |
| **Browsers** | Yes | No | No API secrets in client code; no lossless JSON |

The server entry requires the `ws` peer dependency for authenticated WebSocket connections:

```bash
npm install ws
```

`ws` is optional — if you only use REST endpoints with `skipWsInit: true`, you don't need it.

## Closing the client

Always close the client when done to release WebSocket connections:

```ts
const client = await createClient({ auth });

try {
  // ... use the client
} finally {
  client.close();
}
```

`close()` shuts down all active WebSocket streams and the shared session. It does not cancel in-flight REST requests.

## What's next

- [API Reference](/tools/typescript-sdk/reference/overview) — all 105 operations with code examples
- [WebSocket Reference](/tools/typescript-sdk/reference/websocket) — streams, methods, and wire format
- [Deep dives](/tools/typescript-sdk/deep-dives/order-book) — order book reconstruction, RFQ, sessions, transport internals
