@osqd/jql 0.1.1 → 0.1.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/conformance/cases.json +2 -0
  3. package/dist/chunk-2ZMIVDES.cjs +1661 -0
  4. package/dist/chunk-2ZMIVDES.cjs.map +1 -0
  5. package/dist/chunk-6PWYYYWU.js +1637 -0
  6. package/dist/chunk-6PWYYYWU.js.map +1 -0
  7. package/dist/chunk-74QKHM6Y.cjs +145 -0
  8. package/dist/chunk-74QKHM6Y.cjs.map +1 -0
  9. package/dist/chunk-ATRXHPBJ.js +112 -0
  10. package/dist/chunk-ATRXHPBJ.js.map +1 -0
  11. package/dist/chunk-EJFHVGBC.cjs +63 -0
  12. package/dist/chunk-EJFHVGBC.cjs.map +1 -0
  13. package/dist/chunk-ITPY6VTV.cjs +115 -0
  14. package/dist/chunk-ITPY6VTV.cjs.map +1 -0
  15. package/dist/chunk-JK6OYDLX.js +712 -0
  16. package/dist/chunk-JK6OYDLX.js.map +1 -0
  17. package/dist/chunk-N7DS4QNW.js +60 -0
  18. package/dist/chunk-N7DS4QNW.js.map +1 -0
  19. package/dist/chunk-PNBQ2HIJ.js +133 -0
  20. package/dist/chunk-PNBQ2HIJ.js.map +1 -0
  21. package/dist/chunk-VMFEQXBB.js +578 -0
  22. package/dist/chunk-VMFEQXBB.js.map +1 -0
  23. package/dist/cjs/text/index.d.ts +1 -1
  24. package/dist/cjs/text/parse.d.ts +2 -1
  25. package/dist/cjs/text/quote.d.ts +17 -0
  26. package/dist/cjs/text/suggest.d.ts +27 -5
  27. package/dist/cli.js +13 -2395
  28. package/dist/cli.js.map +1 -1
  29. package/dist/global.cjs +26 -1849
  30. package/dist/global.cjs.map +1 -1
  31. package/dist/global.js +8 -1831
  32. package/dist/global.js.map +1 -1
  33. package/dist/index.cjs +143 -1996
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.js +5 -2493
  36. package/dist/index.js.map +1 -1
  37. package/dist/mongo.cjs +20 -119
  38. package/dist/mongo.cjs.map +1 -1
  39. package/dist/mongo.js +2 -101
  40. package/dist/mongo.js.map +1 -1
  41. package/dist/text/index.d.ts +1 -1
  42. package/dist/text/parse.d.ts +2 -1
  43. package/dist/text/quote.d.ts +17 -0
  44. package/dist/text/suggest.d.ts +27 -5
  45. package/dist/text.cjs +146 -101
  46. package/dist/text.cjs.map +1 -1
  47. package/dist/text.js +3 -665
  48. package/dist/text.js.map +1 -1
  49. package/docs/course/11-the-search-box.md +49 -2
  50. package/docs/course/16-extending.md +1 -1
  51. package/docs/reference/api.md +14 -1
  52. package/docs/reference/specification.md +9 -2
  53. package/docs/reference/text-syntax.md +41 -6
  54. package/package.json +2 -2
