Basic usage
Compose a Durable Streams HTTP server with @streamsy/core, then choose a durable storage backend.
A Streamsy server is composed from three pieces:
- a
StorageAdapterfrom a storage package (where messages are persisted); - a protocol facade from
createStreamProtocol({ storage: { adapter } })(the Durable Streams operations); - an HTTP facade from
createHttpHandler({ protocol, pathPrefix })(serves the protocol over HTTP).
The protocol and HTTP facades never change — only the adapter you pass decides where data lives.
Install
Streamsy targets the Bun runtime. Start with the core package, which bundles the in-memory adapter:
bun add @streamsy/coreCompose the server
import { createHttpHandler, createMemoryStorageAdapter, createStreamProtocol } from "@streamsy/core";
const adapter = createMemoryStorageAdapter();
const protocol = createStreamProtocol({ storage: { adapter } });
const handler = createHttpHandler({ protocol, pathPrefix: "/" });
Bun.serve({ fetch: (request) => handler.fetch(request) });The server now speaks the Durable Streams HTTP protocol on the configured pathPrefix. The in-memory adapter keeps everything in process — ideal for development, examples, and conformance testing, but not durable across restarts.
Use the protocol directly
You can also drive the protocol in-process, without HTTP — useful for tests, scripts, and server-side materializers:
const created = await protocol.create("events", { contentType: "application/json" });
if (created.status === "created") {
await created.stream.append({ data: new TextEncoder().encode('{"hello":"world"}') });
const read = await created.stream.read();
// read.status === "ok" -> read.messages
}Results are discriminated unions keyed on status ("created", "exists", "ok", "not-found", "conflict", …); narrow on status before using a result.
Choose a durable backend
To persist data, swap createMemoryStorageAdapter() for a durable adapter. Nothing else in the composition changes.
Bun SQLite — @streamsy/storage-sqlite
File-backed persistence on bun:sqlite, with automatic migrations, in-process mutation locking, live-read notification, and lazy expiry. Requires the Bun runtime.
bun add @streamsy/storage-sqliteimport { createHttpHandler, createStreamProtocol } from "@streamsy/core";
import { createSqliteStorageAdapter } from "@streamsy/storage-sqlite";
const adapter = createSqliteStorageAdapter({ filename: "streams.db" }); // omit for :memory:
const protocol = createStreamProtocol({ storage: { adapter } });
const handler = createHttpHandler({ protocol, pathPrefix: "/" });
Bun.serve({ fetch: (request) => handler.fetch(request) });
// adapter.close() closes the underlying database connection.Cloudflare Durable Object — @streamsy/storage-durable-object
SQLite-backed persistence inside a Durable Object, with long-polling, SSE, and TTL/expiry. Each stream id maps to one Durable Object instance.
bun add @streamsy/storage-durable-objectimport { createHttpHandler, createStreamProtocol } from "@streamsy/core";
import {
createDurableObjectStorageAdapter,
DurableObjectStreamStorage,
} from "@streamsy/storage-durable-object";
// Re-export the storage class so Wrangler can bind it as a Durable Object.
export { DurableObjectStreamStorage };
interface Env {
STREAMS: DurableObjectNamespace<DurableObjectStreamStorage>;
}
export default {
fetch(request: Request, env: Env) {
const adapter = createDurableObjectStorageAdapter({ namespace: env.STREAMS });
const protocol = createStreamProtocol({ storage: { adapter } });
const handler = createHttpHandler({ protocol, pathPrefix: "/" });
return handler.fetch(request);
},
};Bind STREAMS to the DurableObjectStreamStorage class in your Wrangler config (with a SQLite-backed migration). The adapter routes each stream id to its Durable Object via namespace.idFromName(streamId).
Typed layers
Most applications don't work with raw bytes. Two packages wrap the protocol factory with typed facades:
- JSON mode (
@streamsy/json) — typed JSON messages validated through a codec or Standard Schema. - Durable State (
@streamsy/state) — typed change/control messages over named collections.
Both accept the same protocol object returned by createStreamProtocol, so you layer them on top of any storage backend.