Skip to main content
The TypeScript SDK generator creates modern TypeScript client libraries from your contract definitions. The generated code works across Node.js, Bun, and Deno without modifications, uses native fetch, and provides full type inference with a resource-based developer experience.

Key Features

  • Runtime Agnostic: Works on Node.js 18+, Bun, and Deno
  • Zero Dependencies: Uses native fetch API only
  • TypeScript-First: Full type inference and autocompletion
  • ES Modules: Modern JavaScript with async/await
  • SSE Streaming: First-class AsyncIterable streaming
  • npm-Ready: Includes package.json and tsconfig.json

Quick Start

Step 1: Define Your Contract

# api.yaml
name: TodoAPI
description: Todo list API

defaults:
  base_url: http://localhost:8080

resources:
  - name: todos
    description: Manage todo items
    methods:
      - name: create
        input: CreateInput
        output: Todo
        http:
          method: POST
          path: /todos
      - name: list
        output: ListOutput
        http:
          method: GET
          path: /todos
      - name: get
        input: GetInput
        output: Todo
        http:
          method: GET
          path: /todos/{id}
      - name: delete
        input: DeleteInput
        http:
          method: DELETE
          path: /todos/{id}

types:
  - name: Todo
    kind: struct
    fields:
      - name: id
        type: string
      - name: title
        type: string
      - name: done
        type: bool
  - name: CreateInput
    kind: struct
    fields:
      - name: title
        type: string
  - name: GetInput
    kind: struct
    fields:
      - name: id
        type: string
  - name: DeleteInput
    kind: struct
    fields:
      - name: id
        type: string
  - name: ListOutput
    kind: struct
    fields:
      - name: items
        type: "[]Todo"
      - name: total
        type: int

Step 2: Generate the SDK

mizu contract gen api.yaml --client --lang typescript --output ./sdk/typescript --package todoclient --version 1.0.0

Step 3: Install and Use

# Install dependencies and build
cd sdk/typescript
npm install
npm run build
import { Client } from './dist/index.js';

// Create a client
const client = new Client({ baseURL: 'http://localhost:8080' });

// Create a todo
const todo = await client.todos.create({ title: 'Learn Mizu TypeScript SDK' });
console.log(`Created: ${todo.id}`);

// List all todos
const result = await client.todos.list();
console.log(`Total: ${result.total} todos`);

// Get a specific todo
const fetched = await client.todos.get({ id: todo.id });
console.log(`Got: ${fetched.id} - ${fetched.title}`);

// Delete
await client.todos.delete({ id: todo.id });
console.log('Deleted successfully');

Generated Code Structure

sdk/typescript/
├── package.json
├── tsconfig.json
└── src/
    ├── index.ts
    ├── _client.ts
    ├── _types.ts
    ├── _resources.ts
    └── _streaming.ts

package.json

{
  "name": "todoclient",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsc",
    "typecheck": "tsc --noEmit"
  },
  "devDependencies": {
    "typescript": "^5.0.0"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2020", "DOM"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"]
}

Client Configuration

Creating a Client

import { Client } from 'todoclient';

// Basic client
const client = new Client({ baseURL: 'http://localhost:8080' });

// With authentication
const client = new Client({
  apiKey: 'your-api-key',
  baseURL: 'http://localhost:8080',
});

// With all options
const client = new Client({
  apiKey: 'your-api-key',
  baseURL: 'http://localhost:8080',
  timeout: 30000,           // 30 seconds
  maxRetries: 3,
  defaultHeaders: {
    'X-Custom-Header': 'value',
  },
});

Configuration Options

OptionTypeDefaultDescription
apiKeystring?undefinedAuthentication token
baseURLstring?Service defaultAPI base URL
timeoutnumber?60000Request timeout in milliseconds
maxRetriesnumber?2Maximum retry attempts
defaultHeadersRecord<string, string>?{}Headers for all requests

Accessing Config

const client = new Client({ baseURL: 'http://localhost:8080' });

// Access current configuration
console.log(client.config.baseURL);  // "http://localhost:8080"
console.log(client.config.timeout);  // 60000

Type System

Type Mapping Reference

Contract TypeTypeScript Type
stringstring
boolboolean
int, int8-int64number
uint, uint8-uint64number
float32, float64number
time.Timestring (ISO 8601)
json.RawMessageunknown
anyunknown
[]TT[]
map[string]TRecord<string, T>

Interface Types

