@roostjs/workflow
v0.2.0
Published
Durable execution on Cloudflare Workflows. Extends `WorkflowEntrypoint` with a clean base class, a typed client, and a saga pattern for compensating multi-step operations.
Readme
@roostjs/workflow
Durable execution on Cloudflare Workflows. Extends WorkflowEntrypoint with a clean base class, a typed client, and a saga pattern for compensating multi-step operations.
Part of Roost — the Laravel of Cloudflare Workers.
Installation
bun add @roostjs/workflowQuick Start
import { Workflow, WorkflowClient, Compensable } from '@roostjs/workflow';
import type { WorkflowEvent, WorkflowStep } from 'cloudflare:workers';
interface ProvisionParams {
orgId: string;
plan: string;
}
export class ProvisionOrg extends Workflow<Env, ProvisionParams> {
async run(event: WorkflowEvent<ProvisionParams>, step: WorkflowStep) {
const saga = new Compensable();
const org = await step.do('create-org', async () => {
const o = await createOrg(event.payload.orgId);
saga.register(() => deleteOrg(o.id));
return o;
});
await step.do('send-welcome-email', async () => {
await sendWelcome(org.id, event.payload.plan);
});
}
}
// Trigger from any Worker handler
const client = new WorkflowClient<ProvisionParams>(env.PROVISION_ORG);
const handle = await client.create({ params: { orgId: '123', plan: 'pro' } });
const status = await handle.status();Features
Workflowbase class extending CF'sWorkflowEntrypointwithfake()/ assert helpersWorkflowClienttyped wrapper around the CF Workflow bindingCompensablefor saga-style rollback: register compensation functions as you go, callcompensate()on failure to run them in reverseWorkflowErrorandNonRetryableErrorfor step-level error handlingWorkflow.fake()/Workflow.assertCreated()for unit testing without CF infrastructure
API
Workflow
abstract class Workflow<Env, TParams> extends WorkflowEntrypoint<Env, TParams> {
abstract run(event: WorkflowEvent<TParams>, step: WorkflowStep): Promise<unknown>
}Static testing helpers:
ProvisionOrg.fake()
ProvisionOrg.restore()
ProvisionOrg.assertCreated(id?) // asserts at least one (or specific) workflow was created
ProvisionOrg.assertNotCreated()WorkflowClient
const client = new WorkflowClient<TParams>(binding)
client.create({ id?, params }) // => WorkflowInstanceHandle
client.get(id) // => WorkflowInstanceHandle
client.terminate(id)WorkflowInstanceHandle exposes id, pause(), resume(), abort(reason?), and status() which returns { status, output?, error? }.
Possible status values: 'queued' | 'running' | 'paused' | 'complete' | 'errored' | 'terminated'.
Compensable (saga pattern)
const saga = new Compensable();
// Register compensations as each step succeeds
saga.register(async () => await rollbackStep());
// On failure, run all registered compensations in reverse order
await saga.compensate();Compensations run best-effort — a failing compensation does not block the others.
Errors
import { WorkflowError, NonRetryableError } from '@roostjs/workflow';
// Throw NonRetryableError inside a step to skip CF's retry logic
throw new NonRetryableError('Payment declined — not retrying');Documentation
Full documentation at roost.birdcar.dev/docs/reference/workflow
License
MIT
