Skip to content

Transaction Hooks

withTransactionHooks buffers side effects (like notify events) during a transaction and flushes them only after the callback returns successfully. On error, all buffered side effects are discarded.

Pass a callback to withTransactionHooks. Flush and discard are managed automatically — no manual cleanup needed.

await withTransactionHooks(async (transactionHooks) =>
sql.begin(async (sql) => {
await client.startJobChain({ sql, transactionHooks, typeName: "send-email", input });
// If the transaction rolls back, no notifications are sent
}),
);

TransactionHooks is a generic container for named hooks with mutable state. It knows nothing about Queuert itself — consumers (client, worker) register their own hooks using symbol keys.

A hook definition (HookDefinition) contains mutable state, a flush function, an optional discard function, and an optional checkpoint function. Multiple hooks can be registered on the same TransactionHooks instance. During the transaction, code mutates hook state freely. After the outer callback completes successfully, each hook’s flush function is called with its accumulated state. If the callback throws, each hook’s discard function is called instead — no flush occurs.

TransactionHooks supports savepoints for partial rollback of buffered side effects. This is useful when a sub-operation within a transaction may fail without invalidating the entire transaction.

withSavepoint runs a function and automatically rolls back hook state on error:

await transactionHooks.withSavepoint(async (transactionHooks) => {
// buffer side effects here
// if this throws, only side effects buffered inside this callback are rolled back
});

Savepoints rely on the checkpoint callback in HookDefinition. When a savepoint is created, each registered hook’s checkpoint is called to capture its current state. On rollback, the returned function restores the hook state to that point.

Hooks are registered lazily via getOrInsert. This avoids a separate registration step — the hook is created on first access and reused on subsequent accesses within the same transaction.

withTransactionHooks manages this lifecycle automatically: it creates the TransactionHooks instance, passes it to the callback, and calls flush on success or discard on error. createTransactionHooks exposes the same lifecycle for manual control — the caller is responsible for calling flush() after commit and discard() on error.