@warlock.js/cascade 4.6.0 → 4.7.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/cjs/index.cjs +372 -56
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/contracts/database-driver.contract.d.mts +8 -0
  5. package/esm/contracts/database-driver.contract.d.mts.map +1 -1
  6. package/esm/contracts/index.d.mts +1 -1
  7. package/esm/contracts/query-builder.contract.d.mts +36 -1
  8. package/esm/contracts/query-builder.contract.d.mts.map +1 -1
  9. package/esm/drivers/mongodb/mongodb-driver.d.mts +5 -0
  10. package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
  11. package/esm/drivers/mongodb/mongodb-driver.mjs +10 -4
  12. package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
  13. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +4 -0
  14. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
  15. package/esm/drivers/mongodb/mongodb-migration-driver.mjs +5 -2
  16. package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
  17. package/esm/drivers/mongodb/mongodb-query-builder.d.mts +15 -0
  18. package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
  19. package/esm/drivers/mongodb/mongodb-query-builder.mjs +24 -0
  20. package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
  21. package/esm/drivers/mongodb/mongodb-query-parser.d.mts +16 -0
  22. package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
  23. package/esm/drivers/mongodb/mongodb-query-parser.mjs +33 -1
  24. package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
  25. package/esm/drivers/postgres/postgres-driver.d.mts +69 -13
  26. package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
  27. package/esm/drivers/postgres/postgres-driver.mjs +155 -27
  28. package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
  29. package/esm/drivers/postgres/postgres-query-builder.d.mts +14 -3
  30. package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
  31. package/esm/drivers/postgres/postgres-query-builder.mjs +44 -8
  32. package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
  33. package/esm/drivers/postgres/postgres-query-parser.d.mts +6 -1
  34. package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
  35. package/esm/drivers/postgres/postgres-query-parser.mjs +13 -0
  36. package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
  37. package/esm/drivers/postgres/postgres-sql-serializer.mjs +15 -4
  38. package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
  39. package/esm/index.d.mts +2 -2
  40. package/esm/migration/migration-runner.d.mts.map +1 -1
  41. package/esm/migration/migration-runner.mjs +25 -3
  42. package/esm/migration/migration-runner.mjs.map +1 -1
  43. package/esm/migration/migration.d.mts +6 -3
  44. package/esm/migration/migration.d.mts.map +1 -1
  45. package/esm/migration/migration.mjs +6 -3
  46. package/esm/migration/migration.mjs.map +1 -1
  47. package/esm/model/methods/scope-methods.mjs +18 -4
  48. package/esm/model/methods/scope-methods.mjs.map +1 -1
  49. package/esm/model/model.d.mts +6 -0
  50. package/esm/model/model.d.mts.map +1 -1
  51. package/esm/model/model.mjs +6 -0
  52. package/esm/model/model.mjs.map +1 -1
  53. package/esm/query-builder/query-builder.d.mts +12 -1
  54. package/esm/query-builder/query-builder.d.mts.map +1 -1
  55. package/esm/query-builder/query-builder.mjs +19 -0
  56. package/esm/query-builder/query-builder.mjs.map +1 -1
  57. package/llms-full.txt +41 -7
  58. package/llms.txt +1 -1
  59. package/package.json +4 -4
  60. package/skills/README.md +1 -1
  61. package/skills/manage-transactions/SKILL.md +41 -7
