import { P as PostgresConfig, S as Sql, H as HttpTransportConfig, T as Transport, R as Row, Q as QueryResult, a as TransactionOptions, C as CDCTransport, b as CDCWsTransportConfig, c as CDCSubscriptionOptions, d as CDCChangeEvent, e as CDCSseTransportConfig, f as TypeParser, g as StreamOptions, h as StreamResult, i as Cursor } from './types-YTdPe6Qz.cjs'; export { o as CDCClientSubscribeOptions, n as CDCClientSubscription, p as CDCClientSubscriptionInfo, m as CDCOperation, r as ConnectionError, u as CursorFetchResult, F as FieldInfo, I as IsolationLevel, l as PendingQuery, q as PostgresError, t as PostgresErrorFields, k as ReservedSql, s as TimeoutError, j as TransactionSql, W as WsTransportConfig } from './types-YTdPe6Qz.cjs'; import { AuthenticatedUser, AuthTokenValidationResult } from '@dotdo/postgres-shared'; export { AuthenticatedUser, PoolError, AuthTokenValidationResult as TokenValidationResult, extractBearerToken, generateDatabaseId } from '@dotdo/postgres-shared'; export { R as RpcTransport, a as RpcTransportConfig, R as WsTransport, c as createRpcTransport, c as createWsTransport } from './rpc-CmyjTKd1.cjs'; export { EXTENSION_REGISTRY, ExecuteWithAutoLoadResult, ExtensionLoadResult, ExtensionLoaderStats, ExtensionMetadata, ExtensionName, LazyExtensionLoader, LazyExtensionLoaderOptions, QueryExecutor, createLazyExtensionLoader, detectExtensions } from './extensions.cjs'; import '@dotdo/postgres-shared/errors'; /** * PostgreSQL client for postgres.do * SQL tagged template function with Drizzle ORM compatibility */ /** * Create the main SQL client */ declare function createClient(config?: PostgresConfig): Sql; /** * Main entry point - create a postgres client * @param urlOrConfig - Connection URL or config object * @param options - Additional options */ declare function postgres(urlOrConfig?: string | PostgresConfig, options?: PostgresConfig): Sql; /** * Authentication integration for postgres.do * * Provides authentication helpers using oauth.do for both * client-side (browser) and server-side (Node.js/Cloudflare Workers) usage. * * The DO-based architecture means each authenticated user can have their own * database, simplifying security by eliminating row-level access control. */ interface AuthenticatedClientOptions extends PostgresConfig { /** OAuth API URL (default: https://oauth.do) */ oauthUrl?: string; } interface AuthenticatedContext { user: AuthenticatedUser; token: string; } /** * Create an authenticated postgres client for server-side usage * * This function verifies the user's authentication token and returns * a postgres client configured for their specific database. * * @example * ```typescript * import { createAuthenticatedClient } from 'postgres.do' * import { ensureLoggedIn } from 'oauth.do/node' * * export default { * async fetch(request: Request) { * // Verify authentication * const auth = await ensureLoggedIn(request) * * // Create client for this user's database * const sql = createAuthenticatedClient(auth.token, { * url: `https://db.postgres.do/${auth.user.id}` * }) * * const users = await sql`SELECT * FROM users` * return Response.json(users) * } * } * ``` */ declare function createAuthenticatedClient(token: string, options?: AuthenticatedClientOptions): Sql; /** * Create a postgres client from an authenticated request (server-side) * * This is a convenience function that extracts the Bearer token from * the request Authorization header. * * @example * ```typescript * import { createClientFromRequest } from 'postgres.do' * * export default { * async fetch(request: Request) { * const sql = createClientFromRequest(request, { * url: 'https://db.postgres.do/mydb' * }) * * const users = await sql`SELECT * FROM users` * return Response.json(users) * } * } * ``` */ declare function createClientFromRequest(request: Request, options?: AuthenticatedClientOptions): Sql; /** * Higher-order function to wrap a request handler with authentication * * @example * ```typescript * import { withAuth } from 'postgres.do' * * export default { * fetch: withAuth(async (request, { user, sql }) => { * // user is guaranteed to be authenticated * // sql is configured for this user's database * const data = await sql`SELECT * FROM my_table` * return Response.json({ user, data }) * }) * } * ``` */ declare function withAuth(handler: (request: Request, context: { user: AuthenticatedUser; sql: Sql; token: string; }) => Promise, options?: AuthenticatedClientOptions & { /** Custom function to verify and extract user from token */ verifyToken?: (token: string) => Promise; /** URL to get database URL for user (default: uses user.id) */ getDatabaseUrl?: (user: AuthenticatedUser) => string; /** Custom unauthorized response */ onUnauthorized?: (request: Request) => Response; }): (request: Request) => Promise; /** * Extract user ID from a request's authentication token * Useful when you need just the user ID without creating a client */ declare function extractUserId(request: Request, options?: { oauthUrl?: string; }): Promise; /** * Validate a token and return the full result */ declare function validateToken(token: string, options?: { oauthUrl?: string; }): Promise; /** * Create a user-scoped database client * The database URL is derived from the user's ID * * @example * ```typescript * import { createUserScopedClient } from 'postgres.do' * * // After validating the user * const sql = createUserScopedClient(user.id, token) * * const data = await sql`SELECT * FROM my_table` * ``` */ declare function createUserScopedClient(userId: string, token: string, options?: AuthenticatedClientOptions): Sql; /** * Connection URL parsing for postgres.do * * Parses postgres:// URLs into configuration objects */ /** * Parsed connection information */ interface ConnectionInfo { /** The https base URL for postgres.do */ url: string; /** Database name */ database?: string; /** API key for authentication */ apiKey?: string; /** SSL mode */ ssl?: PostgresConfig['ssl']; /** Additional parameters from URL query string */ params: Record; } /** * Parse a postgres:// connection URL * * @param connectionUrl - The postgres:// or postgresql:// URL * @returns Parsed connection information * * @example * ```typescript * // Basic URL * parseConnectionUrl('postgres://db.postgres.do/mydb') * // => { url: 'https://db.postgres.do/mydb', database: 'mydb', params: {} } * * // With API key in password * parseConnectionUrl('postgres://user:apikey123@db.postgres.do/mydb') * // => { url: 'https://db.postgres.do/mydb', apiKey: 'apikey123', params: {} } * * // With query parameters * parseConnectionUrl('postgres://db.postgres.do/mydb?sslmode=require') * // => { url: 'https://db.postgres.do/mydb', ssl: 'require', params: { sslmode: 'require' } } * ``` */ declare function parseConnectionUrl(connectionUrl: string): ConnectionInfo; /** * HTTP Transport for postgres.do * Sends SQL queries over HTTP to the postgres.do API */ /** * HTTP Transport implementation * Stateless transport that sends each query as an HTTP request */ declare class HttpTransport implements Transport { private readonly config; private readonly fetchImpl; private connected; constructor(config: HttpTransportConfig); /** * Execute a SQL query */ query(sql: string, params?: unknown[]): Promise>; /** * Execute multiple queries in a transaction */ transaction(queries: Array<{ sql: string; params?: unknown[]; }>, options?: TransactionOptions): Promise; /** * Close the transport (no-op for HTTP) */ close(): Promise; /** * Check if transport is connected */ isConnected(): boolean; /** * Make an HTTP request to the postgres.do API */ private request; /** * Parse error response from the API */ private parseErrorResponse; /** * Parse query response into QueryResult */ private parseQueryResponse; /** * Serialize transaction options for the API */ private serializeTransactionOptions; } /** * Create an HTTP transport instance */ declare function createHttpTransport(config: HttpTransportConfig): HttpTransport; /** * CDC WebSocket Transport for postgres.do * * Provides real-time change data capture streaming via WebSocket: * - Bidirectional communication for subscription management * - Automatic reconnection with exponential backoff * - LSN-based resumption for reliable delivery * - Heartbeat/ping-pong for connection keepalive */ /** * CDC WebSocket Transport implementation * * Extends BaseCDCTransport for common functionality. */ declare class CDCWsTransport implements CDCTransport { private readonly config; private readonly WebSocketImpl; private ws; private connected; private authenticated; private connecting; private lastEventId; private reconnectAttempt; private reconnectTimer; private pingTimer; private shouldReconnect; private pendingSubscriptions; private pendingUnsubscriptions; private pendingAuth; private eventHandler; private errorHandler; private reconnectHandler; private closeHandler; private activeSubscriptions; constructor(config: CDCWsTransportConfig); /** * Connect to the CDC stream */ connect(): Promise; /** * Internal connect implementation */ private _connect; /** * Authenticate with the API key */ private authenticate; /** * Disconnect from the CDC stream */ disconnect(): Promise; /** * Check if connected */ isConnected(): boolean; /** * Get the last event ID received */ getLastEventId(): string | null; /** * Subscribe to a table */ subscribe(subscriptionId: string, table: string, schema: string, options: CDCSubscriptionOptions): Promise; /** * Unsubscribe from a subscription */ unsubscribe(subscriptionId: string): Promise; /** * Set the event handler */ onEvent(handler: (subscriptionId: string, event: CDCChangeEvent) => void): void; /** * Set the error handler */ onError(handler: (subscriptionId: string, error: Error) => void): void; /** * Set the reconnect handler */ onReconnect(handler: (attempt: number, lastLsn: string | undefined) => void): void; /** * Set the close handler */ onClose(handler: () => void): void; /** * Send a message */ private send; /** * Handle incoming message */ private handleMessage; /** * Handle CDC event message */ private handleEventMessage; /** * Handle error message */ private handleErrorMessage; /** * Handle subscribe result */ private handleSubscribeResult; /** * Handle unsubscribe result */ private handleUnsubscribeResult; /** * Handle auth result */ private handleAuthResult; /** * Start ping interval for keepalive */ private startPingInterval; /** * Stop ping interval */ private stopPingInterval; /** * Schedule reconnection with exponential backoff */ private scheduleReconnect; /** * Reconnect and resubscribe */ private reconnect; /** * Reject all pending requests */ private rejectAllPending; } /** * Create a CDC WebSocket transport */ declare function createCdcWsTransport(config: CDCWsTransportConfig): CDCWsTransport; /** * CDC SSE (Server-Sent Events) Transport for postgres.do * * Provides HTTP-based change data capture streaming via SSE: * - Works in environments where WebSocket is blocked * - Uses standard EventSource/fetch with streaming body * - Automatic reconnection with Last-Event-ID header * - LSN-based resumption for reliable delivery * * Performance optimizations: * - Event batching for high-throughput scenarios * - Adaptive heartbeat intervals based on activity * - Reusable TextDecoder for memory efficiency * - Exponential backoff with jitter for reconnection * - Compression support via Accept-Encoding */ /** * Extended SSE transport configuration with optimization options */ interface CDCSseTransportOptimizedConfig extends CDCSseTransportConfig { /** Enable event batching for high-throughput scenarios */ enableBatching?: boolean; /** Maximum events to batch before flushing */ batchSize?: number; /** Maximum time to wait before flushing batch (ms) */ batchTimeoutMs?: number; /** Enable compression (gzip/deflate) if server supports it */ enableCompression?: boolean; /** Enable adaptive heartbeat intervals */ adaptiveHeartbeat?: boolean; /** Minimum heartbeat interval (ms) */ minHeartbeatMs?: number; /** Maximum heartbeat interval (ms) */ maxHeartbeatMs?: number; /** Maximum buffer size before applying backpressure (bytes) */ maxBufferSize?: number; } /** * CDC SSE Transport implementation with streaming optimizations */ declare class CDCSseTransport implements CDCTransport { private config; private fetchImpl; private connected; private lastEventId; private shouldReconnect; private reconnectAttempt; private reconnectTimer; private abortController; private eventHandler; private errorHandler; private reconnectHandler; private closeHandler; private activeSubscriptions; private pendingSubscriptions; private pendingUnsubscriptions; private readonly decoder; private enableBatching; private batchSize; private batchTimeoutMs; private eventBatch; private batchTimer; private adaptiveHeartbeat; private minHeartbeatMs; private maxHeartbeatMs; private currentHeartbeatMs; private lastEventTime; private eventRateWindow; private heartbeatTimer; private maxBufferSize; private currentBufferSize; private enableCompression; private metrics; constructor(config: CDCSseTransportConfig | CDCSseTransportOptimizedConfig); /** * Connect to the CDC stream */ connect(): Promise; /** * Internal connect implementation with optimized headers */ private _connect; /** * Build URL with subscription parameters */ private buildSubscriptionUrl; /** * Process the SSE stream with optimized buffering and memory management */ private processStream; /** * Process buffer when under backpressure - yields to event loop */ private processBufferWithBackpressure; /** * Parse and handle an SSE event */ private parseAndHandleEvent; /** * Handle a parsed event with optional batching */ private handleParsedEvent; /** * Add event to batch for coalesced delivery */ private addToBatch; /** * Flush all batched events to handlers */ private flushEventBatch; /** * Start monitoring connection health with adaptive intervals */ private startHeartbeatMonitor; /** * Stop the heartbeat monitor */ private stopHeartbeatMonitor; /** * Check connection health and adjust heartbeat interval */ private checkConnectionHealth; /** * Adapt heartbeat interval based on observed event rate */ private adaptHeartbeatInterval; /** * Update heartbeat timing when server sends keepalive */ private updateHeartbeatFromServer; /** * Schedule reconnection with exponential backoff and jitter * * Uses a decorrelated jitter algorithm for optimal reconnection behavior: * - Exponential backoff: delay = retryMs * 1.5^attempt * - Jitter: adds random variation to prevent thundering herd * - Capped at maxReconnectDelay to prevent excessive waits */ private scheduleReconnect; /** * Disconnect from the CDC stream */ disconnect(): Promise; /** * Get transport performance metrics */ getMetrics(): Readonly; /** * Reset performance metrics */ resetMetrics(): void; /** * Get current heartbeat interval (useful for monitoring) */ getCurrentHeartbeatMs(): number; /** * Check if connected */ isConnected(): boolean; /** * Get the last event ID received */ getLastEventId(): string | null; /** * Subscribe to a table * For SSE, this adds to the subscription set and reconnects */ subscribe(subscriptionId: string, table: string, schema: string, options: CDCSubscriptionOptions): Promise; /** * Unsubscribe from a subscription * For SSE, this removes from the subscription set and reconnects */ unsubscribe(subscriptionId: string): Promise; /** * Set the event handler */ onEvent(handler: (subscriptionId: string, event: CDCChangeEvent) => void): void; /** * Set the error handler */ onError(handler: (subscriptionId: string, error: Error) => void): void; /** * Set the reconnect handler */ onReconnect(handler: (attempt: number, lastLsn: string | undefined) => void): void; /** * Set the close handler */ onClose(handler: () => void): void; } /** * Create a CDC SSE transport */ declare function createCdcSseTransport(config: CDCSseTransportConfig): CDCSseTransport; /** * CDC Subscription for postgres.do * * Provides a user-friendly API for subscribing to database changes: * - Callback API (onChange, onInsert, onUpdate, onDelete) * - Async iterator API for use with for-await-of loops * - TypeScript generics for typed change payloads * - Automatic connection management and reconnection */ /** * Options for creating a subscription */ interface SubscribeOptions extends CDCSubscriptionOptions { /** Called for every change event */ onChange?: (event: CDCChangeEvent) => void | Promise; /** Called for INSERT events */ onInsert?: (row: Row) => void | Promise; /** Called for UPDATE events */ onUpdate?: (newRow: Row, oldRow?: Row) => void | Promise; /** Called for DELETE events */ onDelete?: (oldRow: Row) => void | Promise; /** Called when an error occurs */ onError?: (error: Error) => void; } /** * Typed subscribe options with generics */ interface TypedSubscribeOptions extends Omit { /** Called for every change event */ onChange?: (event: CDCChangeEvent & { newRow?: T; oldRow?: T; }) => void | Promise; /** Called for INSERT events */ onInsert?: (row: T) => void | Promise; /** Called for UPDATE events */ onUpdate?: (newRow: T, oldRow?: T) => void | Promise; /** Called for DELETE events */ onDelete?: (oldRow: T) => void | Promise; /** Called when an error occurs */ onError?: (error: Error) => void; } /** * Subscription interface for CDC streaming */ interface Subscription extends AsyncIterable { /** Unique subscription ID */ readonly id: string; /** Table name */ readonly table: string; /** Schema name */ readonly schema: string; /** Whether the subscription is currently active */ readonly isActive: boolean; /** Last processed LSN for resumption */ readonly lastLsn: string | null; /** Unsubscribe and stop receiving events */ unsubscribe(): Promise; /** Register a handler for all change events */ onChange(handler: (event: CDCChangeEvent & { newRow?: T; oldRow?: T; }) => void | Promise): this; /** Register a handler for INSERT events */ onInsert(handler: (row: T) => void | Promise): this; /** Register a handler for UPDATE events */ onUpdate(handler: (newRow: T, oldRow?: T) => void | Promise): this; /** Register a handler for DELETE events */ onDelete(handler: (oldRow: T) => void | Promise): this; } /** * CDC Subscription implementation */ declare class CDCSubscription implements Subscription { readonly id: string; readonly table: string; readonly schema: string; private state; private unsubscribeCallback; constructor(id: string, table: string, schema: string, transport: CDCTransport, unsubscribeCallback: () => Promise, options?: SubscribeOptions); get isActive(): boolean; get lastLsn(): string | null; /** * Handle an incoming event from the transport */ handleEvent(event: CDCChangeEvent): void; /** * Handle an error from the transport */ handleError(error: Error): void; /** * Mark the subscription as inactive */ setInactive(): void; unsubscribe(): Promise; onChange(handler: (event: CDCChangeEvent & { newRow?: T; oldRow?: T; }) => void | Promise): this; onInsert(handler: (row: T) => void | Promise): this; onUpdate(handler: (newRow: T, oldRow?: T) => void | Promise): this; onDelete(handler: (oldRow: T) => void | Promise): this; /** * Async iterator implementation for for-await-of support */ [Symbol.asyncIterator](): AsyncIterator; } /** * Validate a table name to prevent SQL injection */ declare function validateTableName(name: string): { table: string; schema: string; }; /** * Generate a unique subscription ID */ declare function generateSubscriptionId(): string; /** * Streaming API for postgres.do * * Provides cursor-based streaming for large datasets to avoid OOM errors * in Cloudflare Workers' 128MB memory limit. * * Uses PostgreSQL cursors under the hood: * - DECLARE cursor FOR SELECT ... * - FETCH n FROM cursor * - CLOSE cursor * * @example * ```typescript * // Stream rows one at a time * for await (const row of sql.stream`SELECT * FROM huge_table`) { * process(row) * } * * // Stream in batches * for await (const batch of sql.stream`SELECT * FROM huge_table`.batch(1000)) { * processBatch(batch) * } * * // Manual cursor control * const cursor = sql.cursor`SELECT * FROM huge_table` * while (const rows = await cursor.fetch(100)) { * process(rows) * } * await cursor.close() * ``` * * ## Security Considerations * * ### Cursor Name Injection Prevention * * Cursor names are used in SQL statements (DECLARE, FETCH, CLOSE) and must be * carefully validated to prevent SQL injection attacks. This module implements * multiple layers of protection: * * 1. **PostgreSQL Identifier Validation**: Cursor names must conform to PostgreSQL * identifier rules - start with letter or underscore, contain only alphanumeric * characters and underscores, max 63 characters. * * 2. **Cryptographic Randomness**: Auto-generated cursor names use crypto.getRandomValues() * to prevent name prediction attacks. The format is: * `pg_cursor_{timestamp}_{counter}_{8-char-hex-random}` * * 3. **Double-Quote Escaping**: Cursor names are always wrapped in double quotes * in SQL statements, preventing injection through special characters. * * 4. **No User Input Echoing**: Error messages do not echo potentially malicious * cursor names back to the user, preventing information leakage. * * ### Isolation Level Validation * * Transaction isolation levels are validated against a whitelist to prevent * SQL injection through the isolationLevel option. * * ### Defense in Depth * * Even if validation were bypassed, the cursor name quoting provides additional * protection. However, users should never rely solely on quoting - always use * validated cursor names or let the system generate them automatically. * * @see validateCursorName for the validation implementation * @see generateCursorName for the secure name generation */ /** * Create a cursor for a query */ declare function createCursor(transport: Transport, sql: string, params: unknown[], parsers: Record, options?: StreamOptions): Promise>; /** * Create a stream result for a query */ declare function createStreamResult(transport: Transport, sql: string, params: unknown[], parsers: Record, options?: StreamOptions): StreamResult; /** * Build parameterized SQL string from template */ declare function buildParameterizedSql(strings: TemplateStringsArray, values: unknown[]): { sql: string; params: unknown[]; }; /** * PGLite Connection Pool * * A connection pool implementation for PGLite that efficiently manages * database connections, particularly optimized for Cloudflare Workers * where memory is constrained and each Durable Object is the sole client. * * ## Design Philosophy * * Unlike traditional connection pools that manage multiple physical database * connections, this pool manages "virtual" clients over a single PGLite instance. * Since PGLite runs PostgreSQL in-process (WASM), there's no network latency, * making client acquisition essentially free. The pool's value comes from: * * 1. **Controlled concurrency** - Prevents overwhelming the single-threaded WASM * instance with parallel operations that could cause memory pressure * 2. **Familiar API** - Provides the standard pool.query/transaction interface * that developers expect from postgres.js, pg-pool, etc. * 3. **Queue management** - FIFO request queuing with configurable timeouts * * ## Key Features * * - **Configurable pool size** - Defaults to 1 (optimal for Workers with max_connections=1) * - **FIFO queue** - Requests are processed in order with timeout support * - **Automatic cleanup** - Clients released after query/transaction completion * - **Transaction support** - Full isolation level and read-only transaction support * - **Statistics tracking** - Real-time pool metrics for monitoring * - **Idempotent operations** - Safe to call release() multiple times * * ## Memory Optimization * * For Cloudflare Workers (128MB limit), use maxSize=1 to serialize operations. * This prevents memory spikes from concurrent queries and aligns with PGLite's * max_connections=1 optimization in WASM builds. * * @example Basic usage * ```typescript * import { PGlitePool } from './pool' * * const pool = new PGlitePool({ * pglite: myPGLiteInstance, * maxSize: 1, // Optimal for Workers (max_connections=1) * acquireTimeout: 30000, * }) * * // Simple query - client automatically acquired and released * const result = await pool.query('SELECT * FROM users WHERE id = $1', [1]) * ``` * * @example Transactions * ```typescript * // Transaction with automatic commit/rollback * const user = await pool.transaction(async (client) => { * await client.query('INSERT INTO users (name) VALUES ($1)', ['Alice']) * return await client.query('SELECT * FROM users WHERE name = $1', ['Alice']) * }) * ``` * * @example Manual client management * ```typescript * // Manual acquire/release when you need more control * const client = await pool.acquire() * try { * await client.query('SELECT 1') * await client.query('SELECT 2') * } finally { * pool.release(client) * } * * await pool.end() * ``` * * @example Statistics monitoring * ```typescript * const stats = pool.getStats() * console.log(`Active: ${stats.activeCount}/${stats.maxSize}`) * console.log(`Waiting: ${stats.waitingCount}`) * ``` * * @module */ /** * PGLite-like interface that the pool works with. * * This interface defines the minimal contract required from a PGLite instance. * It allows the pool to work with different PGLite variants (full, tiny) and * enables easy mocking in tests. * * @remarks * The pool does not own the PGLite instance lifecycle - call {@link PGlitePool.destroy} * if you want the pool to also close the underlying connection. */ interface PGLiteLike { /** * Promise that resolves when PGLite is ready to accept queries. * The pool awaits this before creating clients. */ waitReady: Promise; /** * Synchronous ready check. True after waitReady resolves. */ ready: boolean; /** * Execute a SQL query with optional parameters. * * @typeParam T - The row type for the query result * @param sql - SQL query string with $1, $2, etc. placeholders * @param params - Parameter values to bind to placeholders * @returns Query result with rows, field metadata, and affected row count */ query(sql: string, params?: unknown[]): Promise<{ rows: T[]; fields: Array<{ name: string; dataTypeID: number; }>; affectedRows?: number; }>; /** * Close the PGLite connection and release resources. */ close(): Promise; } /** * Query result from a pool client operation. * * @typeParam T - The row type for typed queries */ interface PoolQueryResult { /** Array of result rows, typed as T */ rows: T[]; /** Field metadata including column names and PostgreSQL type OIDs */ fields: Array<{ name: string; dataTypeID: number; }>; /** Number of rows affected (for INSERT/UPDATE/DELETE) */ affectedRows?: number; } /** * Pool client that wraps access to the underlying PGLite connection. * * Clients are acquired from the pool, used for queries, and then released back. * The pool tracks client state to prevent use-after-release bugs. * * @example * ```typescript * const client = await pool.acquire() * try { * const result = await client.query('SELECT * FROM users WHERE id = $1', [1]) * console.log(result.rows[0]) * } finally { * pool.release(client) * } * ``` */ interface PoolClient { /** Unique client ID within the pool (monotonically increasing) */ readonly id: number; /** Whether this client is currently in use (acquired but not released) */ inUse: boolean; /** Reference to the underlying PGLite instance */ readonly pglite: PGLiteLike; /** Timestamp when the client was acquired, undefined when idle */ acquiredAt?: Date | undefined; /** * Execute a query using this client. * * @typeParam T - The expected row type for typed results * @param sql - SQL query string with $1, $2, etc. placeholders * @param params - Parameter values to bind to placeholders * @returns Query result with typed rows * @throws {PoolError} If the client has been released (use-after-release) * * @example * ```typescript * const result = await client.query( * 'SELECT * FROM users WHERE id = $1', * [userId] * ) * ``` */ query(sql: string, params?: unknown[]): Promise>; } /** * PostgreSQL transaction isolation levels. * * @see {@link https://www.postgresql.org/docs/current/transaction-iso.html PostgreSQL Isolation Levels} * * - `'read uncommitted'` - Allows dirty reads (PostgreSQL treats as read committed) * - `'read committed'` - Default level, prevents dirty reads * - `'repeatable read'` - Snapshot isolation, prevents non-repeatable reads * - `'serializable'` - Strictest level, prevents phantom reads */ type PoolIsolationLevel = 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable'; /** * Options for transaction execution. */ interface PoolTransactionOptions { /** * Transaction isolation level. * Higher isolation levels provide stronger consistency guarantees * but may reduce concurrency. * * @default 'read committed' (PostgreSQL default) */ isolationLevel?: PoolIsolationLevel; /** * Whether the transaction is read-only. * Read-only transactions can be optimized by the database engine * and will error if write operations are attempted. * * @default false */ readOnly?: boolean; } /** * Configuration for creating a PGLite pool. */ interface PGlitePoolConfig { /** * The PGLite instance to pool. * The pool does not own this instance - it will not be closed * when {@link PGlitePool.end} is called. Use {@link PGlitePool.destroy} * to close both the pool and the underlying PGLite instance. */ pglite: PGLiteLike; /** * Maximum number of virtual clients in the pool. * * Since PGLite runs in-process (no network), this effectively controls * how many concurrent operations can execute before new requests queue. * * **Cloudflare Workers recommendation:** Use `maxSize: 1` to serialize * operations and prevent memory spikes from concurrent WASM execution. * This aligns with PGLite's `max_connections=1` optimization. * * @default 1 */ maxSize?: number; /** * Timeout in milliseconds for acquiring a client from the pool. * * When the pool is at capacity, new acquire requests wait in a queue. * If a client cannot be acquired within this timeout, the request fails * with a {@link PoolError}. * * Set to `0` for no timeout (wait indefinitely - not recommended for production). * * @default 30000 (30 seconds) */ acquireTimeout?: number; } /** * Real-time pool statistics for monitoring and debugging. * * Use {@link PGlitePool.getStats} to retrieve current statistics. * * @example * ```typescript * const stats = pool.getStats() * * // Check pool utilization * const utilization = stats.activeCount / stats.maxSize * * // Alert if requests are queuing * if (stats.waitingCount > 10) { * console.warn('High queue depth:', stats.waitingCount) * } * ``` */ interface PoolStats { /** Total number of clients created (active + idle) */ totalCount: number; /** Number of idle clients ready to be acquired */ idleCount: number; /** Number of clients currently in use */ activeCount: number; /** Number of requests waiting in queue for a client */ waitingCount: number; /** Maximum pool size from configuration */ maxSize: number; } /** * PGLite Connection Pool. * * Manages virtual connections to a PGLite instance, providing connection * pooling semantics even though PGLite is a single-connection in-process * database. * * @see {@link PGlitePoolConfig} for configuration options * @see {@link PoolStats} for available statistics */ declare class PGlitePool { private readonly pglite; private readonly maxSize; private readonly acquireTimeout; /** All clients in the pool (by ID) */ private readonly clients; /** Stack of idle clients available for acquisition (LIFO for cache locality) */ private readonly idleClients; /** Queue of requests waiting for a client (FIFO for fairness) */ private readonly waitingRequests; /** Auto-incrementing client ID counter */ private nextClientId; /** Whether the pool has been closed via end() or destroy() */ private closed; /** Cached ready promise to avoid re-awaiting */ private readyPromise; /** * Create a new PGLite connection pool. * * @param config - Pool configuration options * * @example * ```typescript * const pool = new PGlitePool({ * pglite: myPGLiteInstance, * maxSize: 1, * acquireTimeout: 30000, * }) * ``` */ constructor(config: PGlitePoolConfig); /** * Total number of clients created in the pool. * Clients are created lazily as needed, up to maxSize. */ get totalCount(): number; /** * Number of idle clients available for immediate acquisition. */ get idleCount(): number; /** * Number of clients currently in use (acquired but not released). */ get activeCount(): number; /** * Number of requests waiting in queue for a client. * Non-zero indicates pool is at capacity. */ get waitingCount(): number; /** * Whether the pool has been closed via {@link end} or {@link destroy}. * Closed pools reject all new acquire requests. */ get isClosed(): boolean; /** * Get a snapshot of current pool statistics. * * @returns Current pool statistics for monitoring * * @example * ```typescript * const stats = pool.getStats() * console.log(`Pool: ${stats.activeCount}/${stats.maxSize} active, ${stats.waitingCount} waiting`) * ``` */ getStats(): PoolStats; /** * Check if PGLite is ready (synchronous check). * Used to determine if we can skip the async ready wait. */ private isReady; /** * Ensure PGLite is ready before operations. * Caches the ready promise to avoid re-awaiting. */ private ensureReady; /** * Create a new pool client. * Called when pool capacity allows expansion. */ private createClient; /** * Create a queued request that waits for a client with timeout. * This encapsulates the common pattern of timeout-bounded waiting. * * @returns Promise that resolves with a client or rejects on timeout */ private createQueuedRequest; /** * Acquire a client from the pool. * * If a client is immediately available (idle client exists or pool can grow), * the acquisition is nearly instantaneous. Otherwise, the request is queued * and waits until a client becomes available or the timeout is reached. * * **Important:** Always release the client when done, preferably using * try/finally to ensure release even on errors. * * @returns A pool client that must be released after use * @throws {PoolError} If the pool is closed * @throws {PoolError} If timeout is reached while waiting for a client * * @example * ```typescript * const client = await pool.acquire() * try { * await client.query('SELECT * FROM users') * } finally { * pool.release(client) * } * ``` */ acquire(): Promise; /** * Internal async acquisition logic. * Handles the full flow: wait for ready, check idle, create new, or queue. */ private acquireAsync; /** * Release a client back to the pool. * * This operation is idempotent - calling release() multiple times on the * same client is safe and will only have effect the first time. * * When released, if there are queued requests waiting for a client, * the first waiting request receives this client immediately (FIFO). * Otherwise, the client is added to the idle pool for reuse. * * @param client - The client to release (must have been acquired from this pool) * @throws {PoolError} If the client is not from this pool (wrong pool or invalid) * * @example * ```typescript * const client = await pool.acquire() * try { * await client.query('SELECT 1') * } finally { * pool.release(client) // Always release, even on error * } * ``` */ release(client: PoolClient): void; /** * Execute a query using a pooled client. * * This is the simplest way to execute queries - the client is automatically * acquired before the query and released after, even if the query fails. * * For multiple queries in sequence, consider using {@link acquire} manually * or {@link transaction} to avoid acquire/release overhead per query. * * @typeParam T - The expected row type for typed results * @param sql - SQL query string with $1, $2, etc. placeholders * @param params - Parameter values to bind to placeholders * @returns Query result with typed rows * @throws {PoolError} If client acquisition fails * @throws {Error} If the query execution fails * * @example * ```typescript * // Simple query * const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]) * * // Typed query * interface User { id: number; name: string } * const { rows } = await pool.query('SELECT * FROM users') * const user = rows[0] // Type: User * ``` */ query(sql: string, params?: unknown[]): Promise>; /** Valid PostgreSQL isolation levels (lowercase for comparison) */ private static readonly VALID_ISOLATION_LEVELS; /** * Build the BEGIN statement for a transaction with options. * * @param options - Transaction options * @returns SQL string for BEGIN with isolation level and access mode * @throws {Error} If isolation level is invalid */ private buildBeginStatement; /** * Execute a transaction using a pooled client. * * The client is held for the duration of the transaction and automatically * released when complete. The transaction is committed on success or rolled * back if the callback throws an error. * * @typeParam T - The return type of the transaction callback * @param fn - Transaction callback function that receives the client * @param options - Transaction options (isolation level, read-only) * @returns Result of the transaction callback * @throws {PoolError} If client acquisition fails * @throws {Error} If isolation level validation fails * @throws {Error} Any error thrown by the callback (transaction is rolled back) * * @example * ```typescript * // Basic transaction * const result = await pool.transaction(async (client) => { * await client.query('INSERT INTO users (name) VALUES ($1)', ['Alice']) * const { rows } = await client.query('SELECT * FROM users WHERE name = $1', ['Alice']) * return rows[0] * }) * * // Transaction with options * await pool.transaction(async (client) => { * const { rows } = await client.query('SELECT * FROM accounts') * return rows * }, { isolationLevel: 'serializable', readOnly: true }) * ``` */ transaction(fn: (client: PoolClient) => Promise, options?: PoolTransactionOptions): Promise; /** * Close the pool and release all clients. * * This operation: * 1. Marks the pool as closed (rejects new acquire requests) * 2. Rejects all pending queued requests with PoolError * 3. Clears all client references * * The underlying PGLite instance is NOT closed - use {@link destroy} * if you want to close both the pool and the PGLite connection. * * This operation is idempotent - calling end() multiple times is safe. * * @example * ```typescript * // Clean shutdown * await pool.end() * // Pool is now closed, but PGLite can still be used directly * ``` */ end(): Promise; /** * Destroy the pool and close the underlying PGLite instance. * * This is the "full shutdown" method that: * 1. Calls {@link end} to close the pool * 2. Calls close() on the PGLite instance * * After destroy(), neither the pool nor the PGLite instance can be used. * * @example * ```typescript * // Full cleanup when completely done with the database * await pool.destroy() * ``` */ destroy(): Promise; } /** * Query Middleware Layer for postgres.do * * Provides a middleware chain pattern for intercepting and transforming queries. * Enables cross-cutting concerns like logging, metrics, rate limiting, and tracing. * * ## Architecture * * Middleware functions follow an "onion" pattern where each middleware wraps * the next one in the chain. Execution flows inward through all middlewares, * reaches the executor, then flows outward back through the middlewares. * * ``` * Request → [Middleware A] → [Middleware B] → [Executor] * Response ← [Middleware A] ← [Middleware B] ← * ``` * * ## Creating Custom Middleware * * A middleware is an async function that receives a request and a `next` function. * Call `next()` to proceed to the next middleware or executor. You can: * * - Modify the request before calling `next()` * - Modify the response after calling `next()` * - Short-circuit by returning a response without calling `next()` * - Handle errors by wrapping `next()` in try/catch * * @example Basic usage * ```typescript * import postgres from 'postgres.do' * import { loggingMiddleware, metricsMiddleware } from 'postgres.do/middleware' * * const sql = postgres('postgres://db.postgres.do/mydb') * .use(loggingMiddleware({ level: 'debug' })) * .use(metricsMiddleware({ onMetric: console.log })) * * // All queries now flow through the middleware chain * const users = await sql`SELECT * FROM users` * ``` * * @example Custom middleware * ```typescript * import type { QueryMiddleware } from 'postgres.do/middleware' * * const myMiddleware: QueryMiddleware = async (request, next) => { * console.log('Before query:', request.sql) * const response = await next() * console.log('After query:', response.durationMs, 'ms') * return response * } * ``` * * @module middleware */ /** * Query request passed to middleware. * * Contains all information about a query before execution. * Middleware can read and modify this object before passing to `next()`. * * @example * ```typescript * const middleware: QueryMiddleware = async (request, next) => { * console.log('Query:', request.sql) * console.log('Params:', request.params) * console.log('Query ID:', request.queryId) * return next() * } * ``` */ interface QueryRequest { /** The SQL query string (with parameter placeholders) */ sql: string; /** Query parameters */ params: unknown[]; /** Timestamp when the query was initiated */ timestamp: number; /** Unique ID for this query (useful for tracing) */ queryId: string; /** Optional context passed through the middleware chain */ context?: Record; } /** * Query response returned from middleware. * * Contains the result of query execution along with timing and status information. * Middleware can modify this object before returning it to the caller. * * @example * ```typescript * const middleware: QueryMiddleware = async (request, next) => { * const response = await next() * if (!response.success) { * console.error('Query failed:', response.error) * } * return response * } * ``` */ interface QueryResponse { /** Query result */ result: QueryResult; /** Duration in milliseconds */ durationMs: number; /** Was the query successful */ success: boolean; /** Error if query failed */ error?: Error; /** Optional metadata added by middleware */ metadata?: Record; } /** * Next function to call the next middleware in the chain. * * Calling `next()` invokes the next middleware in the chain, or the query * executor if this is the last middleware. Always returns a Promise that * resolves to a QueryResponse. * * @remarks * - You must call `next()` exactly once to continue the chain * - Not calling `next()` short-circuits the chain (useful for caching, auth) * - Calling `next()` multiple times may cause unexpected behavior */ type NextFunction = () => Promise>; /** * Query middleware function signature. * * Middleware functions receive a request and a `next` function, and must return * a Promise that resolves to a QueryResponse. This is the core type for building * custom middleware. * * @param request - The query request containing SQL, params, and context * @param next - Function to call the next middleware or executor * @returns The query response (possibly transformed) * * @example Simple logging middleware * ```typescript * const loggingMiddleware: QueryMiddleware = async (request, next) => { * console.log('Query:', request.sql) * const response = await next() * console.log('Duration:', response.durationMs, 'ms') * return response * } * ``` * * @example Error handling middleware * ```typescript * const errorHandler: QueryMiddleware = async (request, next) => { * try { * return await next() * } catch (error) { * // Handle or transform the error * return { * result: { rows: [], rowCount: 0, fields: [], command: 'ERROR' }, * durationMs: 0, * success: false, * error: error instanceof Error ? error : new Error(String(error)), * } * } * } * ``` */ type QueryMiddleware = (request: QueryRequest, next: NextFunction) => Promise>; /** * Generate a unique query ID for tracing and logging. * * The ID format is `q__` where: * - `q_` is a fixed prefix for easy identification * - `` is the current time in base-36 encoding * - `` is a random string for uniqueness within the same millisecond * * @returns A unique query identifier string * * @example * ```typescript * const queryId = generateQueryId() * // => "q_lz4k8x_abc123d" * ``` */ declare function generateQueryId(): string; /** * Execute a query through a middleware chain. * * This is the core function that orchestrates middleware execution. * It builds a chain from the provided middlewares using a right-to-left * reduction, where each middleware wraps the next one. * * @param middlewares - Array of middleware functions to execute in order * @param request - The query request containing SQL, params, and context * @param executor - The final query executor function that runs the actual query * @returns The query response, possibly transformed by middlewares * * @remarks * - Middlewares execute in array order (first middleware runs first) * - Errors in the executor are caught and returned as failed responses * - Errors thrown by middleware propagate up and must be caught by the caller * * @example * ```typescript * const response = await executeWithMiddleware( * [loggingMiddleware(), metricsMiddleware({ onMetric: console.log })], * { sql: 'SELECT 1', params: [], timestamp: Date.now(), queryId: 'q_123' }, * async () => ({ rows: [{ id: 1 }], rowCount: 1, fields: [], command: 'SELECT' }) * ) * ``` */ declare function executeWithMiddleware(middlewares: QueryMiddleware[], request: QueryRequest, executor: () => Promise>): Promise>; /** * Options for configuring the logging middleware. */ interface LoggingMiddlewareOptions { /** Log level: 'debug' | 'info' | 'warn' | 'error'. Defaults to 'info'. */ level?: 'debug' | 'info' | 'warn' | 'error'; /** Custom logger function. Defaults to console methods. */ logger?: (level: string, message: string, data?: Record) => void; /** Whether to log query parameters (default: false for security) */ logParams?: boolean; /** Only log queries slower than this threshold (ms). Omit to log all queries. */ slowQueryThreshold?: number; /** Custom prefix for log messages. Defaults to '[postgres.do]'. */ prefix?: string; } /** * Create a logging middleware for query observability. * * Logs query execution details including SQL, duration, row count, and errors. * By default, query parameters are not logged for security reasons. * * @param options - Configuration options for the logging middleware * @returns A middleware function that logs query information * * @example Basic usage * ```typescript * const sql = postgres() * .use(loggingMiddleware({ level: 'info', logParams: false })) * ``` * * @example Slow query logging * ```typescript * const sql = postgres() * .use(loggingMiddleware({ * slowQueryThreshold: 100, // Only log queries taking > 100ms * level: 'warn' * })) * ``` * * @example Custom logger * ```typescript * const sql = postgres() * .use(loggingMiddleware({ * logger: (level, message, data) => { * myLogger[level](message, data) * } * })) * ``` */ declare function loggingMiddleware(options?: LoggingMiddlewareOptions): QueryMiddleware; /** * Options for configuring the metrics middleware. */ interface MetricsMiddlewareOptions { /** Callback invoked for each metric event. Required. */ onMetric: (metric: QueryMetric) => void; /** Custom tags to add to all metrics. */ tags?: Record; /** Whether to include histogram bucket information. Defaults to false. */ histogram?: boolean; /** Custom histogram bucket boundaries (ms). Defaults to standard latency percentiles. */ buckets?: number[]; } /** * Query metric data emitted by the metrics middleware. * * Each query execution emits multiple metrics: * - `query_duration`: Time taken to execute the query (ms) * - `query_count`: Incremented for each query (value is always 1) * - `query_rows`: Number of rows returned * - `query_error`: Incremented for failed queries (value is always 1) */ interface QueryMetric { /** Metric type */ type: 'query_duration' | 'query_count' | 'query_error' | 'query_rows'; /** Metric value */ value: number; /** Query ID for correlation */ queryId: string; /** SQL query (truncated to prevent memory bloat) */ query: string; /** Whether the query succeeded */ success: boolean; /** Command type (SELECT, INSERT, UPDATE, DELETE, etc.) */ command: string; /** Timestamp when the metric was recorded */ timestamp: number; /** Custom tags */ tags?: Record; /** Histogram bucket label (if enabled) */ bucket?: string; } /** * Create a metrics middleware for query instrumentation. * * Emits metrics for query duration, count, row count, and errors. * Useful for monitoring, alerting, and performance analysis. * * @param options - Configuration options for the metrics middleware * @returns A middleware function that emits query metrics * * @example Basic usage * ```typescript * const metrics: QueryMetric[] = [] * const sql = postgres() * .use(metricsMiddleware({ * onMetric: (m) => metrics.push(m), * tags: { service: 'api', env: 'production' } * })) * ``` * * @example With histogram buckets * ```typescript * const sql = postgres() * .use(metricsMiddleware({ * onMetric: (m) => prometheus.observe(m.type, m.value, m.tags), * histogram: true, * buckets: [5, 10, 25, 50, 100, 250, 500, 1000] * })) * ``` */ declare function metricsMiddleware(options: MetricsMiddlewareOptions): QueryMiddleware; /** * Options for configuring the rate limiting middleware. */ interface RateLimitMiddlewareOptions { /** Maximum queries allowed per window. Required. */ maxQueries: number; /** Window size in milliseconds. Required. */ windowMs: number; /** Function to determine the rate limit key (e.g., by user). Defaults to 'default'. */ keyFn?: (request: QueryRequest) => string; /** Callback invoked when rate limit is exceeded. */ onRateLimited?: (request: QueryRequest, remainingSeconds: number) => void; } /** * Create a rate limiting middleware to prevent query abuse. * * Uses a sliding window algorithm to track query counts per key. * When the limit is exceeded, returns an error response with retry information. * * @param options - Configuration options for the rate limiting middleware * @returns A middleware function that enforces rate limits * * @example Basic usage * ```typescript * const sql = postgres() * .use(rateLimitMiddleware({ * maxQueries: 100, * windowMs: 60000, // 1 minute * })) * ``` * * @example Per-user rate limiting * ```typescript * const sql = postgres() * .use(rateLimitMiddleware({ * maxQueries: 100, * windowMs: 60000, * keyFn: (req) => req.context?.userId as string || 'anonymous', * onRateLimited: (req, remaining) => { * console.warn(`Rate limited user ${req.context?.userId}, retry in ${remaining}s`) * } * })) * ``` */ declare function rateLimitMiddleware(options: RateLimitMiddlewareOptions): QueryMiddleware; /** * Options for configuring the tracing middleware. */ interface TracingMiddlewareOptions { /** Trace ID: a static string, generator function, or undefined to use context/auto-generate. */ traceId?: string | ((request: QueryRequest) => string); /** Span ID generator function. Defaults to random base-36 string. */ spanId?: () => string; /** Callback invoked with trace data after each query. */ onTrace?: (trace: QueryTrace) => void; } /** * Query trace data for distributed tracing. * * Compatible with OpenTelemetry and similar tracing systems. */ interface QueryTrace { /** Unique trace identifier (spans same trace across services) */ traceId: string; /** Unique span identifier for this query */ spanId: string; /** Parent span ID if this query is part of a larger operation */ parentSpanId?: string | undefined; /** Query identifier for correlation with other middleware */ queryId: string; /** SQL query string */ sql: string; /** Unix timestamp when query started */ startTime: number; /** Unix timestamp when query completed */ endTime: number; /** Query duration in milliseconds */ durationMs: number; /** Whether the query succeeded */ success: boolean; /** Error message if query failed */ error?: string | undefined; } /** * Create a tracing middleware for distributed tracing. * * Generates trace and span IDs for each query, enabling correlation * across services and query analysis. * * @param options - Configuration options for the tracing middleware * @returns A middleware function that adds trace information * * @example Basic usage * ```typescript * const sql = postgres() * .use(tracingMiddleware({ * onTrace: (trace) => tracer.export(trace) * })) * ``` * * @example With OpenTelemetry * ```typescript * const sql = postgres() * .use(tracingMiddleware({ * traceId: (req) => req.context?.traceId as string, * onTrace: (trace) => { * const span = tracer.startSpan('postgres.query', { * attributes: { 'db.statement': trace.sql } * }) * span.end() * } * })) * ``` */ declare function tracingMiddleware(options?: TracingMiddlewareOptions): QueryMiddleware; /** * Options for configuring the transformation middleware. */ interface TransformMiddlewareOptions { /** Transform the SQL query and params before execution. */ transformQuery?: (sql: string, params: unknown[]) => { sql: string; params: unknown[]; }; /** Transform the result after successful execution. */ transformResult?: (result: QueryResult) => QueryResult; } /** * Create a query transformation middleware. * * Allows modifying queries before execution and results after execution. * Useful for query rewriting, result mapping, and data sanitization. * * @param options - Configuration options for the transformation middleware * @returns A middleware function that transforms queries and/or results * * @example Query rewriting * ```typescript * const sql = postgres() * .use(transformMiddleware({ * transformQuery: (query, params) => ({ * sql: query.replace('SELECT *', 'SELECT id, name, email'), * params * }) * })) * ``` * * @example Result transformation * ```typescript * const sql = postgres() * .use(transformMiddleware({ * transformResult: (result) => ({ * ...result, * rows: result.rows.map(row => ({ * ...row, * createdAt: new Date(row.created_at) * })) * }) * })) * ``` */ declare function transformMiddleware(options: TransformMiddlewareOptions): QueryMiddleware; /** * Options for configuring the retry middleware. */ interface RetryMiddlewareOptions { /** Maximum number of retry attempts. Defaults to 3. */ maxRetries?: number; /** Base delay between retries in milliseconds. Defaults to 100. */ baseDelayMs?: number; /** Maximum delay between retries in milliseconds. Defaults to 5000. */ maxDelayMs?: number; /** Whether to use exponential backoff. Defaults to true. */ exponentialBackoff?: boolean; /** Function to determine if an error is retryable. Defaults to always true. */ isRetryable?: (error: Error) => boolean; /** Callback invoked before each retry attempt. */ onRetry?: (attempt: number, error: Error, request: QueryRequest) => void; } /** * Create a retry middleware with exponential backoff. * * Automatically retries failed queries based on configurable rules. * Supports exponential backoff to prevent overwhelming the database. * * @param options - Configuration options for the retry middleware * @returns A middleware function that retries failed queries * * @example Basic usage * ```typescript * const sql = postgres() * .use(retryMiddleware({ * maxRetries: 3, * baseDelayMs: 100, * })) * ``` * * @example Selective retry * ```typescript * const sql = postgres() * .use(retryMiddleware({ * maxRetries: 5, * baseDelayMs: 200, * isRetryable: (error) => { * // Only retry connection errors * return error.message.includes('connection') || * error.message.includes('timeout') * }, * onRetry: (attempt, error) => { * console.warn(`Retry attempt ${attempt}: ${error.message}`) * } * })) * ``` */ declare function retryMiddleware(options?: RetryMiddlewareOptions): QueryMiddleware; /** * Combine multiple middlewares into a single middleware. * * Useful for creating reusable middleware bundles or organizing * middleware by concern. * * @param middlewares - Middleware functions to compose * @returns A single middleware that executes all provided middlewares in order * * @example Creating a middleware bundle * ```typescript * const observabilityMiddleware = composeMiddleware( * loggingMiddleware({ level: 'info' }), * metricsMiddleware({ onMetric: console.log }), * tracingMiddleware({ onTrace: tracer.export }) * ) * * const sql = postgres().use(observabilityMiddleware) * ``` * * @example Conditional composition * ```typescript * const prodMiddleware = composeMiddleware( * metricsMiddleware({ onMetric: prometheus.record }), * rateLimitMiddleware({ maxQueries: 1000, windowMs: 60000 }) * ) * * const devMiddleware = composeMiddleware( * loggingMiddleware({ level: 'debug', logParams: true }) * ) * * const sql = postgres() * .use(process.env.NODE_ENV === 'production' ? prodMiddleware : devMiddleware) * ``` */ declare function composeMiddleware(...middlewares: QueryMiddleware[]): QueryMiddleware; export { type AuthenticatedClientOptions, type AuthenticatedContext, CDCChangeEvent, CDCSseTransport, CDCSseTransportConfig, CDCSubscription, CDCSubscriptionOptions, CDCTransport, CDCWsTransport, CDCWsTransportConfig, type ConnectionInfo, Cursor, HttpTransport, HttpTransportConfig, type LoggingMiddlewareOptions, type MetricsMiddlewareOptions, type NextFunction, type PGLiteLike, PGlitePool, type PGlitePoolConfig, type PoolClient, type PoolIsolationLevel, type PoolQueryResult, type PoolStats, type PoolTransactionOptions, PostgresConfig, type QueryMetric, type QueryMiddleware, type QueryRequest, type QueryResponse, QueryResult, type QueryTrace, type RateLimitMiddlewareOptions, type RetryMiddlewareOptions, Row, Sql, StreamOptions, StreamResult, type SubscribeOptions, type Subscription, type TracingMiddlewareOptions, TransactionOptions, type TransformMiddlewareOptions, Transport, TypeParser, type TypedSubscribeOptions, buildParameterizedSql as buildStreamSql, composeMiddleware, createAuthenticatedClient, createCdcSseTransport, createCdcWsTransport, createClient, createClientFromRequest, createCursor, createHttpTransport, createStreamResult, createUserScopedClient, postgres as default, executeWithMiddleware, extractUserId, generateQueryId, generateSubscriptionId, loggingMiddleware, metricsMiddleware, parseConnectionUrl, postgres, rateLimitMiddleware, retryMiddleware, tracingMiddleware, transformMiddleware, validateTableName, validateToken, withAuth };