import type { PoolClient, QueryResult, QueryResultRow } from 'pg';
import { AuthContextProvider } from '../auth/providers/auth-context.provider.js';
import { DBProvider } from './db.provider.js';
export declare function isDataModifyingQuery(text: string): boolean;
export interface TenantDbClientStats {
    /** Clients holding a checked-out connection right now. */
    holdingConnection: number;
    /** Longest any holder has gone without issuing a query. */
    maxIdleMs: number;
}
export declare function getTenantDbClientStats(): TenantDbClientStats;
export interface WatchdogOptions {
    /** Force-dispose a client idle for longer than this. */
    maxIdleMs: number;
    /**
     * Ceiling for a client whose GraphQL operation is *still executing*.
     *
     * A request can legitimately go quiet on the database for minutes while it
     * does external work — a document upload fetches the file, pushes it to
     * Cloudinary and waits on OCR before it writes anything. Reclaiming its client
     * on the plain idle rule would leave the request alive but its database
     * connection gone, so the write at the end fails with "already disposed" after
     * all that work. Such a client is therefore given a longer rope; it is not
     * exempt, because the flag saying it is executing lives on a request context
     * that a missed hook could leave set forever.
     *
     * Defaults to {@link maxIdleMs} when unset.
     */
    activeMaxIdleMs?: number;
    /**
     * Ceiling for a client whose caller has already hung up.
     *
     * Disposal is deferred for these (the operation keeps running and still has to
     * write), so this is what bounds that deferral. It is deliberately much
     * tighter than {@link activeMaxIdleMs}: an aborted request that is genuinely
     * still working keeps issuing queries and never comes near it, while one whose
     * execution died with the connection — a query urql cancelled on the next
     * keystroke — goes silent immediately and is reclaimed within a sweep or two
     * rather than being held for the full active ceiling.
     *
     * Must stay above the statement timeout: a long query only bumps activity at
     * its start and end, so a shorter ceiling would reclaim a connection mid-query.
     */
    abortedMaxIdleMs?: number;
    /** How often to sweep. */
    intervalMs: number;
    onLeak?: (info: {
        idleMs: number;
        lastQuery: string | null;
        operationInFlight: boolean;
        requestAborted: boolean;
    }) => void;
}
/**
 * Last line of defence against a connection leak.
 *
 * Disposal is driven by request lifecycle hooks, and the whole class of bug
 * this guards against is a hook that does not fire. So the watchdog trusts no
 * hook: it sweeps every live client and reclaims any that has gone quiet for
 * longer than a request could plausibly stay quiet.
 *
 * The predicate is *idle* time (since the last query), not total age — a slow
 * but healthy request keeps querying, while a leaked client never issues
 * another statement, so its idle time grows without bound.
 */
export declare function startTenantDbClientWatchdog(options: WatchdogOptions): {
    stop: () => void;
};
/**
 * TenantAwareDBClient enforces Row-Level Security (RLS) by setting PostgreSQL
 * session variables on a request-scoped transaction.
 *
 * RLS Enforcement:
 * - app.current_business_id: Set to the authenticated user's active business
 * - app.current_user_id: Set to the authenticated user's ID (or NULL for API keys)
 * - app.auth_type: Set to 'jwt' or 'apiKey'
 *
 * **Usage:**
 * Inject into Operation-scoped providers via constructor DI:
 *
 * @example
 * @Injectable({ scope: Scope.Operation })
 * class BusinessesProvider {
 *   constructor(private db: TenantAwareDBClient) {}
 *
 *   async getBusinesses() {
 *     return this.db.query('SELECT * FROM businesses')
 *   }
 * }
 *
 * Session model (request-scoped):
 * - The first query checks out one pooled connection and opens a transaction
 *   with the RLS variables set once. Subsequent read queries reuse it — one
 *   round trip per query instead of BEGIN/SET/query/COMMIT for each.
 * - Data-modifying stand-alone queries and explicit `transaction()` scopes are
 *   committed immediately on success, so a mutation response always reflects
 *   durable state. The read session re-opens lazily on the next query.
 * - A failed statement aborts the surrounding transaction (Postgres 25P02), so
 *   errors roll the session back and the next query starts a fresh one. Only
 *   uncommitted read-only work is discarded — writes were already committed.
 * - `dispose()` (invoked by dbCleanupPlugin at request/stream end) commits any
 *   open read session and releases the connection back to the pool.
 *
 * Transaction Management:
 * - Supports nested transactions via SAVEPOINTs
 * - Automatically rolls back on error
 * - Automatically releases connection on dispose
 *
 * **DO NOT** access from Yoga context - use DI injection instead.
 *
 * @throws {GraphQLError} UNAUTHENTICATED if auth context is null
 */
