/** * PostgresOutboxStore — durable OutboxStore adapter backed by PostgreSQL. * * Companion schema: src/manifest/outbox/stores/postgres.sql. * * Concurrency model: `claim` uses `FOR UPDATE SKIP LOCKED` so concurrent * dispatcher workers receive disjoint batches without lock contention. * See https://www.postgresql.org/docs/current/sql-select.html § "The * Locking Clause" for the official semantics. * * Transactional enqueue: when callers pass a PoolClient via `tx`, the * INSERT runs inside that transaction so state mutation and outbox * persistence commit atomically. This is the load-bearing piece of the * "transactional outbox" pattern. Without a `tx`, enqueue runs on its * own pool connection — durable but NOT atomic w.r.t. mutation. * * Idempotency: ON CONFLICT (entry_id) DO NOTHING — a retried enqueue with * the same entryId is silently ignored. * * DO NOT import this file in browser code — it requires `pg`. */ import type { Pool } from 'pg'; import type { OutboxEntry, OutboxStore } from '../outbox-store'; export interface PostgresOutboxStoreOptions { /** A pg Pool. The store does NOT own the pool's lifecycle. */ pool: Pool; /** Table name override. Default: `manifest_outbox_entries`. */ tableName?: string; /** * When true, project `event.subject.entity` and `event.subject.id` into * the `subject_entity` and `subject_id` columns at enqueue time. Requires * the optional columns from the companion schema (postgres.sql). Default: false. */ projectSubject?: boolean; } export declare class PostgresOutboxStore implements OutboxStore { private pool; private tableName; private projectSubject; constructor(opts: PostgresOutboxStoreOptions); /** * Enqueue entries. If `tx` is a PoolClient bound to an open transaction, * the INSERT participates in that transaction (the transactional outbox * guarantee). Otherwise, the INSERT runs on a fresh pool connection. */ enqueue(entries: OutboxEntry[], tx?: unknown): Promise; /** * Claim up to `batchSize` pending entries for delivery. * * Atomicity guarantee: a single UPDATE … WHERE entry_id IN (SELECT … FOR * UPDATE SKIP LOCKED) runs in one implicit transaction. The inner SELECT * holds row locks on the matching rows until the outer UPDATE commits, so * two concurrent workers calling `claim` cannot lock the same row. * * Re-claim safety: after commit, the row's `claimed_at` is non-NULL, and * the WHERE clause filters `claimed_at IS NULL` — so even though `status` * is still 'pending', a follow-up `claim` call will not return the same * row. The dispatcher worker MUST eventually call `markDelivered` or * `markFailed` to flip status off 'pending'. See § "Crash Recovery" in * docs/spec/adapters.md for the documented stale-claim recovery path * (a worker that crashes after claiming but before marking leaves the * row stuck until an operator resets `claimed_at`). * * Increments `attempts` and stamps `claimed_at = NOW()` on every claim. */ claim(batchSize: number): Promise; /** * Reset `claimed_at` to NULL for the given entry ids without changing * `status`. Use after a dispatcher worker crash to re-enqueue stale * claims for re-delivery. Not part of the OutboxStore contract — exposed * here so operators have a documented recovery path. Callers MUST be * confident the worker is dead; releasing a claim held by a live worker * can produce duplicate delivery attempts. */ releaseStaleClaims(entryIds: string[]): Promise; /** Mark entries delivered. Idempotent — repeated calls have no effect. */ markDelivered(entryIds: string[]): Promise; /** Mark entries failed and record the last error message. */ markFailed(entryIds: string[], error: string): Promise; } //# sourceMappingURL=postgres.d.ts.map