WorkflowIntroduction

Introduction

Get started with @visulima/workflow, a reusable, edge-ready durable workflow engine

Workflow

Reusable, ESM-only, edge-ready durable execution

@visulima/workflow lets you write a long-running process as a plain async function and makes it resumable: it survives process restarts, hour-long delays and waits for external events, replaying deterministically from an append-only history. It is the generic engine that powers higher-level features such as @visulima/notification's workflow steps, but it has no notification dependency and is useful on its own.

Why @visulima/workflow?

  • Infra-free - No dashboard, no SaaS account, no separate server process. Bring any store you already run.
  • Edge-ready - The engine core and the in-memory / unstorage stores are fetch + Web Crypto only, with zero Node built-ins, so they run unmodified on Cloudflare Workers, Vercel Edge, Deno and Bun.
  • Code-first - Workflows are ordinary async functions; durability comes from ctx.step / ctx.sleep / ctx.waitForEvent, not a YAML DSL or a visual editor.
  • Type-safe - Payloads are validated and typed via any Standard Schema validator (Zod, Valibot, ArkType).
  • Pluggable durability - A small WorkflowStore contract; an in-memory and an unstorage store ship in the box, and you can back runs with Redis, Postgres or a Durable Object.

How it works

You define a workflow and drive it through a runtime:

import { createRuntime, defineWorkflow } from "@visulima/workflow";
import { z } from "zod";

const onSignup = defineWorkflow({
    id: "welcome",
    payload: z.object({ userId: z.string(), email: z.string() }),
    run: async (ctx) => {
        await ctx.step("send-welcome", () => sendEmail(ctx.payload.email, "Welcome!"));
        await ctx.sleep("wait-a-day", { amount: 1, unit: "days" });

        const activated = await ctx.step("check-activation", () => isActivated(ctx.payload.userId));

        if (!activated) {
            await ctx.step("send-nudge", () => sendEmail(ctx.payload.email, "Need a hand?"));
        }
    },
});

const runtime = createRuntime({ workflows: [onSignup] });

const { runId, status } = await runtime.trigger(onSignup, { userId: "u_1", email: "a@b.com" });
// status === "suspended" — the run is sleeping for a day.

The engine re-runs the workflow body from the top on every activation; already-completed steps short-circuit to their recorded result, and the first unsatisfied sleep/waitForEvent suspends the run. See Durability & replay for the model.

Next steps

Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues