@warlock.js/cascade 5.15.0 → 5.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +261 -254
- package/cjs/index.cjs +131 -30
- package/cjs/index.cjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +12 -6
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +36 -10
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/types.d.mts +12 -2
- package/esm/drivers/postgres/types.d.mts.map +1 -1
- package/esm/errors/undefined-where-value.error.d.mts +32 -0
- package/esm/errors/undefined-where-value.error.d.mts.map +1 -0
- package/esm/errors/undefined-where-value.error.mjs +38 -0
- package/esm/errors/undefined-where-value.error.mjs.map +1 -0
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +3 -2
- package/esm/query-builder/query-builder.d.mts.map +1 -1
- package/esm/query-builder/query-builder.mjs +23 -11
- package/esm/query-builder/query-builder.mjs.map +1 -1
- package/esm/utils/sanitize-filter.d.mts +21 -6
- package/esm/utils/sanitize-filter.d.mts.map +1 -1
- package/esm/utils/sanitize-filter.mjs +26 -6
- package/esm/utils/sanitize-filter.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -52,7 +52,8 @@ function buildPostgresPoolConfig(config) {
|
|
|
52
52
|
max: config.max ?? 10,
|
|
53
53
|
min: config.min ?? 0,
|
|
54
54
|
idleTimeoutMillis: config.idleTimeoutMillis ?? 3e4,
|
|
55
|
-
connectionTimeoutMillis: config.connectionTimeoutMillis ??
|
|
55
|
+
connectionTimeoutMillis: config.connectionTimeoutMillis ?? 1e4,
|
|
56
|
+
keepAlive: config.keepAlive ?? true,
|
|
56
57
|
application_name: config.application_name ?? "cascade",
|
|
57
58
|
ssl: config.ssl
|
|
58
59
|
};
|
|
@@ -226,6 +227,9 @@ var PostgresDriver = class {
|
|
|
226
227
|
const poolConfig = buildPostgresPoolConfig(this.config);
|
|
227
228
|
log.info("database.postgres", "connection", `Connecting to database ${colors.bold(colors.yellowBright(poolConfig.database ?? ""))}`);
|
|
228
229
|
this._pool = new pg.Pool(poolConfig);
|
|
230
|
+
this._pool.on("error", (error) => {
|
|
231
|
+
log.error("database.postgres", "pool", `Unexpected error on an idle client: ${error.message}`);
|
|
232
|
+
});
|
|
229
233
|
(await this._pool.connect()).release();
|
|
230
234
|
log.success("database.postgres", "connection", `Connected to database ${colors.bold(colors.yellowBright(this.config.database))}`);
|
|
231
235
|
this._isConnected = true;
|
|
@@ -749,19 +753,34 @@ var PostgresDriver = class {
|
|
|
749
753
|
*/
|
|
750
754
|
async beginTransaction(options) {
|
|
751
755
|
const client = await this.pool.connect();
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
756
|
+
try {
|
|
757
|
+
let beginSql = "BEGIN";
|
|
758
|
+
if (options?.isolationLevel) beginSql += ` ISOLATION LEVEL ${options.isolationLevel.toUpperCase()}`;
|
|
759
|
+
if (options?.readOnly) beginSql += " READ ONLY";
|
|
760
|
+
if (options?.deferrable) beginSql += " DEFERRABLE";
|
|
761
|
+
await client.query(beginSql);
|
|
762
|
+
} catch (error) {
|
|
763
|
+
client.release(error instanceof Error ? error : new Error(String(error)));
|
|
764
|
+
throw error;
|
|
765
|
+
}
|
|
757
766
|
return {
|
|
758
767
|
context: client,
|
|
759
768
|
commit: async () => {
|
|
760
|
-
|
|
769
|
+
try {
|
|
770
|
+
await client.query("COMMIT");
|
|
771
|
+
} catch (error) {
|
|
772
|
+
client.release(error instanceof Error ? error : new Error(String(error)));
|
|
773
|
+
throw error;
|
|
774
|
+
}
|
|
761
775
|
client.release();
|
|
762
776
|
},
|
|
763
777
|
rollback: async () => {
|
|
764
|
-
|
|
778
|
+
try {
|
|
779
|
+
await client.query("ROLLBACK");
|
|
780
|
+
} catch (error) {
|
|
781
|
+
client.release(error instanceof Error ? error : new Error(String(error)));
|
|
782
|
+
throw error;
|
|
783
|
+
}
|
|
765
784
|
client.release();
|
|
766
785
|
}
|
|
767
786
|
};
|
|
@@ -784,13 +803,20 @@ var PostgresDriver = class {
|
|
|
784
803
|
if (databaseTransactionContext.hasActiveTransaction()) return fn(ctx);
|
|
785
804
|
const tx = await this.beginTransaction(options);
|
|
786
805
|
databaseTransactionContext.enter({ session: tx.context });
|
|
806
|
+
let commitStarted = false;
|
|
787
807
|
try {
|
|
788
808
|
const result = await fn(ctx);
|
|
809
|
+
commitStarted = true;
|
|
789
810
|
await tx.commit();
|
|
790
811
|
return result;
|
|
791
812
|
} catch (error) {
|
|
792
|
-
|
|
793
|
-
|
|
813
|
+
if (commitStarted) log.error(`database.postgress`, "transaction", "Transaction COMMIT failed; client discarded, no rollback issued");
|
|
814
|
+
else try {
|
|
815
|
+
await tx.rollback();
|
|
816
|
+
log.error(`database.postgress`, "transaction", "Transaction operation failed, rolled back everything");
|
|
817
|
+
} catch (rollbackError) {
|
|
818
|
+
log.error(`database.postgress`, "transaction", rollbackError);
|
|
819
|
+
}
|
|
794
820
|
throw error;
|
|
795
821
|
} finally {
|
|
796
822
|
databaseTransactionContext.exit();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-driver.mjs","names":[],"sources":["../../../../../../../../cascade/src/drivers/postgres/postgres-driver.ts"],"sourcesContent":["/**\n * PostgreSQL Driver\n *\n * Main driver implementation for PostgreSQL database operations.\n * Implements the DriverContract interface to provide a unified API\n * for CRUD operations, transactions, and query building.\n *\n * Uses the `pg` package for database connectivity with connection pooling.\n *\n * @module cascade/drivers/postgres\n */\n\nimport { colors } from \"@mongez/copper\";\nimport { log } from \"@warlock.js/logger\";\nimport { databaseTransactionContext } from \"../../context/database-transaction-context\";\nimport type {\n AtomicUpdate,\n CreateDatabaseOptions,\n DriverAtomicUpdateOptions,\n DriverContract,\n DriverEventListener,\n DriverTransactionContract,\n DropDatabaseOptions,\n InsertResult,\n TransactionContext,\n UpdateOperations,\n UpdateResult,\n} from \"../../contracts/database-driver.contract\";\nimport type { DriverBlueprintContract } from \"../../contracts/driver-blueprint.contract\";\nimport type { MigrationDriverContract } from \"../../contracts/migration-driver.contract\";\nimport type { QueryBuilderContract } from \"../../contracts/query-builder.contract\";\nimport type { SyncAdapterContract } from \"../../contracts/sync-adapter.contract\";\nimport { TransactionRollbackError } from \"../../errors/transaction-rollback.error\";\nimport { UnsupportedUpdateOperationError } from \"../../errors/unsupported-update-operation.error\";\nimport { SQLSerializer } from \"../../migration/sql-serializer\";\nimport { SqlDatabaseDirtyTracker } from \"../../sql-database-dirty-tracker\";\nimport type { ModelDefaults } from \"../../types\";\nimport { type DatabaseDriver } from \"../../utils/connect-to-database\";\nimport { isValidDateValue } from \"../../utils/is-valid-date-value\";\nimport { PostgresBlueprint } from \"./postgres-blueprint\";\nimport { PostgresDialect } from \"./postgres-dialect\";\nimport { PostgresMigrationDriver } from \"./postgres-migration-driver\";\nimport { PostgresQueryBuilder } from \"./postgres-query-builder\";\nimport { PostgresSQLSerializer } from \"./postgres-sql-serializer\";\nimport { PostgresSyncAdapter } from \"./postgres-sync-adapter\";\nimport { assertPostgresUpdate } from \"./postgres-update-validator\";\nimport type { PostgresPoolConfig, PostgresQueryResult, PostgresTransactionOptions } from \"./types\";\n\n/**\n * A unique index usable as an `ON CONFLICT` target.\n */\ntype UniqueKey = {\n /** Indexed columns, in index order. */\n columns: string[];\n /** Whether the index backs the primary key. */\n primary: boolean;\n};\n\n/**\n * Lazily loaded pg module types.\n */\ntype PgPool = import(\"pg\").Pool;\ntype PgPoolClient = import(\"pg\").PoolClient;\ntype PgPoolConfig = import(\"pg\").PoolConfig;\n\n/**\n * Build pg's `PoolConfig` from cascade's `PostgresPoolConfig`, coercing the\n * string-typed connection fields.\n *\n * `env()` (@mongez/dotenv) coerces a numeric-looking value to a number, so a\n * `DB_NAME` / `DB_USERNAME` / `DB_PASSWORD` like \"12345\" arrives here as a\n * number. pg's connection fields must be strings — a number reaches deep into\n * the driver and crashes with an inscrutable Buffer error rather than a\n * legible \"database does not exist\" (finding 51639bf8). So `host`, `database`,\n * `user` and `password` are coerced to strings; `port` is a real number and\n * `undefined` is left untouched so pg's own defaults still apply.\n *\n * Exported (not inlined into `connect()`) so the coercion is unit-testable\n * without a live database.\n */\nexport function buildPostgresPoolConfig(config: PostgresPoolConfig): PgPoolConfig {\n const asString = (value: unknown): string | undefined =>\n typeof value === \"number\" ? String(value) : (value as string | undefined);\n\n return {\n host: asString(config.host) ?? \"localhost\",\n port: config.port ?? 5432,\n database: asString(config.database),\n user: asString(config.user),\n password: asString(config.password),\n connectionString: config.connectionString,\n max: config.max ?? 10,\n min: config.min ?? 0,\n idleTimeoutMillis: config.idleTimeoutMillis ?? 30000,\n connectionTimeoutMillis: config.connectionTimeoutMillis ?? 2000,\n application_name: config.application_name ?? \"cascade\",\n ssl: config.ssl,\n };\n}\n\n/**\n * Cached pg module reference.\n */\nlet pgModule: typeof import(\"pg\") | undefined;\n\n/**\n * Lazily load the pg package.\n *\n * @returns The pg module\n * @throws Error if pg is not installed\n */\nasync function loadPg(): Promise<typeof import(\"pg\")> {\n if (pgModule) {\n return pgModule;\n }\n\n try {\n pgModule = await import(\"pg\");\n return pgModule;\n } catch {\n throw new Error(\n 'The \"pg\" package is required for PostgreSQL support. ' + \"Please install it: npm install pg\",\n );\n }\n}\n\n/**\n * PostgreSQL database driver implementing the Cascade DriverContract.\n *\n * Provides connection pooling, CRUD operations, transactions, and\n * integration with Cascade's query builder and migration systems.\n *\n * @example\n * ```typescript\n * const driver = new PostgresDriver({\n * host: 'localhost',\n * port: 5432,\n * database: 'myapp',\n * user: 'postgres',\n * password: 'secret'\n * });\n *\n * await driver.connect();\n *\n * // Insert a document\n * const result = await driver.insert('users', { name: 'Alice', email: 'alice@example.com' });\n *\n * // Query using the query builder\n * const users = await driver.queryBuilder('users')\n * .where('name', 'Alice')\n * .get();\n *\n * await driver.disconnect();\n * ```\n */\nexport class PostgresDriver implements DriverContract {\n /**\n * Driver name identifier.\n */\n public readonly name = \"postgres\" as DatabaseDriver;\n\n /**\n * SQL dialect for PostgreSQL-specific syntax.\n */\n public readonly dialect = new PostgresDialect();\n\n /**\n * PostgreSQL driver model defaults.\n *\n * PostgreSQL follows SQL conventions:\n * - snake_case naming for columns (created_at, updated_at, deleted_at)\n * - Native AUTO_INCREMENT for IDs (no manual generation)\n * - Timestamps enabled by default\n * - Permanent delete strategy (hard deletes)\n */\n public readonly modelDefaults: Partial<ModelDefaults> = {\n namingConvention: \"snake_case\",\n createdAtColumn: \"created_at\",\n updatedAtColumn: \"updated_at\",\n deletedAtColumn: \"deleted_at\",\n timestamps: true,\n autoGenerateId: false, // PostgreSQL uses SERIAL/BIGSERIAL\n strictMode: \"fail\",\n deleteStrategy: \"permanent\",\n };\n\n /**\n * Connection pool instance.\n */\n private _pool: PgPool | undefined;\n\n /**\n * Event listeners for driver lifecycle events.\n */\n private readonly _eventListeners = new Map<string, Set<DriverEventListener>>();\n\n /**\n * Whether the driver is currently connected.\n */\n private _isConnected = false;\n\n /**\n * Blueprint instance (lazy-loaded).\n */\n private _blueprint: DriverBlueprintContract | undefined;\n\n /**\n * Migration driver instance (lazy-loaded).\n */\n private _migrationDriver: MigrationDriverContract | undefined;\n\n /**\n * Sync adapter instance (lazy-loaded).\n */\n private _syncAdapter: SyncAdapterContract | undefined;\n\n /**\n * Explicit, table-agnostic override list of column names that hold native\n * PostgreSQL arrays (`JSONB[]`, `TEXT[]`, …) and must NOT be JSON-text\n * encoded. Merged with (and superseded per-table by) the schema\n * introspection below; kept as a manual escape hatch.\n *\n * @see PostgresPoolConfig.nativeArrayColumns\n */\n private readonly _nativeArrayColumns: ReadonlySet<string>;\n\n /** Unique keys per table, resolved from `pg_index` for upsert conflict targets. */\n private readonly _uniqueKeys = new Map<string, ReadonlyArray<UniqueKey>>();\n\n /**\n * Native-array columns discovered by introspecting the live schema on\n * connect, keyed `table → { column, … }`. Authoritative and table-scoped, so\n * a column that is `TEXT[]` in one table and `jsonb` in another is encoded\n * correctly for each — no app configuration required.\n */\n private _introspectedArrayColumns: ReadonlyMap<string, ReadonlySet<string>> = new Map();\n\n /**\n * Create a new PostgreSQL driver instance.\n *\n * @param config - PostgreSQL connection configuration\n */\n public constructor(private readonly config: PostgresPoolConfig) {\n this._nativeArrayColumns = new Set(config.nativeArrayColumns ?? []);\n }\n\n /**\n * Get the connection pool instance.\n *\n * @throws Error if not connected\n */\n public get pool(): PgPool {\n if (!this._pool) {\n throw new Error(\"PostgreSQL driver is not connected. Call connect() first.\");\n }\n return this._pool;\n }\n\n /**\n * Get database native client\n */\n public getClient<Client = PgPool>(): Client {\n return this.pool as Client;\n }\n\n /**\n * Check if the driver is currently connected.\n */\n public get isConnected(): boolean {\n return this._isConnected;\n }\n\n /**\n * Get the driver blueprint (information schema).\n */\n public get blueprint(): DriverBlueprintContract {\n if (!this._blueprint) {\n this._blueprint = new PostgresBlueprint(this);\n }\n return this._blueprint;\n }\n\n /**\n * Establish connection to the PostgreSQL database.\n *\n * Creates a connection pool with the configured options.\n * Emits 'connected' event on successful connection.\n */\n public async connect(): Promise<void> {\n if (this._isConnected) {\n return;\n }\n\n const pg = await loadPg();\n\n try {\n const poolConfig = buildPostgresPoolConfig(this.config);\n\n log.info(\n \"database.postgres\",\n \"connection\",\n `Connecting to database ${colors.bold(colors.yellowBright(poolConfig.database ?? \"\"))}`,\n );\n\n this._pool = new pg.Pool(poolConfig);\n\n // Test the connection\n const client = await this._pool.connect();\n client.release();\n\n log.success(\n \"database.postgres\",\n \"connection\",\n `Connected to database ${colors.bold(colors.yellowBright(this.config.database))}`,\n );\n\n this._isConnected = true;\n\n // Learn which columns are native arrays straight from the live schema so\n // the value serializer encodes them correctly with zero app config.\n await this.loadNativeArrayColumns();\n\n this.emit(\"connected\");\n } catch (error) {\n // Boot-time database connection failure is unrecoverable in every\n // realistic caller (app boot, CLI migrations, workers) — `fatal` makes\n // \"page on fatal only\" alerting clean. Per-query failures stay at error.\n log.fatal(\"database.postgres\", \"connection\", \"Failed to connect to database\");\n throw error;\n }\n }\n\n /**\n * Close the database connection pool.\n *\n * Waits for all active queries to complete before closing.\n * Emits 'disconnected' event on successful disconnection.\n */\n public async disconnect(): Promise<void> {\n if (!this._isConnected || !this._pool) {\n return;\n }\n\n await this._pool.end();\n this._pool = undefined;\n this._isConnected = false;\n this.emit(\"disconnected\");\n }\n\n /**\n * Register an event listener for driver lifecycle events.\n *\n * @param event - Event name ('connected', 'disconnected', etc.)\n * @param listener - Callback function to invoke\n */\n public on(event: string, listener: DriverEventListener): void {\n if (!this._eventListeners.has(event)) {\n this._eventListeners.set(event, new Set());\n }\n\n this._eventListeners.get(event)!.add(listener);\n }\n\n /**\n * Serialize data for storage in PostgreSQL.\n *\n * Handles Date objects, BigInt, and other JavaScript types\n * that need special handling for PostgreSQL storage.\n *\n * @param data - The data object to serialize\n * @param table - Optional table name; when given, columns introspected as\n * native arrays on that table are bound raw (see {@link serializeValue}).\n * @returns Serialized data ready for PostgreSQL\n */\n public serialize(\n data: Record<string, unknown>,\n table?: string,\n ): Record<string, unknown> {\n const serialized: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n if (value === undefined) {\n continue; // Skip undefined values\n }\n\n serialized[key] = this.serializeValue(key, value, table);\n }\n\n return serialized;\n }\n\n /**\n * Serialize a single column value into a node-pg bindable parameter.\n *\n * Shared by {@link serialize} (INSERT path) and {@link buildUpdateQuery}\n * `$set` (UPDATE path) so both encode `json` / `jsonb` columns identically.\n *\n * Encoding rules (in order):\n * - `Date` → ISO string.\n * - `bigint` → decimal string (node-pg has no native bigint binding).\n * - all-number array → pgvector literal `'[n1,n2,...]'`. node-pg would\n * otherwise emit a `{n1,n2,...}` array literal, which the `vector` type\n * rejects. This branch is preserved exactly.\n * - any other array (object-array, string-array, mixed, empty `[]`) →\n * `JSON.stringify`. node-pg renders a raw JS array as a PostgreSQL array\n * literal `{...}` (and `[]` as `{}`), which a `json` / `jsonb` column\n * rejects — so we bind the value as JSON text instead, the form those\n * columns accept. Columns known to be native arrays — via schema\n * introspection or the `nativeArrayColumns` config — are exempt: their raw\n * array is passed through so node-pg emits the `{...}` literal a genuine\n * `JSONB[]` / `TEXT[]` column needs.\n * - plain object → `JSON.stringify`. Equivalent to node-pg's own object\n * handling, made explicit so both write paths agree.\n * - everything else (scalars: string, number, boolean, null) → untouched.\n *\n * Distinguishing native-array from `json` / `jsonb` columns: a value alone\n * can't tell them apart, so the driver introspects the live schema on connect\n * (see {@link loadNativeArrayColumns}) and consults that per-table map here\n * via {@link isNativeArrayColumn}. The explicit `nativeArrayColumns` config\n * still works as a table-agnostic override. No `::jsonb` placeholder cast is\n * added: a JSON-text string binds correctly to `json` / `jsonb` without one,\n * and a blind cast would misfire on columns we cannot positively identify as\n * jsonb.\n *\n * @param key - Column name (used to resolve native-array columns)\n * @param value - The raw value to serialize (never `undefined`)\n * @param table - Optional table name; enables the per-table native-array lookup\n * @returns The value ready to bind as a query parameter\n */\n private serializeValue(key: string, value: unknown, table?: string): unknown {\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n if (typeof value === \"bigint\") {\n return value.toString();\n }\n\n if (Array.isArray(value)) {\n if (value.length > 0 && value.every((v) => typeof v === \"number\")) {\n // pgvector columns expect the literal '[n1,n2,...]' format.\n return `[${value.join(\",\")}]`;\n }\n\n // Genuine native PostgreSQL array columns (JSONB[], TEXT[], …) must\n // keep their raw array so node-pg emits a '{...}' array literal.\n if (this.isNativeArrayColumn(table, key)) {\n return value;\n }\n\n // json / jsonb columns: bind as JSON text. Covers object-arrays,\n // string-arrays, mixed arrays, and empty [] (which would otherwise\n // store as '{}' instead of '[]').\n return JSON.stringify(value);\n }\n\n if (typeof value === \"object\" && value !== null) {\n // Plain object → JSONB. Explicit JSON.stringify matches node-pg's own\n // object encoding while keeping both write paths consistent.\n return JSON.stringify(value);\n }\n\n return value;\n }\n\n /**\n * Whether `column` on `table` is a native PostgreSQL array. True when the\n * connect-time schema introspection saw it as `data_type = 'ARRAY'` for that\n * table (authoritative, per-table), or when it's listed in the table-agnostic\n * `nativeArrayColumns` config override.\n */\n private isNativeArrayColumn(table: string | undefined, column: string): boolean {\n if (table && this._introspectedArrayColumns.get(table)?.has(column)) {\n return true;\n }\n\n return this._nativeArrayColumns.has(column);\n }\n\n /**\n * Introspect the live schema for native-array columns so array values bind\n * correctly with zero app configuration.\n *\n * A JS array must be bound two opposite ways depending on the column: as JSON\n * text for a `json` / `jsonb` column, but as a raw array (which node-pg\n * renders `{...}`) for a native `TEXT[]` / `JSONB[]` / `INTEGER[]` column. The\n * serializer sees values, not types, so without this it JSON-stringifies\n * every array — which a native-array column rejects with \"malformed array\n * literal\". One `information_schema` query at connect, cached for the\n * connection lifetime, removes the need to hand-list `nativeArrayColumns`.\n *\n * Best-effort: any failure (e.g. restricted catalog access) is logged and\n * leaves the map empty so the config override still applies — it never blocks\n * connect. A schema change made within a live connection isn't reflected\n * until the next connect.\n */\n private async loadNativeArrayColumns(): Promise<void> {\n try {\n const result = await this.query<{ table_name: string; column_name: string }>(\n `SELECT table_name, column_name\n FROM information_schema.columns\n WHERE table_schema = ANY (current_schemas(false))\n AND data_type = 'ARRAY'`,\n );\n\n const map = new Map<string, Set<string>>();\n\n for (const { table_name, column_name } of result.rows) {\n let columns = map.get(table_name);\n\n if (!columns) {\n columns = new Set<string>();\n map.set(table_name, columns);\n }\n\n columns.add(column_name);\n }\n\n this._introspectedArrayColumns = map;\n } catch {\n // Introspection is an optimization, never a hard dependency — fall back\n // to the `nativeArrayColumns` config so a locked-down catalog or an\n // unusual search_path can't break connect.\n log.warn(\n \"database.postgres\",\n \"introspection\",\n \"Could not introspect native-array columns; using the nativeArrayColumns config only\",\n );\n }\n }\n\n /**\n * Get the dirty tracker for this driver.\n */\n public getDirtyTracker(data: Record<string, unknown>): SqlDatabaseDirtyTracker {\n return new SqlDatabaseDirtyTracker(data);\n }\n\n /**\n * Deserialize data retrieved from PostgreSQL.\n *\n * Converts PostgreSQL types back to JavaScript equivalents.\n *\n * @param data - The data object from PostgreSQL\n * @returns Deserialized JavaScript object\n */\n public deserialize(data: Record<string, unknown>): Record<string, unknown> {\n // PostgreSQL pg driver handles most type conversions automatically\n // Special handling can be added here if needed\n for (const [key, value] of Object.entries(data)) {\n // Only re-inflate strings — pg already returns Date objects from DB reads\n if (typeof value !== \"string\") continue;\n\n if (isValidDateValue(value)) {\n data[key] = new Date(value);\n continue;\n }\n\n // pgvector columns are returned as '[n1,n2,...]' strings.\n // charCodeAt is faster than startsWith/endsWith — no string allocation.\n // '[' = 91, ']' = 93\n if (value.charCodeAt(0) === 91 && value.charCodeAt(value.length - 1) === 93) {\n const parts = value.slice(1, -1).split(\",\");\n const nums = new Array<number>(parts.length);\n let isNumericVector = parts.length > 0;\n\n for (let i = 0; i < parts.length; i++) {\n // `parts[i]` is `string | undefined` under noUncheckedIndexedAccess even\n // though `i < parts.length`. `+undefined` is NaN, which the finite check\n // below already rejects — so `?? \"\"` keeps the coercion total without\n // changing which inputs count as a numeric vector (+\"\" is 0, but an\n // empty segment cannot occur for i < length).\n const n = +(parts[i] ?? \"\"); // unary + is the fastest string-to-number coercion\n if (!Number.isFinite(n)) {\n isNumericVector = false;\n break; // early-exit — not a numeric vector, leave value untouched\n }\n nums[i] = n;\n }\n\n if (isNumericVector) {\n data[key] = nums;\n }\n }\n }\n\n return data;\n }\n\n /**\n * Insert a single row into a table.\n *\n * Uses INSERT ... RETURNING to get the inserted row with generated values.\n *\n * @param table - Target table name\n * @param document - Data to insert\n * @param options - Optional insertion options\n * @returns The inserted document\n */\n public async insert(\n table: string,\n document: Record<string, unknown>,\n _options?: Record<string, unknown>,\n ): Promise<InsertResult> {\n const serialized = this.serialize(document, table);\n\n // Filter out id if null/undefined to let PostgreSQL SERIAL auto-generate\n const filteredData = Object.fromEntries(\n Object.entries(serialized).filter(([key, value]) => {\n // Exclude id if null/undefined (let SERIAL handle it)\n if (key === \"id\" && (value === null || value === undefined)) {\n return false;\n }\n return true;\n }),\n );\n\n const columns = Object.keys(filteredData);\n const values = Object.values(filteredData);\n\n if (columns.length === 0) {\n throw new Error(\"Cannot insert empty document\");\n }\n\n const quotedColumns = columns.map((c) => this.dialect.quoteIdentifier(c)).join(\", \");\n const placeholders = columns.map((_, i) => this.dialect.placeholder(i + 1)).join(\", \");\n const quotedTable = this.dialect.quoteIdentifier(table);\n\n const sql = `INSERT INTO ${quotedTable} (${quotedColumns}) VALUES (${placeholders}) RETURNING *`;\n\n const result = await this.query<Record<string, unknown>>(sql, values);\n\n return {\n document: result.rows[0],\n };\n }\n\n /**\n * Insert multiple rows into a table.\n *\n * Uses a single INSERT statement with multiple value sets for efficiency.\n *\n * @param table - Target table name\n * @param documents - Array of documents to insert\n * @param options - Optional insertion options\n * @returns Array of inserted documents\n */\n public async insertMany(\n table: string,\n documents: Record<string, unknown>[],\n _options?: Record<string, unknown>,\n ): Promise<InsertResult[]> {\n if (documents.length === 0) {\n return [];\n }\n\n // Get all unique columns across all documents\n const allColumns = new Set<string>();\n for (const doc of documents) {\n const serialized = this.serialize(doc, table);\n Object.keys(serialized).forEach((key) => allColumns.add(key));\n }\n const columns = Array.from(allColumns);\n\n const quotedColumns = columns.map((c) => this.dialect.quoteIdentifier(c)).join(\", \");\n const quotedTable = this.dialect.quoteIdentifier(table);\n\n // Build value sets and params\n const valueSets: string[] = [];\n const params: unknown[] = [];\n let paramIndex = 1;\n\n for (const doc of documents) {\n const serialized = this.serialize(doc, table);\n const rowPlaceholders: string[] = [];\n\n for (const col of columns) {\n if (col in serialized) {\n rowPlaceholders.push(this.dialect.placeholder(paramIndex++));\n params.push(serialized[col]);\n } else {\n rowPlaceholders.push(\"DEFAULT\");\n }\n }\n\n valueSets.push(`(${rowPlaceholders.join(\", \")})`);\n }\n\n const sql = `INSERT INTO ${quotedTable} (${quotedColumns}) VALUES ${valueSets.join(\", \")} RETURNING *`;\n\n const result = await this.query<Record<string, unknown>>(sql, params);\n\n return result.rows as unknown as InsertResult[];\n }\n\n /**\n * Update a single row matching the filter.\n *\n * @param table - Target table name\n * @param filter - Filter conditions\n * @param update - Update operations ($set, $unset, $inc)\n * @param options - Optional update options\n * @returns Update result with modified count\n */\n public async update(\n table: string,\n filter: Record<string, unknown>,\n update: UpdateOperations,\n _options?: Record<string, unknown>,\n ): Promise<UpdateResult> {\n const { sql, params } = this.buildUpdateQuery(table, filter, update, 1);\n try {\n const result = await this.query(sql, params);\n\n return {\n modifiedCount: result.rowCount ?? 0,\n };\n } catch (error) {\n console.log(\"PG Query Error in:\", sql, params);\n\n throw error;\n }\n }\n\n /**\n * Find one row matching the filter, update it, and return it.\n *\n * - `upsert`: `INSERT … ON CONFLICT (target) DO UPDATE … RETURNING *`, where\n * the target is the primary key or a unique index whose columns are all\n * equality keys of the filter (see {@link executeUpsert}).\n * - `returnDocument` (default `\"after\"`): `\"before\"` returns the row as it\n * was, and is not available together with `upsert`.\n *\n * The row is chosen with `SELECT … LIMIT 1 FOR UPDATE` and matched by primary\n * key with the filter repeated, so concurrent callers re-check the filter\n * against the latest committed row instead of overshooting it.\n *\n * @throws UnsupportedUpdateOperationError for pipeline updates, `arrayFilters`,\n * array operators, `before` + `upsert`, or an underivable conflict target\n */\n public async findOneAndUpdate<T = unknown>(\n table: string,\n filter: Record<string, unknown>,\n update: AtomicUpdate,\n options?: DriverAtomicUpdateOptions,\n ): Promise<T | null> {\n const operations = assertPostgresUpdate(update, options);\n const returnBefore = options?.returnDocument === \"before\";\n\n if (options?.upsert) {\n if (returnBefore) {\n throw new UnsupportedUpdateOperationError(\n \"returnDocument: before with upsert\",\n \"postgres\",\n 'Use returnDocument: \"after\", or read the row before upserting inside a transaction.',\n );\n }\n\n const { row } = await this.executeUpsert(table, filter, operations);\n\n return (row as T | undefined) ?? null;\n }\n\n const primaryKey = (await this.getUniqueKeys(table)).find((key) => key.primary)?.columns;\n\n if (!primaryKey) {\n if (returnBefore) {\n throw new UnsupportedUpdateOperationError(\n \"returnDocument: before without a primary key\",\n \"postgres\",\n );\n }\n\n const { sql, params } = this.buildUpdateQuery(table, filter, operations, 1);\n const result = await this.query<T>(`${sql} RETURNING *`, params);\n\n return result.rows[0] ?? null;\n }\n\n const quotedTable = this.dialect.quoteIdentifier(table);\n const keyList = (qualifier?: string) =>\n primaryKey\n .map((column) => (qualifier ? `${qualifier}.` : \"\") + this.dialect.quoteIdentifier(column))\n .join(\", \");\n\n const set = this.buildSetClauses(table, operations, 1, returnBefore ? quotedTable : undefined);\n\n if (set.clauses.length === 0) {\n throw new Error(\"No update operations specified\");\n }\n\n const inner = this.buildWhereClause(filter, set.nextIndex);\n const outer = this.buildWhereClause(\n filter,\n set.nextIndex + inner.whereParams.length,\n quotedTable,\n );\n const params = [...set.params, ...inner.whereParams, ...outer.whereParams];\n const outerCondition = outer.condition ? ` AND ${outer.condition}` : \"\";\n\n if (returnBefore) {\n const before = this.dialect.quoteIdentifier(\"__cascade_before\");\n const sql =\n `UPDATE ${quotedTable} SET ${set.clauses.join(\", \")} ` +\n `FROM (SELECT * FROM ${quotedTable} ${inner.whereClause} LIMIT 1 FOR UPDATE) AS ${before} ` +\n `WHERE (${keyList(quotedTable)}) = (${keyList(before)})${outerCondition} RETURNING ${before}.*`;\n const result = await this.query<T>(sql, params);\n\n return result.rows[0] ?? null;\n }\n\n const sql =\n `UPDATE ${quotedTable} SET ${set.clauses.join(\", \")} ` +\n `WHERE (${keyList(quotedTable)}) = (SELECT ${keyList()} FROM ${quotedTable} ${inner.whereClause} LIMIT 1 FOR UPDATE)` +\n `${outerCondition} RETURNING *`;\n const result = await this.query<T>(sql, params);\n\n return result.rows[0] ?? null;\n }\n\n /**\n * Upsert through `INSERT … ON CONFLICT (target) DO UPDATE SET … [WHERE rest] RETURNING *`.\n *\n * Conflict target rule: the first unique index of the table (primary key\n * first; partial and expression indexes excluded) whose columns are ALL\n * equality keys of the filter — a non-null value that is not an operator\n * object. Any other filter predicate becomes the `DO UPDATE … WHERE`, so an\n * existing row that fails it is left untouched and no row is returned.\n *\n * Inserted row: filter equality values, then `$setOnInsert`, then `$set`,\n * then `$inc` (n) / `$dec` (-n); `$unset` columns are omitted.\n *\n * @throws UnsupportedUpdateOperationError when no conflict target can be derived\n */\n private async executeUpsert(\n table: string,\n filter: Record<string, unknown>,\n operations: UpdateOperations,\n ): Promise<{ row?: Record<string, unknown>; inserted: boolean }> {\n const equalityKeys = new Set(\n Object.entries(filter)\n .filter(([, value]) => value !== null && value !== undefined && !this.isOperatorFilter(value))\n .map(([key]) => key),\n );\n const uniqueKeys = await this.getUniqueKeys(table);\n const target = uniqueKeys.find((key) => key.columns.every((column) => equalityKeys.has(column)));\n\n if (!target) {\n throw new UnsupportedUpdateOperationError(\n \"upsert without a unique conflict target\",\n \"postgres\",\n `Filter on every column of the primary key or of a unique index of \"${table}\".`,\n );\n }\n\n const insertRow: Record<string, unknown> = {};\n\n for (const key of equalityKeys) {\n insertRow[key] = filter[key];\n }\n\n Object.assign(insertRow, operations.$setOnInsert, operations.$set);\n\n for (const [key, amount] of Object.entries(operations.$inc ?? {})) {\n insertRow[key] = amount;\n }\n\n for (const [key, amount] of Object.entries(operations.$dec ?? {})) {\n insertRow[key] = -amount;\n }\n\n for (const key of Object.keys(operations.$unset ?? {})) {\n delete insertRow[key];\n }\n\n const columns = Object.keys(insertRow).filter((key) => insertRow[key] !== undefined);\n const params: unknown[] = columns.map((key) => this.serializeValue(key, insertRow[key], table));\n const quotedTable = this.dialect.quoteIdentifier(table);\n const quotedColumns = columns.map((column) => this.dialect.quoteIdentifier(column)).join(\", \");\n const placeholders = columns.map((_, index) => this.dialect.placeholder(index + 1)).join(\", \");\n\n const set = this.buildSetClauses(table, operations, columns.length + 1, quotedTable);\n params.push(...set.params);\n\n const firstTargetColumn = this.dialect.quoteIdentifier(target.columns[0]!);\n const doUpdateSet =\n set.clauses.length > 0\n ? set.clauses.join(\", \")\n : `${firstTargetColumn} = ${quotedTable}.${firstTargetColumn}`;\n\n const remainingFilter = Object.fromEntries(\n Object.entries(filter).filter(([key]) => !target.columns.includes(key)),\n );\n const where = this.buildWhereClause(remainingFilter, set.nextIndex, quotedTable);\n params.push(...where.whereParams);\n\n const conflictColumns = target.columns\n .map((column) => this.dialect.quoteIdentifier(column))\n .join(\", \");\n const insertedFlag = \"__cascade_inserted\";\n const sql =\n `INSERT INTO ${quotedTable} (${quotedColumns}) VALUES (${placeholders}) ` +\n `ON CONFLICT (${conflictColumns}) DO UPDATE SET ${doUpdateSet} ${where.whereClause} ` +\n `RETURNING *, (xmax = 0) AS \"${insertedFlag}\"`;\n\n const result = await this.query<Record<string, unknown>>(sql, params);\n const returned = result.rows[0];\n\n if (!returned) {\n return { inserted: false };\n }\n\n const { [insertedFlag]: inserted, ...row } = returned;\n\n return { row, inserted: inserted === true };\n }\n\n /**\n * Unique keys of a table (primary key first), read from `pg_index`.\n *\n * Partial and expression indexes are excluded — they cannot be an\n * `ON CONFLICT (columns)` target. Non-empty results are cached per table.\n */\n private async getUniqueKeys(table: string): Promise<ReadonlyArray<UniqueKey>> {\n const cached = this._uniqueKeys.get(table);\n\n if (cached) {\n return cached;\n }\n\n const result = await this.query<UniqueKey>(\n `SELECT array_agg(a.attname::text ORDER BY k.ord) AS columns, i.indisprimary AS primary\n FROM pg_index i\n CROSS JOIN LATERAL unnest(i.indkey::int2[]) WITH ORDINALITY AS k(attnum, ord)\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum\n WHERE i.indrelid = to_regclass($1) AND i.indisunique AND i.indpred IS NULL AND i.indexprs IS NULL\n GROUP BY i.indexrelid, i.indisprimary\n ORDER BY i.indisprimary DESC, i.indexrelid`,\n [this.dialect.quoteIdentifier(table)],\n );\n\n if (result.rows.length > 0) {\n this._uniqueKeys.set(table, result.rows);\n }\n\n return result.rows;\n }\n\n /**\n * Update multiple rows matching the filter.\n *\n * @param table - Target table name\n * @param filter - Filter conditions\n * @param update - Update operations\n * @param options - Optional update options\n * @returns Update result with modified count\n */\n public async updateMany(\n table: string,\n filter: Record<string, unknown>,\n update: UpdateOperations,\n _options?: Record<string, unknown>,\n ): Promise<UpdateResult> {\n const { sql, params } = this.buildUpdateQuery(table, filter, update);\n\n const result = await this.query(sql, params);\n\n return {\n modifiedCount: result.rowCount ?? 0,\n };\n }\n\n /**\n * Replace a document matching the filter.\n *\n * Completely replaces the document (not a partial update).\n *\n * @param table - Target table name\n * @param filter - Filter conditions\n * @param document - New document data\n * @param options - Optional options\n * @returns The replaced document or null\n */\n public async replace<T = unknown>(\n table: string,\n filter: Record<string, unknown>,\n document: Record<string, unknown>,\n _options?: Record<string, unknown>,\n ): Promise<T | null> {\n const serialized = this.serialize(document, table);\n const columns = Object.keys(serialized);\n const values = Object.values(serialized);\n\n const quotedTable = this.dialect.quoteIdentifier(table);\n const setClauses = columns\n .map((col, i) => `${this.dialect.quoteIdentifier(col)} = ${this.dialect.placeholder(i + 1)}`)\n .join(\", \");\n\n const { whereClause, whereParams } = this.buildWhereClause(filter, columns.length + 1);\n\n const sql = `UPDATE ${quotedTable} SET ${setClauses} ${whereClause} RETURNING *`;\n const params = [...values, ...whereParams];\n\n const result = await this.query<T>(sql, params);\n\n return result.rows[0] ?? null;\n }\n\n /**\n * Upsert (insert or update) a single row.\n *\n * Uses PostgreSQL's INSERT ... ON CONFLICT ... DO UPDATE syntax.\n *\n * @param table - Target table name\n * @param filter - Filter conditions to find existing row (used for conflict detection)\n * @param document - Document data to insert or update\n * @param options - Upsert options (conflictColumns for conflict target)\n * @returns The upserted row\n */\n public async upsert<T = unknown>(\n table: string,\n filter: Record<string, unknown>,\n document: Record<string, unknown>,\n options?: Record<string, unknown>,\n ): Promise<T> {\n const serialized = this.serialize(document, table);\n const columns = Object.keys(serialized);\n const values = Object.values(serialized);\n\n if (columns.length === 0) {\n throw new Error(\"Cannot upsert empty document\");\n }\n\n const quotedTable = this.dialect.quoteIdentifier(table);\n const quotedColumns = columns.map((c) => this.dialect.quoteIdentifier(c)).join(\", \");\n const placeholders = columns.map((_, i) => this.dialect.placeholder(i + 1)).join(\", \");\n\n // Determine conflict columns from options or filter\n const conflictColumns = (options?.conflictColumns as string[]) ?? Object.keys(filter);\n if (conflictColumns.length === 0) {\n throw new Error(\"Upsert requires conflictColumns option or filter with columns\");\n }\n\n const quotedConflictColumns = conflictColumns\n .map((c) => this.dialect.quoteIdentifier(c))\n .join(\", \");\n\n // Build UPDATE clause for ON CONFLICT\n // Update all columns except the conflict columns (they stay the same)\n const updateColumns = columns.filter((col) => !conflictColumns.includes(col));\n const setClauses = updateColumns\n .map((col, i) => {\n const valueIndex = columns.indexOf(col) + 1;\n return `${this.dialect.quoteIdentifier(col)} = ${this.dialect.placeholder(valueIndex)}`;\n })\n .join(\", \");\n\n // If there are no columns to update (all columns are conflict columns), use EXCLUDED\n const updateClause =\n setClauses.length > 0\n ? setClauses\n : columns\n .map(\n (col) =>\n `${this.dialect.quoteIdentifier(col)} = EXCLUDED.${this.dialect.quoteIdentifier(col)}`,\n )\n .join(\", \");\n\n const sql = `INSERT INTO ${quotedTable} (${quotedColumns}) VALUES (${placeholders}) ON CONFLICT (${quotedConflictColumns}) DO UPDATE SET ${updateClause} RETURNING *`;\n\n const result = await this.query<T>(sql, values);\n\n return result.rows[0]! as T;\n }\n\n /**\n * Find one and delete a single row matching the filter and return the deleted row.\n *\n * @param table - Target table name\n * @param filter - Filter conditions\n * @param options - Optional delete options\n * @returns The deleted row or null\n */\n public async findOneAndDelete<T = unknown>(\n table: string,\n filter: Record<string, unknown>,\n _options?: Record<string, unknown>,\n ): Promise<T | null> {\n const quotedTable = this.dialect.quoteIdentifier(table);\n const { whereClause, whereParams } = this.buildWhereClause(filter, 1);\n\n // Use ctid for single row deletion with RETURNING\n const sql = `DELETE FROM ${quotedTable} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${whereClause} LIMIT 1) RETURNING *`;\n\n const result = await this.query<T>(sql, whereParams);\n\n return result.rows[0] ? (result.rows[0] as T) : null;\n }\n\n /**\n * Delete a single row matching the filter.\n *\n * @param table - Target table name\n * @param filter - Filter conditions\n * @param options - Optional options\n * @returns Number of deleted rows (0 or 1)\n */\n public async delete(\n table: string,\n filter?: Record<string, unknown>,\n _options?: Record<string, unknown>,\n ): Promise<number> {\n const quotedTable = this.dialect.quoteIdentifier(table);\n const { whereClause, whereParams } = this.buildWhereClause(filter ?? {}, 1);\n\n // Use ctid for single row deletion\n const sql = `DELETE FROM ${quotedTable} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${whereClause} LIMIT 1)`;\n\n const result = await this.query(sql, whereParams);\n\n return result.rowCount ?? 0;\n }\n\n /**\n * Delete multiple rows matching the filter.\n *\n * @param table - Target table name\n * @param filter - Filter conditions\n * @param options - Optional options\n * @returns Number of deleted rows\n */\n public async deleteMany(\n table: string,\n filter?: Record<string, unknown>,\n _options?: Record<string, unknown>,\n ): Promise<number> {\n const quotedTable = this.dialect.quoteIdentifier(table);\n const { whereClause, whereParams } = this.buildWhereClause(filter ?? {}, 1);\n\n const sql = `DELETE FROM ${quotedTable} ${whereClause}`;\n\n const result = await this.query(sql, whereParams);\n\n return result.rowCount ?? 0;\n }\n\n /**\n * Truncate a table (remove all rows).\n *\n * Uses TRUNCATE TABLE for fast deletion with RESTART IDENTITY.\n *\n * @param table - Target table name\n * @param options - Optional options\n * @param options.cascade - If true, automatically truncate all tables with foreign key references (use with caution)\n * @returns Number of deleted rows (always 0 for TRUNCATE)\n */\n public async truncateTable(table: string, options?: { cascade?: boolean }): Promise<number> {\n const quotedTable = this.dialect.quoteIdentifier(table);\n const cascadeClause = options?.cascade ? \" CASCADE\" : \"\";\n await this.query(`TRUNCATE TABLE ${quotedTable} RESTART IDENTITY${cascadeClause}`);\n return 0; // TRUNCATE doesn't return row count\n }\n\n /**\n * Get a query builder for the specified table.\n *\n * @param table - Target table name\n * @returns Query builder instance\n */\n public queryBuilder<T = unknown>(table: string): QueryBuilderContract<T> {\n return new PostgresQueryBuilder<T>(table) as unknown as QueryBuilderContract<T>;\n }\n\n /**\n * Begin a new database transaction.\n *\n * Acquires a client from the pool and starts a transaction.\n * The client is stored in AsyncLocalStorage for automatic\n * participation by subsequent queries.\n *\n * @param options - Optional transaction options\n * @returns Transaction contract with commit/rollback methods\n */\n public async beginTransaction(\n options?: PostgresTransactionOptions,\n ): Promise<DriverTransactionContract<PgPoolClient>> {\n const client = await this.pool.connect();\n\n let beginSql = \"BEGIN\";\n if (options?.isolationLevel) {\n beginSql += ` ISOLATION LEVEL ${options.isolationLevel.toUpperCase()}`;\n }\n if (options?.readOnly) {\n beginSql += \" READ ONLY\";\n }\n if (options?.deferrable) {\n beginSql += \" DEFERRABLE\";\n }\n\n await client.query(beginSql);\n\n return {\n context: client,\n commit: async () => {\n await client.query(\"COMMIT\");\n client.release();\n },\n rollback: async () => {\n await client.query(\"ROLLBACK\");\n client.release();\n },\n };\n }\n\n /**\n * Execute a function within a transaction scope (recommended pattern).\n *\n * Automatically commits on success, rolls back on any error, and guarantees\n * resource cleanup. This is the recommended way to use transactions.\n *\n * @param fn - Async function to execute within transaction\n * @param options - Transaction options (isolation level, read-only, etc.)\n * @returns The return value of the callback function\n * @throws {Error} If transaction fails or is explicitly rolled back\n */\n public async transaction<T>(\n fn: (ctx: TransactionContext) => Promise<T>,\n options?: Record<string, unknown>,\n ): Promise<T> {\n const ctx: TransactionContext = {\n rollback(reason?: string): never {\n throw new TransactionRollbackError(reason);\n },\n };\n\n // Flat nesting: a transaction() called while one is already active JOINS it\n // instead of opening a second, independent transaction on another pool\n // connection. An independent inner transaction can't see the outer's\n // uncommitted writes (a row inserted moments earlier), so a service that\n // opens its own transaction — correct when called standalone — would hit\n // phantom FK violations when called inside an outer transaction (e.g. a\n // seeder that creates a row, then calls a service that references it). The\n // outermost transaction owns BEGIN / COMMIT / ROLLBACK; the inner block\n // runs on the same session, and a throw still unwinds the whole outer\n // transaction. (Savepoint-based partial rollback is a separate, explicit\n // concern — use `beginTransaction()` directly for that.)\n if (databaseTransactionContext.hasActiveTransaction()) {\n return fn(ctx);\n }\n\n const tx = await this.beginTransaction(options);\n\n // Set transaction context for queries within callback\n databaseTransactionContext.enter({ session: tx.context });\n\n try {\n // Execute callback\n const result = await fn(ctx);\n\n // Auto-commit on success\n await tx.commit();\n\n return result;\n } catch (error) {\n // Auto-rollback on any error (including explicit rollback)\n await tx.rollback();\n log.error(\n `database.postgress`,\n \"transaction\",\n \"Transaction operation failed, rolled back everything\",\n );\n throw error;\n } finally {\n // Guaranteed cleanup\n databaseTransactionContext.exit();\n }\n }\n\n /**\n * Perform an atomic update operation.\n *\n * Builds and executes an UPDATE query for the given filter and operations.\n * Updates EVERY matching row — the MongoDB driver's atomic() delegates to\n * updateMany, and Model.findAndUpdate documents multi-row semantics, so the\n * two drivers must agree.\n *\n * @param table - Target table name\n * @param filter - Filter conditions\n * @param operations - Update operations\n * @param options - Optional options\n * @returns Update result\n */\n public async atomic(\n table: string,\n filter: Record<string, unknown>,\n operations: AtomicUpdate,\n options?: DriverAtomicUpdateOptions,\n ): Promise<UpdateResult> {\n const update = assertPostgresUpdate(operations, options);\n\n if (options?.upsert) {\n const { row, inserted } = await this.executeUpsert(table, filter, update);\n\n return {\n modifiedCount: row && !inserted ? 1 : 0,\n upsertedCount: row && inserted ? 1 : 0,\n };\n }\n\n const { sql, params } = this.buildUpdateQuery(table, filter, update);\n\n const result = await this.query(sql, params);\n\n return {\n modifiedCount: result.rowCount ?? 0,\n };\n }\n\n /**\n * Get the sync adapter for bulk denormalized updates.\n *\n * @returns Sync adapter instance\n */\n public syncAdapter(): SyncAdapterContract {\n if (!this._syncAdapter) {\n this._syncAdapter = new PostgresSyncAdapter(this);\n }\n return this._syncAdapter;\n }\n\n /**\n * Get the migration driver for schema operations.\n *\n * @returns Migration driver instance\n */\n public migrationDriver(): MigrationDriverContract {\n if (!this._migrationDriver) {\n this._migrationDriver = new PostgresMigrationDriver(this);\n }\n\n return this._migrationDriver;\n }\n\n /**\n * Return a SQL serializer for this driver's dialect.\n * Used by Migration.toSQL() to convert pending operations to SQL strings.\n */\n public getSQLSerializer(): SQLSerializer {\n return new PostgresSQLSerializer(this.dialect);\n }\n\n /**\n * Execute a raw SQL query.\n *\n * Automatically uses the transaction client if one is active.\n *\n * @param sql - SQL query string\n * @param params - Query parameters\n * @returns Query result\n */\n public async query<T = Record<string, unknown>>(\n sql: string,\n params: unknown[] = [],\n ): Promise<PostgresQueryResult<T>> {\n // Check for active transaction client\n const txClient = databaseTransactionContext.getSession() as PgPoolClient | undefined;\n\n const startTime = this.config.logging ? performance.now() : 0;\n\n let paramsString = \"\";\n if (this.config.logging && params.length > 0) {\n paramsString = JSON.stringify(params);\n if (paramsString.length > 300) {\n paramsString = paramsString.substring(0, 300) + \"...\";\n }\n paramsString = ` | Params: ${paramsString}`;\n }\n\n try {\n let result;\n if (this.config.logging) {\n log.info({\n module: \"database.postgres\",\n action: \"query.executing\",\n message: `${sql}${paramsString}`,\n context: { params, sql },\n });\n }\n if (txClient) {\n result = await txClient.query(sql, params);\n } else {\n result = await this.pool.query(sql, params);\n }\n\n if (this.config.logging) {\n const duration = (performance.now() - startTime).toFixed(2);\n log.success({\n module: \"database.postgres\",\n action: \"query.executed\",\n message: `[${duration}ms] ${sql}${paramsString}`,\n context: { params, sql, duration },\n });\n }\n\n return result as PostgresQueryResult<T>;\n } catch (error) {\n if (this.config.logging) {\n const duration = (performance.now() - startTime).toFixed(2);\n log.error({\n module: \"database.postgres\",\n action: \"query.error\",\n message: `[${duration}ms] ${sql}${paramsString}`,\n context: {\n sql,\n params,\n error: error instanceof Error ? error.message : String(error),\n },\n });\n }\n throw error;\n }\n }\n\n /**\n * Emit an event to all registered listeners.\n *\n * @param event - Event name\n * @param args - Event arguments\n */\n private emit(event: string, ...args: unknown[]): void {\n const listeners = this._eventListeners.get(event);\n if (listeners) {\n for (const listener of listeners) {\n listener(...args);\n }\n }\n }\n\n /**\n * Build a simple WHERE clause from a filter object.\n *\n * Values are bound as plain equality, except Mongo-style operator objects\n * (`{ $in: [...] }`, `{ $gt: 5 }`, ...) which are translated to their SQL\n * equivalents — driver-level callers (e.g. pivot detach) build filters in\n * that portable form. An unrecognized `$` operator throws instead of being\n * bound literally, which would only surface as a cryptic type error from\n * Postgres.\n *\n * @param filter - Filter conditions\n * @param startParamIndex - Starting parameter index\n * @returns Object with WHERE clause string and parameters\n */\n private buildWhereClause(\n filter: Record<string, unknown>,\n startParamIndex: number,\n qualifier?: string,\n ): { whereClause: string; whereParams: unknown[]; condition: string } {\n const conditions: string[] = [];\n const params: unknown[] = [];\n let paramIndex = startParamIndex;\n\n for (const [key, value] of Object.entries(filter)) {\n const quotedKey = (qualifier ? `${qualifier}.` : \"\") + this.dialect.quoteIdentifier(key);\n\n if (value === null) {\n conditions.push(`${quotedKey} IS NULL`);\n } else if (this.isOperatorFilter(value)) {\n for (const [operator, operand] of Object.entries(value as Record<string, unknown>)) {\n switch (operator) {\n case \"$in\":\n case \"$nin\": {\n const list = operand as unknown[];\n if (list.length === 0) {\n // $in [] matches nothing; $nin [] matches everything.\n conditions.push(operator === \"$in\" ? \"FALSE\" : \"TRUE\");\n break;\n }\n const placeholders = list.map(() => this.dialect.placeholder(paramIndex++));\n params.push(...list);\n conditions.push(\n `${quotedKey} ${operator === \"$in\" ? \"IN\" : \"NOT IN\"} (${placeholders.join(\", \")})`,\n );\n break;\n }\n case \"$eq\":\n if (operand === null) {\n conditions.push(`${quotedKey} IS NULL`);\n } else {\n conditions.push(`${quotedKey} = ${this.dialect.placeholder(paramIndex++)}`);\n params.push(operand);\n }\n break;\n case \"$ne\":\n if (operand === null) {\n conditions.push(`${quotedKey} IS NOT NULL`);\n } else {\n conditions.push(`${quotedKey} != ${this.dialect.placeholder(paramIndex++)}`);\n params.push(operand);\n }\n break;\n case \"$gt\":\n case \"$gte\":\n case \"$lt\":\n case \"$lte\": {\n const sqlOperator = { $gt: \">\", $gte: \">=\", $lt: \"<\", $lte: \"<=\" }[operator];\n conditions.push(`${quotedKey} ${sqlOperator} ${this.dialect.placeholder(paramIndex++)}`);\n params.push(operand);\n break;\n }\n default:\n throw new Error(\n `Unsupported filter operator \"${operator}\" for column \"${key}\" on the Postgres driver.`,\n );\n }\n }\n } else {\n conditions.push(`${quotedKey} = ${this.dialect.placeholder(paramIndex++)}`);\n params.push(value);\n }\n }\n\n const condition = conditions.join(\" AND \");\n const whereClause = condition ? `WHERE ${condition}` : \"\";\n\n return { whereClause, whereParams: params, condition };\n }\n\n /**\n * A filter value is an operator object when it is a plain object whose keys\n * ALL start with `$`. Arrays, Dates, and value objects (e.g. jsonb equality\n * payloads) keep their existing bind-as-value behavior.\n */\n private isOperatorFilter(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n\n if (value instanceof Date || Buffer.isBuffer(value)) {\n return false;\n }\n\n const keys = Object.keys(value);\n return keys.length > 0 && keys.every((key) => key.startsWith(\"$\"));\n }\n\n /**\n * Build the SET clauses for an update.\n *\n * @param table - Target table name (for value serialization)\n * @param update - Update operations (`$setOnInsert` is not part of SET)\n * @param startIndex - First placeholder index\n * @param qualifier - Quoted table name to qualify right-hand column references\n * (needed where another relation with the same columns is in scope)\n */\n private buildSetClauses(\n table: string,\n update: UpdateOperations,\n startIndex: number,\n qualifier?: string,\n ): { clauses: string[]; params: unknown[]; nextIndex: number } {\n const clauses: string[] = [];\n const params: unknown[] = [];\n let paramIndex = startIndex;\n const reference = (quotedKey: string) => (qualifier ? `${qualifier}.${quotedKey}` : quotedKey);\n\n // Handle $set\n if (update.$set) {\n for (const [key, value] of Object.entries(update.$set)) {\n clauses.push(\n `${this.dialect.quoteIdentifier(key)} = ${this.dialect.placeholder(paramIndex++)}`,\n );\n // Apply the same json/jsonb-aware serialization used on the INSERT\n // path so object/array $set values aren't corrupted into Postgres\n // array literals. undefined is skipped — never bind it.\n params.push(value === undefined ? value : this.serializeValue(key, value, table));\n }\n }\n\n // Handle $unset (set to NULL)\n if (update.$unset) {\n for (const key of Object.keys(update.$unset)) {\n clauses.push(`${this.dialect.quoteIdentifier(key)} = NULL`);\n }\n }\n\n // Handle $inc / $dec\n const arithmetic = [\n [\"$inc\", \"+\"],\n [\"$dec\", \"-\"],\n ] as const;\n\n for (const [operator, sign] of arithmetic) {\n for (const [key, amount] of Object.entries(update[operator] ?? {})) {\n const quotedKey = this.dialect.quoteIdentifier(key);\n clauses.push(\n `${quotedKey} = COALESCE(${reference(quotedKey)}, 0) ${sign} ${this.dialect.placeholder(paramIndex++)}`,\n );\n params.push(amount);\n }\n }\n\n return { clauses, params, nextIndex: paramIndex };\n }\n\n /**\n * Build an UPDATE query from update operations.\n *\n * @param table - Target table name\n * @param filter - Filter conditions\n * @param update - Update operations\n * @param limit - Optional limit (for single row update)\n * @returns Object with SQL and parameters\n * @throws UnsupportedUpdateOperationError for operations SQL cannot express\n */\n private buildUpdateQuery(\n table: string,\n filter: Record<string, unknown>,\n update: AtomicUpdate,\n limit?: number,\n ): { sql: string; params: unknown[] } {\n const set = this.buildSetClauses(table, assertPostgresUpdate(update), 1);\n\n if (set.clauses.length === 0) {\n throw new Error(\"No update operations specified\");\n }\n\n const params = [...set.params];\n const quotedTable = this.dialect.quoteIdentifier(table);\n const { whereClause, whereParams } = this.buildWhereClause(filter, set.nextIndex);\n params.push(...whereParams);\n\n let sql = `UPDATE ${quotedTable} SET ${set.clauses.join(\", \")} ${whereClause}`;\n\n // For single row update, use ctid subquery\n if (limit === 1 && whereClause) {\n sql = `UPDATE ${quotedTable} SET ${set.clauses.join(\", \")} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${whereClause} LIMIT 1)`;\n }\n\n return { sql, params };\n }\n\n // ============================================================\n // Database Lifecycle Operations\n // ============================================================\n\n /**\n * Create a new database.\n *\n * Note: This requires connecting to a system database (like 'postgres')\n * since you cannot create a database while connected to it.\n *\n * @param name - Database name to create\n * @param options - Creation options (encoding, template, etc.)\n * @returns true if created, false if already exists\n */\n public async createDatabase(name: string, options?: CreateDatabaseOptions): Promise<boolean> {\n // Check if database already exists\n if (await this.databaseExists(name)) {\n return false;\n }\n\n // Build CREATE DATABASE statement\n const quotedName = this.dialect.quoteIdentifier(name);\n let sql = `CREATE DATABASE ${quotedName}`;\n\n const withClauses: string[] = [];\n\n if (options?.encoding) {\n withClauses.push(`ENCODING = '${options.encoding}'`);\n }\n if (options?.template) {\n withClauses.push(`TEMPLATE = ${this.dialect.quoteIdentifier(options.template)}`);\n }\n if (options?.locale) {\n withClauses.push(`LC_COLLATE = '${options.locale}'`);\n withClauses.push(`LC_CTYPE = '${options.locale}'`);\n }\n if (options?.owner) {\n withClauses.push(`OWNER = ${this.dialect.quoteIdentifier(options.owner)}`);\n }\n\n if (withClauses.length > 0) {\n sql += ` WITH ${withClauses.join(\" \")}`;\n }\n\n try {\n await this.query(sql);\n log.success(\"database\", \"lifecycle\", `Created database ${name}`);\n return true;\n } catch (error) {\n log.error(\"database\", \"lifecycle\", `Failed to create database ${name}: ${error}`);\n throw error;\n }\n }\n\n /**\n * Drop a database.\n *\n * @param name - Database name to drop\n * @param options - Drop options\n * @returns true if dropped, false if didn't exist\n */\n public async dropDatabase(name: string, options?: DropDatabaseOptions): Promise<boolean> {\n // Check if database exists first (if ifExists option not set)\n if (!options?.ifExists && !(await this.databaseExists(name))) {\n return false;\n }\n\n const quotedName = this.dialect.quoteIdentifier(name);\n let sql = \"DROP DATABASE\";\n\n if (options?.ifExists) {\n sql += \" IF EXISTS\";\n }\n\n sql += ` ${quotedName}`;\n\n // PostgreSQL 13+ supports WITH (FORCE) to terminate active connections\n if (options?.force) {\n sql += \" WITH (FORCE)\";\n }\n\n try {\n await this.query(sql);\n log.success(\"database\", \"lifecycle\", `Dropped database ${name}`);\n return true;\n } catch (error) {\n log.error(\"database\", \"lifecycle\", `Failed to drop database ${name}: ${error}`);\n throw error;\n }\n }\n\n /**\n * Check if a database exists.\n *\n * @param name - Database name to check\n * @returns true if database exists\n */\n public async databaseExists(name: string): Promise<boolean> {\n const result = await this.query<{ exists: boolean }>(\n `SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1) as exists`,\n [name],\n );\n\n return result.rows[0]?.exists ?? false;\n }\n\n /**\n * List all databases.\n *\n * @returns Array of database names\n */\n public async listDatabases(): Promise<string[]> {\n const result = await this.query<{ datname: string }>(\n `SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname`,\n );\n\n return result.rows.map((row) => row.datname);\n }\n\n // ============================================================\n // Table Management Operations\n // ============================================================\n\n /**\n * Drop a table.\n *\n * @param name - Table name to drop\n * @throws Error if table doesn't exist\n */\n public async dropTable(name: string): Promise<void> {\n const quotedName = this.dialect.quoteIdentifier(name);\n await this.query(`DROP TABLE ${quotedName}`);\n log.success(\"database\", \"table\", `Dropped table ${name}`);\n }\n\n /**\n * Drop a table if it exists.\n *\n * @param name - Table name to drop\n */\n public async dropTableIfExists(name: string): Promise<void> {\n const quotedName = this.dialect.quoteIdentifier(name);\n await this.query(`DROP TABLE IF EXISTS ${quotedName}`);\n }\n\n /**\n * Drop all tables in the current database.\n *\n * Uses CASCADE to handle foreign key dependencies.\n * Useful for `migrate:fresh` command.\n */\n public async dropAllTables(): Promise<void> {\n // Get all tables from blueprint\n const tables = await this.blueprint.listTables();\n\n if (tables.length === 0) {\n return;\n }\n\n // Drop all tables with CASCADE to handle foreign keys\n for (const table of tables) {\n const quotedName = this.dialect.quoteIdentifier(table);\n await this.query(`DROP TABLE IF EXISTS ${quotedName} CASCADE`);\n }\n\n log.success(\"database\", \"table\", `Dropped ${tables.length} tables`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"postgres-driver.mjs","names":[],"sources":["../../../../../../../../cascade/src/drivers/postgres/postgres-driver.ts"],"sourcesContent":["/**\r\n * PostgreSQL Driver\r\n *\r\n * Main driver implementation for PostgreSQL database operations.\r\n * Implements the DriverContract interface to provide a unified API\r\n * for CRUD operations, transactions, and query building.\r\n *\r\n * Uses the `pg` package for database connectivity with connection pooling.\r\n *\r\n * @module cascade/drivers/postgres\r\n */\r\n\r\nimport { colors } from \"@mongez/copper\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport { databaseTransactionContext } from \"../../context/database-transaction-context\";\r\nimport type {\r\n AtomicUpdate,\r\n CreateDatabaseOptions,\r\n DriverAtomicUpdateOptions,\r\n DriverContract,\r\n DriverEventListener,\r\n DriverTransactionContract,\r\n DropDatabaseOptions,\r\n InsertResult,\r\n TransactionContext,\r\n UpdateOperations,\r\n UpdateResult,\r\n} from \"../../contracts/database-driver.contract\";\r\nimport type { DriverBlueprintContract } from \"../../contracts/driver-blueprint.contract\";\r\nimport type { MigrationDriverContract } from \"../../contracts/migration-driver.contract\";\r\nimport type { QueryBuilderContract } from \"../../contracts/query-builder.contract\";\r\nimport type { SyncAdapterContract } from \"../../contracts/sync-adapter.contract\";\r\nimport { TransactionRollbackError } from \"../../errors/transaction-rollback.error\";\r\nimport { UnsupportedUpdateOperationError } from \"../../errors/unsupported-update-operation.error\";\r\nimport { SQLSerializer } from \"../../migration/sql-serializer\";\r\nimport { SqlDatabaseDirtyTracker } from \"../../sql-database-dirty-tracker\";\r\nimport type { ModelDefaults } from \"../../types\";\r\nimport { type DatabaseDriver } from \"../../utils/connect-to-database\";\r\nimport { isValidDateValue } from \"../../utils/is-valid-date-value\";\r\nimport { PostgresBlueprint } from \"./postgres-blueprint\";\r\nimport { PostgresDialect } from \"./postgres-dialect\";\r\nimport { PostgresMigrationDriver } from \"./postgres-migration-driver\";\r\nimport { PostgresQueryBuilder } from \"./postgres-query-builder\";\r\nimport { PostgresSQLSerializer } from \"./postgres-sql-serializer\";\r\nimport { PostgresSyncAdapter } from \"./postgres-sync-adapter\";\r\nimport { assertPostgresUpdate } from \"./postgres-update-validator\";\r\nimport type { PostgresPoolConfig, PostgresQueryResult, PostgresTransactionOptions } from \"./types\";\r\n\r\n/**\r\n * A unique index usable as an `ON CONFLICT` target.\r\n */\r\ntype UniqueKey = {\r\n /** Indexed columns, in index order. */\r\n columns: string[];\r\n /** Whether the index backs the primary key. */\r\n primary: boolean;\r\n};\r\n\r\n/**\r\n * Lazily loaded pg module types.\r\n */\r\ntype PgPool = import(\"pg\").Pool;\r\ntype PgPoolClient = import(\"pg\").PoolClient;\r\ntype PgPoolConfig = import(\"pg\").PoolConfig;\r\n\r\n/**\r\n * Build pg's `PoolConfig` from cascade's `PostgresPoolConfig`, coercing the\r\n * string-typed connection fields.\r\n *\r\n * `env()` (@mongez/dotenv) coerces a numeric-looking value to a number, so a\r\n * `DB_NAME` / `DB_USERNAME` / `DB_PASSWORD` like \"12345\" arrives here as a\r\n * number. pg's connection fields must be strings — a number reaches deep into\r\n * the driver and crashes with an inscrutable Buffer error rather than a\r\n * legible \"database does not exist\" (finding 51639bf8). So `host`, `database`,\r\n * `user` and `password` are coerced to strings; `port` is a real number and\r\n * `undefined` is left untouched so pg's own defaults still apply.\r\n *\r\n * Exported (not inlined into `connect()`) so the coercion is unit-testable\r\n * without a live database.\r\n */\r\nexport function buildPostgresPoolConfig(config: PostgresPoolConfig): PgPoolConfig {\r\n const asString = (value: unknown): string | undefined =>\r\n typeof value === \"number\" ? String(value) : (value as string | undefined);\r\n\r\n return {\r\n host: asString(config.host) ?? \"localhost\",\r\n port: config.port ?? 5432,\r\n database: asString(config.database),\r\n user: asString(config.user),\r\n password: asString(config.password),\r\n connectionString: config.connectionString,\r\n max: config.max ?? 10,\r\n min: config.min ?? 0,\r\n idleTimeoutMillis: config.idleTimeoutMillis ?? 30000,\r\n // 2000ms was too tight under load (card ba1193b4): at concurrency 10, a\r\n // pool of `max` clients queues checkout requests, and 2s is easy to blow\r\n // through waiting for a client to free up — surfacing as pg-pool's\r\n // \"timeout exceeded when trying to connect\" on a healthy database that's\r\n // just briefly saturated. 10000ms gives queued callers real headroom\r\n // without masking a truly wedged connection.\r\n connectionTimeoutMillis: config.connectionTimeoutMillis ?? 10000,\r\n // TCP keep-alive so a NAT/firewall/OS doesn't silently drop an idle\r\n // connection out from under the pool — see PostgresPoolConfig.keepAlive.\r\n keepAlive: config.keepAlive ?? true,\r\n application_name: config.application_name ?? \"cascade\",\r\n ssl: config.ssl,\r\n };\r\n}\r\n\r\n/**\r\n * Cached pg module reference.\r\n */\r\nlet pgModule: typeof import(\"pg\") | undefined;\r\n\r\n/**\r\n * Lazily load the pg package.\r\n *\r\n * @returns The pg module\r\n * @throws Error if pg is not installed\r\n */\r\nasync function loadPg(): Promise<typeof import(\"pg\")> {\r\n if (pgModule) {\r\n return pgModule;\r\n }\r\n\r\n try {\r\n pgModule = await import(\"pg\");\r\n return pgModule;\r\n } catch {\r\n throw new Error(\r\n 'The \"pg\" package is required for PostgreSQL support. ' + \"Please install it: npm install pg\",\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * PostgreSQL database driver implementing the Cascade DriverContract.\r\n *\r\n * Provides connection pooling, CRUD operations, transactions, and\r\n * integration with Cascade's query builder and migration systems.\r\n *\r\n * @example\r\n * ```typescript\r\n * const driver = new PostgresDriver({\r\n * host: 'localhost',\r\n * port: 5432,\r\n * database: 'myapp',\r\n * user: 'postgres',\r\n * password: 'secret'\r\n * });\r\n *\r\n * await driver.connect();\r\n *\r\n * // Insert a document\r\n * const result = await driver.insert('users', { name: 'Alice', email: 'alice@example.com' });\r\n *\r\n * // Query using the query builder\r\n * const users = await driver.queryBuilder('users')\r\n * .where('name', 'Alice')\r\n * .get();\r\n *\r\n * await driver.disconnect();\r\n * ```\r\n */\r\nexport class PostgresDriver implements DriverContract {\r\n /**\r\n * Driver name identifier.\r\n */\r\n public readonly name = \"postgres\" as DatabaseDriver;\r\n\r\n /**\r\n * SQL dialect for PostgreSQL-specific syntax.\r\n */\r\n public readonly dialect = new PostgresDialect();\r\n\r\n /**\r\n * PostgreSQL driver model defaults.\r\n *\r\n * PostgreSQL follows SQL conventions:\r\n * - snake_case naming for columns (created_at, updated_at, deleted_at)\r\n * - Native AUTO_INCREMENT for IDs (no manual generation)\r\n * - Timestamps enabled by default\r\n * - Permanent delete strategy (hard deletes)\r\n */\r\n public readonly modelDefaults: Partial<ModelDefaults> = {\r\n namingConvention: \"snake_case\",\r\n createdAtColumn: \"created_at\",\r\n updatedAtColumn: \"updated_at\",\r\n deletedAtColumn: \"deleted_at\",\r\n timestamps: true,\r\n autoGenerateId: false, // PostgreSQL uses SERIAL/BIGSERIAL\r\n strictMode: \"fail\",\r\n deleteStrategy: \"permanent\",\r\n };\r\n\r\n /**\r\n * Connection pool instance.\r\n */\r\n private _pool: PgPool | undefined;\r\n\r\n /**\r\n * Event listeners for driver lifecycle events.\r\n */\r\n private readonly _eventListeners = new Map<string, Set<DriverEventListener>>();\r\n\r\n /**\r\n * Whether the driver is currently connected.\r\n */\r\n private _isConnected = false;\r\n\r\n /**\r\n * Blueprint instance (lazy-loaded).\r\n */\r\n private _blueprint: DriverBlueprintContract | undefined;\r\n\r\n /**\r\n * Migration driver instance (lazy-loaded).\r\n */\r\n private _migrationDriver: MigrationDriverContract | undefined;\r\n\r\n /**\r\n * Sync adapter instance (lazy-loaded).\r\n */\r\n private _syncAdapter: SyncAdapterContract | undefined;\r\n\r\n /**\r\n * Explicit, table-agnostic override list of column names that hold native\r\n * PostgreSQL arrays (`JSONB[]`, `TEXT[]`, …) and must NOT be JSON-text\r\n * encoded. Merged with (and superseded per-table by) the schema\r\n * introspection below; kept as a manual escape hatch.\r\n *\r\n * @see PostgresPoolConfig.nativeArrayColumns\r\n */\r\n private readonly _nativeArrayColumns: ReadonlySet<string>;\r\n\r\n /** Unique keys per table, resolved from `pg_index` for upsert conflict targets. */\r\n private readonly _uniqueKeys = new Map<string, ReadonlyArray<UniqueKey>>();\r\n\r\n /**\r\n * Native-array columns discovered by introspecting the live schema on\r\n * connect, keyed `table → { column, … }`. Authoritative and table-scoped, so\r\n * a column that is `TEXT[]` in one table and `jsonb` in another is encoded\r\n * correctly for each — no app configuration required.\r\n */\r\n private _introspectedArrayColumns: ReadonlyMap<string, ReadonlySet<string>> = new Map();\r\n\r\n /**\r\n * Create a new PostgreSQL driver instance.\r\n *\r\n * @param config - PostgreSQL connection configuration\r\n */\r\n public constructor(private readonly config: PostgresPoolConfig) {\r\n this._nativeArrayColumns = new Set(config.nativeArrayColumns ?? []);\r\n }\r\n\r\n /**\r\n * Get the connection pool instance.\r\n *\r\n * @throws Error if not connected\r\n */\r\n public get pool(): PgPool {\r\n if (!this._pool) {\r\n throw new Error(\"PostgreSQL driver is not connected. Call connect() first.\");\r\n }\r\n return this._pool;\r\n }\r\n\r\n /**\r\n * Get database native client\r\n */\r\n public getClient<Client = PgPool>(): Client {\r\n return this.pool as Client;\r\n }\r\n\r\n /**\r\n * Check if the driver is currently connected.\r\n */\r\n public get isConnected(): boolean {\r\n return this._isConnected;\r\n }\r\n\r\n /**\r\n * Get the driver blueprint (information schema).\r\n */\r\n public get blueprint(): DriverBlueprintContract {\r\n if (!this._blueprint) {\r\n this._blueprint = new PostgresBlueprint(this);\r\n }\r\n return this._blueprint;\r\n }\r\n\r\n /**\r\n * Establish connection to the PostgreSQL database.\r\n *\r\n * Creates a connection pool with the configured options.\r\n * Emits 'connected' event on successful connection.\r\n */\r\n public async connect(): Promise<void> {\r\n if (this._isConnected) {\r\n return;\r\n }\r\n\r\n const pg = await loadPg();\r\n\r\n try {\r\n const poolConfig = buildPostgresPoolConfig(this.config);\r\n\r\n log.info(\r\n \"database.postgres\",\r\n \"connection\",\r\n `Connecting to database ${colors.bold(colors.yellowBright(poolConfig.database ?? \"\"))}`,\r\n );\r\n\r\n this._pool = new pg.Pool(poolConfig);\r\n\r\n // pg emits 'error' on the Pool for problems on an already-idle client\r\n // (e.g. the server or a NAT/firewall closed it out from under us —\r\n // exactly the \"Connection terminated unexpectedly\" symptom in card\r\n // ba1193b4). node's EventEmitter throws if an 'error' event has no\r\n // listener, which would crash the whole process on a single dropped\r\n // idle connection; a listener here turns that into a log line and lets\r\n // the pool prune the dead client and open a fresh one on next checkout.\r\n this._pool.on(\"error\", (error: Error) => {\r\n log.error(\r\n \"database.postgres\",\r\n \"pool\",\r\n `Unexpected error on an idle client: ${error.message}`,\r\n );\r\n });\r\n\r\n // Test the connection\r\n const client = await this._pool.connect();\r\n client.release();\r\n\r\n log.success(\r\n \"database.postgres\",\r\n \"connection\",\r\n `Connected to database ${colors.bold(colors.yellowBright(this.config.database))}`,\r\n );\r\n\r\n this._isConnected = true;\r\n\r\n // Learn which columns are native arrays straight from the live schema so\r\n // the value serializer encodes them correctly with zero app config.\r\n await this.loadNativeArrayColumns();\r\n\r\n this.emit(\"connected\");\r\n } catch (error) {\r\n // Boot-time database connection failure is unrecoverable in every\r\n // realistic caller (app boot, CLI migrations, workers) — `fatal` makes\r\n // \"page on fatal only\" alerting clean. Per-query failures stay at error.\r\n log.fatal(\"database.postgres\", \"connection\", \"Failed to connect to database\");\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Close the database connection pool.\r\n *\r\n * Waits for all active queries to complete before closing.\r\n * Emits 'disconnected' event on successful disconnection.\r\n */\r\n public async disconnect(): Promise<void> {\r\n if (!this._isConnected || !this._pool) {\r\n return;\r\n }\r\n\r\n await this._pool.end();\r\n this._pool = undefined;\r\n this._isConnected = false;\r\n this.emit(\"disconnected\");\r\n }\r\n\r\n /**\r\n * Register an event listener for driver lifecycle events.\r\n *\r\n * @param event - Event name ('connected', 'disconnected', etc.)\r\n * @param listener - Callback function to invoke\r\n */\r\n public on(event: string, listener: DriverEventListener): void {\r\n if (!this._eventListeners.has(event)) {\r\n this._eventListeners.set(event, new Set());\r\n }\r\n\r\n this._eventListeners.get(event)!.add(listener);\r\n }\r\n\r\n /**\r\n * Serialize data for storage in PostgreSQL.\r\n *\r\n * Handles Date objects, BigInt, and other JavaScript types\r\n * that need special handling for PostgreSQL storage.\r\n *\r\n * @param data - The data object to serialize\r\n * @param table - Optional table name; when given, columns introspected as\r\n * native arrays on that table are bound raw (see {@link serializeValue}).\r\n * @returns Serialized data ready for PostgreSQL\r\n */\r\n public serialize(data: Record<string, unknown>, table?: string): Record<string, unknown> {\r\n const serialized: Record<string, unknown> = {};\r\n\r\n for (const [key, value] of Object.entries(data)) {\r\n if (value === undefined) {\r\n continue; // Skip undefined values\r\n }\r\n\r\n serialized[key] = this.serializeValue(key, value, table);\r\n }\r\n\r\n return serialized;\r\n }\r\n\r\n /**\r\n * Serialize a single column value into a node-pg bindable parameter.\r\n *\r\n * Shared by {@link serialize} (INSERT path) and {@link buildUpdateQuery}\r\n * `$set` (UPDATE path) so both encode `json` / `jsonb` columns identically.\r\n *\r\n * Encoding rules (in order):\r\n * - `Date` → ISO string.\r\n * - `bigint` → decimal string (node-pg has no native bigint binding).\r\n * - all-number array → pgvector literal `'[n1,n2,...]'`. node-pg would\r\n * otherwise emit a `{n1,n2,...}` array literal, which the `vector` type\r\n * rejects. This branch is preserved exactly.\r\n * - any other array (object-array, string-array, mixed, empty `[]`) →\r\n * `JSON.stringify`. node-pg renders a raw JS array as a PostgreSQL array\r\n * literal `{...}` (and `[]` as `{}`), which a `json` / `jsonb` column\r\n * rejects — so we bind the value as JSON text instead, the form those\r\n * columns accept. Columns known to be native arrays — via schema\r\n * introspection or the `nativeArrayColumns` config — are exempt: their raw\r\n * array is passed through so node-pg emits the `{...}` literal a genuine\r\n * `JSONB[]` / `TEXT[]` column needs.\r\n * - plain object → `JSON.stringify`. Equivalent to node-pg's own object\r\n * handling, made explicit so both write paths agree.\r\n * - everything else (scalars: string, number, boolean, null) → untouched.\r\n *\r\n * Distinguishing native-array from `json` / `jsonb` columns: a value alone\r\n * can't tell them apart, so the driver introspects the live schema on connect\r\n * (see {@link loadNativeArrayColumns}) and consults that per-table map here\r\n * via {@link isNativeArrayColumn}. The explicit `nativeArrayColumns` config\r\n * still works as a table-agnostic override. No `::jsonb` placeholder cast is\r\n * added: a JSON-text string binds correctly to `json` / `jsonb` without one,\r\n * and a blind cast would misfire on columns we cannot positively identify as\r\n * jsonb.\r\n *\r\n * @param key - Column name (used to resolve native-array columns)\r\n * @param value - The raw value to serialize (never `undefined`)\r\n * @param table - Optional table name; enables the per-table native-array lookup\r\n * @returns The value ready to bind as a query parameter\r\n */\r\n private serializeValue(key: string, value: unknown, table?: string): unknown {\r\n if (value instanceof Date) {\r\n return value.toISOString();\r\n }\r\n\r\n if (typeof value === \"bigint\") {\r\n return value.toString();\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n if (value.length > 0 && value.every((v) => typeof v === \"number\")) {\r\n // pgvector columns expect the literal '[n1,n2,...]' format.\r\n return `[${value.join(\",\")}]`;\r\n }\r\n\r\n // Genuine native PostgreSQL array columns (JSONB[], TEXT[], …) must\r\n // keep their raw array so node-pg emits a '{...}' array literal.\r\n if (this.isNativeArrayColumn(table, key)) {\r\n return value;\r\n }\r\n\r\n // json / jsonb columns: bind as JSON text. Covers object-arrays,\r\n // string-arrays, mixed arrays, and empty [] (which would otherwise\r\n // store as '{}' instead of '[]').\r\n return JSON.stringify(value);\r\n }\r\n\r\n if (typeof value === \"object\" && value !== null) {\r\n // Plain object → JSONB. Explicit JSON.stringify matches node-pg's own\r\n // object encoding while keeping both write paths consistent.\r\n return JSON.stringify(value);\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * Whether `column` on `table` is a native PostgreSQL array. True when the\r\n * connect-time schema introspection saw it as `data_type = 'ARRAY'` for that\r\n * table (authoritative, per-table), or when it's listed in the table-agnostic\r\n * `nativeArrayColumns` config override.\r\n */\r\n private isNativeArrayColumn(table: string | undefined, column: string): boolean {\r\n if (table && this._introspectedArrayColumns.get(table)?.has(column)) {\r\n return true;\r\n }\r\n\r\n return this._nativeArrayColumns.has(column);\r\n }\r\n\r\n /**\r\n * Introspect the live schema for native-array columns so array values bind\r\n * correctly with zero app configuration.\r\n *\r\n * A JS array must be bound two opposite ways depending on the column: as JSON\r\n * text for a `json` / `jsonb` column, but as a raw array (which node-pg\r\n * renders `{...}`) for a native `TEXT[]` / `JSONB[]` / `INTEGER[]` column. The\r\n * serializer sees values, not types, so without this it JSON-stringifies\r\n * every array — which a native-array column rejects with \"malformed array\r\n * literal\". One `information_schema` query at connect, cached for the\r\n * connection lifetime, removes the need to hand-list `nativeArrayColumns`.\r\n *\r\n * Best-effort: any failure (e.g. restricted catalog access) is logged and\r\n * leaves the map empty so the config override still applies — it never blocks\r\n * connect. A schema change made within a live connection isn't reflected\r\n * until the next connect.\r\n */\r\n private async loadNativeArrayColumns(): Promise<void> {\r\n try {\r\n const result = await this.query<{ table_name: string; column_name: string }>(\r\n `SELECT table_name, column_name\r\n FROM information_schema.columns\r\n WHERE table_schema = ANY (current_schemas(false))\r\n AND data_type = 'ARRAY'`,\r\n );\r\n\r\n const map = new Map<string, Set<string>>();\r\n\r\n for (const { table_name, column_name } of result.rows) {\r\n let columns = map.get(table_name);\r\n\r\n if (!columns) {\r\n columns = new Set<string>();\r\n map.set(table_name, columns);\r\n }\r\n\r\n columns.add(column_name);\r\n }\r\n\r\n this._introspectedArrayColumns = map;\r\n } catch {\r\n // Introspection is an optimization, never a hard dependency — fall back\r\n // to the `nativeArrayColumns` config so a locked-down catalog or an\r\n // unusual search_path can't break connect.\r\n log.warn(\r\n \"database.postgres\",\r\n \"introspection\",\r\n \"Could not introspect native-array columns; using the nativeArrayColumns config only\",\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Get the dirty tracker for this driver.\r\n */\r\n public getDirtyTracker(data: Record<string, unknown>): SqlDatabaseDirtyTracker {\r\n return new SqlDatabaseDirtyTracker(data);\r\n }\r\n\r\n /**\r\n * Deserialize data retrieved from PostgreSQL.\r\n *\r\n * Converts PostgreSQL types back to JavaScript equivalents.\r\n *\r\n * @param data - The data object from PostgreSQL\r\n * @returns Deserialized JavaScript object\r\n */\r\n public deserialize(data: Record<string, unknown>): Record<string, unknown> {\r\n // PostgreSQL pg driver handles most type conversions automatically\r\n // Special handling can be added here if needed\r\n for (const [key, value] of Object.entries(data)) {\r\n // Only re-inflate strings — pg already returns Date objects from DB reads\r\n if (typeof value !== \"string\") continue;\r\n\r\n if (isValidDateValue(value)) {\r\n data[key] = new Date(value);\r\n continue;\r\n }\r\n\r\n // pgvector columns are returned as '[n1,n2,...]' strings.\r\n // charCodeAt is faster than startsWith/endsWith — no string allocation.\r\n // '[' = 91, ']' = 93\r\n if (value.charCodeAt(0) === 91 && value.charCodeAt(value.length - 1) === 93) {\r\n const parts = value.slice(1, -1).split(\",\");\r\n const nums = new Array<number>(parts.length);\r\n let isNumericVector = parts.length > 0;\r\n\r\n for (let i = 0; i < parts.length; i++) {\r\n // `parts[i]` is `string | undefined` under noUncheckedIndexedAccess even\r\n // though `i < parts.length`. `+undefined` is NaN, which the finite check\r\n // below already rejects — so `?? \"\"` keeps the coercion total without\r\n // changing which inputs count as a numeric vector (+\"\" is 0, but an\r\n // empty segment cannot occur for i < length).\r\n const n = +(parts[i] ?? \"\"); // unary + is the fastest string-to-number coercion\r\n if (!Number.isFinite(n)) {\r\n isNumericVector = false;\r\n break; // early-exit — not a numeric vector, leave value untouched\r\n }\r\n nums[i] = n;\r\n }\r\n\r\n if (isNumericVector) {\r\n data[key] = nums;\r\n }\r\n }\r\n }\r\n\r\n return data;\r\n }\r\n\r\n /**\r\n * Insert a single row into a table.\r\n *\r\n * Uses INSERT ... RETURNING to get the inserted row with generated values.\r\n *\r\n * @param table - Target table name\r\n * @param document - Data to insert\r\n * @param options - Optional insertion options\r\n * @returns The inserted document\r\n */\r\n public async insert(\r\n table: string,\r\n document: Record<string, unknown>,\r\n _options?: Record<string, unknown>,\r\n ): Promise<InsertResult> {\r\n const serialized = this.serialize(document, table);\r\n\r\n // Filter out id if null/undefined to let PostgreSQL SERIAL auto-generate\r\n const filteredData = Object.fromEntries(\r\n Object.entries(serialized).filter(([key, value]) => {\r\n // Exclude id if null/undefined (let SERIAL handle it)\r\n if (key === \"id\" && (value === null || value === undefined)) {\r\n return false;\r\n }\r\n return true;\r\n }),\r\n );\r\n\r\n const columns = Object.keys(filteredData);\r\n const values = Object.values(filteredData);\r\n\r\n if (columns.length === 0) {\r\n throw new Error(\"Cannot insert empty document\");\r\n }\r\n\r\n const quotedColumns = columns.map((c) => this.dialect.quoteIdentifier(c)).join(\", \");\r\n const placeholders = columns.map((_, i) => this.dialect.placeholder(i + 1)).join(\", \");\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n\r\n const sql = `INSERT INTO ${quotedTable} (${quotedColumns}) VALUES (${placeholders}) RETURNING *`;\r\n\r\n const result = await this.query<Record<string, unknown>>(sql, values);\r\n\r\n return {\r\n document: result.rows[0],\r\n };\r\n }\r\n\r\n /**\r\n * Insert multiple rows into a table.\r\n *\r\n * Uses a single INSERT statement with multiple value sets for efficiency.\r\n *\r\n * @param table - Target table name\r\n * @param documents - Array of documents to insert\r\n * @param options - Optional insertion options\r\n * @returns Array of inserted documents\r\n */\r\n public async insertMany(\r\n table: string,\r\n documents: Record<string, unknown>[],\r\n _options?: Record<string, unknown>,\r\n ): Promise<InsertResult[]> {\r\n if (documents.length === 0) {\r\n return [];\r\n }\r\n\r\n // Get all unique columns across all documents\r\n const allColumns = new Set<string>();\r\n for (const doc of documents) {\r\n const serialized = this.serialize(doc, table);\r\n Object.keys(serialized).forEach((key) => allColumns.add(key));\r\n }\r\n const columns = Array.from(allColumns);\r\n\r\n const quotedColumns = columns.map((c) => this.dialect.quoteIdentifier(c)).join(\", \");\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n\r\n // Build value sets and params\r\n const valueSets: string[] = [];\r\n const params: unknown[] = [];\r\n let paramIndex = 1;\r\n\r\n for (const doc of documents) {\r\n const serialized = this.serialize(doc, table);\r\n const rowPlaceholders: string[] = [];\r\n\r\n for (const col of columns) {\r\n if (col in serialized) {\r\n rowPlaceholders.push(this.dialect.placeholder(paramIndex++));\r\n params.push(serialized[col]);\r\n } else {\r\n rowPlaceholders.push(\"DEFAULT\");\r\n }\r\n }\r\n\r\n valueSets.push(`(${rowPlaceholders.join(\", \")})`);\r\n }\r\n\r\n const sql = `INSERT INTO ${quotedTable} (${quotedColumns}) VALUES ${valueSets.join(\", \")} RETURNING *`;\r\n\r\n const result = await this.query<Record<string, unknown>>(sql, params);\r\n\r\n return result.rows as unknown as InsertResult[];\r\n }\r\n\r\n /**\r\n * Update a single row matching the filter.\r\n *\r\n * @param table - Target table name\r\n * @param filter - Filter conditions\r\n * @param update - Update operations ($set, $unset, $inc)\r\n * @param options - Optional update options\r\n * @returns Update result with modified count\r\n */\r\n public async update(\r\n table: string,\r\n filter: Record<string, unknown>,\r\n update: UpdateOperations,\r\n _options?: Record<string, unknown>,\r\n ): Promise<UpdateResult> {\r\n const { sql, params } = this.buildUpdateQuery(table, filter, update, 1);\r\n try {\r\n const result = await this.query(sql, params);\r\n\r\n return {\r\n modifiedCount: result.rowCount ?? 0,\r\n };\r\n } catch (error) {\r\n console.log(\"PG Query Error in:\", sql, params);\r\n\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Find one row matching the filter, update it, and return it.\r\n *\r\n * - `upsert`: `INSERT … ON CONFLICT (target) DO UPDATE … RETURNING *`, where\r\n * the target is the primary key or a unique index whose columns are all\r\n * equality keys of the filter (see {@link executeUpsert}).\r\n * - `returnDocument` (default `\"after\"`): `\"before\"` returns the row as it\r\n * was, and is not available together with `upsert`.\r\n *\r\n * The row is chosen with `SELECT … LIMIT 1 FOR UPDATE` and matched by primary\r\n * key with the filter repeated, so concurrent callers re-check the filter\r\n * against the latest committed row instead of overshooting it.\r\n *\r\n * @throws UnsupportedUpdateOperationError for pipeline updates, `arrayFilters`,\r\n * array operators, `before` + `upsert`, or an underivable conflict target\r\n */\r\n public async findOneAndUpdate<T = unknown>(\r\n table: string,\r\n filter: Record<string, unknown>,\r\n update: AtomicUpdate,\r\n options?: DriverAtomicUpdateOptions,\r\n ): Promise<T | null> {\r\n const operations = assertPostgresUpdate(update, options);\r\n const returnBefore = options?.returnDocument === \"before\";\r\n\r\n if (options?.upsert) {\r\n if (returnBefore) {\r\n throw new UnsupportedUpdateOperationError(\r\n \"returnDocument: before with upsert\",\r\n \"postgres\",\r\n 'Use returnDocument: \"after\", or read the row before upserting inside a transaction.',\r\n );\r\n }\r\n\r\n const { row } = await this.executeUpsert(table, filter, operations);\r\n\r\n return (row as T | undefined) ?? null;\r\n }\r\n\r\n const primaryKey = (await this.getUniqueKeys(table)).find((key) => key.primary)?.columns;\r\n\r\n if (!primaryKey) {\r\n if (returnBefore) {\r\n throw new UnsupportedUpdateOperationError(\r\n \"returnDocument: before without a primary key\",\r\n \"postgres\",\r\n );\r\n }\r\n\r\n const { sql, params } = this.buildUpdateQuery(table, filter, operations, 1);\r\n const result = await this.query<T>(`${sql} RETURNING *`, params);\r\n\r\n return result.rows[0] ?? null;\r\n }\r\n\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n const keyList = (qualifier?: string) =>\r\n primaryKey\r\n .map((column) => (qualifier ? `${qualifier}.` : \"\") + this.dialect.quoteIdentifier(column))\r\n .join(\", \");\r\n\r\n const set = this.buildSetClauses(table, operations, 1, returnBefore ? quotedTable : undefined);\r\n\r\n if (set.clauses.length === 0) {\r\n throw new Error(\"No update operations specified\");\r\n }\r\n\r\n const inner = this.buildWhereClause(filter, set.nextIndex);\r\n const outer = this.buildWhereClause(\r\n filter,\r\n set.nextIndex + inner.whereParams.length,\r\n quotedTable,\r\n );\r\n const params = [...set.params, ...inner.whereParams, ...outer.whereParams];\r\n const outerCondition = outer.condition ? ` AND ${outer.condition}` : \"\";\r\n\r\n if (returnBefore) {\r\n const before = this.dialect.quoteIdentifier(\"__cascade_before\");\r\n const sql =\r\n `UPDATE ${quotedTable} SET ${set.clauses.join(\", \")} ` +\r\n `FROM (SELECT * FROM ${quotedTable} ${inner.whereClause} LIMIT 1 FOR UPDATE) AS ${before} ` +\r\n `WHERE (${keyList(quotedTable)}) = (${keyList(before)})${outerCondition} RETURNING ${before}.*`;\r\n const result = await this.query<T>(sql, params);\r\n\r\n return result.rows[0] ?? null;\r\n }\r\n\r\n const sql =\r\n `UPDATE ${quotedTable} SET ${set.clauses.join(\", \")} ` +\r\n `WHERE (${keyList(quotedTable)}) = (SELECT ${keyList()} FROM ${quotedTable} ${inner.whereClause} LIMIT 1 FOR UPDATE)` +\r\n `${outerCondition} RETURNING *`;\r\n const result = await this.query<T>(sql, params);\r\n\r\n return result.rows[0] ?? null;\r\n }\r\n\r\n /**\r\n * Upsert through `INSERT … ON CONFLICT (target) DO UPDATE SET … [WHERE rest] RETURNING *`.\r\n *\r\n * Conflict target rule: the first unique index of the table (primary key\r\n * first; partial and expression indexes excluded) whose columns are ALL\r\n * equality keys of the filter — a non-null value that is not an operator\r\n * object. Any other filter predicate becomes the `DO UPDATE … WHERE`, so an\r\n * existing row that fails it is left untouched and no row is returned.\r\n *\r\n * Inserted row: filter equality values, then `$setOnInsert`, then `$set`,\r\n * then `$inc` (n) / `$dec` (-n); `$unset` columns are omitted.\r\n *\r\n * @throws UnsupportedUpdateOperationError when no conflict target can be derived\r\n */\r\n private async executeUpsert(\r\n table: string,\r\n filter: Record<string, unknown>,\r\n operations: UpdateOperations,\r\n ): Promise<{ row?: Record<string, unknown>; inserted: boolean }> {\r\n const equalityKeys = new Set(\r\n Object.entries(filter)\r\n .filter(\r\n ([, value]) => value !== null && value !== undefined && !this.isOperatorFilter(value),\r\n )\r\n .map(([key]) => key),\r\n );\r\n const uniqueKeys = await this.getUniqueKeys(table);\r\n const target = uniqueKeys.find((key) =>\r\n key.columns.every((column) => equalityKeys.has(column)),\r\n );\r\n\r\n if (!target) {\r\n throw new UnsupportedUpdateOperationError(\r\n \"upsert without a unique conflict target\",\r\n \"postgres\",\r\n `Filter on every column of the primary key or of a unique index of \"${table}\".`,\r\n );\r\n }\r\n\r\n const insertRow: Record<string, unknown> = {};\r\n\r\n for (const key of equalityKeys) {\r\n insertRow[key] = filter[key];\r\n }\r\n\r\n Object.assign(insertRow, operations.$setOnInsert, operations.$set);\r\n\r\n for (const [key, amount] of Object.entries(operations.$inc ?? {})) {\r\n insertRow[key] = amount;\r\n }\r\n\r\n for (const [key, amount] of Object.entries(operations.$dec ?? {})) {\r\n insertRow[key] = -amount;\r\n }\r\n\r\n for (const key of Object.keys(operations.$unset ?? {})) {\r\n delete insertRow[key];\r\n }\r\n\r\n const columns = Object.keys(insertRow).filter((key) => insertRow[key] !== undefined);\r\n const params: unknown[] = columns.map((key) => this.serializeValue(key, insertRow[key], table));\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n const quotedColumns = columns.map((column) => this.dialect.quoteIdentifier(column)).join(\", \");\r\n const placeholders = columns.map((_, index) => this.dialect.placeholder(index + 1)).join(\", \");\r\n\r\n const set = this.buildSetClauses(table, operations, columns.length + 1, quotedTable);\r\n params.push(...set.params);\r\n\r\n const firstTargetColumn = this.dialect.quoteIdentifier(target.columns[0]!);\r\n const doUpdateSet =\r\n set.clauses.length > 0\r\n ? set.clauses.join(\", \")\r\n : `${firstTargetColumn} = ${quotedTable}.${firstTargetColumn}`;\r\n\r\n const remainingFilter = Object.fromEntries(\r\n Object.entries(filter).filter(([key]) => !target.columns.includes(key)),\r\n );\r\n const where = this.buildWhereClause(remainingFilter, set.nextIndex, quotedTable);\r\n params.push(...where.whereParams);\r\n\r\n const conflictColumns = target.columns\r\n .map((column) => this.dialect.quoteIdentifier(column))\r\n .join(\", \");\r\n const insertedFlag = \"__cascade_inserted\";\r\n const sql =\r\n `INSERT INTO ${quotedTable} (${quotedColumns}) VALUES (${placeholders}) ` +\r\n `ON CONFLICT (${conflictColumns}) DO UPDATE SET ${doUpdateSet} ${where.whereClause} ` +\r\n `RETURNING *, (xmax = 0) AS \"${insertedFlag}\"`;\r\n\r\n const result = await this.query<Record<string, unknown>>(sql, params);\r\n const returned = result.rows[0];\r\n\r\n if (!returned) {\r\n return { inserted: false };\r\n }\r\n\r\n const { [insertedFlag]: inserted, ...row } = returned;\r\n\r\n return { row, inserted: inserted === true };\r\n }\r\n\r\n /**\r\n * Unique keys of a table (primary key first), read from `pg_index`.\r\n *\r\n * Partial and expression indexes are excluded — they cannot be an\r\n * `ON CONFLICT (columns)` target. Non-empty results are cached per table.\r\n */\r\n private async getUniqueKeys(table: string): Promise<ReadonlyArray<UniqueKey>> {\r\n const cached = this._uniqueKeys.get(table);\r\n\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const result = await this.query<UniqueKey>(\r\n `SELECT array_agg(a.attname::text ORDER BY k.ord) AS columns, i.indisprimary AS primary\r\n FROM pg_index i\r\n CROSS JOIN LATERAL unnest(i.indkey::int2[]) WITH ORDINALITY AS k(attnum, ord)\r\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = k.attnum\r\n WHERE i.indrelid = to_regclass($1) AND i.indisunique AND i.indpred IS NULL AND i.indexprs IS NULL\r\n GROUP BY i.indexrelid, i.indisprimary\r\n ORDER BY i.indisprimary DESC, i.indexrelid`,\r\n [this.dialect.quoteIdentifier(table)],\r\n );\r\n\r\n if (result.rows.length > 0) {\r\n this._uniqueKeys.set(table, result.rows);\r\n }\r\n\r\n return result.rows;\r\n }\r\n\r\n /**\r\n * Update multiple rows matching the filter.\r\n *\r\n * @param table - Target table name\r\n * @param filter - Filter conditions\r\n * @param update - Update operations\r\n * @param options - Optional update options\r\n * @returns Update result with modified count\r\n */\r\n public async updateMany(\r\n table: string,\r\n filter: Record<string, unknown>,\r\n update: UpdateOperations,\r\n _options?: Record<string, unknown>,\r\n ): Promise<UpdateResult> {\r\n const { sql, params } = this.buildUpdateQuery(table, filter, update);\r\n\r\n const result = await this.query(sql, params);\r\n\r\n return {\r\n modifiedCount: result.rowCount ?? 0,\r\n };\r\n }\r\n\r\n /**\r\n * Replace a document matching the filter.\r\n *\r\n * Completely replaces the document (not a partial update).\r\n *\r\n * @param table - Target table name\r\n * @param filter - Filter conditions\r\n * @param document - New document data\r\n * @param options - Optional options\r\n * @returns The replaced document or null\r\n */\r\n public async replace<T = unknown>(\r\n table: string,\r\n filter: Record<string, unknown>,\r\n document: Record<string, unknown>,\r\n _options?: Record<string, unknown>,\r\n ): Promise<T | null> {\r\n const serialized = this.serialize(document, table);\r\n const columns = Object.keys(serialized);\r\n const values = Object.values(serialized);\r\n\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n const setClauses = columns\r\n .map((col, i) => `${this.dialect.quoteIdentifier(col)} = ${this.dialect.placeholder(i + 1)}`)\r\n .join(\", \");\r\n\r\n const { whereClause, whereParams } = this.buildWhereClause(filter, columns.length + 1);\r\n\r\n const sql = `UPDATE ${quotedTable} SET ${setClauses} ${whereClause} RETURNING *`;\r\n const params = [...values, ...whereParams];\r\n\r\n const result = await this.query<T>(sql, params);\r\n\r\n return result.rows[0] ?? null;\r\n }\r\n\r\n /**\r\n * Upsert (insert or update) a single row.\r\n *\r\n * Uses PostgreSQL's INSERT ... ON CONFLICT ... DO UPDATE syntax.\r\n *\r\n * @param table - Target table name\r\n * @param filter - Filter conditions to find existing row (used for conflict detection)\r\n * @param document - Document data to insert or update\r\n * @param options - Upsert options (conflictColumns for conflict target)\r\n * @returns The upserted row\r\n */\r\n public async upsert<T = unknown>(\r\n table: string,\r\n filter: Record<string, unknown>,\r\n document: Record<string, unknown>,\r\n options?: Record<string, unknown>,\r\n ): Promise<T> {\r\n const serialized = this.serialize(document, table);\r\n const columns = Object.keys(serialized);\r\n const values = Object.values(serialized);\r\n\r\n if (columns.length === 0) {\r\n throw new Error(\"Cannot upsert empty document\");\r\n }\r\n\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n const quotedColumns = columns.map((c) => this.dialect.quoteIdentifier(c)).join(\", \");\r\n const placeholders = columns.map((_, i) => this.dialect.placeholder(i + 1)).join(\", \");\r\n\r\n // Determine conflict columns from options or filter\r\n const conflictColumns = (options?.conflictColumns as string[]) ?? Object.keys(filter);\r\n if (conflictColumns.length === 0) {\r\n throw new Error(\"Upsert requires conflictColumns option or filter with columns\");\r\n }\r\n\r\n const quotedConflictColumns = conflictColumns\r\n .map((c) => this.dialect.quoteIdentifier(c))\r\n .join(\", \");\r\n\r\n // Build UPDATE clause for ON CONFLICT\r\n // Update all columns except the conflict columns (they stay the same)\r\n const updateColumns = columns.filter((col) => !conflictColumns.includes(col));\r\n const setClauses = updateColumns\r\n .map((col, i) => {\r\n const valueIndex = columns.indexOf(col) + 1;\r\n return `${this.dialect.quoteIdentifier(col)} = ${this.dialect.placeholder(valueIndex)}`;\r\n })\r\n .join(\", \");\r\n\r\n // If there are no columns to update (all columns are conflict columns), use EXCLUDED\r\n const updateClause =\r\n setClauses.length > 0\r\n ? setClauses\r\n : columns\r\n .map(\r\n (col) =>\r\n `${this.dialect.quoteIdentifier(col)} = EXCLUDED.${this.dialect.quoteIdentifier(col)}`,\r\n )\r\n .join(\", \");\r\n\r\n const sql = `INSERT INTO ${quotedTable} (${quotedColumns}) VALUES (${placeholders}) ON CONFLICT (${quotedConflictColumns}) DO UPDATE SET ${updateClause} RETURNING *`;\r\n\r\n const result = await this.query<T>(sql, values);\r\n\r\n return result.rows[0]! as T;\r\n }\r\n\r\n /**\r\n * Find one and delete a single row matching the filter and return the deleted row.\r\n *\r\n * @param table - Target table name\r\n * @param filter - Filter conditions\r\n * @param options - Optional delete options\r\n * @returns The deleted row or null\r\n */\r\n public async findOneAndDelete<T = unknown>(\r\n table: string,\r\n filter: Record<string, unknown>,\r\n _options?: Record<string, unknown>,\r\n ): Promise<T | null> {\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n const { whereClause, whereParams } = this.buildWhereClause(filter, 1);\r\n\r\n // Use ctid for single row deletion with RETURNING\r\n const sql = `DELETE FROM ${quotedTable} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${whereClause} LIMIT 1) RETURNING *`;\r\n\r\n const result = await this.query<T>(sql, whereParams);\r\n\r\n return result.rows[0] ? (result.rows[0] as T) : null;\r\n }\r\n\r\n /**\r\n * Delete a single row matching the filter.\r\n *\r\n * @param table - Target table name\r\n * @param filter - Filter conditions\r\n * @param options - Optional options\r\n * @returns Number of deleted rows (0 or 1)\r\n */\r\n public async delete(\r\n table: string,\r\n filter?: Record<string, unknown>,\r\n _options?: Record<string, unknown>,\r\n ): Promise<number> {\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n const { whereClause, whereParams } = this.buildWhereClause(filter ?? {}, 1);\r\n\r\n // Use ctid for single row deletion\r\n const sql = `DELETE FROM ${quotedTable} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${whereClause} LIMIT 1)`;\r\n\r\n const result = await this.query(sql, whereParams);\r\n\r\n return result.rowCount ?? 0;\r\n }\r\n\r\n /**\r\n * Delete multiple rows matching the filter.\r\n *\r\n * @param table - Target table name\r\n * @param filter - Filter conditions\r\n * @param options - Optional options\r\n * @returns Number of deleted rows\r\n */\r\n public async deleteMany(\r\n table: string,\r\n filter?: Record<string, unknown>,\r\n _options?: Record<string, unknown>,\r\n ): Promise<number> {\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n const { whereClause, whereParams } = this.buildWhereClause(filter ?? {}, 1);\r\n\r\n const sql = `DELETE FROM ${quotedTable} ${whereClause}`;\r\n\r\n const result = await this.query(sql, whereParams);\r\n\r\n return result.rowCount ?? 0;\r\n }\r\n\r\n /**\r\n * Truncate a table (remove all rows).\r\n *\r\n * Uses TRUNCATE TABLE for fast deletion with RESTART IDENTITY.\r\n *\r\n * @param table - Target table name\r\n * @param options - Optional options\r\n * @param options.cascade - If true, automatically truncate all tables with foreign key references (use with caution)\r\n * @returns Number of deleted rows (always 0 for TRUNCATE)\r\n */\r\n public async truncateTable(table: string, options?: { cascade?: boolean }): Promise<number> {\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n const cascadeClause = options?.cascade ? \" CASCADE\" : \"\";\r\n await this.query(`TRUNCATE TABLE ${quotedTable} RESTART IDENTITY${cascadeClause}`);\r\n return 0; // TRUNCATE doesn't return row count\r\n }\r\n\r\n /**\r\n * Get a query builder for the specified table.\r\n *\r\n * @param table - Target table name\r\n * @returns Query builder instance\r\n */\r\n public queryBuilder<T = unknown>(table: string): QueryBuilderContract<T> {\r\n return new PostgresQueryBuilder<T>(table) as unknown as QueryBuilderContract<T>;\r\n }\r\n\r\n /**\r\n * Begin a new database transaction.\r\n *\r\n * Acquires a client from the pool and starts a transaction.\r\n * The client is stored in AsyncLocalStorage for automatic\r\n * participation by subsequent queries.\r\n *\r\n * @param options - Optional transaction options\r\n * @returns Transaction contract with commit/rollback methods\r\n */\r\n public async beginTransaction(\r\n options?: PostgresTransactionOptions,\r\n ): Promise<DriverTransactionContract<PgPoolClient>> {\r\n const client = await this.pool.connect();\r\n\r\n // Every checkout above must be released on every path — including the\r\n // BEGIN itself failing. Before this try/catch, a BEGIN that threw (e.g.\r\n // the connection was terminated the instant it came off the pool) left\r\n // the client checked out forever: under concurrency that's exactly the\r\n // pool-exhaustion \"timeout exceeded when trying to connect\" symptom in\r\n // card ba1193b4.\r\n try {\r\n let beginSql = \"BEGIN\";\r\n if (options?.isolationLevel) {\r\n beginSql += ` ISOLATION LEVEL ${options.isolationLevel.toUpperCase()}`;\r\n }\r\n if (options?.readOnly) {\r\n beginSql += \" READ ONLY\";\r\n }\r\n if (options?.deferrable) {\r\n beginSql += \" DEFERRABLE\";\r\n }\r\n\r\n await client.query(beginSql);\r\n } catch (error) {\r\n // Pass the error to release() so pg destroys the client instead of\r\n // returning a possibly-broken connection to the pool.\r\n client.release(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n\r\n return {\r\n context: client,\r\n commit: async () => {\r\n // COMMIT/ROLLBACK failing must still release the client — a bare\r\n // `await client.query(...); client.release();` skips release() on\r\n // that throw, which is the other leak this card describes. A failed\r\n // COMMIT releases with the error (exactly once) so pg discards the\r\n // client instead of recycling a connection left in an unknown\r\n // transaction state; the original COMMIT error still propagates and\r\n // the caller must NOT issue ROLLBACK afterwards (see transaction()).\r\n try {\r\n await client.query(\"COMMIT\");\r\n } catch (error) {\r\n client.release(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n client.release();\r\n },\r\n rollback: async () => {\r\n try {\r\n await client.query(\"ROLLBACK\");\r\n } catch (error) {\r\n client.release(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n client.release();\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Execute a function within a transaction scope (recommended pattern).\r\n *\r\n * Automatically commits on success, rolls back on any error, and guarantees\r\n * resource cleanup. This is the recommended way to use transactions.\r\n *\r\n * @param fn - Async function to execute within transaction\r\n * @param options - Transaction options (isolation level, read-only, etc.)\r\n * @returns The return value of the callback function\r\n * @throws {Error} If transaction fails or is explicitly rolled back\r\n */\r\n public async transaction<T>(\r\n fn: (ctx: TransactionContext) => Promise<T>,\r\n options?: Record<string, unknown>,\r\n ): Promise<T> {\r\n const ctx: TransactionContext = {\r\n rollback(reason?: string): never {\r\n throw new TransactionRollbackError(reason);\r\n },\r\n };\r\n\r\n // Flat nesting: a transaction() called while one is already active JOINS it\r\n // instead of opening a second, independent transaction on another pool\r\n // connection. An independent inner transaction can't see the outer's\r\n // uncommitted writes (a row inserted moments earlier), so a service that\r\n // opens its own transaction — correct when called standalone — would hit\r\n // phantom FK violations when called inside an outer transaction (e.g. a\r\n // seeder that creates a row, then calls a service that references it). The\r\n // outermost transaction owns BEGIN / COMMIT / ROLLBACK; the inner block\r\n // runs on the same session, and a throw still unwinds the whole outer\r\n // transaction. (Savepoint-based partial rollback is a separate, explicit\r\n // concern — use `beginTransaction()` directly for that.)\r\n if (databaseTransactionContext.hasActiveTransaction()) {\r\n return fn(ctx);\r\n }\r\n\r\n const tx = await this.beginTransaction(options);\r\n\r\n // Set transaction context for queries within callback\r\n databaseTransactionContext.enter({ session: tx.context });\r\n\r\n // Tracks whether tx.commit() was reached, so the catch block below can\r\n // tell a callback failure (client still live, must ROLLBACK) apart from\r\n // a COMMIT failure (commit() already released the client — with the\r\n // error, per above — so it's discarded; issuing ROLLBACK on it would\r\n // touch an already-released client and double-release it).\r\n let commitStarted = false;\r\n\r\n try {\r\n // Execute callback\r\n const result = await fn(ctx);\r\n\r\n // Auto-commit on success\r\n commitStarted = true;\r\n await tx.commit();\r\n\r\n return result;\r\n } catch (error) {\r\n if (commitStarted) {\r\n // COMMIT itself failed: the original error propagates unchanged and\r\n // no ROLLBACK is issued after it.\r\n log.error(\r\n `database.postgress`,\r\n \"transaction\",\r\n \"Transaction COMMIT failed; client discarded, no rollback issued\",\r\n );\r\n } else {\r\n // Auto-rollback on any error (including explicit rollback). A failing\r\n // ROLLBACK is logged, never thrown: the callback's error is the one\r\n // the caller needs, and rollback() has already discarded the client.\r\n try {\r\n await tx.rollback();\r\n log.error(\r\n `database.postgress`,\r\n \"transaction\",\r\n \"Transaction operation failed, rolled back everything\",\r\n );\r\n } catch (rollbackError) {\r\n log.error(`database.postgress`, \"transaction\", rollbackError);\r\n }\r\n }\r\n throw error;\r\n } finally {\r\n // Guaranteed cleanup\r\n databaseTransactionContext.exit();\r\n }\r\n }\r\n\r\n /**\r\n * Perform an atomic update operation.\r\n *\r\n * Builds and executes an UPDATE query for the given filter and operations.\r\n * Updates EVERY matching row — the MongoDB driver's atomic() delegates to\r\n * updateMany, and Model.findAndUpdate documents multi-row semantics, so the\r\n * two drivers must agree.\r\n *\r\n * @param table - Target table name\r\n * @param filter - Filter conditions\r\n * @param operations - Update operations\r\n * @param options - Optional options\r\n * @returns Update result\r\n */\r\n public async atomic(\r\n table: string,\r\n filter: Record<string, unknown>,\r\n operations: AtomicUpdate,\r\n options?: DriverAtomicUpdateOptions,\r\n ): Promise<UpdateResult> {\r\n const update = assertPostgresUpdate(operations, options);\r\n\r\n if (options?.upsert) {\r\n const { row, inserted } = await this.executeUpsert(table, filter, update);\r\n\r\n return {\r\n modifiedCount: row && !inserted ? 1 : 0,\r\n upsertedCount: row && inserted ? 1 : 0,\r\n };\r\n }\r\n\r\n const { sql, params } = this.buildUpdateQuery(table, filter, update);\r\n\r\n const result = await this.query(sql, params);\r\n\r\n return {\r\n modifiedCount: result.rowCount ?? 0,\r\n };\r\n }\r\n\r\n /**\r\n * Get the sync adapter for bulk denormalized updates.\r\n *\r\n * @returns Sync adapter instance\r\n */\r\n public syncAdapter(): SyncAdapterContract {\r\n if (!this._syncAdapter) {\r\n this._syncAdapter = new PostgresSyncAdapter(this);\r\n }\r\n return this._syncAdapter;\r\n }\r\n\r\n /**\r\n * Get the migration driver for schema operations.\r\n *\r\n * @returns Migration driver instance\r\n */\r\n public migrationDriver(): MigrationDriverContract {\r\n if (!this._migrationDriver) {\r\n this._migrationDriver = new PostgresMigrationDriver(this);\r\n }\r\n\r\n return this._migrationDriver;\r\n }\r\n\r\n /**\r\n * Return a SQL serializer for this driver's dialect.\r\n * Used by Migration.toSQL() to convert pending operations to SQL strings.\r\n */\r\n public getSQLSerializer(): SQLSerializer {\r\n return new PostgresSQLSerializer(this.dialect);\r\n }\r\n\r\n /**\r\n * Execute a raw SQL query.\r\n *\r\n * Automatically uses the transaction client if one is active.\r\n *\r\n * @param sql - SQL query string\r\n * @param params - Query parameters\r\n * @returns Query result\r\n */\r\n public async query<T = Record<string, unknown>>(\r\n sql: string,\r\n params: unknown[] = [],\r\n ): Promise<PostgresQueryResult<T>> {\r\n // Check for active transaction client\r\n const txClient = databaseTransactionContext.getSession() as PgPoolClient | undefined;\r\n\r\n const startTime = this.config.logging ? performance.now() : 0;\r\n\r\n let paramsString = \"\";\r\n if (this.config.logging && params.length > 0) {\r\n paramsString = JSON.stringify(params);\r\n if (paramsString.length > 300) {\r\n paramsString = paramsString.substring(0, 300) + \"...\";\r\n }\r\n paramsString = ` | Params: ${paramsString}`;\r\n }\r\n\r\n try {\r\n let result;\r\n if (this.config.logging) {\r\n log.info({\r\n module: \"database.postgres\",\r\n action: \"query.executing\",\r\n message: `${sql}${paramsString}`,\r\n context: { params, sql },\r\n });\r\n }\r\n if (txClient) {\r\n result = await txClient.query(sql, params);\r\n } else {\r\n // No automatic retry here: a client that looked idle-healthy to the\r\n // pool can already have been dropped by the server/OS (card\r\n // ba1193b4: \"Connection terminated unexpectedly\" after the process\r\n // sat idle), and the pool only discovers this on the query that\r\n // reuses it. Re-running based on the SQL text \"looking like\" a\r\n // read-only SELECT is not a safe idempotency check — `SELECT\r\n // nextval('seq')` and `SELECT some_write_function()` both change\r\n // state, and a retry after the connection dropped between the\r\n // side effect and the response would duplicate it. Callers that\r\n // need retry semantics must implement them with real idempotency\r\n // guarantees at the call site.\r\n result = await this.pool.query(sql, params);\r\n }\r\n\r\n if (this.config.logging) {\r\n const duration = (performance.now() - startTime).toFixed(2);\r\n log.success({\r\n module: \"database.postgres\",\r\n action: \"query.executed\",\r\n message: `[${duration}ms] ${sql}${paramsString}`,\r\n context: { params, sql, duration },\r\n });\r\n }\r\n\r\n return result as PostgresQueryResult<T>;\r\n } catch (error) {\r\n if (this.config.logging) {\r\n const duration = (performance.now() - startTime).toFixed(2);\r\n log.error({\r\n module: \"database.postgres\",\r\n action: \"query.error\",\r\n message: `[${duration}ms] ${sql}${paramsString}`,\r\n context: {\r\n sql,\r\n params,\r\n error: error instanceof Error ? error.message : String(error),\r\n },\r\n });\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Emit an event to all registered listeners.\r\n *\r\n * @param event - Event name\r\n * @param args - Event arguments\r\n */\r\n private emit(event: string, ...args: unknown[]): void {\r\n const listeners = this._eventListeners.get(event);\r\n if (listeners) {\r\n for (const listener of listeners) {\r\n listener(...args);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Build a simple WHERE clause from a filter object.\r\n *\r\n * Values are bound as plain equality, except Mongo-style operator objects\r\n * (`{ $in: [...] }`, `{ $gt: 5 }`, ...) which are translated to their SQL\r\n * equivalents — driver-level callers (e.g. pivot detach) build filters in\r\n * that portable form. An unrecognized `$` operator throws instead of being\r\n * bound literally, which would only surface as a cryptic type error from\r\n * Postgres.\r\n *\r\n * @param filter - Filter conditions\r\n * @param startParamIndex - Starting parameter index\r\n * @returns Object with WHERE clause string and parameters\r\n */\r\n private buildWhereClause(\r\n filter: Record<string, unknown>,\r\n startParamIndex: number,\r\n qualifier?: string,\r\n ): { whereClause: string; whereParams: unknown[]; condition: string } {\r\n const conditions: string[] = [];\r\n const params: unknown[] = [];\r\n let paramIndex = startParamIndex;\r\n\r\n for (const [key, value] of Object.entries(filter)) {\r\n const quotedKey = (qualifier ? `${qualifier}.` : \"\") + this.dialect.quoteIdentifier(key);\r\n\r\n if (value === null) {\r\n conditions.push(`${quotedKey} IS NULL`);\r\n } else if (this.isOperatorFilter(value)) {\r\n for (const [operator, operand] of Object.entries(value as Record<string, unknown>)) {\r\n switch (operator) {\r\n case \"$in\":\r\n case \"$nin\": {\r\n const list = operand as unknown[];\r\n if (list.length === 0) {\r\n // $in [] matches nothing; $nin [] matches everything.\r\n conditions.push(operator === \"$in\" ? \"FALSE\" : \"TRUE\");\r\n break;\r\n }\r\n const placeholders = list.map(() => this.dialect.placeholder(paramIndex++));\r\n params.push(...list);\r\n conditions.push(\r\n `${quotedKey} ${operator === \"$in\" ? \"IN\" : \"NOT IN\"} (${placeholders.join(\", \")})`,\r\n );\r\n break;\r\n }\r\n case \"$eq\":\r\n if (operand === null) {\r\n conditions.push(`${quotedKey} IS NULL`);\r\n } else {\r\n conditions.push(`${quotedKey} = ${this.dialect.placeholder(paramIndex++)}`);\r\n params.push(operand);\r\n }\r\n break;\r\n case \"$ne\":\r\n if (operand === null) {\r\n conditions.push(`${quotedKey} IS NOT NULL`);\r\n } else {\r\n conditions.push(`${quotedKey} != ${this.dialect.placeholder(paramIndex++)}`);\r\n params.push(operand);\r\n }\r\n break;\r\n case \"$gt\":\r\n case \"$gte\":\r\n case \"$lt\":\r\n case \"$lte\": {\r\n const sqlOperator = { $gt: \">\", $gte: \">=\", $lt: \"<\", $lte: \"<=\" }[operator];\r\n conditions.push(\r\n `${quotedKey} ${sqlOperator} ${this.dialect.placeholder(paramIndex++)}`,\r\n );\r\n params.push(operand);\r\n break;\r\n }\r\n default:\r\n throw new Error(\r\n `Unsupported filter operator \"${operator}\" for column \"${key}\" on the Postgres driver.`,\r\n );\r\n }\r\n }\r\n } else {\r\n conditions.push(`${quotedKey} = ${this.dialect.placeholder(paramIndex++)}`);\r\n params.push(value);\r\n }\r\n }\r\n\r\n const condition = conditions.join(\" AND \");\r\n const whereClause = condition ? `WHERE ${condition}` : \"\";\r\n\r\n return { whereClause, whereParams: params, condition };\r\n }\r\n\r\n /**\r\n * A filter value is an operator object when it is a plain object whose keys\r\n * ALL start with `$`. Arrays, Dates, and value objects (e.g. jsonb equality\r\n * payloads) keep their existing bind-as-value behavior.\r\n */\r\n private isOperatorFilter(value: unknown): boolean {\r\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\r\n return false;\r\n }\r\n\r\n if (value instanceof Date || Buffer.isBuffer(value)) {\r\n return false;\r\n }\r\n\r\n const keys = Object.keys(value);\r\n return keys.length > 0 && keys.every((key) => key.startsWith(\"$\"));\r\n }\r\n\r\n /**\r\n * Build the SET clauses for an update.\r\n *\r\n * @param table - Target table name (for value serialization)\r\n * @param update - Update operations (`$setOnInsert` is not part of SET)\r\n * @param startIndex - First placeholder index\r\n * @param qualifier - Quoted table name to qualify right-hand column references\r\n * (needed where another relation with the same columns is in scope)\r\n */\r\n private buildSetClauses(\r\n table: string,\r\n update: UpdateOperations,\r\n startIndex: number,\r\n qualifier?: string,\r\n ): { clauses: string[]; params: unknown[]; nextIndex: number } {\r\n const clauses: string[] = [];\r\n const params: unknown[] = [];\r\n let paramIndex = startIndex;\r\n const reference = (quotedKey: string) => (qualifier ? `${qualifier}.${quotedKey}` : quotedKey);\r\n\r\n // Handle $set\r\n if (update.$set) {\r\n for (const [key, value] of Object.entries(update.$set)) {\r\n clauses.push(\r\n `${this.dialect.quoteIdentifier(key)} = ${this.dialect.placeholder(paramIndex++)}`,\r\n );\r\n // Apply the same json/jsonb-aware serialization used on the INSERT\r\n // path so object/array $set values aren't corrupted into Postgres\r\n // array literals. undefined is skipped — never bind it.\r\n params.push(value === undefined ? value : this.serializeValue(key, value, table));\r\n }\r\n }\r\n\r\n // Handle $unset (set to NULL)\r\n if (update.$unset) {\r\n for (const key of Object.keys(update.$unset)) {\r\n clauses.push(`${this.dialect.quoteIdentifier(key)} = NULL`);\r\n }\r\n }\r\n\r\n // Handle $inc / $dec\r\n const arithmetic = [\r\n [\"$inc\", \"+\"],\r\n [\"$dec\", \"-\"],\r\n ] as const;\r\n\r\n for (const [operator, sign] of arithmetic) {\r\n for (const [key, amount] of Object.entries(update[operator] ?? {})) {\r\n const quotedKey = this.dialect.quoteIdentifier(key);\r\n clauses.push(\r\n `${quotedKey} = COALESCE(${reference(quotedKey)}, 0) ${sign} ${this.dialect.placeholder(paramIndex++)}`,\r\n );\r\n params.push(amount);\r\n }\r\n }\r\n\r\n return { clauses, params, nextIndex: paramIndex };\r\n }\r\n\r\n /**\r\n * Build an UPDATE query from update operations.\r\n *\r\n * @param table - Target table name\r\n * @param filter - Filter conditions\r\n * @param update - Update operations\r\n * @param limit - Optional limit (for single row update)\r\n * @returns Object with SQL and parameters\r\n * @throws UnsupportedUpdateOperationError for operations SQL cannot express\r\n */\r\n private buildUpdateQuery(\r\n table: string,\r\n filter: Record<string, unknown>,\r\n update: AtomicUpdate,\r\n limit?: number,\r\n ): { sql: string; params: unknown[] } {\r\n const set = this.buildSetClauses(table, assertPostgresUpdate(update), 1);\r\n\r\n if (set.clauses.length === 0) {\r\n throw new Error(\"No update operations specified\");\r\n }\r\n\r\n const params = [...set.params];\r\n const quotedTable = this.dialect.quoteIdentifier(table);\r\n const { whereClause, whereParams } = this.buildWhereClause(filter, set.nextIndex);\r\n params.push(...whereParams);\r\n\r\n let sql = `UPDATE ${quotedTable} SET ${set.clauses.join(\", \")} ${whereClause}`;\r\n\r\n // For single row update, use ctid subquery\r\n if (limit === 1 && whereClause) {\r\n sql = `UPDATE ${quotedTable} SET ${set.clauses.join(\", \")} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${whereClause} LIMIT 1)`;\r\n }\r\n\r\n return { sql, params };\r\n }\r\n\r\n // ============================================================\r\n // Database Lifecycle Operations\r\n // ============================================================\r\n\r\n /**\r\n * Create a new database.\r\n *\r\n * Note: This requires connecting to a system database (like 'postgres')\r\n * since you cannot create a database while connected to it.\r\n *\r\n * @param name - Database name to create\r\n * @param options - Creation options (encoding, template, etc.)\r\n * @returns true if created, false if already exists\r\n */\r\n public async createDatabase(name: string, options?: CreateDatabaseOptions): Promise<boolean> {\r\n // Check if database already exists\r\n if (await this.databaseExists(name)) {\r\n return false;\r\n }\r\n\r\n // Build CREATE DATABASE statement\r\n const quotedName = this.dialect.quoteIdentifier(name);\r\n let sql = `CREATE DATABASE ${quotedName}`;\r\n\r\n const withClauses: string[] = [];\r\n\r\n if (options?.encoding) {\r\n withClauses.push(`ENCODING = '${options.encoding}'`);\r\n }\r\n if (options?.template) {\r\n withClauses.push(`TEMPLATE = ${this.dialect.quoteIdentifier(options.template)}`);\r\n }\r\n if (options?.locale) {\r\n withClauses.push(`LC_COLLATE = '${options.locale}'`);\r\n withClauses.push(`LC_CTYPE = '${options.locale}'`);\r\n }\r\n if (options?.owner) {\r\n withClauses.push(`OWNER = ${this.dialect.quoteIdentifier(options.owner)}`);\r\n }\r\n\r\n if (withClauses.length > 0) {\r\n sql += ` WITH ${withClauses.join(\" \")}`;\r\n }\r\n\r\n try {\r\n await this.query(sql);\r\n log.success(\"database\", \"lifecycle\", `Created database ${name}`);\r\n return true;\r\n } catch (error) {\r\n log.error(\"database\", \"lifecycle\", `Failed to create database ${name}: ${error}`);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Drop a database.\r\n *\r\n * @param name - Database name to drop\r\n * @param options - Drop options\r\n * @returns true if dropped, false if didn't exist\r\n */\r\n public async dropDatabase(name: string, options?: DropDatabaseOptions): Promise<boolean> {\r\n // Check if database exists first (if ifExists option not set)\r\n if (!options?.ifExists && !(await this.databaseExists(name))) {\r\n return false;\r\n }\r\n\r\n const quotedName = this.dialect.quoteIdentifier(name);\r\n let sql = \"DROP DATABASE\";\r\n\r\n if (options?.ifExists) {\r\n sql += \" IF EXISTS\";\r\n }\r\n\r\n sql += ` ${quotedName}`;\r\n\r\n // PostgreSQL 13+ supports WITH (FORCE) to terminate active connections\r\n if (options?.force) {\r\n sql += \" WITH (FORCE)\";\r\n }\r\n\r\n try {\r\n await this.query(sql);\r\n log.success(\"database\", \"lifecycle\", `Dropped database ${name}`);\r\n return true;\r\n } catch (error) {\r\n log.error(\"database\", \"lifecycle\", `Failed to drop database ${name}: ${error}`);\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Check if a database exists.\r\n *\r\n * @param name - Database name to check\r\n * @returns true if database exists\r\n */\r\n public async databaseExists(name: string): Promise<boolean> {\r\n const result = await this.query<{ exists: boolean }>(\r\n `SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname = $1) as exists`,\r\n [name],\r\n );\r\n\r\n return result.rows[0]?.exists ?? false;\r\n }\r\n\r\n /**\r\n * List all databases.\r\n *\r\n * @returns Array of database names\r\n */\r\n public async listDatabases(): Promise<string[]> {\r\n const result = await this.query<{ datname: string }>(\r\n `SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname`,\r\n );\r\n\r\n return result.rows.map((row) => row.datname);\r\n }\r\n\r\n // ============================================================\r\n // Table Management Operations\r\n // ============================================================\r\n\r\n /**\r\n * Drop a table.\r\n *\r\n * @param name - Table name to drop\r\n * @throws Error if table doesn't exist\r\n */\r\n public async dropTable(name: string): Promise<void> {\r\n const quotedName = this.dialect.quoteIdentifier(name);\r\n await this.query(`DROP TABLE ${quotedName}`);\r\n log.success(\"database\", \"table\", `Dropped table ${name}`);\r\n }\r\n\r\n /**\r\n * Drop a table if it exists.\r\n *\r\n * @param name - Table name to drop\r\n */\r\n public async dropTableIfExists(name: string): Promise<void> {\r\n const quotedName = this.dialect.quoteIdentifier(name);\r\n await this.query(`DROP TABLE IF EXISTS ${quotedName}`);\r\n }\r\n\r\n /**\r\n * Drop all tables in the current database.\r\n *\r\n * Uses CASCADE to handle foreign key dependencies.\r\n * Useful for `migrate:fresh` command.\r\n */\r\n public async dropAllTables(): Promise<void> {\r\n // Get all tables from blueprint\r\n const tables = await this.blueprint.listTables();\r\n\r\n if (tables.length === 0) {\r\n return;\r\n }\r\n\r\n // Drop all tables with CASCADE to handle foreign keys\r\n for (const table of tables) {\r\n const quotedName = this.dialect.quoteIdentifier(table);\r\n await this.query(`DROP TABLE IF EXISTS ${quotedName} CASCADE`);\r\n }\r\n\r\n log.success(\"database\", \"table\", `Dropped ${tables.length} tables`);\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -30,8 +30,18 @@ type PostgresConnectionConfig = {
|
|
|
30
30
|
type PostgresPoolConfig = PostgresConnectionConfig & {
|
|
31
31
|
/** Maximum number of clients in the pool (default: 10) */readonly max?: number; /** Minimum number of clients in the pool (default: 0) */
|
|
32
32
|
readonly min?: number; /** How long a client can sit idle before being closed (ms) */
|
|
33
|
-
readonly idleTimeoutMillis?: number; /** How long to wait for a client before timing out (ms) */
|
|
34
|
-
readonly connectionTimeoutMillis?: number;
|
|
33
|
+
readonly idleTimeoutMillis?: number; /** How long to wait for a client before timing out (ms) (default: 10000) */
|
|
34
|
+
readonly connectionTimeoutMillis?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Enable TCP keep-alive on socket connections (default: true).
|
|
37
|
+
*
|
|
38
|
+
* Without it, a NAT/firewall/OS can silently drop an idle TCP connection
|
|
39
|
+
* with neither side told. The next query on that connection then fails
|
|
40
|
+
* with pg's "Connection terminated unexpectedly" instead of a clean
|
|
41
|
+
* timeout, which is exactly what surfaced on first requests after the
|
|
42
|
+
* server sat idle (card ba1193b4).
|
|
43
|
+
*/
|
|
44
|
+
readonly keepAlive?: boolean; /** Maximum times to use a connection before destroying it */
|
|
35
45
|
readonly maxUses?: number; /** Application name for connection identification */
|
|
36
46
|
readonly application_name?: string;
|
|
37
47
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../cascade/src/drivers/postgres/types.ts"],"mappings":";;AAWA;;;;;;;;;KAAY,wBAAA;EAcD,oDAZA,IAAA,WAgBM;EAAA,SAdN,IAAA,WAgBM;EAAA,SAdN,QAAA,UAiBO;EAAA,SAfP,IAAA,WAqBC;EAAA,SAnBD,QAAA;WAEA,gBAAA,WAiBsB;EAAA,SAftB,GAAA;IAAA,SAGM,kBAAA;IAAA,SACA,EAAA;IAAA,SACA,IAAA;IAAA,SACA,GAAA;EAAA,
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../../cascade/src/drivers/postgres/types.ts"],"mappings":";;AAWA;;;;;;;;;KAAY,wBAAA;EAcD,oDAZA,IAAA,WAgBM;EAAA,SAdN,IAAA,WAgBM;EAAA,SAdN,QAAA,UAiBO;EAAA,SAfP,IAAA,WAqBC;EAAA,SAnBD,QAAA;WAEA,gBAAA,WAiBsB;EAAA,SAftB,GAAA;IAAA,SAGM,kBAAA;IAAA,SACA,EAAA;IAAA,SACA,IAAA;IAAA,SACA,GAAA;EAAA,GA+BN;EAAA,SA5BA,OAAA;AAAA;AA2CkB;AAM7B;;AAN6B,KArCjB,kBAAA,GAAqB,wBAAwB;EA2CrB,mEAzCzB,GAAA,WA+CQ;EAAA,SA7CR,GAAA,WA6Ca;EAAA,SA3Cb,iBAAA,WAqCyB;EAAA,SAnCzB,uBAAA;EAqCM;;;;;;;;AASC;EATD,SA3BN,SAAA,YA0CuB;EAAA,SAxCvB,OAAA,WAwCuB;EAAA,SAtCvB,gBAAA;EA4CC;;;;;;;;;AAMS;AAMrB;;;;EAZY,SA7BD,kBAAA;AAAA;;;;KAMC,mBAAA,KAAwB,MAAA;EAgDxB,mDA9CD,IAAA,EAAM,CAAA;WAEN,QAAA,iBA8CA;EAAA,SA5CA,MAAA,EAAQ,KAAA;IAAA,SACN,IAAA;IAAA,SACA,UAAA;EAAA,IAgEF;EAAA,SA7DA,OAAA;AAAA;;;AAiE4B;KA3D3B,sBAAA;;;;KAMA,0BAAA;EA+DD,mDA7DA,cAAA,GAAiB,sBAAsB,EA+D9B;EAAA,SA7DT,QAAA,YAmEC;EAAA,SAjED,UAAA;AAAA;;;;KAMC,iBAAA;EAmED,2DAjEA,KAAA,gGAqEA;EAAA,SAlEA,IAAA,UAoEO;EAAA,SAlEP,IAAA,EAAM,MAAM;AAAA;;;;KAMX,mBAAA;kEAED,OAAA;WAEA,IAAA;WAcA,MAAA;WAEA,QAAA;WAEA,KAAA;WAEA,GAAA;WAEA,QAAA;WAEA,MAAA,GAAS,mBAAmB;AAAA;;;;KAM3B,oBAAA;8BAED,OAAA;WAEA,OAAA;WAEA,SAAA;AAAA;;;;KAMC,mBAAA;4CAED,MAAA;WAEA,SAAA;WAEA,KAAA;WAEA,MAAA;WAEA,MAAA;WAEA,IAAA;WAEA,OAAA;AAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region ../cascade/src/errors/undefined-where-value.error.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when a `where()`-family call is given `undefined` as the
|
|
4
|
+
* bound value.
|
|
5
|
+
*
|
|
6
|
+
* A bound `undefined` reaches SQL drivers as `= NULL`, which never matches
|
|
7
|
+
* any row (SQL's three-valued logic — `NULL = NULL` is `NULL`, not `true`) —
|
|
8
|
+
* and reaches the MongoDB driver as a filter that matches every document
|
|
9
|
+
* missing the field. Either way the query silently returns the wrong rows
|
|
10
|
+
* instead of failing loudly, which usually means the caller forgot to guard
|
|
11
|
+
* an id/value that turned out to be missing (e.g. `User.find(post.authorId)`
|
|
12
|
+
* when `authorId` came back `undefined`).
|
|
13
|
+
*
|
|
14
|
+
* To match `NULL` explicitly, pass `null` — `where("deletedAt", null)` is a
|
|
15
|
+
* valid, intentional query. To skip the query entirely when there's no
|
|
16
|
+
* value, guard the call site instead of calling `where()`.
|
|
17
|
+
*/
|
|
18
|
+
declare class UndefinedWhereValueError extends Error {
|
|
19
|
+
/**
|
|
20
|
+
* The field whose value was `undefined`.
|
|
21
|
+
*/
|
|
22
|
+
readonly field: string;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new UndefinedWhereValueError.
|
|
25
|
+
*
|
|
26
|
+
* @param field - The field name whose value was rejected
|
|
27
|
+
*/
|
|
28
|
+
constructor(field: string);
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { UndefinedWhereValueError };
|
|
32
|
+
//# sourceMappingURL=undefined-where-value.error.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"undefined-where-value.error.d.mts","names":[],"sources":["../../../../../../../cascade/src/errors/undefined-where-value.error.ts"],"mappings":";;AAgBA;;;;;;;;;AAWkC;;;;;;cAXrB,wBAAA,SAAiC,KAAK;;;;WAIjC,KAAA;;;;;;cAOG,KAAA;AAAA"}
|