@warlock.js/cascade 5.24.0 → 5.25.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 (70) hide show
  1. package/CHANGELOG.md +18 -5
  2. package/README.md +101 -101
  3. package/bin/cascade.js +0 -0
  4. package/cjs/index.cjs +201 -71
  5. package/cjs/index.cjs.map +1 -1
  6. package/esm/contracts/database-driver.contract.d.mts +22 -1
  7. package/esm/contracts/database-driver.contract.d.mts.map +1 -1
  8. package/esm/contracts/index.d.mts +1 -1
  9. package/esm/data-source/data-source-registry.d.mts +8 -6
  10. package/esm/data-source/data-source-registry.d.mts.map +1 -1
  11. package/esm/data-source/data-source-registry.mjs +7 -1
  12. package/esm/data-source/data-source-registry.mjs.map +1 -1
  13. package/esm/drivers/mongodb/mongodb-driver.d.mts +13 -0
  14. package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
  15. package/esm/drivers/mongodb/mongodb-driver.mjs +71 -2
  16. package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
  17. package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
  18. package/esm/drivers/mongodb/mongodb-query-builder.mjs +2 -1
  19. package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
  20. package/esm/drivers/postgres/postgres-driver.d.mts +9 -0
  21. package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
  22. package/esm/drivers/postgres/postgres-driver.mjs +55 -7
  23. package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
  24. package/esm/drivers/postgres/postgres-query-parser.d.mts +1 -4
  25. package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
  26. package/esm/drivers/postgres/postgres-query-parser.mjs +4 -5
  27. package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
  28. package/esm/errors/unsafe-filter.error.mjs.map +1 -1
  29. package/esm/errors/unsafe-raw-expression.error.mjs.map +1 -1
  30. package/esm/errors/unsupported-lock-for-update.error.d.mts +11 -0
  31. package/esm/errors/unsupported-lock-for-update.error.d.mts.map +1 -0
  32. package/esm/errors/unsupported-lock-for-update.error.mjs +16 -0
  33. package/esm/errors/unsupported-lock-for-update.error.mjs.map +1 -0
  34. package/esm/events/model-events.mjs.map +1 -1
  35. package/esm/index.d.mts +3 -2
  36. package/esm/index.mjs +2 -1
  37. package/esm/migration/migration-runner.mjs.map +1 -1
  38. package/esm/migration/sql-grammar.mjs.map +1 -1
  39. package/esm/model/generate-model-id.mjs +23 -0
  40. package/esm/model/generate-model-id.mjs.map +1 -0
  41. package/esm/model/methods/delete-methods.mjs.map +1 -1
  42. package/esm/model/methods/meta-methods.mjs.map +1 -1
  43. package/esm/model/methods/query-methods.mjs +25 -2
  44. package/esm/model/methods/query-methods.mjs.map +1 -1
  45. package/esm/model/methods/write-methods.mjs.map +1 -1
  46. package/esm/query-builder/query-builder.mjs +1 -1
  47. package/esm/query-builder/query-builder.mjs.map +1 -1
  48. package/esm/utils/sanitize-filter.mjs.map +1 -1
  49. package/esm/validation/plugins/database-rules-plugin.mjs.map +1 -1
  50. package/esm/writer/database-writer.d.mts +0 -33
  51. package/esm/writer/database-writer.d.mts.map +1 -1
  52. package/esm/writer/database-writer.mjs +3 -52
  53. package/esm/writer/database-writer.mjs.map +1 -1
  54. package/llms-full.txt +2901 -2864
  55. package/package.json +5 -5
  56. package/skills/aggregate-data/SKILL.md +165 -165
  57. package/skills/alter-migration/SKILL.md +531 -531
  58. package/skills/configure-delete-strategy/SKILL.md +130 -130
  59. package/skills/define-model/SKILL.md +205 -205
  60. package/skills/define-relations/SKILL.md +171 -171
  61. package/skills/manage-data-sources/SKILL.md +177 -140
  62. package/skills/manage-transactions/SKILL.md +215 -205
  63. package/skills/paginate-results/SKILL.md +122 -122
  64. package/skills/perform-atomic-ops/SKILL.md +156 -156
  65. package/skills/query-data/SKILL.md +213 -213
  66. package/skills/run-cascade-cli/SKILL.md +125 -125
  67. package/skills/search-by-vector/SKILL.md +127 -127
  68. package/skills/subscribe-to-model-events/SKILL.md +148 -148
  69. package/skills/track-changes/SKILL.md +127 -127
  70. package/skills/write-migration/SKILL.md +206 -206
