@warlock.js/cascade 5.22.1 → 5.23.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 (37) hide show
  1. package/CHANGELOG.md +28 -17
  2. package/cjs/index.cjs +119 -40
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/drivers/postgres/index.d.mts +2 -2
  5. package/esm/drivers/postgres/index.mjs +1 -1
  6. package/esm/drivers/postgres/naming.mjs +18 -0
  7. package/esm/drivers/postgres/naming.mjs.map +1 -0
  8. package/esm/drivers/postgres/postgres-dialect.d.mts +9 -1
  9. package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
  10. package/esm/drivers/postgres/postgres-dialect.mjs +10 -0
  11. package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
  12. package/esm/drivers/postgres/postgres-driver.d.mts +7 -1
  13. package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
  14. package/esm/drivers/postgres/postgres-driver.mjs +46 -16
  15. package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
  16. package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
  17. package/esm/drivers/postgres/postgres-query-builder.mjs +22 -15
  18. package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
  19. package/esm/drivers/postgres/postgres-query-parser.d.mts +1 -0
  20. package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
  21. package/esm/drivers/postgres/postgres-query-parser.mjs +16 -10
  22. package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
  23. package/esm/drivers/postgres/types.d.mts +2 -1
  24. package/esm/drivers/postgres/types.d.mts.map +1 -1
  25. package/esm/index.d.mts +3 -3
  26. package/esm/index.mjs +2 -2
  27. package/esm/model/model.d.mts +5 -1
  28. package/esm/model/model.d.mts.map +1 -1
  29. package/esm/model/model.mjs +6 -0
  30. package/esm/model/model.mjs.map +1 -1
  31. package/esm/utils/connect-to-database.d.mts.map +1 -1
  32. package/esm/utils/connect-to-database.mjs +6 -0
  33. package/esm/utils/connect-to-database.mjs.map +1 -1
  34. package/esm/validation/plugins/database-rules-plugin.d.mts +12 -4
  35. package/esm/validation/plugins/database-rules-plugin.d.mts.map +1 -1
  36. package/esm/validation/plugins/database-rules-plugin.mjs.map +1 -1
  37. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -4,22 +4,34 @@ All notable changes to `@warlock.js/cascade` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
- ## 5.22.1 - 2026-09-25
8
-
9
- ### Changed
10
-
11
- - Lockstep patch release; package APIs are unchanged.
12
-
13
- ## 5.22.0 - 2026-09-25
14
-
15
- ### Added
16
-
17
- - PostgreSQL TTL indexes register purge jobs and persist retention rules in index comments so jobs restore after reconnect.
18
-
19
- ### Fixed
20
-
21
- - Post-commit sync fan-out retries transient failures without failing committed writes.
22
-
7
+ ## 5.23.0 - 2026-09-25
8
+
9
+ ### Added
10
+
11
+ - Postgres `naming: "snake_case"` (opt-in, default `"preserve"`): camelCase model keys map to snake_case columns in inserts, updates, `where`, `orderBy`, `select`, `groupBy` and joins, and rows come back camelCase. Table names and raw SQL are left as written.
12
+ - Static `Model.whereRaw(expression, bindings?)`, matching the other static query entry points (`Model.query().whereRaw(...)` still works).
13
+
14
+ ### Fixed
15
+
16
+ - Postgres `bigint`/`int8` columns (and int8 arrays) come back as numbers when they fit in a safe integer, so a bigint id passes `v.number()`; larger values stay strings. The parser is installed on Cascade's own pool, not on the global `pg.types`.
17
+ - `unique()` / `exists()` on `StringValidator` and `NumberValidator` now return `this`, so `Infer<>` keeps the concrete output type (was `unknown`).
18
+
19
+ ## 5.22.1 - 2026-09-25
20
+
21
+ ### Changed
22
+
23
+ - Lockstep patch release; package APIs are unchanged.
24
+
25
+ ## 5.22.0 - 2026-09-25
26
+
27
+ ### Added
28
+
29
+ - PostgreSQL TTL indexes register purge jobs and persist retention rules in index comments so jobs restore after reconnect.
30
+
31
+ ### Fixed
32
+
33
+ - Post-commit sync fan-out retries transient failures without failing committed writes.
34
+
23
35
  ## 5.21.0 - 2026-09-25
24
36
 
25
37
  ### Added
@@ -45,7 +57,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
45
57
  - Migrations: `.index()`/`.unique()`/`.vectorIndex()` inside `Migration.alter` `modify` now create the index, and `randomIncrement` takes effect.
46
58
  - Mongo: replica-set detection runs `hello` once per driver.
47
59
 
48
-
49
60
  ### Changed
50
61
 
51
62
  - Lockstep release maintenance and dependency refresh.
package/cjs/index.cjs CHANGED
@@ -7189,6 +7189,12 @@ var Model = class Model {
7189
7189
  return this.query().where(...args);
7190
7190
  }
7191
7191
  /**
7192
+ * Use a raw where clause directly.
7193
+ */
7194
+ static whereRaw(expression, bindings) {
7195
+ return this.query().whereRaw(expression, bindings);
7196
+ }
7197
+ /**
7192
7198
  * Count the number of records in the table
7193
7199
  * @param filter - The filter to apply to the query
7194
7200
  */
@@ -15164,6 +15170,22 @@ var PostgresBlueprint = class {
15164
15170
  }
