import postgres from 'postgres'; import type { Manifold } from '../core/pipework.js'; import { type SetupProgressListener } from './progress.js'; export interface TestDatabase { name: string; url: string; appUrl: string; appRole: string; /** The per-file database that gets dropped on teardown. */ database: string; } /** Log prefix for what the orphan reaper left alone (#391). */ export declare const TEST_REAP_LOG_PREFIX = "pipework:test-reap"; export interface SetupOptions { /** Told about every step setup reaches, in order. Issue #380. */ onProgress?: SetupProgressListener; /** Overrides `test.setupStallMs`. Injectable for tests. */ stallMs?: number; /** How often the stall watchdog looks. Injectable for tests. */ stallPollMs?: number; } export declare function setupTestDatabases(instance: Manifold, options?: SetupOptions): Promise>; /** A probe that never answered. Never a reason to rebuild. */ export declare class TemplateProbeFailed extends Error { constructor(attempts: number, cause: unknown); } /** * Run `probe` until it answers. An absent/unbuilt template answers `false`; a * transient connection failure is retried and then thrown as * `TemplateProbeFailed`; anything else propagates. Injectable for tests. */ export declare function probeTemplateReady(probe: () => Promise, sleep?: (ms: number) => Promise, delaysMs?: readonly number[]): Promise; /** Waited out another process's template build. Names the key, the builder, the wait. */ export declare class TemplateBuildWaitTimeout extends Error { constructor(buildKey: string, builderPid: number | null, waitedMs: number); } export interface TemplateBuildWait { buildKey: string; timeoutMs: number; /** Is the template built and ours? Never touches the build key. */ isReady: () => Promise; /** Non-blocking `pg_try_advisory_lock` on the build key. */ tryLock: () => Promise; unlock: () => Promise; build: () => Promise; builderPid: () => Promise; sleep?: (ms: number) => Promise; pollIntervalMs?: number; now?: () => number; /** Called before each wait for another process's build, with the poll count. */ onWait?: (polls: number) => void; } /** * Make sure the template exists, building it here only if no other process is * already building it. * * Exactly one process per fingerprint builds: the one that wins the build key. * Everyone else re-asks `isReady` on an interval — a poll, not a lock queue — * so a batch of forks costs one build and N cheap probes rather than N serial * acquisitions of the exclusive key. The try-lock in the loop is what recovers * a builder that died: its lock goes with its connection, and the next poller * wins the key and builds. Injectable for tests. Issue #362. */ export declare function awaitTemplateBuild(wait: TemplateBuildWait): Promise<'ready' | 'built'>; /** * The backend holding an advisory lock, or null. Diagnostic only: a failure to * answer must never be the thing that fails a bootstrap — which is exactly why * the predicate is covered by a real-Postgres test. */ export declare function advisoryLockHolder(key: string, client?: postgres.Sql): Promise; interface CloneGuards { readonly statementTimeoutMs: number; readonly idleInTransactionTimeoutMs: number; readonly clientCheckIntervalMs: number; } /** The `ALTER DATABASE ... SET` statements for one clone. 0 means "leave it alone". */ export declare function cloneGuardStatements(dbIdent: string, guards: CloneGuards): string[]; export type RetryLog = (message: string) => void; export declare const TEMPLATE_CLONE_LOG_PREFIX = "pipework:template-clone"; export declare const TEMPLATE_DROP_LOG_PREFIX = "pipework:template-drop"; export interface TemplateRetryOptions { /** Injectable for tests. */ sleep?: (ms: number) => Promise; /** Injectable for tests. */ delaysMs?: readonly number[]; /** Injectable for tests. */ log?: RetryLog; /** Injectable for tests. */ now?: () => number; /** Names the operation the lines are about. Defaults to the clone prefix. */ prefix?: string; } /** * Run `attempt`, retrying only on SQLSTATE 55006 with capped exponential * backoff. Any other error propagates immediately; 55006 past the last delay * propagates too. Every retry, and the eventual recovery or exhaustion, is * logged under `prefix`, so contention on the shared template is visible in a * run's output and exhaustion is stated rather than inferred from a missing * line; an attempt that succeeds first try says nothing. Reported elapsed is * wall clock from before the first attempt, so it counts the failed * round-trips, not just the sleeps. */ export declare function withTemplateRetry(attempt: () => Promise, options?: TemplateRetryOptions): Promise; export declare function teardownTestDatabases(): Promise; export declare const TEST_DROP_LOG_PREFIX = "pipework:test-drop"; /** * Drop every queued database, back to back. Best effort per database: one that * refuses (a backend attached between teardown and here) stays for the reaper * rather than failing the flush for the rest. */ export declare function flushPendingDrops(reason: 'batch' | 'shutdown', log?: RetryLog, now?: () => number): Promise; /** Test-only: how many databases are waiting to be dropped. */ export declare function pendingDropCount(): number; export declare function getTestDatabase(name: string): TestDatabase | undefined; export declare function getTestDatabases(): ReadonlyMap; /** * Forget the in-process template cache, so the next `setupTestDatabases` takes * the same path a freshly-forked worker takes: shared lock, readiness check, * clone. This is how a single process can prove the cross-process claim — that * a second fork reuses the template rather than rebuilding it — without * actually forking. Test-only. Issue #340. */ export declare function forgetTemplateCacheForTests(): void; export {}; //# sourceMappingURL=setup.d.ts.map