package/cjs/index.cjs CHANGED
@@ -317,7 +317,7 @@ var DataSourceRegistry = class {
317
317
  defaultSource;
318
318
  defaultIsExplicit = false;
319
319
  events = new node_events.EventEmitter();
320
- /** Replaced sources; their forwarded driver events are ignored (drivers have no `off`). */
320
+ /** Replaced sources; their forwarded driver events are ignored. */
321
321
  detached = /* @__PURE__ */ new WeakSet();
322
322
  /** In-flight `getOrRegister` factories, so concurrent callers share one creation. */
323
323
  pending = /* @__PURE__ */ new Map();
@@ -368,6 +368,12 @@ var DataSourceRegistry = class {
368
368
  source.driver.on("disconnected", () => {
369
369
  if (!this.detached.has(source)) this.events.emit("disconnected", source);
370
370
  });
371
+ source.driver.on("query", (event) => {
372
+ if (!this.detached.has(source)) this.events.emit("query", {
373
+ ...event,
374
+ connection: source.name
375
+ });
376
+ });
371
377
  return source;
372
378
  }
373
379
  /**
@@ -670,6 +676,20 @@ var UnsupportedLeanOperationError = class UnsupportedLeanOperationError extends
670
676
  }
671
677
  };
672
678
 
679
+ //#endregion
680
+ //#region ../cascade/src/errors/unsupported-lock-for-update.error.ts
681
+ /**
682
+ * Error thrown when `lockForUpdate()` is requested on MongoDB, whose document
683
+ * model has no row-level `SELECT ... FOR UPDATE` equivalent.
684
+ */
685
+ var UnsupportedLockForUpdateError = class UnsupportedLockForUpdateError extends Error {
686
+ constructor() {
687
+ super("lockForUpdate() is unsupported on MongoDB. MongoDB has no row-level SELECT locking. Use a conditional atomic update instead, e.g. Model.atomic({ id, leaseUntil: { $lt: now } }, { $set: { status: \"processing\" } }, { trustedFilter: true }).");
688
+ this.name = "UnsupportedLockForUpdateError";
689
+ if (Error.captureStackTrace) Error.captureStackTrace(this, UnsupportedLockForUpdateError);
690
+ }
691
+ };
692
+
673
693
  //#endregion
674
694
  //#region ../cascade/src/errors/unsupported-query-operation.error.ts
675
695
  /**
@@ -4045,6 +4065,27 @@ function offModelEvent(model, event, listener) {
4045
4065
  model.events.off(event, listener);
4046
4066
  }
4047
4067
 
4068
+ //#endregion
4069
+ //#region ../cascade/src/model/generate-model-id.ts
4070
+ /**
4071
+ * Generate the next model id using the model's configured sequence options.
4072
+ *
4073
+ * This is shared by normal inserts and MongoDB upserts so both write paths
4074
+ * resolve random and configured sequence settings identically.
4075
+ */
4076
+ async function generateModelId(ModelClass) {
4077
+ const idGenerator = ModelClass.getDataSource().idGenerator;
4078
+ if (!idGenerator) return;
4079
+ const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
4080
+ const initialId = ModelClass.initialId ? ModelClass.initialId : ModelClass.randomInitialId ? typeof ModelClass.randomInitialId === "function" ? ModelClass.randomInitialId() : randomInt(1e4, 499999) : 1;
4081
+ const incrementIdBy = ModelClass.randomIncrement ? typeof ModelClass.randomIncrement === "function" ? ModelClass.randomIncrement() : randomInt(1, 10) : ModelClass.incrementIdBy || 1;
4082
+ return await idGenerator.generateNextId({
4083
+ table: ModelClass.table,
4084
+ initialId,
4085
+ incrementIdBy
4086
+ });
4087
+ }
4088
+
4048
4089
  //#endregion
4049
4090
  //#region ../cascade/src/validation/database-writer-validation-error.ts
4050
4091
  /**
@@ -4708,15 +4749,8 @@ var DatabaseWriter = class {
4708
4749
  */
4709
4750
  async generateNextId() {
4710
4751
  if (!this.ctor.autoGenerateId || this.model.get("id")) return;
4711
- const idGenerator = this.dataSource.idGenerator;
4712
- if (!idGenerator) return;
4713
- const initialId = this.resolveInitialId();
4714
- const incrementIdBy = this.resolveIncrementBy();
4715
- const id = await idGenerator.generateNextId({
4716
- table: this.table,
4717
- initialId,
4718
- incrementIdBy
4719
- });
4752
+ const id = await generateModelId(this.ctor);
4753
+ if (id === void 0) return;
4720
4754
  this.model.set("id", id);
4721
4755
  }
4722
4756
  /**
@@ -4763,49 +4797,6 @@ var DatabaseWriter = class {
4763
4797
  return operations;
4764
4798
  }
4765
4799
  /**
4766
- * Resolve the initial ID from model configuration.
4767
- *
4768
- * Priority:
4769
- * 1. Model.initialId (explicit value)
4770
- * 2. Model.randomInitialId (random or function)
4771
- * 3. Default: 1
4772
- *
4773
- * @returns The initial ID value
4774
- * @private
4775
- */
4776
- resolveInitialId() {
4777
- if (this.ctor.initialId) return this.ctor.initialId;
4778
- if (this.ctor.randomInitialId) return typeof this.ctor.randomInitialId === "function" ? this.ctor.randomInitialId() : this.randomInt(1e4, 499999);
4779
- return 1;
4780
- }
4781
- /**
4782
- * Resolve the increment value from model configuration.
4783
- *
4784
- * Priority:
4785
- * 1. Model.incrementIdBy (explicit value)
4786
- * 2. Model.randomIncrement (random or function)
4787
- * 3. Default: 1
4788
- *
4789
- * @returns The increment value
4790
- * @private
4791
- */
4792
- resolveIncrementBy() {
4793
- if (this.ctor.randomIncrement) return typeof this.ctor.randomIncrement === "function" ? this.ctor.randomIncrement() : this.randomInt(1, 10);
4794
- if (this.ctor.incrementIdBy) return this.ctor.incrementIdBy;
4795
- return 1;
4796
- }
4797
- /**
4798
- * Generate a random integer between min and max (inclusive).
4799
- *
4800
- * @param min - Minimum value
4801
- * @param max - Maximum value
4802
- * @returns Random integer
4803
- * @private
4804
- */
4805
- randomInt(min, max) {
4806
- return Math.floor(Math.random() * (max - min + 1)) + min;
4807
- }
4808
- /**
4809
4800
  * Trigger sync operations after successful save.
4810
4801
  *
4811
4802
  * Emits a model.updated event that ModelSyncOperation listens to.
@@ -5319,6 +5310,26 @@ function toDriverOptions(options) {
5319
5310
  return rest;
5320
5311
  }
5321
5312
  /**
5313
+ * Add the same generated `id` that normal Mongo inserts receive to an upsert's
5314
+ * insert branch. `$setOnInsert` keeps an existing document's id immutable.
5315
+ */
5316
+ async function withAutoGeneratedUpsertId(ModelClass, filter, operations, options) {
5317
+ if (!options?.upsert || !ModelClass.autoGenerateId || ModelClass.getDriver().name !== "mongodb" || Array.isArray(operations)) return operations;
5318
+ const filterId = filter.id;
5319
+ const filterConstrainsId = Object.hasOwn(filter, "id") && (filterId === null || typeof filterId !== "object" || !Array.isArray(filterId) && Object.hasOwn(filterId, "$eq"));
5320
+ const updateSuppliesId = Object.hasOwn(operations.$set ?? {}, "id") || Object.hasOwn(operations.$setOnInsert ?? {}, "id");
5321
+ if (filterConstrainsId || updateSuppliesId) return operations;
5322
+ const id = await generateModelId(ModelClass);
5323
+ if (id === void 0) return operations;
5324
+ return {
5325
+ ...operations,
5326
+ $setOnInsert: {
5327
+ ...operations.$setOnInsert,
5328
+ id
5329
+ }
5330
+ };
5331
+ }
5332
+ /**
5322
5333
  * Pin a driver-level write to the rows the model's global scopes (tenant, soft
5323
5334
  * delete) allow. The driver write APIs take a raw filter and never see scopes,
5324
5335
  * so the target ids are resolved through the scoped query builder and added to
@@ -5352,7 +5363,8 @@ async function performAtomic(ModelClass, filter, operations, options) {
5352
5363
  const resolved = resolveFilter(filter, options);
5353
5364
  const scoped = options?.upsert ? await scopeUpsertFilter(ModelClass, resolved) : await scopeWriteFilter(ModelClass, resolved, false);
5354
5365
  if (!scoped) return 0;
5355
- const result = await ModelClass.getDriver().atomic(ModelClass.table, scoped, operations, toDriverOptions(options));
5366
+ const upsertOperations = await withAutoGeneratedUpsertId(ModelClass, scoped, operations, options);
5367
+ const result = await ModelClass.getDriver().atomic(ModelClass.table, scoped, upsertOperations, toDriverOptions(options));
5356
5368
  return result.modifiedCount + (result.upsertedCount ?? 0);
5357
5369
  }
5358
5370
  /**
@@ -5389,7 +5401,8 @@ async function findOneAndUpdateRecord(ModelClass, filter, update, options) {
5389
5401
  const resolved = resolveFilter(filter, options);
5390
5402
  const scoped = options?.upsert ? await scopeUpsertFilter(ModelClass, resolved) : await scopeWriteFilter(ModelClass, resolved, true);
5391
5403
  if (!scoped) return null;
5392
- const result = await ModelClass.getDriver().findOneAndUpdate(ModelClass.table, scoped, update, toDriverOptions(options));
5404
+ const upsertUpdate = await withAutoGeneratedUpsertId(ModelClass, scoped, update, options);
5405
+ const result = await ModelClass.getDriver().findOneAndUpdate(ModelClass.table, scoped, upsertUpdate, toDriverOptions(options));
5393
5406
  if (!result) return null;
5394
5407
  return ModelClass.hydrate(result);
5395
5408
  }
@@ -10466,7 +10479,7 @@ var QueryBuilder = class QueryBuilder {
10466
10479
  }
10467
10480
  /** Raw GROUP BY expression. */
10468
10481
  groupByRaw(expression, bindings) {
10469
- this.addOperation("groupBy", {
10482
+ this.addOperation("groupByRaw", {
10470
10483
  expression,
10471
10484
  bindings: bindings ?? []
10472
10485
  });
@@ -13583,7 +13596,7 @@ var MongoQueryBuilder = class MongoQueryBuilder extends QueryBuilder {
13583
13596
  * keeps a queue-claim pattern from silently running unlocked.
13584
13597
  */
13585
13598
  lockForUpdate() {
13586
- 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).");
13599
+ throw new UnsupportedLockForUpdateError();
13587
13600
  }
13588
13601
  /**
13589
13602
  * Executes a callback with the query builder without breaking the chain.
@@ -14390,6 +14403,7 @@ var MongoDbDriver = class {
14390
14403
  transactionOptions;
14391
14404
  idGeneratorInstance;
14392
14405
  _blueprint;
14406
+ monitoredCommands = /* @__PURE__ */ new Map();
14393
14407
  get blueprint() {
14394
14408
  if (!this._blueprint) this._blueprint = new MongoDBBlueprint(this.database);
14395
14409
  return this._blueprint;
@@ -14517,13 +14531,18 @@ var MongoDbDriver = class {
14517
14531
  _warlock_js_logger.log.warn("database.mongodb", "connection", "Disconnected from database");
14518
14532
  }
14519
14533
  });
14534
+ client.on("commandStarted", (event) => this.commandStarted(event));
14535
+ client.on("commandSucceeded", (event) => this.commandSucceeded(event));
14536
+ client.on("commandFailed", (event) => this.commandFailed(event));
14520
14537
  if (this.config.logging) {
14521
14538
  const ignoredCommands = [
14522
14539
  "isMaster",
14523
14540
  "hello",
14524
14541
  "ping",
14525
14542
  "saslStart",
14526
- "saslContinue"
14543
+ "saslContinue",
14544
+ "endSessions",
14545
+ "buildInfo"
14527
14546
  ];
14528
14547
  client.on("commandStarted", (event) => {
14529
14548
  if (ignoredCommands.includes(event.commandName)) return;
@@ -14580,6 +14599,10 @@ var MongoDbDriver = class {
14580
14599
  on(event, listener) {
14581
14600
  this.events.on(event, listener);
14582
14601
  }
14602
+ /** Remove a previously registered driver event listener. */
14603
+ off(event, listener) {
14604
+ this.events.off(event, listener);
14605
+ }
14583
14606
  /**
14584
14607
  * Insert a single document into the given collection.
14585
14608
  */
@@ -14896,7 +14919,7 @@ var MongoDbDriver = class {
14896
14919
  */
14897
14920
  buildClientOptions() {
14898
14921
  const baseOptions = { ...this.config.clientOptions ?? {} };
14899
- if (this.config.logging) baseOptions.monitorCommands = true;
14922
+ if (baseOptions.monitorCommands === void 0) baseOptions.monitorCommands = true;
14900
14923
  if (this.config.username && !baseOptions.auth) baseOptions.auth = {
14901
14924
  username: this.config.username,
14902
14925
  password: this.config.password
@@ -14910,6 +14933,65 @@ var MongoDbDriver = class {
14910
14933
  emit(event, ...args) {
14911
14934
  this.events.emit(event, ...args);
14912
14935
  }
14936
+ commandStarted(event) {
14937
+ if (this.ignoredCommand(event.commandName) || this.events.listenerCount("query") === 0) return;
14938
+ const started = performance.now();
14939
+ const { lsid: _lsid, $clusterTime: _clusterTime, $db: _database, ...pipeline } = event.command;
14940
+ const collection = event.command[event.commandName];
14941
+ this.monitoredCommands.set(event.requestId, {
14942
+ connection: this.name,
14943
+ driver: "mongodb",
14944
+ command: event.commandName,
14945
+ collection: typeof collection === "string" ? collection : void 0,
14946
+ pipeline,
14947
+ started,
14948
+ startedAt: performance.timeOrigin + started
14949
+ });
14950
+ }
14951
+ commandSucceeded(event) {
14952
+ const command = this.monitoredCommands.get(event.requestId);
14953
+ if (!command) return;
14954
+ this.monitoredCommands.delete(event.requestId);
14955
+ const { started, ...query } = command;
14956
+ const rowCount = event.reply?.n ?? event.reply?.cursor?.firstBatch?.length;
14957
+ this.emitQueryEvent({
14958
+ ...query,
14959
+ durationMs: performance.now() - started,
14960
+ ...typeof rowCount === "number" ? { rowCount } : {}
14961
+ });
14962
+ }
14963
+ commandFailed(event) {
14964
+ const command = this.monitoredCommands.get(event.requestId);
14965
+ if (!command) return;
14966
+ this.monitoredCommands.delete(event.requestId);
14967
+ const { started, ...query } = command;
14968
+ this.emitQueryEvent({
14969
+ ...query,
14970
+ durationMs: performance.now() - started,
14971
+ error: event.failure
14972
+ });
14973
+ }
14974
+ /**
14975
+ * Emit a `query` event. It runs inside the MongoDB client's own monitoring
14976
+ * callback, and an observer must never break the client, so a throwing
14977
+ * listener is swallowed here.
14978
+ */
14979
+ emitQueryEvent(event) {
14980
+ try {
14981
+ this.emit("query", event);
14982
+ } catch {}
14983
+ }
14984
+ ignoredCommand(command) {
14985
+ return [
14986
+ "isMaster",
14987
+ "hello",
14988
+ "ping",
14989
+ "saslStart",
14990
+ "saslContinue",
14991
+ "endSessions",
14992
+ "buildInfo"
14993
+ ].includes(command);
14994
+ }
14913
14995
  /**
14914
14996
  * Ensure MongoDB is running as a replica set (required for transactions).
14915
14997
  *
@@ -17315,12 +17397,11 @@ var PostgresQueryParser = class PostgresQueryParser {
17315
17397
  const columns = Array.isArray(fields) ? fields : [fields];
17316
17398
  this.groupColumns.push(...columns);
17317
17399
  }
17318
- /**
17319
- * Process a raw GROUP BY expression (emitted verbatim, no quoting). Used by
17320
- * `groupByDate` to add a `date_trunc(...)` bucket to the GROUP BY clause.
17321
- */
17400
+ /** Process a raw GROUP BY expression, binding `?` placeholders positionally. */
17322
17401
  processGroupByRaw(data) {
17323
- const expression = data.expression;
17402
+ let expression = data.expression;
17403
+ const bindings = data.bindings ?? [];
17404
+ for (const binding of bindings) expression = expression.replace("?", this.addParam(binding));
17324
17405
  this.groupRawExpressions.push(expression);
17325
17406
  }
17326
17407
  /**
@@ -19323,6 +19404,13 @@ var PostgresDriver = class {
19323
19404
  if (!this._eventListeners.has(event)) this._eventListeners.set(event, /* @__PURE__ */ new Set());
19324
19405
  this._eventListeners.get(event).add(listener);
19325
19406
  }
19407
+ /** Remove a previously registered driver event listener. */
19408
+ off(event, listener) {
19409
+ const listeners = this._eventListeners.get(event);
19410
+ if (!listeners) return;
19411
+ listeners.delete(listener);
19412
+ if (listeners.size === 0) this._eventListeners.delete(event);
19413
+ }
19326
19414
  /**
19327
19415
  * Serialize data for storage in PostgreSQL.
19328
19416
  *
@@ -19824,12 +19912,12 @@ var PostgresDriver = class {
19824
19912
  if (options?.isolationLevel) beginSql += ` ISOLATION LEVEL ${options.isolationLevel.toUpperCase()}`;
19825
19913
  if (options?.readOnly) beginSql += " READ ONLY";
19826
19914
  if (options?.deferrable) beginSql += " DEFERRABLE";
19827
- await client.query(beginSql);
19915
+ await this.executePostgresQuery(() => client.query(beginSql), beginSql);
19828
19916
  transactionStarted = true;
19829
- for (const [name, value] of settings) await client.query("SELECT set_config($1, $2, true)", [name, String(value)]);
19917
+ for (const [name, value] of settings) await this.executePostgresQuery(() => client.query("SELECT set_config($1, $2, true)", [name, String(value)]), "SELECT set_config($1, $2, true)", [name, String(value)]);
19830
19918
  } catch (error) {
19831
19919
  if (transactionStarted) try {
19832
- await client.query("ROLLBACK");
19920
+ await this.executePostgresQuery(() => client.query("ROLLBACK"), "ROLLBACK");
19833
19921
  } catch {}
19834
19922
  client.release(error instanceof Error ? error : new Error(String(error)));
19835
19923
  throw error;
@@ -19838,7 +19926,7 @@ var PostgresDriver = class {
19838
19926
  context: client,
19839
19927
  commit: async () => {
19840
19928
  try {
19841
- await client.query("COMMIT");
19929
+ await this.executePostgresQuery(() => client.query("COMMIT"), "COMMIT");
19842
19930
  } catch (error) {
19843
19931
  client.release(error instanceof Error ? error : new Error(String(error)));
19844
19932
  throw error;
@@ -19847,7 +19935,7 @@ var PostgresDriver = class {
19847
19935
  },
19848
19936
  rollback: async () => {
19849
19937
  try {
19850
- await client.query("ROLLBACK");
19938
+ await this.executePostgresQuery(() => client.query("ROLLBACK"), "ROLLBACK");
19851
19939
  } catch (error) {
19852
19940
  client.release(error instanceof Error ? error : new Error(String(error)));
19853
19941
  throw error;
@@ -20080,8 +20168,8 @@ var PostgresDriver = class {
20080
20168
  paramCount: params.length
20081
20169
  }
20082
20170
  });
20083
- if (txClient) result = await txClient.query(sql, params);
20084
- else result = await this.pool.query(sql, params);
20171
+ if (txClient) result = await this.executePostgresQuery(() => txClient.query(sql, params), sql, params);
20172
+ else result = await this.executePostgresQuery(() => this.pool.query(sql, params), sql, params);
20085
20173
  if (this.config.logging) {
20086
20174
  const duration = (performance.now() - startTime).toFixed(2);
20087
20175
  _warlock_js_logger.log.success({
@@ -20114,6 +20202,47 @@ var PostgresDriver = class {
20114
20202
  throw error;
20115
20203
  }
20116
20204
  }
20205
+ /** Execute SQL and emit its public query event when the event is observed. */
20206
+ async executePostgresQuery(execute, sql, bindings = []) {
20207
+ if (!this._eventListeners.get("query")?.size) return execute();
20208
+ const started = performance.now();
20209
+ const startedAt = performance.timeOrigin + started;
20210
+ let result;
20211
+ try {
20212
+ result = await execute();
20213
+ } catch (error) {
20214
+ this.emitQueryEvent({
20215
+ connection: this.name,
20216
+ driver: "postgres",
20217
+ sql,
20218
+ bindings,
20219
+ durationMs: performance.now() - started,
20220
+ startedAt,
20221
+ error
20222
+ });
20223
+ throw error;
20224
+ }
20225
+ const event = {
20226
+ connection: this.name,
20227
+ driver: "postgres",
20228
+ sql,
20229
+ bindings,
20230
+ durationMs: performance.now() - started,
20231
+ startedAt
20232
+ };
20233
+ if (typeof result.rowCount === "number") event.rowCount = result.rowCount;
20234
+ this.emitQueryEvent(event);
20235
+ return result;
20236
+ }
20237
+ /**
20238
+ * Emit a `query` event. An observer is never allowed to fail the query it
20239
+ * observes, so a throwing listener is swallowed here.
20240
+ */
20241
+ emitQueryEvent(event) {
20242
+ try {
20243
+ this.emit("query", event);
20244
+ } catch {}
20245
+ }
20117
20246
  /**
20118
20247
  * Emit an event to all registered listeners.
20119
20248
  *
@@ -25321,6 +25450,7 @@ exports.UndefinedWhereValueError = UndefinedWhereValueError;
25321
25450
  exports.UnsafeFilterError = UnsafeFilterError;
25322
25451
  exports.UnsafeRawExpressionError = UnsafeRawExpressionError;
25323
25452
  exports.UnsupportedLeanOperationError = UnsupportedLeanOperationError;
25453
+ exports.UnsupportedLockForUpdateError = UnsupportedLockForUpdateError;
25324
25454
  exports.UnsupportedQueryOperationError = UnsupportedQueryOperationError;
25325
25455
  exports.UnsupportedUpdateOperationError = UnsupportedUpdateOperationError;
25326
25456
  exports.afterCommit = afterCommit;