package/cjs/index.cjs CHANGED
@@ -4812,20 +4812,34 @@ async function restoreAllRecords(ModelClass, options) {
4812
4812
 
4813
4813
  //#endregion
4814
4814
  //#region ../@warlock.js/cascade/src/model/methods/scope-methods.ts
4815
+ /**
4816
+ * Give the model class its OWN scope map before mutating.
4817
+ *
4818
+ * The `globalScopes` / `localScopes` statics live on the base `Model`; without
4819
+ * this, `ModelClass.globalScopes.set(...)` from any subclass mutates the ONE
4820
+ * inherited Map and the scope leaks onto every other model (a soft-delete
4821
+ * `notDeleted` scope registered on `User` would filter `Post` too). The own
4822
+ * map snapshots the currently-inherited entries so parent scopes registered so
4823
+ * far are kept.
4824
+ */
4825
+ function ownScopeMap(ModelClass, property) {
4826
+ if (!Object.prototype.hasOwnProperty.call(ModelClass, property)) ModelClass[property] = new Map(ModelClass[property]);
4827
+ return ModelClass[property];
4828
+ }
4815
4829
  function addGlobalModelScope(ModelClass, name, callback, options = {}) {
4816
- ModelClass.globalScopes.set(name, {
4830
+ ownScopeMap(ModelClass, "globalScopes").set(name, {
4817
4831
  callback,
4818
4832
  timing: options.timing || "before"
4819
4833
  });
4820
4834
  }
4821
4835
  function removeGlobalModelScope(ModelClass, name) {
4822
- ModelClass.globalScopes.delete(name);
4836
+ ownScopeMap(ModelClass, "globalScopes").delete(name);
4823
4837
  }
4824
4838
  function addLocalModelScope(ModelClass, name, callback) {
4825
- ModelClass.localScopes.set(name, callback);
4839
+ ownScopeMap(ModelClass, "localScopes").set(name, callback);
4826
4840
  }
4827
4841
  function removeLocalModelScope(ModelClass, name) {
4828
- ModelClass.localScopes.delete(name);
4842
+ ownScopeMap(ModelClass, "localScopes").delete(name);
4829
4843
  }
4830
4844
 
4831
4845
  //#endregion
@@ -5417,11 +5431,17 @@ var Model = class Model {
5417
5431
  /**
5418
5432
  * Global scopes that are automatically applied to all queries.
5419
5433
  * These scopes are inherited by child models.
5434
+ *
5435
+ * Registration via `addGlobalScope` is per-subclass: the registering class
5436
+ * gets its own map (seeded with the entries inherited so far), so a scope
5437
+ * added on one model never leaks onto sibling models.
5420
5438
  */
5421
5439
  static globalScopes = /* @__PURE__ */ new Map();
5422
5440
  /**
5423
5441
  * Local scopes that can be manually applied to queries.
5424
5442
  * These are reusable query snippets that developers opt into.
5443
+ *
5444
+ * Registration via `addLocalScope` is per-subclass, like `globalScopes`.
5425
5445
  */
5426
5446
  static localScopes = /* @__PURE__ */ new Map();
5427
5447
  /**
@@ -7868,12 +7888,15 @@ var MongoMigrationDriver = class {
7868
7888
  /**
7869
7889
  * Drop an index by name or columns.
7870
7890
  *
7891
+ * A string is the literal index name and is passed through untouched; a
7892
+ * columns array resolves to the MongoDB convention name
7893
+ * (`"column1_1_column2_1"`) those columns auto-name to.
7894
+ *
7871
7895
  * @param indexNameOrColumns - Index name (string) or columns array
7872
7896
  */
7873
7897
  async dropIndex(table, indexNameOrColumns) {
7874
7898
  const collection = this.db.collection(table);
7875
- if (!Array.isArray(indexNameOrColumns)) indexNameOrColumns = [indexNameOrColumns];
7876
- const indexName = indexNameOrColumns.map((col) => `${col}_1`).join("_");
7899
+ const indexName = Array.isArray(indexNameOrColumns) ? indexNameOrColumns.map((col) => `${col}_1`).join("_") : indexNameOrColumns;
7877
7900
  await collection.dropIndex(indexName);
7878
7901
  }
7879
7902
  /**
@@ -9209,6 +9232,25 @@ var QueryBuilder = class QueryBuilder {
9209
9232
  return this.limit(value);
9210
9233
  }
9211
9234
  /**
9235
+ * Lock the selected rows for update (`SELECT ... FOR UPDATE`).
9236
+ *
9237
+ * `skipLocked` skips rows other transactions hold locks on (concurrent
9238
+ * queue-claim shape); `noWait` errors immediately instead of waiting. The
9239
+ * two are mutually exclusive. Only meaningful inside a transaction.
9240
+ *
9241
+ * SQL drivers emit the locking clause; drivers without row locking
9242
+ * (MongoDB) override this to throw.
9243
+ */
9244
+ lockForUpdate(options) {
9245
+ if (options?.skipLocked && options?.noWait) throw new Error("lockForUpdate: `skipLocked` and `noWait` are mutually exclusive.");
9246
+ this.addOperation("lock", {
9247
+ mode: "update",
9248
+ skipLocked: options?.skipLocked ?? false,
9249
+ noWait: options?.noWait ?? false
9250
+ });
9251
+ return this;
9252
+ }
9253
+ /**
9212
9254
  * GROUP BY clause.
9213
9255
  * @example q.groupBy("status")
9214
9256
  * @example q.groupBy(["year", "month"])
@@ -9628,7 +9670,7 @@ var MongoQueryParser = class {
9628
9670
  const pipeline = [];
9629
9671
  let currentStage = null;
9630
9672
  let currentBuffer = [];
9631
- for (const op of this.operations) if (op.mergeable && op.stage === currentStage) currentBuffer.push(op);
9673
+ for (const op of this.orderStages(this.operations)) if (op.mergeable && op.stage === currentStage) currentBuffer.push(op);
9632
9674
  else {
9633
9675
  if (currentBuffer.length > 0) {
9634
9676
  const builtStage = this.buildStage(currentStage, currentBuffer);
@@ -9663,6 +9705,38 @@ var MongoQueryParser = class {
9663
9705
  return this.postProcessGroupStages(pipeline);
9664
9706
  }
9665
9707
  /**
9708
+ * Reorder operations so filters run before projections, mirroring SQL
9709
+ * semantics: in `select(...).where(...)`, the WHERE always applies to the
9710
+ * source columns regardless of call order. Without this, a `$project` that
9711
+ * strips the filter column would run before the `$match` and silently drop
9712
+ * every document (`select(["a"]).where("b", x)` → `[]`).
9713
+ *
9714
+ * Only *mergeable* `$match` operations are hoisted, and only within a
9715
+ * segment of neighboring mergeable `$match` / `$project` / `$sort`
9716
+ * operations. Any other operation — `$group`, `$lookup`, `$limit`, `$skip`,
9717
+ * `$setWindowFields`, or a non-mergeable op (raw escapes, having-style
9718
+ * post-group matches, `$sample`) — is a barrier: nothing moves across it.
9719
+ * So `groupBy(...).where(...)` still filters AFTER the group, and
9720
+ * `limit(...)` / `random()` keep their call-order meaning.
9721
+ */
9722
+ orderStages(operations) {
9723
+ const reordered = [];
9724
+ let segment = [];
9725
+ const flushSegment = () => {
9726
+ if (segment.length === 0) return;
9727
+ reordered.push(...segment.filter((op) => op.stage === "$match"));
9728
+ reordered.push(...segment.filter((op) => op.stage !== "$match"));
9729
+ segment = [];
9730
+ };
9731
+ for (const op of operations) if (op.mergeable && (op.stage === "$match" || op.stage === "$project" || op.stage === "$sort")) segment.push(op);
9732
+ else {
9733
+ flushSegment();
9734
+ reordered.push(op);
9735
+ }
9736
+ flushSegment();
9737
+ return reordered;
9738
+ }
9739
+ /**
9666
9740
  * Track field names for group stages that need _id renaming.
9667
9741
  */
9668
9742
  trackGroupFieldNames(stage, operations, stageIndex) {
@@ -11894,6 +11968,14 @@ var MongoQueryBuilder = class MongoQueryBuilder extends QueryBuilder {
11894
11968
  return cloned;
11895
11969
  }
11896
11970
  /**
11971
+ * Row-level locking is a SQL capability — MongoDB has no
11972
+ * `SELECT ... FOR UPDATE`. Throwing (instead of silently ignoring the call)
11973
+ * keeps a queue-claim pattern from silently running unlocked.
11974
+ */
11975
+ lockForUpdate() {
11976
+ throw new Error("lockForUpdate() is not supported by the MongoDB driver — MongoDB has no row-level SELECT locking. Use an atomic claim instead (e.g. findOneAndUpdate with a reservation filter).");
11977
+ }
11978
+ /**
11897
11979
  * Executes a callback with the query builder without breaking the chain.
11898
11980
  * @param callback - Function to execute with the builder
11899
11981
  */
@@ -11928,6 +12010,7 @@ var MongoQueryBuilder = class MongoQueryBuilder extends QueryBuilder {
11928
12010
  hydrateCallback: this.hydrateCallback
11929
12011
  });
11930
12012
  const hydratedRecords = this.hydrateCallback ? rawRecords.map(this.hydrateCallback) : rawRecords;
12013
+ await this.applyEagerLoading(hydratedRecords);
11931
12014
  if (this.fetchedCallback) await this.fetchedCallback(hydratedRecords, {
11932
12015
  query: this,
11933
12016
  rawRecords,
@@ -11936,6 +12019,20 @@ var MongoQueryBuilder = class MongoQueryBuilder extends QueryBuilder {
11936
12019
  return hydratedRecords;
11937
12020
  }
11938
12021
  /**
12022
+ * Run the RelationLoader against the fetched documents for every relation
12023
+ * registered via `with()`. Mutates each model instance in place — attaches
12024
+ * loaded relations onto `model.loadedRelations` and as direct properties.
12025
+ *
12026
+ * Skipped silently when `modelClass` is absent (raw driver-level
12027
+ * `queryBuilder()` usage has no relations map to consult).
12028
+ */
12029
+ async applyEagerLoading(records) {
12030
+ if (!this.modelClass || this.eagerLoadRelations.size === 0 || records.length === 0) return;
12031
+ const constraints = {};
12032
+ for (const [name, constraint] of this.eagerLoadRelations) if (typeof constraint === "function") constraints[name] = constraint;
12033
+ await new RelationLoader(records, this.modelClass).load([...this.eagerLoadRelations.keys()], constraints);
12034
+ }
12035
+ /**
11939
12036
  * Execute the query and get first result
11940
12037
  * This is different than `first` as first adds a `limit = 1` to the pipeline
11941
12038
  */
@@ -13035,7 +13132,10 @@ var MongoDbDriver = class {
13035
13132
  * @throws {Error} If transaction fails, is explicitly rolled back, or replica set not configured
13036
13133
  */
13037
13134
  async transaction(fn, options) {
13038
- if (databaseTransactionContext.hasActiveTransaction()) throw new Error("Nested transaction() calls are not supported. Use beginTransaction() with savepoints for advanced transaction patterns.");
13135
+ const ctx = { rollback(reason) {
13136
+ throw new TransactionRollbackError(reason);
13137
+ } };
13138
+ if (databaseTransactionContext.hasActiveTransaction()) return fn(ctx);
13039
13139
  await this.ensureReplicaSetAvailable();
13040
13140
  const session = this.getClientInstance().startSession();
13041
13141
  try {
@@ -13045,9 +13145,7 @@ var MongoDbDriver = class {
13045
13145
  });
13046
13146
  databaseTransactionContext.enter({ session });
13047
13147
  try {
13048
- const result = await fn({ rollback(reason) {
13049
- throw new TransactionRollbackError(reason);
13050
- } });
13148
+ const result = await fn(ctx);
13051
13149
  await session.commitTransaction();
13052
13150
  return result;
13053
13151
  } catch (error) {
@@ -13158,6 +13256,11 @@ var MongoDbDriver = class {
13158
13256
  return baseOptions;
13159
13257
  }
13160
13258
  /**
13259
+ * MongoDB has no SQL dialect — the MigrationRunner executes migrations
13260
+ * through the migration driver directly instead of Migration.toSQL().
13261
+ */
13262
+ supportsSqlSerialization = false;
13263
+ /**
13161
13264
  * Return a SQL serializer for this driver's dialect.
13162
13265
  * Not supported for MongoDB.
13163
13266
  */
@@ -14518,6 +14621,11 @@ var PostgresQueryParser = class PostgresQueryParser {
14518
14621
  */
14519
14622
  isDistinct = false;
14520
14623
  /**
14624
+ * Row-locking clause (`FOR UPDATE [SKIP LOCKED | NOWAIT]`). Appended after
14625
+ * LIMIT/OFFSET — the locking clause is last in PostgreSQL's SELECT grammar.
14626
+ */
14627
+ lockClause = "";
14628
+ /**
14521
14629
  * Whether the query has any JOIN operations (pre-scanned before processing).
14522
14630
  * Used by qualifyColumn() to decide whether to prefix columns with the main table.
14523
14631
  */
@@ -14687,6 +14795,13 @@ var PostgresQueryParser = class PostgresQueryParser {
14687
14795
  case "offset":
14688
14796
  this.offsetValue = data.value;
14689
14797
  break;
14798
+ case "lock": {
14799
+ let clause = "FOR UPDATE";
14800
+ if (data.skipLocked) clause += " SKIP LOCKED";
14801
+ else if (data.noWait) clause += " NOWAIT";
14802
+ this.lockClause = clause;
14803
+ break;
14804
+ }
14690
14805
  case "distinct":
14691
14806
  this.isDistinct = true;
14692
14807
  break;
@@ -14717,6 +14832,7 @@ var PostgresQueryParser = class PostgresQueryParser {
14717
14832
  if (this.orderClauses.length > 0) parts.push(`ORDER BY ${this.orderClauses.join(", ")}`);
14718
14833
  const limitOffset = this.dialect.limitOffset(this.limitValue, this.offsetValue);
14719
14834
  if (limitOffset) parts.push(limitOffset);
14835
+ if (this.lockClause) parts.push(this.lockClause);
14720
14836
  return parts.join(" ");
14721
14837
  }
14722
14838
  /**
@@ -15707,38 +15823,45 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
15707
15823
  /** SUM a numeric field. */
15708
15824
  async sum(field) {
15709
15825
  this.applyPendingScopes();
15826
+ this.hydrateCallback = void 0;
15710
15827
  const result = await this.selectRaw(`SUM(${field}) as sum`).first();
15711
15828
  return parseFloat(result?.sum ?? "0");
15712
15829
  }
15713
15830
  /** AVG of a numeric field. */
15714
15831
  async avg(field) {
15715
15832
  this.applyPendingScopes();
15833
+ this.hydrateCallback = void 0;
15716
15834
  const result = await this.selectRaw(`AVG(${field}) as avg`).first();
15717
15835
  return parseFloat(result?.avg ?? "0");
15718
15836
  }
15719
15837
  /** MIN of a numeric field. */
15720
15838
  async min(field) {
15721
15839
  this.applyPendingScopes();
15840
+ this.hydrateCallback = void 0;
15722
15841
  const result = await this.selectRaw(`MIN(${field}) as min`).first();
15723
15842
  return parseFloat(result?.min ?? "0");
15724
15843
  }
15725
15844
  /** MAX of a numeric field. */
15726
15845
  async max(field) {
15727
15846
  this.applyPendingScopes();
15847
+ this.hydrateCallback = void 0;
15728
15848
  const result = await this.selectRaw(`MAX(${field}) as max`).first();
15729
15849
  return parseFloat(result?.max ?? "0");
15730
15850
  }
15731
15851
  /** Get distinct values for a field. */
15732
15852
  async distinct(field) {
15853
+ this.hydrateCallback = void 0;
15733
15854
  this.distinctValues(field);
15734
15855
  return (await this.get()).map((row) => row[field]);
15735
15856
  }
15736
15857
  /** Get array of all values for a single field. */
15737
15858
  async pluck(field) {
15859
+ this.hydrateCallback = void 0;
15738
15860
  return (await this.select([field]).get()).map((row) => row[field]);
15739
15861
  }
15740
15862
  /** Get a single scalar value. */
15741
15863
  async value(field) {
15864
+ this.hydrateCallback = void 0;
15742
15865
  return (await this.select([field]).first())?.[field] ?? null;
15743
15866
  }
15744
15867
  /** Check whether any matching rows exist. */
@@ -15751,6 +15874,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
15751
15874
  }
15752
15875
  /** COUNT DISTINCT a field. */
15753
15876
  async countDistinct(field) {
15877
+ this.hydrateCallback = void 0;
15754
15878
  const result = await this.selectRaw(`COUNT(DISTINCT ${field}) as count`).first();
15755
15879
  return parseInt(result?.count ?? "0", 10);
15756
15880
  }
@@ -15875,21 +15999,49 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
15875
15999
  const deleteSql = `DELETE FROM ${this.driver.dialect.quoteIdentifier(this.table)} ${sql}`;
15876
16000
  return (await this.driver.query(deleteSql, params)).rowCount ?? 0;
15877
16001
  }
15878
- /** Delete the first matching row. */
16002
+ /**
16003
+ * Delete the first matching row. Wraps the filter in a ctid subquery —
16004
+ * Postgres has no `DELETE ... LIMIT`, and `delete()`'s filter only reads
16005
+ * `where*` ops, so a plain `limit(1)` would be silently ignored and every
16006
+ * matching row deleted (MongoDB's deleteOne() is single-document).
16007
+ */
15879
16008
  async deleteOne() {
15880
- return this.limit(1).delete();
16009
+ this.applyPendingScopes();
16010
+ const { sql: filterSql, params } = this.buildFilter();
16011
+ const quotedTable = this.driver.dialect.quoteIdentifier(this.table);
16012
+ const deleteSql = `DELETE FROM ${quotedTable} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${filterSql} LIMIT 1)`;
16013
+ return (await this.driver.query(deleteSql, params)).rowCount ?? 0;
15881
16014
  }
15882
- /** Update matching rows. */
16015
+ /**
16016
+ * Update matching rows. Honors every chained `where*` (and applied scopes)
16017
+ * — without a filter the whole table is updated, matching SQL semantics.
16018
+ */
15883
16019
  async update(fields) {
15884
16020
  this.applyPendingScopes();
15885
- return (await this.driver.updateMany(this.table, {}, { $set: fields })).modifiedCount;
16021
+ const { sql: filterSql, params: filterParams } = this.buildFilter();
16022
+ const serialized = this.driver.serialize(fields, this.table);
16023
+ let paramIndex = filterParams.length + 1;
16024
+ const setClauses = [];
16025
+ const setParams = [];
16026
+ for (const [key, value] of Object.entries(serialized)) {
16027
+ setClauses.push(`${this.driver.dialect.quoteIdentifier(key)} = ${this.driver.dialect.placeholder(paramIndex++)}`);
16028
+ setParams.push(value);
16029
+ }
16030
+ if (setClauses.length === 0) throw new Error("No update operations specified");
16031
+ const updateSql = `UPDATE ${this.driver.dialect.quoteIdentifier(this.table)} SET ${setClauses.join(", ")}` + (filterSql ? ` ${filterSql}` : "");
16032
+ return (await this.driver.query(updateSql, [...filterParams, ...setParams])).rowCount ?? 0;
15886
16033
  }
15887
- /** Unset fields from matching rows. */
16034
+ /**
16035
+ * Unset (NULL out) fields from matching rows. Honors every chained `where*`
16036
+ * (and applied scopes), like `update()`.
16037
+ */
15888
16038
  async unset(...fields) {
15889
16039
  this.applyPendingScopes();
15890
- const updateObj = {};
15891
- for (const field of fields) updateObj[field] = 1;
15892
- return (await this.driver.updateMany(this.table, {}, { $unset: updateObj })).modifiedCount;
16040
+ if (fields.length === 0) throw new Error("No update operations specified");
16041
+ const { sql: filterSql, params: filterParams } = this.buildFilter();
16042
+ const setClauses = fields.map((field) => `${this.driver.dialect.quoteIdentifier(field)} = NULL`);
16043
+ const updateSql = `UPDATE ${this.driver.dialect.quoteIdentifier(this.table)} SET ${setClauses.join(", ")}` + (filterSql ? ` ${filterSql}` : "");
16044
+ return (await this.driver.query(updateSql, filterParams)).rowCount ?? 0;
15893
16045
  }
15894
16046
  /**
15895
16047
  * Return the SQL + bindings without executing.
@@ -16458,8 +16610,11 @@ var PostgresSQLSerializer = class extends SQLSerializer {
16458
16610
  case "dropForeignKey": return this.dropForeignKey(table, operation.payload);
16459
16611
  case "addPrimaryKey": return this.addPrimaryKey(table, operation.payload);
16460
16612
  case "dropPrimaryKey": return this.dropPrimaryKey(table);
16461
- case "addCheck": return null;
16462
- case "dropCheck": return null;
16613
+ case "addCheck": {
16614
+ const payload = operation.payload;
16615
+ return this.addCheck(table, payload.name, payload.expression);
16616
+ }
16617
+ case "dropCheck": return this.dropCheck(table, operation.payload);
16463
16618
  case "createTimestamps": return this.createTimestamps(table);
16464
16619
  case "rawStatement": return operation.payload;
16465
16620
  case "setSchemaValidation":
@@ -16513,8 +16668,10 @@ var PostgresSQLSerializer = class extends SQLSerializer {
16513
16668
  if (column.primary) sql += " PRIMARY KEY";
16514
16669
  if (column.unique) sql += " UNIQUE";
16515
16670
  }
16516
- if (column.type === "vector") return ["CREATE EXTENSION IF NOT EXISTS vector", sql];
16517
- return sql;
16671
+ const statements = [sql];
16672
+ if (column.checkConstraint) statements.push(this.addCheck(table, column.checkConstraint.name, column.checkConstraint.expression));
16673
+ if (column.type === "vector") return ["CREATE EXTENSION IF NOT EXISTS vector", ...statements];
16674
+ return statements.length === 1 ? statements[0] : statements;
16518
16675
  }
16519
16676
  dropColumn(table, column) {
16520
16677
  return `ALTER TABLE ${this.dialect.quoteIdentifier(table)} DROP COLUMN ${this.dialect.quoteIdentifier(column)}`;
@@ -16639,6 +16796,12 @@ var PostgresSQLSerializer = class extends SQLSerializer {
16639
16796
  const constraintName = `pk_${table}`;
16640
16797
  return `ALTER TABLE ${quotedTable} DROP CONSTRAINT ${this.dialect.quoteIdentifier(constraintName)}`;
16641
16798
  }
16799
+ addCheck(table, name, expression) {
16800
+ return `ALTER TABLE ${this.dialect.quoteIdentifier(table)} ADD CONSTRAINT ${this.dialect.quoteIdentifier(name)} CHECK (${expression})`;
16801
+ }
16802
+ dropCheck(table, name) {
16803
+ return `ALTER TABLE ${this.dialect.quoteIdentifier(table)} DROP CONSTRAINT ${this.dialect.quoteIdentifier(name)}`;
16804
+ }
16642
16805
  mapForeignKeyAction(action) {
16643
16806
  switch (action) {
16644
16807
  case "cascade": return "CASCADE";
@@ -16925,13 +17088,22 @@ var PostgresDriver = class {
16925
17088
  */
16926
17089
  _syncAdapter;
16927
17090
  /**
16928
- * Lookup set of column names that hold native PostgreSQL arrays
16929
- * (`JSONB[]`, `TEXT[]`, …) and must NOT be JSON-text encoded.
17091
+ * Explicit, table-agnostic override list of column names that hold native
17092
+ * PostgreSQL arrays (`JSONB[]`, `TEXT[]`, …) and must NOT be JSON-text
17093
+ * encoded. Merged with (and superseded per-table by) the schema
17094
+ * introspection below; kept as a manual escape hatch.
16930
17095
  *
16931
17096
  * @see PostgresPoolConfig.nativeArrayColumns
16932
17097
  */
16933
17098
  _nativeArrayColumns;
16934
17099
  /**
17100
+ * Native-array columns discovered by introspecting the live schema on
17101
+ * connect, keyed `table → { column, … }`. Authoritative and table-scoped, so
17102
+ * a column that is `TEXT[]` in one table and `jsonb` in another is encoded
17103
+ * correctly for each — no app configuration required.
17104
+ */
17105
+ _introspectedArrayColumns = /* @__PURE__ */ new Map();
17106
+ /**
16935
17107
  * Create a new PostgreSQL driver instance.
16936
17108
  *
16937
17109
  * @param config - PostgreSQL connection configuration
@@ -16997,6 +17169,7 @@ var PostgresDriver = class {
16997
17169
  (await this._pool.connect()).release();
16998
17170
  _warlock_js_logger.log.success("database.postgres", "connection", `Connected to database ${_mongez_copper.colors.bold(_mongez_copper.colors.yellowBright(this.config.database))}`);
16999
17171
  this._isConnected = true;
17172
+ await this.loadNativeArrayColumns();
17000
17173
  this.emit("connected");
17001
17174
  } catch (error) {
17002
17175
  _warlock_js_logger.log.fatal("database.postgres", "connection", "Failed to connect to database");
@@ -17033,13 +17206,15 @@ var PostgresDriver = class {
17033
17206
  * that need special handling for PostgreSQL storage.
17034
17207
  *
17035
17208
  * @param data - The data object to serialize
17209
+ * @param table - Optional table name; when given, columns introspected as
17210
+ * native arrays on that table are bound raw (see {@link serializeValue}).
17036
17211
  * @returns Serialized data ready for PostgreSQL
17037
17212
  */
17038
- serialize(data) {
17213
+ serialize(data, table) {
17039
17214
  const serialized = {};
17040
17215
  for (const [key, value] of Object.entries(data)) {
17041
17216
  if (value === void 0) continue;
17042
- serialized[key] = this.serializeValue(key, value);
17217
+ serialized[key] = this.serializeValue(key, value, table);
17043
17218
  }
17044
17219
  return serialized;
17045
17220
  }
@@ -17059,36 +17234,87 @@ var PostgresDriver = class {
17059
17234
  * `JSON.stringify`. node-pg renders a raw JS array as a PostgreSQL array
17060
17235
  * literal `{...}` (and `[]` as `{}`), which a `json` / `jsonb` column
17061
17236
  * rejects — so we bind the value as JSON text instead, the form those
17062
- * columns accept. Columns listed in `nativeArrayColumns` are exempt:
17063
- * their raw array is passed through so node-pg emits the `{...}` literal
17064
- * a genuine `JSONB[]` / `TEXT[]` column needs.
17237
+ * columns accept. Columns known to be native arrays — via schema
17238
+ * introspection or the `nativeArrayColumns` config are exempt: their raw
17239
+ * array is passed through so node-pg emits the `{...}` literal a genuine
17240
+ * `JSONB[]` / `TEXT[]` column needs.
17065
17241
  * - plain object → `JSON.stringify`. Equivalent to node-pg's own object
17066
17242
  * handling, made explicit so both write paths agree.
17067
17243
  * - everything else (scalars: string, number, boolean, null) → untouched.
17068
17244
  *
17069
- * Boundary note: the serializer has no access to the table schema, so it
17070
- * cannot tell a `json` / `jsonb` column from a native-array column purely
17071
- * from the value. `nativeArrayColumns` is the explicit, opt-in escape hatch
17072
- * for the latter. No `::jsonb` placeholder cast is added: a JSON-text string
17073
- * binds correctly to `json` / `jsonb` without one, and a blind cast would
17074
- * misfire on columns we cannot positively identify as jsonb.
17245
+ * Distinguishing native-array from `json` / `jsonb` columns: a value alone
17246
+ * can't tell them apart, so the driver introspects the live schema on connect
17247
+ * (see {@link loadNativeArrayColumns}) and consults that per-table map here
17248
+ * via {@link isNativeArrayColumn}. The explicit `nativeArrayColumns` config
17249
+ * still works as a table-agnostic override. No `::jsonb` placeholder cast is
17250
+ * added: a JSON-text string binds correctly to `json` / `jsonb` without one,
17251
+ * and a blind cast would misfire on columns we cannot positively identify as
17252
+ * jsonb.
17075
17253
  *
17076
- * @param key - Column name (used to honour `nativeArrayColumns`)
17254
+ * @param key - Column name (used to resolve native-array columns)
17077
17255
  * @param value - The raw value to serialize (never `undefined`)
17256
+ * @param table - Optional table name; enables the per-table native-array lookup
17078
17257
  * @returns The value ready to bind as a query parameter
17079
17258
  */
17080
- serializeValue(key, value) {
17259
+ serializeValue(key, value, table) {
17081
17260
  if (value instanceof Date) return value.toISOString();
17082
17261
  if (typeof value === "bigint") return value.toString();
17083
17262
  if (Array.isArray(value)) {
17084
17263
  if (value.length > 0 && value.every((v) => typeof v === "number")) return `[${value.join(",")}]`;
17085
- if (this._nativeArrayColumns.has(key)) return value;
17264
+ if (this.isNativeArrayColumn(table, key)) return value;
17086
17265
  return JSON.stringify(value);
17087
17266
  }
17088
17267
  if (typeof value === "object" && value !== null) return JSON.stringify(value);
17089
17268
  return value;
17090
17269
  }
17091
17270
  /**
17271
+ * Whether `column` on `table` is a native PostgreSQL array. True when the
17272
+ * connect-time schema introspection saw it as `data_type = 'ARRAY'` for that
17273
+ * table (authoritative, per-table), or when it's listed in the table-agnostic
17274
+ * `nativeArrayColumns` config override.
17275
+ */
17276
+ isNativeArrayColumn(table, column) {
17277
+ if (table && this._introspectedArrayColumns.get(table)?.has(column)) return true;
17278
+ return this._nativeArrayColumns.has(column);
17279
+ }
17280
+ /**
17281
+ * Introspect the live schema for native-array columns so array values bind
17282
+ * correctly with zero app configuration.
17283
+ *
17284
+ * A JS array must be bound two opposite ways depending on the column: as JSON
17285
+ * text for a `json` / `jsonb` column, but as a raw array (which node-pg
17286
+ * renders `{...}`) for a native `TEXT[]` / `JSONB[]` / `INTEGER[]` column. The
17287
+ * serializer sees values, not types, so without this it JSON-stringifies
17288
+ * every array — which a native-array column rejects with "malformed array
17289
+ * literal". One `information_schema` query at connect, cached for the
17290
+ * connection lifetime, removes the need to hand-list `nativeArrayColumns`.
17291
+ *
17292
+ * Best-effort: any failure (e.g. restricted catalog access) is logged and
17293
+ * leaves the map empty so the config override still applies — it never blocks
17294
+ * connect. A schema change made within a live connection isn't reflected
17295
+ * until the next connect.
17296
+ */
17297
+ async loadNativeArrayColumns() {
17298
+ try {
17299
+ const result = await this.query(`SELECT table_name, column_name
17300
+ FROM information_schema.columns
17301
+ WHERE table_schema = ANY (current_schemas(false))
17302
+ AND data_type = 'ARRAY'`);
17303
+ const map = /* @__PURE__ */ new Map();
17304
+ for (const { table_name, column_name } of result.rows) {
17305
+ let columns = map.get(table_name);
17306
+ if (!columns) {
17307
+ columns = /* @__PURE__ */ new Set();
17308
+ map.set(table_name, columns);
17309
+ }
17310
+ columns.add(column_name);
17311
+ }
17312
+ this._introspectedArrayColumns = map;
17313
+ } catch {
17314
+ _warlock_js_logger.log.warn("database.postgres", "introspection", "Could not introspect native-array columns; using the nativeArrayColumns config only");
17315
+ }
17316
+ }
17317
+ /**
17092
17318
  * Get the dirty tracker for this driver.
17093
17319
  */
17094
17320
  getDirtyTracker(data) {
@@ -17137,7 +17363,7 @@ var PostgresDriver = class {
17137
17363
  * @returns The inserted document
17138
17364
  */
17139
17365
  async insert(table, document, _options) {
17140
- const serialized = this.serialize(document);
17366
+ const serialized = this.serialize(document, table);
17141
17367
  const filteredData = Object.fromEntries(Object.entries(serialized).filter(([key, value]) => {
17142
17368
  if (key === "id" && (value === null || value === void 0)) return false;
17143
17369
  return true;
@@ -17164,7 +17390,7 @@ var PostgresDriver = class {
17164
17390
  if (documents.length === 0) return [];
17165
17391
  const allColumns = /* @__PURE__ */ new Set();
17166
17392
  for (const doc of documents) {
17167
- const serialized = this.serialize(doc);
17393
+ const serialized = this.serialize(doc, table);
17168
17394
  Object.keys(serialized).forEach((key) => allColumns.add(key));
17169
17395
  }
17170
17396
  const columns = Array.from(allColumns);
@@ -17174,7 +17400,7 @@ var PostgresDriver = class {
17174
17400
  const params = [];
17175
17401
  let paramIndex = 1;
17176
17402
  for (const doc of documents) {
17177
- const serialized = this.serialize(doc);
17403
+ const serialized = this.serialize(doc, table);
17178
17404
  const rowPlaceholders = [];
17179
17405
  for (const col of columns) if (col in serialized) {
17180
17406
  rowPlaceholders.push(this.dialect.placeholder(paramIndex++));
@@ -17241,7 +17467,7 @@ var PostgresDriver = class {
17241
17467
  * @returns The replaced document or null
17242
17468
  */
17243
17469
  async replace(table, filter, document, _options) {
17244
- const serialized = this.serialize(document);
17470
+ const serialized = this.serialize(document, table);
17245
17471
  const columns = Object.keys(serialized);
17246
17472
  const values = Object.values(serialized);
17247
17473
  const quotedTable = this.dialect.quoteIdentifier(table);
@@ -17263,7 +17489,7 @@ var PostgresDriver = class {
17263
17489
  * @returns The upserted row
17264
17490
  */
17265
17491
  async upsert(table, filter, document, options) {
17266
- const serialized = this.serialize(document);
17492
+ const serialized = this.serialize(document, table);
17267
17493
  const columns = Object.keys(serialized);
17268
17494
  const values = Object.values(serialized);
17269
17495
  if (columns.length === 0) throw new Error("Cannot upsert empty document");
@@ -17389,13 +17615,14 @@ var PostgresDriver = class {
17389
17615
  * @throws {Error} If transaction fails or is explicitly rolled back
17390
17616
  */
17391
17617
  async transaction(fn, options) {
17392
- if (databaseTransactionContext.hasActiveTransaction()) {}
17618
+ const ctx = { rollback(reason) {
17619
+ throw new TransactionRollbackError(reason);
17620
+ } };
17621
+ if (databaseTransactionContext.hasActiveTransaction()) return fn(ctx);
17393
17622
  const tx = await this.beginTransaction(options);
17394
17623
  databaseTransactionContext.enter({ session: tx.context });
17395
17624
  try {
17396
- const result = await fn({ rollback(reason) {
17397
- throw new TransactionRollbackError(reason);
17398
- } });
17625
+ const result = await fn(ctx);
17399
17626
  await tx.commit();
17400
17627
  return result;
17401
17628
  } catch (error) {
@@ -17410,6 +17637,9 @@ var PostgresDriver = class {
17410
17637
  * Perform an atomic update operation.
17411
17638
  *
17412
17639
  * Builds and executes an UPDATE query for the given filter and operations.
17640
+ * Updates EVERY matching row — the MongoDB driver's atomic() delegates to
17641
+ * updateMany, and Model.findAndUpdate documents multi-row semantics, so the
17642
+ * two drivers must agree.
17413
17643
  *
17414
17644
  * @param table - Target table name
17415
17645
  * @param filter - Filter conditions
@@ -17418,7 +17648,7 @@ var PostgresDriver = class {
17418
17648
  * @returns Update result
17419
17649
  */
17420
17650
  async atomic(table, filter, operations, _options) {
17421
- const { sql, params } = this.buildUpdateQuery(table, filter, operations, 1);
17651
+ const { sql, params } = this.buildUpdateQuery(table, filter, operations);
17422
17652
  return { modifiedCount: (await this.query(sql, params)).rowCount ?? 0 };
17423
17653
  }
17424
17654
  /**
@@ -17521,6 +17751,13 @@ var PostgresDriver = class {
17521
17751
  /**
17522
17752
  * Build a simple WHERE clause from a filter object.
17523
17753
  *
17754
+ * Values are bound as plain equality, except Mongo-style operator objects
17755
+ * (`{ $in: [...] }`, `{ $gt: 5 }`, ...) which are translated to their SQL
17756
+ * equivalents — driver-level callers (e.g. pivot detach) build filters in
17757
+ * that portable form. An unrecognized `$` operator throws instead of being
17758
+ * bound literally, which would only surface as a cryptic type error from
17759
+ * Postgres.
17760
+ *
17524
17761
  * @param filter - Filter conditions
17525
17762
  * @param startParamIndex - Starting parameter index
17526
17763
  * @returns Object with WHERE clause string and parameters
@@ -17532,6 +17769,49 @@ var PostgresDriver = class {
17532
17769
  for (const [key, value] of Object.entries(filter)) {
17533
17770
  const quotedKey = this.dialect.quoteIdentifier(key);
17534
17771
  if (value === null) conditions.push(`${quotedKey} IS NULL`);
17772
+ else if (this.isOperatorFilter(value)) for (const [operator, operand] of Object.entries(value)) switch (operator) {
17773
+ case "$in":
17774
+ case "$nin": {
17775
+ const list = operand;
17776
+ if (list.length === 0) {
17777
+ conditions.push(operator === "$in" ? "FALSE" : "TRUE");
17778
+ break;
17779
+ }
17780
+ const placeholders = list.map(() => this.dialect.placeholder(paramIndex++));
17781
+ params.push(...list);
17782
+ conditions.push(`${quotedKey} ${operator === "$in" ? "IN" : "NOT IN"} (${placeholders.join(", ")})`);
17783
+ break;
17784
+ }
17785
+ case "$eq":
17786
+ if (operand === null) conditions.push(`${quotedKey} IS NULL`);
17787
+ else {
17788
+ conditions.push(`${quotedKey} = ${this.dialect.placeholder(paramIndex++)}`);
17789
+ params.push(operand);
17790
+ }
17791
+ break;
17792
+ case "$ne":
17793
+ if (operand === null) conditions.push(`${quotedKey} IS NOT NULL`);
17794
+ else {
17795
+ conditions.push(`${quotedKey} != ${this.dialect.placeholder(paramIndex++)}`);
17796
+ params.push(operand);
17797
+ }
17798
+ break;
17799
+ case "$gt":
17800
+ case "$gte":
17801
+ case "$lt":
17802
+ case "$lte": {
17803
+ const sqlOperator = {
17804
+ $gt: ">",
17805
+ $gte: ">=",
17806
+ $lt: "<",
17807
+ $lte: "<="
17808
+ }[operator];
17809
+ conditions.push(`${quotedKey} ${sqlOperator} ${this.dialect.placeholder(paramIndex++)}`);
17810
+ params.push(operand);
17811
+ break;
17812
+ }
17813
+ default: throw new Error(`Unsupported filter operator "${operator}" for column "${key}" on the Postgres driver.`);
17814
+ }
17535
17815
  else {
17536
17816
  conditions.push(`${quotedKey} = ${this.dialect.placeholder(paramIndex++)}`);
17537
17817
  params.push(value);
@@ -17543,6 +17823,17 @@ var PostgresDriver = class {
17543
17823
  };
17544
17824
  }
17545
17825
  /**
17826
+ * A filter value is an operator object when it is a plain object whose keys
17827
+ * ALL start with `$`. Arrays, Dates, and value objects (e.g. jsonb equality
17828
+ * payloads) keep their existing bind-as-value behavior.
17829
+ */
17830
+ isOperatorFilter(value) {
17831
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
17832
+ if (value instanceof Date || Buffer.isBuffer(value)) return false;
17833
+ const keys = Object.keys(value);
17834
+ return keys.length > 0 && keys.every((key) => key.startsWith("$"));
17835
+ }
17836
+ /**
17546
17837
  * Build an UPDATE query from update operations.
17547
17838
  *
17548
17839
  * @param table - Target table name
@@ -17557,7 +17848,7 @@ var PostgresDriver = class {
17557
17848
  let paramIndex = 1;
17558
17849
  if (update.$set) for (const [key, value] of Object.entries(update.$set)) {
17559
17850
  setClauses.push(`${this.dialect.quoteIdentifier(key)} = ${this.dialect.placeholder(paramIndex++)}`);
17560
- params.push(value === void 0 ? value : this.serializeValue(key, value));
17851
+ params.push(value === void 0 ? value : this.serializeValue(key, value, table));
17561
17852
  }
17562
17853
  if (update.$unset) for (const key of Object.keys(update.$unset)) setClauses.push(`${this.dialect.quoteIdentifier(key)} = NULL`);
17563
17854
  if (update.$inc) for (const [key, amount] of Object.entries(update.$inc)) {
@@ -19382,10 +19673,13 @@ var Migration = class {
19382
19673
  return this.driver.driver.name;
19383
19674
  }
19384
19675
  /**
19385
- * Execute all pending operations.
19676
+ * Execute all pending operations directly through the migration driver.
19677
+ *
19678
+ * SQL-capable drivers go through toSQL() + raw query execution instead
19679
+ * (phase-ordering, dry-run, export). This path is how the MigrationRunner
19680
+ * executes migrations on drivers WITHOUT SQL serialization (MongoDB), where
19681
+ * each pending operation maps to a native driver command.
19386
19682
  *
19387
- * @deprecated Use toSQL() instead — migrations now generate SQL rather than
19388
- * executing DDL directly through the driver.
19389
19683
  * @internal
19390
19684
  */
19391
19685
  async execute() {
@@ -21616,6 +21910,19 @@ var MigrationRunner = class {
21616
21910
  _warlock_js_logger.log.warn("database", "migration", "Nothing to migrate.");
21617
21911
  return results;
21618
21912
  }
21913
+ if (this.getDataSource().driver.supportsSqlSerialization === false) {
21914
+ const batch = await this.getNextBatchNumber();
21915
+ for (const MigrationClass of pending) {
21916
+ const result = await this.runMigration(MigrationClass, "up", {
21917
+ dryRun,
21918
+ record,
21919
+ batch
21920
+ });
21921
+ results.push(result);
21922
+ if (!result.success) break;
21923
+ }
21924
+ return results;
21925
+ }
21619
21926
  _warlock_js_logger.log.info("database", "migration", `Found ${pending.length} pending migration(s). Generating SQL pool...`);
21620
21927
  const nextBatch = await this.getNextBatchNumber();
21621
21928
  const taggedStatements = [];
@@ -21713,6 +22020,7 @@ var MigrationRunner = class {
21713
22020
  * By default, it exports all registered migrations. Use `pendingOnly: true` to export only pending ones.
21714
22021
  */
21715
22022
  async exportSQL(options = {}) {
22023
+ if (this.getDataSource().driver.supportsSqlSerialization === false) throw new Error("SQL export is not supported on this data source — its driver has no SQL dialect. Migrations on this driver execute native commands through the migration driver instead.");
21716
22024
  const migrationsToExport = options.pendingOnly ? await this.getPendingMigrations() : this.migrations;
21717
22025
  if (migrationsToExport.length === 0) {
21718
22026
  _warlock_js_logger.log.warn("database", "migration", "No migrations to export.");
@@ -21892,17 +22200,25 @@ var MigrationRunner = class {
21892
22200
  const shouldUseTransaction = migration.transactional ?? this.getDataSource().migrations?.transactional ?? driver.getDefaultTransactional();
21893
22201
  if (direction === "up") await migration.up();
21894
22202
  else await migration.down();
21895
- const sqlStatements = migration.toSQL();
21896
22203
  const databaseDriver = this.getDataSource().driver;
21897
- if (shouldUseTransaction && databaseDriver.transaction) await databaseDriver.transaction(async () => {
22204
+ const directExecution = databaseDriver.supportsSqlSerialization === false;
22205
+ const sqlStatements = directExecution ? [] : migration.toSQL();
22206
+ const applyMigration = async () => {
22207
+ if (directExecution) {
22208
+ await migration.execute();
22209
+ return;
22210
+ }
21898
22211
  for (const sql of sqlStatements) await databaseDriver.query(sql);
22212
+ };
22213
+ if (shouldUseTransaction && databaseDriver.transaction) await databaseDriver.transaction(async () => {
22214
+ await applyMigration();
21899
22215
  if (record) if (direction === "up") {
21900
22216
  const batch = options.batch ?? await this.getNextBatchNumber();
21901
22217
  await this.recordMigration(name, batch, MigrationClass.createdAt ? parseCreatedAt(MigrationClass.createdAt) : /* @__PURE__ */ new Date());
21902
22218
  } else await this.removeMigrationRecord(name);
21903
22219
  });
21904
22220
  else {
21905
- for (const sql of sqlStatements) await databaseDriver.query(sql);
22221
+ await applyMigration();
21906
22222
  if (record) if (direction === "up") {
21907
22223
  const batch = options.batch ?? await this.getNextBatchNumber();
21908
22224
  await this.recordMigration(name, batch, MigrationClass.createdAt ? parseCreatedAt(MigrationClass.createdAt) : /* @__PURE__ */ new Date());