@warlock.js/cascade 5.16.0 → 5.17.1

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/cjs/index.cjs CHANGED
@@ -411,6 +411,42 @@ var TransactionRollbackError = class TransactionRollbackError extends Error {
411
411
  }
412
412
  };
413
413
 
414
+ //#endregion
415
+ //#region ../cascade/src/errors/undefined-where-value.error.ts
416
+ /**
417
+ * Error thrown when a `where()`-family call is given `undefined` as the
418
+ * bound value.
419
+ *
420
+ * A bound `undefined` reaches SQL drivers as `= NULL`, which never matches
421
+ * any row (SQL's three-valued logic — `NULL = NULL` is `NULL`, not `true`) —
422
+ * and reaches the MongoDB driver as a filter that matches every document
423
+ * missing the field. Either way the query silently returns the wrong rows
424
+ * instead of failing loudly, which usually means the caller forgot to guard
425
+ * an id/value that turned out to be missing (e.g. `User.find(post.authorId)`
426
+ * when `authorId` came back `undefined`).
427
+ *
428
+ * To match `NULL` explicitly, pass `null` — `where("deletedAt", null)` is a
429
+ * valid, intentional query. To skip the query entirely when there's no
430
+ * value, guard the call site instead of calling `where()`.
431
+ */
432
+ var UndefinedWhereValueError = class UndefinedWhereValueError extends Error {
433
+ /**
434
+ * The field whose value was `undefined`.
435
+ */
436
+ field;
437
+ /**
438
+ * Creates a new UndefinedWhereValueError.
439
+ *
440
+ * @param field - The field name whose value was rejected
441
+ */
442
+ constructor(field) {
443
+ super(`where("${field}", undefined) — a bound "undefined" value silently becomes a NULL comparison that never matches, hiding the real bug. Pass null to match NULL explicitly, or skip the query when there's no value for "${field}".`);
444
+ this.name = "UndefinedWhereValueError";
445
+ this.field = field;
446
+ if (Error.captureStackTrace) Error.captureStackTrace(this, UndefinedWhereValueError);
447
+ }
448
+ };
449
+
414
450
  //#endregion
415
451
  //#region ../cascade/src/errors/unsafe-filter.error.ts
416
452
  /**
@@ -3504,32 +3540,51 @@ const assertNoOperatorKeys = (value, field) => {
3504
3540
  }
3505
3541
  };
3506
3542
  /**
3507
- * Assert a single equality-position value carries no `$`-prefixed keys.
3508
- * Scalars, `Date`s and other non-plain objects pass through untouched;
3509
- * plain objects/arrays are checked recursively.
3543
+ * Reject a bound `undefined` value in an equality position. `undefined`
3544
+ * silently becomes `= NULL` (SQL) / "field missing" (MongoDB) a query that
3545
+ * never matches what the caller meant, instead of failing loudly. `null` is
3546
+ * the explicit, intentional way to match NULL and is never rejected here.
3547
+ *
3548
+ * @param value - The equality value to check
3549
+ * @param field - The field name, used for the error message
3550
+ * @throws UndefinedWhereValueError when `value` is `undefined`
3551
+ */
3552
+ const assertDefined = (value, field) => {
3553
+ if (value === void 0) throw new UndefinedWhereValueError(field);
3554
+ };
3555
+ /**
3556
+ * Assert a single equality-position value carries no `$`-prefixed keys and
3557
+ * is not `undefined`. Scalars, `Date`s and other non-plain objects pass
3558
+ * through untouched; plain objects/arrays are checked recursively for
3559
+ * operator keys.
3510
3560
  *
3511
3561
  * @param value - The equality value to check
3512
3562
  * @param field - The field name, used for the error message
3513
3563
  * @returns The value, unchanged
3514
3564
  * @throws UnsafeFilterError when a `$`-prefixed key is found
3565
+ * @throws UndefinedWhereValueError when `value` is `undefined`
3515
3566
  */
3516
3567
  function sanitizeFilterValue(value, field) {
3568
+ assertDefined(value, field);
3517
3569
  assertNoOperatorKeys(value, field);
3518
3570
  return value;
3519
3571
  }
3520
3572
  /**
3521
3573
  * Assert a `{ field: value }` equality filter carries no `$`-prefixed keys —
3522
3574
  * neither as top-level field names (`{ $where: … }`) nor inside any value
3523
- * (`{ password: { $ne: null } }`). Dotted field paths ("profile.name") and
3524
- * plain nested documents remain valid.
3575
+ * (`{ password: { $ne: null } }`) and that no field's value is `undefined`.
3576
+ * Dotted field paths ("profile.name") and plain nested documents remain
3577
+ * valid.
3525
3578
  *
3526
3579
  * @param filter - The filter object to check
3527
3580
  * @returns The filter, unchanged
3528
3581
  * @throws UnsafeFilterError when a `$`-prefixed key is found
3582
+ * @throws UndefinedWhereValueError when a field's value is `undefined`
3529
3583
  */