15165
15171
  };
15166
15172
 
15173
+ //#endregion
15174
+ //#region ../cascade/src/drivers/postgres/naming.ts
15175
+ /** Convert a JavaScript camelCase key to a PostgreSQL snake_case column name. */
15176
+ function camelToSnake(value) {
15177
+ if (value === "*" || value.includes("_")) return value;
15178
+ return (value.match(/[A-Z]+(?=[A-Z][a-z]|\d|$)|[A-Z]?[a-z]+|\d+/g) ?? [value]).map((part) => part.toLowerCase()).join("_");
15179
+ }
15180
+ /** Convert a PostgreSQL snake_case column name to a JavaScript camelCase key. */
15181
+ function snakeToCamel(value) {
15182
+ return value.replace(/_([a-z0-9])/g, (_match, character) => character.toUpperCase());
15183
+ }
15184
+ /** Convert top-level database row keys without rewriting nested JSON values. */
15185
+ function snakeKeysToCamel(row) {
15186
+ return Object.fromEntries(Object.entries(row).map(([key, value]) => [snakeToCamel(key), value]));
15187
+ }
15188
+
15167
15189
  //#endregion
15168
15190
  //#region ../cascade/src/drivers/postgres/postgres-dialect.ts
15169
15191
  /**
@@ -15186,6 +15208,10 @@ var PostgresBlueprint = class {
15186
15208
  * ```
15187
15209
  */
