self-hosted · one Postgres · no Docker required

Workflows that survive anything,
written as ordinary code.

Durable replays memoized steps so each one runs exactly once — through crashes, deploys, retries, and week-long waits. Background jobs, crons, human-in-the-loop approvals and event-driven pipelines, without building queues, timers or state machines.

run trace · onboardingcompleted
load-user57ms
cooldown2d
await-approval4h
send-email6ms
durable (queue / sleep)your serverwaiting for event

Everything a workflow engine should do.
Nothing you have to build yourself.

Durable steps

Each step.run executes exactly once. Crash mid-run, deploy new code — the run resumes where it left off, completed steps are never repeated.

Sleep & wait for events

step.sleep("2d") holds no server hostage. step.waitForEvent parks a run until an approval, payment or webhook arrives — with a timeout.

Cron & scheduling

Trigger functions on a schedule — 5-field cron or 6-field with seconds. No double-fires, no catch-up storms after downtime.

Flow control, complete

Concurrency limits (exact, per key), priority, throttle, rate-limit, debounce and batching — declared per function, enforced in the queue.

Apps × environments

Every app × environment (dev/acc/prod) is a fully isolated workspace with its own signing key. Prod actions require a confirming second click.

Realtime dashboard

Live trace waterfalls split durable-time vs app-time per step. Metrics, backlog history, rerun, rerun-from-step and cancel — updates pushed over SSE.

One Postgres, zero infra

State, queue (SKIP LOCKED), timers and history in one database. Steps persist and schedule in a single transaction — no dual writes, no Redis.

TypeScript & PHP SDKs

The engine never runs your code — it calls your app over signed HTTP. SDKs for TS and PHP; the wire protocol is small enough to port in an afternoon.

Ops-grade by default

Graceful drain on shutdown, worker processes that scale horizontally, admin-token auth for production dashboards, HMAC on every hop.

Plain functions. Durable execution.

TypeScript
const onboarding = createFunction({
  id: "onboarding",
  trigger: { event: "user.created" },
  concurrency: { limit: 2, key: "tenantId" },
  handler: async ({ event, step }) => {
    const user = await step.run("load-user", () =>
      db.users.find(event.data.id));

    await step.sleep("cooldown", "2d");   // survives deploys

    const ok = await step.waitForEvent("approve", {
      event: "approval.received",
      match: { userId: user.id },
      timeout: "7d",
    });

    await step.run("send-email", () => mail.send(user));
    return { approved: ok !== null };
  },
});
PHP
$onboarding = new DurableFunction(
  id: 'onboarding',
  trigger: ['event' => 'user.created'],
  handler: function (array $event, Step $step) {
    $user = $step->run('load-user', fn () =>
      loadUser($event['data']['id']));

    $step->sleep('cooldown', '2d');  // survives deploys

    $ok = $step->waitForEvent('approve', [
      'event'   => 'approval.received',
      'match'   => ['userId' => $user['id']],
      'timeout' => '7d',
    ]);

    $step->run('send-email', fn () => sendMail($user));
    return ['approved' => $ok !== null];
  },
);

Same replay model, same wire protocol. If the process dies between steps, the next invocation replays the memoized results in microseconds and continues exactly where it stopped.

Running in one command.

Ships with an embedded Postgres — the first run downloads the binary, your data persists in ~/.durable/pgdata. No Docker, no config.

$ npx @dicabrio/durable
[durable] embedded postgres on :5434
[durable] 14 migrations applied
[durable] service on http://localhost:3030