Quick Start
A Yieldstar application has three parts: workflows that define the work, a worker that executes it, and an app that triggers it. This guide sets up all three with SQLite persistence.
Install
bun add yieldstar @yieldstar/core @yieldstar/worker-invoker @yieldstar/sqlite-runtime pinoThis quick start uses Bun. For Node, use the node:sqlite connector guide; the shared runtime APIs are identical.
1. Define a workflow
A workflow is a generator function that yields steps. Each step is a checkpoint, the result of which is persisted by the runtime and can be retrieved if the workflow is paused or needs to be re-run.
import { workflow, createWorkflowRouter } from "yieldstar";
import { SqliteEventLoop } from "@yieldstar/sqlite-runtime";
import { createSqliteDb } from "@yieldstar/sqlite-runtime/bun";
export const greet = workflow<{ name: string }, string>(async function* (step, event) {
const greeting = yield* step.run(() => `Hello, ${event.params.name}`);
yield* step.delay(1000);
return yield* step.run(() => `${greeting}!`);
});
export const router = createWorkflowRouter({ greet });
export type Router = typeof router;
export const db = createSqliteDb({ path: "./.db/local.sqlite" });
export const eventLoop = new SqliteEventLoop(db);createWorkflowRouter registers workflows by ID. Those IDs become the typed keys the SDK uses to trigger them.
2. Create the worker
The worker is a subprocess that runs workflow executions in isolation. It connects the WorkflowRunner to the SQLite heap, scheduler, and durable store, then listens for execution events over IPC.
import pino from "pino";
import { WorkflowRunner } from "@yieldstar/core";
import { createWorkflowWorker } from "@yieldstar/worker-invoker";
import {
SqliteHeapClient,
SqliteSchedulerClient,
SqliteStoreClient,
SqliteTaskQueueClient,
SqliteTimersClient,
} from "@yieldstar/sqlite-runtime";
import { router, db } from "./shared";
const logger = pino();
const schedulerClient = new SqliteSchedulerClient({
taskQueueClient: new SqliteTaskQueueClient(db),
timersClient: new SqliteTimersClient(db),
});
const runner = new WorkflowRunner({
router,
heapClient: new SqliteHeapClient(db),
schedulerClient,
storeClient: new SqliteStoreClient({ db, schedulerClient }),
logger,
});
createWorkflowWorker(runner, logger).listen();3. Trigger and await the result
The app entry point starts the event loop, creates an invoker that spawns worker subprocesses on demand, and uses the typed SDK to trigger workflows.
import pino from "pino";
import { createWorkflowInvoker } from "@yieldstar/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: "greet",
params: { name: "World" },
});
console.log(result); // "Hello, World!"
eventLoop.stop();4. Run it
bun app.tsThe workflow executes two steps with a 1-second delay between them.
The first step computes a greeting, the delay pauses execution and persists the resume timestamp to SQLite, and the second step appends punctuation.
The state lives in .db/local.sqlite.
On resume, completed steps replay from the heap without re-executing.