turbine-orm 0.28.0 → 0.28.2

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.
@@ -52,229 +52,10 @@ const index_advisor_js_1 = require("../index-advisor.js");
52
52
  const nested_write_js_1 = require("../nested-write.js");
53
53
  const schema_js_1 = require("../schema.js");
54
54
  const batched_loader_js_1 = require("./batched-loader.js");
55
+ const filters_js_1 = require("./filters.js");
55
56
  const utils_js_1 = require("./utils.js");
56
- // ---------------------------------------------------------------------------
57
- // Internal detection helpers — used by QueryInterface
58
- // ---------------------------------------------------------------------------
59
- /** Check if a value is a where operator object (has at least one known operator key) */
60
- function isWhereOperator(value) {
61
- if (value === null ||
62
- value === undefined ||
63
- typeof value !== 'object' ||
64
- Array.isArray(value) ||
65
- value instanceof Date) {
66
- return false;
67
- }
68
- const keys = Object.keys(value);
69
- return keys.length > 0 && keys.every((k) => utils_js_1.OPERATOR_KEYS.has(k));
70
- }
71
- /**
72
- * True for a *plain object literal* that reached an equality fallthrough
73
- * without matching any known filter shape — the misspelled-operator case.
74
- * Class instances (Buffer for bytea, Decimal wrappers, ...) are legitimate
75
- * bind values and return false, as do arrays and Dates.
76
- */
77
- function isUnmatchedPlainObject(value) {
78
- if (typeof value !== 'object' || value === null || Array.isArray(value) || value instanceof Date)
79
- return false;
80
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))
81
- return false;
82
- const proto = Object.getPrototypeOf(value);
83
- return proto === Object.prototype || proto === null;
84
- }
85
- /**
86
- * Fingerprint the SHAPE of a where-operator object. Null-valued `equals` /
87
- * `not` compile to parameterless `IS NULL` / `IS NOT NULL` (different SQL, no
88
- * param pushed), so null-ness is part of the shape — without it a cache entry
89
- * warmed by `{ not: 5 }` would serve `{ not: null }` with a desynced param list.
90
- */
91
- function fingerprintOperatorShape(value) {
92
- const obj = value;
93
- const opKeys = Object.keys(obj)
94
- .filter((k) => k !== 'mode')
95
- .map((k) => ((k === 'equals' || k === 'not') && obj[k] === null ? `${k}:null` : k))
96
- .sort();
97
- const modeStr = value.mode === 'insensitive' ? ':i' : '';
98
- return `op(${opKeys.join(',')}${modeStr})`;
99
- }
100
- /**
101
- * Guard for the value of an `equals` operator reaching the plain-equality
102
- * operator path. A plain object literal can only legitimately be an equality
103
- * value on a json/jsonb column — and those route to the JSONB filter branch
104
- * BEFORE the operator branch, so any plain object that reaches here is a
105
- * mistake (e.g. `{ equals: { foo: 1 } }` on a text column). Shared by the
106
- * SQL-build path and the cache-hit param-collect path so a warmed cache can
107
- * never skip the check.
108
- */
109
- function assertBindableEqualsOperand(value, column) {
110
- if (!isUnmatchedPlainObject(value))
111
- return;
112
- throw new errors_js_1.ValidationError(`[turbine] Plain-object value for operator 'equals' on ${column}: ` +
113
- `objects are only valid 'equals' values on JSON (json/jsonb) columns, ` +
114
- `where 'equals' is the JSONB containment filter.`);
115
- }
116
- /**
117
- * Object keys in sorted order, mirroring the canonical order used by every
118
- * cache fingerprint. The SQL-build and cache-hit param-collect paths MUST
119
- * enumerate object keys in this exact order: fingerprints sort keys, so two
120
- * where clauses with the same fields in different insertion order share one
121
- * cache entry — if build/collect iterated insertion order, the cached SQL's
122
- * `$N` placeholders would bind the wrong values (cross-tenant-leak class).
123
- * Array order (OR/AND members) is positional and is never sorted.
124
- */
125
- function sortedKeys(obj) {
126
- return Object.keys(obj).sort();
127
- }
128
- /** {@link sortedKeys}, but yielding `[key, value]` pairs. */
129
- function sortedEntries(obj) {
130
- return Object.entries(obj).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
131
- }
132
- /** Known atomic-update operator keys — used to detect operator objects vs plain JSON values */
133
57
  /** Relations already warned about missing FK indexes (once per process, dev only). */
134
58
  const unindexedRelationWarned = new Set();