15188
15210
  var PostgresDialect = class {
15211
+ naming;
15212
+ constructor(options = {}) {
15213
+ this.naming = options.naming ?? "preserve";
15214
+ }
15189
15215
  /**
15190
15216
  * Dialect name identifier.
15191
15217
  */
@@ -15219,6 +15245,10 @@ var PostgresDialect = class {
15219
15245
  * @returns The quoted identifier (e.g., '"user"')
15220
15246
  */
15221
15247
  quoteIdentifier(identifier) {
15248
+ return identifier.split(".").map((part) => `"${(this.naming === "snake_case" ? camelToSnake(part) : part).replace(/"/g, "\"\"")}"`).join(".");
15249
+ }
15250
+ /** Quote a table identifier without applying the column naming policy. */
15251
+ quoteTableIdentifier(identifier) {
15222
15252
  return identifier.split(".").map((part) => `"${part.replace(/"/g, "\"\"")}"`).join(".");
15223
15253
  }
15224
15254
  /**
@@ -16741,7 +16771,7 @@ var PostgresQueryParser = class PostgresQueryParser {
16741
16771
  buildSql() {
16742
16772
  const parts = [];
16743
16773
  parts.push(this.buildSelectClause());
16744
- const quotedTable = this.dialect.quoteIdentifier(this.table);
16774
+ const quotedTable = this.quoteTable(this.table);
16745
16775
  const fromClause = this.alias ? `FROM ${quotedTable} AS ${this.dialect.quoteIdentifier(this.alias)}` : `FROM ${quotedTable}`;
16746
16776
  parts.push(fromClause);
16747
16777
  if (this.joinClauses.length > 0) parts.push(this.joinClauses.join(" "));
@@ -16764,7 +16794,7 @@ var PostgresQueryParser = class PostgresQueryParser {
16764
16794
  */
16765
16795
  buildSelectClause() {
16766
16796
  const distinct = this.isDistinct ? "DISTINCT " : "";
16767
- if (this.selectColumns.length === 0 && this.selectRaw.length === 0) return this.hasJoins ? `SELECT ${distinct}${this.dialect.quoteIdentifier(this.table)}.*` : `SELECT ${distinct}*`;
16797
+ if (this.selectColumns.length === 0 && this.selectRaw.length === 0) return this.hasJoins ? `SELECT ${distinct}${this.quoteTable(this.table)}.*` : `SELECT ${distinct}*`;
16768
16798
  const columns = [];
16769
16799
  for (const col of this.selectColumns) if (!this.deselectColumns.includes(col)) columns.push(this.parseColumnIdentifier(col, this.table, this.alias));
16770
16800
  columns.push(...this.selectRaw);
@@ -17057,16 +17087,16 @@ var PostgresQueryParser = class PostgresQueryParser {
17057
17087
  const relationType = data.type;
17058
17088
  const constraintOps = data.constraintOps;
17059
17089
  const quotedAlias = this.dialect.quoteIdentifier(alias);
17060
- const quotedTable = this.dialect.quoteIdentifier(this.table);
17090
+ const quotedTable = this.quoteTable(this.table);
17061
17091
  if (!(this.selectColumns.length > 0) && !this.selectRaw.includes(`${quotedTable}.*`)) this.selectRaw.unshift(`${quotedTable}.*`);
17062
17092
  if (relationType === "hasMany") {
17063
17093
  const relatedTable = data.table;
17064
17094
  const foreignKey = data.foreignKey;
17065
17095
  const localKey = data.localKey;
17066
- const quotedRelatedTable = this.dialect.quoteIdentifier(relatedTable);
17096
+ const quotedRelatedTable = this.quoteTable(relatedTable);
17067
17097
  const quotedForeignKey = this.dialect.quoteIdentifier(foreignKey);
17068
17098
  const quotedLocalKey = this.dialect.quoteIdentifier(localKey);
17069
- const quotedMainTable = this.dialect.quoteIdentifier(this.table);
17099
+ const quotedMainTable = this.quoteTable(this.table);
17070
17100
  let innerSelect;
17071
17101
  if (select && select.length > 0) innerSelect = `json_agg(json_build_object(${select.map((col) => `'${escapeSqlLiteral(col)}', a.${this.dialect.quoteIdentifier(col)}`).join(", ")}))`;
17072
17102
  else innerSelect = `json_agg(row_to_json(a.*))`;
@@ -17078,6 +17108,7 @@ var PostgresQueryParser = class PostgresQueryParser {
17078
17108
  const subParser = new PostgresQueryParser({
17079
17109
  table: relatedTable,
17080
17110
  alias: "a",
17111
+ dialect: this.dialect,
17081
17112
  operations: constraintOps
17082
17113
  });
17083
17114
  subParser.parse();
@@ -17095,6 +17126,7 @@ var PostgresQueryParser = class PostgresQueryParser {
17095
17126
  const subParser = new PostgresQueryParser({
17096
17127
  table: data.table ?? alias,
17097
17128
  alias,
17129
+ dialect: this.dialect,
17098
17130
  operations: constraintOps
17099
17131
  });
17100
17132
  subParser.parse();
@@ -17122,7 +17154,7 @@ var PostgresQueryParser = class PostgresQueryParser {
17122
17154
  const foreignField = "foreignField" in options ? options.foreignField : "";
17123
17155
  const alias = "alias" in options ? options.alias : void 0;
17124
17156
  const constraintOps = data.constraintOps ?? [];
17125
- const quotedTable = this.dialect.quoteIdentifier(joinTable);
17157
+ const quotedTable = this.quoteTable(joinTable);
17126
17158
  const tableRef = alias ? `${quotedTable} AS ${this.dialect.quoteIdentifier(alias)}` : quotedTable;
17127
17159
  const tableAlias = alias ?? joinTable;
17128
17160
  this.joinedTables.add(joinTable);
@@ -17134,6 +17166,7 @@ var PostgresQueryParser = class PostgresQueryParser {
17134
17166
  const subParser = new PostgresQueryParser({
17135
17167
  table: joinTable,
17136
17168
  alias: tableAlias,
17169
+ dialect: this.dialect,
17137
17170
  operations: whereOps
17138
17171
  });
17139
17172
  subParser.parse();
@@ -17175,11 +17208,11 @@ var PostgresQueryParser = class PostgresQueryParser {
17175
17208
  const effectiveTable = tableAlias ?? defaultTable;
17176
17209
  const parts = field.split(".");
17177
17210
  let expression;
17178
- if (parts.length === 1) expression = `${this.dialect.quoteIdentifier(effectiveTable)}.${this.dialect.quoteIdentifier(field)}`;
17211
+ if (parts.length === 1) expression = `${this.quoteTable(effectiveTable)}.${this.dialect.quoteIdentifier(field)}`;
17179
17212
  else if (parts.length === 2) {
17180
17213
  const [first, second] = parts;
17181
17214
  if (first === void 0 || second === void 0) throw new Error(`Cannot parse field path "${field}": expected two non-empty segments.`);
17182
- if (this.isTableReference(first)) expression = `${this.dialect.quoteIdentifier(first)}.${this.dialect.quoteIdentifier(second)}`;
17215
+ if (this.isTableReference(first)) expression = `${this.quoteTable(first)}.${this.dialect.quoteIdentifier(second)}`;
17183
17216
  else expression = this.buildJsonbPath(effectiveTable, first, [second]);
17184
17217
  } else {
17185
17218
  const [first, second, ...rest] = parts;
@@ -17201,6 +17234,9 @@ var PostgresQueryParser = class PostgresQueryParser {
17201
17234
  if (this.joinedTables.has(name)) return true;
17202
17235
  return false;
17203
17236
  }
17237
+ quoteTable(table) {
17238
+ return this.dialect instanceof PostgresDialect ? this.dialect.quoteTableIdentifier(table) : this.dialect.quoteIdentifier(table);
17239
+ }
17204
17240
  /**
17205
17241
  * Build a JSONB path expression.
17206
17242
  *
@@ -17221,7 +17257,7 @@ var PostgresQueryParser = class PostgresQueryParser {
17221
17257
  * // Returns: "posts"."createdBy"->'address'->>'city'
17222
17258
  */
17223
17259
  buildJsonbPath(table, column, path) {
17224
- const quotedTable = this.dialect.quoteIdentifier(table);
17260
+ const quotedTable = this.quoteTable(table);
17225
17261
  const quotedColumn = this.dialect.quoteIdentifier(column);
17226
17262
  if (path.length === 0) return `${quotedTable}.${quotedColumn}`;
17227
17263
  let expression = `${quotedTable}.${quotedColumn}`;
@@ -17236,7 +17272,7 @@ var PostgresQueryParser = class PostgresQueryParser {
17236
17272
  */
17237
17273
  processCrossJoin(data) {
17238
17274
  const table = data.table;
17239
- const quotedTable = this.dialect.quoteIdentifier(table);
17275
+ const quotedTable = this.quoteTable(table);
17240
17276
  this.joinClauses.push(`CROSS JOIN ${quotedTable}`);
17241
17277
  }
17242
17278
  /**
@@ -17585,7 +17621,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
17585
17621
  similarTo(column, embedding, alias = "score") {
17586
17622
  const literal = `[${embedding.join(",")}]`;
17587
17623
  const quotedCol = this.driver.dialect.quoteIdentifier(column);
17588
- const quotedTable = this.driver.dialect.quoteIdentifier(this.table);
17624
+ const quotedTable = this.driver.dialect.quoteTableIdentifier(this.table);
17589
17625
  this.addOperation("selectRaw", {
17590
17626
  expression: `${quotedTable}.*`,
17591
17627
  bindings: []
@@ -17679,7 +17715,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
17679
17715
  }
17680
17716
  /** Full-text search across the whole row, plus optional equality filters. */
17681
17717
  textSearch(query, filters) {
17682
- const table = this.driver.dialect.quoteIdentifier(this.table);
17718
+ const table = this.driver.dialect.quoteTableIdentifier(this.table);
17683
17719
  this.addOperation("whereRaw", {
17684
17720
  expression: `to_tsvector('english', ${table}::text) @@ plainto_tsquery('english', ?)`,
17685
17721
  bindings: [query]
@@ -17834,6 +17870,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
17834
17870
  if (this.fetchingCallback) await this.fetchingCallback(this);
17835
17871
  const { query = "", bindings = [] } = new PostgresQueryParser({
17836
17872
  table: this.table,
17873
+ dialect: this.driver.dialect,
17837
17874
  operations: toParserOps(this.operations)
17838
17875
  }).parse();
17839
17876
  try {
@@ -17923,6 +17960,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
17923
17960
  }]);
17924
17961
  const inner = new PostgresQueryParser({
17925
17962
  table: this.table,
17963
+ dialect: this.driver.dialect,
17926
17964
  operations: innerOps
17927
17965
  }).parse();
17928
17966
  query = `SELECT COUNT(*) AS "count" FROM (${inner.query}) AS "__count"`;
@@ -17934,6 +17972,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
17934
17972
  }]);
17935
17973
  const parsed = new PostgresQueryParser({
17936
17974
  table: this.table,
17975
+ dialect: this.driver.dialect,
17937
17976
  operations: countOps
17938
17977
  }).parse();
17939
17978
  query = parsed.query ?? "";
@@ -18001,6 +18040,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18001
18040
  ]);
18002
18041
  const { query = "", bindings = [] } = new PostgresQueryParser({
18003
18042
  table: this.table,
18043
+ dialect: this.driver.dialect,
18004
18044
  operations: probeOps
18005
18045
  }).parse();
18006
18046
  return (await this.driver.query(query, bindings)).rows.length > 0;
@@ -18024,7 +18064,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18024
18064
  this.applyPendingScopes();
18025
18065
  const { sql: filterSql, params: filterParams } = this.buildFilter();
18026
18066
  const amountPlaceholder = `$${filterParams.length + 1}`;
18027
- const updateSql = `UPDATE ${this.driver.dialect.quoteIdentifier(this.table)} SET ${this.driver.dialect.quoteIdentifier(field)} = COALESCE(${this.driver.dialect.quoteIdentifier(field)}, 0) + ${amountPlaceholder} WHERE ctid IN (SELECT ctid FROM ${this.driver.dialect.quoteIdentifier(this.table)} ${filterSql} LIMIT 1 FOR UPDATE SKIP LOCKED) RETURNING ${this.driver.dialect.quoteIdentifier(field)}`;
18067
+ const updateSql = `UPDATE ${this.driver.dialect.quoteTableIdentifier(this.table)} SET ${this.driver.dialect.quoteIdentifier(field)} = COALESCE(${this.driver.dialect.quoteIdentifier(field)}, 0) + ${amountPlaceholder} WHERE ctid IN (SELECT ctid FROM ${this.driver.dialect.quoteTableIdentifier(this.table)} ${filterSql} LIMIT 1 FOR UPDATE SKIP LOCKED) RETURNING ${this.driver.dialect.quoteIdentifier(field)}`;
18028
18068
  return (await this.driver.query(updateSql, [...filterParams, amount])).rows[0]?.[field] ?? 0;
18029
18069
  }
18030
18070
  /** Decrement a numeric field. Returns new value. */
@@ -18036,7 +18076,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18036
18076
  this.applyPendingScopes();
18037
18077
  const { sql: filterSql, params: filterParams } = this.buildFilter();
18038
18078
  const amountPlaceholder = `$${filterParams.length + 1}`;
18039
- const updateSql = `UPDATE ${this.driver.dialect.quoteIdentifier(this.table)} SET ${this.driver.dialect.quoteIdentifier(field)} = COALESCE(${this.driver.dialect.quoteIdentifier(field)}, 0) + ${amountPlaceholder}` + (filterSql ? ` WHERE ${filterSql.replace("WHERE ", "")}` : "");
18079
+ const updateSql = `UPDATE ${this.driver.dialect.quoteTableIdentifier(this.table)} SET ${this.driver.dialect.quoteIdentifier(field)} = COALESCE(${this.driver.dialect.quoteIdentifier(field)}, 0) + ${amountPlaceholder}` + (filterSql ? ` WHERE ${filterSql.replace("WHERE ", "")}` : "");
18040
18080
  return (await this.driver.query(updateSql, [...filterParams, amount])).rowCount ?? 0;
18041
18081
  }
18042
18082
  /** Decrement a field for all matching rows. Returns affected row count. */
@@ -18133,7 +18173,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18133
18173
  async delete() {
18134
18174
  this.applyPendingScopes();
18135
18175
  const { sql, params } = this.buildFilter();
18136
- const deleteSql = `DELETE FROM ${this.driver.dialect.quoteIdentifier(this.table)} ${sql}`;
18176
+ const deleteSql = `DELETE FROM ${this.driver.dialect.quoteTableIdentifier(this.table)} ${sql}`;
18137
18177
  return (await this.driver.query(deleteSql, params)).rowCount ?? 0;
18138
18178
  }
18139
18179
  /**
@@ -18145,7 +18185,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18145
18185
  async deleteOne() {
18146
18186
  this.applyPendingScopes();
18147
18187
  const { sql: filterSql, params } = this.buildFilter();
18148
- const quotedTable = this.driver.dialect.quoteIdentifier(this.table);
18188
+ const quotedTable = this.driver.dialect.quoteTableIdentifier(this.table);
18149
18189
  const deleteSql = `DELETE FROM ${quotedTable} WHERE ctid IN (SELECT ctid FROM ${quotedTable} ${filterSql} LIMIT 1 FOR UPDATE SKIP LOCKED)`;
18150
18190
  return (await this.driver.query(deleteSql, params)).rowCount ?? 0;
18151
18191
  }
@@ -18165,7 +18205,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18165
18205
  setParams.push(value);
18166
18206
  }
18167
18207
  if (setClauses.length === 0) throw new Error("No update operations specified");
18168
- const updateSql = `UPDATE ${this.driver.dialect.quoteIdentifier(this.table)} SET ${setClauses.join(", ")}` + (filterSql ? ` ${filterSql}` : "");
18208
+ const updateSql = `UPDATE ${this.driver.dialect.quoteTableIdentifier(this.table)} SET ${setClauses.join(", ")}` + (filterSql ? ` ${filterSql}` : "");
18169
18209
  return (await this.driver.query(updateSql, [...filterParams, ...setParams])).rowCount ?? 0;
18170
18210
  }
18171
18211
  /**
@@ -18177,7 +18217,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18177
18217
  if (fields.length === 0) throw new Error("No update operations specified");
18178
18218
  const { sql: filterSql, params: filterParams } = this.buildFilter();
18179
18219
  const setClauses = fields.map((field) => `${this.driver.dialect.quoteIdentifier(field)} = NULL`);
18180
- const updateSql = `UPDATE ${this.driver.dialect.quoteIdentifier(this.table)} SET ${setClauses.join(", ")}` + (filterSql ? ` ${filterSql}` : "");
18220
+ const updateSql = `UPDATE ${this.driver.dialect.quoteTableIdentifier(this.table)} SET ${setClauses.join(", ")}` + (filterSql ? ` ${filterSql}` : "");
18181
18221
  return (await this.driver.query(updateSql, filterParams)).rowCount ?? 0;
18182
18222
  }
18183
18223
  /**
@@ -18197,6 +18237,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18197
18237
  this.applyGroupByAggregates();
18198
18238
  return new PostgresQueryParser({
18199
18239
  table: this.table,
18240
+ dialect: this.driver.dialect,
18200
18241
  operations: toParserOps(this.operations)
18201
18242
  }).parse();
18202
18243
  }
@@ -18348,8 +18389,8 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18348
18389
  */
18349
18390
  buildHasSubquery(opType, relationName, definition, RelatedModel, constraintOps, operator, count) {
18350
18391
  const dialect = this.driver.dialect;
18351
- const quotedSelfTable = dialect.quoteIdentifier(this.table);
18352
- const quotedRelatedTable = dialect.quoteIdentifier(RelatedModel.table);
18392
+ const quotedSelfTable = dialect.quoteTableIdentifier(this.table);
18393
+ const quotedRelatedTable = dialect.quoteTableIdentifier(RelatedModel.table);
18353
18394
  const relationType = definition.type;
18354
18395
  const selfModel = this.modelClass;
18355
18396
  const conventions = this.dataSource?.relationDefaults;
@@ -18368,7 +18409,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18368
18409
  joinCondition = `${quotedRelatedTable}.${dialect.quoteIdentifier(ownerKey)} = ${quotedSelfTable}.${dialect.quoteIdentifier(foreignKey)}`;
18369
18410
  } else if (relationType === "belongsToMany") {
18370
18411
  const pivotTableName = definition.pivot ?? inferPivotTable(selfModel?.name ?? "Model", RelatedModel.name, conventions);
18371
- const quotedPivot = dialect.quoteIdentifier(pivotTableName);
18412
+ const quotedPivot = dialect.quoteTableIdentifier(pivotTableName);
18372
18413
  const pivotLocalCol = definition.localKey ?? inferPivotKey(selfModel?.name ?? "Model", conventions);
18373
18414
  const pivotForeignCol = definition.foreignKey ?? inferPivotKey(RelatedModel.name, conventions);
18374
18415
  const selfPk = definition.pivotLocalKey ?? selfModel?.primaryKey ?? "id";
@@ -18426,7 +18467,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18426
18467
  */
18427
18468
  ensureMainColumnsForCount() {
18428
18469
  if (this.operations.some((op) => op.type === "select" || op.type === "selectRaw" || op.type === "selectRelatedColumns")) return;
18429
- const quotedTable = this.driver.dialect.quoteIdentifier(this.table);
18470
+ const quotedTable = this.driver.dialect.quoteTableIdentifier(this.table);
18430
18471
  this.addOperation("selectRaw", {
18431
18472
  expression: `${quotedTable}.*`,
18432
18473
  bindings: []
@@ -18441,8 +18482,8 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18441
18482
  buildCountSubquery(alias, relationName, definition, RelatedModel, constraintOps) {
18442
18483
  const dialect = this.driver.dialect;
18443
18484
  const quotedAlias = dialect.quoteIdentifier(alias);
18444
- const quotedSelfTable = dialect.quoteIdentifier(this.table);
18445
- const quotedRelatedTable = dialect.quoteIdentifier(RelatedModel.table);
18485
+ const quotedSelfTable = dialect.quoteTableIdentifier(this.table);
18486
+ const quotedRelatedTable = dialect.quoteTableIdentifier(RelatedModel.table);
18446
18487
  const relationType = definition.type;
18447
18488
  const selfModel = this.modelClass;
18448
18489
  const relatedMeta = RelatedModel;
@@ -18469,7 +18510,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18469
18510
  }
18470
18511
  if (relationType === "belongsToMany") {
18471
18512
  const pivotTableName = definition.pivot ?? inferPivotTable(selfModel?.name ?? "Model", relatedMeta.name, conventions);
18472
- const quotedPivot = dialect.quoteIdentifier(pivotTableName);
18513
+ const quotedPivot = dialect.quoteTableIdentifier(pivotTableName);
18473
18514
  const pivotLocalCol = definition.localKey ?? inferPivotKey(selfModel?.name ?? "Model", conventions);
18474
18515
  const pivotForeignCol = definition.foreignKey ?? inferPivotKey(relatedMeta.name, conventions);
18475
18516
  const selfPk = definition.pivotLocalKey ?? selfModel?.primaryKey ?? "id";
@@ -18508,6 +18549,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18508
18549
  };
18509
18550
  const { query = "", bindings = [] } = new PostgresQueryParser({
18510
18551
  table: relatedTable,
18552
+ dialect: this.driver.dialect,
18511
18553
  operations: toParserOps(whereOps)
18512
18554
  }).parse();
18513
18555
  const match = query.match(/WHERE\s+(.+)$/);
@@ -18650,6 +18692,7 @@ var PostgresQueryBuilder = class PostgresQueryBuilder extends QueryBuilder {
18650
18692
  };
18651
18693
  const { query = "", bindings = [] } = new PostgresQueryParser({
18652
18694
  table: this.table,
18695
+ dialect: this.driver.dialect,
18653
18696
  operations: toParserOps(whereOps)
18654
18697
  }).parse();
18655
18698
  const whereMatch = query.match(/WHERE .+$/);
@@ -18997,6 +19040,34 @@ function assertPostgresUpdate(update, options) {
18997
19040
  *
18998
19041
  * @module cascade/drivers/postgres
18999
19042
  */
19043
+ const POSTGRES_INT8_OID = 20;
19044
+ const POSTGRES_INT8_ARRAY_OID = 1016;
19045
+ /**
19046
+ * Convert a PostgreSQL int8 result to a number only when it can be represented
19047
+ * exactly. PostgreSQL sends int8 values as strings by default to avoid silently
19048
+ * losing precision above JavaScript's safe-integer limit.
19049
+ */
19050
+ function parsePostgresInt8(value) {
19051
+ if (value === null) return null;
19052
+ const numberValue = Number(value);
19053
+ return Number.isSafeInteger(numberValue) ? numberValue : value;
19054
+ }
19055
+ /**
19056
+ * Build pool-local type overrides without changing pg's process-wide registry.
19057
+ */
19058
+ function createPostgresTypes(types) {
19059
+ return { getTypeParser(oid, format) {
19060
+ if (oid === POSTGRES_INT8_OID) return parsePostgresInt8;
19061
+ if (oid === POSTGRES_INT8_ARRAY_OID) {
19062
+ const parseArray = types.getTypeParser(oid, format);
19063
+ return (value) => {
19064
+ const array = parseArray(value);
19065
+ return Array.isArray(array) ? array.map(parsePostgresInt8) : array;
19066
+ };
19067
+ }
19068
+ return types.getTypeParser(oid, format);
19069
+ } };
19070
+ }
19000
19071
  /**
19001
19072
  * Build pg's `PoolConfig` from cascade's `PostgresPoolConfig`, coercing the
19002
19073
  * string-typed connection fields.
@@ -19097,7 +19168,7 @@ var PostgresDriver = class {
19097
19168
  /**
19098
19169
  * SQL dialect for PostgreSQL-specific syntax.
19099
19170
  */
19100
- dialect = new PostgresDialect();
19171
+ dialect;
19101
19172
  /**
19102
19173
  * PostgreSQL driver model defaults.
19103
19174
  *
@@ -19168,6 +19239,7 @@ var PostgresDriver = class {
19168
19239
  */
19169
19240
  constructor(config) {
19170
19241
  this.config = config;
19242
+ this.dialect = new PostgresDialect({ naming: config.naming });
19171
19243
  this._nativeArrayColumns = new Set(config.nativeArrayColumns ?? []);
19172
19244
  }
19173
19245
  /**
@@ -19209,6 +19281,7 @@ var PostgresDriver = class {
19209
19281
  const pg = await loadPg();
19210
19282
  try {
19211
19283
  const poolConfig = buildPostgresPoolConfig(this.config);
19284
+ poolConfig.types = createPostgresTypes(pg.types);
19212
19285
  _warlock_js_logger.log.info("database.postgres", "connection", `Connecting to database ${_mongez_copper.colors.bold(_mongez_copper.colors.yellowBright(poolConfig.database ?? ""))}`);
19213
19286
  this._pool = new pg.Pool(poolConfig);
19214
19287
  this._pool.on("error", (error) => {
@@ -19390,12 +19463,11 @@ var PostgresDriver = class {
19390
19463
  */
19391
19464
  deserialize(data) {
19392
19465
  const columnTypes = this.rowColumnTypes.get(data);
19393
- if (!columnTypes) return data;
19394
- for (const [key, value] of Object.entries(data)) {
19466
+ if (columnTypes) for (const [key, value] of Object.entries(data)) {
19395
19467
  if (typeof value !== "string") continue;
19396
19468
  data[key] = convertPostgresValue(value, columnTypes.get(key));
19397
19469
  }
19398
- return data;
19470
+ return this.config.naming === "snake_case" ? snakeKeysToCamel(data) : data;
19399
19471
  }
19400
19472
  /**
19401
19473
  * Insert a single row into a table.
@@ -19418,7 +19490,7 @@ var PostgresDriver = class {
19418
19490
  if (columns.length === 0) throw new Error("Cannot insert empty document");
19419
19491
  const quotedColumns = columns.map((c) => this.dialect.quoteIdentifier(c)).join(", ");
19420
19492
  const placeholders = columns.map((_, i) => this.dialect.placeholder(i + 1)).join(", ");
19421
- const sql = `INSERT INTO ${this.dialect.quoteIdentifier(table)} (${quotedColumns}) VALUES (${placeholders}) RETURNING *`;
19493
+ const sql = `INSERT INTO ${this.dialect.quoteTableIdentifier(table)} (${quotedColumns}) VALUES (${placeholders}) RETURNING *`;
19422
19494
  return { document: (await this.query(sql, values)).rows[0] };
19423
19495
  }
19424
19496
  /**
@@ -19440,7 +19512,7 @@ var PostgresDriver = class {
19440
19512
  }
19441
19513
  const columns = Array.from(allColumns);
19442
19514
  const quotedColumns = columns.map((c) => this.dialect.quoteIdentifier(c)).join(", ");
19443
- const quotedTable = this.dialect.quoteIdentifier(table);
19515
+ const quotedTable = this.dialect.quoteTableIdentifier(table);
19444
19516
  const valueSets = [];
19445
19517
  const params = [];
19446
19518
  let paramIndex = 1;
@@ -19512,7 +19584,7 @@ var PostgresDriver = class {
19512
19584
  const { sql, params } = this.buildUpdateQuery(table, filter, operations, 1);
19513
19585
  return (await this.query(`${sql} RETURNING *`, params)).rows[0] ?? null;
19514
19586
  }
19515
- const quotedTable = this.dialect.quoteIdentifier(table);
19587
+ const quotedTable = this.dialect.quoteTableIdentifier(table);
19516
19588
  const keyList = (qualifier) => primaryKey.map((column) => (qualifier ? `${qualifier}.` : "") + this.dialect.quoteIdentifier(column)).join(", ");
19517
19589
  const set = this.buildSetClauses(table, operations, 1, returnBefore ? quotedTable : void 0);
19518
19590
  if (set.clauses.length === 0) throw new Error("No update operations specified");
@@ -19558,7 +19630,7 @@ var PostgresDriver = class {
19558
19630
  for (const key of Object.keys(operations.$unset ?? {})) delete insertRow[key];
19559
19631
  const columns = Object.keys(insertRow).filter((key) => insertRow[key] !== void 0);
19560
19632
  const params = columns.map((key) => this.serializeValue(key, insertRow[key], table));
19561
- const quotedTable = this.dialect.quoteIdentifier(table);
19633
+ const quotedTable = this.dialect.quoteTableIdentifier(table);
19562
19634
  const quotedColumns = columns.map((column) => this.dialect.quoteIdentifier(column)).join(", ");
19563
19635
  const placeholders = columns.map((_, index) => this.dialect.placeholder(index + 1)).join(", ");
19564
19636
  const set = this.buildSetClauses(table, operations, columns.length + 1, quotedTable);
@@ -19626,7 +19698,7 @@ var PostgresDriver = class {
19626
19698
  const serialized = this.serialize(document, table);
19627
19699
  const columns = Object.keys(serialized);
19628
19700
  const values = Object.values(serialized);
19629
- const quotedTable = this.dialect.quoteIdentifier(table);
19701
+ const quotedTable = this.dialect.quoteTableIdentifier(table);
19630
19702
  const setClauses = columns.map((col, i) => `${this.dialect.quoteIdentifier(col)} = ${this.dialect.placeholder(i + 1)}`).join(", ");
19631
19703
  const { whereClause, whereParams } = this.buildWhereClause(filter, columns.length + 1);
19632
19704
  const sql = `UPDATE ${quotedTable} SET ${setClauses} ${whereClause} RETURNING *`;
@@ -19649,7 +19721,7 @@ var PostgresDriver = class {
19649
19721
  const columns = Object.keys(serialized);
19650
19722
  const values = Object.values(serialized);
19651
19723
  if (columns.length === 0) throw new Error("Cannot upsert empty document");
19652
- const quotedTable = this.dialect.quoteIdentifier(table);
19724
+ const quotedTable = this.dialect.quoteTableIdentifier(table);
19653
19725
  const quotedColumns = columns.map((c) => this.dialect.quoteIdentifier(c)).join(", ");
19654
19726
  const placeholders = columns.map((_, i) => this.dialect.placeholder(i + 1)).join(", ");
19655
19727
  const conflictColumns = options?.conflictColumns ?? Object.keys(filter);
@@ -19672,7 +19744,7 @@ var PostgresDriver = class {
19672
19744
  * @returns The deleted row or null
19673
19745
  */
19674
19746
  async findOneAndDelete(table, filter, _options) {
19675
- const quotedTable = this.dialect.quoteIdentifier(table);
19747
+ const quotedTable = this.dialect.quoteTableIdentifier(table);
19676
19748
  const { whereClause, whereParams } = this.buildWhereClause(filter, 1);
19677
19749
  const sql = `${this.singleRowTarget("DELETE FROM " + quotedTable, quotedTable, filter, whereClause)} RETURNING *`;
19678
19750
  const result = await this.query(sql, whereParams);
@@ -19687,7 +19759,7 @@ var PostgresDriver = class {
19687
19759
  * @returns Number of deleted rows (0 or 1)
19688
19760
  */
19689
19761
  async delete(table, filter, _options) {
19690
- const quotedTable = this.dialect.quoteIdentifier(table);
19762
+ const quotedTable = this.dialect.quoteTableIdentifier(table);
19691
19763
  const { whereClause, whereParams } = this.buildWhereClause(filter ?? {}, 1);
19692
19764
  const sql = this.singleRowTarget("DELETE FROM " + quotedTable, quotedTable, filter ?? {}, whereClause);
19693
19765
  return (await this.query(sql, whereParams)).rowCount ?? 0;
@@ -19701,7 +19773,7 @@ var PostgresDriver = class {
19701
19773
  * @returns Number of deleted rows
19702
19774
  */
19703
19775
  async deleteMany(table, filter, _options) {
19704
- const quotedTable = this.dialect.quoteIdentifier(table);
19776
+ const quotedTable = this.dialect.quoteTableIdentifier(table);
19705
19777
  const { whereClause, whereParams } = this.buildWhereClause(filter ?? {}, 1);
19706
19778
  const sql = `DELETE FROM ${quotedTable} ${whereClause}`;
19707
19779
  return (await this.query(sql, whereParams)).rowCount ?? 0;
@@ -19717,7 +19789,7 @@ var PostgresDriver = class {
19717
19789
  * @returns Number of deleted rows (always 0 for TRUNCATE)
19718
19790
  */
19719
19791
  async truncateTable(table, options) {
19720
- const quotedTable = this.dialect.quoteIdentifier(table);
19792
+ const quotedTable = this.dialect.quoteTableIdentifier(table);
19721
19793
  const cascadeClause = options?.cascade ? " CASCADE" : "";
19722
19794
  await this.query(`TRUNCATE TABLE ${quotedTable} RESTART IDENTITY${cascadeClause}`);
19723
19795
  return 0;
@@ -20174,7 +20246,7 @@ var PostgresDriver = class {
20174
20246
  const set = this.buildSetClauses(table, assertPostgresUpdate(update), 1);
20175
20247
  if (set.clauses.length === 0) throw new Error("No update operations specified");
20176
20248
  const params = [...set.params];
20177
- const quotedTable = this.dialect.quoteIdentifier(table);
20249
+ const quotedTable = this.dialect.quoteTableIdentifier(table);
20178
20250
  const { whereClause, whereParams } = this.buildWhereClause(filter, set.nextIndex);
20179
20251
  params.push(...whereParams);
20180
20252
  const head = `UPDATE ${quotedTable} SET ${set.clauses.join(", ")}`;
@@ -20380,6 +20452,7 @@ async function connectToDatabase(options) {
20380
20452
  user: options.username,
20381
20453
  password: options.password,
20382
20454
  logging: options.logging,
20455
+ ...pickPostgresDriverOptions(options.driverOptions),
20383
20456
  ...options.clientOptions
20384
20457
  });
20385
20458
  break;
@@ -20427,6 +20500,11 @@ function getDatabaseDriver() {
20427
20500
  async function transaction(fn, options) {
20428
20501
  return getDatabaseDriver().transaction(fn, options);
20429
20502
  }
20503
+ /** The Cascade-side Postgres driver options `driverOptions` may carry. */
20504
+ function pickPostgresDriverOptions(driverOptions) {
20505
+ const naming = driverOptions?.naming;
20506
+ return naming === void 0 ? {} : { naming };
20507
+ }
20430
20508
 
20431
20509
  //#endregion
20432
20510
  //#region ../cascade/src/utils/database-writer.utils.ts
@@ -25301,6 +25379,7 @@ exports.modelSync = modelSync;
25301
25379
  exports.objectCol = objectCol;
25302
25380
  exports.onceConnected = onceConnected;
25303
25381
  exports.onceDisconnected = onceDisconnected;
25382
+ exports.parsePostgresInt8 = parsePostgresInt8;
25304
25383
  exports.point = point;
25305
25384
  exports.polygon = polygon;
25306
25385
  exports.registerModelInRegistry = registerModelInRegistry;