@warlock.js/cascade 5.19.1 → 5.21.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 (194) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/cjs/index.cjs +2356 -1039
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/context/database-transaction-context.d.mts +10 -0
  5. package/esm/context/database-transaction-context.d.mts.map +1 -1
  6. package/esm/context/database-transaction-context.mjs +17 -0
  7. package/esm/context/database-transaction-context.mjs.map +1 -1
  8. package/esm/contracts/query-builder.contract.d.mts +30 -3
  9. package/esm/contracts/query-builder.contract.d.mts.map +1 -1
  10. package/esm/data-source/data-source-registry.d.mts +33 -1
  11. package/esm/data-source/data-source-registry.d.mts.map +1 -1
  12. package/esm/data-source/data-source-registry.mjs +75 -5
  13. package/esm/data-source/data-source-registry.mjs.map +1 -1
  14. package/esm/database-dirty-tracker.d.mts +5 -0
  15. package/esm/database-dirty-tracker.d.mts.map +1 -1
  16. package/esm/database-dirty-tracker.mjs +16 -0
  17. package/esm/database-dirty-tracker.mjs.map +1 -1
  18. package/esm/drivers/mongodb/mongodb-driver.d.mts +12 -0
  19. package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
  20. package/esm/drivers/mongodb/mongodb-driver.mjs +58 -19
  21. package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
  22. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -1
  23. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
  24. package/esm/drivers/mongodb/mongodb-migration-driver.mjs +15 -18
  25. package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
  26. package/esm/drivers/mongodb/mongodb-query-builder.d.mts +13 -3
  27. package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
  28. package/esm/drivers/mongodb/mongodb-query-builder.mjs +72 -19
  29. package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
  30. package/esm/drivers/mongodb/mongodb-query-parser.d.mts +6 -0
  31. package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
  32. package/esm/drivers/mongodb/mongodb-query-parser.mjs +128 -30
  33. package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
  34. package/esm/drivers/postgres/index.d.mts +2 -3
  35. package/esm/drivers/postgres/index.mjs +1 -2
  36. package/esm/drivers/postgres/postgres-blueprint.mjs +4 -4
  37. package/esm/drivers/postgres/postgres-blueprint.mjs.map +1 -1
  38. package/esm/drivers/postgres/postgres-dialect.d.mts +5 -1
  39. package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
  40. package/esm/drivers/postgres/postgres-dialect.mjs +38 -8
  41. package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
  42. package/esm/drivers/postgres/postgres-driver.d.mts +52 -29
  43. package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
  44. package/esm/drivers/postgres/postgres-driver.mjs +196 -58
  45. package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
  46. package/esm/drivers/postgres/postgres-migration-driver.d.mts +2 -1
  47. package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
  48. package/esm/drivers/postgres/postgres-migration-driver.mjs +8 -6
  49. package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
  50. package/esm/drivers/postgres/postgres-query-builder.d.mts +18 -7
  51. package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
  52. package/esm/drivers/postgres/postgres-query-builder.mjs +146 -31
  53. package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
  54. package/esm/drivers/postgres/postgres-query-parser.d.mts +23 -1
  55. package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
  56. package/esm/drivers/postgres/postgres-query-parser.mjs +160 -43
  57. package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
  58. package/esm/drivers/postgres/postgres-sql-serializer.mjs +3 -2
  59. package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
  60. package/esm/drivers/postgres/postgres-type-converters.mjs +57 -0
  61. package/esm/drivers/postgres/postgres-type-converters.mjs.map +1 -0
  62. package/esm/events/model-events.d.mts +1 -1
  63. package/esm/events/model-events.d.mts.map +1 -1
  64. package/esm/events/model-events.mjs.map +1 -1
  65. package/esm/index.d.mts +6 -6
  66. package/esm/index.mjs +5 -5
  67. package/esm/migration/column-builder.d.mts.map +1 -1
  68. package/esm/migration/column-builder.mjs +2 -2
  69. package/esm/migration/column-builder.mjs.map +1 -1
  70. package/esm/migration/index.d.mts +2 -2
  71. package/esm/migration/index.mjs +1 -1
  72. package/esm/migration/migration-order.mjs +5 -2
  73. package/esm/migration/migration-order.mjs.map +1 -1
  74. package/esm/migration/migration-runner.d.mts +50 -5
  75. package/esm/migration/migration-runner.d.mts.map +1 -1
  76. package/esm/migration/migration-runner.mjs +370 -147
  77. package/esm/migration/migration-runner.mjs.map +1 -1
  78. package/esm/migration/migration.d.mts +36 -3
  79. package/esm/migration/migration.d.mts.map +1 -1
  80. package/esm/migration/migration.mjs +62 -5
  81. package/esm/migration/migration.mjs.map +1 -1
  82. package/esm/model/methods/accessor-methods.mjs +1 -3
  83. package/esm/model/methods/accessor-methods.mjs.map +1 -1
  84. package/esm/model/methods/delete-methods.mjs +6 -2
  85. package/esm/model/methods/delete-methods.mjs.map +1 -1
  86. package/esm/model/methods/hydration-methods.mjs +16 -2
  87. package/esm/model/methods/hydration-methods.mjs.map +1 -1
  88. package/esm/model/methods/instance-event-methods.mjs +2 -1
  89. package/esm/model/methods/instance-event-methods.mjs.map +1 -1
  90. package/esm/model/methods/meta-methods.mjs +53 -16
  91. package/esm/model/methods/meta-methods.mjs.map +1 -1
  92. package/esm/model/methods/query-methods.mjs +86 -15
  93. package/esm/model/methods/query-methods.mjs.map +1 -1
  94. package/esm/model/methods/static-event-methods.mjs +16 -1
  95. package/esm/model/methods/static-event-methods.mjs.map +1 -1
  96. package/esm/model/methods/write-methods.d.mts.map +1 -1
  97. package/esm/model/methods/write-methods.mjs +50 -50
  98. package/esm/model/methods/write-methods.mjs.map +1 -1
  99. package/esm/model/model.d.mts +16 -1
  100. package/esm/model/model.d.mts.map +1 -1
  101. package/esm/model/model.mjs +39 -5
  102. package/esm/model/model.mjs.map +1 -1
  103. package/esm/model/register-model.d.mts +6 -1
  104. package/esm/model/register-model.d.mts.map +1 -1
  105. package/esm/model/register-model.mjs +16 -1
  106. package/esm/model/register-model.mjs.map +1 -1
  107. package/esm/query-builder/query-builder.d.mts +21 -2
  108. package/esm/query-builder/query-builder.d.mts.map +1 -1
  109. package/esm/query-builder/query-builder.mjs +97 -20
  110. package/esm/query-builder/query-builder.mjs.map +1 -1
  111. package/esm/relations/key-conventions.mjs +13 -3
  112. package/esm/relations/key-conventions.mjs.map +1 -1
  113. package/esm/relations/pivot-operations.mjs +1 -1
  114. package/esm/relations/relation-loader.d.mts +15 -5
  115. package/esm/relations/relation-loader.d.mts.map +1 -1
  116. package/esm/relations/relation-loader.mjs +51 -16
  117. package/esm/relations/relation-loader.mjs.map +1 -1
  118. package/esm/remover/database-remover.d.mts.map +1 -1
  119. package/esm/remover/database-remover.mjs +21 -7
  120. package/esm/remover/database-remover.mjs.map +1 -1
  121. package/esm/restorer/database-restorer.d.mts +12 -2
  122. package/esm/restorer/database-restorer.d.mts.map +1 -1
  123. package/esm/restorer/database-restorer.mjs +43 -13
  124. package/esm/restorer/database-restorer.mjs.map +1 -1
  125. package/esm/sync/model-events.mjs +63 -3
  126. package/esm/sync/model-events.mjs.map +1 -1
  127. package/esm/sync/model-sync-operation.d.mts.map +1 -1
  128. package/esm/sync/model-sync-operation.mjs +3 -2
  129. package/esm/sync/model-sync-operation.mjs.map +1 -1
  130. package/esm/sync/sync-manager.d.mts +3 -42
  131. package/esm/sync/sync-manager.d.mts.map +1 -1
  132. package/esm/sync/sync-manager.mjs +37 -126
  133. package/esm/sync/sync-manager.mjs.map +1 -1
  134. package/esm/transactions/after-commit.d.mts +24 -0
  135. package/esm/transactions/after-commit.d.mts.map +1 -0
  136. package/esm/transactions/after-commit.mjs +47 -0
  137. package/esm/transactions/after-commit.mjs.map +1 -0
  138. package/esm/utils/connect-to-database.d.mts.map +1 -1
  139. package/esm/utils/connect-to-database.mjs +4 -4
  140. package/esm/utils/connect-to-database.mjs.map +1 -1
  141. package/esm/utils/database-writer.utils.d.mts +1 -1
  142. package/esm/utils/database-writer.utils.d.mts.map +1 -1
  143. package/esm/utils/database-writer.utils.mjs +1 -0
  144. package/esm/utils/database-writer.utils.mjs.map +1 -1
  145. package/esm/utils/define-model.d.mts.map +1 -1
  146. package/esm/utils/define-model.mjs +2 -10
  147. package/esm/utils/define-model.mjs.map +1 -1
  148. package/esm/utils/is-valid-date-value.mjs +3 -2
  149. package/esm/utils/is-valid-date-value.mjs.map +1 -1
  150. package/esm/utils/once-connected.d.mts.map +1 -1
  151. package/esm/utils/once-connected.mjs +12 -2
  152. package/esm/utils/once-connected.mjs.map +1 -1
  153. package/esm/validation/mutators/embed-mutator.mjs +19 -11
  154. package/esm/validation/mutators/embed-mutator.mjs.map +1 -1
  155. package/esm/validation/plugins/embed-validator-plugin.d.mts +2 -2
  156. package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
  157. package/esm/validation/plugins/embed-validator-plugin.mjs +8 -2
  158. package/esm/validation/plugins/embed-validator-plugin.mjs.map +1 -1
  159. package/esm/validation/rules/database-model-rule.mjs +6 -5
  160. package/esm/validation/rules/database-model-rule.mjs.map +1 -1
  161. package/esm/validation/rules/exists-rule.mjs +6 -4
  162. package/esm/validation/rules/exists-rule.mjs.map +1 -1
  163. package/esm/validation/rules/unique-rule.mjs +5 -2
  164. package/esm/validation/rules/unique-rule.mjs.map +1 -1
  165. package/esm/validation/validators/embed-validator.d.mts.map +1 -1
  166. package/esm/validation/validators/embed-validator.mjs +1 -1
  167. package/esm/validation/validators/embed-validator.mjs.map +1 -1
  168. package/esm/writer/database-writer.d.mts +27 -0
  169. package/esm/writer/database-writer.d.mts.map +1 -1
  170. package/esm/writer/database-writer.mjs +84 -17
  171. package/esm/writer/database-writer.mjs.map +1 -1
  172. package/llms-full.txt +2783 -2766
  173. package/llms.txt +15 -15
  174. package/package.json +6 -6
  175. package/skills/aggregate-data/SKILL.md +165 -165
  176. package/skills/alter-migration/SKILL.md +531 -531
  177. package/skills/configure-delete-strategy/SKILL.md +130 -130
  178. package/skills/define-model/SKILL.md +205 -205
  179. package/skills/define-relations/SKILL.md +171 -171
  180. package/skills/index.md +23 -0
  181. package/skills/manage-data-sources/SKILL.md +140 -140
  182. package/skills/manage-transactions/SKILL.md +208 -191
  183. package/skills/paginate-results/SKILL.md +122 -122
  184. package/skills/perform-atomic-ops/SKILL.md +156 -156
  185. package/skills/query-data/SKILL.md +213 -213
  186. package/skills/run-cascade-cli/SKILL.md +125 -125
  187. package/skills/search-by-vector/SKILL.md +127 -127
  188. package/skills/subscribe-to-model-events/SKILL.md +148 -148
  189. package/skills/track-changes/SKILL.md +127 -127
  190. package/skills/write-migration/SKILL.md +206 -206
  191. package/esm/drivers/postgres/postgres-sync-adapter.d.mts +0 -79
  192. package/esm/drivers/postgres/postgres-sync-adapter.d.mts.map +0 -1
  193. package/esm/drivers/postgres/postgres-sync-adapter.mjs +0 -162
  194. package/esm/drivers/postgres/postgres-sync-adapter.mjs.map +0 -1
