@warlock.js/cascade 5.20.0 → 5.22.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 +40 -1
- package/cjs/index.cjs +2460 -1049
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/query-builder.contract.d.mts +30 -3
- package/esm/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.d.mts +33 -1
- package/esm/data-source/data-source-registry.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.mjs +75 -5
- package/esm/data-source/data-source-registry.mjs.map +1 -1
- package/esm/database-dirty-tracker.d.mts +5 -0
- package/esm/database-dirty-tracker.d.mts.map +1 -1
- package/esm/database-dirty-tracker.mjs +16 -0
- package/esm/database-dirty-tracker.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.d.mts +12 -0
- package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.mjs +47 -16
- package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs +15 -18
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts +13 -3
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +72 -19
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts +6 -0
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs +128 -30
- package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/index.d.mts +2 -3
- package/esm/drivers/postgres/index.mjs +1 -2
- package/esm/drivers/postgres/postgres-blueprint.mjs +4 -4
- package/esm/drivers/postgres/postgres-blueprint.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts +5 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.mjs +38 -8
- package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts +52 -29
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +193 -57
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts +2 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.mjs +13 -6
- package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.d.mts +18 -7
- package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs +146 -31
- package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts +23 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.mjs +160 -43
- package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-sql-serializer.mjs +3 -2
- package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-ttl-purge.mjs +98 -0
- package/esm/drivers/postgres/postgres-ttl-purge.mjs.map +1 -0
- package/esm/drivers/postgres/postgres-type-converters.mjs +57 -0
- package/esm/drivers/postgres/postgres-type-converters.mjs.map +1 -0
- package/esm/events/model-events.d.mts +1 -1
- package/esm/events/model-events.d.mts.map +1 -1
- package/esm/events/model-events.mjs.map +1 -1
- package/esm/index.d.mts +5 -6
- package/esm/index.mjs +4 -5
- package/esm/migration/column-builder.d.mts.map +1 -1
- package/esm/migration/column-builder.mjs +2 -2
- package/esm/migration/column-builder.mjs.map +1 -1
- package/esm/migration/index.d.mts +2 -2
- package/esm/migration/index.mjs +1 -1
- package/esm/migration/migration-order.mjs +5 -2
- package/esm/migration/migration-order.mjs.map +1 -1
- package/esm/migration/migration-runner.d.mts +50 -5
- package/esm/migration/migration-runner.d.mts.map +1 -1
- package/esm/migration/migration-runner.mjs +369 -146
- package/esm/migration/migration-runner.mjs.map +1 -1
- package/esm/migration/migration.d.mts +36 -3
- package/esm/migration/migration.d.mts.map +1 -1
- package/esm/migration/migration.mjs +62 -5
- package/esm/migration/migration.mjs.map +1 -1
- package/esm/model/methods/accessor-methods.mjs +1 -3
- package/esm/model/methods/accessor-methods.mjs.map +1 -1
- package/esm/model/methods/delete-methods.mjs +6 -2
- package/esm/model/methods/delete-methods.mjs.map +1 -1
- package/esm/model/methods/hydration-methods.mjs +16 -2
- package/esm/model/methods/hydration-methods.mjs.map +1 -1
- package/esm/model/methods/instance-event-methods.mjs +2 -1
- package/esm/model/methods/instance-event-methods.mjs.map +1 -1
- package/esm/model/methods/meta-methods.mjs +53 -16
- package/esm/model/methods/meta-methods.mjs.map +1 -1
- package/esm/model/methods/query-methods.mjs +86 -15
- package/esm/model/methods/query-methods.mjs.map +1 -1
- package/esm/model/methods/static-event-methods.mjs +16 -1
- package/esm/model/methods/static-event-methods.mjs.map +1 -1
- package/esm/model/methods/write-methods.d.mts.map +1 -1
- package/esm/model/methods/write-methods.mjs +50 -50
- package/esm/model/methods/write-methods.mjs.map +1 -1
- package/esm/model/model.d.mts +16 -1
- package/esm/model/model.d.mts.map +1 -1
- package/esm/model/model.mjs +39 -5
- package/esm/model/model.mjs.map +1 -1
- package/esm/model/register-model.d.mts +6 -1
- package/esm/model/register-model.d.mts.map +1 -1
- package/esm/model/register-model.mjs +16 -1
- package/esm/model/register-model.mjs.map +1 -1
- package/esm/query-builder/query-builder.d.mts +21 -2
- package/esm/query-builder/query-builder.d.mts.map +1 -1
- package/esm/query-builder/query-builder.mjs +97 -20
- package/esm/query-builder/query-builder.mjs.map +1 -1
- package/esm/relations/key-conventions.mjs +13 -3
- package/esm/relations/key-conventions.mjs.map +1 -1
- package/esm/relations/pivot-operations.mjs +1 -1
- package/esm/relations/relation-loader.d.mts +15 -5
- package/esm/relations/relation-loader.d.mts.map +1 -1
- package/esm/relations/relation-loader.mjs +51 -16
- package/esm/relations/relation-loader.mjs.map +1 -1
- package/esm/remover/database-remover.d.mts.map +1 -1
- package/esm/remover/database-remover.mjs +22 -8
- package/esm/remover/database-remover.mjs.map +1 -1
- package/esm/restorer/database-restorer.d.mts +11 -2
- package/esm/restorer/database-restorer.d.mts.map +1 -1
- package/esm/restorer/database-restorer.mjs +45 -13
- package/esm/restorer/database-restorer.mjs.map +1 -1
- package/esm/sync/model-events.mjs +63 -3
- package/esm/sync/model-events.mjs.map +1 -1
- package/esm/sync/model-sync-operation.d.mts.map +1 -1
- package/esm/sync/model-sync-operation.mjs +3 -2
- package/esm/sync/model-sync-operation.mjs.map +1 -1
- package/esm/sync/sync-fanout.mjs +67 -0
- package/esm/sync/sync-fanout.mjs.map +1 -0
- package/esm/sync/sync-manager.d.mts +3 -42
- package/esm/sync/sync-manager.d.mts.map +1 -1
- package/esm/sync/sync-manager.mjs +37 -126
- package/esm/sync/sync-manager.mjs.map +1 -1
- package/esm/utils/connect-to-database.d.mts.map +1 -1
- package/esm/utils/connect-to-database.mjs +4 -4
- package/esm/utils/connect-to-database.mjs.map +1 -1
- package/esm/utils/database-writer.utils.d.mts +1 -1
- package/esm/utils/database-writer.utils.d.mts.map +1 -1
- package/esm/utils/database-writer.utils.mjs +1 -0
- package/esm/utils/database-writer.utils.mjs.map +1 -1
- package/esm/utils/define-model.d.mts.map +1 -1
- package/esm/utils/define-model.mjs +2 -10
- package/esm/utils/define-model.mjs.map +1 -1
- package/esm/utils/is-valid-date-value.mjs +3 -2
- package/esm/utils/is-valid-date-value.mjs.map +1 -1
- package/esm/utils/once-connected.d.mts.map +1 -1
- package/esm/utils/once-connected.mjs +12 -2
- package/esm/utils/once-connected.mjs.map +1 -1
- package/esm/validation/mutators/embed-mutator.mjs +19 -11
- package/esm/validation/mutators/embed-mutator.mjs.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.d.mts +2 -2
- package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.mjs +8 -2
- package/esm/validation/plugins/embed-validator-plugin.mjs.map +1 -1
- package/esm/validation/rules/database-model-rule.mjs +6 -5
- package/esm/validation/rules/database-model-rule.mjs.map +1 -1
- package/esm/validation/rules/exists-rule.mjs +6 -4
- package/esm/validation/rules/exists-rule.mjs.map +1 -1
- package/esm/validation/rules/unique-rule.mjs +5 -2
- package/esm/validation/rules/unique-rule.mjs.map +1 -1
- package/esm/validation/validators/embed-validator.d.mts.map +1 -1
- package/esm/validation/validators/embed-validator.mjs +1 -1
- package/esm/validation/validators/embed-validator.mjs.map +1 -1
- package/esm/writer/database-writer.d.mts +27 -0
- package/esm/writer/database-writer.d.mts.map +1 -1
- package/esm/writer/database-writer.mjs +85 -18
- package/esm/writer/database-writer.mjs.map +1 -1
- package/package.json +8 -7
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts +0 -79
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts.map +0 -1
- package/esm/drivers/postgres/postgres-sync-adapter.mjs +0 -162
- package/esm/drivers/postgres/postgres-sync-adapter.mjs.map +0 -1
|
@@ -2,14 +2,14 @@ import { databaseTransactionContext } from "../../context/database-transaction-c
|
|
|
2
2
|
import { flushAfterCommit } from "../../transactions/after-commit.mjs";
|
|
3
3
|
import { TransactionRollbackError } from "../../errors/transaction-rollback.error.mjs";
|
|
4
4
|
import { UnsupportedUpdateOperationError } from "../../errors/unsupported-update-operation.error.mjs";
|
|
5
|
-
import { isValidDateValue } from "../../utils/is-valid-date-value.mjs";
|
|
6
5
|
import { PostgresBlueprint } from "./postgres-blueprint.mjs";
|
|
7
6
|
import { PostgresDialect } from "./postgres-dialect.mjs";
|
|
8
7
|
import { SqlDatabaseDirtyTracker } from "../../sql-database-dirty-tracker.mjs";
|
|
8
|
+
import { convertPostgresValue } from "./postgres-type-converters.mjs";
|
|
9
|
+
import { loadPostgresTTLPurgeJobs, unregisterAllPostgresTTLPurgeJobs } from "./postgres-ttl-purge.mjs";
|
|
9
10
|
import { PostgresMigrationDriver } from "./postgres-migration-driver.mjs";
|
|
10
11
|
import { PostgresQueryBuilder } from "./postgres-query-builder.mjs";
|
|
11
12
|
import { PostgresSQLSerializer } from "./postgres-sql-serializer.mjs";
|
|
12
|
-
import { PostgresSyncAdapter } from "./postgres-sync-adapter.mjs";
|
|
13
13
|
import { assertPostgresUpdate } from "./postgres-update-validator.mjs";
|
|
14
14
|
import { log } from "@warlock.js/logger";
|
|
15
15
|
import { colors } from "@mongez/copper";
|
|
@@ -55,6 +55,7 @@ function buildPostgresPoolConfig(config) {
|
|
|
55
55
|
idleTimeoutMillis: config.idleTimeoutMillis ?? 3e4,
|
|
56
56
|
connectionTimeoutMillis: config.connectionTimeoutMillis ?? 1e4,
|
|
57
57
|
keepAlive: config.keepAlive ?? true,
|
|
58
|
+
maxUses: config.maxUses,
|
|
58
59
|
application_name: config.application_name ?? "cascade",
|
|
59
60
|
ssl: config.ssl
|
|
60
61
|
};
|
|
@@ -79,6 +80,10 @@ async function loadPg() {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
83
|
+
* Transaction session -> the driver (data source) that opened it.
|
|
84
|
+
*/
|
|
85
|
+
const transactionOwners = /* @__PURE__ */ new WeakMap();
|
|
86
|
+
/**
|
|
82
87
|
* PostgreSQL database driver implementing the Cascade DriverContract.
|
|
83
88
|
*
|
|
84
89
|
* Provides connection pooling, CRUD operations, transactions, and
|
|
@@ -107,6 +112,11 @@ async function loadPg() {
|
|
|
107
112
|
* await driver.disconnect();
|
|
108
113
|
* ```
|
|
109
114
|
*/
|
|
115
|
+
/** Summarise bound parameters as count and types, never values. */
|
|
116
|
+
function describeParams(params) {
|
|
117
|
+
const types = params.map((param) => param === null ? "null" : typeof param);
|
|
118
|
+
return `Params: ${params.length} (${types.join(", ")})`;
|
|
119
|
+
}
|
|
110
120
|
var PostgresDriver = class {
|
|
111
121
|
config;
|
|
112
122
|
/**
|
|
@@ -169,6 +179,8 @@ var PostgresDriver = class {
|
|
|
169
179
|
* @see PostgresPoolConfig.nativeArrayColumns
|
|
170
180
|
*/
|
|
171
181
|
_nativeArrayColumns;
|
|
182
|
+
/** Column `dataTypeID`s per result row, filled by `query()` for `deserialize()`. */
|
|
183
|
+
rowColumnTypes = /* @__PURE__ */ new WeakMap();
|
|
172
184
|
/** Unique keys per table, resolved from `pg_index` for upsert conflict targets. */
|
|
173
185
|
_uniqueKeys = /* @__PURE__ */ new Map();
|
|
174
186
|
/**
|
|
@@ -235,6 +247,7 @@ var PostgresDriver = class {
|
|
|
235
247
|
log.success("database.postgres", "connection", `Connected to database ${colors.bold(colors.yellowBright(this.config.database))}`);
|
|
236
248
|
this._isConnected = true;
|
|
237
249
|
await this.loadNativeArrayColumns();
|
|
250
|
+
await loadPostgresTTLPurgeJobs(this);
|
|
238
251
|
this.emit("connected");
|
|
239
252
|
} catch (error) {
|
|
240
253
|
log.fatal("database.postgres", "connection", "Failed to connect to database");
|
|
@@ -249,6 +262,7 @@ var PostgresDriver = class {
|
|
|
249
262
|
*/
|
|
250
263
|
async disconnect() {
|
|
251
264
|
if (!this._isConnected || !this._pool) return;
|
|
265
|
+
unregisterAllPostgresTTLPurgeJobs(this);
|
|
252
266
|
await this._pool.end();
|
|
253
267
|
this._pool = void 0;
|
|
254
268
|
this._isConnected = false;
|
|
@@ -343,6 +357,16 @@ var PostgresDriver = class {
|
|
|
343
357
|
return this._nativeArrayColumns.has(column);
|
|
344
358
|
}
|
|
345
359
|
/**
|
|
360
|
+
* Drop the cached unique keys and re-read the native-array columns.
|
|
361
|
+
*
|
|
362
|
+
* Called by the migration driver after DDL so an in-process migration is
|
|
363
|
+
* visible to the serializer and to upserts without reconnecting.
|
|
364
|
+
*/
|
|
365
|
+
async invalidateSchemaCaches() {
|
|
366
|
+
this._uniqueKeys.clear();
|
|
367
|
+
await this.loadNativeArrayColumns();
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
346
370
|
* Introspect the live schema for native-array columns so array values bind
|
|
347
371
|
* correctly with zero app configuration.
|
|
348
372
|
*
|
|
@@ -394,26 +418,11 @@ var PostgresDriver = class {
|
|
|
394
418
|
* @returns Deserialized JavaScript object
|
|
395
419
|
*/
|
|
396
420
|
deserialize(data) {
|
|
421
|
+
const columnTypes = this.rowColumnTypes.get(data);
|
|
422
|
+
if (!columnTypes) return data;
|
|
397
423
|
for (const [key, value] of Object.entries(data)) {
|
|
398
424
|
if (typeof value !== "string") continue;
|
|
399
|
-
|
|
400
|
-
data[key] = new Date(value);
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
if (value.charCodeAt(0) === 91 && value.charCodeAt(value.length - 1) === 93) {
|
|
404
|
-
const parts = value.slice(1, -1).split(",");
|
|
405
|
-
const nums = new Array(parts.length);
|
|
406
|
-
let isNumericVector = parts.length > 0;
|
|
407
|
-
for (let i = 0; i < parts.length; i++) {
|
|
408
|
-
const n = +(parts[i] ?? "");
|
|
409
|
-
if (!Number.isFinite(n)) {
|
|
410
|
-
isNumericVector = false;
|
|
411
|
-
break;
|
|
412
|
-
}
|
|
413
|
-
nums[i] = n;
|
|
414
|
-
}
|
|
415
|
-
if (isNumericVector) data[key] = nums;
|
|
416
|
-
}
|
|
425
|
+
data[key] = convertPostgresValue(value, columnTypes.get(key));
|
|
417
426
|
}
|
|
418
427
|
return data;
|
|
419
428
|
}
|
|
@@ -490,7 +499,15 @@ var PostgresDriver = class {
|
|
|
490
499
|
try {
|
|
491
500
|
return { modifiedCount: (await this.query(sql, params)).rowCount ?? 0 };
|
|
492
501
|
} catch (error) {
|
|
493
|
-
|
|
502
|
+
log.error({
|
|
503
|
+
module: "database.postgres",
|
|
504
|
+
action: "update.error",
|
|
505
|
+
message: `${sql} | ${describeParams(params)}`,
|
|
506
|
+
context: {
|
|
507
|
+
sql,
|
|
508
|
+
paramCount: params.length
|
|
509
|
+
}
|
|
510
|
+
});
|
|
494
511
|
throw error;
|
|
495
512
|
}
|
|
496
513
|
}
|
|
@@ -667,7 +684,8 @@ var PostgresDriver = class {
|
|
|
667
684
|
const conflictColumns = options?.conflictColumns ?? Object.keys(filter);
|
|
668
685
|
if (conflictColumns.length === 0) throw new Error("Upsert requires conflictColumns option or filter with columns");
|
|
669
686
|
const quotedConflictColumns = conflictColumns.map((c) => this.dialect.quoteIdentifier(c)).join(", ");
|
|
670
|
-
const
|
|
687
|
+
const createdAtColumn = options?.createdAtColumn ?? this.modelDefaults.createdAtColumn;
|
|
688
|
+
const setClauses = columns.filter((col) => !conflictColumns.includes(col) && col !== createdAtColumn).map((col, i) => {
|
|
671
689
|
const valueIndex = columns.indexOf(col) + 1;
|
|
672
690
|
return `${this.dialect.quoteIdentifier(col)} = ${this.dialect.placeholder(valueIndex)}`;
|
|
673
691
|
}).join(", ");
|
|
@@ -685,7 +703,7 @@ var PostgresDriver = class {
|
|
|
685
703
|
async findOneAndDelete(table, filter, _options) {
|
|
686
704
|
const quotedTable = this.dialect.quoteIdentifier(table);
|
|
687
705
|
const { whereClause, whereParams } = this.buildWhereClause(filter, 1);
|
|
688
|
-
const sql =
|
|
706
|
+
const sql = `${this.singleRowTarget("DELETE FROM " + quotedTable, quotedTable, filter, whereClause)} RETURNING *`;
|
|
689
707
|
const result = await this.query(sql, whereParams);
|
|
690
708
|
return result.rows[0] ? result.rows[0] : null;
|
|
691
709
|
}
|
|
@@ -700,7 +718,7 @@ var PostgresDriver = class {
|
|
|
700
718
|
async delete(table, filter, _options) {
|
|
701
719
|
const quotedTable = this.dialect.quoteIdentifier(table);
|
|
702
720
|
const { whereClause, whereParams } = this.buildWhereClause(filter ?? {}, 1);
|
|
703
|
-
const sql =
|
|
721
|
+
const sql = this.singleRowTarget("DELETE FROM " + quotedTable, quotedTable, filter ?? {}, whereClause);
|
|
704
722
|
return (await this.query(sql, whereParams)).rowCount ?? 0;
|
|
705
723
|
}
|
|
706
724
|
/**
|
|
@@ -801,29 +819,32 @@ var PostgresDriver = class {
|
|
|
801
819
|
const ctx = { rollback(reason) {
|
|
802
820
|
throw new TransactionRollbackError(reason);
|
|
803
821
|
} };
|
|
804
|
-
if (
|
|
822
|
+
if (this.canJoinActiveTransaction()) return fn(ctx);
|
|
805
823
|
const tx = await this.beginTransaction(options);
|
|
806
|
-
|
|
824
|
+
transactionOwners.set(tx.context, this);
|
|
807
825
|
let commitStarted = false;
|
|
808
826
|
let committedCallbacks = [];
|
|
809
827
|
let result;
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
828
|
+
await databaseTransactionContext.run({
|
|
829
|
+
session: tx.context,
|
|
830
|
+
afterCommit: []
|
|
831
|
+
}, async () => {
|
|
832
|
+
try {
|
|
833
|
+
result = await fn(ctx);
|
|
834
|
+
commitStarted = true;
|
|
835
|
+
await tx.commit();
|
|
836
|
+
committedCallbacks = databaseTransactionContext.takeAfterCommit();
|
|
837
|
+
} catch (error) {
|
|
838
|
+
if (commitStarted) log.error(`database.postgress`, "transaction", "Transaction COMMIT failed; client discarded, no rollback issued");
|
|
839
|
+
else try {
|
|
840
|
+
await tx.rollback();
|
|
841
|
+
log.error(`database.postgress`, "transaction", "Transaction operation failed, rolled back everything");
|
|
842
|
+
} catch (rollbackError) {
|
|
843
|
+
log.error(`database.postgress`, "transaction", rollbackError);
|
|
844
|
+
}
|
|
845
|
+
throw error;
|
|
822
846
|
}
|
|
823
|
-
|
|
824
|
-
} finally {
|
|
825
|
-
databaseTransactionContext.exit();
|
|
826
|
-
}
|
|
847
|
+
});
|
|
827
848
|
await flushAfterCommit(committedCallbacks);
|
|
828
849
|
return result;
|
|
829
850
|
}
|
|
@@ -856,13 +877,94 @@ var PostgresDriver = class {
|
|
|
856
877
|
/**
|
|
857
878
|
* Get the sync adapter for bulk denormalized updates.
|
|
858
879
|
*
|
|
880
|
+
* Sync instructions arrive in the portable Mongo shape (`$set` with a `field.$`
|
|
881
|
+
* positional key, `$pull`, `$unset`, dotted filter keys), so they are translated
|
|
882
|
+
* to jsonb SQL here instead of being handed to `updateMany`, which would quote
|
|
883
|
+
* `"tags.$"` as a column name.
|
|
884
|
+
*
|
|
859
885
|
* @returns Sync adapter instance
|
|
860
886
|
*/
|
|
861
887
|
syncAdapter() {
|
|
862
|
-
if (!this._syncAdapter)
|
|
888
|
+
if (!this._syncAdapter) {
|
|
889
|
+
const executeOne = (instruction) => this.executeSyncInstruction(instruction);
|
|
890
|
+
this._syncAdapter = {
|
|
891
|
+
executeOne,
|
|
892
|
+
executeArrayUpdate: executeOne,
|
|
893
|
+
executeBatch: async (instructions) => {
|
|
894
|
+
let affected = 0;
|
|
895
|
+
for (const instruction of instructions) affected += await executeOne(instruction);
|
|
896
|
+
return affected;
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
}
|
|
863
900
|
return this._syncAdapter;
|
|
864
901
|
}
|
|
865
902
|
/**
|
|
903
|
+
* Execute one sync instruction as a jsonb UPDATE.
|
|
904
|
+
*
|
|
905
|
+
* @param instruction - Sync instruction in the portable Mongo shape
|
|
906
|
+
* @returns Number of affected rows
|
|
907
|
+
*/
|
|
908
|
+
async executeSyncInstruction(instruction) {
|
|
909
|
+
const { sql, params } = this.buildSyncStatement(instruction);
|
|
910
|
+
return (await this.query(sql, params)).rowCount ?? 0;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Translate a sync instruction to a jsonb UPDATE statement.
|
|
914
|
+
*
|
|
915
|
+
* @param instruction - Sync instruction
|
|
916
|
+
* @returns SQL and parameters
|
|
917
|
+
* @throws Error for update operators a sync cannot express
|
|
918
|
+
*/
|
|
919
|
+
buildSyncStatement(instruction) {
|
|
920
|
+
const params = [];
|
|
921
|
+
const bind = (value) => {
|
|
922
|
+
params.push(value);
|
|
923
|
+
return this.dialect.placeholder(params.length);
|
|
924
|
+
};
|
|
925
|
+
const quote = (name) => this.dialect.quoteIdentifier(name);
|
|
926
|
+
const elementMatch = (idField, idValue, operator) => `(e->>${bind(idField)}::text) ${operator} ${bind(String(idValue))}::text`;
|
|
927
|
+
const clauses = [];
|
|
928
|
+
for (const [key, value] of Object.entries(instruction.update)) if (key === "$set") for (const [path, embed] of Object.entries(value)) if (path.endsWith(".$")) {
|
|
929
|
+
const column = quote(path.slice(0, -2));
|
|
930
|
+
const { identifierField, identifierValue } = instruction;
|
|
931
|
+
if (!identifierField || identifierValue === void 0) throw new Error(`Positional update on "${path}" requires an identifier field/value.`);
|
|
932
|
+
const match = elementMatch(identifierField, identifierValue, "=");
|
|
933
|
+
clauses.push(`${column} = (SELECT COALESCE(jsonb_agg(CASE WHEN ${match} THEN ${bind(JSON.stringify(embed))}::jsonb ELSE e END), '[]'::jsonb) FROM jsonb_array_elements(${column}::jsonb) e)`);
|
|
934
|
+
} else if (path.includes(".")) {
|
|
935
|
+
const [head, ...rest] = path.split(".");
|
|
936
|
+
const column = quote(head);
|
|
937
|
+
clauses.push(`${column} = jsonb_set(COALESCE(${column}::jsonb, '{}'::jsonb), ${bind(rest)}::text[], ${bind(JSON.stringify(embed))}::jsonb)`);
|
|
938
|
+
} else clauses.push(`${quote(path)} = ${bind(JSON.stringify(embed))}::jsonb`);
|
|
939
|
+
else if (key === "$pull") for (const [field, criteria] of Object.entries(value)) {
|
|
940
|
+
const column = quote(field);
|
|
941
|
+
const entries = Object.entries(criteria);
|
|
942
|
+
const [idField, idValue] = entries[0] ?? [];
|
|
943
|
+
if (entries.length !== 1 || !idField) throw new Error(`$pull on "${field}" must match a single identifier field.`);
|
|
944
|
+
const match = elementMatch(idField, idValue, "IS DISTINCT FROM");
|
|
945
|
+
clauses.push(`${column} = (SELECT COALESCE(jsonb_agg(e), '[]'::jsonb) FROM jsonb_array_elements(${column}::jsonb) e WHERE ${match})`);
|
|
946
|
+
}
|
|
947
|
+
else if (key === "$unset") for (const field of Object.keys(value)) clauses.push(`${quote(field)} = NULL`);
|
|
948
|
+
else throw new Error(`Unsupported sync update operator "${key}" on the Postgres driver.`);
|
|
949
|
+
if (clauses.length === 0) throw new Error("No update operations specified for the sync instruction.");
|
|
950
|
+
const conditions = [];
|
|
951
|
+
for (const [key, value] of Object.entries(instruction.filter)) {
|
|
952
|
+
if (!key.includes(".")) {
|
|
953
|
+
conditions.push(`${quote(key)} = ${bind(value)}`);
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
const [head, ...rest] = key.split(".");
|
|
957
|
+
const column = quote(head);
|
|
958
|
+
const path = bind(rest);
|
|
959
|
+
conditions.push(instruction.isArrayUpdate && head === instruction.arrayField ? `EXISTS (SELECT 1 FROM jsonb_array_elements(${column}::jsonb) e WHERE (e #>> ${path}::text[]) = ${bind(String(value))}::text)` : `(${column}::jsonb #>> ${path}::text[]) = ${bind(String(value))}::text`);
|
|
960
|
+
}
|
|
961
|
+
const where = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
962
|
+
return {
|
|
963
|
+
sql: `UPDATE ${quote(instruction.targetTable)} SET ${clauses.join(", ")}${where}`,
|
|
964
|
+
params
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
866
968
|
* Get the migration driver for schema operations.
|
|
867
969
|
*
|
|
868
970
|
* @returns Migration driver instance
|
|
@@ -879,6 +981,25 @@ var PostgresDriver = class {
|
|
|
879
981
|
return new PostgresSQLSerializer(this.dialect);
|
|
880
982
|
}
|
|
881
983
|
/**
|
|
984
|
+
* Whether an active transaction can be joined: one exists and it is not
|
|
985
|
+
* owned by a different data source (sessions opened outside `transaction()`
|
|
986
|
+
* carry no owner tag and are joinable).
|
|
987
|
+
*/
|
|
988
|
+
canJoinActiveTransaction() {
|
|
989
|
+
const session = databaseTransactionContext.getSession();
|
|
990
|
+
if (!session) return false;
|
|
991
|
+
const owner = transactionOwners.get(session);
|
|
992
|
+
return owner === void 0 || owner === this;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* The ambient transaction session, only when it belongs to this driver's
|
|
996
|
+
* data source.
|
|
997
|
+
*/
|
|
998
|
+
getOwnSession() {
|
|
999
|
+
const session = databaseTransactionContext.getSession();
|
|
1000
|
+
return session && transactionOwners.get(session) === this ? session : void 0;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
882
1003
|
* Execute a raw SQL query.
|
|
883
1004
|
*
|
|
884
1005
|
* Automatically uses the transaction client if one is active.
|
|
@@ -887,15 +1008,15 @@ var PostgresDriver = class {
|
|
|
887
1008
|
* @param params - Query parameters
|
|
888
1009
|
* @returns Query result
|
|
889
1010
|
*/
|
|
1011
|
+
recordColumnTypes(result) {
|
|
1012
|
+
if (!result.fields?.length || !result.rows?.length) return;
|
|
1013
|
+
const columnTypes = new Map(result.fields.map((field) => [field.name, field.dataTypeID]));
|
|
1014
|
+
for (const row of result.rows) if (row && typeof row === "object") this.rowColumnTypes.set(row, columnTypes);
|
|
1015
|
+
}
|
|
890
1016
|
async query(sql, params = []) {
|
|
891
|
-
const txClient =
|
|
1017
|
+
const txClient = this.getOwnSession();
|
|
892
1018
|
const startTime = this.config.logging ? performance.now() : 0;
|
|
893
|
-
|
|
894
|
-
if (this.config.logging && params.length > 0) {
|
|
895
|
-
paramsString = JSON.stringify(params);
|
|
896
|
-
if (paramsString.length > 300) paramsString = paramsString.substring(0, 300) + "...";
|
|
897
|
-
paramsString = ` | Params: ${paramsString}`;
|
|
898
|
-
}
|
|
1019
|
+
const paramsString = this.config.logging && params.length > 0 ? ` | ${describeParams(params)}` : "";
|
|
899
1020
|
try {
|
|
900
1021
|
let result;
|
|
901
1022
|
if (this.config.logging) log.info({
|
|
@@ -903,8 +1024,8 @@ var PostgresDriver = class {
|
|
|
903
1024
|
action: "query.executing",
|
|
904
1025
|
message: `${sql}${paramsString}`,
|
|
905
1026
|
context: {
|
|
906
|
-
|
|
907
|
-
|
|
1027
|
+
sql,
|
|
1028
|
+
paramCount: params.length
|
|
908
1029
|
}
|
|
909
1030
|
});
|
|
910
1031
|
if (txClient) result = await txClient.query(sql, params);
|
|
@@ -916,12 +1037,13 @@ var PostgresDriver = class {
|
|
|
916
1037
|
action: "query.executed",
|
|
917
1038
|
message: `[${duration}ms] ${sql}${paramsString}`,
|
|
918
1039
|
context: {
|
|
919
|
-
params,
|
|
920
1040
|
sql,
|
|
1041
|
+
paramCount: params.length,
|
|
921
1042
|
duration
|
|
922
1043
|
}
|
|
923
1044
|
});
|
|
924
1045
|
}
|
|
1046
|
+
this.recordColumnTypes(result);
|
|
925
1047
|
return result;
|
|
926
1048
|
} catch (error) {
|
|
927
1049
|
if (this.config.logging) {
|
|
@@ -932,7 +1054,7 @@ var PostgresDriver = class {
|
|
|
932
1054
|
message: `[${duration}ms] ${sql}${paramsString}`,
|
|
933
1055
|
context: {
|
|
934
1056
|
sql,
|
|
935
|
-
params,
|
|
1057
|
+
paramCount: params.length,
|
|
936
1058
|
error: error instanceof Error ? error.message : String(error)
|
|
937
1059
|
}
|
|
938
1060
|
});
|
|
@@ -1084,14 +1206,28 @@ var PostgresDriver = class {
|
|
|
1084
1206
|
const quotedTable = this.dialect.quoteIdentifier(table);
|
|
1085
1207
|
const { whereClause, whereParams } = this.buildWhereClause(filter, set.nextIndex);
|
|
1086
1208
|
params.push(...whereParams);
|
|
1087
|
-
|
|
1088
|
-
if (limit === 1 && whereClause) sql = `UPDATE ${quotedTable} SET ${set.clauses.join(", ")} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${whereClause} LIMIT 1)`;
|
|
1209
|
+
const head = `UPDATE ${quotedTable} SET ${set.clauses.join(", ")}`;
|
|
1089
1210
|
return {
|
|
1090
|
-
sql,
|
|
1211
|
+
sql: limit === 1 && whereClause ? this.singleRowTarget(head, quotedTable, filter, whereClause) : `${head} ${whereClause}`,
|
|
1091
1212
|
params
|
|
1092
1213
|
};
|
|
1093
1214
|
}
|
|
1094
1215
|
/**
|
|
1216
|
+
* Build the `<head> WHERE ...` statement for a single-row UPDATE/DELETE.
|
|
1217
|
+
*
|
|
1218
|
+
* A filter pinned to the primary key (`id = <value>`) already matches at most
|
|
1219
|
+
* one row, so it is used as-is: a ctid subquery would re-check a stale tuple
|
|
1220
|
+
* after waiting on a concurrent write and silently update 0 rows.
|
|
1221
|
+
* Without a PK, the ctid subquery locks its candidate with
|
|
1222
|
+
* `FOR UPDATE SKIP LOCKED` so concurrent callers claim different rows
|
|
1223
|
+
* instead of losing the write.
|
|
1224
|
+
*/
|
|
1225
|
+
singleRowTarget(head, quotedTable, filter, whereClause) {
|
|
1226
|
+
const id = filter.id;
|
|
1227
|
+
if (id !== void 0 && id !== null && (typeof id !== "object" || id instanceof Date)) return `${head} ${whereClause}`;
|
|
1228
|
+
return `${head} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${whereClause} LIMIT 1 FOR UPDATE SKIP LOCKED)`;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1095
1231
|
* Create a new database.
|
|
1096
1232
|
*
|
|
1097
1233
|
* Note: This requires connecting to a system database (like 'postgres')
|