@expo/entity-database-adapter-knex 0.59.0 → 0.61.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 (103) hide show
  1. package/build/src/AuthorizationResultBasedKnexEntityLoader.d.ts +8 -9
  2. package/build/src/AuthorizationResultBasedKnexEntityLoader.js +5 -11
  3. package/build/src/BasePostgresEntityDatabaseAdapter.d.ts +4 -3
  4. package/build/src/BasePostgresEntityDatabaseAdapter.js +13 -19
  5. package/build/src/BaseSQLQueryBuilder.d.ts +4 -3
  6. package/build/src/BaseSQLQueryBuilder.js +4 -9
  7. package/build/src/EnforcingKnexEntityLoader.d.ts +7 -7
  8. package/build/src/EnforcingKnexEntityLoader.js +4 -10
  9. package/build/src/EntityFields.js +4 -11
  10. package/build/src/KnexEntityLoaderFactory.d.ts +4 -4
  11. package/build/src/KnexEntityLoaderFactory.js +8 -13
  12. package/build/src/PaginationStrategy.js +2 -6
  13. package/build/src/PostgresEntity.d.ts +4 -3
  14. package/build/src/PostgresEntity.js +5 -10
  15. package/build/src/PostgresEntityDatabaseAdapter.d.ts +6 -5
  16. package/build/src/PostgresEntityDatabaseAdapter.js +19 -24
  17. package/build/src/PostgresEntityDatabaseAdapterProvider.d.ts +1 -1
  18. package/build/src/PostgresEntityDatabaseAdapterProvider.js +3 -8
  19. package/build/src/PostgresEntityQueryContextProvider.d.ts +3 -2
  20. package/build/src/PostgresEntityQueryContextProvider.js +5 -10
  21. package/build/src/ReadonlyPostgresEntity.d.ts +4 -3
  22. package/build/src/ReadonlyPostgresEntity.js +5 -10
  23. package/build/src/SQLOperator.d.ts +324 -56
  24. package/build/src/SQLOperator.js +490 -150
  25. package/build/src/errors/wrapNativePostgresCallAsync.js +11 -15
  26. package/build/src/index.d.ts +20 -20
  27. package/build/src/index.js +20 -37
  28. package/build/src/internal/EntityKnexDataManager.d.ts +5 -5
  29. package/build/src/internal/EntityKnexDataManager.js +76 -84
  30. package/build/src/internal/getKnexDataManager.d.ts +2 -2
  31. package/build/src/internal/getKnexDataManager.js +7 -14
  32. package/build/src/internal/getKnexEntityLoaderFactory.d.ts +2 -2
  33. package/build/src/internal/getKnexEntityLoaderFactory.js +5 -9
  34. package/build/src/internal/utilityTypes.js +1 -3
  35. package/build/src/internal/weakMaps.js +1 -6
  36. package/build/src/knexLoader.d.ts +3 -3
  37. package/build/src/knexLoader.js +5 -10
  38. package/package.json +7 -6
  39. package/src/AuthorizationResultBasedKnexEntityLoader.ts +15 -12
  40. package/src/BasePostgresEntityDatabaseAdapter.ts +3 -3
  41. package/src/BaseSQLQueryBuilder.ts +5 -4
  42. package/src/EnforcingKnexEntityLoader.ts +8 -8
  43. package/src/KnexEntityLoaderFactory.ts +6 -6
  44. package/src/PostgresEntity.ts +5 -5
  45. package/src/PostgresEntityDatabaseAdapter.ts +13 -15
  46. package/src/PostgresEntityDatabaseAdapterProvider.ts +2 -2
  47. package/src/PostgresEntityQueryContextProvider.ts +3 -6
  48. package/src/ReadonlyPostgresEntity.ts +5 -5
  49. package/src/SQLOperator.ts +875 -194
  50. package/src/__integration-tests__/EntityCreationUtils-test.ts +5 -4
  51. package/src/__integration-tests__/PostgresEntityIntegration-test.ts +42 -15
  52. package/src/__integration-tests__/PostgresEntityQueryContextProvider-test.ts +6 -5
  53. package/src/__integration-tests__/PostgresInvalidSetup-test.ts +5 -4
  54. package/src/__integration-tests__/errors-test.ts +5 -4
  55. package/src/__testfixtures__/ErrorsTestEntity.ts +2 -3
  56. package/src/__testfixtures__/InvalidTestEntity.ts +2 -3
  57. package/src/__testfixtures__/PostgresTestEntity.ts +5 -6
  58. package/src/__testfixtures__/PostgresTriggerTestEntity.ts +7 -5
  59. package/src/__testfixtures__/PostgresUniqueTestEntity.ts +6 -4
  60. package/src/__testfixtures__/PostgresValidatorTestEntity.ts +7 -5
  61. package/src/__testfixtures__/createKnexIntegrationTestEntityCompanionProvider.ts +5 -8
  62. package/src/__tests__/AuthorizationResultBasedKnexEntityLoader-test.ts +12 -14
  63. package/src/__tests__/BasePostgresEntityDatabaseAdapter-test.ts +7 -5
  64. package/src/__tests__/EnforcingKnexEntityLoader-test.ts +7 -6
  65. package/src/__tests__/EntityFields-test.ts +1 -1
  66. package/src/__tests__/PostgresEntity-test.ts +6 -6
  67. package/src/__tests__/ReadonlyEntity-test.ts +5 -5
  68. package/src/__tests__/SQLOperator-test.ts +675 -95
  69. package/src/__tests__/fixtures/StubPostgresDatabaseAdapter.ts +9 -8
  70. package/src/__tests__/fixtures/StubPostgresDatabaseAdapterProvider.ts +2 -2
  71. package/src/__tests__/fixtures/TestEntity.ts +7 -7
  72. package/src/__tests__/fixtures/TestPaginationEntity.ts +9 -7
  73. package/src/__tests__/fixtures/createUnitTestPostgresEntityCompanionProvider.ts +3 -6
  74. package/src/errors/__tests__/wrapNativePostgresCallAsync-test.ts +1 -1
  75. package/src/errors/wrapNativePostgresCallAsync.ts +2 -2
  76. package/src/index.ts +20 -20
  77. package/src/internal/EntityKnexDataManager.ts +14 -17
  78. package/src/internal/__tests__/EntityKnexDataManager-test.ts +8 -15
  79. package/src/internal/__tests__/weakMaps-test.ts +1 -1
  80. package/src/internal/getKnexDataManager.ts +4 -4
  81. package/src/internal/getKnexEntityLoaderFactory.ts +4 -4
  82. package/src/knexLoader.ts +4 -4
  83. package/build/src/AuthorizationResultBasedKnexEntityLoader.js.map +0 -1
  84. package/build/src/BasePostgresEntityDatabaseAdapter.js.map +0 -1
  85. package/build/src/BaseSQLQueryBuilder.js.map +0 -1
  86. package/build/src/EnforcingKnexEntityLoader.js.map +0 -1
  87. package/build/src/EntityFields.js.map +0 -1
  88. package/build/src/KnexEntityLoaderFactory.js.map +0 -1
  89. package/build/src/PaginationStrategy.js.map +0 -1
  90. package/build/src/PostgresEntity.js.map +0 -1
  91. package/build/src/PostgresEntityDatabaseAdapter.js.map +0 -1
  92. package/build/src/PostgresEntityDatabaseAdapterProvider.js.map +0 -1
  93. package/build/src/PostgresEntityQueryContextProvider.js.map +0 -1
  94. package/build/src/ReadonlyPostgresEntity.js.map +0 -1
  95. package/build/src/SQLOperator.js.map +0 -1
  96. package/build/src/errors/wrapNativePostgresCallAsync.js.map +0 -1
  97. package/build/src/index.js.map +0 -1
  98. package/build/src/internal/EntityKnexDataManager.js.map +0 -1
  99. package/build/src/internal/getKnexDataManager.js.map +0 -1
  100. package/build/src/internal/getKnexEntityLoaderFactory.js.map +0 -1
  101. package/build/src/internal/utilityTypes.js.map +0 -1
  102. package/build/src/internal/weakMaps.js.map +0 -1
  103. package/build/src/knexLoader.js.map +0 -1
