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.
Each step.run executes exactly once. Crash mid-run, deploy new code — the run resumes where it left off, completed steps are never repeated.
step.sleep("2d") holds no server hostage. step.waitForEvent parks a run until an approval, payment or webhook arrives — with a timeout.
Trigger functions on a schedule — 5-field cron or 6-field with seconds. No double-fires, no catch-up storms after downtime.
Concurrency limits (exact, per key), priority, throttle, rate-limit, debounce and batching — declared per function, enforced in the queue.
Every app × environment (dev/acc/prod) is a fully isolated workspace with its own signing key. Prod actions require a confirming second click.
Live trace waterfalls split durable-time vs app-time per step. Metrics, backlog history, rerun, rerun-from-step and cancel — updates pushed over SSE.
State, queue (SKIP LOCKED), timers and history in one database. Steps persist and schedule in a single transaction — no dual writes, no Redis.
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.
Graceful drain on shutdown, worker processes that scale horizontally, admin-token auth for production dashboards, HMAC on every hop.
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 };
},
});$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.
Ships with an embedded Postgres — the first run downloads the binary, your data persists in ~/.durable/pgdata. No Docker, no config.