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 }),);Use createTransactionHooks directly when your database client uses explicit
BEGIN/COMMIT/ROLLBACK rather than a callback-style transaction.
const { transactionHooks, flush, discard } = createTransactionHooks();const connection = await db.connect();try { await connection.query("BEGIN"); const result = await client.startJobChain({ connection, transactionHooks, typeName: "send-email", input, }); await connection.query("COMMIT"); await flush(); // Side effects fire only after commit return result;} catch (error) { await connection.query("ROLLBACK").catch(() => {}); await discard(); // Side effects discarded on error throw error;} finally { connection.release();}How It Works
Section titled “How It Works”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.
Savepoints
Section titled “Savepoints”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});createSavepoint returns a handle for fine-grained control:
const sp = transactionHooks.createSavepoint();try { // buffer side effects sp.release(); // keep changes} catch { sp.rollback(); // discard changes since the savepoint}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.