@@ -1,6 +1,10 @@
1
- import { PostgresDialect } from "./postgres-dialect.mjs";
1
+ import { PostgresDialect, escapeSqlLiteral } from "./postgres-dialect.mjs";
2
2
 
3
3
  //#region ../cascade/src/drivers/postgres/postgres-query-parser.ts
4
+ /** Allowed `::cast` type names, e.g. `int`, `numeric(10,2)`, `text[]`. */
5
+ const SAFE_CAST_PATTERN = /^[a-z_][a-z0-9_ ]*(\(\d+(,\d+)?\))?(\[\])?$/i;
6
+ /** Allowed orderBy direction, with optional NULLS placement. */
7
+ const SAFE_DIRECTION_PATTERN = /^(asc|desc)(\s+nulls\s+(first|last))?$/i;
4
8
  /**
5
9
  * PostgreSQL Query Parser.
6
10
  *
@@ -188,12 +192,27 @@ var PostgresQueryParser = class PostgresQueryParser {
188
192
  case "whereNotIn":
189
193
  this.processWhereIn(data, true);
190
194
  break;
195
+ case "orWhereIn":
196
+ this.processWhereIn(data, false, "OR");
197
+ break;
198
+ case "orWhereNotIn":
199
+ this.processWhereIn(data, true, "OR");
200
+ break;
191
201
  case "whereNull":
192
202
  this.processWhereNull(data, false);
193
203
  break;
194
204
  case "whereNotNull":
195
205
  this.processWhereNull(data, true);
196
206
  break;
207
+ case "orWhereNull":
208
+ this.processWhereNull(data, false, "OR");
209
+ break;
210
+ case "orWhereNotNull":
211
+ this.processWhereNull(data, true, "OR");
212
+ break;
213
+ case "orWhereBetween":
214
+ this.processWhereBetween(data, false, "OR");
215
+ break;
197
216
  case "whereBetween":
198
217
  this.processWhereBetween(data, false);
199
218
  break;
@@ -221,6 +240,30 @@ var PostgresQueryParser = class PostgresQueryParser {
221
240
  case "whereFullText":
222
241
  this.processWhereFullText(data);
223
242
  break;
243
+ case "whereNot":
244
+ this.processNested(data.nested, "AND", true, type);
245
+ break;
246
+ case "orWhereNot":
247
+ this.processNested(data.nested, "OR", true, type);
248
+ break;
249
+ case "whereExists":
250
+ this.processNested(data.subquery, "AND", false, type);
251
+ break;
252
+ case "whereNotExists":
253
+ this.processNested(data.subquery, "AND", true, type);
254
+ break;
255
+ case "whereDate":
256
+ this.processWhereDate(data, "=");
257
+ break;
258
+ case "whereDateBefore":
259
+ this.processWhereDate(data, "<");
260
+ break;
261
+ case "whereDateAfter":
262
+ this.processWhereDate(data, ">");
263
+ break;
264
+ case "whereDateBetween":
265
+ this.processWhereDateBetween(data);
266
+ break;
224
267
  case "select":
225
268
  this.processSelect(data);
226
269
  break;
@@ -286,7 +329,9 @@ var PostgresQueryParser = class PostgresQueryParser {
286
329
  case "selectRelatedColumns":
287
330
  this.processSelectRelatedColumns(data);
288
331
  break;
289
- default: break;
332
+ case "cursor":
333
+ case "joinWith": break;
334
+ default: throw new Error(`PostgresQueryParser: unsupported operation type "${type}"`);
290
335
  }
291
336
  }
292
337
  /**
@@ -369,40 +414,40 @@ var PostgresQueryParser = class PostgresQueryParser {
369
414
  * single placeholder (between, in, like-variants, exists, etc.).
370
415
  */
