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/db

Define a Workflow

Create a workflow definition and a registry of workflows:

lib/workflows.ts
import { defineWorkflow, type WorkflowEvent, type WorkflowStep } from "@fragno-dev/workflows";

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:

lib/workflows-fragment.ts
import { defaultFragnoRuntime, instantiate } from "@fragno-dev/core";
import { type DatabaseAdapter } from "@fragno-dev/db";
import { createDurableHooksProcessor } from "@fragno-dev/db/dispatchers/node";
import { workflowsFragmentDefinition, workflowsRoutesFactory } from "@fragno-dev/workflows";
import { workflows } from "./workflows";

export function createWorkflowsFragmentServer(adapter: DatabaseAdapter<any>) {
  const runtime = defaultFragnoRuntime;

  const config = { workflows, runtime };
  const fragment = instantiate(workflowsFragmentDefinition)
    .withConfig(config)
    .withRoutes([workflowsRoutesFactory])
    .withOptions({ databaseAdapter: adapter })
    .build();

  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 a schema and run migrations with the Fragno CLI:

npx fragno-cli db generate lib/workflows-fragment.ts --format drizzle -o db/workflows.schema.ts
npx fragno-cli db migrate lib/workflows-fragment.ts

Test Workflows

Use the test harness to drive workflow ticks with a controllable clock and deterministic runtime:

lib/workflows.test.ts
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: "drizzle-pglite" },
  runtime,
});

const instanceId = await harness.createInstance("approval", {
  params: { requestId: "req_1", amount: 125 },
});

await harness.runUntilIdle();
await harness.sendEvent("approval", instanceId, {
  type: "approval",
  payload: { approved: true },
});

harness.clock.advanceBy("2 s");
await harness.runUntilIdle();

For higher-level scripting, see the Scenario DSL guide.

Drive the Runner

For external schedulers, run a durable hooks dispatcher (Node or Cloudflare DO) so hooks are processed when work is enqueued.

Use the CLI

The fragno-wf CLI uses the HTTP API to list workflows, inspect instances, and send events:

fragno-wf workflows list -b https://your-app.example.com/api/workflows

fragno-wf instances get -b https://your-app.example.com/api/workflows -w approval -i inst_123

Next Steps

  • Build a client or CLI on top of the HTTP API in the fragment.
  • Review workflow history and logs using the /history routes.