@@ -0,0 +1,578 @@
1
+ import { FIELD_OPERATORS, QUERY_OPERATORS, reachField, compile, compareValues, CASE_AWARE, validate, planRequest } from './chunk-6PWYYYWU.js';
2
+ import { checkVocabulary } from './chunk-N7DS4QNW.js';
3
+ import { JqlError, describe, isPlainObject, isFieldReference, isDateLiteral, resolveDate } from './chunk-PNBQ2HIJ.js';
4
+
5
+ // src/group.ts
6
+ function group(source, by, options = {}) {
7
+ if (typeof by !== "string") throw new JqlError(`a group is named by a field, not ${describe(by)}`, "by");
8
+ if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 0)) {
9
+ throw new JqlError(`is a whole number of groups, zero or more, not ${describe(options.limit)}`, "limit");
10
+ }
11
+ if (options.items !== void 0 && typeof options.items !== "boolean" && (!Number.isInteger(options.items) || options.items < 0)) {
12
+ throw new JqlError(`is true, false, or how many items to keep, not ${describe(options.items)}`, "items");
13
+ }
14
+ const reach = reachField(by, checkVocabulary(options.vocabulary), "by");
15
+ const test = options.where === void 0 ? void 0 : compile(options.where, options);
16
+ const keep = options.items === true ? Number.POSITIVE_INFINITY : options.items === void 0 || options.items === false ? 0 : options.items;
17
+ const groups = /* @__PURE__ */ new Map();
18
+ const keys = [];
19
+ const visit = (item) => {
20
+ if (test !== void 0 && !test(item)) return;
21
+ keys.length = 0;
22
+ reach(item, (value) => {
23
+ collect(value, keys);
24
+ return false;
25
+ });
26
+ for (const key of keys) {
27
+ let held = groups.get(key);
28
+ if (held === void 0) {
29
+ held = { key, count: 0, items: [] };
30
+ groups.set(key, held);
31
+ }
32
+ held.count++;
33
+ if (held.items.length < keep) held.items.push(item);
34
+ }
35
+ };
36
+ if (Array.isArray(source)) for (let i = 0; i < source.length; i++) visit(source[i]);
37
+ else if (source instanceof Map) for (const item of source.values()) visit(item);
38
+ else if (typeof source[Symbol.iterator] === "function") for (const item of source) visit(item);
39
+ else {
40
+ const list = source;
41
+ for (let i = 0; i < list.length; i++) visit(list[i]);
42
+ }
43
+ const byKey = options.sort === "key";
44
+ const found = [...groups.values()].sort((a, b) => byKey ? compareValues(a.key, b.key) : b.count - a.count || compareValues(a.key, b.key));
45
+ const limited = options.limit === void 0 ? found : found.slice(0, options.limit);
46
+ return limited.map((held) => keep > 0 ? { key: held.key, count: held.count, items: held.items } : { key: held.key, count: held.count });
47
+ }
48
+ function collect(value, into) {
49
+ if (Array.isArray(value)) {
50
+ if (value.length === 0) {
51
+ if (!into.includes(null)) into.push(null);
52
+ return;
53
+ }
54
+ for (const element of value) collect(element, into);
55
+ return;
56
+ }
57
+ const key = value === void 0 || value === null || typeof value === "object" ? null : value;
58
+ if (!into.includes(key)) into.push(key);
59
+ }
60
+
61
+ // src/explain.ts
62
+ var FIELD_OPERATOR_SET = new Set(FIELD_OPERATORS);
63
+ function explain(query, item, options = {}) {
64
+ return node(query, item, "", options);
65
+ }
66
+ function matchedBy(clause, item, options) {
67
+ return compile(clause, options)(item);
68
+ }
69
+ function node(clause, item, at, options) {
70
+ if (typeof clause === "function" || !isPlainObject(clause)) {
71
+ return leaf(clause, item, at, options, typeof clause === "function" ? "a predicate of your own" : "the whole query");
72
+ }
73
+ const keys = Object.keys(clause);
74
+ if (keys.length === 0) return { matched: true, at, clause, because: "an empty query matches everything" };
75
+ if (keys.length > 1) {
76
+ const self = keys.filter((key2) => key2 !== "$not" && FIELD_OPERATOR_SET.has(key2));
77
+ const rest = keys.filter((key2) => !self.includes(key2));
78
+ const parts = rest.map((key2) => node({ [key2]: clause[key2] }, item, at, options));
79
+ if (self.length > 0) {
80
+ const whole = {};
81
+ for (const key2 of self) whole[key2] = clause[key2];
82
+ parts.push(leaf(whole, item, at, options, self[0]));
83
+ }
84
+ return branch(clause, at, parts, "and");
85
+ }
86
+ const key = keys[0];
87
+ const value = clause[key];
88
+ const here = join(at, key);
89
+ switch (key) {
90
+ case "$and":
91
+ case "$or":
92
+ case "$nor": {
93
+ if (!Array.isArray(value)) break;
94
+ const parts = value.map((part, index) => node(part, item, `${here}[${index}]`, options));
95
+ return branch(clause, here, parts, key === "$and" ? "and" : key === "$or" ? "or" : "nor");
96
+ }
97
+ case "$not": {
98
+ const inner = node(value, item, here, options);
99
+ return {
100
+ matched: !inner.matched,
101
+ at: here,
102
+ clause,
103
+ because: inner.matched ? "what it negates holds" : "what it negates does not hold",
104
+ parts: [inner]
105
+ };
106
+ }
107
+ }
108
+ if (!key.startsWith("$") && isPlainObject(value) && !isFieldReference(value) && Object.keys(value).length > 1 && Object.keys(value).every((name) => name.startsWith("$"))) {
109
+ const operators = Object.keys(value).filter((name) => name !== "$options");
110
+ const flags = value.$options === void 0 ? {} : { $options: value.$options };
111
+ const parts = operators.map((name) => leaf({ [key]: { [name]: value[name], ...CASE_AWARE.has(name) ? flags : {} } }, item, join(here, name), options, key));
112
+ return branch(clause, here, parts, "and");
113
+ }
114
+ return leaf(clause, item, here, options, key);
115
+ }
116
+ function branch(clause, at, parts, kind) {
117
+ const held = parts.filter((part) => part.matched).length;
118
+ const matched = kind === "and" ? held === parts.length : kind === "or" ? held > 0 : held === 0;
119
+ const many = parts.length === 1 ? "part" : "parts";
120
+ const because = kind === "nor" ? `${held} of ${parts.length} ${many} hold, and none may` : `${held} of ${parts.length} ${many} hold${kind === "or" ? ", and one is enough" : ""}`;
121
+ return { matched, at, clause, because, parts };
122
+ }
123
+ function leaf(clause, item, at, options, key) {
124
+ const matched = matchedBy(clause, item, options);
125
+ return { matched, at, clause, because: reason(clause, item, options, key) };
126
+ }
127
+ function reason(clause, item, options, key) {
128
+ if (key === "$text") return matchedBy(clause, item, options) ? "the item holds that text" : "no value of the item holds that text";
129
+ if (key === "$comment") return "a comment decides nothing";
130
+ if (key.startsWith("$") && FIELD_OPERATOR_SET.has(key)) return `the item itself is ${values(item === void 0 ? [] : [item])}`;
131
+ if (!isPlainObject(clause)) return "the clause was run as given";
132
+ const path = Object.keys(clause)[0];
133
+ if (path === void 0 || path.startsWith("$")) return "the clause was run as given";
134
+ const found = [];
135
+ try {
136
+ reachField(path, options.vocabulary, path)(item, (value) => {
137
+ if (found.length < 4) found.push(value);
138
+ return false;
139
+ });
140
+ } catch {
141
+ return "the clause was run as given";
142
+ }
143
+ return `${path} is ${values(found)}`;
144
+ }
145
+ function values(found) {
146
+ if (found.length === 0 || found.length === 1 && found[0] === void 0) return "missing";
147
+ const written = found.map((value) => value === void 0 ? "missing" : shorten(value));
148
+ return written.length === 1 ? written[0] : `each of ${written.join(", ")}`;
149
+ }
150
+ function shorten(value) {
151
+ if (value instanceof Date) return value.toISOString();
152
+ let text2;
153
+ try {
154
+ text2 = JSON.stringify(value) ?? String(value);
155
+ } catch {
156
+ return "a value that cannot be written out";
157
+ }
158
+ return text2.length > 60 ? `${text2.slice(0, 60)}\u2026` : text2;
159
+ }
160
+ function join(at, key) {
161
+ return at === "" ? key : `${at}.${key}`;
162
+ }
163
+
164
+ // src/canonical.ts
165
+ var SETS = /* @__PURE__ */ new Set(["$in", "$nin", "$all", "$contains", "$startsWith", "$endsWith", "$word", "$glob"]);
166
+ var QUERIES = /* @__PURE__ */ new Set(["$elemMatch"]);
167
+ var LOGIC = /* @__PURE__ */ new Set(["$and", "$or", "$nor"]);
168
+ function canonical(query, options) {
169
+ if (typeof query === "function") throw new TypeError("a compiled predicate has no canonical form: it is code, not a query");
170
+ compile(query, options);
171
+ return rewrite(query);
172
+ }
173
+ function fingerprint(query, options) {
174
+ return checksum(JSON.stringify(canonical(query, options)) ?? "");
175
+ }
176
+ function rewrite(value) {
177
+ if (!isPlainObject(value)) return value;
178
+ if (isDateLiteral(value) || isFieldReference(value)) return value;
179
+ const out = {};
180
+ const keys = Object.keys(value).sort().filter((key) => key !== "$comment");
181
+ const alone = keys.length === 1;
182
+ for (const key of keys) {
183
+ const held = value[key];
184
+ if (LOGIC.has(key) && Array.isArray(held)) {
185
+ const parts = flatten(key, held).map(rewrite).sort(byText);
186
+ if (parts.length === 1 && key !== "$nor" && alone) return parts[0];
187
+ out[key] = parts;
188
+ continue;
189
+ }
190
+ if (key === "$not") {
191
+ out[key] = rewrite(held);
192
+ continue;
193
+ }
194
+ if (key === "$text") {
195
+ out[key] = text(held);
196
+ continue;
197
+ }
198
+ if (key.startsWith("$")) {
199
+ out[key] = operand(key, held);
200
+ continue;
201
+ }
202
+ out[key] = condition(held);
203
+ }
204
+ return fold(out);
205
+ }
206
+ function fold(out) {
207
+ const parts = out.$and;
208
+ if (!Array.isArray(parts) || parts.length === 0) return out;
209
+ const taken = new Set(Object.keys(out).filter((key) => key !== "$and"));
210
+ const lifted = {};
211
+ const kept = [];
212
+ for (const part of parts) {
213
+ const keys = isPlainObject(part) ? Object.keys(part) : void 0;
214
+ if (keys === void 0 || keys.length === 0 || keys.some((key) => taken.has(key))) {
215
+ kept.push(part);
216
+ continue;
217
+ }
218
+ for (const key of keys) {
219
+ taken.add(key);
220
+ lifted[key] = part[key];
221
+ }
222
+ }
223
+ if (Object.keys(lifted).length === 0) return out;
224
+ const merged = { ...lifted };
225
+ for (const [key, value] of Object.entries(out)) if (key !== "$and") merged[key] = value;
226
+ if (kept.length > 0) merged.$and = kept;
227
+ const sorted = {};
228
+ for (const key of Object.keys(merged).sort()) sorted[key] = merged[key];
229
+ return sorted;
230
+ }
231
+ function condition(value) {
232
+ if (isPlainObject(value) && !isDateLiteral(value) && !isFieldReference(value) && Object.keys(value).length > 0 && Object.keys(value).every((key) => key.startsWith("$"))) {
233
+ const out = {};
234
+ for (const key of Object.keys(value).sort()) out[key] = key === "$not" ? condition(value[key]) : operand(key, value[key]);
235
+ return out;
236
+ }
237
+ return { $eq: literal(value) };
238
+ }
239
+ function operand(key, value) {
240
+ if (SETS.has(key) && Array.isArray(value)) return unique(value.map(literal));
241
+ if (QUERIES.has(key)) return rewrite(value);
242
+ if (key === "$type" && Array.isArray(value)) {
243
+ const types = unique(value);
244
+ return types.length === 1 ? types[0] : types;
245
+ }
246
+ if (key === "$options" && typeof value === "string") return [...value].sort().join("");
247
+ if ((key === "$size" || key === "$length") && isPlainObject(value)) return condition(value);
248
+ return literal(value);
249
+ }
250
+ function literal(value) {
251
+ if (Array.isArray(value)) return value.map(literal);
252
+ if (!isPlainObject(value) || isDateLiteral(value) || isFieldReference(value)) return value;
253
+ const out = {};
254
+ for (const key of Object.keys(value).sort()) out[key] = literal(value[key]);
255
+ return out;
256
+ }
257
+ function text(value) {
258
+ const search2 = typeof value === "string" ? { $search: value } : isPlainObject(value) ? value : void 0;
259
+ if (search2 === void 0) return value;
260
+ const out = { $search: search2.$search };
261
+ if (Array.isArray(search2.$fields)) out.$fields = unique(search2.$fields);
262
+ if (search2.$caseSensitive === true) out.$caseSensitive = true;
263
+ return out;
264
+ }
265
+ function flatten(key, parts) {
266
+ if (key === "$nor") return [...parts];
267
+ const out = [];
268
+ for (const part of parts) {
269
+ const inner = isPlainObject(part) && Object.keys(part).length === 1 ? part[key] : void 0;
270
+ if (Array.isArray(inner)) out.push(...flatten(key, inner));
271
+ else out.push(part);
272
+ }
273
+ return out;
274
+ }
275
+ function byText(a, b) {
276
+ const left = JSON.stringify(a) ?? "";
277
+ const right = JSON.stringify(b) ?? "";
278
+ return left < right ? -1 : left > right ? 1 : 0;
279
+ }
280
+ function unique(values2) {
281
+ const seen = /* @__PURE__ */ new Map();
282
+ for (const value of values2) {
283
+ const written = JSON.stringify(value) ?? String(value);
284
+ if (!seen.has(written)) seen.set(written, value);
285
+ }
286
+ return [...seen.values()].sort(byText);
287
+ }
288
+ function checksum(text2) {
289
+ let low = 2166136261;
290
+ let high = 16777619;
291
+ for (let i = 0; i < text2.length; i++) {
292
+ const code = text2.charCodeAt(i);
293
+ low = Math.imul(low ^ code, 16777619) >>> 0;
294
+ high = Math.imul(high ^ code + i, 2246822507) >>> 0;
295
+ }
296
+ return low.toString(16).padStart(8, "0") + high.toString(16).padStart(8, "0");
297
+ }
298
+
299
+ // src/plan.ts
300
+ var KNOWN = /* @__PURE__ */ new Set([...FIELD_OPERATORS, ...QUERY_OPERATORS, "$field"]);
301
+ function plan(query, capabilities, options = {}) {
302
+ if (typeof query === "function") throw new TypeError("a compiled predicate cannot be split: it is code, not a query");
303
+ const verdict = validate(query, options);
304
+ if (!verdict.valid) throw verdict.error;
305
+ const now = options.now === void 0 ? Date.now() : options.now();
306
+ const vocabulary = options.vocabulary;
307
+ const fields = capabilities.fields ?? "all";
308
+ const operators = capabilities.operators ?? "all";
309
+ const context = {
310
+ vocabulary,
311
+ now,
312
+ fields: fields === "all" ? "all" : new Set(fields),
313
+ operators: operators === "all" ? "all" : new Set(operators),
314
+ or: capabilities.or ?? false,
315
+ not: capabilities.not ?? false,
316
+ accepts: capabilities.accepts
317
+ };
318
+ const pushed = [];
319
+ const stays = [];
320
+ const kept = [];
321
+ for (const { clause, at } of conjuncts(query, "")) {
322
+ const why = unpushable(clause, context);
323
+ if (why === void 0) pushed.push(resolve(clause, context));
324
+ else {
325
+ stays.push(clause);
326
+ kept.push({ at, clause, why });
327
+ }
328
+ }
329
+ return {
330
+ pushed: join2(pushed),
331
+ remaining: join2(stays),
332
+ complete: stays.length === 0,
333
+ kept
334
+ };
335
+ }
336
+ function conjuncts(query, at) {
337
+ const out = [];
338
+ for (const [key, value] of Object.entries(query)) {
339
+ const here = at === "" ? key : `${at}.${key}`;
340
+ if (key === "$comment") continue;
341
+ if (key === "$and" && Array.isArray(value)) {
342
+ value.forEach((part, index) => {
343
+ if (isPlainObject(part)) out.push(...conjuncts(part, `${here}[${index}]`));
344
+ });
345
+ continue;
346
+ }
347
+ out.push({ clause: { [key]: value }, at: here });
348
+ }
349
+ return out;
350
+ }
351
+ function join2(parts) {
352
+ if (parts.length === 0) return void 0;
353
+ const query = parts.length === 1 ? parts[0] : { $and: parts };
354
+ return query;
355
+ }
356
+ function unpushable(clause, context, at) {
357
+ for (const [key, value] of Object.entries(clause)) {
358
+ if (key.startsWith("$")) {
359
+ const why2 = unpushableOperator(key, value, context);
360
+ if (why2 !== void 0) return why2;
361
+ continue;
362
+ }
363
+ const why = unpushableField(key, value, context);
364
+ if (why !== void 0) return why;
365
+ }
366
+ return void 0;
367
+ }
368
+ function unpushableOperator(key, value, context, at) {
369
+ switch (key) {
370
+ case "$and":
371
+ if (!Array.isArray(value)) return "the clause is malformed";
372
+ for (const part of value) {
373
+ if (!isPlainObject(part)) return "the clause is malformed";
374
+ const why = unpushable(part, context);
375
+ if (why !== void 0) return why;
376
+ }
377
+ return void 0;
378
+ case "$or":
379
+ case "$nor": {
380
+ if (!context.or && key === "$or") return "the store cannot answer an $or";
381
+ if (!context.not && key === "$nor") return "the store cannot answer a negation";
382
+ if (!context.or && key === "$nor") return "the store cannot answer an $or";
383
+ if (!Array.isArray(value) || value.length === 0) return "the clause is malformed";
384
+ for (const part of value) {
385
+ if (!isPlainObject(part)) return "the clause is malformed";
386
+ const why = unpushable(part, context);
387
+ if (why !== void 0) return why;
388
+ }
389
+ return void 0;
390
+ }
391
+ case "$not": {
392
+ if (!context.not) return "the store cannot answer a negation";
393
+ if (!isPlainObject(value)) return "the clause is malformed";
394
+ return unpushable(value, context);
395
+ }
396
+ case "$text":
397
+ return allows(context, "$text") ? void 0 : "the store cannot answer a text search";
398
+ default:
399
+ return `the store cannot test the item itself with "${key}"`;
400
+ }
401
+ }
402
+ function unpushableField(name, value, context, at) {
403
+ const field = context.vocabulary?.lookup.get(name.toLowerCase());
404
+ if (field?.get !== void 0) return `"${name}" is computed here, so the store has no such field`;
405
+ const stored = storedName(name, context);
406
+ if (context.fields !== "all" && !context.fields.has(stored)) return `the store cannot filter on "${stored}"`;
407
+ if (isFieldReference(value)) return allows(context, "$field") ? void 0 : "the store cannot compare two fields";
408
+ if (!isPlainObject(value) || isDateLiteral(value)) {
409
+ if (!allows(context, "$eq")) return 'the store cannot answer "$eq"';
410
+ return accepted(context, stored, { $eq: value });
411
+ }
412
+ const keys = Object.keys(value);
413
+ if (!keys.every((key) => key.startsWith("$"))) {
414
+ if (!allows(context, "$eq")) return 'the store cannot answer "$eq"';
415
+ return accepted(context, stored, { $eq: value });
416
+ }
417
+ for (const key of keys) {
418
+ if (key === "$options" || key === "$comment") continue;
419
+ if (!KNOWN.has(key)) return `"${key}" is an added operator, which only this engine has`;
420
+ if (!allows(context, key)) return `the store cannot answer "${key}"`;
421
+ if ((key === "$ne" || key === "$nin" || key === "$not" || key === "$exists" && value[key] === false) && !context.not) {
422
+ return "the store cannot answer a negation";
423
+ }
424
+ const operand2 = value[key];
425
+ if (isFieldReference(operand2) && !allows(context, "$field")) return "the store cannot compare two fields";
426
+ if (key === "$elemMatch") {
427
+ if (!isPlainObject(operand2)) return "the clause is malformed";
428
+ const why = unpushable(operand2, context);
429
+ if (why !== void 0) return why;
430
+ }
431
+ if (key === "$not") {
432
+ if (!isPlainObject(operand2)) return "the clause is malformed";
433
+ const why = unpushableField(name, operand2, context);
434
+ if (why !== void 0) return why;
435
+ }
436
+ }
437
+ return accepted(context, stored, value);
438
+ }
439
+ function accepted(context, field, condition2) {
440
+ if (context.accepts === void 0 || context.accepts(field, condition2)) return void 0;
441
+ return "the store cannot answer this condition's values";
442
+ }
443
+ function allows(context, operator) {
444
+ return context.operators === "all" || context.operators.has(operator);
445
+ }
446
+ function resolve(value, context) {
447
+ if (!isPlainObject(value)) return value;
448
+ const out = {};
449
+ for (const [key, held] of Object.entries(value)) {
450
+ switch (key) {
451
+ case "$and":
452
+ case "$or":
453
+ case "$nor":
454
+ out[key] = Array.isArray(held) ? held.map((part) => resolve(part, context)) : held;
455
+ break;
456
+ case "$not":
457
+ out[key] = resolve(held, context);
458
+ break;
459
+ case "$text":
460
+ out[key] = resolveText(held, context);
461
+ break;
462
+ default:
463
+ if (key.startsWith("$")) out[key] = resolveOperand(key, held, context);
464
+ else out[storedName(key, context)] = resolveCondition(held, context);
465
+ break;
466
+ }
467
+ }
468
+ return out;
469
+ }
470
+ function resolveCondition(value, context) {
471
+ if (!isPlainObject(value)) return resolveLiteral(value, context);
472
+ if (isDateLiteral(value) || isFieldReference(value)) return resolveOperand("$eq", value, context);
473
+ const keys = Object.keys(value);
474
+ if (keys.length === 0 || !keys.every((key) => key.startsWith("$"))) return resolveLiteral(value, context);
475
+ const out = {};
476
+ for (const key of keys) out[key] = resolveOperand(key, value[key], context);
477
+ return out;
478
+ }
479
+ function resolveOperand(key, operand2, context) {
480
+ if (key === "$elemMatch") return resolve(operand2, context);
481
+ if (key === "$not") return resolveCondition(operand2, context);
482
+ if (isFieldReference(operand2)) return { $field: storedName(operand2.$field, context) };
483
+ if (Array.isArray(operand2)) return operand2.map((item) => isFieldReference(item) ? { $field: storedName(item.$field, context) } : resolveLiteral(item, context));
484
+ return resolveLiteral(operand2, context);
485
+ }
486
+ function resolveText(value, context) {
487
+ if (!isPlainObject(value) || !Array.isArray(value.$fields)) return value;
488
+ return { ...value, $fields: value.$fields.map((name) => typeof name === "string" ? storedName(name, context) : name) };
489
+ }
490
+ function resolveLiteral(value, context) {
491
+ if (Array.isArray(value)) return value.map((item) => resolveLiteral(item, context));
492
+ if (!isPlainObject(value)) return value;
493
+ if (isDateLiteral(value)) {
494
+ const time = resolveDate(value.$date, context.now);
495
+ if (Number.isNaN(time)) throw new JqlError(`${JSON.stringify(value.$date)} is not a date`);
496
+ return { $date: time };
497
+ }
498
+ const out = {};
499
+ for (const [key, held] of Object.entries(value)) out[key] = resolveLiteral(held, context);
500
+ return out;
501
+ }
502
+ function storedName(key, context) {
503
+ if (key.startsWith("$") || context.vocabulary === void 0) return key;
504
+ const field = context.vocabulary.lookup.get(key.toLowerCase());
505
+ if (field !== void 0) return field.path;
506
+ const dot = key.indexOf(".");
507
+ if (dot > 0) {
508
+ const head = context.vocabulary.lookup.get(key.slice(0, dot).toLowerCase());
509
+ if (head !== void 0 && head.get === void 0) return `${head.path}${key.slice(dot)}`;
510
+ }
511
+ return key;
512
+ }
513
+
514
+ // src/async.ts
515
+ async function* iterate(source) {
516
+ if (Array.isArray(source)) {
517
+ for (let i = 0; i < source.length; i++) yield source[i];
518
+ return;
519
+ }
520
+ if (source instanceof Map) {
521
+ for (const item of source.values()) yield item;
522
+ return;
523
+ }
524
+ if (typeof source[Symbol.asyncIterator] === "function") {
525
+ for await (const item of source) yield item;
526
+ return;
527
+ }
528
+ if (typeof source[Symbol.iterator] === "function") {
529
+ for (const item of source) yield item;
530
+ return;
531
+ }
532
+ const list = source;
533
+ for (let i = 0; i < list.length; i++) yield list[i];
534
+ }
535
+ async function* filterStream(source, query, options) {
536
+ const test = compile(query, options);
537
+ for await (const item of iterate(source)) if (test(item)) yield item;
538
+ }
539
+ async function findAsync(source, query, options) {
540
+ for await (const item of filterStream(source, query, options)) return item;
541
+ return void 0;
542
+ }
543
+ async function filterAsync(source, query, options) {
544
+ const out = [];
545
+ for await (const item of filterStream(source, query, options)) out.push(item);
546
+ return out;
547
+ }
548
+ async function countAsync(source, query, options) {
549
+ let total = 0;
550
+ const test = compile(query, options);
551
+ for await (const item of iterate(source)) if (test(item)) total++;
552
+ return total;
553
+ }
554
+ async function someAsync(source, query, options) {
555
+ const test = compile(query, options);
556
+ for await (const item of iterate(source)) if (test(item)) return true;
557
+ return false;
558
+ }
559
+ async function everyAsync(source, query, options) {
560
+ const test = compile(query, options);
561
+ for await (const item of iterate(source)) if (!test(item)) return false;
562
+ return true;
563
+ }
564
+ async function searchAsync(source, request = {}, options = {}) {
565
+ const plan2 = planRequest(request, options);
566
+ const collect2 = plan2.collect();
567
+ let index = 0;
568
+ for await (const item of iterate(source)) {
569
+ const position = index++;
570
+ if (plan2.test !== void 0 && !plan2.test(item)) continue;
571
+ if (collect2.offer(item, position)) break;
572
+ }
573
+ return plan2.shape(collect2.finish());
574
+ }
575
+
576
+ export { canonical, countAsync, everyAsync, explain, filterAsync, filterStream, findAsync, fingerprint, group, plan, searchAsync, someAsync };
577
+ //# sourceMappingURL=chunk-VMFEQXBB.js.map
578
+ //# sourceMappingURL=chunk-VMFEQXBB.js.map