export declare class TenantAwareDBClient {
    private dbProvider;
    private authContextProvider;
    private mutex;
    private storage;
    private activeClient;
    private sessionOpen;
    private transactionDepth;
    private isDisposed;
    private disposalRequested;
    private authContext;
    private authContextInitialized;
    private clientErrorListener;
    private readonly context;
    /** Timestamp of the last query issued, for leak detection. See the watchdog. */
    lastActivityAt: number;
    /** First line of the last statement issued, to identify a leak's origin. */
    lastQuery: string | null;
    get holdsConnection(): boolean;
    /**
     * Whether the GraphQL operation that owns this client is still executing.
     *
     * Set by `dbCleanupPlugin` around execution. It is what tells a request that
     * has gone quiet on the database — because it is fetching a file or waiting on
     * OCR — apart from one that has gone away.
     */
    get operationInFlight(): boolean;
    /**
     * Whether the caller of the owning request has hung up. Set when a deferred
     * disposal is recorded; puts the client on the watchdog's short leash, which
     * is what bounds the deferral.
     */
    get requestAborted(): boolean;
    /**
     * Per-operation mode: commit and release the connection after every
     * top-level query/transaction (the pre-request-scoped behavior). Defaults to
     * true for direct constructions outside the GraphQL request lifecycle (no
     * CONTEXT injection — test harnesses, scripts) where nothing calls
     * dispose(): a held connection would otherwise leak from the pool, keep
     * table locks, and block pool.end().
     */
    autoRelease: boolean;
    constructor(dbProvider: DBProvider, authContextProvider: AuthContextProvider, context?: GraphQLModules.GlobalContext);
    /** Records query activity so the watchdog can tell a busy client from a leaked one. */
    private markActivity;
    /**
     * Execute a query with RLS enforcement on the request-scoped session.
     * Data-modifying statements are committed immediately.
     */
    query<T extends QueryResultRow = QueryResultRow>(text: string, params?: unknown[]): Promise<QueryResult<T> & {
        rowCount: number;
    }>;
    private queryOnSession;
    /**
     * Execute a function within a transaction block.
     * Handles nested transactions using SAVEPOINTs. The outermost scope is
     * committed immediately on success.
     */
    transaction<T>(fn: (client: PoolClient) => Promise<T>): Promise<T>;
    private executeTransactionInternal;
    /**
     * Ensure the request-scoped session is open: one pooled connection for the
     * whole request, with an open transaction carrying the RLS variables.
     * Always called while holding the mutex.
     */
    private ensureSession;
    /**
     * Close the open transaction (COMMIT or ROLLBACK). The connection is kept
     * for the next session unless the close itself fails, in which case the
     * connection state is unknown and it is destroyed.
     */
    private endSession;
    private releaseClient;
    /**
     * Set PostgreSQL session variables for Row-Level Security.
     */
    private setRLSVariables;
    /**
     * Hand the pooled connection back for the duration of long *non-database*
     * work, without ending the client's life.
     *
     * Document ingestion is the motivating case: between one query and the next it
     * downloads a file, uploads it to Cloudinary and waits on OCR — minutes during
     * which the connection would otherwise sit checked out and `idle in
     * transaction`, occupying a pool slot and pinning the oldest transaction
     * snapshot. Any open read session is committed (writes commit as they go
     * anyway) and a fresh one opens lazily on the next query.
     *
     * A no-op inside an explicit `transaction()` scope, whose atomicity depends on
     * keeping the connection, and a no-op once disposed.
     */
    releaseIdleConnection(): Promise<void>;
    /**
     * Dispose, but not out from under a request that is still running.
     *
     * Used for client-side aborts: the HTTP connection going away says nothing
     * about the mutation still executing on this server, and JavaScript cannot
     * cancel the promise chain it is running on. Disposing there is what turned a
     * timed-out document upload into an "already disposed" failure at the final
     * INSERT — minutes of fetching and OCR thrown away at the last step, with the
     * charge left untouched.
     *
     * So while the operation is in flight the disposal is only *recorded*; the
     * work finishes and writes, and the client is released at the next natural
     * point — the end-of-execution hook, or the completion of its final query. The
     * watchdog remains the backstop for a request that never completes at all.
     *
     * @returns whether disposal was deferred (`true`) rather than performed.
     */
    disposeWhenIdle(): Promise<boolean>;
    /** Run a disposal that was deferred by {@link disposeWhenIdle}, if it is now due. */
    private disposeIfRequested;
    /**
     * End-of-request cleanup: commit any open read session and release the
     * connection. Invoked by dbCleanupPlugin once the response (including any
     * deferred stream) is fully sent; safe to call manually for direct
     * constructions.
     */
    dispose(): Promise<void>;
    private markDisposed;
    private ensureNotDisposed;
    /**
     * Lazy initialization of auth context on first use.
     * This ensures the async provider is called only when needed.
     */
    private ensureAuthContext;
}
