@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/CHANGELOG.md +261 -254
- package/cjs/index.cjs +131 -30
- package/cjs/index.cjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +12 -6
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +36 -10
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/types.d.mts +12 -2
- package/esm/drivers/postgres/types.d.mts.map +1 -1
- package/esm/errors/undefined-where-value.error.d.mts +32 -0
- package/esm/errors/undefined-where-value.error.d.mts.map +1 -0
- package/esm/errors/undefined-where-value.error.mjs +38 -0
- package/esm/errors/undefined-where-value.error.mjs.map +1 -0
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +3 -2
- package/esm/query-builder/query-builder.d.mts.map +1 -1
- package/esm/query-builder/query-builder.mjs +23 -11
- package/esm/query-builder/query-builder.mjs.map +1 -1
- package/esm/utils/sanitize-filter.d.mts +21 -6
- package/esm/utils/sanitize-filter.d.mts.map +1 -1
- package/esm/utils/sanitize-filter.mjs +26 -6
- package/esm/utils/sanitize-filter.mjs.map +1 -1
- package/package.json +4 -4
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
|
-
*
|
|
3508
|
-
*
|
|
3509
|
-
*
|
|
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 } }`)
|
|
3524
|
-
* plain nested documents remain
|
|
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
|
|
8891
|
-
field
|
|
8892
|
-
operator
|
|
8893
|
-
value
|
|
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
|
|
8913
|
-
field
|
|
8914
|
-
operator
|
|
8915
|
-
value
|
|
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)
|
|
12154
|
-
field
|
|
12155
|
-
operator
|
|
12156
|
-
value
|
|
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 ??
|
|
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
|
-
|
|
18599
|
-
|
|
18600
|
-
|
|
18601
|
-
|
|
18602
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18639
|
-
|
|
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;
|