Contract struct types generate TypeScript interfaces:
export interface Todo {
  id: string;
  title: string;
  done: boolean;
  createdAt: string;  // ISO 8601 date string
}

Optional and Nullable Fields

Contract DefinitionTypeScript Type
Required fieldT
optional: trueT? (optional property)
nullable: trueT | null
Both optional and nullable(T | null)?
Example:
export interface UpdateInput {
  title: string;            // required
  done?: boolean;           // optional
  description?: string | null;  // optional and nullable
}

Enum Fields

Enum fields generate as union types:
export interface Task {
  /** One of: "pending", "active", "completed" */
  status: "pending" | "active" | "completed";
}

Const Fields

Const fields generate as literal types:
export interface TextContent {
  type: "text";  // always "text"
  content: string;
}

Array and Record Types

// Contract: kind: slice, elem: Todo
export type TodoList = Todo[];

// Contract: kind: map, elem: string
export type Metadata = Record<string, string>;

Union Types (Discriminated)

Union types generate as TypeScript union types:
export type ContentPart = ContentPartText | ContentPartImage;

// Type guards
export function isContentPartText(v: ContentPart): v is ContentPartText {
  return v.type === "text";
}

export function isContentPartImage(v: ContentPart): v is ContentPartImage {
  return v.type === "image";
}
Usage:
function handleContent(part: ContentPart) {
  if (isContentPartText(part)) {
    console.log("Text:", part.content);
  } else if (isContentPartImage(part)) {
    console.log("Image:", part.url);
  }
}

// Or with type narrowing
function handleContent(part: ContentPart) {
  switch (part.type) {
    case "text":
      console.log("Text:", part.content);
      break;
    case "image":
      console.log("Image:", part.url);
      break;
  }
}

Resources and Methods

Resource Pattern

Each contract resource becomes a property on the client:
client.todos      // TodosResource
client.users      // UsersResource

Method Signatures

Methods use camelCase naming:
Contract MethodTypeScript Signature
Createcreate(input: CreateInput): Promise<Todo>
Listlist(): Promise<ListOutput>
Getget(input: { id: string }): Promise<Todo>
Deletedelete(input: { id: string }): Promise<void>

Calling Methods

// Create with input object
const todo = await client.todos.create({ title: 'My task' });

// List (no input)
const result = await client.todos.list();

// Get with ID
const fetched = await client.todos.get({ id: 'todo-123' });

// Delete
await client.todos.delete({ id: 'todo-123' });

Streaming (SSE)

For methods with streaming support, the SDK provides AsyncIterable streams:

Basic Streaming

const stream = client.responses.stream({
  model: 'gpt-4',
  input: 'Hello!',
});

for await (const event of stream) {
  if (event.type === 'text_delta') {
    process.stdout.write(event.text);
  } else if (event.type === 'completed') {
    console.log('\n--- Done ---');
  }
}

Stream with Cancellation

const stream = client.responses.stream({
  model: 'gpt-4',
  input: 'Write a long story',
});

// Cancel after 5 seconds
setTimeout(() => {
  stream.controller.abort();
}, 5000);

try {
  for await (const event of stream) {
    console.log(event);
  }
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Stream cancelled');
  } else {
    throw error;
  }
}

Collecting Stream Events

// Collect all events into an array
const events: ResponseEvent[] = [];
for await (const event of client.responses.stream(input)) {
  events.push(event);
}

// Or with Array.fromAsync (Node.js 22+)
const events = await Array.fromAsync(client.responses.stream(input));

Stream Type

The stream object implements AsyncIterable and has an AbortController:
interface Stream<T> extends AsyncIterable<T> {
  readonly controller: AbortController;
}

Error Handling

Error Types

The SDK defines three error classes:
export class SDKError extends Error {
  name = "SDKError";
}

export class APIConnectionError extends SDKError {
  name = "APIConnectionError";
}

export class APIStatusError extends SDKError {
  name = "APIStatusError";
  readonly status: number;
  readonly body: unknown;
}

Handling Errors

import { Client, APIStatusError, APIConnectionError } from 'todoclient';

const client = new Client({ baseURL: 'http://localhost:8080' });

try {
  const todo = await client.todos.get({ id: 'nonexistent' });
} catch (error) {
  if (error instanceof APIStatusError) {
    switch (error.status) {
      case 404:
        console.log('Todo not found');
        break;
      case 401:
        console.log('Unauthorized');
        break;
      case 400:
        console.log(`Bad request: ${error.message}`);
        break;
      default:
        console.log(`API error ${error.status}: ${error.message}`);
    }
  } else if (error instanceof APIConnectionError) {
    console.log(`Connection failed: ${error.message}`);
  } else {
    throw error;
  }
}

