@expo/entity-database-adapter-knex 0.58.0 → 0.60.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.
@@ -85,6 +85,15 @@ export declare class SQLEntityField<TFields extends Record<string, any>> {
85
85
  readonly fieldName: keyof TFields;
86
86
  constructor(fieldName: keyof TFields);
87
87
  }
88
+ /**
89
+ * Helper for passing an array as a single bound parameter (e.g. for PostgreSQL's = ANY(?)).
90
+ * Unlike bare arrays interpolated in the sql template (which expand to (?, ?, ?) for IN clauses),
91
+ * this binds the entire array as one parameter, letting knex handle the array encoding.
92
+ */
93
+ export declare class SQLArrayValue {
94
+ readonly values: readonly SupportedSQLValue[];
95
+ constructor(values: readonly SupportedSQLValue[]);
96
+ }
88
97
  /**
89
98
  * Helper for raw SQL that should not be parameterized
90
99
  * WARNING: Only use this with trusted input to avoid SQL injection
@@ -111,6 +120,18 @@ export declare function identifier(name: string): SQLIdentifier;
111
120
  * @param fieldName - The entity field name to reference.
112
121
  */
113
122
  export declare function entityField<TFields extends Record<string, any>>(fieldName: keyof TFields): SQLEntityField<TFields>;
123
+ /**
124
+ * Wrap an array so it is bound as a single parameter rather than expanded for IN clauses.
125
+ * Generates PostgreSQL's = ANY(?) syntax.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * const statuses = ['active', 'pending'];
130
+ * const query = sql`${entityField('status')} = ANY(${arrayValue(statuses)})`;
131
+ * // Generates: ?? = ANY(?) with the array bound as a single parameter
132
+ * ```
133
+ */
134
+ export declare function arrayValue(values: readonly SupportedSQLValue[]): SQLArrayValue;
114
135
  /**
115
136
  * Insert raw SQL that will not be parameterized
116
137
  * WARNING: This bypasses SQL injection protection. Only use with trusted input.
@@ -126,6 +147,17 @@ export declare function entityField<TFields extends Record<string, any>>(fieldNa
126
147
  * ```
127
148
  */
128
149
  export declare function unsafeRaw(sqlString: string): SQLUnsafeRaw;
150
+ /**
151
+ * Wraps a SQLFragment or entity field name into an SQLExpression for fluent comparison usage.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * expression<MyFields>('age').gte(18)
156
+ * expression<MyFields>('name').ilike('%john%')
157
+ * expression(sql`${entityField('status')}`).eq('active')
158
+ * ```
159
+ */
160
+ export declare function expression<TFields extends Record<string, any>>(fragmentOrFieldName: SQLFragment<TFields> | keyof TFields): SQLExpression<TFields>;
129
161
  /**
130
162
  * Tagged template literal function for SQL queries
131
163
  *
@@ -135,7 +167,7 @@ export declare function unsafeRaw(sqlString: string): SQLUnsafeRaw;
135
167
  * const query = sql`age >= ${age} AND status = ${'active'}`;
136
168
  * ```
137
169
  */
138
- export declare function sql<TFields extends Record<string, any>>(strings: TemplateStringsArray, ...values: readonly (SupportedSQLValue | SQLFragment<TFields> | SQLIdentifier | SQLUnsafeRaw | SQLEntityField<TFields>)[]): SQLFragment<TFields>;
170
+ export declare function sql<TFields extends Record<string, any>>(strings: TemplateStringsArray, ...values: readonly (SupportedSQLValue | SQLFragment<TFields> | SQLIdentifier | SQLUnsafeRaw | SQLEntityField<TFields> | SQLArrayValue)[]): SQLFragment<TFields>;
139
171
  type PickSupportedSQLValueKeys<T> = {
140
172
  [K in keyof T]: T[K] extends SupportedSQLValue ? K : never;
141
173
  }[keyof T];
