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.
- package/README.md +4 -4
- package/dist/cjs/errors.js +15 -1
- package/dist/cjs/powdb.js +1 -1
- package/dist/cjs/powql.js +1 -1
- package/dist/cjs/query/builder.js +75 -294
- package/dist/cjs/query/deferred.js +7 -0
- package/dist/cjs/query/filters.js +251 -0
- package/dist/errors.js +15 -1
- package/dist/powdb.d.ts +1 -1
- package/dist/powdb.js +1 -1
- package/dist/powql.d.ts +1 -1
- package/dist/powql.js +1 -1
- package/dist/query/builder.d.ts +3 -122
- package/dist/query/builder.js +1 -220
- package/dist/query/deferred.d.ts +130 -0
- package/dist/query/deferred.js +6 -0
- package/dist/query/filters.d.ts +120 -0
- package/dist/query/filters.js +232 -0
- package/package.json +2 -2
|
@@ -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);
|