135
- const UPDATE_OPERATOR_KEYS = new Set(['set', 'increment', 'decrement', 'multiply', 'divide']);
136
- /** Known JSONB operator keys */
137
- const JSONB_OPERATOR_KEYS = new Set(['path', 'equals', 'contains', 'hasKey']);
138
- /**
139
- * JSONB operator keys that are *unique* to {@link JsonFilter} — they cannot
140
- * appear in any other where-filter shape, so the presence of one of these is
141
- * an unambiguous signal that the user meant a JSON filter. Used by the
142
- * strict-validation path so that `{ contains: 'foo' }` (which is also a valid
143
- * `WhereOperator` for LIKE) is not misclassified. Note `equals` is NOT in this
144
- * set: on non-JSON columns it is a plain equality operator (`WhereOperator`),
145
- * so it must fall through instead of throwing.
146
- */
147
- const JSONB_UNIQUE_KEYS = new Set(['path', 'hasKey']);
148
- /** Check if a value is a JSONB filter object */
149
- function isJsonFilter(value) {
150
- if (value === null ||
151
- value === undefined ||
152
- typeof value !== 'object' ||
153
- Array.isArray(value) ||
154
- value instanceof Date) {
155
- return false;
156
- }
157
- const keys = Object.keys(value);
158
- return keys.length > 0 && keys.some((k) => JSONB_OPERATOR_KEYS.has(k));
159
- }
160
- /**
161
- * Returns the first JSON-unique key found in `value`, or `null` if none.
162
- * Used to drive the strict-validation error message.
163
- */
164
- function findJsonUniqueKey(value) {
165
- for (const k of Object.keys(value)) {
166
- if (JSONB_UNIQUE_KEYS.has(k))
167
- return k;
168
- }
169
- return null;
170
- }
171
- /** Known Array operator keys */
172
- const ARRAY_OPERATOR_KEYS = new Set(['has', 'hasEvery', 'hasSome', 'isEmpty']);
173
- /**
174
- * Array operator keys that are *unique* to {@link ArrayFilter}. None of the
175
- * array operators currently overlap with `WhereOperator` or `JsonFilter`, so
176
- * this set equals {@link ARRAY_OPERATOR_KEYS}; it is kept as a separate
177
- * constant so a future overlap (e.g. a `contains` for arrays) is easy to
178
- * carve out.
179
- */
180
- const ARRAY_UNIQUE_KEYS = new Set(['has', 'hasEvery', 'hasSome', 'isEmpty']);
181
- /** Check if a value is an Array filter object */
182
- function isArrayFilter(value) {
183
- if (value === null ||
184
- value === undefined ||
185
- typeof value !== 'object' ||
186
- Array.isArray(value) ||
187
- value instanceof Date) {
188
- return false;
189
- }
190
- const keys = Object.keys(value);
191
- return keys.length > 0 && keys.some((k) => ARRAY_OPERATOR_KEYS.has(k));
192
- }
193
- /**
194
- * Returns the first array-unique key found in `value`, or `null` if none.
195
- * Used to drive the strict-validation error message.
196
- */
197
- function findArrayUniqueKey(value) {
198
- for (const k of Object.keys(value)) {
199
- if (ARRAY_UNIQUE_KEYS.has(k))
200
- return k;
201
- }
202
- return null;
203
- }
204
- /** Known text search operator keys */
205
- const TEXT_SEARCH_KEYS = new Set(['search', 'config']);
206
- /** Check if a value is a TextSearchFilter object */
207
- function isTextSearchFilter(value) {
208
- if (value === null ||
209
- value === undefined ||
210
- typeof value !== 'object' ||
211
- Array.isArray(value) ||
212
- value instanceof Date) {
213
- return false;
214
- }
215
- const keys = Object.keys(value);
216
- // Must have 'search' key and only known text search keys
217
- return keys.includes('search') && keys.every((k) => TEXT_SEARCH_KEYS.has(k));
218
- }
219
- /**
220
- * Validate a text search config name. Only alphanumeric characters and
221
- * underscores are allowed to prevent SQL injection via the config parameter.
222
- */
223
- function validateTextSearchConfig(config) {
224
- return /^[a-zA-Z0-9_]+$/.test(config);
225
- }
226
- /**
227
- * pgvector distance metric → operator allow-list. This is the ONLY mapping
228
- * from a user-supplied metric token to a SQL operator; any token not present
229
- * here is rejected, so a user value can never become an arbitrary operator.
230
- *
231
- * - `l2` → `<->` (Euclidean / L2 distance)
232
- * - `cosine` → `<=>` (cosine distance)
233
- * - `ip` → `<#>` (negative inner product)
234
- */
235
- const VECTOR_METRIC_OPERATORS = {
236
- l2: '<->',
237
- cosine: '<=>',
238
- ip: '<#>',
239
- };
240
- /** Comparison keys allowed on a {@link VectorDistanceFilter}. */
241
- const VECTOR_DISTANCE_COMPARATORS = {
242
- lt: '<',
243
- lte: '<=',
244
- gt: '>',
245
- gte: '>=',
246
- };
247
- /** Check if a value is a vector distance WHERE filter: `{ distance: { to, metric } }` */
248
- function isVectorFilter(value) {
249
- if (value === null || typeof value !== 'object' || Array.isArray(value) || value instanceof Date) {
250
- return false;
251
- }
252
- const dist = value.distance;
253
- return (typeof dist === 'object' &&
254
- dist !== null &&
255
- !Array.isArray(dist) &&
256
- 'to' in dist &&
257
- 'metric' in dist);
258
- }
259
- /** Check if an orderBy value is a vector KNN ordering: `{ distance: { to, metric } }` */
260
- function isVectorOrderBy(value) {
261
- return isVectorFilter(value);
262
- }
263
- /** Check if an orderBy value is an explicit `{ sort, nulls? }` spec. */
264
- function isOrderBySpec(value) {
265
- return typeof value === 'object' && value !== null && !Array.isArray(value) && 'sort' in value;
266
- }
267
- /**
268
- * Normalize an orderBy value into `{ direction, nulls }`. Accepts a plain
269
- * direction string or an {@link OrderBySpec}. Used by every ORDER BY compile
270
- * path (findMany, groupBy, relation inner subqueries).
271
- */
272
- function normalizeOrderBy(value) {
273
- if (isOrderBySpec(value)) {
274
- return { dir: value.sort.toLowerCase() === 'desc' ? 'DESC' : 'ASC', nulls: value.nulls };
275
- }
276
- return { dir: String(value).toLowerCase() === 'desc' ? 'DESC' : 'ASC' };
277
- }
278
59
  // biome-ignore lint/complexity/noBannedTypes: {} means "no relations known" — intentional for untyped table access