3530
3584
  function sanitizeFilter(filter) {
3531
3585
  for (const [field, value] of Object.entries(filter)) {
3532
3586
  if (field.startsWith("$")) rejectOperatorKey(field, field);
3587
+ assertDefined(value, field);
3533
3588
  assertNoOperatorKeys(value, field);
3534
3589
  }
3535
3590
  return filter;
@@ -8887,11 +8942,17 @@ var QueryBuilder = class QueryBuilder {
8887
8942
  operator: "=",
8888
8943
  value: sanitizeFilterValue(args[1], String(args[0]))
8889
8944
  });
8890
- else this.addOperation("where", {
8891
- field: args[0],
8892
- operator: args[1],
8893
- value: args[1] === "=" ? sanitizeFilterValue(args[2], String(args[0])) : args[2]
8894
- });
8945
+ else {
8946
+ const field = String(args[0]);
8947
+ const operator = args[1];
8948
+ const value = operator === "=" ? sanitizeFilterValue(args[2], field) : args[2];
8949
+ if (operator !== "=") assertDefined(value, field);
8950
+ this.addOperation("where", {
8951
+ field: args[0],
8952
+ operator,
8953
+ value
8954
+ });
8955
+ }
8895
8956
  return this;
8896
8957
  }
8897
8958
  orWhere(...args) {
@@ -8909,11 +8970,17 @@ var QueryBuilder = class QueryBuilder {
8909
8970
  operator: "=",
8910
8971
  value: sanitizeFilterValue(args[1], String(args[0]))
8911
8972
  });
8912
- else this.addOperation("orWhere", {
8913
- field: args[0],
8914
- operator: args[1],
8915
- value: args[1] === "=" ? sanitizeFilterValue(args[2], String(args[0])) : args[2]
8916
- });
8973
+ else {
8974
+ const field = String(args[0]);
8975
+ const operator = args[1];
8976
+ const value = operator === "=" ? sanitizeFilterValue(args[2], field) : args[2];
8977
+ if (operator !== "=") assertDefined(value, field);
8978
+ this.addOperation("orWhere", {
8979
+ field: args[0],
8980
+ operator,
8981
+ value
8982
+ });
8983
+ }
8917
8984
  return this;
8918
8985
  }
8919
8986
  /**
@@ -12150,11 +12217,17 @@ var MongoQueryBuilder = class MongoQueryBuilder extends QueryBuilder {
12150
12217
  operator: "=",
12151
12218
  value: sanitizeFilterValue(args[1], String(args[0]))
12152
12219
  });
12153
- else if (args.length === 3) this.operationsHelper.addMatchOperation(prefix, {
12154
- field: args[0],
12155
- operator: args[1],
12156
- value: args[1] === "=" ? sanitizeFilterValue(args[2], String(args[0])) : args[2]
12157
- });
12220
+ else if (args.length === 3) {
12221
+ const field = String(args[0]);
12222
+ const operator = args[1];
12223
+ const value = operator === "=" ? sanitizeFilterValue(args[2], field) : args[2];
12224
+ if (operator !== "=") assertDefined(value, field);
12225
+ this.operationsHelper.addMatchOperation(prefix, {
12226
+ field: args[0],
12227
+ operator,
12228
+ value
12229
+ });
12230
+ }
12158
12231
  }
12159
12232
  /**
12160
12233
  * Internal helper for adding raw where clauses.
@@ -17898,7 +17971,8 @@ function buildPostgresPoolConfig(config) {
17898
17971
  max: config.max ?? 10,
17899
17972
  min: config.min ?? 0,
17900
17973
  idleTimeoutMillis: config.idleTimeoutMillis ?? 3e4,
17901
- connectionTimeoutMillis: config.connectionTimeoutMillis ?? 2e3,
17974
+ connectionTimeoutMillis: config.connectionTimeoutMillis ?? 1e4,
17975
+ keepAlive: config.keepAlive ?? true,
17902
17976
  application_name: config.application_name ?? "cascade",
17903
17977
  ssl: config.ssl
17904
17978
  };
@@ -18072,6 +18146,9 @@ var PostgresDriver = class {
18072
18146
  const poolConfig = buildPostgresPoolConfig(this.config);
18073
18147
  _warlock_js_logger.log.info("database.postgres", "connection", `Connecting to database ${_mongez_copper.colors.bold(_mongez_copper.colors.yellowBright(poolConfig.database ?? ""))}`);
18074
18148
  this._pool = new pg.Pool(poolConfig);
18149
+ this._pool.on("error", (error) => {
18150
+ _warlock_js_logger.log.error("database.postgres", "pool", `Unexpected error on an idle client: ${error.message}`);
18151
+ });
18075
18152
  (await this._pool.connect()).release();
18076
18153
  _warlock_js_logger.log.success("database.postgres", "connection", `Connected to database ${_mongez_copper.colors.bold(_mongez_copper.colors.yellowBright(this.config.database))}`);
18077
18154
  this._isConnected = true;
@@ -18595,19 +18672,34 @@ var PostgresDriver = class {
18595
18672
  */