371
416
  processWhere(data, boolean) {
417
+ if (data.nested !== void 0) return this.processNested(data.nested, boolean, false, "where");
372
418
  const field = data.field;
373
419
  const operator = data.operator ?? "=";
374
420
  const value = data.value;
421
+ const range = value;
422
+ const like = {
423
+ field,
424
+ pattern: value
425
+ };
375
426
  switch (operator) {
376
427
  case "between": return this.processWhereBetween({
377
428
  field,
378
- range: value
379
- }, false);
429
+ range
430
+ }, false, boolean);
380
431
  case "notBetween": return this.processWhereBetween({
381
432
  field,
382
- range: value
383
- }, true);
433
+ range
434
+ }, true, boolean);
384
435
  case "in": return this.processWhereIn({
385
436
  field,
386
437
  values: value
387
- }, false);
438
+ }, false, boolean);
388
439
  case "notIn": return this.processWhereIn({
389
440
  field,
390
441
  values: value
391
- }, true);
442
+ }, true, boolean);
392
443
  case "like":
393
- case "ilike":
444
+ case "ilike": return this.processWhereLike(like, false, boolean);
394
445
  case "startsWith":
395
- case "endsWith": return this.processWhereLike({
396
- field,
397
- pattern: value
398
- }, false);
399
- case "notLike":
400
- case "notStartsWith":
401
- case "notEndsWith": return this.processWhereLike({
402
- field,
403
- pattern: value
404
- }, true);
405
- case "exists": return this.addWhereClause(`EXISTS (${value})`, boolean);
446
+ case "endsWith": return this.processWhereLike(like, false, boolean, operator);
447
+ case "notLike": return this.processWhereLike(like, true, boolean);
448
+ case "notStartsWith": return this.processWhereLike(like, true, boolean, "startsWith");
449
+ case "notEndsWith": return this.processWhereLike(like, true, boolean, "endsWith");
450
+ case "exists": throw new Error("PostgresQueryParser: where(field, \"exists\", value) does not accept a raw SQL string; use a query builder or callback");
406
451
  }
