> ## Documentation Index
> Fetch the complete documentation index at: https://docs.strait.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Effect-first TypeScript SDK with generated operations, authoring DSL, and XState FSMs.

The TypeScript SDK (`@strait/ts`) is an Effect-first SDK with full API coverage, a code-first authoring DSL, and XState state machines for run lifecycle management.

## Installation

```bash theme={null}
npm install @strait/ts
# or
bun add @strait/ts
```

## Client Creation

### From `strait.json` (recommended)

```ts theme={null}
import { createClientFromConfigFile } from "@strait/ts/node";

const client = await createClientFromConfigFile();
```

See [Configuration](/sdks/configuration) for `strait.json` schema and options.

### From environment variables

```ts theme={null}
import { createClientFromEnv } from "@strait/ts/node";

// Reads STRAIT_BASE_URL, STRAIT_API_KEY, STRAIT_AUTH_TYPE, STRAIT_TIMEOUT_MS
const client = createClientFromEnv();
```

### Inline

```ts theme={null}
import { createClient } from "@strait/ts";

const client = createClient({
  baseUrl: "https://api.strait.dev",
  auth: { type: "bearer", token: process.env.STRAIT_API_KEY! },
  timeoutMs: 30_000,
});
```

## Calling Operations

### High-level methods

```ts theme={null}
const job = await client.createJob({
  body: {
    project_id: "proj_1",
    name: "Sync inventory",
    slug: "sync-inventory",
    endpoint_url: "https://worker.example/jobs/sync",
  },
});
```

### Namespaced methods

```ts theme={null}
const jobs = await client.jobs.list({ query: { project_id: "proj_1" } });
```

### Result variants

Non-GET operations have `*Result` variants that return `{ ok, data, error }` instead of throwing:

```ts theme={null}
const result = await client.createJobResult({
  body: { project_id: "proj_1", name: "...", slug: "...", endpoint_url: "..." },
});

if (!result.ok) {
  console.error(result.error);
}
```

## Authoring DSL

### Defining jobs

```ts theme={null}
import { defineJob, zodSchema } from "@strait/ts";
import { z } from "zod";

const syncInventory = defineJob({
  name: "Sync Inventory",
  slug: "sync-inventory",
  endpointUrl: "https://worker.dev/jobs/sync",
  projectId: "proj_1",
  schema: zodSchema(z.object({ sku: z.string() })),
  maxConcurrency: 5,
  maxAttempts: 5,
  retryStrategy: "exponential",
  timeoutSecs: 300,

  run: async (payload, ctx) => {
    ctx.logger.info("Starting sync", { sku: payload.sku });
    await ctx.reportProgress(0.5);
    const result = await fetchInventory(payload.sku);
    return { synced: true, count: result.items.length };
  },

  onSuccess: async ({ output }) => console.log("Synced", output.count, "items"),
  onFailure: async ({ error }) => alertOncall(error),
});

// Register, trigger, wait
await syncInventory.register(client);
const run = await syncInventory.trigger(client, { payload: { sku: "ABC-123" } });
const completed = await syncInventory.triggerAndWait(client, { payload: { sku: "ABC-123" } });
```

### Defining workflows

```ts theme={null}
import { defineWorkflow, step } from "@strait/ts";

const pipeline = defineWorkflow({
  name: "Order Pipeline",
  slug: "order-pipeline",
  projectId: "proj_1",
  steps: [
    step.job("validate", "job_validate"),
    step.job("charge", "job_charge", { dependsOn: ["validate"] }),
    step.approval("review", { dependsOn: ["charge"], approvalTimeoutSecs: 3600 }),
    step.waitForEvent("shipping", "shipping.confirmed", { dependsOn: ["review"] }),
    step.sleep("cooldown", 60, { dependsOn: ["shipping"] }),
  ],
});
// DAG is validated at definition time
```

## Composition Helpers

```ts theme={null}
import { withRetry, waitForRun, paginate, collectAll } from "@strait/ts";

// Retry with jitter
const run = await withRetry(
  () => client.triggerJob({ pathParams: { jobID: "job_1" }, body: { payload: { sku: "A" } } }),
  { attempts: 5, delayMs: 250, jitter: "full" }
);

// Wait for run completion
await waitForRun(client.getRun, run.id, { timeoutMs: 120_000 });

// Paginate
for await (const r of paginate((q) => client.listRuns({ query: q }))) {
  console.log(r.id, r.status);
}
const all = await collectAll(paginate((q) => client.listRuns({ query: q })));
```

## FSM State Machines

XState v5 machines for run lifecycle validation and UI state management:

```ts theme={null}
import { canTransitionRun, isTerminalRunStatus, runMachine } from "@strait/ts";
import { createActor } from "xstate";

canTransitionRun("executing", "COMPLETE"); // true
isTerminalRunStatus("completed"); // true

const actor = createActor(runMachine);
actor.start();
actor.send({ type: "ENQUEUE" });
actor.send({ type: "DEQUEUE" });
actor.send({ type: "EXECUTE" });
actor.getSnapshot().value; // "executing"
```

## Middleware

```ts theme={null}
const client = createClient({
  baseUrl: "https://api.strait.dev",
  auth: { type: "bearer", token: "..." },
}, {
  middleware: [{
    onRequest: ({ method, url }) => console.log(`-> ${method} ${url}`),
    onResponse: ({ status, durationMs }) => console.log(`<- ${status} (${durationMs}ms)`),
    onError: ({ error }) => console.error(error),
  }],
});
```

## Error Handling

All errors extend Effect's `Data.TaggedError`:

```ts theme={null}
import { NotFoundError, UnauthorizedError, RateLimitedError } from "@strait/ts";

try {
  await client.getJob({ pathParams: { jobID: "nonexistent" } });
} catch (e) {
  if (e instanceof NotFoundError) console.log("Not found:", e.message);
  if (e instanceof RateLimitedError) console.log("Rate limited:", e.message);
}
```

| Error                | HTTP Status | Description             |
| -------------------- | ----------- | ----------------------- |
| `TransportError`     | —           | Network failure         |
| `DecodeError`        | —           | JSON decode failure     |
| `ValidationError`    | —           | Config/input validation |
| `UnauthorizedError`  | 401, 403    | Auth failure            |
| `NotFoundError`      | 404         | Resource not found      |
| `ConflictError`      | 409         | Duplicate/conflict      |
| `RateLimitedError`   | 429         | Rate limit exceeded     |
| `ApiError`           | other       | Generic HTTP error      |
| `TimeoutError`       | —           | Polling timeout         |
| `DagValidationError` | —           | Invalid workflow DAG    |
