@osqd/jql 0.1.1

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 (115) hide show
  1. package/CHANGELOG.md +220 -0
  2. package/LICENSE +102 -0
  3. package/README.md +84 -0
  4. package/bin/jql.mjs +5 -0
  5. package/conformance/cases.json +290 -0
  6. package/dist/array.d.ts +11 -0
  7. package/dist/async.d.ts +44 -0
  8. package/dist/canonical.d.ts +18 -0
  9. package/dist/cjs/array.d.ts +11 -0
  10. package/dist/cjs/async.d.ts +44 -0
  11. package/dist/cjs/canonical.d.ts +18 -0
  12. package/dist/cjs/cli.d.ts +15 -0
  13. package/dist/cjs/collections.d.ts +34 -0
  14. package/dist/cjs/core.d.ts +117 -0
  15. package/dist/cjs/errors.d.ts +17 -0
  16. package/dist/cjs/explain.d.ts +30 -0
  17. package/dist/cjs/global.d.ts +90 -0
  18. package/dist/cjs/group.d.ts +47 -0
  19. package/dist/cjs/index.d.ts +25 -0
  20. package/dist/cjs/internal/closest.d.ts +9 -0
  21. package/dist/cjs/internal/duration.d.ts +15 -0
  22. package/dist/cjs/internal/equal.d.ts +34 -0
  23. package/dist/cjs/internal/glob.d.ts +30 -0
  24. package/dist/cjs/internal/order.d.ts +24 -0
  25. package/dist/cjs/internal/path.d.ts +109 -0
  26. package/dist/cjs/internal/record.d.ts +18 -0
  27. package/dist/cjs/internal/values.d.ts +41 -0
  28. package/dist/cjs/limits.d.ts +64 -0
  29. package/dist/cjs/operators.d.ts +81 -0
  30. package/dist/cjs/package.json +3 -0
  31. package/dist/cjs/plan.d.ts +79 -0
  32. package/dist/cjs/search.d.ts +42 -0
  33. package/dist/cjs/targets/mongo.d.ts +55 -0
  34. package/dist/cjs/text/index.d.ts +12 -0
  35. package/dist/cjs/text/parse.d.ts +91 -0
  36. package/dist/cjs/text/suggest.d.ts +16 -0
  37. package/dist/cjs/text/write.d.ts +34 -0
  38. package/dist/cjs/types.d.ts +236 -0
  39. package/dist/cjs/vocabulary.d.ts +106 -0
  40. package/dist/cli.d.ts +15 -0
  41. package/dist/cli.js +2729 -0
  42. package/dist/cli.js.map +1 -0
  43. package/dist/collections.d.ts +34 -0
  44. package/dist/core.d.ts +117 -0
  45. package/dist/errors.d.ts +17 -0
  46. package/dist/explain.d.ts +30 -0
  47. package/dist/global.cjs +1953 -0
  48. package/dist/global.cjs.map +1 -0
  49. package/dist/global.d.ts +90 -0
  50. package/dist/global.js +1950 -0
  51. package/dist/global.js.map +1 -0
  52. package/dist/group.d.ts +47 -0
  53. package/dist/index.cjs +2529 -0
  54. package/dist/index.cjs.map +1 -0
  55. package/dist/index.d.ts +25 -0
  56. package/dist/index.js +2495 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/internal/closest.d.ts +9 -0
  59. package/dist/internal/duration.d.ts +15 -0
  60. package/dist/internal/equal.d.ts +34 -0
  61. package/dist/internal/glob.d.ts +30 -0
  62. package/dist/internal/order.d.ts +24 -0
  63. package/dist/internal/path.d.ts +109 -0
  64. package/dist/internal/record.d.ts +18 -0
  65. package/dist/internal/values.d.ts +41 -0
  66. package/dist/limits.d.ts +64 -0
  67. package/dist/mongo.cjs +357 -0
  68. package/dist/mongo.cjs.map +1 -0
  69. package/dist/mongo.js +354 -0
  70. package/dist/mongo.js.map +1 -0
  71. package/dist/operators.d.ts +81 -0
  72. package/dist/plan.d.ts +79 -0
  73. package/dist/search.d.ts +42 -0
  74. package/dist/targets/mongo.d.ts +55 -0
  75. package/dist/text/index.d.ts +12 -0
  76. package/dist/text/parse.d.ts +91 -0
  77. package/dist/text/suggest.d.ts +16 -0
  78. package/dist/text/write.d.ts +34 -0
  79. package/dist/text.cjs +674 -0
  80. package/dist/text.cjs.map +1 -0
  81. package/dist/text.js +667 -0
  82. package/dist/text.js.map +1 -0
  83. package/dist/types.d.ts +236 -0
  84. package/dist/vocabulary.d.ts +106 -0
  85. package/docs/course/01-first-query.md +217 -0
  86. package/docs/course/02-operators.md +285 -0
  87. package/docs/course/03-arrays-and-paths.md +239 -0
  88. package/docs/course/04-combining.md +221 -0
  89. package/docs/course/05-dates.md +214 -0
  90. package/docs/course/06-typed-queries.md +240 -0
  91. package/docs/course/07-requests.md +261 -0
  92. package/docs/course/08-grouping.md +210 -0
  93. package/docs/course/09-explaining.md +171 -0
  94. package/docs/course/10-vocabulary.md +276 -0
  95. package/docs/course/11-the-search-box.md +349 -0
  96. package/docs/course/12-untrusted.md +257 -0
  97. package/docs/course/13-saved-filters.md +199 -0
  98. package/docs/course/14-streams-and-cli.md +276 -0
  99. package/docs/course/15-pushdown.md +240 -0
  100. package/docs/course/16-extending.md +199 -0
  101. package/docs/course/index.md +185 -0
  102. package/docs/design/decisions.md +198 -0
  103. package/docs/design/performance.md +102 -0
  104. package/docs/guides/adopting.md +81 -0
  105. package/docs/guides/pushdown.md +147 -0
  106. package/docs/guides/typescript.md +115 -0
  107. package/docs/guides/untrusted-input.md +86 -0
  108. package/docs/index.md +102 -0
  109. package/docs/reference/api.md +266 -0
  110. package/docs/reference/cli.md +103 -0
  111. package/docs/reference/index.md +12 -0
  112. package/docs/reference/specification.md +549 -0
  113. package/docs/reference/text-syntax.md +152 -0
  114. package/docs/start/quick-start.md +84 -0
  115. package/package.json +136 -0
