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.
| Option | Default | Value |
|---|---|---|
port | 3000 | The port |
hostname | 127.0.0.1 | The bind address |
idleTimeout | 0 | 0 keeps parked long polls open |
gracefulShutdownTimeout | None | A 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.
| Option | Default | Value |
|---|---|---|
client | (required) | Options for the Bun SQLite client, such as filename |
repairIntervalMs | 1000 | The longest delay before a wake from another process |
transactionRetryAttempts | 16 | Attempts for a busy transaction |
transactionRetryDelayMs | 25 | The 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.
| Scope | Belongs to one |
|---|---|
| A port | Process |
| A projection runner | Process, for each key |
A CommitBoundary transaction | Process |
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.