import pg from "pg"; /** * Postgres does not parse `bigint`/`numeric` into JS numbers by default; it * returns strings to avoid precision loss. For canary comparison we want the * literal text either way, so leave the defaults alone and compare as strings. */ export interface DbOptions { connectionString: string; /** Statement timeout for probe queries. A hung probe must not hang a CI run. */ statementTimeoutMs?: number; } /** * How to negotiate TLS with this server. * * `verify` and `require` come from an explicit `sslmode` in the connection * string and are never second-guessed — silently downgrading a connection the * developer asked to be encrypted would be a real vulnerability, not a * convenience. The two `prefer-*` modes are only a first guess from the * hostname, and a wrong guess is recovered at connect time instead of being * treated as fate: `@db:5432` in docker-compose, `@postgres:5432` in a CI * service container, and private IPs are all "not localhost" and all speak * plaintext, and they used to fail hard because the guess was final. */ type SslPreference = "verify" | "require" | "disable" | "prefer-ssl" | "prefer-plain"; /** * Anything that can answer a query. * * Introspection takes this rather than {@link Db} so that the same catalogue * reads can be driven by a connection that is structurally incapable of writing * — see `src/assure/reader.ts`. The narrower type is the point: a reader that * cannot expose `inRollback` or `inTransaction` cannot be handed to seeding or * probing by mistake. */ export interface Queryable { query(sql: string, params?: unknown[]): Promise>; } export declare class Db { private pool; readonly connectionString: string; private readonly preference; private readonly statementTimeoutMs; private useSsl; private flipped; constructor(opts: DbOptions); private makePool; /** * Check a client out of the pool with the session timeout in place. * * The timeout is set per-session rather than passed as a startup parameter. * pgbouncer rejects unknown startup parameters outright, and the pooled * connection string is the one Supabase and Neon dashboards offer first — so * putting `statement_timeout` in the startup packet made the most commonly * pasted string fail at connect. It is awaited here, once per client, rather * than fired from the pool's connect event, which would race the caller's * first query on the same client. Best-effort on purpose: a pooler that * refuses the SET costs us hang protection, not correctness. */ private acquire; /** * Run `fn`, and if it failed because the TLS guess was wrong — in either * direction — rebuild the pool the other way and try once more. Only the * hostname-derived guess is ever flipped; an explicit sslmode is a promise. */ private withSslRecovery; private shouldFlip; private flip; query(sql: string, params?: unknown[]): Promise>; /** * Take a client out of the pool and keep it. * * For the one caller that needs a session property to hold across many * statements rather than one — the read-only transaction in * `src/assure/reader.ts`. The caller owns releasing it. */ checkout(): Promise; /** Run `fn` inside a transaction and always roll it back. */ inRollback(fn: (c: pg.PoolClient) => Promise): Promise; /** Run `fn` inside a transaction and commit if it succeeds. */ inTransaction(fn: (c: pg.PoolClient) => Promise): Promise; close(): Promise; } /** Never print a password, even into a local report file. */ export declare function redact(connectionString: string): string; /** * Say what went wrong reaching the database, and what to do about it. * * A connection string that will not parse, a host that will not resolve, a * refused connection and a rejected password are four different problems with * four different fixes, and the driver's message distinguishes none of them for * a reader. `getaddrinfo EINVAL …your schema…` — a real first run, from someone * who pasted the README's placeholder — names the failing hostname and nothing * else, and reads as the tool being broken rather than the input being wrong. * * Nothing is validated ahead of time. The connection is attempted exactly as * given, and only a failure Postgres or the resolver has already reported is * interpreted, so a legitimately unusual connection string is never rejected * before the server has had its chance to accept it. Where the shape is not one * of the four, the original error is returned untouched — and the driver's own * message is quoted in every case, because whoever is debugging a real network * problem needs it. */ export declare function explainConnectionFailure(err: unknown, connectionString: string): unknown; export declare function sslPreference(connectionString: string): SslPreference; /** * A request's identity: which database role it runs as, and whether it carries * a logged-in subject. The role name comes from the database rather than from a * convention, so this works on a schema that never heard of Supabase. */ export interface Identity { role: string; kind: "anon" | "authenticated"; userId?: string; /** * Session settings this schema reads to identify the caller, beyond * PostgREST's own — discovered from the policy text by `identitySettings`. * * Each is set to the same value as the JWT subject, because that is what an * application using one of these does: it authenticates the request and then * tells the database which user it is for. */ settings?: readonly string[]; } /** * Switching to the role failed — the actor could not even be *become*, which is * a fact about the connecting user's privileges, not about any table. * * The distinction is load-bearing. Postgres raises the same SQLSTATE (42501) * for "permission denied to set role" as for a table-level denial, and scoring * the former as a policy refusal turns every probe for that actor into a false * conclusive pass. The error names a role, not a table; this type carries that * difference to the scorer. Deliberately, no SQLSTATE is propagated. */ export declare class RoleAssumptionError extends Error { readonly role: string; constructor(role: string, cause: Error); } /** * Assume the identity a browser would have. * * This is exactly how PostgREST evaluates a request: it sets the `request.jwt.claims` * GUC from the verified JWT and switches to the `anon` or `authenticated` role. RLS * then evaluates against `auth.uid()`, which reads that GUC. Reproducing it at the * SQL level means we test the real policies rather than an approximation of them — * and it works against any Postgres, not just Supabase. * * Must be called inside a transaction: `SET LOCAL` is scoped to it. */ export declare function assumeIdentity(client: pg.PoolClient, identity: Identity): Promise; /** Restore the connection's owning role (superuser/service) inside a transaction. */ export declare function dropIdentity(client: pg.PoolClient): Promise; /** * Run `fn` inside a savepoint that is always rolled back. * * The whole run happens inside one outer transaction that is itself rolled * back, so seeded rows and attempted writes never reach disk. Individual probes * nest inside savepoints so a destructive probe cannot disturb the next one, * and so a probe that errors does not poison the surrounding transaction. */ export declare function inSavepoint(client: pg.PoolClient, fn: () => Promise): Promise; /** * Run `fn` inside a savepoint, keeping its effects when it succeeds and * discarding only them when it fails. * * Postgres aborts the *entire* transaction on any error, so one rejected INSERT * during seeding would otherwise poison every statement that followed it — the * rest of the seeding and all of the probes. That failure mode is silent and * inverted: the run comes back with no findings at all, which reads exactly * like a clean bill of health on a database that has no protection whatsoever. * Confining each attempt to a savepoint is what keeps "we could not seed this * one table" from becoming "we checked nothing and said so in the language of * success". */ export declare function trySavepoint(client: pg.PoolClient, fn: () => Promise): Promise<{ ok: true; value: T; } | { ok: false; error: Error; }>; export declare function quoteIdent(name: string): string; export declare function qualify(schema: string, table: string): string; /** Split a "schema.table" id back into parts. */ export declare function splitId(id: string): { schema: string; table: string; }; export {};