279
60
  class QueryInterface {
280
61
  pool;
@@ -788,7 +569,7 @@ class QueryInterface {
788
569
  !whereObj.NOT &&
789
570
  whereKeys.every((k) => {
790
571
  const v = whereObj[k];
791
- return v !== null && !isWhereOperator(v) && !this.tableMeta.relations[k];
572
+ return v !== null && !(0, filters_js_1.isWhereOperator)(v) && !this.tableMeta.relations[k];
792
573
  });
793
574
  // Simple path: plain equality, no operators/null/OR
794
575
  if (!args.with && isSimpleWhere) {
@@ -989,14 +770,14 @@ class QueryInterface {
989
770
  let sql = `SELECT ${distinctPrefix}${selectClause} FROM ${qt}${freshWhereSql}`;
990
771
  if (args?.cursor) {
991
772
  // Sorted (canonical) order — MUST match cursorFp and the cache-hit collect below.
992
- const cursorEntries = sortedEntries(args.cursor).filter(([, v]) => v !== undefined);
773
+ const cursorEntries = (0, filters_js_1.sortedEntries)(args.cursor).filter(([, v]) => v !== undefined);
993
774
  if (cursorEntries.length > 0) {
994
775
  const cursorConditions = cursorEntries.map(([k, v]) => {
995
776
  const col = this.toSqlColumn(k);
996
777
  // orderBy values can be the { sort, nulls } spec form — normalize
997
778
  // before comparing, or a desc spec would seek the ascending side.
998
779
  const dir = args.orderBy?.[k];
999
- const desc = isOrderBySpec(dir) ? dir.sort === 'desc' : dir === 'desc';
780
+ const desc = (0, filters_js_1.isOrderBySpec)(dir) ? dir.sort === 'desc' : dir === 'desc';
1000
781
  const op = desc ? '<' : '>';
1001
782
  freshParams.push(v);
1002
783
  return `${qt}.${col} ${op} ${this.p(freshParams.length)}`;
@@ -1016,7 +797,7 @@ class QueryInterface {
1016
797
  // order") need two levels: inner DISTINCT ON ordered by the distinct
1017
798
  // columns then the user's order (picks the right representative row),
1018
799
  // outer re-ordered by the user's order alone.
1019
- if (Object.values(args.orderBy).some((d) => isVectorOrderBy(d))) {
800
+ if (Object.values(args.orderBy).some((d) => (0, filters_js_1.isVectorOrderBy)(d))) {
1020
801
  throw new errors_js_1.ValidationError('[turbine] `distinct` cannot be combined with vector distance ordering.');
1021
802
  }
1022
803
  const userOrder = this.buildOrderBy(args.orderBy, freshParams);
@@ -1054,7 +835,7 @@ class QueryInterface {
1054
835
  }
1055
836
  // 3. Cursor params — sorted (canonical) order, matching cursorFp and the build path.
1056
837
  if (args?.cursor) {
1057
- const cursorEntries = sortedEntries(args.cursor).filter(([, v]) => v !== undefined);
838
+ const cursorEntries = (0, filters_js_1.sortedEntries)(args.cursor).filter(([, v]) => v !== undefined);
1058
839
  for (const [, v] of cursorEntries) {
1059
840
  params.push(v);
1060
841
  }
@@ -2315,7 +2096,7 @@ class QueryInterface {
2315
2096
  !Buffer.isBuffer(value)) {
2316
2097
  const v = value;
2317
2098
  const keys = Object.keys(v);
2318
- if (keys.length === 1 && UPDATE_OPERATOR_KEYS.has(keys[0])) {
2099
+ if (keys.length === 1 && filters_js_1.UPDATE_OPERATOR_KEYS.has(keys[0])) {
2319
2100
  const op = keys[0];
2320
2101
  const opValue = v[op];
2321
2102
  if (op === 'set') {
@@ -2432,15 +2213,15 @@ class QueryInterface {
2432
2213
  continue;
2433
2214
  }
2434
2215
  // Operator objects
2435
- if (isWhereOperator(value)) {
2436
- parts.push(`${key}:${fingerprintOperatorShape(value)}`);
2216
+ if ((0, filters_js_1.isWhereOperator)(value)) {
2217
+ parts.push(`${key}:${(0, filters_js_1.fingerprintOperatorShape)(value)}`);
2437
2218
  continue;
2438
2219
  }
2439
2220
  // Vector distance filter — metric (operator) and present comparators
2440
2221
  // change the SQL shape, so both go in the fingerprint.
2441
- if (typeof value === 'object' && !Array.isArray(value) && isVectorFilter(value)) {
2222
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isVectorFilter)(value)) {
2442
2223
  const dist = value.distance;
2443
- const cmps = Object.keys(VECTOR_DISTANCE_COMPARATORS)
2224
+ const cmps = Object.keys(filters_js_1.VECTOR_DISTANCE_COMPARATORS)
2444
2225
  .filter((c) => dist[c] !== undefined)
2445
2226
  .sort()
2446
2227
  .join('|');
@@ -2448,18 +2229,18 @@ class QueryInterface {
2448
2229
  continue;
2449
2230
  }
2450
2231
  // JSON filter
2451
- if (typeof value === 'object' && !Array.isArray(value) && isJsonFilter(value)) {
2232
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isJsonFilter)(value)) {
2452
2233
  const jKeys = Object.keys(value).sort();
2453
2234
  parts.push(`${key}:json(${jKeys.join(',')})`);
2454
2235
  continue;
2455
2236
  }
2456
2237
  // Array filter
2457
- if (typeof value === 'object' && !Array.isArray(value) && isArrayFilter(value)) {
2238
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isArrayFilter)(value)) {
2458
2239
  parts.push(`${key}:arr(${this.fingerprintArrayFilter(value)})`);
2459
2240
  continue;
2460
2241
  }
2461
2242
  // Text search filter
2462
- if (typeof value === 'object' && !Array.isArray(value) && isTextSearchFilter(value)) {
2243
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isTextSearchFilter)(value)) {
2463
2244
  const cfg = value.config ?? 'english';
2464
2245
  parts.push(`${key}:fts(${cfg})`);
2465
2246
  continue;
@@ -2468,7 +2249,7 @@ class QueryInterface {
2468
2249
  // fingerprint distinct from real equality. The build path throws for
2469
2250
  // these on non-JSON columns; sharing `key:eq` would let a cache entry
2470
2251
  // warmed by genuine equality serve the bad filter silently.
2471
- if (isUnmatchedPlainObject(value)) {
2252
+ if ((0, filters_js_1.isUnmatchedPlainObject)(value)) {
2472
2253
  parts.push(`${key}:obj(${Object.keys(value)
2473
2254
  .sort()
2474
2255
  .join(',')})`);
@@ -2533,10 +2314,10 @@ class QueryInterface {
2533
2314
  if (value === null) {
2534
2315
  parts.push(`${key}:null`);
2535
2316
  }
2536
- else if (isWhereOperator(value)) {
2537
- parts.push(`${key}:${fingerprintOperatorShape(value)}`);
2317
+ else if ((0, filters_js_1.isWhereOperator)(value)) {
2318
+ parts.push(`${key}:${(0, filters_js_1.fingerprintOperatorShape)(value)}`);
2538
2319
  }
2539
- else if (isUnmatchedPlainObject(value)) {
2320
+ else if ((0, filters_js_1.isUnmatchedPlainObject)(value)) {
2540
2321
  parts.push(`${key}:obj(${Object.keys(value)
2541
2322
  .sort()
2542
2323
  .join(',')})`);
@@ -2556,7 +2337,7 @@ class QueryInterface {
2556
2337
  */
2557
2338
  collectWhereParams(where, params) {
2558
2339
  // Sorted (canonical) order — MUST match fingerprintWhere and buildWhereClause.
2559
- const keys = sortedKeys(where);
2340
+ const keys = (0, filters_js_1.sortedKeys)(where);
2560
2341
  for (const key of keys) {
2561
2342
  const value = where[key];
2562
2343
  if (value === undefined)
@@ -2602,7 +2383,7 @@ class QueryInterface {
2602
2383
  continue;
2603
2384
  const rawColumn = this.toColumn(key);
2604
2385
  // Vector distance filter — mirrors buildVectorFilterClauses push order.
2605
- if (typeof value === 'object' && !Array.isArray(value) && isVectorFilter(value)) {
2386
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isVectorFilter)(value)) {
2606
2387
  // Validate the same way the build path does so the collect path never
2607
2388
  // diverges (it would throw before any param was pushed).
2608
2389
  this.vectorOperator(key, rawColumn, value.distance.metric);
@@ -2610,7 +2391,7 @@ class QueryInterface {
2610
2391
  continue;
2611
2392
  }
2612
2393
  // JSONB filter
2613
- if (typeof value === 'object' && !Array.isArray(value) && isJsonFilter(value)) {
2394
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isJsonFilter)(value)) {
2614
2395
  const colType = this.getColumnPgType(rawColumn);
2615
2396
  if (colType === 'json' || colType === 'jsonb') {
2616
2397
  this.collectJsonFilterParams(value, params);
@@ -2618,7 +2399,7 @@ class QueryInterface {
2618
2399
  }
2619
2400
  }
2620
2401
  // Array filter
2621
- if (typeof value === 'object' && !Array.isArray(value) && isArrayFilter(value)) {
2402
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isArrayFilter)(value)) {
2622
2403
  const colType = this.getColumnPgType(rawColumn);
2623
2404
  if (colType.startsWith('_')) {
2624
2405
  this.collectArrayFilterParams(value, params);
@@ -2626,12 +2407,12 @@ class QueryInterface {
2626
2407
  }
2627
2408
  }
2628
2409
  // Text search filter
2629
- if (typeof value === 'object' && !Array.isArray(value) && isTextSearchFilter(value)) {
2410
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isTextSearchFilter)(value)) {
2630
2411
  params.push(value.search);
2631
2412
  continue;
2632
2413
  }
2633
2414
  // Operator objects
2634
- if (isWhereOperator(value)) {
2415
+ if ((0, filters_js_1.isWhereOperator)(value)) {
2635
2416
  this.collectOperatorParams(rawColumn, value, params);
2636
2417
  continue;
2637
2418
  }
@@ -2685,7 +2466,7 @@ class QueryInterface {
2685
2466
  if (!meta)
2686
2467
  return;
2687
2468
  // Sorted (canonical) order — MUST match fingerprintRelFilter and buildSubWhereForRelation.
2688
- for (const field of sortedKeys(subWhere)) {
2469
+ for (const field of (0, filters_js_1.sortedKeys)(subWhere)) {
2689
2470
  const value = subWhere[field];
2690
2471
  if (value === undefined)
2691
2472
  continue;
@@ -2714,7 +2495,7 @@ class QueryInterface {
2714
2495
  }
2715
2496
  }
2716
2497
  const col = meta.columnMap[field] ?? (0, schema_js_1.camelToSnake)(field);
2717
- if (isWhereOperator(value)) {
2498
+ if ((0, filters_js_1.isWhereOperator)(value)) {
2718
2499
  this.collectOperatorParams(col, value, params);
2719
2500
  continue;
2720
2501
  }
@@ -2725,7 +2506,7 @@ class QueryInterface {
2725
2506
  /** Collect params from operator clauses. Mirrors buildOperatorClauses. */
2726
2507
  collectOperatorParams(column, op, params) {
2727
2508
  if (op.equals !== undefined && op.equals !== null) {
2728
- assertBindableEqualsOperand(op.equals, `"${column}"`);
2509
+ (0, filters_js_1.assertBindableEqualsOperand)(op.equals, `"${column}"`);
2729
2510
  params.push(op.equals);
2730
2511
  }
2731
2512
  if (op.gt !== undefined)
@@ -2783,7 +2564,7 @@ class QueryInterface {
2783
2564
  */
2784
2565
  collectOrderByParams(orderBy, params) {
2785
2566
  for (const [key, dir] of Object.entries(orderBy)) {
2786
- if (isVectorOrderBy(dir)) {
2567
+ if ((0, filters_js_1.isVectorOrderBy)(dir)) {
2787
2568
  const rawColumn = this.toColumn(key);
2788
2569
  // Re-run the same validation as buildOrderBy so the collect path can
2789
2570
  // never push a param that the build path rejected (or vice versa).
@@ -2815,7 +2596,7 @@ class QueryInterface {
2815
2596
  collectVectorFilterParams(field, rawColumn, filter, params) {
2816
2597
  const dist = filter.distance;
2817
2598
  this.pushVectorParam(field, rawColumn, dist.to, params);
2818
- for (const cmp of Object.keys(VECTOR_DISTANCE_COMPARATORS)) {
2599
+ for (const cmp of Object.keys(filters_js_1.VECTOR_DISTANCE_COMPARATORS)) {
2819
2600
  const threshold = dist[cmp];
2820
2601
  if (threshold !== undefined)
2821
2602
  params.push(threshold);
@@ -2911,7 +2692,7 @@ class QueryInterface {
2911
2692
  const meta = this.schema.tables[table ?? this.table];
2912
2693
  if (!meta)
2913
2694
  return;
2914
- for (const [relName, relSpec] of sortedEntries(withClause)) {
2695
+ for (const [relName, relSpec] of (0, filters_js_1.sortedEntries)(withClause)) {
2915
2696
  const relDef = meta.relations[relName];
2916
2697
  if (!relDef)
2917
2698
  continue;
@@ -2948,7 +2729,7 @@ class QueryInterface {
2948
2729
  params.push(Number(spec.limit));
2949
2730
  }
2950
2731
  if (spec.with) {
2951
- for (const [nestedRelName, nestedSpec] of sortedEntries(spec.with)) {
2732
+ for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(spec.with)) {
2952
2733
  const nestedRelDef = targetMeta.relations[nestedRelName];
2953
2734
  if (!nestedRelDef)
2954
2735
  continue;
@@ -2962,7 +2743,7 @@ class QueryInterface {
2962
2743
  const willWrap = relDef.type === 'hasMany' && (spec.limit !== undefined || hasOrder);
2963
2744
  // Non-wrapped path: nested relations BEFORE where/limit
2964
2745
  if (!willWrap && spec.with) {
2965
- for (const [nestedRelName, nestedSpec] of sortedEntries(spec.with)) {
2746
+ for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(spec.with)) {
2966
2747
  const nestedRelDef = targetMeta.relations[nestedRelName];
2967
2748
  if (!nestedRelDef)
2968
2749
  continue;
@@ -2985,7 +2766,7 @@ class QueryInterface {
2985
2766
  }
2986
2767
  // Wrapped path: nested relations AFTER where/limit (inside inner subquery)
2987
2768
  if (willWrap && spec.with) {
2988
- for (const [nestedRelName, nestedSpec] of sortedEntries(spec.with)) {
2769
+ for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(spec.with)) {
2989
2770
  const nestedRelDef = targetMeta.relations[nestedRelName];
2990
2771
  if (!nestedRelDef)
2991
2772
  continue;
@@ -3007,7 +2788,7 @@ class QueryInterface {
3007
2788
  !(v instanceof Date) &&
3008
2789
  !(typeof Buffer !== 'undefined' && Buffer.isBuffer(v))) {
3009
2790
  const keys = Object.keys(v);
3010
- if (keys.length === 1 && UPDATE_OPERATOR_KEYS.has(keys[0])) {
2791
+ if (keys.length === 1 && filters_js_1.UPDATE_OPERATOR_KEYS.has(keys[0])) {
3011
2792
  parts.push(`${k}:${keys[0]}`);
3012
2793
  continue;
3013
2794
  }
@@ -3029,7 +2810,7 @@ class QueryInterface {
3029
2810
  !(typeof Buffer !== 'undefined' && Buffer.isBuffer(v))) {
3030
2811
  const obj = v;
3031
2812
  const keys = Object.keys(obj);
3032
- if (keys.length === 1 && UPDATE_OPERATOR_KEYS.has(keys[0])) {
2813
+ if (keys.length === 1 && filters_js_1.UPDATE_OPERATOR_KEYS.has(keys[0])) {
3033
2814
  params.push(obj[keys[0]]);
3034
2815
  continue;
3035
2816
  }
@@ -3202,7 +2983,7 @@ class QueryInterface {
3202
2983
  */
3203
2984
  buildWhereClause(where, params) {
3204
2985
  // Sorted (canonical) order — MUST match fingerprintWhere and collectWhereParams.
3205
- const keys = sortedKeys(where);
2986
+ const keys = (0, filters_js_1.sortedKeys)(where);
3206
2987
  if (keys.length === 0)
3207
2988
  return null;
3208
2989
  const andClauses = [];
@@ -3270,13 +3051,13 @@ class QueryInterface {
3270
3051
  continue;
3271
3052
  }
3272
3053
  // Handle vector distance filter (pgvector): `{ distance: { to, metric, lt } }`
3273
- if (typeof value === 'object' && !Array.isArray(value) && isVectorFilter(value)) {
3054
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isVectorFilter)(value)) {
3274
3055
  const vecClauses = this.buildVectorFilterClauses(key, rawColumn, value, params);
3275
3056
  andClauses.push(...vecClauses);
3276
3057
  continue;
3277
3058
  }
3278
3059
  // Handle JSONB filter operators (for json/jsonb columns)
3279
- if (typeof value === 'object' && !Array.isArray(value) && isJsonFilter(value)) {
3060
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isJsonFilter)(value)) {
3280
3061
  const colType = this.getColumnPgType(rawColumn);
3281
3062
  if (colType === 'json' || colType === 'jsonb') {
3282
3063
  const jsonClauses = this.buildJsonFilterClauses(column, value, params);
@@ -3288,14 +3069,14 @@ class QueryInterface {
3288
3069
  // equality (the previous behaviour) wasted hours of debugging time. Only
3289
3070
  // throw when the operator is unambiguously JSON-specific — `contains` is
3290
3071
  // shared with WhereOperator's LIKE so it must continue to fall through.
3291
- const jsonKey = findJsonUniqueKey(value);
3072
+ const jsonKey = (0, filters_js_1.findJsonUniqueKey)(value);
3292
3073
  if (jsonKey) {
3293
3074
  throw new errors_js_1.ValidationError(`[turbine] Column "${rawColumn}" on table "${this.table}" is not a JSON column ` +
3294
3075
  `(actual type: ${colType}); cannot apply JSON operator '${jsonKey}'.`);
3295
3076
  }
3296
3077
  }
3297
3078
  // Handle Array filter operators (for array columns)
3298
- if (typeof value === 'object' && !Array.isArray(value) && isArrayFilter(value)) {
3079
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isArrayFilter)(value)) {
3299
3080
  const colType = this.getColumnPgType(rawColumn);
3300
3081
  if (colType.startsWith('_')) {
3301
3082
  const arrayClauses = this.buildArrayFilterClauses(column, value, params, colType);
@@ -3305,20 +3086,20 @@ class QueryInterface {
3305
3086
  // Strict validation: array operators (`has`, `hasEvery`, ...) on a
3306
3087
  // non-array column always indicate a mistake. None of these keys
3307
3088
  // overlap with other filter shapes so we can throw unconditionally.
3308
- const arrayKey = findArrayUniqueKey(value);
3089
+ const arrayKey = (0, filters_js_1.findArrayUniqueKey)(value);
3309
3090
  if (arrayKey) {
3310
3091
  throw new errors_js_1.ValidationError(`[turbine] Column "${rawColumn}" on table "${this.table}" is not an array column ` +
3311
3092
  `(actual type: ${colType}); cannot apply array operator '${arrayKey}'.`);
3312
3093
  }
3313
3094
  }
3314
3095
  // Handle full-text search filter
3315
- if (typeof value === 'object' && !Array.isArray(value) && isTextSearchFilter(value)) {
3096
+ if (typeof value === 'object' && !Array.isArray(value) && (0, filters_js_1.isTextSearchFilter)(value)) {
3316
3097
  const tsClause = this.buildTextSearchClause(column, value, params);
3317
3098
  andClauses.push(tsClause);
3318
3099
  continue;
3319
3100
  }
3320
3101
  // Handle operator objects
3321
- if (isWhereOperator(value)) {
3102
+ if ((0, filters_js_1.isWhereOperator)(value)) {
3322
3103
  const opClauses = this.buildOperatorClauses(column, value, params);
3323
3104
  andClauses.push(...opClauses);
3324
3105
  continue;
@@ -3436,7 +3217,7 @@ class QueryInterface {
3436
3217
  const qt = this.q(targetTable);
3437
3218
  const conditions = [];
3438
3219
  // Sorted (canonical) order — MUST match fingerprintRelFilter and collectRelFilterParams.
3439
- for (const field of sortedKeys(subWhere)) {
3220
+ for (const field of (0, filters_js_1.sortedKeys)(subWhere)) {
3440
3221
  const value = subWhere[field];
3441
3222
  if (value === undefined)
3442
3223
  continue;
@@ -3483,7 +3264,7 @@ class QueryInterface {
3483
3264
  conditions.push(`${qCol} IS NULL`);
3484
3265
  continue;
3485
3266
  }
3486
- if (isWhereOperator(value)) {
3267
+ if ((0, filters_js_1.isWhereOperator)(value)) {
3487
3268
  const opClauses = this.buildOperatorClauses(qCol, value, params);
3488
3269
  conditions.push(...opClauses);
3489
3270
  continue;
@@ -3511,7 +3292,7 @@ class QueryInterface {
3511
3292
  * columns (object equality).
3512
3293
  */
3513
3294
  assertBindableEqualityValue(rawColumn, value, columnPgType, table) {
3514
- if (!isUnmatchedPlainObject(value))
3295
+ if (!(0, filters_js_1.isUnmatchedPlainObject)(value))
3515
3296
  return;
3516
3297
  if (columnPgType === 'json' || columnPgType === 'jsonb')
3517
3298
  return;
@@ -3536,7 +3317,7 @@ class QueryInterface {
3536
3317
  buildAliasWhere(targetTable, targetMeta, alias, where, params) {
3537
3318
  const clauses = [];
3538
3319
  // Sorted (canonical) order — MUST match fingerprintAliasWhere and collectAliasWhereParams.
3539
- for (const key of sortedKeys(where)) {
3320
+ for (const key of (0, filters_js_1.sortedKeys)(where)) {
3540
3321
  const value = where[key];
3541
3322
  if (value === undefined)
3542
3323
  continue;
@@ -3579,7 +3360,7 @@ class QueryInterface {
3579
3360
  clauses.push(`${qCol} IS NULL`);
3580
3361
  continue;
3581
3362
  }
3582
- if (isWhereOperator(value)) {
3363
+ if ((0, filters_js_1.isWhereOperator)(value)) {
3583
3364
  clauses.push(...this.buildOperatorClauses(qCol, value, params));
3584
3365
  continue;
3585
3366
  }
@@ -3592,7 +3373,7 @@ class QueryInterface {
3592
3373
  /** Mirrors {@link buildAliasWhere} param-push order for the cache-hit collect path. */
3593
3374
  collectAliasWhereParams(targetTable, targetMeta, where, params) {
3594
3375
  // Sorted (canonical) order — MUST match fingerprintAliasWhere and buildAliasWhere.
3595
- for (const key of sortedKeys(where)) {
3376
+ for (const key of (0, filters_js_1.sortedKeys)(where)) {
3596
3377
  const value = where[key];
3597
3378
  if (value === undefined)
3598
3379
  continue;
@@ -3622,7 +3403,7 @@ class QueryInterface {
3622
3403
  }
3623
3404
  }
3624
3405
  const col = targetMeta.columnMap[key] ?? (0, schema_js_1.camelToSnake)(key);
3625
- if (isWhereOperator(value)) {
3406
+ if ((0, filters_js_1.isWhereOperator)(value)) {
3626
3407
  this.collectOperatorParams(col, value, params);
3627
3408
  continue;
3628
3409
  }
@@ -3676,11 +3457,11 @@ class QueryInterface {
3676
3457
  continue;
3677
3458
  }
3678
3459
  }
3679
- if (isWhereOperator(value)) {
3680
- parts.push(`${key}:${fingerprintOperatorShape(value)}`);
3460
+ if ((0, filters_js_1.isWhereOperator)(value)) {
3461
+ parts.push(`${key}:${(0, filters_js_1.fingerprintOperatorShape)(value)}`);
3681
3462
  continue;
3682
3463
  }
3683
- if (isUnmatchedPlainObject(value)) {
3464
+ if ((0, filters_js_1.isUnmatchedPlainObject)(value)) {
3684
3465
  parts.push(`${key}:obj(${Object.keys(value)
3685
3466
  .sort()
3686
3467
  .join(',')})`);
@@ -3701,7 +3482,7 @@ class QueryInterface {
3701
3482
  clauses.push(`${column} IS NULL`);
3702
3483
  }
3703
3484
  else {
3704
- assertBindableEqualsOperand(op.equals, column);
3485
+ (0, filters_js_1.assertBindableEqualsOperand)(op.equals, column);
3705
3486
  params.push(op.equals);
3706
3487
  clauses.push(`${column} = ${this.p(params.length)}`);
3707
3488
  }
@@ -3773,10 +3554,10 @@ class QueryInterface {
3773
3554
  orderByEntryFingerprint(d) {
3774
3555
  // Vector KNN ordering changes the emitted operator by metric and adds a
3775
3556
  // `::vector` param, so metric + direction must be part of the cache key.
3776
- if (isVectorOrderBy(d)) {
3557
+ if ((0, filters_js_1.isVectorOrderBy)(d)) {
3777
3558
  return `vec(${d.distance.metric},${d.distance.direction ?? 'asc'})`;
3778
3559
  }
3779
- if (isOrderBySpec(d))
3560
+ if ((0, filters_js_1.isOrderBySpec)(d))
3780
3561
  return `spec(${d.sort},${d.nulls ?? ''})`;
3781
3562
  if (d && typeof d === 'object') {
3782
3563
  // Relation ordering (`{ _count: 'desc' }` or `{ name: 'asc' }`).
@@ -3807,7 +3588,7 @@ class QueryInterface {
3807
3588
  return Object.entries(orderBy)
3808
3589
  .map(([key, value]) => {
3809
3590
  // Vector KNN ordering: { distance: { to, metric, direction? } }
3810
- if (isVectorOrderBy(value)) {
3591
+ if ((0, filters_js_1.isVectorOrderBy)(value)) {
3811
3592
  if (meta && !(key in meta.columnMap)) {
3812
3593
  throw new errors_js_1.ValidationError(`[turbine] Unknown field "${key}" in orderBy on table "${this.table}". ` +
3813
3594
  `Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`);
@@ -3832,7 +3613,7 @@ class QueryInterface {
3832
3613
  throw new errors_js_1.ValidationError(`[turbine] Unknown field "${key}" in orderBy on table "${this.table}". ` +
3833
3614
  `Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`);
3834
3615
  }
3835
- const { dir, nulls } = normalizeOrderBy(value);
3616
+ const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(value);
3836
3617
  return `${this.toSqlColumn(key)} ${dir}${this.nullsSuffix(nulls)}`;
3837
3618
  })
3838
3619
  .join(', ');
@@ -3846,8 +3627,8 @@ class QueryInterface {
3846
3627
  return (typeof value === 'object' &&
3847
3628
  value !== null &&
3848
3629
  !Array.isArray(value) &&
3849
- !isVectorOrderBy(value) &&
3850
- !isOrderBySpec(value));
3630
+ !(0, filters_js_1.isVectorOrderBy)(value) &&
3631
+ !(0, filters_js_1.isOrderBySpec)(value));
3851
3632
  }
3852
3633
  /**
3853
3634
  * Render the ` NULLS FIRST` / ` NULLS LAST` suffix for a column ordering.
@@ -3885,7 +3666,7 @@ class QueryInterface {
3885
3666
  throw new errors_js_1.ValidationError(`[turbine] orderBy on to-many relation "${relName}" only supports "_count" ` +
3886
3667
  `(got: ${keys.join(', ') || '(empty)'}).`);
3887
3668
  }
3888
- const { dir } = normalizeOrderBy(value._count);
3669
+ const { dir } = (0, filters_js_1.normalizeOrderBy)(value._count);
3889
3670
  return `${this.buildRelationCountExpr(relDef, this.table, alias, params)} ${dir}`;
3890
3671
  }
3891
3672
  // To-one: each entry orders by a correlated scalar subquery on a target column.
@@ -3908,7 +3689,7 @@ class QueryInterface {
3908
3689
  if (!targetMeta.allColumns.includes(snakeCol)) {
3909
3690
  throw new errors_js_1.ValidationError(`[turbine] Unknown column "${col}" in orderBy on relation "${relName}" (table "${relDef.to}").`);
3910
3691
  }
3911
- const { dir, nulls } = normalizeOrderBy(dirValue);
3692
+ const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(dirValue);
3912
3693
  // Target's global filter applies here too — otherwise ordering keys off
3913
3694
  // a soft-deleted / other-tenant related row's value (matches the with
3914
3695
  // subquery semantics for belongsTo/hasOne).
@@ -4032,10 +3813,10 @@ class QueryInterface {
4032
3813
  throw new errors_js_1.ValidationError(`[turbine] Column "${field}" on table "${this.table}" is not a vector column ` +
4033
3814
  `(actual type: ${colType}); cannot apply a vector distance operation.`);
4034
3815
  }
4035
- const op = VECTOR_METRIC_OPERATORS[metric];
3816
+ const op = filters_js_1.VECTOR_METRIC_OPERATORS[metric];
4036
3817
  if (!op) {
4037
3818
  throw new errors_js_1.ValidationError(`[turbine] Unknown vector metric "${metric}" for column "${field}". ` +
4038
- `Valid metrics: ${Object.keys(VECTOR_METRIC_OPERATORS).join(', ')}.`);
3819
+ `Valid metrics: ${Object.keys(filters_js_1.VECTOR_METRIC_OPERATORS).join(', ')}.`);
4039
3820
  }
4040
3821
  return op;
4041
3822
  }
@@ -4264,7 +4045,7 @@ class QueryInterface {
4264
4045
  if (!meta)
4265
4046
  return {};
4266
4047
  const shapes = {};
4267
- for (const [relName, relSpec] of sortedEntries(withClause)) {
4048
+ for (const [relName, relSpec] of (0, filters_js_1.sortedEntries)(withClause)) {
4268
4049
  const relDef = meta.relations[relName];
4269
4050
  if (!relDef)
4270
4051
  continue; // buildSelectWithRelations already threw for this
@@ -4287,7 +4068,7 @@ class QueryInterface {
4287
4068
  const keys = targetColumns.map((col) => targetMeta.reverseColumnMap[col] ?? (0, schema_js_1.snakeToCamel)(col));
4288
4069
  const nested = {};
4289
4070
  if (spec !== true && spec.with) {
4290
- for (const [nestedRelName, nestedSpec] of sortedEntries(spec.with)) {
4071
+ for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(spec.with)) {
4291
4072
  const nestedRelDef = targetMeta.relations[nestedRelName];
4292
4073
  if (!nestedRelDef)
4293
4074
  continue;
@@ -4420,7 +4201,7 @@ class QueryInterface {
4420
4201
  const baseCols = cols.map((col) => `${qtbl}.${this.q(col)}`).join(', ');
4421
4202
  const relationSelects = [];
4422
4203
  const aliasCounter = { n: 0 };
4423
- for (const [relName, relSpec] of sortedEntries(withClause)) {
4204
+ for (const [relName, relSpec] of (0, filters_js_1.sortedEntries)(withClause)) {
4424
4205
  // `_count` is a reserved key handled after the relation subqueries.
4425
4206
  if (relName === '_count')
4426
4207
  continue;
@@ -4624,7 +4405,7 @@ class QueryInterface {
4624
4405
  }
4625
4406
  // Nested relations — only in the non-wrapped path (wrapped path builds them separately)
4626
4407
  if (!willWrap && spec !== true && spec.with) {
4627
- for (const [nestedRelName, nestedSpec] of sortedEntries(spec.with)) {
4408
+ for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(spec.with)) {
4628
4409
  const nestedRelDef = targetMeta.relations[nestedRelName];
4629
4410
  if (!nestedRelDef) {
4630
4411
  throw new errors_js_1.RelationError(`[turbine] Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
@@ -4650,7 +4431,7 @@ class QueryInterface {
4650
4431
  if (!targetMeta.allColumns.includes(col)) {
4651
4432
  throw new errors_js_1.ValidationError(`[turbine] Unknown column "${k}" in orderBy for table "${targetTable}"`);
4652
4433
  }
4653
- const { dir, nulls } = normalizeOrderBy(dirValue);
4434
+ const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(dirValue);
4654
4435
  return `${alias}.${this.q(col)} ${dir}${this.nullsSuffix(nulls)}`;
4655
4436
  })
4656
4437
  .join(', ');
@@ -4708,7 +4489,7 @@ class QueryInterface {
4708
4489
  ]);
4709
4490
  // Build nested relation subqueries referencing innerAlias
4710
4491
  if (spec !== true && spec.with) {
4711
- for (const [nestedRelName, nestedSpec] of sortedEntries(spec.with)) {
4492
+ for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(spec.with)) {
4712
4493
  const nestedRelDef = targetMeta.relations[nestedRelName];
4713
4494
  if (!nestedRelDef) {
4714
4495
  throw new errors_js_1.RelationError(`[turbine] Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
@@ -4798,7 +4579,7 @@ class QueryInterface {
4798
4579
  if (!targetMeta.allColumns.includes(col)) {
4799
4580
  throw new errors_js_1.ValidationError(`[turbine] Unknown column "${k}" in orderBy for table "${targetTable}"`);
4800
4581
  }
4801
- const { dir, nulls } = normalizeOrderBy(dirValue);
4582
+ const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(dirValue);
4802
4583
  return `${talias}.${this.q(col)} ${dir}${this.nullsSuffix(nulls)}`;
4803
4584
  })
4804
4585
  .join(', ');
@@ -4834,7 +4615,7 @@ class QueryInterface {
4834
4615
  ]);
4835
4616
  // Nested relations reference the inner alias.
4836
4617
  if (spec !== true && spec.with) {
4837
- for (const [nestedRelName, nestedSpec] of sortedEntries(spec.with)) {
4618
+ for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(spec.with)) {
4838
4619
  const nestedRelDef = targetMeta.relations[nestedRelName];
4839
4620
  if (!nestedRelDef) {
4840
4621
  throw new errors_js_1.RelationError(`[turbine] Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
@@ -4857,7 +4638,7 @@ class QueryInterface {
4857
4638
  `${talias}.${this.q(col)}`,
4858
4639
  ]);
4859
4640
  if (spec !== true && spec.with) {
4860
- for (const [nestedRelName, nestedSpec] of sortedEntries(spec.with)) {
4641
+ for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(spec.with)) {
4861
4642
  const nestedRelDef = targetMeta.relations[nestedRelName];
4862
4643
  if (!nestedRelDef) {
4863
4644
  throw new errors_js_1.RelationError(`[turbine] Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
@@ -4983,7 +4764,7 @@ class QueryInterface {
4983
4764
  const placeholder = this.pushVectorParam(field, rawColumn, dist.to, params);
4984
4765
  const distanceExpr = `${this.q(rawColumn)} ${operator} ${placeholder}`;
4985
4766
  const clauses = [];
4986
- for (const [cmp, sqlOp] of Object.entries(VECTOR_DISTANCE_COMPARATORS)) {
4767
+ for (const [cmp, sqlOp] of Object.entries(filters_js_1.VECTOR_DISTANCE_COMPARATORS)) {
4987
4768
  const threshold = dist[cmp];
4988
4769
  if (threshold === undefined)
4989
4770
  continue;
@@ -5005,7 +4786,7 @@ class QueryInterface {
5005
4786
  */
5006
4787
  buildTextSearchClause(column, filter, params) {
5007
4788
  const config = filter.config ?? 'english';
5008
- if (!validateTextSearchConfig(config)) {
4789
+ if (!(0, filters_js_1.validateTextSearchConfig)(config)) {
5009
4790
  throw new errors_js_1.ValidationError(`[turbine] Invalid text search config "${config}": only alphanumeric characters and underscores are allowed.`);
5010
4791
  }
5011
4792
  params.push(filter.search);