import type { EventBus, EventEnvelope, EventMetadata, OutboxEventStore } from "@voyantjs/core"; import { type EventOutboxRow } from "./schema/infra/event_outbox.js"; import type { DrizzleClient } from "./types.js"; export interface DrainOutboxOptions { /** Max rows claimed per drain pass. Default 25. */ limit?: number; /** * How long a claimed row stays invisible to other drains. Must exceed * the worst-case delivery time of one event (all subscribers, each * bounded by the bus's per-handler timeout). A crashed claimer's rows * simply become due again after this window. Default 120s. */ visibilityTimeoutMs?: number; } export interface DrainOutboxResult { claimed: number; delivered: number; retried: number; deadLettered: number; } /** * Postgres-backed {@link OutboxEventStore} for `createEventBus`'s durable * emit path. `getDb` is called per operation so the store can be bound * to a per-request client (Workers) or a long-lived one (Node). */ export declare function createOutboxEventStore(getDb: () => DrizzleClient): OutboxEventStore; /** * Persist envelopes as pending outbox rows. Pass a transaction handle to * capture events atomically with the domain write ("transactional * outbox" proper): * * await db.transaction(async (tx) => { * await tx.insert(bookings).values(...) * await insertOutboxEvents(tx, [{ name: "booking.created", data, metadata }]) * }) * // post-commit: the drain (or a waitUntil kick) delivers. * * Duplicate `metadata.eventId`s are skipped (ON CONFLICT DO NOTHING) — * re-emits and webhook redeliveries capture once. Envelopes without an * eventId get one stamped here. */ export declare function insertOutboxEvents(db: DrizzleClient, envelopes: ReadonlyArray & { metadata?: EventMetadata; emittedAt?: string; }>): Promise; /** Mark a row delivered. */ export declare function completeOutboxEvent(db: DrizzleClient, id: string): Promise; /** * Record a failed delivery: reschedules with exponential backoff * (5s · 2^attempts, capped at 15min, ±20% jitter) or dead-letters as * `failed` once `max_attempts` is exhausted. Single statement — safe on * neon-http. Returns the resulting status when the row exists. */ export declare function failOutboxEvent(db: DrizzleClient, id: string, error: string): Promise<"pending" | "failed" | null>; /** * Atomically claim due pending rows: bumps `attempts` and pushes * `next_attempt_at` past the visibility timeout so concurrent drains * (and a crashed claimer's successor) never double-claim. One statement * (`FOR UPDATE SKIP LOCKED` inside the subquery) — safe on neon-http. */ export declare function claimDueOutboxEvents(db: DrizzleClient, options?: DrainOutboxOptions): Promise; /** Rebuild the bus envelope from a stored row. */ export declare function outboxRowToEnvelope(row: EventOutboxRow): EventEnvelope; /** * One drain pass: claim due rows, redeliver each through the bus, mark * delivered / reschedule / dead-letter. Call from a scheduled handler * (cron), a `waitUntil` kick after emit, or a long-running Node loop. * Safe to run concurrently from multiple isolates (SKIP LOCKED claim). */ export declare function drainOutbox(db: DrizzleClient, bus: EventBus, options?: DrainOutboxOptions): Promise; /** * Delete delivered rows past the retention window (delivered rows are * receipts, not a log sink — long-term archival belongs elsewhere). * Dead-lettered (`failed`) rows are NOT pruned: they represent lost * deliveries until a human resolves them. Returns the deleted count. */ export declare function pruneDeliveredOutboxEvents(db: DrizzleClient, options?: { olderThanDays?: number; }): Promise; /** Row counts by status — observability for dashboards/health checks. */ export declare function getOutboxStats(db: DrizzleClient): Promise<{ pending: number; delivered: number; failed: number; dueNow: number; }>; //# sourceMappingURL=outbox.d.ts.map