import { type Conflict, type InviteRecord, type MemberId, type MemberRecord, type MemberStateRow, type PairId, type PairRecord, type PairStateRow } from "../types.js"; import type { AcceptInviteAtomicInput, AcceptInviteAtomicResult, AcceptInviteInput, AddMemberInput, CreateInviteInput, CreatePairInput, SetMemberStateInput, SetPairStateInput, TenantStore } from "./interface.js"; /** * Minimal contract this adapter requires from a `pg.Pool`-like client. * Keeps the dependency optional and the surface small for testing with a mock. * * For race-safe `acceptInviteAtomic` the pool MUST also implement `connect()` * returning a per-call client (real `pg.Pool` does). Mock-pools without * `connect()` get a non-atomic fallback path with a console.warn. */ export interface PgQueryable { query(text: string, params?: unknown[]): Promise<{ rows: R[]; }>; end?(): Promise; connect?(): Promise; } /** Minimal `pg.PoolClient` contract for the atomic transaction path. */ export interface PgClient { query(text: string, params?: unknown[]): Promise<{ rows: R[]; }>; release(err?: Error | boolean): void; } export interface PostgresTenantStoreOptions { pool: PgQueryable; /** Optional schema name. Defaults to "public". MUST match `/^[a-z_][a-z0-9_]{0,62}$/` — validated in constructor. */ schema?: string; } export declare class PostgresTenantStore implements TenantStore { #private; private readonly pool; private readonly schema; private initialized; constructor(options: PostgresTenantStoreOptions); init(): Promise; close(): Promise; createPair(input: CreatePairInput): Promise; getPair(pairId: PairId): Promise; addMember(input: AddMemberInput): Promise; reactivateMember(pairId: PairId, memberId: MemberId, joinedAt: string): Promise; removeMember(pairId: PairId, memberId: MemberId, leftAt: string): Promise; listMembers(pairId: PairId, includeLeft?: boolean): Promise; getMember(pairId: PairId, memberId: MemberId): Promise; createInvite(input: CreateInviteInput): Promise; getInvite(inviteToken: string): Promise; acceptInvite(input: AcceptInviteInput): Promise; /** * Atomic accept-invite via single-connection transaction with row-locking. * Closes Reviewer F2 / Cold-Cross F2: TOCTOU race where two concurrent * callers could both pass the accepted_at check. * * Locking strategy: BEGIN → SELECT FOR UPDATE on invite row → caller is * serialized. The first to commit takes the slot, the second reads * accepted_at IS NOT NULL on its own SELECT and returns INVITE_ALREADY_ACCEPTED. * * Fallback: when the pool does NOT expose `connect()` (mock-pools in unit * tests), we run the non-atomic path with a console.warn — production * `pg.Pool` always has connect(). */ acceptInviteAtomic(input: AcceptInviteAtomicInput): Promise; countPendingInvites(pairId: PairId, asOf: string): Promise; forgetMember(pairId: PairId, memberId: MemberId): Promise; setMemberState(input: SetMemberStateInput): Promise; getMemberState(pairId: PairId, memberId: MemberId, keys?: string[]): Promise; getMemberStateHistory(pairId: PairId, memberId: MemberId, key: string): Promise; setPairState(input: SetPairStateInput): Promise<{ version: number; }>; getPairState(pairId: PairId, namespace?: string): Promise; getPairStateHistory(pairId: PairId, namespace: string, key: string): Promise; listConflicts(pairId: PairId, namespace?: string): Promise; markConflictResolved(pairId: PairId, namespace: string, key: string, winnerVersion: number, resolvedAt: string): Promise; } //# sourceMappingURL=postgres.d.ts.map