@@ -1,19 +1,8 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SQLFragmentHelpers = exports.SQLUnsafeRaw = exports.SQLArrayValue = exports.SQLEntityField = exports.SQLIdentifier = exports.SQLFragment = void 0;
7
- exports.identifier = identifier;
8
- exports.entityField = entityField;
9
- exports.arrayValue = arrayValue;
10
- exports.unsafeRaw = unsafeRaw;
11
- exports.sql = sql;
12
- const assert_1 = __importDefault(require("assert"));
1
+ import assert from 'assert';
13
2
  /**
14
3
  * SQL Fragment class that safely handles parameterized queries.
15
4
  */
16
- class SQLFragment {
5
+ export class SQLFragment {
17
6
  sql;
18
7
  bindings;
19
8
  constructor(sql, bindings) {
@@ -153,52 +142,47 @@ class SQLFragment {
153
142
  return proto.constructor === Object;
154
143
  }
155
144
  }
156
- exports.SQLFragment = SQLFragment;
157
145
  /**
158
146
  * Helper for SQL identifiers (table/column names).
159
147
  * Stores the raw identifier name to be escaped by Knex using ?? placeholder.
160
148
  */
161
- class SQLIdentifier {
149
+ export class SQLIdentifier {
162
150
  name;
163
151
  constructor(name) {
164
152
  this.name = name;
165
153
  }
166
154
  }
167
- exports.SQLIdentifier = SQLIdentifier;
168
155
  /**
169
156
  * Helper for referencing entity fields that can be used in SQL queries. This allows for type-safe references to fields of an entity
170
157
  * and does automatic translation to DB field names.
171
158
  */
172
- class SQLEntityField {
159
+ export class SQLEntityField {
173
160
  fieldName;
174
161
  constructor(fieldName) {
175
162
  this.fieldName = fieldName;
176
163
  }
177
164
  }
178
- exports.SQLEntityField = SQLEntityField;
179
165
  /**
180
166
  * Helper for passing an array as a single bound parameter (e.g. for PostgreSQL's = ANY(?)).
181
167
  * Unlike bare arrays interpolated in the sql template (which expand to (?, ?, ?) for IN clauses),
182
168
  * this binds the entire array as one parameter, letting knex handle the array encoding.
183
169
  */
184
- class SQLArrayValue {
170
+ export class SQLArrayValue {
185
171
  values;
186
172
  constructor(values) {
187
173
  this.values = values;
188
174
  }
189
175
  }
190
- exports.SQLArrayValue = SQLArrayValue;
191
176
  /**
192
177
  * Helper for raw SQL that should not be parameterized
193
178
  * WARNING: Only use this with trusted input to avoid SQL injection
194
179
  */
195
- class SQLUnsafeRaw {
180
+ export class SQLUnsafeRaw {
196
181
  rawSql;
197
182
  constructor(rawSql) {
198
183
  this.rawSql = rawSql;
199
184
  }
200
185
  }
201
- exports.SQLUnsafeRaw = SQLUnsafeRaw;
202
186
  /**
203
187
  * Create a SQL identifier (table/column name) that will be escaped by Knex using ??.
204
188
  *
@@ -209,7 +193,7 @@ exports.SQLUnsafeRaw = SQLUnsafeRaw;
209
193
  * identifier('column"; DROP TABLE users; --') // Will be safely escaped
210
194
  * ```
211
195
  */
212
- function identifier(name) {
196
+ export function identifier(name) {
213
197
  return new SQLIdentifier(name);
214
198
  }
215
199
  /**
@@ -218,7 +202,7 @@ function identifier(name) {
218
202
  *
219
203
  * @param fieldName - The entity field name to reference.
220
204
  */
221
- function entityField(fieldName) {
205
+ export function entityField(fieldName) {
222
206
  return new SQLEntityField(fieldName);
223
207
  }
224
208
  /**
@@ -232,7 +216,7 @@ function entityField(fieldName) {
232
216
  * // Generates: ?? = ANY(?) with the array bound as a single parameter
233
217
  * ```
234
218
  */
235
- function arrayValue(values) {
219
+ export function arrayValue(values) {
236
220
  return new SQLArrayValue(values);
237
221
  }
238
222
  /**
@@ -249,7 +233,7 @@ function arrayValue(values) {
249
233
  * const query = sql`WHERE ${unsafeRaw('EXTRACT(year FROM created_at)')} = ${2024}`;
250
234
  * ```
251
235
  */
252
- function unsafeRaw(sqlString) {
236
+ export function unsafeRaw(sqlString) {
253
237
  return new SQLUnsafeRaw(sqlString);
254
238
  }
255
239
  /**
@@ -261,7 +245,7 @@ function unsafeRaw(sqlString) {
261
245
  * const query = sql`age >= ${age} AND status = ${'active'}`;
262
246
  * ```
263
247
  */
264
- function sql(strings, ...values) {
248
+ export function sql(strings, ...values) {
265
249
  let sqlString = '';
266
250
  const bindings = [];
267
251
  strings.forEach((string, i) => {
@@ -307,25 +291,373 @@ function sql(strings, ...values) {
307
291
  return new SQLFragment(sqlString, bindings);
308
292
  }
309
293
  /**
310
- * Common SQL helper functions for building queries
294
+ * An SQL expression that supports fluent comparison methods.
295
+ * Extends SQLFragment so it can be used anywhere a SQLFragment is accepted.
296
+ * The fluent methods return plain SQLFragment instances since they produce
297
+ * complete conditions, not further chainable expressions.
311
298
  */
312
- exports.SQLFragmentHelpers = {
299
+ export class SQLChainableFragment extends SQLFragment {
300
+ /**
301
+ * Generates an equality condition (`= value`).
302
+ * Automatically converts `null`/`undefined` to `IS NULL`.
303
+ *
304
+ * @param value - The value to compare against
305
+ * @returns A {@link SQLFragment} representing the equality condition
306
+ */
307
+ eq(value) {
308
+ if (value === null || value === undefined) {
309
+ return this.isNull();
310
+ }
311
+ return sql `${this} = ${value}`;
312
+ }
313
+ /**
314
+ * Generates an inequality condition (`!= value`).
315
+ * Automatically converts `null`/`undefined` to `IS NOT NULL`.
316
+ *
317
+ * @param value - The value to compare against
318
+ * @returns A {@link SQLFragment} representing the inequality condition
319
+ */
320
+ neq(value) {
321
+ if (value === null || value === undefined) {
322
+ return this.isNotNull();
323
+ }
324
+ return sql `${this} != ${value}`;
325
+ }
326
+ /**
327
+ * Generates a greater-than condition (`> value`).
328
+ *
329
+ * @param value - The value to compare against
330
+ * @returns A {@link SQLFragment} representing the condition
331
+ */
332
+ gt(value) {
333
+ return sql `${this} > ${value}`;
334
+ }
335
+ /**
336
+ * Generates a greater-than-or-equal-to condition (`>= value`).
337
+ *
338
+ * @param value - The value to compare against
339
+ * @returns A {@link SQLFragment} representing the condition
340
+ */
341
+ gte(value) {
342
+ return sql `${this} >= ${value}`;
343
+ }
344
+ /**
345
+ * Generates a less-than condition (`< value`).
346
+ *
347
+ * @param value - The value to compare against
348
+ * @returns A {@link SQLFragment} representing the condition
349
+ */
350
+ lt(value) {
351
+ return sql `${this} < ${value}`;
352
+ }
353
+ /**
354
+ * Generates a less-than-or-equal-to condition (`<= value`).
355
+ *
356
+ * @param value - The value to compare against
357
+ * @returns A {@link SQLFragment} representing the condition
358
+ */
359
+ lte(value) {
360
+ return sql `${this} <= ${value}`;
361
+ }
362
+ /**
363
+ * Generates an `IS NULL` condition.
364
+ *
365
+ * @returns A {@link SQLFragment} representing the IS NULL check
366
+ */
367
+ isNull() {
368
+ return sql `${this} IS NULL`;
369
+ }
370
+ /**
371
+ * Generates an `IS NOT NULL` condition.
372
+ *
373
+ * @returns A {@link SQLFragment} representing the IS NOT NULL check
374
+ */
375
+ isNotNull() {
376
+ return sql `${this} IS NOT NULL`;
377
+ }
378
+ /**
379
+ * Generates a case-sensitive `LIKE` condition for pattern matching.
380
+ *
381
+ * @param pattern - The LIKE pattern (use `%` for wildcards, `_` for single character)
382
+ * @returns A {@link SQLFragment} representing the LIKE condition
383
+ */
384
+ like(pattern) {
385
+ return sql `${this} LIKE ${pattern}`;
386
+ }
387
+ /**
388
+ * Generates a case-sensitive `NOT LIKE` condition.
389
+ *
390
+ * @param pattern - The LIKE pattern (use `%` for wildcards, `_` for single character)
391
+ * @returns A {@link SQLFragment} representing the NOT LIKE condition
392
+ */
393
+ notLike(pattern) {
394
+ return sql `${this} NOT LIKE ${pattern}`;
395
+ }
396
+ /**
397
+ * Generates a case-insensitive `ILIKE` condition (PostgreSQL-specific).
398
+ *
399
+ * @param pattern - The LIKE pattern (use `%` for wildcards, `_` for single character)
400
+ * @returns A {@link SQLFragment} representing the ILIKE condition
401
+ */
402
+ ilike(pattern) {
403
+ return sql `${this} ILIKE ${pattern}`;
404
+ }
405
+ /**
406
+ * Generates a case-insensitive `NOT ILIKE` condition (PostgreSQL-specific).
407
+ *
408
+ * @param pattern - The LIKE pattern (use `%` for wildcards, `_` for single character)
409
+ * @returns A {@link SQLFragment} representing the NOT ILIKE condition
410
+ */
411
+ notIlike(pattern) {
412
+ return sql `${this} NOT ILIKE ${pattern}`;
413
+ }
414
+ /**
415
+ * Generates an `IN (...)` condition. Each array element becomes a separate
416
+ * bound parameter (`IN (?, ?, ?)`).
417
+ * Returns `FALSE` when the values array is empty.
418
+ *
419
+ * @param values - The values to check membership against
420
+ * @returns A {@link SQLFragment} representing the IN condition
421
+ */
422
+ inArray(values) {
423
+ if (values.length === 0) {
424
+ return sql `FALSE`;
425
+ }
426
+ return sql `${this} IN ${values}`;
427
+ }
428
+ /**
429
+ * Generates a `NOT IN (...)` condition. Each array element becomes a separate
430
+ * bound parameter.
431
+ * Returns `TRUE` when the values array is empty.
432
+ *
433
+ * @param values - The values to check non-membership against
434
+ * @returns A {@link SQLFragment} representing the NOT IN condition
435
+ */
436
+ notInArray(values) {
437
+ if (values.length === 0) {
438
+ return sql `TRUE`;
439
+ }
440
+ return sql `${this} NOT IN ${values}`;
441
+ }
313
442
  /**
314
- * IN clause helper
443
+ * Generates an `= ANY(?)` condition. Unlike {@link inArray}, the array is bound
444
+ * as a single parameter, producing a consistent query shape for query metrics.
445
+ * Returns `FALSE` when the values array is empty.
446
+ *
447
+ * @param values - The values to check membership against
448
+ * @returns A {@link SQLFragment} representing the = ANY condition
449
+ */
450
+ anyArray(values) {
451
+ if (values.length === 0) {
452
+ return sql `FALSE`;
453
+ }
454
+ return sql `${this} = ANY(${arrayValue(values)})`;
455
+ }
456
+ /**
457
+ * Generates a `BETWEEN min AND max` condition (inclusive on both ends).
458
+ *
459
+ * @param min - The lower bound
460
+ * @param max - The upper bound
461
+ * @returns A {@link SQLFragment} representing the BETWEEN condition
462
+ */
463
+ between(min, max) {
464
+ return sql `${this} BETWEEN ${min} AND ${max}`;
465
+ }
466
+ /**
467
+ * Generates a `NOT BETWEEN min AND max` condition.
468
+ *
469
+ * @param min - The lower bound
470
+ * @param max - The upper bound
471
+ * @returns A {@link SQLFragment} representing the NOT BETWEEN condition
472
+ */
473
+ notBetween(min, max) {
474
+ return sql `${this} NOT BETWEEN ${min} AND ${max}`;
475
+ }
476
+ }
477
+ /**
478
+ * Allowed PostgreSQL type names for the cast() helper.
479
+ * Only these types can be used to prevent SQL injection through type name interpolation.
480
+ */
481
+ const ALLOWED_CAST_TYPES = [
482
+ 'int',
483
+ 'integer',
484
+ 'int2',
485
+ 'int4',
486
+ 'int8',
487
+ 'smallint',
488
+ 'bigint',
489
+ 'numeric',
490
+ 'decimal',
491
+ 'real',
492
+ 'double precision',
493
+ 'float',
494
+ 'float4',
495
+ 'float8',
496
+ 'text',
497
+ 'varchar',
498
+ 'char',
499
+ 'character varying',
500
+ 'boolean',
501
+ 'bool',
502
+ 'date',
503
+ 'time',
504
+ 'timestamp',
505
+ 'timestamptz',
506
+ 'interval',
507
+ 'json',
508
+ 'jsonb',
509
+ 'uuid',
510
+ 'bytea',
511
+ ];
512
+ const ALLOWED_CAST_TYPES_SET = new Set(ALLOWED_CAST_TYPES);
513
+ // Helper to resolve expressionOrFieldName to a SQLFragment
514
+ function resolveInner(expressionOrFieldName) {
515
+ return expressionOrFieldName instanceof SQLFragment
516
+ ? expressionOrFieldName
517
+ : sql `${entityField(expressionOrFieldName)}`;
518
+ }
519
+ // Helper to resolve expressionOrFieldName to a SQLChainableFragment for fluent chaining
520
+ function resolveInnerExpr(expressionOrFieldName) {
521
+ if (expressionOrFieldName instanceof SQLChainableFragment) {
522
+ return expressionOrFieldName;
523
+ }
524
+ const inner = resolveInner(expressionOrFieldName);
525
+ return new SQLChainableFragment(inner.sql, inner.bindings);
526
+ }
527
+ function inArrayHelper(expressionOrFieldName, values) {
528
+ return resolveInnerExpr(expressionOrFieldName).inArray(values);
529
+ }
530
+ function anyArrayHelper(expressionOrFieldName, values) {
531
+ return resolveInnerExpr(expressionOrFieldName).anyArray(values);
532
+ }
533
+ function notInArrayHelper(expressionOrFieldName, values) {
534
+ return resolveInnerExpr(expressionOrFieldName).notInArray(values);
535
+ }
536
+ function betweenHelper(expressionOrFieldName, min, max) {
537
+ return resolveInnerExpr(expressionOrFieldName).between(min, max);
538
+ }
539
+ function notBetweenHelper(expressionOrFieldName, min, max) {
540
+ return resolveInnerExpr(expressionOrFieldName).notBetween(min, max);
541
+ }
542
+ function likeHelper(expressionOrFieldName, pattern) {
543
+ return resolveInnerExpr(expressionOrFieldName).like(pattern);
544
+ }
545
+ function notLikeHelper(expressionOrFieldName, pattern) {
546
+ return resolveInnerExpr(expressionOrFieldName).notLike(pattern);
547
+ }
548
+ function ilikeHelper(expressionOrFieldName, pattern) {
549
+ return resolveInnerExpr(expressionOrFieldName).ilike(pattern);
550
+ }
551
+ function notIlikeHelper(expressionOrFieldName, pattern) {
552
+ return resolveInnerExpr(expressionOrFieldName).notIlike(pattern);
553
+ }
554
+ function isNullHelper(expressionOrFieldName) {
555
+ return resolveInnerExpr(expressionOrFieldName).isNull();
556
+ }
557
+ function isNotNullHelper(expressionOrFieldName) {
558
+ return resolveInnerExpr(expressionOrFieldName).isNotNull();
559
+ }
560
+ function eqHelper(expressionOrFieldName, value) {
561
+ return resolveInnerExpr(expressionOrFieldName).eq(value);
562
+ }
563
+ function neqHelper(expressionOrFieldName, value) {
564
+ return resolveInnerExpr(expressionOrFieldName).neq(value);
565
+ }
566
+ function gtHelper(expressionOrFieldName, value) {
567
+ return resolveInnerExpr(expressionOrFieldName).gt(value);
568
+ }
569
+ function gteHelper(expressionOrFieldName, value) {
570
+ return resolveInnerExpr(expressionOrFieldName).gte(value);
571
+ }
572
+ function ltHelper(expressionOrFieldName, value) {
573
+ return resolveInnerExpr(expressionOrFieldName).lt(value);
574
+ }
575
+ function lteHelper(expressionOrFieldName, value) {
576
+ return resolveInnerExpr(expressionOrFieldName).lte(value);
577
+ }
578
+ function jsonContainsHelper(expressionOrFieldName, value) {
579
+ const serialized = JSON.stringify(value);
580
+ assert(serialized !== undefined || value === undefined, 'jsonContains: value is not JSON-serializable');
581
+ const inner = resolveInner(expressionOrFieldName);
582
+ return sql `${inner} @> ${serialized}::jsonb`;
583
+ }
584
+ function jsonContainedByHelper(expressionOrFieldName, value) {
585
+ const serialized = JSON.stringify(value);
586
+ assert(serialized !== undefined || value === undefined, 'jsonContainedBy: value is not JSON-serializable');
587
+ const inner = resolveInner(expressionOrFieldName);
588
+ return sql `${inner} <@ ${serialized}::jsonb`;
589
+ }
590
+ function jsonPathHelper(expressionOrFieldName, path) {
591
+ const inner = resolveInner(expressionOrFieldName);
592
+ const wrapped = sql `${inner}->${path}`;
593
+ return new SQLChainableFragment(wrapped.sql, wrapped.bindings);
594
+ }
595
+ function jsonPathTextHelper(expressionOrFieldName, path) {
596
+ const inner = resolveInner(expressionOrFieldName);
597
+ const wrapped = sql `${inner}->>${path}`;
598
+ return new SQLChainableFragment(wrapped.sql, wrapped.bindings);
599
+ }
600
+ function jsonDeepPathHelper(expressionOrFieldName, path) {
601
+ const pathLiteral = `{${path.map(quotePostgresArrayElement).join(',')}}`;
602
+ const inner = resolveInner(expressionOrFieldName);
603
+ const wrapped = sql `${inner} #> ${pathLiteral}`;
604
+ return new SQLChainableFragment(wrapped.sql, wrapped.bindings);
605
+ }
606
+ function jsonDeepPathTextHelper(expressionOrFieldName, path) {
607
+ const pathLiteral = `{${path.map(quotePostgresArrayElement).join(',')}}`;
608
+ const inner = resolveInner(expressionOrFieldName);
609
+ const wrapped = sql `${inner} #>> ${pathLiteral}`;
610
+ return new SQLChainableFragment(wrapped.sql, wrapped.bindings);
611
+ }
612
+ function castHelper(expressionOrFieldName, typeName) {
613
+ assert(ALLOWED_CAST_TYPES_SET.has(typeName), `cast: unsupported type name "${typeName}". Allowed types: ${[...ALLOWED_CAST_TYPES_SET].join(', ')}`);
614
+ const inner = resolveInner(expressionOrFieldName);
615
+ const wrapped = sql `(${inner})::${unsafeRaw(typeName)}`;
616
+ return new SQLChainableFragment(wrapped.sql, wrapped.bindings);
617
+ }
618
+ function lowerHelper(expressionOrFieldName) {
619
+ const inner = resolveInner(expressionOrFieldName);
620
+ const wrapped = sql `LOWER(${inner})`;
621
+ return new SQLChainableFragment(wrapped.sql, wrapped.bindings);
622
+ }
623
+ function upperHelper(expressionOrFieldName) {
624
+ const inner = resolveInner(expressionOrFieldName);
625
+ const wrapped = sql `UPPER(${inner})`;
626
+ return new SQLChainableFragment(wrapped.sql, wrapped.bindings);
627
+ }
628
+ function trimHelper(expressionOrFieldName) {
629
+ const inner = resolveInner(expressionOrFieldName);
630
+ const wrapped = sql `TRIM(${inner})`;
631
+ return new SQLChainableFragment(wrapped.sql, wrapped.bindings);
632
+ }
633
+ /**
634
+ * Common SQL helper functions for building queries.
635
+ *
636
+ * All methods accept either a field name (string) or a SQLFragment/SQLChainableFragment as the
637
+ * first argument. When a SQLChainableFragment with a known TValue is passed (e.g. from trim, lower),
638
+ * value parameters are type-checked against that TValue.
639
+ *
640
+ * @example
641
+ * ```ts
642
+ * // Field name usage
643
+ * SQLExpression.eq('status', 'active')
644
+ *
645
+ * // SQLFragment/SQLChainableFragment usage
646
+ * SQLExpression.eq(sql`${entityField('status')}`, 'active')
647
+ * SQLExpression.eq(SQLExpression.trim('name'), 'hello') // value constrained to string
648
+ * ```
649
+ */
650
+ export const SQLExpression = {
651
+ /**
652
+ * IN clause helper.
315
653
  *
316
654
  * @example
317
655
  * ```ts
318
- * const query = SQLFragmentHelpers.inArray<MyFields, 'id'>('status', ['active', 'pending']);
319
- * // Generates: ?? IN (?, ?) with entityField binding for 'status' and value bindings
656
+ * SQLExpression.inArray('status', ['active', 'pending'])
657
+ * SQLExpression.inArray(SQLExpression.lower('status'), ['active', 'pending'])
320
658
  * ```
321
659
  */
322
- inArray(fieldName, values) {
323
- if (values.length === 0) {
324
- // Handle empty array case - always false
325
- return sql `1 = 0`;
326
- }
327
- return sql `${entityField(fieldName)} IN ${values}`;
328
- },
660
+ inArray: inArrayHelper,
329
661
  /**
330
662
  * = ANY() clause helper. Binds the array as a single parameter instead of expanding it.
331
663
  * Semantically equivalent to IN for most cases, but retains a consistent query shape for
@@ -333,192 +665,200 @@ exports.SQLFragmentHelpers = {
333
665
  *
334
666
  * @example
335
667
  * ```ts
336
- * const query = SQLFragmentHelpers.anyArray('status', ['active', 'pending']);
337
- * // Generates: ?? = ANY(?) with entityField binding for 'status' and a single array value binding
668
+ * SQLExpression.anyArray('status', ['active', 'pending'])
338
669
  * ```
339
670
  */
340
- anyArray(fieldName, values) {
341
- if (values.length === 0) {
342
- // Handle empty array case - always false
343
- return sql `1 = 0`;
344
- }
345
- return sql `${entityField(fieldName)} = ANY(${arrayValue(values)})`;
346
- },
671
+ anyArray: anyArrayHelper,
347
672
  /**
348
- * NOT IN clause helper
673
+ * NOT IN clause helper.
349
674
  */
350
- notInArray(fieldName, values) {
351
- if (values.length === 0) {
352
- // Handle empty array case - always true
353
- return sql `1 = 1`;
354
- }
355
- return sql `${entityField(fieldName)} NOT IN ${values}`;
356
- },
675
+ notInArray: notInArrayHelper,
357
676
  /**
358
- * BETWEEN helper
677
+ * BETWEEN helper.
359
678
  *
360
679
  * @example
361
680
  * ```ts
362
- * const query = SQLFragmentHelpers.between<MyFields, 'id'>('age', 18, 65);
363
- * // Generates: ?? BETWEEN ? AND ? with entityField binding for 'age' and value bindings
681
+ * SQLExpression.between('age', 18, 65)
682
+ * SQLExpression.between(SQLExpression.cast('age', 'int'), 18, 65)
364
683
  * ```
365
684
  */
366
- between(fieldName, min, max) {
367
- return sql `${entityField(fieldName)} BETWEEN ${min} AND ${max}`;
368
- },
685
+ between: betweenHelper,
369
686
  /**
370
- * NOT BETWEEN helper
687
+ * NOT BETWEEN helper.
371
688
  */
372
- notBetween(fieldName, min, max) {
373
- return sql `${entityField(fieldName)} NOT BETWEEN ${min} AND ${max}`;
374
- },
689
+ notBetween: notBetweenHelper,
375
690
  /**
376
- * LIKE helper with automatic escaping
691
+ * LIKE helper for case-sensitive pattern matching.
377
692
  *
378
693
  * @example
379
694
  * ```ts
380
- * const query = SQLFragmentHelpers.like<MyFields, 'id'>('name', '%John%');
381
- * // Generates: ?? LIKE ? with entityField binding for 'name' and value binding
695
+ * SQLExpression.like('name', '%John%')
382
696
  * ```
383
697
  */
384
- like(fieldName, pattern) {
385
- return sql `${entityField(fieldName)} LIKE ${pattern}`;
386
- },
698
+ like: likeHelper,
387
699
  /**
388
- * NOT LIKE helper
700
+ * NOT LIKE helper.
389
701
  */
390
- notLike(fieldName, pattern) {
391
- return sql `${entityField(fieldName)} NOT LIKE ${pattern}`;
392
- },
702
+ notLike: notLikeHelper,
393
703
  /**
394
- * ILIKE helper for case-insensitive matching
704
+ * ILIKE helper for case-insensitive matching (PostgreSQL-specific).
395
705
  */
396
- ilike(fieldName, pattern) {
397
- return sql `${entityField(fieldName)} ILIKE ${pattern}`;
398
- },
706
+ ilike: ilikeHelper,
399
707
  /**
400
- * NOT ILIKE helper for case-insensitive non-matching
708
+ * NOT ILIKE helper for case-insensitive non-matching (PostgreSQL-specific).
401
709
  */
402
- notIlike(fieldName, pattern) {
403
- return sql `${entityField(fieldName)} NOT ILIKE ${pattern}`;
404
- },
710
+ notIlike: notIlikeHelper,
405
711
  /**
406
- * NULL check helper
712
+ * IS NULL check helper.
407
713
  */
408
- isNull(fieldName) {
409
- return sql `${entityField(fieldName)} IS NULL`;
410
- },
714
+ isNull: isNullHelper,
411
715
  /**
412
- * NOT NULL check helper
716
+ * IS NOT NULL check helper.
413
717
  */
414
- isNotNull(fieldName) {
415
- return sql `${entityField(fieldName)} IS NOT NULL`;
416
- },
718
+ isNotNull: isNotNullHelper,
417
719
  /**
418
- * Single-equals-equality operator
720
+ * Equality operator. Automatically converts null/undefined to IS NULL.
419
721
  */
420
- eq(fieldName, value) {
421
- if (value === null || value === undefined) {
422
- return exports.SQLFragmentHelpers.isNull(fieldName);
423
- }
424
- return sql `${entityField(fieldName)} = ${value}`;
425
- },
722
+ eq: eqHelper,
426
723
  /**
427
- * Single-equals-inequality operator
724
+ * Inequality operator. Automatically converts null/undefined to IS NOT NULL.
428
725
  */
429
- neq(fieldName, value) {
430
- if (value === null || value === undefined) {
431
- return exports.SQLFragmentHelpers.isNotNull(fieldName);
432
- }
433
- return sql `${entityField(fieldName)} != ${value}`;
434
- },
726
+ neq: neqHelper,
435
727
  /**
436
- * Greater-than comparison operator
728
+ * Greater-than comparison operator.
437
729
  */
438
- gt(fieldName, value) {
439
- return sql `${entityField(fieldName)} > ${value}`;
440
- },
730
+ gt: gtHelper,
441
731
  /**
442
- * Greater-than-or-equal-to comparison operator
732
+ * Greater-than-or-equal-to comparison operator.
443
733
  */
444
- gte(fieldName, value) {
445
- return sql `${entityField(fieldName)} >= ${value}`;
446
- },
734
+ gte: gteHelper,
447
735
  /**
448
- * Less-than comparison operator
736
+ * Less-than comparison operator.
449
737
  */
450
- lt(fieldName, value) {
451
- return sql `${entityField(fieldName)} < ${value}`;
452
- },
738
+ lt: ltHelper,
453
739
  /**
454
- * Less-than-or-equal-to comparison operator
740
+ * Less-than-or-equal-to comparison operator.
455
741
  */
456
- lte(fieldName, value) {
457
- return sql `${entityField(fieldName)} <= ${value}`;
458
- },
742
+ lte: lteHelper,
459
743
  /**
460
- * JSON contains operator (\@\>)
744
+ * JSON contains operator (\@\>).
461
745
  */
462
- jsonContains(fieldName, value) {
463
- const serialized = JSON.stringify(value);
464
- // JSON.stringify returns undefined for unsupported types, but we also want to allow undefined as a value
465
- (0, assert_1.default)(serialized !== undefined || value === undefined, 'jsonContains: value is not JSON-serializable');
466
- return sql `${entityField(fieldName)} @> ${serialized}::jsonb`;
467
- },
746
+ jsonContains: jsonContainsHelper,
468
747
  /**
469
- * JSON contained by operator (\<\@\)
748
+ * JSON contained by operator (\<\@\).
470
749
  */
471
- jsonContainedBy(fieldName, value) {
472
- const serialized = JSON.stringify(value);
473
- // JSON.stringify returns undefined for unsupported types, but we also want to allow undefined as a value
474
- (0, assert_1.default)(serialized !== undefined || value === undefined, 'jsonContainedBy: value is not JSON-serializable');
475
- return sql `${entityField(fieldName)} <@ ${serialized}::jsonb`;
476
- },
750
+ jsonContainedBy: jsonContainedByHelper,
477
751
  /**
478
- * JSON path extraction helper (-\>)
752
+ * JSON path extraction helper (-\>).
753
+ * Returns an SQLChainableFragment so that fluent comparison methods can be chained.
479
754
  */
480
- jsonPath(fieldName, path) {
481
- return sql `${entityField(fieldName)}->${path}`;
482
- },
755
+ jsonPath: jsonPathHelper,
483
756
  /**
484
- * JSON path text extraction helper (-\>\>)
757
+ * JSON path text extraction helper (-\>\>).
758
+ * Returns an SQLChainableFragment so that fluent comparison methods can be chained.
485
759
  */
486
- jsonPathText(fieldName, path) {
487
- return sql `${entityField(fieldName)}->>${path}`;
760
+ jsonPathText: jsonPathTextHelper,
761
+ /**
762
+ * JSON deep path extraction helper (#\>).
763
+ * Extracts a JSON sub-object at the specified key path, returning jsonb.
764
+ * Returns an SQLChainableFragment so that fluent comparison methods can be chained.
765
+ *
766
+ * @param expressionOrFieldName - A SQLFragment/SQLChainableFragment or entity field name
767
+ * @param path - Array of keys forming the path (e.g., ['user', 'address', 'city'])
768
+ */
769
+ jsonDeepPath: jsonDeepPathHelper,
770
+ /**
771
+ * JSON deep path text extraction helper (#\>\>).
772
+ * Extracts a JSON sub-object at the specified key path as text.
773
+ * Returns an SQLChainableFragment so that fluent comparison methods can be chained.
774
+ *
775
+ * @param expressionOrFieldName - A SQLFragment/SQLChainableFragment or entity field name
776
+ * @param path - Array of keys forming the path (e.g., ['user', 'address', 'city'])
777
+ */
778
+ jsonDeepPathText: jsonDeepPathTextHelper,
779
+ /**
780
+ * SQL type cast helper (::type).
781
+ * Casts an expression or field to a PostgreSQL type.
782
+ * Returns an SQLChainableFragment so that fluent comparison methods can be chained.
783
+ *
784
+ * @param expressionOrFieldName - A SQLFragment/SQLChainableFragment or entity field name to cast
785
+ * @param typeName - The PostgreSQL type name (e.g., 'int', 'text', 'timestamptz')
786
+ */
787
+ cast: castHelper,
788
+ /**
789
+ * COALESCE helper.
790
+ * Returns the first non-null value from the given expressions/values.
791
+ * Returns an SQLChainableFragment so that fluent comparison methods can be chained.
792
+ */
793
+ coalesce(...args) {
794
+ const fragments = args.map((arg) => {
795
+ if (arg instanceof SQLFragment) {
796
+ return arg;
797
+ }
798
+ return sql `${arg}`;
799
+ });
800
+ const inner = sql `COALESCE(${SQLFragment.joinWithCommaSeparator(...fragments)})`;
801
+ return new SQLChainableFragment(inner.sql, inner.bindings);
488
802
  },
803
+ /**
804
+ * LOWER helper
805
+ * Converts a string expression to lowercase.
806
+ * Returns an SQLChainableFragment so that fluent comparison methods can be chained.
807
+ */
808
+ lower: lowerHelper,
809
+ /**
810
+ * UPPER helper
811
+ * Converts a string expression to uppercase.
812
+ * Returns an SQLChainableFragment so that fluent comparison methods can be chained.
813
+ */
814
+ upper: upperHelper,
815
+ /**
816
+ * TRIM helper
817
+ * Removes leading and trailing whitespace from a string expression.
818
+ * Returns an SQLChainableFragment so that fluent comparison methods can be chained.
819
+ */
820
+ trim: trimHelper,
489
821
  /**
490
822
  * Logical AND of multiple fragments
491
823
  */
492
824
  and(...conditions) {
493
825
  if (conditions.length === 0) {
494
- return sql `1 = 1`;
826
+ return sql `TRUE`;
495
827
  }
496
- return joinSQLFragments(conditions.map((c) => exports.SQLFragmentHelpers.group(c)), ' AND ');
828
+ return joinSQLFragments(conditions.map((c) => SQLExpression.group(c)), ' AND ');
497
829
  },
498
830
  /**
499
831
  * Logical OR of multiple fragments
500
832
  */
501
833
  or(...conditions) {
502
834
  if (conditions.length === 0) {
503
- return sql `1 = 0`;
835
+ return sql `FALSE`;
504
836
  }
505
- return joinSQLFragments(conditions.map((c) => exports.SQLFragmentHelpers.group(c)), ' OR ');
837
+ return joinSQLFragments(conditions.map((c) => SQLExpression.group(c)), ' OR ');
506
838
  },
507
839
  /**
508
840
  * Logical NOT of a fragment
509
841
  */
510
842
  not(condition) {
511
- return new SQLFragment('NOT (' + condition.sql + ')', condition.bindings);
843
+ return sql `NOT (${condition})`;
512
844
  },
513
845
  /**
514
846
  * Parentheses helper for grouping conditions
515
847
  */
516
848
  group(condition) {
517
- return new SQLFragment('(' + condition.sql + ')', condition.bindings);
849
+ return sql `(${condition})`;
518
850
  },
519
851
  };
520
852
  // Internal helper function to join SQL fragments with a specified separator
521
853
  function joinSQLFragments(fragments, separator) {
522
854
  return new SQLFragment(fragments.map((f) => f.sql).join(separator), fragments.flatMap((f) => f.bindings));
523
855
  }
524
- //# sourceMappingURL=SQLOperator.js.map
856
+ // Internal helper to properly quote elements for PostgreSQL array literals.
857
+ // Elements containing special characters (commas, braces, quotes, backslashes, whitespace)
858
+ // or empty strings must be double-quoted with internal escaping.
859
+ function quotePostgresArrayElement(element) {
860
+ if (element === '' || /[,{}"\\\s]/.test(element)) {
861
+ return `"${element.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
862
+ }
863
+ return element;
864
+ }