Local Runtime (SQLite)
The local runtime executes workflows in Bun subprocesses with SQLite-backed persistence. It consists of three layers: a shared module (router + database), a worker process, and an application entry point.
Shared module
The shared module defines workflows, creates the database, and exports the router type:
shared.ts
import { workflow, createWorkflowRouter } from "yieldstar";
import { SqliteEventLoop, createSqliteDb } from "@yieldstar/bun-sqlite-runtime";
export const myWorkflow = workflow(async function* (step) {
const n = yield* step.run(() => 1);
yield* step.delay(1000);
return yield* step.run(() => n * 2);
});
export const router = createWorkflowRouter({ "my-workflow": myWorkflow });
export type Router = typeof router;
export const db = createSqliteDb({ path: "./.db/local.sqlite" });
export const eventLoop = new SqliteEventLoop(db);Worker process
The worker runs in a Bun subprocess spawned by the invoker. It wires the WorkflowRunner to the heap and scheduler, then listens for IPC messages:
worker.ts
import pino from "pino";
import { WorkflowRunner } from "@yieldstar/core";
import { createWorkflowWorker } from "@yieldstar/bun-worker-invoker";
import {
SqliteHeapClient,
SqliteSchedulerClient,
SqliteTaskQueueClient,
SqliteTimersClient,
} from "@yieldstar/bun-sqlite-runtime";
import { router, db } from "./shared";
const logger = pino();
const runner = new WorkflowRunner({
router,
heapClient: new SqliteHeapClient(db),
schedulerClient: new SqliteSchedulerClient({
taskQueueClient: new SqliteTaskQueueClient(db),
timersClient: new SqliteTimersClient(db),
}),
logger,
});
createWorkflowWorker(runner, logger).listen();Application entry point
The app creates the invoker, starts the event loop, and triggers workflows through the local SDK:
app.ts
import pino from "pino";
import { createWorkflowInvoker } from "@yieldstar/bun-worker-invoker";
import { createLocalSdk } from "yieldstar";
import { eventLoop } from "./shared";
import type { Router } from "./shared";
const logger = pino();
const workerPath = new URL("./worker.ts", import.meta.url).href;
const invoker = createWorkflowInvoker({ workerPath, logger });
eventLoop.start({ onNewEvent: invoker.execute, logger });
const sdk = createLocalSdk<Router>(invoker);
const result = await sdk.triggerAndWait({ workflowId: "my-workflow" });
console.log(result); // 2
eventLoop.stop();How it works
createWorkflowInvokerspawns a new Bun subprocess for each workflow execution.- The subprocess receives the execution event via IPC and runs the workflow generator.
- The generator replays cached steps from the SQLite heap and executes new ones.
- If the generator yields a delay or retry, the
WorkflowRunnercallsschedulerClient.requestWakeUp, which writes a timer to SQLite. - The
SqliteEventLooppolls the task queue every 10ms. When a timer fires, it enqueues the event and the invoker spawns a new subprocess to resume. - When the generator returns a
WorkflowResult, the subprocess sends it back via IPC, and theworkflowEndEmitterresolves the SDK promise.