import { type Fetch, type Params, TaskStore } from "./base.js"; /** * The rewritten SQL and the order its `$n` slots must be filled in. * * Translates the protocol's named-parameter SQL (`:name`) into Postgres positional * placeholders (`$1`), collapsing each DISTINCT name to ONE slot — statements reuse * a name across CASE branches / IS NULL guards (e.g. list.sql). Which names count * as parameters is `statementParams`' decision, shared with the SQLite binding path * so the two can't disagree about, say, a `::type` cast. * * Memoized on the statement text, which is loaded once and never varies: this runs * on every query, including the worker's poll loop, for a result that cannot have * changed. Exported for unit testing. */ export declare function positionalStatement(sql: string): { text: string; order: readonly string[]; }; /** * The statement's rewritten text plus this call's values, in slot order. Names the * statement does not use are simply not bound, so callers may pass a superset. */ export declare function toPositional(sql: string, params: Params): { text: string; values: unknown[]; }; /** * PostgresStore — the Postgres dialect of the shared cairnq-protocol SQL. * * Everything protocol-shaped lives in TaskStore; this file is only what Postgres * does differently: a `pg` Pool, `:name` -> `$n` translation, and time taken from * the DB clock (`now()`) instead of from the SDK, which is what makes this backend * multi-host — unlike SQLite it coordinates API and worker processes across * machines, with no shared clock to agree on. claim uses FOR UPDATE SKIP LOCKED * and needs no claimable_probe, because PG readers don't block writers. JSON * columns are jsonb (bound as JSON text, read back as objects by rowToTask). */ export declare class PostgresStore extends TaskStore { private readonly dsn; private readonly opts; private pool; private connecting; private readonly statements; private listener; private listenerConnecting; /** LISTEN is off for good: the store was closed, or the server accepted a * connection but refused LISTEN (e.g. a transaction-mode pooler) — * deterministic, so retrying would fail the same way every time. */ private listenerUnavailable; /** A failure to even connect is transient (network blip, server restarting): * retry, but not before this time, backing off so a down server is not * hammered from the poll loop. */ private listenerRetryAt; private listenerBackoffMs; /** Queues notified while nobody was waiting; consumed by the next claimWake * so a wake that lands between polls is not lost. */ private readonly pendingQueues; /** Wake callbacks by key: "queued" (broadcast) or "done:". */ private readonly waiters; constructor(dsn: string, opts?: { max?: number; }); connect(): Promise; close(): Promise; private ensure; private doConnect; private applyMigrations; private readProtocolVersion; protocolVersion(): Promise; claimWake(queues: string[], timeoutMs: number): Promise; taskDoneWake(taskId: string, timeoutMs: number): Promise; /** A promise resolving on notification-or-timeout, deregistering either way. * The timer is unref'd: while a listener exists its socket keeps the process * alive, and dropListener wakes every waiter the moment it goes away. */ private wakeOn; /** True once the LISTEN connection is up; starts connecting it otherwise * (respecting the transient-failure backoff). Callers fall back to plain * polling until it is ready (or forever, if it can't be established) — * correctness never depends on it. */ private listenerReady; private startListener; private onNotification; private dropListener; protected fetch(name: string, params: Params): Promise; protected tx(fn: (fetch: Fetch) => Promise): Promise; }