@@ -0,0 +1,290 @@
1
+ {
2
+ "about": "The JQL conformance suite. Every implementation of the language must produce exactly these answers. Each case runs `query` over the named document set and lists the positions of the documents that match, in order; a case with `error` must be refused, and `at` is where in the query the refusal points. The documents are plain JSON, so nothing here depends on JavaScript.",
3
+ "version": "1.1",
4
+ "documents": {
5
+ "people": [
6
+ { "id": "ada", "name": "Ada Lovelace", "age": 36, "born": "1815-12-10", "tags": ["math", "poetry"], "address": { "city": "London", "zip": "W1" }, "languages": [{ "name": "English", "level": 5 }, { "name": "French", "level": 3 }], "active": true, "score": null },
7
+ { "id": "grace", "name": "Grace Hopper", "age": 85, "born": "1906-12-09", "tags": ["navy", "cobol"], "address": { "city": "New York" }, "languages": [{ "name": "English", "level": 5 }], "active": false },
8
+ { "id": "alan", "name": "Alan Turing", "age": 41, "born": "1912-06-23", "tags": ["math", "crypto"], "address": { "city": "Manchester", "zip": "M1" }, "languages": [{ "name": "English", "level": 5 }, { "name": "German", "level": 2 }], "active": true, "score": 99 },
9
+ { "id": "linus", "name": "Linus Torvalds", "age": 56, "tags": [], "address": null, "active": true, "score": 7.5, "nick": "torvalds" }
10
+ ],
11
+ "numbers": [
12
+ 1,
13
+ 5,
14
+ 10,
15
+ -3,
16
+ 2.5
17
+ ],
18
+ "mixed": [
19
+ 1,
20
+ "1",
21
+ "10",
22
+ 9,
23
+ null,
24
+ true,
25
+ [1, 2],
26
+ { "a": 1 }
27
+ ],
28
+ "hosts": [
29
+ { "actor": "1.2.3.4" },
30
+ { "actor": "1.2.3.45" },
31
+ { "actor": "11.2.3.4" },
32
+ { "actor": "1.2.3.0/24" },
33
+ { "actor": "203.0.113.9" }
34
+ ],
35
+ "rows": [
36
+ { "id": 0, "group": "b", "score": 2, "tags": ["m", "a"] },
37
+ { "id": 1, "group": "a", "score": 2, "tags": ["z"] },
38
+ { "id": 2, "group": "b", "score": null, "tags": [] },
39
+ { "id": 3, "group": "a", "score": 9, "tags": ["b", "y"] },
40
+ { "id": 4, "group": "a", "tags": ["c"] }
41
+ ],
42
+ "named": [
43
+ { "id": 1, "__proto__": { "polluted": "no" } }
44
+ ],
45
+ "pairs": [
46
+ { "id": 0, "in": 100, "out": 900 },
47
+ { "id": 1, "in": 900, "out": 100 },
48
+ { "id": 2, "in": 100, "out": 100 },
49
+ { "id": 3, "out": 5 },
50
+ { "id": 4, "in": "5", "out": 5 },
51
+ { "id": 5, "in": [1, 9], "out": 5 }
52
+ ],
53
+ "texts": [
54
+ { "s": "abc" },
55
+ { "s": "☃" },
56
+ { "s": "😀" },
57
+ { "s": "a*b" }
58
+ ],
59
+ "stamps": [
60
+ { "t": "2020-12-31" },
61
+ { "t": "2020-12-31T10:00:00Z" },
62
+ { "t": "2020-12-31 10:00:00" },
63
+ { "t": "12/31/2020" },
64
+ { "t": "Mar 5 2021" },
65
+ { "t": 1609372800000 },
66
+ { "t": "5" }
67
+ ],
68
+ "letters": [
69
+ { "s": "𝐀𝐁" },
70
+ { "s": "𝐀.b" },
71
+ { "s": "ab" },
72
+ { "s": "a.b" }
73
+ ],
74
+ "events": [
75
+ { "at": "2026-09-22T11:30:00Z" },
76
+ { "at": "2026-09-22T09:00:00Z" },
77
+ { "at": "2026-09-15T12:00:00Z" }
78
+ ],
79
+ "values": [
80
+ { "v": "b" },
81
+ { "v": true },
82
+ { "v": 3 },
83
+ {},
84
+ { "v": null },
85
+ { "v": { "a": 1 } },
86
+ { "v": 1 },
87
+ { "v": "a" }
88
+ ],
89
+ "lines": [
90
+ [{ "sku": "pen" }, { "sku": "ink" }],
91
+ [{ "sku": "pad" }],
92
+ [],
93
+ [3, 4],
94
+ { "sku": "pen" }
95
+ ],
96
+ "matrix": [
97
+ { "id": 0, "cells": [[1, 2], [3, 4]] },
98
+ { "id": 1, "cells": [[5]] },
99
+ { "id": 2, "points": [{ "x": 1, "y": 2 }, { "x": 3, "y": 1 }] },
100
+ { "id": 3, "points": [{ "x": 1, "y": 1 }] }
101
+ ]
102
+ },
103
+ "requests": [
104
+ { "group": "sort", "name": "ascending, with missing and null first and ties in arrival order", "documents": "rows", "request": { "sort": { "score": 1 } }, "matches": [2, 4, 0, 1, 3] },
105
+ { "group": "sort", "name": "descending reverses the values but not the ties", "documents": "rows", "request": { "sort": { "score": -1 } }, "matches": [3, 0, 1, 2, 4] },
106
+ { "group": "sort", "name": "asc and desc spelled out", "documents": "rows", "request": { "sort": { "score": "asc" } }, "matches": [2, 4, 0, 1, 3] },
107
+ { "group": "sort", "name": "the second key breaks the first key's ties", "documents": "rows", "request": { "sort": { "group": "asc", "score": "desc" } }, "matches": [3, 1, 4, 0, 2] },
108
+ { "group": "sort", "name": "one order across every type", "documents": "values", "request": { "sort": { "v": 1 } }, "matches": [3, 4, 6, 2, 7, 0, 5, 1] },
109
+ { "group": "sort", "name": "an array sorts by its least element ascending", "documents": "rows", "request": { "sort": { "tags": 1 } }, "matches": [2, 0, 3, 4, 1] },
110
+ { "group": "sort", "name": "and by its greatest descending", "documents": "rows", "request": { "sort": { "tags": -1 } }, "matches": [1, 3, 0, 4, 2] },
111
+ { "group": "paging", "name": "skip and limit walk a sorted result", "documents": "rows", "request": { "sort": { "id": 1 }, "skip": 1, "limit": 2 }, "matches": [1, 2] },
112
+ { "group": "paging", "name": "the next page does not repeat or drop an item", "documents": "rows", "request": { "sort": { "score": 1 }, "skip": 2, "limit": 2 }, "matches": [0, 1] },
113
+ { "group": "paging", "name": "a limit without a sort keeps the given order", "documents": "rows", "request": { "where": { "group": "a" }, "limit": 2 }, "matches": [1, 3] },
114
+ { "group": "paging", "name": "a limit of zero returns nothing", "documents": "rows", "request": { "limit": 0 }, "matches": [] },
115
+ { "group": "paging", "name": "skipping past the end returns nothing", "documents": "rows", "request": { "sort": { "id": 1 }, "skip": 99 }, "matches": [] },
116
+ { "group": "paging", "name": "where, sort, skip and limit together", "documents": "rows", "request": { "where": { "score": { "$exists": true } }, "sort": { "score": -1 }, "skip": 1, "limit": 2 }, "matches": [0, 1] },
117
+ { "group": "projection", "name": "keeps the named paths in the document's own shape", "documents": "people", "request": { "where": { "id": "ada" }, "fields": ["id", "address.city", "languages.name"] }, "projection": [{ "id": "ada", "address": { "city": "London" }, "languages": [{ "name": "English" }, { "name": "French" }] }] },
118
+ { "group": "projection", "name": "a path and its parent keep the parent whole", "documents": "people", "request": { "where": { "id": "ada" }, "fields": ["address.city", "address"] }, "projection": [{ "address": { "city": "London", "zip": "W1" } }] },
119
+ { "group": "projection", "name": "a missing path is left out", "documents": "people", "request": { "where": { "id": "linus" }, "fields": ["nick", "address.city"] }, "projection": [{ "nick": "torvalds" }] },
120
+ { "group": "projection", "name": "a field is data whatever it is called", "documents": "named", "request": { "fields": ["__proto__", "id"] }, "projection": [{ "__proto__": { "polluted": "no" }, "id": 1 }] },
121
+ { "group": "redaction", "name": "drops the named fields from each result", "documents": "people", "request": { "where": { "id": "grace" }, "omit": ["age", "born", "tags", "languages", "active"] }, "projection": [{ "id": "grace", "name": "Grace Hopper", "address": { "city": "New York" } }] },
122
+ { "group": "redaction", "name": "drops a nested path and leaves the rest of the object", "documents": "people", "request": { "where": { "id": "ada" }, "fields": ["id", "address"], "omit": ["address.zip"] }, "projection": [{ "id": "ada", "address": { "city": "London" } }] },
123
+ { "group": "redaction", "name": "drops a path through an array, from every element", "documents": "people", "request": { "where": { "id": "ada" }, "fields": ["languages"], "omit": ["languages.level"] }, "projection": [{ "languages": [{ "name": "English" }, { "name": "French" }] }] },
124
+ { "group": "redaction", "name": "drops nothing for a path the item does not have", "documents": "people", "request": { "where": { "id": "linus" }, "fields": ["id", "nick"], "omit": ["address.zip"] }, "projection": [{ "id": "linus", "nick": "torvalds" }] },
125
+ { "group": "redaction", "name": "applies after fields, so a kept subtree can lose part of itself", "documents": "people", "request": { "where": { "id": "alan" }, "fields": ["address"], "omit": ["address.city"] }, "projection": [{ "address": { "zip": "M1" } }] },
126
+ { "group": "refusals", "name": "an unknown key in a request", "documents": "rows", "request": { "limt": 1 }, "error": true, "at": "limt" },
127
+ { "group": "refusals", "name": "a limit that is not a whole number", "documents": "rows", "request": { "limit": 1.5 }, "error": true, "at": "limit" },
128
+ { "group": "refusals", "name": "a negative skip", "documents": "rows", "request": { "skip": -1 }, "error": true, "at": "skip" },
129
+ { "group": "refusals", "name": "a direction that is not one", "documents": "rows", "request": { "sort": { "id": "up" } }, "error": true, "at": "sort.id" },
130
+ { "group": "refusals", "name": "a field name in omit that is not a string", "documents": "rows", "request": { "omit": [5] }, "error": true, "at": "omit[0]" }
131
+ ],
132
+ "cases": [
133
+ { "group": "equality", "name": "an empty query matches everything", "documents": "people", "query": {}, "matches": [0, 1, 2, 3] },
134
+ { "group": "equality", "name": "a literal is equality", "documents": "people", "query": { "id": "grace" }, "matches": [1] },
135
+ { "group": "equality", "name": "several fields must all hold", "documents": "people", "query": { "active": true, "age": 41 }, "matches": [2] },
136
+ { "group": "equality", "name": "$eq says the same thing", "documents": "people", "query": { "age": { "$eq": 36 } }, "matches": [0] },
137
+ { "group": "equality", "name": "a nested path", "documents": "people", "query": { "address.city": "London" }, "matches": [0] },
138
+ { "group": "equality", "name": "a value compared with an array matches any element", "documents": "people", "query": { "tags": "math" }, "matches": [0, 2] },
139
+ { "group": "equality", "name": "an array literal matches the whole array, in order", "documents": "people", "query": { "tags": ["math", "poetry"] }, "matches": [0] },
140
+ { "group": "equality", "name": "an array literal in another order does not", "documents": "people", "query": { "tags": ["poetry", "math"] }, "matches": [] },
141
+ { "group": "equality", "name": "an empty array literal matches an empty array", "documents": "people", "query": { "tags": [] }, "matches": [3] },
142
+ { "group": "equality", "name": "an object literal matches keys in any order", "documents": "people", "query": { "address": { "zip": "W1", "city": "London" } }, "matches": [0] },
143
+ { "group": "equality", "name": "an object literal needs every key and no more", "documents": "people", "query": { "address": { "city": "London" } }, "matches": [] },
144
+ { "group": "equality", "name": "null matches null and missing", "documents": "people", "query": { "score": null }, "matches": [0, 1] },
145
+ { "group": "equality", "name": "null on a nested path matches a missing parent too", "documents": "people", "query": { "address.zip": null }, "matches": [1, 3] },
146
+ { "group": "equality", "name": "strings are compared exactly", "documents": "people", "query": { "id": "ADA" }, "matches": [] },
147
+ { "group": "equality", "name": "$options i compares strings ignoring case", "documents": "people", "query": { "id": { "$eq": "ADA", "$options": "i" } }, "matches": [0] },
148
+ { "group": "equality", "name": "ignoring case reaches the strings inside a list", "documents": "people", "query": { "tags": { "$eq": ["MATH", "POETRY"], "$options": "i" } }, "matches": [0] },
149
+ { "group": "equality", "name": "and the strings inside an object", "documents": "people", "query": { "address": { "$eq": { "city": "LONDON", "zip": "w1" }, "$options": "i" } }, "matches": [0] },
150
+ { "group": "equality", "name": "but never the keys, which are names rather than values", "documents": "people", "query": { "address": { "$eq": { "CITY": "London", "ZIP": "W1" }, "$options": "i" } }, "matches": [] },
151
+ { "group": "equality", "name": "$all ignoring case", "documents": "people", "query": { "tags": { "$all": ["MATH"], "$options": "i" } }, "matches": [0, 2] },
152
+ { "group": "equality", "name": "$in ignoring case, against a whole list", "documents": "people", "query": { "tags": { "$in": [["MATH", "POETRY"]], "$options": "i" } }, "matches": [0] },
153
+ { "group": "equality", "name": "no coercion between a number and a string", "documents": "mixed", "query": { "$eq": 1 }, "matches": [0, 6] },
154
+ { "group": "equality", "name": "true is not 1", "documents": "mixed", "query": { "$eq": true }, "matches": [5] },
155
+ { "group": "equality", "name": "$ne also matches a missing field", "documents": "people", "query": { "nick": { "$ne": "torvalds" } }, "matches": [0, 1, 2] },
156
+ { "group": "equality", "name": "$ne on an array means no element equals it", "documents": "people", "query": { "tags": { "$ne": "math" } }, "matches": [1, 3] },
157
+ { "group": "arrays and paths", "name": "an item that is itself an array is seen through", "documents": "lines", "query": { "sku": "pen" }, "matches": [0, 4] },
158
+ { "group": "arrays and paths", "name": "a position names an element of the item", "documents": "lines", "query": { "0.sku": "pad" }, "matches": [1] },
159
+ { "group": "arrays and paths", "name": "an empty item reaches nothing", "documents": "lines", "query": { "sku": { "$exists": false } }, "matches": [2, 3] },
160
+ { "group": "arrays and paths", "name": "an array's own length is not a field", "documents": "lines", "query": { "length": 2 }, "matches": [] },
161
+ { "group": "arrays and paths", "name": "a path sees through an array of objects", "documents": "people", "query": { "languages.name": "German" }, "matches": [2] },
162
+ { "group": "arrays and paths", "name": "a numeric segment is a position", "documents": "people", "query": { "languages.1.name": "French" }, "matches": [0] },
163
+ { "group": "arrays and paths", "name": "a position past the end reaches nothing", "documents": "people", "query": { "tags.5": { "$exists": true } }, "matches": [] },
164
+ { "group": "arrays and paths", "name": "operators on an array field are tested separately", "documents": "people", "query": { "languages.level": { "$gt": 4, "$lt": 3 } }, "matches": [2] },
165
+ { "group": "arrays and paths", "name": "$elemMatch needs one element to satisfy all of it", "documents": "people", "query": { "languages": { "$elemMatch": { "name": "English", "level": { "$gte": 5 } } } }, "matches": [0, 1, 2] },
166
+ { "group": "arrays and paths", "name": "$elemMatch rejects a split match", "documents": "people", "query": { "languages": { "$elemMatch": { "name": "German", "level": 5 } } }, "matches": [] },
167
+ { "group": "arrays and paths", "name": "$elemMatch on objects in a nested document", "documents": "matrix", "query": { "points": { "$elemMatch": { "x": 3, "y": { "$lt": 2 } } } }, "matches": [2] },
168
+ { "group": "arrays and paths", "name": "$elemMatch on primitive elements takes a condition", "documents": "people", "query": { "tags": { "$elemMatch": { "$startsWith": "po" } } }, "matches": [0] },
169
+ { "group": "arrays and paths", "name": "a path fans out through nested arrays", "documents": "matrix", "query": { "points.y": 1 }, "matches": [2, 3] },
170
+ { "group": "arrays and paths", "name": "an element of a nested array", "documents": "matrix", "query": { "cells": [5] }, "matches": [1] },
171
+ { "group": "arrays and paths", "name": "$size counts elements", "documents": "people", "query": { "tags": { "$size": 2 } }, "matches": [0, 1, 2] },
172
+ { "group": "arrays and paths", "name": "$size takes a condition", "documents": "people", "query": { "languages": { "$size": { "$gt": 1 } } }, "matches": [0, 2] },
173
+ { "group": "arrays and paths", "name": "$size never matches a non-array", "documents": "people", "query": { "name": { "$size": 12 } }, "matches": [] },
174
+ { "group": "arrays and paths", "name": "$all needs every value present", "documents": "people", "query": { "tags": { "$all": ["crypto", "math"] } }, "matches": [2] },
175
+ { "group": "arrays and paths", "name": "an empty $all matches nothing", "documents": "people", "query": { "tags": { "$all": [] } }, "matches": [] },
176
+ { "group": "existence and type", "name": "$exists true", "documents": "people", "query": { "nick": { "$exists": true } }, "matches": [3] },
177
+ { "group": "existence and type", "name": "$exists false", "documents": "people", "query": { "score": { "$exists": false } }, "matches": [1] },
178
+ { "group": "existence and type", "name": "a null value exists", "documents": "people", "query": { "address": { "$exists": true } }, "matches": [0, 1, 2, 3] },
179
+ { "group": "existence and type", "name": "$type", "documents": "mixed", "query": { "$type": "string" }, "matches": [1, 2] },
180
+ { "group": "existence and type", "name": "$type integer", "documents": "people", "query": { "score": { "$type": "integer" } }, "matches": [2] },
181
+ { "group": "existence and type", "name": "$type with a list", "documents": "mixed", "query": { "$type": ["null", "boolean"] }, "matches": [4, 5] },
182
+ { "group": "existence and type", "name": "$type array matches the array itself", "documents": "mixed", "query": { "$type": "array" }, "matches": [6] },
183
+ { "group": "existence and type", "name": "$type object", "documents": "mixed", "query": { "$type": "object" }, "matches": [7] },
184
+ { "group": "ordering", "name": "$gt on numbers", "documents": "people", "query": { "age": { "$gt": 50 } }, "matches": [1, 3] },
185
+ { "group": "ordering", "name": "a range", "documents": "people", "query": { "age": { "$gte": 36, "$lte": 41 } }, "matches": [0, 2] },
186
+ { "group": "ordering", "name": "strings compare by code unit", "documents": "people", "query": { "id": { "$lt": "b" } }, "matches": [0, 2] },
187
+ { "group": "ordering", "name": "a number bound never matches a string", "documents": "mixed", "query": { "$gt": 5 }, "matches": [3] },
188
+ { "group": "ordering", "name": "a string bound never matches a number", "documents": "mixed", "query": { "$gte": "1" }, "matches": [1, 2] },
189
+ { "group": "ordering", "name": "ordering never matches a missing field", "documents": "people", "query": { "score": { "$lt": 100 } }, "matches": [2, 3] },
190
+ { "group": "ordering", "name": "a $date bound reads ISO strings as dates", "documents": "people", "query": { "born": { "$lt": { "$date": "1900-01-01" } } }, "matches": [0] },
191
+ { "group": "ordering", "name": "a $date literal equals the same instant written differently", "documents": "people", "query": { "born": { "$date": "1906-12-09T00:00:00Z" } }, "matches": [1] },
192
+ { "group": "ordering", "name": "$in", "documents": "people", "query": { "id": { "$in": ["ada", "linus", "nobody"] } }, "matches": [0, 3] },
193
+ { "group": "ordering", "name": "$in with null also matches missing", "documents": "people", "query": { "nick": { "$in": [null, "x"] } }, "matches": [0, 1, 2] },
194
+ { "group": "ordering", "name": "$in against an array field", "documents": "people", "query": { "tags": { "$in": ["navy", "crypto"] } }, "matches": [1, 2] },
195
+ { "group": "ordering", "name": "an empty $in matches nothing", "documents": "people", "query": { "id": { "$in": [] } }, "matches": [] },
196
+ { "group": "ordering", "name": "$nin also matches missing", "documents": "people", "query": { "score": { "$nin": [99, 7.5] } }, "matches": [0, 1] },
197
+ { "group": "ordering", "name": "an empty $nin matches everything", "documents": "people", "query": { "id": { "$nin": [] } }, "matches": [0, 1, 2, 3] },
198
+ { "group": "ordering", "name": "$in ignoring case", "documents": "people", "query": { "id": { "$in": ["ADA", "Alan"], "$options": "i" } }, "matches": [0, 2] },
199
+ { "group": "ordering", "name": "$mod", "documents": "numbers", "query": { "$mod": [5, 0] }, "matches": [1, 2] },
200
+ { "group": "strings", "name": "$contains", "documents": "people", "query": { "name": { "$contains": "ur" } }, "matches": [2] },
201
+ { "group": "strings", "name": "$contains is exact about case by default", "documents": "people", "query": { "name": { "$contains": "ADA" } }, "matches": [] },
202
+ { "group": "strings", "name": "$contains ignoring case", "documents": "people", "query": { "name": { "$contains": "TOR", "$options": "i" } }, "matches": [3] },
203
+ { "group": "strings", "name": "$contains with a list means any of them", "documents": "people", "query": { "name": { "$contains": ["Hop", "Tur"] } }, "matches": [1, 2] },
204
+ { "group": "strings", "name": "$startsWith", "documents": "people", "query": { "address.city": { "$startsWith": "Man" } }, "matches": [2] },
205
+ { "group": "strings", "name": "$endsWith", "documents": "people", "query": { "name": { "$endsWith": "er" } }, "matches": [1] },
206
+ { "group": "strings", "name": "string operators look into arrays", "documents": "people", "query": { "tags": { "$startsWith": "cr" } }, "matches": [2] },
207
+ { "group": "strings", "name": "string operators never match a non-string", "documents": "mixed", "query": { "$contains": "1" }, "matches": [1, 2] },
208
+ { "group": "strings", "name": "$word matches a whole component", "documents": "hosts", "query": { "actor": { "$word": "1.2.3.4" } }, "matches": [0] },
209
+ { "group": "strings", "name": "$word finds a network by its prefix", "documents": "hosts", "query": { "actor": { "$word": "1.2.3" } }, "matches": [0, 1, 3] },
210
+ { "group": "strings", "name": "$word finds an address by its tail", "documents": "hosts", "query": { "actor": { "$word": "3.45" } }, "matches": [1] },
211
+ { "group": "strings", "name": "a value ending in a separator chooses its own boundary", "documents": "hosts", "query": { "actor": { "$word": "203.0.113." } }, "matches": [4] },
212
+ { "group": "strings", "name": "$regex", "documents": "people", "query": { "name": { "$regex": "^A\\w+ [LT]" } }, "matches": [0, 2] },
213
+ { "group": "strings", "name": "$regex with the i flag", "documents": "people", "query": { "nick": { "$regex": "^TOR", "$options": "i" } }, "matches": [3] },
214
+ { "group": "logic", "name": "$or", "documents": "people", "query": { "$or": [{ "id": "ada" }, { "age": { "$gt": 80 } }] }, "matches": [0, 1] },
215
+ { "group": "logic", "name": "$and", "documents": "people", "query": { "$and": [{ "tags": "math" }, { "tags": "crypto" }] }, "matches": [2] },
216
+ { "group": "logic", "name": "$nor", "documents": "people", "query": { "$nor": [{ "active": false }, { "tags": "poetry" }] }, "matches": [2, 3] },
217
+ { "group": "logic", "name": "$not at the top negates a whole query", "documents": "people", "query": { "$not": { "tags": "math" } }, "matches": [1, 3] },
218
+ { "group": "logic", "name": "$not on a field negates a condition, and matches missing", "documents": "people", "query": { "score": { "$not": { "$gt": 50 } } }, "matches": [0, 1, 3] },
219
+ { "group": "logic", "name": "an empty $and matches everything", "documents": "people", "query": { "$and": [] }, "matches": [0, 1, 2, 3] },
220
+ { "group": "logic", "name": "an empty $or matches nothing", "documents": "people", "query": { "$or": [] }, "matches": [] },
221
+ { "group": "logic", "name": "an empty $nor matches everything", "documents": "people", "query": { "$nor": [] }, "matches": [0, 1, 2, 3] },
222
+ { "group": "logic", "name": "$comment is ignored", "documents": "people", "query": { "$comment": "why this exists", "id": "alan" }, "matches": [2] },
223
+ { "group": "logic", "name": "a condition at the top tests the item itself", "documents": "numbers", "query": { "$gt": 1, "$lt": 10 }, "matches": [1, 4] },
224
+ { "group": "logic", "name": "$not at the top on primitives", "documents": "numbers", "query": { "$not": { "$gte": 5 } }, "matches": [0, 3, 4] },
225
+ { "group": "text", "name": "$text finds a phrase anywhere, ignoring case", "documents": "people", "query": { "$text": "new york" }, "matches": [1] },
226
+ { "group": "text", "name": "$text looks inside arrays and nested objects", "documents": "people", "query": { "$text": "german" }, "matches": [2] },
227
+ { "group": "text", "name": "$text searches numbers when the phrase has a digit", "documents": "people", "query": { "$text": "85" }, "matches": [1] },
228
+ { "group": "text", "name": "$text never reads a key as text", "documents": "people", "query": { "$text": "languages" }, "matches": [] },
229
+ { "group": "text", "name": "$text in named fields only", "documents": "people", "query": { "$text": { "$search": "tor", "$fields": ["nick"] } }, "matches": [3] },
230
+ { "group": "text", "name": "$text case-sensitive", "documents": "people", "query": { "$text": { "$search": "Tor", "$caseSensitive": true } }, "matches": [3] },
231
+ { "group": "text", "name": "an empty $text matches everything", "documents": "people", "query": { "$text": "" }, "matches": [0, 1, 2, 3] },
232
+ { "group": "references", "name": "one field greater than another", "documents": "pairs", "query": { "out": { "$gt": { "$field": "in" } } }, "matches": [0, 5] },
233
+ { "group": "references", "name": "one field equal to another, written as a value", "documents": "pairs", "query": { "out": { "$field": "in" } }, "matches": [2] },
234
+ { "group": "references", "name": "$ne against a reference holds when no pair is equal", "documents": "pairs", "query": { "out": { "$ne": { "$field": "in" } } }, "matches": [0, 1, 3, 4, 5] },
235
+ { "group": "references", "name": "ordering against a reference", "documents": "pairs", "query": { "out": { "$lte": { "$field": "in" } } }, "matches": [1, 2, 5] },
236
+ { "group": "references", "name": "a reference that reaches nothing matches nothing", "documents": "pairs", "query": { "out": { "$gt": { "$field": "missing" } } }, "matches": [] },
237
+ { "group": "references", "name": "a reference coerces nothing either", "documents": "pairs", "query": { "out": { "$eq": { "$field": "in" }, "$lt": 6 } }, "matches": [] },
238
+ { "group": "references", "name": "a reference reaching an array holds for any of its values", "documents": "pairs", "query": { "out": { "$lt": { "$field": "in" } } }, "matches": [1, 5] },
239
+ { "group": "length", "name": "$length counts an array's elements", "documents": "people", "query": { "tags": { "$length": 2 } }, "matches": [0, 1, 2] },
240
+ { "group": "length", "name": "$length counts a string's UTF-16 code units", "documents": "people", "query": { "name": { "$length": 12 } }, "matches": [0, 1] },
241
+ { "group": "length", "name": "$length takes a condition", "documents": "people", "query": { "name": { "$length": { "$gt": 12 } } }, "matches": [3] },
242
+ { "group": "length", "name": "a character outside the basic plane is two code units", "documents": "texts", "query": { "s": { "$length": 2 } }, "matches": [2] },
243
+ { "group": "length", "name": "$length never matches something without one", "documents": "people", "query": { "age": { "$length": 2 } }, "matches": [] },
244
+ { "group": "glob", "name": "* stands for any run", "documents": "people", "query": { "name": { "$glob": "A*" } }, "matches": [0, 2] },
245
+ { "group": "glob", "name": "a glob matches the whole value", "documents": "people", "query": { "id": { "$glob": "ad" } }, "matches": [] },
246
+ { "group": "glob", "name": "? stands for one character", "documents": "people", "query": { "id": { "$glob": "a?a" } }, "matches": [0] },
247
+ { "group": "glob", "name": "a list means any of them", "documents": "people", "query": { "id": { "$glob": ["ada", "gr*"] } }, "matches": [0, 1] },
248
+ { "group": "glob", "name": "a glob ignoring case", "documents": "people", "query": { "id": { "$glob": "A*", "$options": "i" } }, "matches": [0, 2] },
249
+ { "group": "glob", "name": "a backslash escapes a star", "documents": "texts", "query": { "s": { "$glob": "a\\*b" } }, "matches": [3] },
250
+ { "group": "glob", "name": "an unescaped star still stands for any run", "documents": "texts", "query": { "s": { "$glob": "a*b" } }, "matches": [3] },
251
+ { "group": "ordering", "name": "a date in a document is ISO 8601, and nothing else a parser might take", "documents": "stamps", "query": { "t": { "$gt": { "$date": "2020-01-01" } } }, "matches": [0, 1, 2, 5] },
252
+ { "group": "ordering", "name": "a timestamp with no offset is UTC, not the reader's local time", "documents": "stamps", "query": { "t": { "$gte": { "$date": "2020-12-31T10:00:00Z" } } }, "matches": [1, 2] },
253
+ { "group": "ordering", "name": "and so is a bound written without one", "documents": "stamps", "query": { "t": { "$eq": { "$date": "2020-12-31T10:00:00" } } }, "matches": [1, 2] },
254
+ { "group": "ordering", "name": "neither side carrying an offset still means one instant", "documents": "stamps", "query": { "t": { "$lt": { "$date": "2020-12-31 10:00:01" } } }, "matches": [0, 1, 2, 5] },
255
+ { "group": "strings", "name": "$word counts a character, not half of one", "documents": "letters", "query": { "s": { "$word": "𝐀" } }, "matches": [1] },
256
+ { "group": "strings", "name": "and the same for an ordinary letter", "documents": "letters", "query": { "s": { "$word": "a" } }, "matches": [3] },
257
+ { "group": "relative dates", "name": "within the last hour", "documents": "events", "now": "2026-09-22T12:00:00Z", "query": { "at": { "$gte": { "$date": { "$ago": "1h" } } } }, "matches": [0] },
258
+ { "group": "relative dates", "name": "within the last day", "documents": "events", "now": "2026-09-22T12:00:00Z", "query": { "at": { "$gte": { "$date": { "$ago": "1d" } } } }, "matches": [0, 1] },
259
+ { "group": "relative dates", "name": "a compound duration", "documents": "events", "now": "2026-09-22T12:00:00Z", "query": { "at": { "$gte": { "$date": { "$ago": "1h30m" } } } }, "matches": [0] },
260
+ { "group": "relative dates", "name": "now itself", "documents": "events", "now": "2026-09-22T12:00:00Z", "query": { "at": { "$lt": { "$date": "now" } } }, "matches": [0, 1, 2] },
261
+ { "group": "relative dates", "name": "ahead of now", "documents": "events", "now": "2026-09-22T12:00:00Z", "query": { "at": { "$gt": { "$date": { "$ahead": "1h" } } } }, "matches": [] },
262
+ { "group": "refusals", "name": "a reference where a value belongs", "query": { "a": { "$in": [{ "$field": "b" }] } }, "error": true, "at": "a.$in[0]" },
263
+ { "group": "refusals", "name": "a reference whose path is not a string", "query": { "a": { "$gt": { "$field": 5 } } }, "error": true, "at": "a.$gt" },
264
+ { "group": "refusals", "name": "a negative $length", "query": { "a": { "$length": -1 } }, "error": true, "at": "a.$length" },
265
+ { "group": "refusals", "name": "a glob that is not a string", "query": { "a": { "$glob": 5 } }, "error": true, "at": "a.$glob" },
266
+ { "group": "refusals", "name": "a duration with no unit anybody fixed", "query": { "a": { "$gt": { "$date": { "$ago": "1fortnight" } } } }, "error": true, "at": "a.$gt" },
267
+ { "group": "refusals", "name": "a relative date that is not a duration", "query": { "a": { "$gt": { "$date": { "$ago": 5 } } } }, "error": true, "at": "a.$gt" },
268
+ { "group": "refusals", "name": "an unknown operator", "query": { "age": { "$gtt": 5 } }, "error": true, "at": "age.$gtt" },
269
+ { "group": "refusals", "name": "an unknown top-level operator", "query": { "$where": "1" }, "error": true, "at": "$where" },
270
+ { "group": "refusals", "name": "a query operator inside a condition", "query": { "age": { "$or": [] } }, "error": true, "at": "age.$or" },
271
+ { "group": "refusals", "name": "operators mixed with field names", "query": { "address": { "$exists": true, "city": "London" } }, "error": true, "at": "address" },
272
+ { "group": "refusals", "name": "a query that is not an object", "query": ["id", "ada"], "error": true, "at": "" },
273
+ { "group": "refusals", "name": "$or that is not a list", "query": { "$or": { "id": "ada" } }, "error": true, "at": "$or" },
274
+ { "group": "refusals", "name": "$in that is not a list", "query": { "id": { "$in": "ada" } }, "error": true, "at": "id.$in" },
275
+ { "group": "refusals", "name": "an ordering bound that is not comparable", "query": { "age": { "$gt": true } }, "error": true, "at": "age.$gt" },
276
+ { "group": "refusals", "name": "a $date that is not a date", "query": { "born": { "$gt": { "$date": "yesterday-ish" } } }, "error": true, "at": "born.$gt" },
277
+ { "group": "refusals", "name": "a pattern that does not compile", "query": { "name": { "$regex": "(" } }, "error": true, "at": "name.$regex" },
278
+ { "group": "refusals", "name": "a stateful regex flag", "query": { "name": { "$regex": "a", "$options": "g" } }, "error": true, "at": "name.$options" },
279
+ { "group": "refusals", "name": "$options with nothing to apply to", "query": { "age": { "$gt": 1, "$options": "i" } }, "error": true, "at": "age.$options" },
280
+ { "group": "refusals", "name": "an unknown type name", "query": { "age": { "$type": "float" } }, "error": true, "at": "age.$type[0]" },
281
+ { "group": "refusals", "name": "$exists that is not a boolean", "query": { "age": { "$exists": 1 } }, "error": true, "at": "age.$exists" },
282
+ { "group": "refusals", "name": "a $mod divisor of zero", "query": { "age": { "$mod": [0, 1] } }, "error": true, "at": "age.$mod" },
283
+ { "group": "refusals", "name": "a negative $size", "query": { "tags": { "$size": -1 } }, "error": true, "at": "tags.$size" },
284
+ { "group": "refusals", "name": "$not holding a value rather than a condition", "query": { "age": { "$not": 5 } }, "error": true, "at": "age.$not" },
285
+ { "group": "refusals", "name": "an empty path segment", "query": { "address..city": "London" }, "error": true, "at": "address..city" },
286
+ { "group": "refusals", "name": "an unknown part of a text search", "query": { "$text": { "$search": "a", "$language": "en" } }, "error": true, "at": "$text.$language" },
287
+ { "group": "refusals", "name": "$fields that is not a list, beside an empty phrase", "query": { "$text": { "$search": "", "$fields": 42 } }, "error": true, "at": "$text.$fields" },
288
+ { "group": "refusals", "name": "an empty $fields, beside an empty phrase", "query": { "$text": { "$search": "", "$fields": [] } }, "error": true, "at": "$text.$fields" }
289
+ ]
290
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Adds the methods.
3
+ *
4
+ * Safe to call more than once, and safe when another copy of JQL has already installed
5
+ * them: whichever copy got there first keeps the field. Throws only for a method of the
6
+ * same name that is not JQL's at all, because installing over that would silently change
7
+ * what its callers run.
8
+ */
9
+ export declare function install(): void;
10
+ /** Removes JQL's methods — this copy's, and any another copy of it installed. */
11
+ export declare function uninstall(): void;
@@ -0,0 +1,44 @@
1
+ import { type CompileOptions } from "./core.js";
2
+ import type { FieldName, QueryLike, Request, Source } from "./types.js";
3
+ /**
4
+ * The same questions, asked of something that arrives over time.
5
+ *
6
+ * A log file read line by line, a cursor over a store, a stream of events: the items are
7
+ * the same shape, and the query that filters an array should filter these. Everything here
8
+ * is the ordinary engine with an `await` in the loop — the query is compiled once, before
9
+ * the first item, and the helpers stop reading as soon as the answer cannot change.
10
+ *
11
+ * That last property is the point of `filterStream` and of a limit: a source that never
12
+ * ends still answers "the first twenty matches", because nothing pulls from it afterwards.
13
+ * Sorting is the exception and says so — an order over items you have not seen yet is not
14
+ * an order — so `searchAsync` with a sort reads the whole source, keeping only the page it
15
+ * was asked for.
16
+ */
17
+ /** Anything that yields items, now or later. */
18
+ export type AsyncSource<T> = AsyncIterable<T> | Source<T> | ReadonlyMap<unknown, T>;
19
+ /** Every matching item, as they arrive. The caller can stop at any point, and the source stops with it. */
20
+ export declare function filterStream<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): AsyncGenerator<T>;
21
+ /** The first match, or `undefined`. Stops reading at it. */
22
+ export declare function findAsync<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<T | undefined>;
23
+ /** Every match, as an array. Reads the whole source, so give an endless one a limit through `searchAsync`. */
24
+ export declare function filterAsync<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<T[]>;
25
+ /** How many match. */
26
+ export declare function countAsync<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<number>;
27
+ /** Whether any item matches. Stops at the first. */
28
+ export declare function someAsync<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<boolean>;
29
+ /** Whether every item matches. True for an empty source. Stops at the first that does not. */
30
+ export declare function everyAsync<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<boolean>;
31
+ /**
32
+ * A whole request over a source that arrives over time.
33
+ *
34
+ * Without a sort it stops as soon as the page is full, so an endless source still answers.
35
+ * With a sort it reads everything — it has to — but holds only `skip + limit` items while
36
+ * it does, the same bounded heap the synchronous version uses.
37
+ */
38
+ export declare function searchAsync<T, Extra extends object = {}>(source: AsyncSource<T>, request: Request<T, NoInfer<Extra>> & {
39
+ readonly fields: readonly FieldName<T, NoInfer<Extra>>[];
40
+ }, options?: CompileOptions<Extra>): Promise<Record<string, unknown>[]>;
41
+ export declare function searchAsync<T, Extra extends object = {}>(source: AsyncSource<T>, request: Request<T, NoInfer<Extra>> & {
42
+ readonly omit: readonly FieldName<T, NoInfer<Extra>>[];
43
+ }, options?: CompileOptions<Extra>): Promise<Partial<T>[]>;
44
+ export declare function searchAsync<T, Extra extends object = {}>(source: AsyncSource<T>, request?: Request<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<T[]>;
@@ -0,0 +1,18 @@
1
+ import { type CompileOptions } from "./core.js";
2
+ import type { QueryLike, UntypedQuery } from "./types.js";
3
+ /**
4
+ * The canonical form of a query.
5
+ *
6
+ * Refuses anything that is not a valid query, because a canonical form of nonsense would
7
+ * be nonsense somebody had stored deliberately.
8
+ */
9
+ export declare function canonical<T, Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): UntypedQuery;
10
+ /**
11
+ * A short, stable name for a query's canonical form.
12
+ *
13
+ * For cache keys, for telling two saved filters apart, and for noticing that the filter
14
+ * somebody is saving is one they already have. Not a cryptographic hash: it is a
15
+ * 64-bit checksum written as hex, so two different queries can collide — rarely, and never
16
+ * in a way that matters for a cache you can miss.
17
+ */
18
+ export declare function fingerprint<T, Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Adds the methods.
3
+ *
4
+ * Safe to call more than once, and safe when another copy of JQL has already installed
5
+ * them: whichever copy got there first keeps the field. Throws only for a method of the
6
+ * same name that is not JQL's at all, because installing over that would silently change
7
+ * what its callers run.
8
+ */
9
+ export declare function install(): void;
10
+ /** Removes JQL's methods — this copy's, and any another copy of it installed. */
11
+ export declare function uninstall(): void;
@@ -0,0 +1,44 @@
1
+ import { type CompileOptions } from "./core.js";
2
+ import type { FieldName, QueryLike, Request, Source } from "./types.js";
3
+ /**
4
+ * The same questions, asked of something that arrives over time.
5
+ *
6
+ * A log file read line by line, a cursor over a store, a stream of events: the items are
7
+ * the same shape, and the query that filters an array should filter these. Everything here
8
+ * is the ordinary engine with an `await` in the loop — the query is compiled once, before
9
+ * the first item, and the helpers stop reading as soon as the answer cannot change.
10
+ *
11
+ * That last property is the point of `filterStream` and of a limit: a source that never
12
+ * ends still answers "the first twenty matches", because nothing pulls from it afterwards.
13
+ * Sorting is the exception and says so — an order over items you have not seen yet is not
14
+ * an order — so `searchAsync` with a sort reads the whole source, keeping only the page it
15
+ * was asked for.
16
+ */
17
+ /** Anything that yields items, now or later. */
18
+ export type AsyncSource<T> = AsyncIterable<T> | Source<T> | ReadonlyMap<unknown, T>;
19
+ /** Every matching item, as they arrive. The caller can stop at any point, and the source stops with it. */
20
+ export declare function filterStream<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): AsyncGenerator<T>;
21
+ /** The first match, or `undefined`. Stops reading at it. */
22
+ export declare function findAsync<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<T | undefined>;
23
+ /** Every match, as an array. Reads the whole source, so give an endless one a limit through `searchAsync`. */
24
+ export declare function filterAsync<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<T[]>;
25
+ /** How many match. */
26
+ export declare function countAsync<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<number>;
27
+ /** Whether any item matches. Stops at the first. */
28
+ export declare function someAsync<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<boolean>;
29
+ /** Whether every item matches. True for an empty source. Stops at the first that does not. */
30
+ export declare function everyAsync<T, Extra extends object = {}>(source: AsyncSource<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<boolean>;
31
+ /**
32
+ * A whole request over a source that arrives over time.
33
+ *
34
+ * Without a sort it stops as soon as the page is full, so an endless source still answers.
35
+ * With a sort it reads everything — it has to — but holds only `skip + limit` items while
36
+ * it does, the same bounded heap the synchronous version uses.
37
+ */
38
+ export declare function searchAsync<T, Extra extends object = {}>(source: AsyncSource<T>, request: Request<T, NoInfer<Extra>> & {
39
+ readonly fields: readonly FieldName<T, NoInfer<Extra>>[];
40
+ }, options?: CompileOptions<Extra>): Promise<Record<string, unknown>[]>;
41
+ export declare function searchAsync<T, Extra extends object = {}>(source: AsyncSource<T>, request: Request<T, NoInfer<Extra>> & {
42
+ readonly omit: readonly FieldName<T, NoInfer<Extra>>[];
43
+ }, options?: CompileOptions<Extra>): Promise<Partial<T>[]>;
44
+ export declare function searchAsync<T, Extra extends object = {}>(source: AsyncSource<T>, request?: Request<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Promise<T[]>;
@@ -0,0 +1,18 @@
1
+ import { type CompileOptions } from "./core.js";
2
+ import type { QueryLike, UntypedQuery } from "./types.js";
3
+ /**
4
+ * The canonical form of a query.
5
+ *
6
+ * Refuses anything that is not a valid query, because a canonical form of nonsense would
7
+ * be nonsense somebody had stored deliberately.
8
+ */
9
+ export declare function canonical<T, Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): UntypedQuery;
10
+ /**
11
+ * A short, stable name for a query's canonical form.
12
+ *
13
+ * For cache keys, for telling two saved filters apart, and for noticing that the filter
14
+ * somebody is saving is one they already have. Not a cryptographic hash: it is a
15
+ * 64-bit checksum written as hex, so two different queries can collide — rarely, and never
16
+ * in a way that matters for a cache you can miss.
17
+ */
18
+ export declare function fingerprint<T, Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): string;
@@ -0,0 +1,15 @@
1
+ export interface Streams {
2
+ readonly out: (text: string) => void;
3
+ readonly err: (text: string) => void;
4
+ /** Where a `-` argument, or no file at all, reads from. */
5
+ readonly stdin: () => AsyncIterable<string>;
6
+ }
7
+ /** Runs the command. Returns the exit code rather than calling `process.exit`, so it can be tested in-process. */
8
+ export declare function main(argv: readonly string[], io: Streams): Promise<number>;
9
+ /**
10
+ * The command as a process: reads stdin, writes the streams, sets the exit code.
11
+ *
12
+ * Kept apart from `main` so the tests can call `main` with writers of their own and assert
13
+ * which stream each line went to — part of the contract, not an implementation detail.
14
+ */
15
+ export declare function run(argv?: readonly string[]): Promise<void>;
@@ -0,0 +1,34 @@
1
+ import { type CompileOptions } from "./core.js";
2
+ import type { QueryLike, Source } from "./types.js";
3
+ /** The first item that matches, or `undefined`. Stops at the first match. */
4
+ export declare function find<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): V | undefined;
5
+ export declare function find<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T | undefined;
6
+ /** The position of the first item that matches, or -1. For a `Map`, the position in insertion order. */
7
+ export declare function findIndex<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
8
+ export declare function findIndex<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
9
+ /** Every item that matches, in order, as a new array. */
10
+ export declare function filter<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): V[];
11
+ export declare function filter<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T[];
12
+ /** How many items match. Counts in place rather than building the list it would count. */
13
+ export declare function count<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
14
+ export declare function count<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
15
+ /** Whether any item matches. Stops at the first. */
16
+ export declare function some<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
17
+ export declare function some<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
18
+ /** Whether every item matches. True for an empty collection. Stops at the first that does not. */
19
+ export declare function every<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
20
+ export declare function every<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
21
+ /** The matches and the rest, in one pass. */
22
+ export declare function partition<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): [V[], V[]];
23
+ export declare function partition<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): [T[], T[]];
24
+ /** The entries of a `Map` whose value matches, as a new `Map` with the same keys. */
25
+ export declare function filterMap<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): Map<K, V>;
26
+ /**
27
+ * The properties of a plain object whose value matches, as a new object.
28
+ *
29
+ * Own enumerable properties only — the same ones `Object.entries` sees.
30
+ */
31
+ export declare function filterRecord<V, Extra extends object = {}>(source: Readonly<Record<string, V>>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): Record<string, V>;
32
+ /** The first value of a plain object that matches, with its key. */
33
+ export declare function findEntry<V, Extra extends object = {}>(source: Readonly<Record<string, V>>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): [string, V] | undefined;
34
+ export declare function findEntry<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): [K, V] | undefined;