Hosting

Bun

Run a Streamsy server on Bun with SQLite storage, a scoped listener, and clean shutdown.

import { Layer, Schema } from "effect";
import { BunRuntime } from "@effect/platform-bun";
import { Http, Streamsy } from "@streamsy/core";
import { Serve } from "@streamsy/serve";
import { listener } from "@streamsy/serve/bun";
import { BunStorage } from "@streamsy/storage/bun";

class Events extends Streamsy.json<Events>()("Game/Events", "events/:seat", {
  params: { seat: Schema.String },
  schema: Schema.String,
}) {}
const game = Streamsy.group(Events);

const App = Layer.mergeAll(
  Http.routes(game, { prefix: "/streams", access: "append" }),
  Serve.mount(Events, "/feed/:seat"),
);
const storage = BunStorage.layer(game, { client: { filename: "./streams.sqlite" } });

const Server = Serve.layer(App).pipe(
  Layer.provide(storage),
  Layer.provideMerge(listener({ port: 3000 })),
);

BunRuntime.runMain(Layer.launch(Server), { teardown: Serve.teardown });

The launched scope owns the listener, the storage, and every fiber. A projection or an outbox drain can be one more Layer in the same graph. Closing the scope is the shutdown.

Listener

listener(options) from @streamsy/serve/bun is a Bun HTTP server Layer.

OptionDefaultValue
port3000The port
hostname127.0.0.1The bind address
idleTimeout00 keeps parked long polls open
gracefulShutdownTimeoutNoneA bound on the wait for in-flight requests

A client abort interrupts a parked long poll and releases its subscription. A bind failure is a typed ServeError.

Clean shutdown

On SIGINT or SIGTERM, Serve.layer answers live reads before the routes close. Serve.teardown makes this clean stop exit 0. See Long-running hosts.

SQLite storage

BunStorage.layer(group, options) keeps streams in one SQLite file.

OptionDefaultValue
client(required)Options for the Bun SQLite client, such as filename
repairIntervalMs1000The longest delay before a wake from another process
transactionRetryAttempts16Attempts for a busy transaction
transactionRetryDelayMs25The delay between those attempts, in ms

The Layer creates the file and its parent folder if they do not exist. A read-only open, or create: false, creates nothing. The file uses WAL mode. The driver busy timeout is zero. Streamsy retries a busy transaction itself, so the event loop does not block.

The Layer gives SqlClient, CommitBoundary, Storage, and the stream services. See Commit with your own tables.

A new file uses the 0.4 format. A file in another format is refused and not changed.

Several processes

Several processes can share one file. Creates, conditional writes, and epoch fences hold across processes.

ScopeBelongs to one
A portProcess
A projection runnerProcess, for each key
A CommitBoundary transactionProcess

A commit in one process wakes followers in another process within repairIntervalMs.

Projection services

Compose CheckpointStore.layer and Transaction.layer from @streamsy/projection over this storage Layer with Layer.provideMerge. They provide CheckpointStore and Transaction on the same SQL owner. Commit.transaction commits SQL, output appends, and offsets together. See Checkpoints and generations.

On this page