@@ -145,6 +177,41 @@ type PickStringValueKeys<T> = {
145
177
  type JsonSerializable = string | number | boolean | null | undefined | readonly JsonSerializable[] | {
146
178
  readonly [key: string]: JsonSerializable;
147
179
  };
180
+ /**
181
+ * An SQL expression that supports fluent comparison methods.
182
+ * Extends SQLFragment so it can be used anywhere a SQLFragment is accepted.
183
+ * The fluent methods return plain SQLFragment instances since they produce
184
+ * complete conditions, not further chainable expressions.
185
+ */
186
+ export declare class SQLExpression<TFields extends Record<string, any>> extends SQLFragment<TFields> {
187
+ eq(value: SupportedSQLValue): SQLFragment<TFields>;
188
+ neq(value: SupportedSQLValue): SQLFragment<TFields>;
189
+ gt(value: SupportedSQLValue): SQLFragment<TFields>;
190
+ gte(value: SupportedSQLValue): SQLFragment<TFields>;
191
+ lt(value: SupportedSQLValue): SQLFragment<TFields>;
192
+ lte(value: SupportedSQLValue): SQLFragment<TFields>;
193
+ isNull(): SQLFragment<TFields>;
194
+ isNotNull(): SQLFragment<TFields>;
195
+ like(pattern: string): SQLFragment<TFields>;
196
+ notLike(pattern: string): SQLFragment<TFields>;
197
+ ilike(pattern: string): SQLFragment<TFields>;
198
+ notIlike(pattern: string): SQLFragment<TFields>;
199
+ inArray(values: readonly SupportedSQLValue[]): SQLFragment<TFields>;
200
+ notInArray(values: readonly SupportedSQLValue[]): SQLFragment<TFields>;
201
+ anyArray(values: readonly SupportedSQLValue[]): SQLFragment<TFields>;
202
+ between(min: SupportedSQLValue, max: SupportedSQLValue): SQLFragment<TFields>;
203
+ notBetween(min: SupportedSQLValue, max: SupportedSQLValue): SQLFragment<TFields>;
204
+ }
205
+ /**
206
+ * Allowed PostgreSQL type names for the cast() helper.
207
+ * Only these types can be used to prevent SQL injection through type name interpolation.
208
+ */
209
+ declare const ALLOWED_CAST_TYPES: readonly ["int", "integer", "int2", "int4", "int8", "smallint", "bigint", "numeric", "decimal", "real", "double precision", "float", "float4", "float8", "text", "varchar", "char", "character varying", "boolean", "bool", "date", "time", "timestamp", "timestamptz", "interval", "json", "jsonb", "uuid", "bytea"];
210
+ /**
211
+ * Allowed PostgreSQL type names for the cast() helper.
212
+ * Only these types can be used to prevent SQL injection through type name interpolation.
213
+ */
214
+ export type PostgresCastType = (typeof ALLOWED_CAST_TYPES)[number];
148
215
  /**
149
216
  * Common SQL helper functions for building queries
150
217
  */
@@ -159,6 +226,18 @@ export declare const SQLFragmentHelpers: {
159
226
  * ```
160
227
  */
161
228
  inArray<TFields extends Record<string, any>, N extends PickSupportedSQLValueKeys<TFields>>(fieldName: N, values: readonly TFields[N][]): SQLFragment<TFields>;
229
+ /**
230
+ * = ANY() clause helper. Binds the array as a single parameter instead of expanding it.
231
+ * Semantically equivalent to IN for most cases, but retains a consistent query shape for
232
+ * query metrics.
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * const query = SQLFragmentHelpers.anyArray('status', ['active', 'pending']);
237
+ * // Generates: ?? = ANY(?) with entityField binding for 'status' and a single array value binding
238
+ * ```
239
+ */
240
+ anyArray<TFields extends Record<string, any>, N extends PickSupportedSQLValueKeys<TFields>>(fieldName: N, values: readonly TFields[N][]): SQLFragment<TFields>;
162
241
  /**
163
242
  * NOT IN clause helper
164
243
  */
@@ -241,12 +320,65 @@ export declare const SQLFragmentHelpers: {
241
320
  jsonContainedBy<TFields extends Record<string, any>>(fieldName: keyof TFields, value: JsonSerializable): SQLFragment<TFields>;
242
321
  /**
243
322
  * JSON path extraction helper (-\>)
323
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
244
324
  */
245
- jsonPath<TFields extends Record<string, any>>(fieldName: keyof TFields, path: string): SQLFragment<TFields>;
325
+ jsonPath<TFields extends Record<string, any>>(fieldName: keyof TFields, path: string): SQLExpression<TFields>;
246
326
  /**
247
327
  * JSON path text extraction helper (-\>\>)
328
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
329
+ */
330
+ jsonPathText<TFields extends Record<string, any>>(fieldName: keyof TFields, path: string): SQLExpression<TFields>;
331
+ /**
332
+ * JSON deep path extraction helper (#\>)
333
+ * Extracts a JSON sub-object at the specified key path, returning jsonb.
334
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
335
+ *
336
+ * @param fieldName - The entity field containing JSON/JSONB data
337
+ * @param path - Array of keys forming the path (e.g., ['user', 'address', 'city'])
338
+ */
339
+ jsonDeepPath<TFields extends Record<string, any>>(fieldName: keyof TFields, path: readonly string[]): SQLExpression<TFields>;
340
+ /**
341
+ * JSON deep path text extraction helper (#\>\>)
342
+ * Extracts a JSON sub-object at the specified key path as text.
343
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
344
+ *
345
+ * @param fieldName - The entity field containing JSON/JSONB data
346
+ * @param path - Array of keys forming the path (e.g., ['user', 'address', 'city'])
347
+ */
348
+ jsonDeepPathText<TFields extends Record<string, any>>(fieldName: keyof TFields, path: readonly string[]): SQLExpression<TFields>;
349
+ /**
350
+ * SQL type cast helper (::type)
351
+ * Casts an expression to a PostgreSQL type.
352
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
353
+ *
354
+ * @param fragment - An SQLFragment or SQLExpression to cast
355
+ * @param typeName - The PostgreSQL type name (e.g., 'int', 'text', 'timestamptz')
356
+ */
357
+ cast<TFields extends Record<string, any>>(fragmentOrExpression: SQLFragment<TFields>, typeName: PostgresCastType): SQLExpression<TFields>;
358
+ /**
359
+ * COALESCE helper
360
+ * Returns the first non-null value from the given expressions/values.
361
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
362
+ */
363
+ coalesce<TFields extends Record<string, any>>(...args: readonly (SQLFragment<TFields> | SupportedSQLValue)[]): SQLExpression<TFields>;
364
+ /**
365
+ * LOWER helper
366
+ * Converts a string expression to lowercase.
367
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
368
+ */
369
+ lower<TFields extends Record<string, any>>(expressionOrFieldName: SQLFragment<TFields> | keyof TFields): SQLExpression<TFields>;
370
+ /**
371
+ * UPPER helper
372
+ * Converts a string expression to uppercase.
373
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
374
+ */
375
+ upper<TFields extends Record<string, any>>(expressionOrFieldName: SQLFragment<TFields> | keyof TFields): SQLExpression<TFields>;
376
+ /**
377
+ * TRIM helper
378
+ * Removes leading and trailing whitespace from a string expression.
379
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
248
380
  */
249
- jsonPathText<TFields extends Record<string, any>>(fieldName: keyof TFields, path: string): SQLFragment<TFields>;
381
+ trim<TFields extends Record<string, any>>(expressionOrFieldName: SQLFragment<TFields> | keyof TFields): SQLExpression<TFields>;
250
382
  /**
251
383
  * Logical AND of multiple fragments
252
384
  */
@@ -3,10 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SQLFragmentHelpers = exports.SQLUnsafeRaw = exports.SQLEntityField = exports.SQLIdentifier = exports.SQLFragment = void 0;
6
+ exports.SQLFragmentHelpers = exports.SQLExpression = exports.SQLUnsafeRaw = exports.SQLArrayValue = exports.SQLEntityField = exports.SQLIdentifier = exports.SQLFragment = void 0;
7
7
  exports.identifier = identifier;
8
8
  exports.entityField = entityField;
9
+ exports.arrayValue = arrayValue;
9
10
  exports.unsafeRaw = unsafeRaw;
11
+ exports.expression = expression;
10
12
  exports.sql = sql;
11
13
  const assert_1 = __importDefault(require("assert"));
12
14
  /**
@@ -175,6 +177,18 @@ class SQLEntityField {
175
177
  }
176
178
  }
177
179
  exports.SQLEntityField = SQLEntityField;
180
+ /**
181
+ * Helper for passing an array as a single bound parameter (e.g. for PostgreSQL's = ANY(?)).
182
+ * Unlike bare arrays interpolated in the sql template (which expand to (?, ?, ?) for IN clauses),
183
+ * this binds the entire array as one parameter, letting knex handle the array encoding.
184
+ */
185
+ class SQLArrayValue {
186
+ values;
187
+ constructor(values) {
188
+ this.values = values;
189
+ }
190
+ }
191
+ exports.SQLArrayValue = SQLArrayValue;
178
192
  /**
179
193
  * Helper for raw SQL that should not be parameterized
180
194
  * WARNING: Only use this with trusted input to avoid SQL injection
@@ -208,6 +222,20 @@ function identifier(name) {
208
222
  function entityField(fieldName) {
209
223
  return new SQLEntityField(fieldName);
210
224
  }
225
+ /**
226
+ * Wrap an array so it is bound as a single parameter rather than expanded for IN clauses.
227
+ * Generates PostgreSQL's = ANY(?) syntax.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * const statuses = ['active', 'pending'];
232
+ * const query = sql`${entityField('status')} = ANY(${arrayValue(statuses)})`;
233
+ * // Generates: ?? = ANY(?) with the array bound as a single parameter
234
+ * ```
235
+ */
236
+ function arrayValue(values) {
237
+ return new SQLArrayValue(values);
238
+ }
211
239
  /**
212
240
  * Insert raw SQL that will not be parameterized
213
241
  * WARNING: This bypasses SQL injection protection. Only use with trusted input.
@@ -225,6 +253,23 @@ function entityField(fieldName) {
225
253
  function unsafeRaw(sqlString) {
226
254
  return new SQLUnsafeRaw(sqlString);
227
255
  }
256
+ /**
257
+ * Wraps a SQLFragment or entity field name into an SQLExpression for fluent comparison usage.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * expression<MyFields>('age').gte(18)
262
+ * expression<MyFields>('name').ilike('%john%')
263
+ * expression(sql`${entityField('status')}`).eq('active')
264
+ * ```
265
+ */
266
+ function expression(fragmentOrFieldName) {
267
+ if (fragmentOrFieldName instanceof SQLFragment) {
268
+ return new SQLExpression(fragmentOrFieldName.sql, fragmentOrFieldName.bindings);
269
+ }
270
+ const fragment = sql `${entityField(fragmentOrFieldName)}`;
271
+ return new SQLExpression(fragment.sql, fragment.bindings);
272
+ }
228
273
  /**
229
274
  * Tagged template literal function for SQL queries
230
275
  *
@@ -256,6 +301,11 @@ function sql(strings, ...values) {
256
301
  sqlString += '??';
257
302
  bindings.push({ type: 'entityField', fieldName: value.fieldName });
258
303
  }
304
+ else if (value instanceof SQLArrayValue) {
305
+ // Handle array as a single bound parameter (for = ANY(?), etc.)
306
+ sqlString += '?';
307
+ bindings.push({ type: 'value', value: value.values });
308
+ }
259
309
  else if (value instanceof SQLUnsafeRaw) {
260
310
  // Handle raw SQL (WARNING: no parameterization)
261
311
  sqlString += value.rawSql;
@@ -274,6 +324,117 @@ function sql(strings, ...values) {
274
324
  });
275
325
  return new SQLFragment(sqlString, bindings);
276
326
  }
327
+ /**
328
+ * An SQL expression that supports fluent comparison methods.
329
+ * Extends SQLFragment so it can be used anywhere a SQLFragment is accepted.
330
+ * The fluent methods return plain SQLFragment instances since they produce
331
+ * complete conditions, not further chainable expressions.
332
+ */
333
+ class SQLExpression extends SQLFragment {
334
+ eq(value) {
335
+ if (value === null || value === undefined) {
336
+ return this.isNull();
337
+ }
338
+ return sql `${this} = ${value}`;
339
+ }
340
+ neq(value) {
341
+ if (value === null || value === undefined) {
342
+ return this.isNotNull();
343
+ }
344
+ return sql `${this} != ${value}`;
345
+ }
346
+ gt(value) {
347
+ return sql `${this} > ${value}`;
348
+ }
349
+ gte(value) {
350
+ return sql `${this} >= ${value}`;
351
+ }
352
+ lt(value) {
353
+ return sql `${this} < ${value}`;
354
+ }
355
+ lte(value) {
356
+ return sql `${this} <= ${value}`;
357
+ }
358
+ isNull() {
359
+ return sql `${this} IS NULL`;
360
+ }
361
+ isNotNull() {
362
+ return sql `${this} IS NOT NULL`;
363
+ }
364
+ like(pattern) {
365
+ return sql `${this} LIKE ${pattern}`;
366
+ }
367
+ notLike(pattern) {
368
+ return sql `${this} NOT LIKE ${pattern}`;
369
+ }
370
+ ilike(pattern) {
371
+ return sql `${this} ILIKE ${pattern}`;
372
+ }
373
+ notIlike(pattern) {
374
+ return sql `${this} NOT ILIKE ${pattern}`;
375
+ }
376
+ inArray(values) {
377
+ if (values.length === 0) {
378
+ return sql `FALSE`;
379
+ }
380
+ return sql `${this} IN ${values}`;
381
+ }
382
+ notInArray(values) {
383
+ if (values.length === 0) {
384
+ return sql `TRUE`;
385
+ }
386
+ return sql `${this} NOT IN ${values}`;
387
+ }
388
+ anyArray(values) {
389
+ if (values.length === 0) {
390
+ return sql `FALSE`;
391
+ }
392
+ return sql `${this} = ANY(${arrayValue(values)})`;
393
+ }
394
+ between(min, max) {
395
+ return sql `${this} BETWEEN ${min} AND ${max}`;
396
+ }
397
+ notBetween(min, max) {
398
+ return sql `${this} NOT BETWEEN ${min} AND ${max}`;
399
+ }
400
+ }
401
+ exports.SQLExpression = SQLExpression;
402
+ /**
403
+ * Allowed PostgreSQL type names for the cast() helper.
404
+ * Only these types can be used to prevent SQL injection through type name interpolation.
405
+ */
406
+ const ALLOWED_CAST_TYPES = [
407
+ 'int',
408
+ 'integer',
409
+ 'int2',
410
+ 'int4',
411
+ 'int8',
412
+ 'smallint',
413
+ 'bigint',
414
+ 'numeric',
415
+ 'decimal',
416
+ 'real',
417
+ 'double precision',
418
+ 'float',
419
+ 'float4',
420
+ 'float8',
421
+ 'text',
422
+ 'varchar',
423
+ 'char',
424
+ 'character varying',
425
+ 'boolean',
426
+ 'bool',
427
+ 'date',
428
+ 'time',
429
+ 'timestamp',
430
+ 'timestamptz',
431
+ 'interval',
432
+ 'json',
433
+ 'jsonb',
434
+ 'uuid',
435
+ 'bytea',
436
+ ];
437
+ const ALLOWED_CAST_TYPES_SET = new Set(ALLOWED_CAST_TYPES);
277
438
  /**
278
439
  * Common SQL helper functions for building queries
279
440
  */
@@ -288,21 +449,27 @@ exports.SQLFragmentHelpers = {
288
449
  * ```
289
450
  */
290
451
  inArray(fieldName, values) {
291
- if (values.length === 0) {
292
- // Handle empty array case - always false
293
- return sql `1 = 0`;
294
- }
295
- return sql `${entityField(fieldName)} IN ${values}`;
452
+ return expression(fieldName).inArray(values);
453
+ },
454
+ /**
455
+ * = ANY() clause helper. Binds the array as a single parameter instead of expanding it.
456
+ * Semantically equivalent to IN for most cases, but retains a consistent query shape for
457
+ * query metrics.
458
+ *
459
+ * @example
460
+ * ```ts
461
+ * const query = SQLFragmentHelpers.anyArray('status', ['active', 'pending']);
462
+ * // Generates: ?? = ANY(?) with entityField binding for 'status' and a single array value binding
463
+ * ```
464
+ */
465
+ anyArray(fieldName, values) {
466
+ return expression(fieldName).anyArray(values);
296
467
  },
297
468
  /**
298
469
  * NOT IN clause helper
299
470
  */
300
471
  notInArray(fieldName, values) {
301
- if (values.length === 0) {
302
- // Handle empty array case - always true
303
- return sql `1 = 1`;
304
- }
305
- return sql `${entityField(fieldName)} NOT IN ${values}`;
472
+ return expression(fieldName).notInArray(values);
306
473
  },
307
474
  /**
308
475
  * BETWEEN helper
@@ -314,13 +481,13 @@ exports.SQLFragmentHelpers = {
314
481
  * ```
315
482
  */
316
483
  between(fieldName, min, max) {
317
- return sql `${entityField(fieldName)} BETWEEN ${min} AND ${max}`;
484
+ return expression(fieldName).between(min, max);
318
485
  },
319
486
  /**
320
487
  * NOT BETWEEN helper
321
488
  */
322
489
  notBetween(fieldName, min, max) {
323
- return sql `${entityField(fieldName)} NOT BETWEEN ${min} AND ${max}`;
490
+ return expression(fieldName).notBetween(min, max);
324
491
  },
325
492
  /**
326
493
  * LIKE helper with automatic escaping
@@ -332,79 +499,73 @@ exports.SQLFragmentHelpers = {
332
499
  * ```
333
500
  */
334
501
  like(fieldName, pattern) {
335
- return sql `${entityField(fieldName)} LIKE ${pattern}`;
502
+ return expression(fieldName).like(pattern);
336
503
  },
337
504
  /**
338
505
  * NOT LIKE helper
339
506
  */
340
507
  notLike(fieldName, pattern) {
341
- return sql `${entityField(fieldName)} NOT LIKE ${pattern}`;
508
+ return expression(fieldName).notLike(pattern);
342
509
  },
343
510
  /**
344
511
  * ILIKE helper for case-insensitive matching
345
512
  */
346
513
  ilike(fieldName, pattern) {
347
- return sql `${entityField(fieldName)} ILIKE ${pattern}`;
514
+ return expression(fieldName).ilike(pattern);
348
515
  },
349
516
  /**
350
517
  * NOT ILIKE helper for case-insensitive non-matching
351
518
  */
352
519
  notIlike(fieldName, pattern) {
353
- return sql `${entityField(fieldName)} NOT ILIKE ${pattern}`;
520
+ return expression(fieldName).notIlike(pattern);
354
521
  },
355
522
  /**
356
523
  * NULL check helper
357
524
  */
358
525
  isNull(fieldName) {
359
- return sql `${entityField(fieldName)} IS NULL`;
526
+ return expression(fieldName).isNull();
360
527
  },
361
528
  /**
362
529
  * NOT NULL check helper
363
530
  */
364
531
  isNotNull(fieldName) {
365
- return sql `${entityField(fieldName)} IS NOT NULL`;
532
+ return expression(fieldName).isNotNull();
366
533
  },
367
534
  /**
368
535
  * Single-equals-equality operator
369
536
  */
370
537
  eq(fieldName, value) {
371
- if (value === null || value === undefined) {
372
- return exports.SQLFragmentHelpers.isNull(fieldName);
373
- }
374
- return sql `${entityField(fieldName)} = ${value}`;
538
+ return expression(fieldName).eq(value);
375
539
  },
376
540
  /**
377
541
  * Single-equals-inequality operator
378
542
  */
379
543
  neq(fieldName, value) {
380
- if (value === null || value === undefined) {
381
- return exports.SQLFragmentHelpers.isNotNull(fieldName);
382
- }
383
- return sql `${entityField(fieldName)} != ${value}`;
544
+ return expression(fieldName).neq(value);
384
545
  },
385
546
  /**
386
547
  * Greater-than comparison operator
387
548
  */
388
549
  gt(fieldName, value) {
389
- return sql `${entityField(fieldName)} > ${value}`;
550
+ return expression(fieldName).gt(value);
390
551
  },
391
552
  /**
392
553
  * Greater-than-or-equal-to comparison operator
393
554
  */
394
555
  gte(fieldName, value) {
395
- return sql `${entityField(fieldName)} >= ${value}`;
556
+ return expression(fieldName).gte(value);
396
557
  },
397
558
  /**
398
559
  * Less-than comparison operator
399
560
  */
400
561
  lt(fieldName, value) {
401
- return sql `${entityField(fieldName)} < ${value}`;
562
+ return expression(fieldName).lt(value);
402
563
  },
403
564
  /**
404
565
  * Less-than-or-equal-to comparison operator
405
566
  */
406
567
  lte(fieldName, value) {
407
- return sql `${entityField(fieldName)} <= ${value}`;
568
+ return expression(fieldName).lte(value);
408
569
  },
409
570
  /**
410
571
  * JSON contains operator (\@\>)
@@ -426,22 +587,108 @@ exports.SQLFragmentHelpers = {
426
587
  },
427
588
  /**
428
589
  * JSON path extraction helper (-\>)
590
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
429
591
  */
430
592
  jsonPath(fieldName, path) {
431
- return sql `${entityField(fieldName)}->${path}`;
593
+ return expression(sql `${entityField(fieldName)}->${path}`);
432
594
  },
433
595
  /**
434
596
  * JSON path text extraction helper (-\>\>)
597
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
435
598
  */
436
599
  jsonPathText(fieldName, path) {
437
- return sql `${entityField(fieldName)}->>${path}`;
600
+ return expression(sql `${entityField(fieldName)}->>${path}`);
601
+ },
602
+ /**
603
+ * JSON deep path extraction helper (#\>)
604
+ * Extracts a JSON sub-object at the specified key path, returning jsonb.
605
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
606
+ *
607
+ * @param fieldName - The entity field containing JSON/JSONB data
608
+ * @param path - Array of keys forming the path (e.g., ['user', 'address', 'city'])
609
+ */
610
+ jsonDeepPath(fieldName, path) {
611
+ const pathLiteral = `{${path.map(quotePostgresArrayElement).join(',')}}`;
612
+ return expression(sql `${entityField(fieldName)} #> ${pathLiteral}`);
613
+ },
614
+ /**
615
+ * JSON deep path text extraction helper (#\>\>)
616
+ * Extracts a JSON sub-object at the specified key path as text.
617
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
618
+ *
619
+ * @param fieldName - The entity field containing JSON/JSONB data
620
+ * @param path - Array of keys forming the path (e.g., ['user', 'address', 'city'])
621
+ */
622
+ jsonDeepPathText(fieldName, path) {
623
+ const pathLiteral = `{${path.map(quotePostgresArrayElement).join(',')}}`;
624
+ return expression(sql `${entityField(fieldName)} #>> ${pathLiteral}`);
625
+ },
626
+ /**
627
+ * SQL type cast helper (::type)
628
+ * Casts an expression to a PostgreSQL type.
629
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
630
+ *
631
+ * @param fragment - An SQLFragment or SQLExpression to cast
632
+ * @param typeName - The PostgreSQL type name (e.g., 'int', 'text', 'timestamptz')
633
+ */
634
+ cast(fragmentOrExpression, typeName) {
635
+ (0, assert_1.default)(ALLOWED_CAST_TYPES_SET.has(typeName), `cast: unsupported type name "${typeName}". Allowed types: ${[...ALLOWED_CAST_TYPES_SET].join(', ')}`);
636
+ return expression(sql `(${fragmentOrExpression})::${unsafeRaw(typeName)}`);
637
+ },
638
+ /**
639
+ * COALESCE helper
640
+ * Returns the first non-null value from the given expressions/values.
641
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
642
+ */
643
+ coalesce(...args) {
644
+ const fragments = args.map((arg) => {
645
+ if (arg instanceof SQLFragment) {
646
+ return arg;
647
+ }
648
+ return sql `${arg}`;
649
+ });
650
+ const inner = SQLFragment.joinWithCommaSeparator(...fragments);
651
+ return expression(sql `COALESCE(${inner})`);
652
+ },
653
+ /**
654
+ * LOWER helper
655
+ * Converts a string expression to lowercase.
656
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
657
+ */
658
+ lower(expressionOrFieldName) {
659
+ const inner = expressionOrFieldName instanceof SQLFragment
660
+ ? expressionOrFieldName
661
+ : sql `${entityField(expressionOrFieldName)}`;
662
+ return expression(sql `LOWER(${inner})`);
663
+ },
664
+ /**
665
+ * UPPER helper
666
+ * Converts a string expression to uppercase.
667
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
668
+ */
669
+ upper(expressionOrFieldName) {
670
+ const inner = expressionOrFieldName instanceof SQLFragment
671
+ ? expressionOrFieldName
672
+ : sql `${entityField(expressionOrFieldName)}`;
673
+ return expression(sql `UPPER(${inner})`);
674
+ },
675
+ /**
676
+ * TRIM helper
677
+ * Removes leading and trailing whitespace from a string expression.
678
+ * Returns an SQLExpression so that fluent comparison methods can be chained.
679
+ */
680
+ trim(expressionOrFieldName) {
681
+ const inner = expressionOrFieldName instanceof SQLFragment
682
+ ? expressionOrFieldName
683
+ : sql `${entityField(expressionOrFieldName)}`;
684
+ return expression(sql `TRIM(${inner})`);
438
685
  },
439
686
  /**
440
687
  * Logical AND of multiple fragments
441
688
  */
442
689
  and(...conditions) {
443
690
  if (conditions.length === 0) {
444
- return sql `1 = 1`;
691
+ return sql `TRUE`;
445
692
  }
446
693
  return joinSQLFragments(conditions.map((c) => exports.SQLFragmentHelpers.group(c)), ' AND ');
447
694
  },
@@ -450,7 +697,7 @@ exports.SQLFragmentHelpers = {
450
697
  */
451
698
  or(...conditions) {
452
699
  if (conditions.length === 0) {
453
- return sql `1 = 0`;
700
+ return sql `FALSE`;
454
701
  }
455
702
  return joinSQLFragments(conditions.map((c) => exports.SQLFragmentHelpers.group(c)), ' OR ');
456
703
  },
@@ -458,17 +705,26 @@ exports.SQLFragmentHelpers = {
458
705
  * Logical NOT of a fragment
459
706
  */
460
707
  not(condition) {
461
- return new SQLFragment('NOT (' + condition.sql + ')', condition.bindings);
708
+ return sql `NOT (${condition})`;
462
709
  },
463
710
  /**
464
711
  * Parentheses helper for grouping conditions
465
712
  */
466
713
  group(condition) {
467
- return new SQLFragment('(' + condition.sql + ')', condition.bindings);
714
+ return sql `(${condition})`;
468
715
  },
469
716
  };
470
717
  // Internal helper function to join SQL fragments with a specified separator
471
718
  function joinSQLFragments(fragments, separator) {
472
719
  return new SQLFragment(fragments.map((f) => f.sql).join(separator), fragments.flatMap((f) => f.bindings));
473
720
  }
721
+ // Internal helper to properly quote elements for PostgreSQL array literals.
722
+ // Elements containing special characters (commas, braces, quotes, backslashes, whitespace)
723
+ // or empty strings must be double-quoted with internal escaping.
724
+ function quotePostgresArrayElement(element) {
725
+ if (element === '' || /[,{}"\\\s]/.test(element)) {
726
+ return `"${element.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
727
+ }
728
+ return element;
729
+ }
474
730
  //# sourceMappingURL=SQLOperator.js.map