18596
18673
  async beginTransaction(options) {
18597
18674
  const client = await this.pool.connect();
18598
- let beginSql = "BEGIN";
18599
- if (options?.isolationLevel) beginSql += ` ISOLATION LEVEL ${options.isolationLevel.toUpperCase()}`;
18600
- if (options?.readOnly) beginSql += " READ ONLY";
18601
- if (options?.deferrable) beginSql += " DEFERRABLE";
18602
- await client.query(beginSql);
18675
+ try {
18676
+ let beginSql = "BEGIN";
18677
+ if (options?.isolationLevel) beginSql += ` ISOLATION LEVEL ${options.isolationLevel.toUpperCase()}`;
18678
+ if (options?.readOnly) beginSql += " READ ONLY";
18679
+ if (options?.deferrable) beginSql += " DEFERRABLE";
18680
+ await client.query(beginSql);
18681
+ } catch (error) {
18682
+ client.release(error instanceof Error ? error : new Error(String(error)));
18683
+ throw error;
18684
+ }
18603
18685
  return {
18604
18686
  context: client,
18605
18687
  commit: async () => {
18606
- await client.query("COMMIT");
18688
+ try {
18689
+ await client.query("COMMIT");
18690
+ } catch (error) {
18691
+ client.release(error instanceof Error ? error : new Error(String(error)));
18692
+ throw error;
18693
+ }
18607
18694
  client.release();
18608
18695
  },
18609
18696
  rollback: async () => {
18610
- await client.query("ROLLBACK");
18697
+ try {
18698
+ await client.query("ROLLBACK");
18699
+ } catch (error) {
18700
+ client.release(error instanceof Error ? error : new Error(String(error)));
18701
+ throw error;
18702
+ }
18611
18703
  client.release();
18612
18704
  }
18613
18705
  };
@@ -18630,13 +18722,20 @@ var PostgresDriver = class {
18630
18722
  if (databaseTransactionContext.hasActiveTransaction()) return fn(ctx);
18631
18723
  const tx = await this.beginTransaction(options);
18632
18724
  databaseTransactionContext.enter({ session: tx.context });
18725
+ let commitStarted = false;
18633
18726
  try {
18634
18727
  const result = await fn(ctx);
18728
+ commitStarted = true;
18635
18729
  await tx.commit();
18636
18730
  return result;
18637
18731
  } catch (error) {
18638
- await tx.rollback();
18639
- _warlock_js_logger.log.error(`database.postgress`, "transaction", "Transaction operation failed, rolled back everything");
18732
+ if (commitStarted) _warlock_js_logger.log.error(`database.postgress`, "transaction", "Transaction COMMIT failed; client discarded, no rollback issued");
18733
+ else try {
18734
+ await tx.rollback();
18735
+ _warlock_js_logger.log.error(`database.postgress`, "transaction", "Transaction operation failed, rolled back everything");
18736
+ } catch (rollbackError) {
18737
+ _warlock_js_logger.log.error(`database.postgress`, "transaction", rollbackError);
18738
+ }
18640
18739
  throw error;
18641
18740
  } finally {
18642
18741
  databaseTransactionContext.exit();
@@ -23653,6 +23752,7 @@ exports.RelationLoader = RelationLoader;
23653
23752
  exports.SyncContextManager = SyncContextManager;
23654
23753
  exports.SyncManager = SyncManager;
23655
23754
  exports.TransactionRollbackError = TransactionRollbackError;
23755
+ exports.UndefinedWhereValueError = UndefinedWhereValueError;
23656
23756
  exports.UnsafeFilterError = UnsafeFilterError;
23657
23757
  exports.UnsafeRawExpressionError = UnsafeRawExpressionError;
23658
23758
  exports.UnsupportedLeanOperationError = UnsupportedLeanOperationError;
@@ -23668,6 +23768,7 @@ exports.arrayJson = arrayJson;
23668
23768
  exports.arrayText = arrayText;
23669
23769
  exports.arrayTimestamp = arrayTimestamp;
23670
23770
  exports.arrayUuid = arrayUuid;
23771
+ exports.assertDefined = assertDefined;
23671
23772
  exports.bigInt = bigInt;
23672
23773
  exports.bigInteger = bigInteger;
23673
23774
  exports.binary = binary;