Quick start
Build and run a contracted workflow in one file. These examples use Bun 1.3, the runtime used by the SDK's own CI and executable Workflow App runners.
Install
Create an empty TypeScript project and install the beta package:
mkdir hello-workflow
cd hello-workflow
bun init -y
bun add @dromio/workflow@^0.2.5 zod@^4.1.12
@dromio/workflow is an ESM package. Zod 4 is installed directly because the
example imports it for runtime contracts. Pin the beta SDK version so upgrades
are deliberate.
Define a step and workflow
Create hello.ts:
import { step, workflow } from "@dromio/workflow";
import { z } from "zod";
const greet = step({
id: "hello.greet",
input: {name: z.string().min(1)},
output: {greeting: z.string().min(1)},
run: ({input}) => ({greeting: `Hello, ${input.name}!`}),
});
export const helloWorkflow = workflow({
catalog: [greet],
document: {
id: "hello-workflow",
version: 1,
trigger: {
id: "request",
type: "manual",
input: {name: {jsonSchema: {type: "string", minLength: 1}}},
},
nodes: [{id: "greet", catalogItemId: greet.id}],
edges: [
{id: "request-to-greet", source: "request", target: "greet"},
{id: "greet-to-result", source: "greet", target: "result"},
],
end: {
id: "result",
type: "result",
output: {greeting: {jsonSchema: {type: "string", minLength: 1}}},
},
},
input: {name: z.string().min(1)},
output: {greeting: z.string().min(1)},
});
if (import.meta.main) {
const session = await helloWorkflow.start({name: "Ada"});
if (session.status !== "completed") {
throw new Error(`Unexpected run status: ${session.status}`);
}
console.log(session.state.greeting);
}
The workflow document is intentionally explicit: the trigger and result are boundaries, the node points to a catalog item, and edges define execution order. Step logic stays in TypeScript. The JSON Schemas make the serialized document portable; the matching Zod sources preserve exact TypeScript inference and runtime parsing. When both are present, their keys and accepted values must agree. Workflows covers this boundary in detail.
Run it
bun run hello.ts
Expected output:
Hello, Ada!
workflow.start() validates the trigger input, executes the graph, validates
step outputs, and returns a session. The session contains status, state,
events, checkpoints, runId, and methods for waiting, resuming, cancelling,
and rerunning.
Watch lifecycle events
Pass an event sink when starting a workflow:
const session = await helloWorkflow.start({name: "Ada"}, {
onEvent(event) {
console.log(event.index, event.type, event.stepId ?? "run");
},
});
Events are also retained in session.events. Use the callback to stream them
to logs or telemetry and the retained array for inspection after the run.
Add an executable surface
Keep the direct start() example as hello.ts. Create a separate cli.ts when
you want a packaged CLI, TUI, GUI, SVG, or server adapter:
import { runWorkflowCli, workflowApp } from "@dromio/workflow";
import { helloWorkflow } from "./hello.js";
const app = workflowApp({
id: "hello-app",
workflows: [helloWorkflow],
});
await runWorkflowCli(app);
The prompt-based app runners require exactly one trigger input key, which this workflow has. Run it with:
bun run cli.ts Ada
import.meta.main is false when cli.ts imports the workflow, so the direct
example does not run again. After the script path and any runner flags, the
remaining positional arguments (Ada here) are joined into one prompt and
mapped to the workflow's single trigger input key. This starts only the
CLI-driven run.
Next, read Steps to choose primitives, Workflows for document rules, and Execution and durability before adding waits or external delivery.