Automatic Retries

The client automatically retries failed requests:
const client = new Client({
  baseURL: 'http://localhost:8080',
  maxRetries: 5,  // Retry up to 5 times
});
Retries are attempted for:
  • Network errors
  • Timeout errors
Note: 4xx and 5xx errors are NOT retried (they throw immediately).

Advanced Usage

Custom Headers

const client = new Client({
  baseURL: 'http://localhost:8080',
  defaultHeaders: {
    'X-Request-ID': 'abc-123',
    'X-API-Version': '2024-01',
  },
});

Authentication Modes

The SDK supports different authentication modes:
// Bearer token (default)
const client = new Client({ apiKey: 'your-token' });
// Sends: Authorization: Bearer your-token

// Basic auth (if service configured with auth: basic)
const client = new Client({ apiKey: 'base64-credentials' });
// Sends: Authorization: Basic base64-credentials

// No auth (if service configured with auth: none)
const client = new Client({});
// Sends no Authorization header

Timeout Configuration

// Set timeout in milliseconds
const client = new Client({
  baseURL: 'http://localhost:8080',
  timeout: 30000,  // 30 seconds
});

Using with Different Runtimes

The SDK works across all modern JavaScript runtimes:
// Requires Node.js 18+ for native fetch
import { Client } from 'todoclient';

const client = new Client({ baseURL: 'http://localhost:8080' });
const todos = await client.todos.list();

Complete Example

Server

// main.go
package main

import (
    "github.com/go-mizu/mizu"
    contract "github.com/go-mizu/mizu/contract/v2"
    "github.com/go-mizu/mizu/contract/v2/transport/rest"

    "yourapp/todo"
)

func main() {
    impl := todo.NewService()
    svc := contract.Register[todo.API](impl,
        contract.WithDefaultResource("todos"),
        contract.WithDefaults(&contract.Defaults{
            BaseURL: "http://localhost:8080",
        }),
    )

    app := mizu.New()
    rest.Mount(app.Router, svc)
    app.Listen(":8080")
}

Client Usage

// example.ts
import { Client } from 'todoclient';

async function main() {
  const client = new Client({ baseURL: 'http://localhost:8080' });

  // Create a todo
  const todo = await client.todos.create({ title: 'Buy groceries' });
  console.log(`Created: ${todo.id}`);

  // List all todos
  const result = await client.todos.list();
  console.log(`\nAll todos (${result.total} total):`);
  for (const t of result.items) {
    const status = t.done ? 'x' : ' ';
    console.log(`  [${status}] ${t.id}: ${t.title}`);
  }

  // Get a specific todo
  const fetched = await client.todos.get({ id: todo.id });
  console.log(`\nFetched: ${fetched.title}`);

  // Delete
  await client.todos.delete({ id: todo.id });
  console.log(`\nDeleted: ${todo.id}`);
}

main().catch(console.error);

Streaming Example

// streaming-example.ts
import { Client } from 'chatclient';

async function main() {
  const client = new Client({
    apiKey: process.env.API_KEY,
    baseURL: 'http://localhost:8080',
  });

  console.log('Streaming response:\n');

  const stream = client.chat.stream({
    model: 'gpt-4',
    messages: [
      { role: 'user', content: 'Write a haiku about programming' },
    ],
  });

  for await (const event of stream) {
    switch (event.type) {
      case 'content_delta':
        process.stdout.write(event.delta);
        break;
      case 'done':
        console.log('\n\n--- Stream complete ---');
        break;
    }
  }
}

main().catch(console.error);

Installation and Distribution

Installing Locally

cd sdk/typescript
npm install
npm run build

# Link for local development
npm link

# In another project
npm link todoclient

Publishing to npm

cd sdk/typescript

# Build
npm run build

# Publish
npm publish

# Or with a specific tag
npm publish --tag beta

Installing from Git

npm install github:yourorg/yourrepo#main/sdk/typescript

Using Without npm

The generated code can be used directly in Deno or Bun without npm:
// Deno - import from URL
import { Client } from 'https://raw.githubusercontent.com/yourorg/yourrepo/main/sdk/typescript/src/index.ts';

// Or copy the src/ folder directly
import { Client } from './sdk/index.ts';

See Also