Skip to content

Setup and execution

  1. Add the workspace package

    Terminal window
    pnpm --filter <consumer-workspace> add '@repo/durability@workspace:*'
  2. Create a SQLite Durable Object migration

    {
    "migrations": [
    { "tag": "v1", "new_sqlite_classes": ["ImageJobs"] }
    ]
    }
  3. Define typed handlers

    import {
    createDurability,
    type DurableHandler,
    } from '@repo/durability';
    const handlers = {
    sendEmail: (async ({ id, payload, attempt, signal }) => {
    const response = await fetch(payload.url, {
    method: 'POST',
    body: JSON.stringify(payload.message),
    headers: { 'Idempotency-Key': id },
    signal,
    });
    return { attempt, messageId: await response.text() };
    }) satisfies DurableHandler<
    { url: string; message: string },
    { attempt: number; messageId: string }
    >,
    };
  4. Create the runtime and delegate the alarm

    private readonly durability = createDurability(this.ctx, handlers);
    alarm(alarmInfo?: AlarmInvocationInfo) {
    return this.durability.alarm(alarmInfo);
    }

Immediate operations are recovered and executed by alarms:

await this.durability.sendEmail({
id: `welcome:${userId}`,
payload: { url, message },
});

Timer-backed background operations use the same handler and result API:

await this.durability.background.sendEmail({
id: `digest:${userId}`,
payload: { url, message },
});

The enqueue promise confirms transactional registration, not handler completion.

const result = await this.durability.sendEmail.getResult(`welcome:${userId}`);
switch (result.status) {
case 'not_found':
break;
case 'pending':
console.log(result.attempt, result.nextAttemptAt, result.lastError);
break;
case 'failed':
console.error(result.error.name, result.error.message);
break;
case 'completed':
console.log(result.result.messageId);
break;
}

Looking up an ID registered for a different operation throws DuplicateDurableCallError, preventing a result from being interpreted as the wrong type.

createDurability migrates to the latest package schema. Migrations are tracked in durability_migrations, and calls live in durability_calls.

import { migrateDurability } from '@repo/durability';
migrateDurability(this.ctx, 'durability_0001_create_calls');
migrateDurability(this.ctx, null);