Quickstart
Install and configure the Workflows fragment.
Overview
The Workflows fragment provides durable, replayable workflow execution with steps, timers, retries, and external events. This quickstart walks through defining a workflow, wiring durable hooks, and exposing routes.
Installation
Install the fragment and database packages:
npm install @fragno-dev/workflows @fragno-dev/dbFor the test harness example, also install the Fragno test package:
npm install --save-dev @fragno-dev/testInstall the Workflows Agent Skill:
npx skills add https://github.com/rejot-dev/fragno --skill fragno-workflowsThen ask: "Use fragno-workflows to define and test a replay-safe workflow."
Define a Workflow
Create a workflow definition and a registry of workflows:
import {
defineWorkflow,
type WorkflowEvent,
type WorkflowStep,
} from "@fragno-dev/workflows/workflow";
type ApprovalParams = {
requestId: string;
amount: number;
};
type ApprovalEvent = { approved: boolean };
type FulfillmentEvent = { confirmationId: string };
export const ApprovalWorkflow = defineWorkflow(
{ name: "approval-workflow" },
async (event: WorkflowEvent<ApprovalParams>, step: WorkflowStep) => {
const approval = await step.waitForEvent<ApprovalEvent>("approval", {
type: "approval",
timeout: "15 min",
});
await step.sleep("cooldown", "2 s");
const fulfillment = await step.waitForEvent<FulfillmentEvent>("fulfillment", {
type: "fulfillment",
timeout: "15 min",
});
return { request: event.payload, approval, fulfillment };
},
);
export const workflows = {
approval: ApprovalWorkflow,
} as const;Optional: pass a Standard Schema as schema to validate params, and an outputSchema to type
output end-to-end.
import { z } from "zod";
const paramsSchema = z.object({ requestId: z.string(), amount: z.number() });
const outputSchema = z.object({ confirmationId: z.string() });
export const ApprovalWorkflow = defineWorkflow(
{ name: "approval-workflow", schema: paramsSchema, outputSchema },
async (event, step) => ({ confirmationId: "conf_123" }),
);Create the Fragment Server
Wire the durable hooks dispatcher and fragment definition:
import { defaultFragnoRuntime } from "@fragno-dev/core";
import { type DatabaseAdapter } from "@fragno-dev/db";
import { createDurableHooksProcessor } from "@fragno-dev/db/dispatchers/node";
import { createWorkflowsFragment } from "@fragno-dev/workflows";
import { workflows } from "./workflows";
export function createWorkflowsFragmentServer(adapter: DatabaseAdapter<any>) {
const fragment = createWorkflowsFragment(
{
workflows,
runtime: defaultFragnoRuntime,
},
{ databaseAdapter: adapter },
);
const dispatcher = createDurableHooksProcessor([fragment], {
pollIntervalMs: 2000,
});
dispatcher.startPolling();
process.on("SIGTERM", () => dispatcher.stopPolling());
return { fragment, dispatcher };
}The in-process dispatcher is ideal for local dev. For Cloudflare deployments use
@fragno-dev/db/dispatchers/cloudflare-do.
Mount the Routes
Mount the fragment using your framework adapter. See Integrating a Fragment for framework-specific examples.
Run Database Migrations
Generate SQL migrations and apply them with the Fragno CLI:
npx fragno-cli db generate lib/workflows-fragment.ts
npx fragno-cli db migrate lib/workflows-fragment.tsFor Drizzle or Prisma, generate the schema output and apply it with that ORM's tooling instead.
Test Workflows
Use the test harness to drive workflow ticks with a controllable clock and deterministic runtime:
import { buildDatabaseFragmentsTest } from "@fragno-dev/test";
import { createWorkflowsTestHarness, createWorkflowsTestRuntime } from "@fragno-dev/workflows/test";
import { workflows } from "./workflows";
const runtime = createWorkflowsTestRuntime({ startAt: 0, seed: 123 });
const harness = await createWorkflowsTestHarness({
workflows,
adapter: { type: "in-memory" },
testBuilder: buildDatabaseFragmentsTest(),
runtime,
autoTickHooks: false,
});
const instanceId = await harness.createInstance("approval", {
params: { requestId: "req_1", amount: 125 },
});
await harness.runUntilIdle({
workflowName: "approval-workflow",
instanceId,
reason: "create",
});
await harness.sendEvent("approval", instanceId, {
type: "approval",
payload: { approved: true },
});
await harness.runUntilIdle({
workflowName: "approval-workflow",
instanceId,
reason: "event",
});
harness.clock.advanceBy("2 s");
await harness.runUntilIdle({
workflowName: "approval-workflow",
instanceId,
reason: "wake",
});
await harness.test.cleanup();For focused harness tests and end-to-end scenarios, see Workflow Testing.
Drive the Runner
For external schedulers, run a durable hooks dispatcher (Node or Cloudflare DO) so hooks are processed when work is enqueued.
Next Steps
- Review the replay and idempotency guidance in Rules of Workflows.
- Publish progress and receive active-step events with Step Events & Emissions.
- Review workflow steps, events, and currently persisted emissions using the
/historyroute.