Skip to content
✨ Markdown

TypeScript client

Use @durable-streams/client when you want direct read and write access to Durable Streams from TypeScript.

It gives you:

  • stream() for fetch-like reads
  • DurableStream for create, append, read, close, and delete
  • IdempotentProducer for exactly-once writes with batching and retries

Key features

  • Exactly-once writes with IdempotentProducer
  • Automatic batching and pipelining for high-throughput producers
  • Streaming reads with promise helpers, ReadableStreams, and subscribers
  • Offset-based resumability and configurable live modes
  • Works with JSON, text, and raw byte streams

Install

bash
npm install @durable-streams/client

Read-only API

The stream() function is the fetch-like API for consuming streams:

typescript
import { stream } from "@durable-streams/client"

const res = await stream<{ message: string }>({
  url: "https://streams.example.com/my-account/chat/room-1",
  offset: savedOffset,
  live: true,
})

const items = await res.json()
console.log(items)

Use stream() when your app only needs to consume a stream.

StreamResponse helpers

StreamResponse supports multiple consumption patterns:

typescript
const bytes = await res.body()
const items = await res.json()
const text = await res.text()

const byteStream = res.bodyStream()
const jsonStream = res.jsonStream()
const textStream = res.textStream()

const unsubscribe = res.subscribeJson(async (batch) => {
  await processBatch(batch.items)
})

Save the returned offset from subscriber batches if you want to resume from the same place later.

Exactly-once writes

For reliable, high-throughput writes with exactly-once semantics, use IdempotentProducer:

typescript
import { DurableStream, IdempotentProducer } from "@durable-streams/client"

const stream = await DurableStream.create({
  url: "https://streams.example.com/events",
  contentType: "application/json",
})

const producer = new IdempotentProducer(stream, "event-processor-1", {
  autoClaim: true,
  onError: (err) => console.error("Batch failed:", err),
})

for (const event of events) {
  producer.append(event)
}

await producer.flush()
await producer.close()

This is the recommended write path when you need safe retries and duplicate prevention.

Read/write API

Use DurableStream when you want a persistent handle for create, append, and read operations:

typescript
import { DurableStream } from "@durable-streams/client"

const handle = await DurableStream.create({
  url: "https://streams.example.com/my-account/chat/room-1",
  contentType: "application/json",
  ttlSeconds: 3600,
})

await handle.append(JSON.stringify({ type: "message", text: "Hello" }))

const res = await handle.stream<{ type: string; text: string }>()
res.subscribeJson(async (batch) => {
  for (const item of batch.items) {
    console.log(item.text)
  }
})

Live modes

  • true uses the default live behavior for the stream type
  • false reads catch-up data only
  • "sse" forces Server-Sent Events
  • "long-poll" forces long-polling

When to use it

  • Use the TypeScript client when you are building directly on the protocol.
  • Use JSON mode when your stream payloads are structured messages.
  • Use Vercel AI SDK or TanStack AI when you want higher-level AI integrations.

More