407
452
  const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
408
453
  if (value === null) {
@@ -413,6 +458,42 @@ var PostgresQueryParser = class PostgresQueryParser {
413
458
  this.addWhereClause(`${quotedField} ${this.mapOperator(operator)} ${placeholder}`, boolean);
414
459
  }
415
460
  /**
461
+ * Process a nested group of operations as `(<group>)` / `NOT (<group>)`.
462
+ *
463
+ * The group is parsed by a sub-parser; its params are renumbered into this
464
+ * parser's namespace.
465
+ */
466
+ processNested(nested, boolean, negate, opType) {
467
+ const subParser = new PostgresQueryParser({
468
+ table: this.table,
469
+ alias: this.alias,
470
+ dialect: this.dialect,
471
+ operations: nested ?? []
472
+ });
473
+ subParser.parse();
474
+ if (subParser.whereClauses.length === 0) throw new Error(`PostgresQueryParser: "${opType}" received an empty condition group`);
475
+ const group = `(${this.absorbSubParserParams(subParser)(subParser.whereClauses.join(" "))})`;
476
+ this.addWhereClause(negate ? `NOT ${group}` : group, boolean);
477
+ }
478
+ /**
479
+ * Compare the date portion of a column (time ignored).
480
+ */
481
+ processWhereDate(data, operator) {
482
+ const quotedField = this.parseColumnIdentifier(data.field, this.table, this.alias);
483
+ const placeholder = this.addParam(data.value);
484
+ this.addWhereClause(`${quotedField}::date ${operator} ${placeholder}::date`, "AND");
485
+ }
486
+ /**
487
+ * Date portion of a column within an inclusive [from, to] range.
488
+ */
489
+ processWhereDateBetween(data) {
490
+ const range = data.range;
491
+ const quotedField = this.parseColumnIdentifier(data.field, this.table, this.alias);
492
+ const from = this.addParam(range[0]);
493
+ const to = this.addParam(range[1]);
494
+ this.addWhereClause(`${quotedField}::date BETWEEN ${from}::date AND ${to}::date`, "AND");
495
+ }
496
+ /**
416
497
  * Process a raw WHERE operation.
417
498
  */
418
499
  processWhereRaw(data, boolean) {
@@ -425,53 +506,72 @@ var PostgresQueryParser = class PostgresQueryParser {
425
506
  /**
426
507
  * Process WHERE IN / NOT IN.
427
508
  */
428
- processWhereIn(data, negate) {
509
+ processWhereIn(data, negate, boolean = "AND") {
429
510
  const field = data.field;
430
511
  const values = data.values;
431
512
  const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
432
513
  const operator = negate ? "!= ALL" : "= ANY";
433
514
  const placeholder = this.addParam(values);
434
- this.addWhereClause(`${quotedField} ${operator}(${placeholder})`, "AND");
515
+ this.addWhereClause(`${quotedField} ${operator}(${placeholder})`, boolean);
435
516
  }
436
517
  /**
437
518
  * Process WHERE NULL / NOT NULL.
438
519
  */
439
- processWhereNull(data, negate) {
520
+ processWhereNull(data, negate, boolean = "AND") {
440
521
  const field = data.field;
441
522
  const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
442
523
  const clause = negate ? `${quotedField} IS NOT NULL` : `${quotedField} IS NULL`;
443
- this.addWhereClause(clause, "AND");
524
+ this.addWhereClause(clause, boolean);
444
525
  }
445
526
  /**
446
527
  * Process WHERE BETWEEN / NOT BETWEEN.
447
528
  */
448
- processWhereBetween(data, negate) {
529
+ processWhereBetween(data, negate, boolean = "AND") {
449
530
  const field = data.field;
450
531
  const range = data.range;
451
532
  const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
452
533
  const placeholder1 = this.addParam(range[0]);
453
534
  const placeholder2 = this.addParam(range[1]);
454
535
  const keyword = negate ? "NOT BETWEEN" : "BETWEEN";
455
- this.addWhereClause(`${quotedField} ${keyword} ${placeholder1} AND ${placeholder2}`, "AND");
536
+ this.addWhereClause(`${quotedField} ${keyword} ${placeholder1} AND ${placeholder2}`, boolean);
456
537
  }
457
538
  /**
458
539
  * Process WHERE LIKE / NOT LIKE.
540
+ *
541
+ * Cross-driver semantics (matches MongoDB): `%` is the only wildcard, `_`
542
+ * and backslash are literal, and a pattern with no `%` matches as a
543
+ * substring. `startsWith` / `endsWith` anchor the escaped text explicitly.
544
+ * A `RegExp` has no SQL equivalent and is rejected.
459
545
  */
460
- processWhereLike(data, negate) {
546
+ processWhereLike(data, negate, boolean = "AND", modeArg) {
461
547
  const field = data.field;
462
- const pattern = data.pattern;
548
+ const mode = modeArg ?? data.mode;
549
+ if (data.isRegExp === true || data.pattern instanceof RegExp) {
550
+ const source = data.pattern instanceof RegExp ? data.pattern.source : String(data.pattern);
551
+ const regexField = this.parseColumnIdentifier(field, this.table, this.alias);
552
+ const regexPlaceholder = this.addParam(source);
553
+ return this.addWhereClause(`${regexField} ${negate ? "!~*" : "~*"} ${regexPlaceholder}`, boolean);
554
+ }
555
+ const pattern = String(mode && data.value !== void 0 ? data.value : data.pattern);
463
556
  const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
464
557
  const { operator } = this.dialect.likePattern(pattern, true);
465
- const placeholder = this.addParam(pattern);
558
+ let escaped = pattern.replace(/\\/g, "\\\\").replace(/_/g, "\\_");
559
+ if (mode) escaped = escaped.replace(/%/g, "\\%");
560
+ let finalPattern = escaped;
561
+ if (mode === "startsWith") finalPattern = `${escaped}%`;
562
+ else if (mode === "endsWith") finalPattern = `%${escaped}`;
563
+ else if (!escaped.includes("%")) finalPattern = `%${escaped}%`;
564
+ const placeholder = this.addParam(finalPattern);
466
565
  const keyword = negate ? `NOT ${operator}` : operator;
467
- this.addWhereClause(`${quotedField} ${keyword} ${placeholder}`, "AND");
566
+ const escapeClause = mode ? " ESCAPE '\\'" : "";
567
+ this.addWhereClause(`${quotedField} ${keyword} ${placeholder}${escapeClause}`, boolean);
468
568
  }
469
569
  /**
470
570
  * Process WHERE column comparison.
471
571
  */
472
572
  processWhereColumn(data, boolean) {
473
573
  const first = data.first;
474
- const operator = data.operator ?? "=";
574
+ const operator = this.mapOperator(data.operator ?? "=");
475
575
  const second = data.second;
476
576
  const quotedFirst = this.parseColumnIdentifier(first, this.table, this.alias);
477
577
  const quotedSecond = this.parseColumnIdentifier(second, this.table, this.alias);
@@ -484,9 +584,13 @@ var PostgresQueryParser = class PostgresQueryParser {
484
584
  const path = data.path;
485
585
  const value = data.value;
486
586
  const quotedPath = this.parseColumnIdentifier(path, this.table, this.alias);
487
- const jsonValue = JSON.stringify(value);
488
587
  const operator = negate ? "NOT @>" : "@>";
489
- this.addWhereClause(`${quotedPath} ${operator} '${jsonValue}'::jsonb`, "AND");
588
+ const placeholder = this.addParam(JSON.stringify(value));
589
+ this.addWhereClause(`${quotedPath} ${operator} ${placeholder}::jsonb`, "AND");
590
+ }
591
+ /** Text search config name; anything but a plain identifier falls back to english. */
592
+ safeLanguage(language) {
593
+ return typeof language === "string" && /^[a-z_]+$/i.test(language) ? language : "english";
490
594
  }
491
595
  /**
492
596
  * Process full-text search WHERE.
@@ -494,9 +598,14 @@ var PostgresQueryParser = class PostgresQueryParser {
494
598
  processWhereFullText(data) {
495
599
  const fields = data.fields;
496
600
  const query = data.query;
497
- const tsVectors = fields.map((f) => `to_tsvector('english', ${this.dialect.quoteIdentifier(f)})`).join(" || ");
601
+ const language = this.safeLanguage(data.language);
602
+ const weights = data.weights ?? {};
603
+ const tsVectors = fields.map((f) => {
604
+ const weight = /^[A-D]$/.test(weights[f] ?? "") ? weights[f] : "A";
605
+ return `setweight(to_tsvector('${language}', COALESCE(${this.dialect.quoteIdentifier(f)}, '')), '${weight}')`;
606
+ }).join(" || ");
498
607
  const placeholder = this.addParam(query);
499
- this.addWhereClause(`(${tsVectors}) @@ plainto_tsquery('english', ${placeholder})`, "AND");
608
+ this.addWhereClause(`(${tsVectors}) @@ plainto_tsquery('${language}', ${placeholder})`, "AND");
500
609
  }
501
610
  /**
502
611
  * Process SELECT operation.
@@ -560,7 +669,7 @@ var PostgresQueryParser = class PostgresQueryParser {
560
669
  const quotedLocalKey = this.dialect.quoteIdentifier(localKey);
561
670
  const quotedMainTable = this.dialect.quoteIdentifier(this.table);
562
671
  let innerSelect;
563
- if (select && select.length > 0) innerSelect = `json_agg(json_build_object(${select.map((col) => `'${col}', a.${this.dialect.quoteIdentifier(col)}`).join(", ")}))`;
672
+ if (select && select.length > 0) innerSelect = `json_agg(json_build_object(${select.map((col) => `'${escapeSqlLiteral(col)}', a.${this.dialect.quoteIdentifier(col)}`).join(", ")}))`;
564
673
  else innerSelect = `json_agg(row_to_json(a.*))`;
565
674
  const fkCondition = `a.${quotedForeignKey} = ${quotedMainTable}.${quotedLocalKey}`;
566
675
  let extraWhere = "";
@@ -576,7 +685,7 @@ var PostgresQueryParser = class PostgresQueryParser {
576
685
  const renumber = this.absorbSubParserParams(subParser);
577
686
  if (subParser.whereClauses.length > 0) extraWhere = ` AND ${renumber(subParser.whereClauses.join(" "))}`;
578
687
  if (subParser.orderClauses.length > 0) orderBy = ` ORDER BY ${renumber(subParser.orderClauses.join(", "))}`;
579
- if (subParser.limitValue !== void 0) limitClause = ` LIMIT ${subParser.limitValue}`;
688
+ if (subParser.limitValue !== void 0) limitClause = ` ${this.dialect.limitOffset(subParser.limitValue)}`;
580
689
  }
581
690
  orderBy || limitClause ? `${quotedRelatedTable}${fkCondition}${extraWhere}${orderBy}${limitClause}` : `${innerSelect.replace("json_agg(", "").replace(/\)$/, "")}${quotedRelatedTable}${fkCondition}${extraWhere}`;
582
691
  const aggregated = orderBy || limitClause ? `(SELECT json_agg(row_to_json(sub.*)) FROM (SELECT * FROM ${quotedRelatedTable} a WHERE ${fkCondition}${extraWhere}${orderBy}${limitClause}) sub) AS ${quotedAlias}` : `(SELECT ${innerSelect} FROM ${quotedRelatedTable} a WHERE ${fkCondition}${extraWhere}) AS ${quotedAlias}`;
@@ -679,7 +788,11 @@ var PostgresQueryParser = class PostgresQueryParser {
679
788
  if (this.isTableReference(first)) expression = this.buildJsonbPath(first, second, rest);
680
789
  else expression = this.buildJsonbPath(effectiveTable, first, [second, ...rest]);
681
790
  }
682
- return cast ? `(${expression})::${cast}` : expression;
791
+ if (cast) {
792
+ if (!SAFE_CAST_PATTERN.test(cast)) throw new Error(`PostgresQueryParser: unsafe cast type "${cast}"`);
793
+ return `(${expression})::${cast}`;
794
+ }
795
+ return expression;
683
796
  }
684
797
  /**
685
798
  * Check if a string is a table reference (main table or join table).
@@ -715,7 +828,7 @@ var PostgresQueryParser = class PostgresQueryParser {
715
828
  let expression = `${quotedTable}.${quotedColumn}`;
716
829
  for (let i = 0; i < path.length; i++) {
717
830
  const operator = i === path.length - 1 ? "->>" : "->";
718
- expression += `${operator}'${path[i]}'`;
831
+ expression += `${operator}'${escapeSqlLiteral(path[i])}'`;
719
832
  }
720
833
  return expression;
721
834
  }
@@ -742,7 +855,9 @@ var PostgresQueryParser = class PostgresQueryParser {
742
855
  */
743
856
  processOrderBy(data) {
744
857
  const field = data.field;
745
- const direction = (data.direction ?? "asc").toUpperCase();
858
+ const rawDirection = String(data.direction ?? "asc").trim();
859
+ if (!SAFE_DIRECTION_PATTERN.test(rawDirection)) throw new Error(`PostgresQueryParser: invalid orderBy direction "${rawDirection}"`);
860
+ const direction = rawDirection.replace(/\s+/g, " ").toUpperCase();
746
861
  const quotedField = this.parseColumnIdentifier(field, this.table, this.alias);
747
862
  this.orderClauses.push(`${quotedField} ${direction}`);
748
863
  }
@@ -777,7 +892,7 @@ var PostgresQueryParser = class PostgresQueryParser {
777
892
  */
778
893
  processHaving(data) {
779
894
  const field = data.field;
780
- const operator = data.operator ?? "=";
895
+ const operator = this.mapOperator(data.operator ?? "=");
781
896
  const value = data.value;
782
897
  const quotedField = this.dialect.quoteIdentifier(field);
783
898
  const placeholder = this.addParam(value);
@@ -808,7 +923,7 @@ var PostgresQueryParser = class PostgresQueryParser {
808
923
  * dedicated processors and should never reach this method.
809
924
  */
810
925
  mapOperator(operator) {
811
- return {
926
+ const mapped = {
812
927
  "=": "=",
813
928
  "!=": "!=",
814
929
  "<>": "<>",
@@ -819,7 +934,9 @@ var PostgresQueryParser = class PostgresQueryParser {
819
934
  like: "LIKE",
820
935
  notlike: "NOT LIKE",
821
936
  ilike: "ILIKE"
822
- }[operator.toLowerCase()] ?? operator;
937
+ }[String(operator).toLowerCase()];
938
+ if (mapped === void 0) throw new Error(`PostgresQueryParser: unsupported operator "${operator}"`);
939
+ return mapped;
823
940
  }
824
941
  };
825
942