@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.
- package/CHANGELOG.md +220 -0
- package/LICENSE +102 -0
- package/README.md +84 -0
- package/bin/jql.mjs +5 -0
- package/conformance/cases.json +290 -0
- package/dist/array.d.ts +11 -0
- package/dist/async.d.ts +44 -0
- package/dist/canonical.d.ts +18 -0
- package/dist/cjs/array.d.ts +11 -0
- package/dist/cjs/async.d.ts +44 -0
- package/dist/cjs/canonical.d.ts +18 -0
- package/dist/cjs/cli.d.ts +15 -0
- package/dist/cjs/collections.d.ts +34 -0
- package/dist/cjs/core.d.ts +117 -0
- package/dist/cjs/errors.d.ts +17 -0
- package/dist/cjs/explain.d.ts +30 -0
- package/dist/cjs/global.d.ts +90 -0
- package/dist/cjs/group.d.ts +47 -0
- package/dist/cjs/index.d.ts +25 -0
- package/dist/cjs/internal/closest.d.ts +9 -0
- package/dist/cjs/internal/duration.d.ts +15 -0
- package/dist/cjs/internal/equal.d.ts +34 -0
- package/dist/cjs/internal/glob.d.ts +30 -0
- package/dist/cjs/internal/order.d.ts +24 -0
- package/dist/cjs/internal/path.d.ts +109 -0
- package/dist/cjs/internal/record.d.ts +18 -0
- package/dist/cjs/internal/values.d.ts +41 -0
- package/dist/cjs/limits.d.ts +64 -0
- package/dist/cjs/operators.d.ts +81 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/plan.d.ts +79 -0
- package/dist/cjs/search.d.ts +42 -0
- package/dist/cjs/targets/mongo.d.ts +55 -0
- package/dist/cjs/text/index.d.ts +12 -0
- package/dist/cjs/text/parse.d.ts +91 -0
- package/dist/cjs/text/suggest.d.ts +16 -0
- package/dist/cjs/text/write.d.ts +34 -0
- package/dist/cjs/types.d.ts +236 -0
- package/dist/cjs/vocabulary.d.ts +106 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +2729 -0
- package/dist/cli.js.map +1 -0
- package/dist/collections.d.ts +34 -0
- package/dist/core.d.ts +117 -0
- package/dist/errors.d.ts +17 -0
- package/dist/explain.d.ts +30 -0
- package/dist/global.cjs +1953 -0
- package/dist/global.cjs.map +1 -0
- package/dist/global.d.ts +90 -0
- package/dist/global.js +1950 -0
- package/dist/global.js.map +1 -0
- package/dist/group.d.ts +47 -0
- package/dist/index.cjs +2529 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +2495 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/closest.d.ts +9 -0
- package/dist/internal/duration.d.ts +15 -0
- package/dist/internal/equal.d.ts +34 -0
- package/dist/internal/glob.d.ts +30 -0
- package/dist/internal/order.d.ts +24 -0
- package/dist/internal/path.d.ts +109 -0
- package/dist/internal/record.d.ts +18 -0
- package/dist/internal/values.d.ts +41 -0
- package/dist/limits.d.ts +64 -0
- package/dist/mongo.cjs +357 -0
- package/dist/mongo.cjs.map +1 -0
- package/dist/mongo.js +354 -0
- package/dist/mongo.js.map +1 -0
- package/dist/operators.d.ts +81 -0
- package/dist/plan.d.ts +79 -0
- package/dist/search.d.ts +42 -0
- package/dist/targets/mongo.d.ts +55 -0
- package/dist/text/index.d.ts +12 -0
- package/dist/text/parse.d.ts +91 -0
- package/dist/text/suggest.d.ts +16 -0
- package/dist/text/write.d.ts +34 -0
- package/dist/text.cjs +674 -0
- package/dist/text.cjs.map +1 -0
- package/dist/text.js +667 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +236 -0
- package/dist/vocabulary.d.ts +106 -0
- package/docs/course/01-first-query.md +217 -0
- package/docs/course/02-operators.md +285 -0
- package/docs/course/03-arrays-and-paths.md +239 -0
- package/docs/course/04-combining.md +221 -0
- package/docs/course/05-dates.md +214 -0
- package/docs/course/06-typed-queries.md +240 -0
- package/docs/course/07-requests.md +261 -0
- package/docs/course/08-grouping.md +210 -0
- package/docs/course/09-explaining.md +171 -0
- package/docs/course/10-vocabulary.md +276 -0
- package/docs/course/11-the-search-box.md +349 -0
- package/docs/course/12-untrusted.md +257 -0
- package/docs/course/13-saved-filters.md +199 -0
- package/docs/course/14-streams-and-cli.md +276 -0
- package/docs/course/15-pushdown.md +240 -0
- package/docs/course/16-extending.md +199 -0
- package/docs/course/index.md +185 -0
- package/docs/design/decisions.md +198 -0
- package/docs/design/performance.md +102 -0
- package/docs/guides/adopting.md +81 -0
- package/docs/guides/pushdown.md +147 -0
- package/docs/guides/typescript.md +115 -0
- package/docs/guides/untrusted-input.md +86 -0
- package/docs/index.md +102 -0
- package/docs/reference/api.md +266 -0
- package/docs/reference/cli.md +103 -0
- package/docs/reference/index.md +12 -0
- package/docs/reference/specification.md +549 -0
- package/docs/reference/text-syntax.md +152 -0
- package/docs/start/quick-start.md +84 -0
- package/package.json +136 -0
package/dist/mongo.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var JqlError = class extends Error {
|
|
3
|
+
/**
|
|
4
|
+
* Where in the query the problem is, written the way you would reach it in code:
|
|
5
|
+
* `$or[1].age.$gt`. Empty for the query as a whole.
|
|
6
|
+
*/
|
|
7
|
+
at;
|
|
8
|
+
constructor(message, at = "") {
|
|
9
|
+
super(at === "" ? message : `at ${show(at)}: ${message}`);
|
|
10
|
+
this.name = "JqlError";
|
|
11
|
+
this.at = at;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
function show(at) {
|
|
15
|
+
return at.length > 80 ? `${at.slice(0, 80)}\u2026` : at;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/internal/duration.ts
|
|
19
|
+
var PART = /(\d+)(ms|s|m|h|d|w)/g;
|
|
20
|
+
var SHAPE = /^(?:\d+(?:ms|s|m|h|d|w))+$/;
|
|
21
|
+
var UNITS = {
|
|
22
|
+
ms: 1,
|
|
23
|
+
s: 1e3,
|
|
24
|
+
m: 6e4,
|
|
25
|
+
h: 36e5,
|
|
26
|
+
d: 864e5,
|
|
27
|
+
w: 6048e5
|
|
28
|
+
};
|
|
29
|
+
function parseDuration(text) {
|
|
30
|
+
if (!SHAPE.test(text)) return void 0;
|
|
31
|
+
let total = 0;
|
|
32
|
+
PART.lastIndex = 0;
|
|
33
|
+
for (; ; ) {
|
|
34
|
+
const part = PART.exec(text);
|
|
35
|
+
if (part === null) break;
|
|
36
|
+
const count = Number(part[1]);
|
|
37
|
+
if (!Number.isSafeInteger(count)) return void 0;
|
|
38
|
+
total += count * UNITS[part[2]];
|
|
39
|
+
}
|
|
40
|
+
return Number.isSafeInteger(total) ? total : void 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/internal/values.ts
|
|
44
|
+
function isPlainObject(value) {
|
|
45
|
+
if (value === null || typeof value !== "object") return false;
|
|
46
|
+
const prototype = Object.getPrototypeOf(value);
|
|
47
|
+
return prototype === Object.prototype || prototype === null;
|
|
48
|
+
}
|
|
49
|
+
function isDateLiteral(value) {
|
|
50
|
+
if (!isPlainObject(value) || !("$date" in value)) return false;
|
|
51
|
+
for (const key in value) if (key !== "$date") return false;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
function resolveDate(value, now) {
|
|
55
|
+
if (typeof value === "number") return value;
|
|
56
|
+
if (typeof value === "string") return value === "now" ? now : instant(value);
|
|
57
|
+
if (isPlainObject(value)) {
|
|
58
|
+
const keys = Object.keys(value);
|
|
59
|
+
if (keys.length !== 1) return Number.NaN;
|
|
60
|
+
const amount = keys[0] === "$ago" || keys[0] === "$ahead" ? value[keys[0]] : void 0;
|
|
61
|
+
if (typeof amount !== "string") return Number.NaN;
|
|
62
|
+
const span = parseDuration(amount);
|
|
63
|
+
if (span === void 0) return Number.NaN;
|
|
64
|
+
return keys[0] === "$ago" ? now - span : now + span;
|
|
65
|
+
}
|
|
66
|
+
return Number.NaN;
|
|
67
|
+
}
|
|
68
|
+
var NO_OFFSET = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?)$/;
|
|
69
|
+
function instant(text) {
|
|
70
|
+
const bare = NO_OFFSET.exec(text);
|
|
71
|
+
return bare === null ? Date.parse(text) : Date.parse(`${bare[1]}T${bare[2]}Z`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/internal/glob.ts
|
|
75
|
+
var ANY = { any: true };
|
|
76
|
+
var ONE = { one: true };
|
|
77
|
+
function compileGlobPattern(pattern2, quoteLiteral2) {
|
|
78
|
+
return tokenize(pattern2).map((token) => token === ANY ? "[\\s\\S]*" : token === ONE ? "[\\s\\S]" : quoteLiteral2(token)).join("");
|
|
79
|
+
}
|
|
80
|
+
function tokenize(pattern2) {
|
|
81
|
+
const tokens = [];
|
|
82
|
+
let escaped = false;
|
|
83
|
+
for (const character of pattern2) {
|
|
84
|
+
if (escaped) {
|
|
85
|
+
tokens.push(character);
|
|
86
|
+
escaped = false;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (character === "\\") {
|
|
90
|
+
escaped = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (character === "*") {
|
|
94
|
+
if (tokens[tokens.length - 1] !== ANY) tokens.push(ANY);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
tokens.push(character === "?" ? ONE : character);
|
|
98
|
+
}
|
|
99
|
+
if (escaped) tokens.push("\\");
|
|
100
|
+
return tokens;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/targets/mongo.ts
|
|
104
|
+
var MONGO_CAPABILITIES = Object.freeze({
|
|
105
|
+
fields: "all",
|
|
106
|
+
operators: Object.freeze([
|
|
107
|
+
"$eq",
|
|
108
|
+
"$ne",
|
|
109
|
+
"$gt",
|
|
110
|
+
"$gte",
|
|
111
|
+
"$lt",
|
|
112
|
+
"$lte",
|
|
113
|
+
"$in",
|
|
114
|
+
"$nin",
|
|
115
|
+
"$exists",
|
|
116
|
+
"$type",
|
|
117
|
+
"$regex",
|
|
118
|
+
"$options",
|
|
119
|
+
"$mod",
|
|
120
|
+
"$size",
|
|
121
|
+
"$all",
|
|
122
|
+
"$elemMatch",
|
|
123
|
+
"$not",
|
|
124
|
+
// Translated to anchored, escaped patterns, which MongoDB runs exactly as JQL does.
|
|
125
|
+
"$contains",
|
|
126
|
+
"$startsWith",
|
|
127
|
+
"$endsWith",
|
|
128
|
+
"$glob"
|
|
129
|
+
]),
|
|
130
|
+
or: true,
|
|
131
|
+
not: true,
|
|
132
|
+
accepts: (_field, condition) => pushable(condition)
|
|
133
|
+
});
|
|
134
|
+
function pushable(condition) {
|
|
135
|
+
const flags = typeof condition.$options === "string" ? condition.$options : "";
|
|
136
|
+
for (const [key, operand] of Object.entries(condition)) {
|
|
137
|
+
switch (key) {
|
|
138
|
+
case "$eq":
|
|
139
|
+
case "$ne":
|
|
140
|
+
case "$in":
|
|
141
|
+
case "$nin":
|
|
142
|
+
case "$all": {
|
|
143
|
+
for (const value of Array.isArray(operand) && key !== "$eq" && key !== "$ne" ? operand : [operand]) {
|
|
144
|
+
if (composite(value)) return false;
|
|
145
|
+
if (flags.includes("i") && typeof value !== "string") return false;
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "$type": {
|
|
150
|
+
const names = Array.isArray(operand) ? operand : [operand];
|
|
151
|
+
if (names.includes("integer") || names.includes("bigint")) return false;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
case "$regex":
|
|
155
|
+
if ([...flags].some((flag) => !"ims".includes(flag))) return false;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
function composite(value) {
|
|
162
|
+
if (Array.isArray(value)) return value.some(composite);
|
|
163
|
+
return isPlainObject(value) && !isDateLiteral(value);
|
|
164
|
+
}
|
|
165
|
+
function toMongoFilter(query, options = {}) {
|
|
166
|
+
const now = options.now === void 0 ? Date.now() : options.now();
|
|
167
|
+
return translateQuery(query, { vocabulary: options.vocabulary, now }, "");
|
|
168
|
+
}
|
|
169
|
+
function translateQuery(query, context, at) {
|
|
170
|
+
if (!isPlainObject(query)) throw new JqlError(`a query is an object, not ${typeof query}`, at);
|
|
171
|
+
const parts = [];
|
|
172
|
+
for (const [key, value] of Object.entries(query)) {
|
|
173
|
+
const here = at === "" ? key : `${at}.${key}`;
|
|
174
|
+
switch (key) {
|
|
175
|
+
case "$comment":
|
|
176
|
+
break;
|
|
177
|
+
case "$and":
|
|
178
|
+
case "$or":
|
|
179
|
+
case "$nor": {
|
|
180
|
+
if (!Array.isArray(value)) throw new JqlError(`${key} takes a list of queries`, here);
|
|
181
|
+
parts.push({ [key]: value.map((part, index) => translateQuery(part, context, `${here}[${index}]`)) });
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case "$not":
|
|
185
|
+
parts.push({ $nor: [translateQuery(value, context, here)] });
|
|
186
|
+
break;
|
|
187
|
+
default: {
|
|
188
|
+
if (key.startsWith("$")) throw new JqlError(`"${key}" has no MongoDB filter of its own; plan() keeps it out of what is pushed`, here);
|
|
189
|
+
parts.push(translateField(key, value, context, here));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (parts.length === 0) return {};
|
|
194
|
+
if (parts.length === 1) return parts[0];
|
|
195
|
+
const merged = {};
|
|
196
|
+
for (const part of parts) {
|
|
197
|
+
for (const [key, value] of Object.entries(part)) {
|
|
198
|
+
if (key in merged) return { $and: parts };
|
|
199
|
+
merged[key] = value;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return merged;
|
|
203
|
+
}
|
|
204
|
+
function translateField(name, value, context, at) {
|
|
205
|
+
const field = context.vocabulary?.lookup.get(name.toLowerCase());
|
|
206
|
+
if (field?.get !== void 0) throw new JqlError(`"${name}" is computed here, so MongoDB has no such field`, at);
|
|
207
|
+
const path = field?.path ?? name;
|
|
208
|
+
if (!isPlainObject(value) || isDateLiteral(value)) return { [path]: literal(value, context, at) };
|
|
209
|
+
const keys = Object.keys(value);
|
|
210
|
+
if (keys.length === 0 || !keys.every((key) => key.startsWith("$"))) return { [path]: literal(value, context, at) };
|
|
211
|
+
const flags = typeof value.$options === "string" ? value.$options : "";
|
|
212
|
+
const ignoreCase = flags.includes("i");
|
|
213
|
+
const condition = {};
|
|
214
|
+
const beside = [];
|
|
215
|
+
for (const key of keys) {
|
|
216
|
+
if (key === "$options" || key === "$comment") continue;
|
|
217
|
+
const operand = value[key];
|
|
218
|
+
const here = `${at}.${key}`;
|
|
219
|
+
switch (key) {
|
|
220
|
+
case "$eq":
|
|
221
|
+
case "$ne": {
|
|
222
|
+
if (ignoreCase && typeof operand === "string") {
|
|
223
|
+
const match = { [path]: anchored(quoteLiteral(operand), flags, here) };
|
|
224
|
+
beside.push(key === "$eq" ? match : { $nor: [match] });
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
condition[key] = literal(operand, context, here);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "$gt":
|
|
231
|
+
case "$gte":
|
|
232
|
+
case "$lt":
|
|
233
|
+
case "$lte":
|
|
234
|
+
condition[key] = literal(operand, context, here);
|
|
235
|
+
break;
|
|
236
|
+
case "$in":
|
|
237
|
+
case "$nin": {
|
|
238
|
+
if (!Array.isArray(operand)) throw new JqlError(`${key} takes a list`, here);
|
|
239
|
+
const members = operand.map((item) => ignoreCase && typeof item === "string" ? anchored(quoteLiteral(item), flags, here) : literal(item, context, here));
|
|
240
|
+
if (key === "$nin" && ignoreCase && members.some((member) => member instanceof RegExp)) beside.push({ $nor: [{ [path]: { $in: members } }] });
|
|
241
|
+
else condition[key] = members;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case "$all": {
|
|
245
|
+
if (!Array.isArray(operand)) throw new JqlError("$all takes a list", here);
|
|
246
|
+
condition[key] = operand.map((item) => literal(item, context, here));
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case "$exists":
|
|
250
|
+
case "$mod":
|
|
251
|
+
case "$size":
|
|
252
|
+
condition[key] = operand;
|
|
253
|
+
break;
|
|
254
|
+
case "$type":
|
|
255
|
+
condition[key] = mongoTypes(operand, here);
|
|
256
|
+
break;
|
|
257
|
+
case "$regex":
|
|
258
|
+
condition.$regex = operand;
|
|
259
|
+
if (keep(flags, here) !== "") condition.$options = keep(flags, here);
|
|
260
|
+
break;
|
|
261
|
+
case "$contains":
|
|
262
|
+
case "$startsWith":
|
|
263
|
+
case "$endsWith":
|
|
264
|
+
case "$glob": {
|
|
265
|
+
const patterns = (Array.isArray(operand) ? operand : [operand]).map((item) => {
|
|
266
|
+
if (typeof item !== "string") throw new JqlError(`${key} takes strings`, here);
|
|
267
|
+
return pattern(key, item, flags, here);
|
|
268
|
+
});
|
|
269
|
+
if (patterns.length === 1) beside.push({ [path]: patterns[0] });
|
|
270
|
+
else beside.push({ [path]: { $in: patterns } });
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case "$elemMatch":
|
|
274
|
+
condition.$elemMatch = translateQuery(operand, context, here);
|
|
275
|
+
break;
|
|
276
|
+
case "$not": {
|
|
277
|
+
if (!isPlainObject(operand)) throw new JqlError("$not takes a condition", here);
|
|
278
|
+
beside.push({ $nor: [translateField(name, operand, context, here)] });
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
default:
|
|
282
|
+
throw new JqlError(`"${key}" has no MongoDB filter of its own; plan() keeps it out of what is pushed`, here);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const written = [];
|
|
286
|
+
if (Object.keys(condition).length > 0) written.push({ [path]: condition });
|
|
287
|
+
written.push(...beside);
|
|
288
|
+
if (written.length === 1) return written[0];
|
|
289
|
+
return { $and: written };
|
|
290
|
+
}
|
|
291
|
+
function literal(value, context, at) {
|
|
292
|
+
if (isDateLiteral(value)) {
|
|
293
|
+
const time = resolveDate(value.$date, context.now);
|
|
294
|
+
if (Number.isNaN(time)) throw new JqlError(`${JSON.stringify(value.$date)} is not a date`, at);
|
|
295
|
+
return new Date(time);
|
|
296
|
+
}
|
|
297
|
+
if (Array.isArray(value)) return value.map((item) => literal(item, context, at));
|
|
298
|
+
if (isPlainObject(value)) {
|
|
299
|
+
const out = {};
|
|
300
|
+
for (const [key, held] of Object.entries(value)) out[key] = literal(held, context, at);
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
var TYPES = {
|
|
306
|
+
string: ["string"],
|
|
307
|
+
number: ["double", "int", "long", "decimal"],
|
|
308
|
+
integer: ["int", "long"],
|
|
309
|
+
bigint: ["long"],
|
|
310
|
+
boolean: ["bool"],
|
|
311
|
+
null: ["null"],
|
|
312
|
+
array: ["array"],
|
|
313
|
+
object: ["object"],
|
|
314
|
+
date: ["date"]
|
|
315
|
+
};
|
|
316
|
+
function mongoTypes(operand, at) {
|
|
317
|
+
const names = Array.isArray(operand) ? operand : [operand];
|
|
318
|
+
const out = [];
|
|
319
|
+
for (const name of names) {
|
|
320
|
+
const mapped = TYPES[name];
|
|
321
|
+
if (mapped === void 0) throw new JqlError(`${JSON.stringify(name)} is not a type name`, at);
|
|
322
|
+
for (const alias of mapped) if (!out.includes(alias)) out.push(alias);
|
|
323
|
+
}
|
|
324
|
+
return out;
|
|
325
|
+
}
|
|
326
|
+
var SPECIAL = /[.*+?^${}()|[\]\\]/g;
|
|
327
|
+
function quoteLiteral(text) {
|
|
328
|
+
return text.replace(SPECIAL, "\\$&");
|
|
329
|
+
}
|
|
330
|
+
function anchored(body, flags, at) {
|
|
331
|
+
return new RegExp(`^${body}$`, keep(flags, at));
|
|
332
|
+
}
|
|
333
|
+
function keep(flags, at) {
|
|
334
|
+
for (const flag of flags) {
|
|
335
|
+
if (!"ims".includes(flag)) throw new JqlError(`MongoDB has no "${flag}" flag on a pattern; plan() keeps a clause carrying one here rather than pushing it`, at);
|
|
336
|
+
}
|
|
337
|
+
return flags;
|
|
338
|
+
}
|
|
339
|
+
function pattern(operator, value, flags, at) {
|
|
340
|
+
switch (operator) {
|
|
341
|
+
case "$contains":
|
|
342
|
+
return new RegExp(quoteLiteral(value), keep(flags, at));
|
|
343
|
+
case "$startsWith":
|
|
344
|
+
return new RegExp(`^${quoteLiteral(value)}`, keep(flags, at));
|
|
345
|
+
case "$endsWith":
|
|
346
|
+
return new RegExp(`${quoteLiteral(value)}$`, keep(flags, at));
|
|
347
|
+
default:
|
|
348
|
+
return new RegExp(`^${compileGlobPattern(value, quoteLiteral)}$`, keep(flags, at));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export { MONGO_CAPABILITIES, toMongoFilter };
|
|
353
|
+
//# sourceMappingURL=mongo.js.map
|
|
354
|
+
//# sourceMappingURL=mongo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/internal/duration.ts","../src/internal/values.ts","../src/internal/glob.ts","../src/targets/mongo.ts"],"names":["pattern","quoteLiteral"],"mappings":"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","file":"mongo.js","sourcesContent":["/**\n * The one way a query is refused.\n *\n * A query that looks right and matches nothing is the failure this library exists to\n * prevent: a misspelt operator, a field value that is `undefined` because a variable was,\n * a regex that does not compile. Each of those used to be a filter that silently let\n * everything through or nothing through, and nobody could tell which from the result. So\n * `compile` refuses, with a sentence saying what was wrong and where in the query it was.\n */\nexport class JqlError extends Error {\n /**\n * Where in the query the problem is, written the way you would reach it in code:\n * `$or[1].age.$gt`. Empty for the query as a whole.\n */\n readonly at: string;\n\n constructor(message: string, at = \"\") {\n super(at === \"\" ? message : `at ${show(at)}: ${message}`);\n this.name = \"JqlError\";\n this.at = at;\n }\n}\n\n/**\n * The location, short enough to read.\n *\n * A location is built from the query's own field names, and a field name is a string from\n * outside — a long one makes the sentence it prefixes unreadable, and turns a log line into\n * a paragraph. `at` itself keeps the whole path, because that is the part a program reads.\n */\nfunction show(at: string): string {\n return at.length > 80 ? `${at.slice(0, 80)}…` : at;\n}\n","/**\n * Durations, for the relative dates a saved filter is written with.\n *\n * `\"1h\"`, `\"30m\"`, `\"1h30m\"`, `\"7d\"`. Written as one or more counts with a unit, largest\n * first by convention but in any order, because a filter is typed by a person and\n * `\"30m1h\"` means what it says.\n *\n * Months and years are deliberately absent: neither has a fixed length, so `\"1mo\"` would\n * mean something slightly different in February, and a filter whose window changes size\n * with the calendar is one nobody can reason about. Use days or weeks.\n */\n\nconst PART = /(\\d+)(ms|s|m|h|d|w)/g;\nconst SHAPE = /^(?:\\d+(?:ms|s|m|h|d|w))+$/;\n\nconst UNITS: Readonly<Record<string, number>> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n w: 604_800_000,\n};\n\n/** The unit names, for an error that says what is accepted. */\nexport const DURATION_UNITS = Object.keys(UNITS);\n\n/** A duration in milliseconds, or `undefined` when the text is not one. */\nexport function parseDuration(text: string): number | undefined {\n if (!SHAPE.test(text)) return undefined;\n let total = 0;\n PART.lastIndex = 0;\n for (;;) {\n const part = PART.exec(text);\n if (part === null) break;\n const count = Number(part[1]);\n // A count that does not survive the round trip through a number is not a duration\n // anybody meant; it is a digit string long enough to have lost its tail.\n if (!Number.isSafeInteger(count)) return undefined;\n total += count * (UNITS[part[2] as string] as number);\n }\n return Number.isSafeInteger(total) ? total : undefined;\n}\n","import { parseDuration } from \"./duration.js\";\nimport type { DateLiteral, FieldReference, TypeName } from \"../types.js\";\n\n/** An object literal or a JSON-parsed object: what a query document is made of. */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\n/** `{ $date: … }` and nothing else. */\nexport function isDateLiteral(value: unknown): value is DateLiteral {\n if (!isPlainObject(value) || !(\"$date\" in value)) return false;\n for (const key in value) if (key !== \"$date\") return false;\n return true;\n}\n\n/** `{ $field: \"path\" }` and nothing else: a comparison with another field of the same item. */\nexport function isFieldReference(value: unknown): value is FieldReference {\n if (!isPlainObject(value) || typeof value.$field !== \"string\") return false;\n for (const key in value) if (key !== \"$field\") return false;\n return true;\n}\n\n/**\n * The instant a `$date` literal stands for, in epoch milliseconds, or `NaN`.\n *\n * `now` is passed in rather than read here, because a query holding `{ \"$ago\": \"1h\" }` has\n * to resolve against one instant for the whole run: reading the clock per comparison would\n * let the window move while a scan is in progress, so two items a second apart could be\n * judged against different hours. It is also what makes a relative filter testable without\n * waiting.\n */\nexport function resolveDate(value: unknown, now: number): number {\n if (typeof value === \"number\") return value;\n if (typeof value === \"string\") return value === \"now\" ? now : instant(value);\n if (isPlainObject(value)) {\n const keys = Object.keys(value);\n if (keys.length !== 1) return Number.NaN;\n const amount = keys[0] === \"$ago\" || keys[0] === \"$ahead\" ? value[keys[0] as string] : undefined;\n if (typeof amount !== \"string\") return Number.NaN;\n const span = parseDuration(amount);\n if (span === undefined) return Number.NaN;\n return keys[0] === \"$ago\" ? now - span : now + span;\n }\n return Number.NaN;\n}\n\n/**\n * A document value read as a date, in epoch milliseconds, or `NaN`.\n *\n * Only called where the query supplied a `$date`, so a string is only ever parsed as a\n * date because somebody asked for that. `NaN` compares false with everything, which is\n * what a value that is not a date should do.\n */\n/**\n * The forms of a date a document may hold, per the specification: a `Date`, epoch\n * milliseconds, or an **ISO 8601** string.\n *\n * `Date.parse` accepts a great deal more than that — `\"12/31/2020\"`, `\"Mar 5 2021\"`, and\n * `\"5\"`, which it reads as a year — and what it accepts beyond ISO 8601 is left to the\n * implementation by the language it comes from. A document that matched here and nowhere\n * else would make the same query mean different things in different engines, which is the\n * one thing this language promises it does not do. The space instead of `T` is allowed\n * because RFC 3339 allows it and people write it.\n */\nconst ISO_8601 = /^\\d{4}-\\d{2}-\\d{2}(?:[T ]\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?(?:Z|[+-]\\d{2}:?\\d{2})?)?$/;\n\n/** A timestamp with a time and no offset, which is the one shape whose meaning is not fixed. */\nconst NO_OFFSET = /^(\\d{4}-\\d{2}-\\d{2})[T ](\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,9})?)?)$/;\n\n/**\n * An instant from a string, read the same way on every machine.\n *\n * JavaScript reads a date-only string as midnight **UTC** and a timestamp with no offset as\n * **local time** — so `\"2026-01-01T12:00:00\"` was a different instant in Tokyo and in London,\n * and a query and a document that both held one answered differently depending on where the\n * process happened to be running. That is the failure the language already rules out for\n * `\"12/31/2020\"`: the same query meaning two things. A missing offset is UTC here, which is\n * the rule the date-only form already follows.\n */\nexport function instant(text: string): number {\n const bare = NO_OFFSET.exec(text);\n return bare === null ? Date.parse(text) : Date.parse(`${bare[1] as string}T${bare[2] as string}Z`);\n}\n\nexport function toTime(value: unknown): number {\n if (typeof value === \"number\") return value;\n if (value instanceof Date) return value.getTime();\n if (typeof value === \"string\") return ISO_8601.test(value) ? instant(value) : Number.NaN;\n return Number.NaN;\n}\n\n/** The type names `$type` knows, in one list, so the error for a wrong one can offer the right one. */\nexport const TYPE_NAMES: readonly TypeName[] = [\"string\", \"number\", \"integer\", \"bigint\", \"boolean\", \"null\", \"array\", \"object\", \"date\"];\n\n/** Whether a document value is of the named type. A missing value is of no type. */\nexport function isOfType(value: unknown, type: TypeName): boolean {\n switch (type) {\n case \"string\":\n return typeof value === \"string\";\n case \"number\":\n // A big integer is a number for ordering, so it is one here too: `$gt` and `$type`\n // disagreeing about the same value is a distinction nobody could explain.\n return typeof value === \"number\" || typeof value === \"bigint\";\n case \"integer\":\n return Number.isInteger(value) || typeof value === \"bigint\";\n case \"bigint\":\n return typeof value === \"bigint\";\n case \"boolean\":\n return typeof value === \"boolean\";\n case \"null\":\n return value === null;\n case \"array\":\n return Array.isArray(value);\n case \"date\":\n return value instanceof Date;\n case \"object\":\n return value !== null && typeof value === \"object\" && !Array.isArray(value) && !(value instanceof Date);\n }\n}\n\n/**\n * A readable name for a value in an error sentence.\n *\n * Specific about what it was given, because \"a query is an object, not an object\" — which\n * is what a `Map`, a `Set` and a class instance all used to produce — tells the reader\n * nothing at all about the thing they passed.\n */\nexport function describe(value: unknown): string {\n if (value === null) return \"null\";\n // Named rather than written out: `String(fn)` is the function's entire source, and a\n // closure of any size turned the sentence refusing it into a listing.\n if (typeof value === \"function\") return \"a function\";\n if (Array.isArray(value)) return \"an array\";\n if (value instanceof RegExp) return \"a RegExp object\";\n if (value instanceof Date) return \"a Date object\";\n if (value instanceof Map) return \"a Map\";\n if (value instanceof Set) return \"a Set\";\n if (typeof value === \"object\") {\n if (isPlainObject(value)) return \"an object\";\n const name = nameOf(value);\n return name === undefined ? \"an object\" : `an instance of ${name}`;\n }\n if (typeof value === \"string\") return `the string ${JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}…` : value)}`;\n return `${typeof value} ${String(value)}`;\n}\n\n/** The class an object came from, when it can be read without running anything of the caller's. */\nfunction nameOf(value: object): string | undefined {\n const prototype = Object.getPrototypeOf(value) as { constructor?: { name?: unknown } } | null;\n // Read from the prototype rather than through the object, so a `constructor` field of\n // the caller's own — which a document or a query read from JSON can have — is not what\n // names it.\n const name = prototype?.constructor?.name;\n return typeof name === \"string\" && name !== \"\" && name !== \"Object\" ? name : undefined;\n}\n","/**\n * Glob matching: `*`, `?`, and `\\` to escape either of them.\n *\n * What people mean when they reach for a pattern in a search box — `/api/*`,\n * `*.example.com` — and the reason it is not `$regex` with the sharp edges filed off: this\n * runs without a regular-expression engine, so it is available when patterns are turned\n * off for untrusted callers, and it cannot backtrack exponentially.\n *\n * No character classes. `[a-z]` is a literal `[a-z]`, because half a class syntax is worse\n * than none: somebody would write `[!abc]` expecting negation and get a match against those\n * four characters instead, with nothing to say so.\n *\n * **A pattern is taken apart once and becomes the narrowest matcher that fits it.** Nearly\n * every real glob is a prefix, a suffix, something in the middle, or a run of literals\n * separated by stars, and each of those is one or two native string calls per value. Only a\n * pattern with `?` in it walks character by character, and only that walk pays for splitting\n * the text into characters — which, measured against `startsWith`, was four times the cost of\n * the whole comparison.\n */\n\nexport type GlobMatcher = (text: string) => boolean;\n\n/**\n * What `*` and `?` become once the pattern is taken apart.\n *\n * Objects rather than strings, because a string sentinel is a string a *pattern* can also\n * contain: with a NUL-prefixed marker standing for `*`, the glob `a*\\u0000\\*` re-formed the\n * marker when the tokens were joined back together and was split in the wrong place — a\n * glob that silently matched everything.\n */\nconst ANY = { any: true } as const;\nconst ONE = { one: true } as const;\n\ntype Token = string | typeof ANY | typeof ONE;\n\nexport function compileGlob(pattern: string): GlobMatcher {\n const tokens = tokenize(pattern);\n const stars = tokens.includes(ANY);\n const questions = tokens.includes(ONE);\n\n if (!stars && !questions) {\n const literal = tokens.join(\"\");\n return (text) => text === literal;\n }\n if (!questions) {\n // Literal runs separated by stars: each run is found with `indexOf`, left to right.\n const segments: string[] = [\"\"];\n for (const token of tokens) {\n if (token === ANY) segments.push(\"\");\n else segments[segments.length - 1] += token as string;\n }\n const open = segments[0] === \"\";\n const close = segments[segments.length - 1] === \"\";\n const parts = segments.filter((segment) => segment !== \"\");\n if (parts.length === 0) return () => true;\n if (open && close && parts.length === 1) {\n const inside = parts[0] as string;\n return (text) => text.includes(inside);\n }\n if (!open && close && parts.length === 1) {\n const prefix = parts[0] as string;\n return (text) => text.startsWith(prefix);\n }\n if (open && !close && parts.length === 1) {\n const suffix = parts[0] as string;\n return (text) => text.endsWith(suffix);\n }\n return (text) => scan(text, parts, open, close);\n }\n return (text) => walk([...text], tokens);\n}\n\n/**\n * The same glob as the body of a regular expression, for a target that speaks patterns\n * rather than running the matcher — a store this query is being pushed down to.\n *\n * It shares the tokenizer with the matcher on purpose: an escape read one way here and\n * another way there would mean a query that found different things depending on which half\n * of the system answered it.\n */\nexport function compileGlobPattern(pattern: string, quoteLiteral: (text: string) => string): string {\n return tokenize(pattern)\n .map((token) => (token === ANY ? \"[\\\\s\\\\S]*\" : token === ONE ? \"[\\\\s\\\\S]\" : quoteLiteral(token as string)))\n .join(\"\");\n}\n\n/** Takes the pattern apart: literal characters, `ANY` for `*`, `ONE` for `?`. */\nfunction tokenize(pattern: string): Token[] {\n const tokens: Token[] = [];\n let escaped = false;\n for (const character of pattern) {\n if (escaped) {\n tokens.push(character);\n escaped = false;\n continue;\n }\n if (character === \"\\\\\") {\n escaped = true;\n continue;\n }\n if (character === \"*\") {\n // A run of stars means what one means, and collapsing it here keeps the matcher's\n // backtracking to one mark per run.\n if (tokens[tokens.length - 1] !== ANY) tokens.push(ANY);\n continue;\n }\n tokens.push(character === \"?\" ? ONE : character);\n }\n // A trailing backslash is somebody mid-word, not an error: it stands for itself.\n if (escaped) tokens.push(\"\\\\\");\n return tokens;\n}\n\n/** Literal runs in order, anchored at each end unless a star opens or closes the pattern. */\nfunction scan(text: string, parts: readonly string[], open: boolean, close: boolean): boolean {\n let at = 0;\n const last = parts.length - 1;\n for (let i = 0; i <= last; i++) {\n const part = parts[i] as string;\n if (i === 0 && !open) {\n if (!text.startsWith(part)) return false;\n at = part.length;\n continue;\n }\n if (i === last && !close) {\n // The final run has to be at the very end, and must not overlap what is matched.\n return text.length - part.length >= at && text.endsWith(part);\n }\n const found = text.indexOf(part, at);\n if (found === -1) return false;\n at = found + part.length;\n }\n return true;\n}\n\n/** The two-pointer walk, for patterns holding `?`. Quadratic at worst, linear on anything typed. */\nfunction walk(characters: readonly string[], tokens: readonly Token[]): boolean {\n let at = 0;\n let token = 0;\n let star = -1;\n let mark = 0;\n while (at < characters.length) {\n const current = tokens[token];\n if (current !== undefined && (current === ONE || current === characters[at])) {\n at++;\n token++;\n continue;\n }\n if (current === ANY) {\n star = token++;\n mark = at;\n continue;\n }\n if (star !== -1) {\n token = star + 1;\n at = ++mark;\n continue;\n }\n return false;\n }\n while (tokens[token] === ANY) token++;\n return token === tokens.length;\n}\n","import { JqlError } from \"../errors.js\";\nimport { isDateLiteral, isPlainObject, resolveDate } from \"../internal/values.js\";\nimport { compileGlobPattern } from \"../internal/glob.js\";\nimport type { Capabilities } from \"../plan.js\";\nimport type { TypeName } from \"../types.js\";\nimport type { Vocabulary } from \"../vocabulary.js\";\n\n/**\n * JQL as a MongoDB filter.\n *\n * The first target, because it is the one where the two languages nearly agree: most of\n * JQL's operators *are* MongoDB's, with the same array semantics, so the translation is\n * mostly a copy and the interesting part is the handful that differ.\n *\n * The translation is checked by *running* it, not by comparing its shape: `tests/helpers/mongo.ts`\n * is a second reading of MongoDB's matching rules, taken from its documented behaviour rather\n * than from this engine, and the split test runs both halves of every generated query through\n * it and through the engine and compares the answers.\n *\n * That check found the one difference no capability can express: **an array held directly\n * inside another array**. A path segment in MongoDB applies to an array's elements but not to\n * the elements of *those* arrays; JQL sees an array through at every level. Which rows come\n * back then differs, in either direction, and nothing in the query says so — the shape is in\n * the data. It is written down in specification §3 and in the pushdown guide, because a\n * caller with such a collection must not read `complete` as permission to skip the second\n * pass.\n *\n * What it will not do is guess. Every operator MongoDB cannot answer exactly —\n * `$word`, `$length`, a `{ \"$field\": … }` reference, `$text` — is left out of\n * `MONGO_CAPABILITIES`, so `plan()` keeps those clauses here and this function never sees\n * them. A translation that was *nearly* right would be the worst of both: fewer rows than\n * the query asked for, from a store that looked like it had answered.\n */\n\n/**\n * What a MongoDB `find` filter can answer, in JQL's own names.\n *\n * Give it to `plan()` and it splits a query into the filter to send and the predicate to\n * apply to what comes back.\n */\nexport const MONGO_CAPABILITIES: Capabilities = Object.freeze({\n fields: \"all\",\n operators: Object.freeze([\n \"$eq\",\n \"$ne\",\n \"$gt\",\n \"$gte\",\n \"$lt\",\n \"$lte\",\n \"$in\",\n \"$nin\",\n \"$exists\",\n \"$type\",\n \"$regex\",\n \"$options\",\n \"$mod\",\n \"$size\",\n \"$all\",\n \"$elemMatch\",\n \"$not\",\n // Translated to anchored, escaped patterns, which MongoDB runs exactly as JQL does.\n \"$contains\",\n \"$startsWith\",\n \"$endsWith\",\n \"$glob\",\n ]),\n or: true,\n not: true,\n accepts: (_field: string, condition: Readonly<Record<string, unknown>>) => pushable(condition),\n});\n\n/** Whether MongoDB answers this condition **exactly**, which some of its operators do only for some values. */\nfunction pushable(condition: Readonly<Record<string, unknown>>): boolean {\n const flags = typeof condition.$options === \"string\" ? condition.$options : \"\";\n for (const [key, operand] of Object.entries(condition)) {\n switch (key) {\n case \"$eq\":\n case \"$ne\":\n case \"$in\":\n case \"$nin\":\n case \"$all\": {\n for (const value of Array.isArray(operand) && key !== \"$eq\" && key !== \"$ne\" ? operand : [operand]) {\n // JQL compares objects without regard to key order; MongoDB's embedded-document\n // equality is a byte comparison, so `{ c: 2, b: 1 }` is not `{ b: 1, c: 2 }` there.\n if (composite(value)) return false;\n // Ignoring case reaches the strings inside a list. A pattern can stand in for one\n // string, and for nothing deeper.\n if (flags.includes(\"i\") && typeof value !== \"string\") return false;\n }\n break;\n }\n case \"$type\": {\n // JQL's `integer` is \"a number with no fractional part\"; MongoDB's `int`/`long` are\n // storage types, so a whole number held as a double is one and not the other.\n // `bigint` is worse: it names a run-time type JSON cannot carry at all, and what a\n // driver hands back for a stored `long` is usually an ordinary number — so the store\n // would answer with rows this engine says are not bigints, and `complete` would say\n // no second pass was needed. Both stay here.\n const names = Array.isArray(operand) ? operand : [operand];\n if (names.includes(\"integer\") || names.includes(\"bigint\")) return false;\n break;\n }\n case \"$regex\":\n // MongoDB takes `i`, `m`, `s` and `x`; JQL's `u` is not one it knows, and a filter\n // it refuses does not run at all.\n if ([...flags].some((flag) => !\"ims\".includes(flag))) return false;\n break;\n default:\n break;\n }\n }\n return true;\n}\n\n/** A value MongoDB compares byte for byte, where JQL compares it by its keys. */\nfunction composite(value: unknown): boolean {\n if (Array.isArray(value)) return value.some(composite);\n return isPlainObject(value) && !isDateLiteral(value);\n}\n\nexport interface MongoOptions {\n /**\n * Resolves a vocabulary's names and aliases to the paths the collection stores.\n *\n * Only needed for a query that did not come from `plan()`: what `plan` pushes already\n * carries the stored names.\n */\n readonly vocabulary?: Vocabulary<never, object> | undefined;\n /** Where \"now\" comes from, for a relative date. Default the system clock, read once. */\n readonly now?: (() => number) | undefined;\n}\n\n/** A MongoDB filter document. Values may be `Date` and `RegExp` objects, which a driver expects. */\nexport type MongoFilter = Record<string, unknown>;\n\n/**\n * Translates a JQL query into a MongoDB filter.\n *\n * Give it only what `plan(query, MONGO_CAPABILITIES)` pushed. Anything else is refused by\n * name rather than approximated.\n */\nexport function toMongoFilter(query: unknown, options: MongoOptions = {}): MongoFilter {\n const now = options.now === undefined ? Date.now() : options.now();\n return translateQuery(query, { vocabulary: options.vocabulary, now }, \"\");\n}\n\ninterface Translating {\n readonly vocabulary: Vocabulary<never, object> | undefined;\n readonly now: number;\n}\n\nfunction translateQuery(query: unknown, context: Translating, at: string): MongoFilter {\n if (!isPlainObject(query)) throw new JqlError(`a query is an object, not ${typeof query}`, at);\n const parts: MongoFilter[] = [];\n for (const [key, value] of Object.entries(query)) {\n const here = at === \"\" ? key : `${at}.${key}`;\n switch (key) {\n case \"$comment\":\n break;\n case \"$and\":\n case \"$or\":\n case \"$nor\": {\n if (!Array.isArray(value)) throw new JqlError(`${key} takes a list of queries`, here);\n parts.push({ [key]: value.map((part, index) => translateQuery(part, context, `${here}[${index}]`)) });\n break;\n }\n case \"$not\":\n // MongoDB has no `$not` over a whole query; `$nor` of one is exactly it.\n parts.push({ $nor: [translateQuery(value, context, here)] });\n break;\n default: {\n if (key.startsWith(\"$\")) throw new JqlError(`\"${key}\" has no MongoDB filter of its own; plan() keeps it out of what is pushed`, here);\n parts.push(translateField(key, value, context, here));\n }\n }\n }\n if (parts.length === 0) return {};\n if (parts.length === 1) return parts[0] as MongoFilter;\n // Merged where the keys do not collide, and `$and` where they do — the same filter either\n // way, and the merged one is what somebody reading the query in a shell would have written.\n const merged: MongoFilter = {};\n for (const part of parts) {\n for (const [key, value] of Object.entries(part)) {\n if (key in merged) return { $and: parts };\n merged[key] = value;\n }\n }\n return merged;\n}\n\nfunction translateField(name: string, value: unknown, context: Translating, at: string): MongoFilter {\n const field = context.vocabulary?.lookup.get(name.toLowerCase());\n if (field?.get !== undefined) throw new JqlError(`\"${name}\" is computed here, so MongoDB has no such field`, at);\n const path = field?.path ?? name;\n if (!isPlainObject(value) || isDateLiteral(value)) return { [path]: literal(value, context, at) };\n const keys = Object.keys(value);\n if (keys.length === 0 || !keys.every((key) => key.startsWith(\"$\"))) return { [path]: literal(value, context, at) };\n\n const flags = typeof value.$options === \"string\" ? value.$options : \"\";\n const ignoreCase = flags.includes(\"i\");\n const condition: Record<string, unknown> = {};\n const beside: MongoFilter[] = [];\n for (const key of keys) {\n if (key === \"$options\" || key === \"$comment\") continue;\n const operand = value[key];\n const here = `${at}.${key}`;\n switch (key) {\n case \"$eq\":\n case \"$ne\": {\n if (ignoreCase && typeof operand === \"string\") {\n // A bare pattern, not `{ $eq: /…/ }`. MongoDB documents the bare form as a match\n // against the pattern, while `$eq` with a regular expression is a comparison with\n // the regex *value* as often as it is a match — and a translation that rests on\n // which reading a server takes is one that returns the wrong rows on the servers\n // that take the other. `$nor` negates it without the same question.\n const match = { [path]: anchored(quoteLiteral(operand), flags, here) };\n beside.push(key === \"$eq\" ? match : { $nor: [match] });\n break;\n }\n condition[key] = literal(operand, context, here);\n break;\n }\n case \"$gt\":\n case \"$gte\":\n case \"$lt\":\n case \"$lte\":\n condition[key] = literal(operand, context, here);\n break;\n case \"$in\":\n case \"$nin\": {\n if (!Array.isArray(operand)) throw new JqlError(`${key} takes a list`, here);\n const members = operand.map((item) => (ignoreCase && typeof item === \"string\" ? anchored(quoteLiteral(item), flags, here) : literal(item, context, here)));\n // `$in` takes patterns; `$nin` with one is less clearly documented, so its\n // case-insensitive form is written as the negation of an `$in`, which is.\n if (key === \"$nin\" && ignoreCase && members.some((member) => member instanceof RegExp)) beside.push({ $nor: [{ [path]: { $in: members } }] });\n else condition[key] = members;\n break;\n }\n case \"$all\": {\n if (!Array.isArray(operand)) throw new JqlError(\"$all takes a list\", here);\n condition[key] = operand.map((item) => literal(item, context, here));\n break;\n }\n case \"$exists\":\n case \"$mod\":\n case \"$size\":\n condition[key] = operand;\n break;\n case \"$type\":\n condition[key] = mongoTypes(operand, here);\n break;\n case \"$regex\":\n condition.$regex = operand;\n // Only the flags MongoDB takes; `plan` keeps back the ones it does not, and this is\n // the second lock on the same door.\n if (keep(flags, here) !== \"\") condition.$options = keep(flags, here);\n break;\n case \"$contains\":\n case \"$startsWith\":\n case \"$endsWith\":\n case \"$glob\": {\n const patterns = (Array.isArray(operand) ? operand : [operand]).map((item) => {\n if (typeof item !== \"string\") throw new JqlError(`${key} takes strings`, here);\n return pattern(key, item, flags, here);\n });\n // A list means \"any of these\", which MongoDB spells `$in` over the patterns.\n if (patterns.length === 1) beside.push({ [path]: patterns[0] as RegExp });\n else beside.push({ [path]: { $in: patterns } });\n break;\n }\n case \"$elemMatch\":\n condition.$elemMatch = translateQuery(operand, context, here);\n break;\n case \"$not\": {\n if (!isPlainObject(operand)) throw new JqlError(\"$not takes a condition\", here);\n // MongoDB's field-level `$not` refuses some of what JQL allows inside it, and\n // `$nor` of the same clause is exactly the same question with none of the rules.\n beside.push({ $nor: [translateField(name, operand, context, here)] });\n break;\n }\n default:\n throw new JqlError(`\"${key}\" has no MongoDB filter of its own; plan() keeps it out of what is pushed`, here);\n }\n }\n const written: MongoFilter[] = [];\n if (Object.keys(condition).length > 0) written.push({ [path]: condition });\n written.push(...beside);\n if (written.length === 1) return written[0] as MongoFilter;\n return { $and: written };\n}\n\n/** A JQL literal as MongoDB would hold it: a date literal becomes a `Date`. */\nfunction literal(value: unknown, context: Translating, at: string): unknown {\n if (isDateLiteral(value)) {\n const time = resolveDate(value.$date, context.now);\n if (Number.isNaN(time)) throw new JqlError(`${JSON.stringify(value.$date)} is not a date`, at);\n return new Date(time);\n }\n if (Array.isArray(value)) return value.map((item) => literal(item, context, at));\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const [key, held] of Object.entries(value)) out[key] = literal(held, context, at);\n return out;\n }\n return value;\n}\n\n/** JQL's type names, in MongoDB's. */\nconst TYPES: Readonly<Record<TypeName, readonly string[]>> = {\n string: [\"string\"],\n number: [\"double\", \"int\", \"long\", \"decimal\"],\n integer: [\"int\", \"long\"],\n bigint: [\"long\"],\n boolean: [\"bool\"],\n null: [\"null\"],\n array: [\"array\"],\n object: [\"object\"],\n date: [\"date\"],\n};\n\nfunction mongoTypes(operand: unknown, at: string): string[] {\n const names = Array.isArray(operand) ? operand : [operand];\n const out: string[] = [];\n for (const name of names) {\n const mapped = TYPES[name as TypeName];\n if (mapped === undefined) throw new JqlError(`${JSON.stringify(name)} is not a type name`, at);\n for (const alias of mapped) if (!out.includes(alias)) out.push(alias);\n }\n return out;\n}\n\nconst SPECIAL = /[.*+?^${}()|[\\]\\\\]/g;\n\nfunction quoteLiteral(text: string): string {\n return text.replace(SPECIAL, \"\\\\$&\");\n}\n\nfunction anchored(body: string, flags: string, at: string): RegExp {\n return new RegExp(`^${body}$`, keep(flags, at));\n}\n\n/**\n * The flags MongoDB takes on a pattern, and only those.\n *\n * A refusal rather than a filter: `plan` keeps a clause with any other flag here, so\n * reaching this at all means `toMongoFilter` was handed something `plan` did not push. Both\n * of the other answers are worse than saying so — dropping the flag changes what the pattern\n * means, and passing it on gives the server a filter it refuses outright, so the query fails\n * later and somewhere else.\n */\nfunction keep(flags: string, at: string): string {\n for (const flag of flags) {\n if (!\"ims\".includes(flag)) throw new JqlError(`MongoDB has no \"${flag}\" flag on a pattern; plan() keeps a clause carrying one here rather than pushing it`, at);\n }\n return flags;\n}\n\nfunction pattern(operator: string, value: string, flags: string, at: string): RegExp {\n switch (operator) {\n case \"$contains\":\n return new RegExp(quoteLiteral(value), keep(flags, at));\n case \"$startsWith\":\n return new RegExp(`^${quoteLiteral(value)}`, keep(flags, at));\n case \"$endsWith\":\n return new RegExp(`${quoteLiteral(value)}$`, keep(flags, at));\n default:\n return new RegExp(`^${compileGlobPattern(value, quoteLiteral)}$`, keep(flags, at));\n }\n}\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each field operator means, as a test on one value.
|
|
3
|
+
*
|
|
4
|
+
* Every builder here runs once, when a query is compiled, and returns the smallest
|
|
5
|
+
* closure that answers its question: the operand is validated, normalised and, where it
|
|
6
|
+
* helps, turned into a `Set` or a lower-cased copy before any document is seen. The
|
|
7
|
+
* returned test only ever does the part that depends on the document.
|
|
8
|
+
*
|
|
9
|
+
* None of these knows about arrays or paths. Whether a test is also tried against each
|
|
10
|
+
* element of an array, and how a field is reached, is `core.ts`'s business — so each
|
|
11
|
+
* operator's meaning is written exactly once.
|
|
12
|
+
*/
|
|
13
|
+
export type Test = (value: unknown) => boolean;
|
|
14
|
+
export declare const NEVER: Test;
|
|
15
|
+
/**
|
|
16
|
+
* Equality with one literal.
|
|
17
|
+
*
|
|
18
|
+
* `null` also matches a missing field, because "has no value" is what
|
|
19
|
+
* people mean by it far more often than "has the value null". `$exists` is there for
|
|
20
|
+
* the times the difference matters.
|
|
21
|
+
*/
|
|
22
|
+
export declare function equalsTest(literal: unknown, ignoreCase: boolean, at: string, now: number): Test;
|
|
23
|
+
/** Membership. A `Set` for the primitives, and a walk only for the object and array literals. */
|
|
24
|
+
export declare function inTest(list: unknown, ignoreCase: boolean, at: string, now: number): Test;
|
|
25
|
+
type Ordering = "$gt" | "$gte" | "$lt" | "$lte";
|
|
26
|
+
/**
|
|
27
|
+
* An ordering comparison.
|
|
28
|
+
*
|
|
29
|
+
* Only like with like: a number against numbers (and bigints), a string against strings
|
|
30
|
+
* in code-unit order, a `$date` against anything that reads as a date. Nothing is
|
|
31
|
+
* coerced, so `"10"` is not greater than `9` — a comparison that converted would give a
|
|
32
|
+
* different answer depending on which side happened to be a string, and a query that
|
|
33
|
+
* means different things on different documents is not one anybody can reason about.
|
|
34
|
+
*/
|
|
35
|
+
export declare function orderTest(operator: Ordering, bound: unknown, at: string, now: number): Test;
|
|
36
|
+
type StringOperator = "$contains" | "$startsWith" | "$endsWith" | "$word" | "$glob";
|
|
37
|
+
/**
|
|
38
|
+
* The string operators, each taking one value or a list meaning "any of these".
|
|
39
|
+
*
|
|
40
|
+
* A list is there because "the path starts with any of `/health`, `/metrics`" is a single
|
|
41
|
+
* thought, and spelling it as an `$or` of three conditions makes it three.
|
|
42
|
+
*/
|
|
43
|
+
export declare function stringTest(operator: StringOperator, operand: unknown, ignoreCase: boolean, at: string, maxGlobLength?: number): Test;
|
|
44
|
+
/**
|
|
45
|
+
* The value as a whole component of the text: bounded on each side by the start or end of
|
|
46
|
+
* the text, or by a character that is not a letter or digit.
|
|
47
|
+
*
|
|
48
|
+
* Ported from BotHandler's actor matching, where it replaced a substring test that let
|
|
49
|
+
* `1.2.3.4` match `1.2.3.45` and `11.2.3.4` — so excluding one address excluded its
|
|
50
|
+
* neighbours too. A side of the value that is itself a separator has chosen its own
|
|
51
|
+
* boundary: `203.0.113.` finds the whole network.
|
|
52
|
+
*/
|
|
53
|
+
export declare function containsWord(actual: string, wanted: string): boolean;
|
|
54
|
+
export declare function parseOptions(options: unknown, at: string): string;
|
|
55
|
+
export declare function regexTest(pattern: unknown, flags: string, limits: {
|
|
56
|
+
allowRegex: boolean;
|
|
57
|
+
maxPatternLength: number;
|
|
58
|
+
}, at: string): Test;
|
|
59
|
+
export declare function typeTest(operand: unknown, at: string): Test;
|
|
60
|
+
export declare function modTest(operand: unknown, at: string): Test;
|
|
61
|
+
/** `$all`: every literal is equal to the value or to one of its elements. An empty list matches nothing. */
|
|
62
|
+
export declare function allTest(list: unknown, ignoreCase: boolean, at: string, now: number): Test;
|
|
63
|
+
/**
|
|
64
|
+
* The order of two values that both came out of a document, or `undefined` when they
|
|
65
|
+
* cannot be compared.
|
|
66
|
+
*
|
|
67
|
+
* Only like with like, as everywhere else in the language: numbers with numbers, strings
|
|
68
|
+
* with strings, dates with dates. Nothing is read as a date here, because neither side is
|
|
69
|
+
* a query saying it is one — `{ "$field": … }` compares what the item holds.
|
|
70
|
+
*/
|
|
71
|
+
export declare function orderOf(a: unknown, b: unknown): number | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Refuses a literal JSON cannot carry.
|
|
74
|
+
*
|
|
75
|
+
* A `RegExp`, a `Date`, a function or `undefined` in a query would work in the process
|
|
76
|
+
* that wrote it and mean something else — or nothing — once the query was serialised.
|
|
77
|
+
* The language is the JSON, so the engine holds to it even when handed an object that
|
|
78
|
+
* could have been more.
|
|
79
|
+
*/
|
|
80
|
+
export declare function checkLiteral(value: unknown, at: string, depth?: number): void;
|
|
81
|
+
export {};
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type CompileOptions } from "./core.js";
|
|
2
|
+
import type { QueryLike, UntypedQuery } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Splitting a query between a store and this engine.
|
|
5
|
+
*
|
|
6
|
+
* A backend can usually answer part of a question — an index on a field, an operator it has
|
|
7
|
+
* natively — and nothing of the rest. The shape that works is the one HackerPot's stores
|
|
8
|
+
* already use: push what the store can do, keep what it cannot, and make sure the two
|
|
9
|
+
* together are exactly the question that was asked.
|
|
10
|
+
*
|
|
11
|
+
* That last part is the whole contract, and it is why this splits only on **conjunctions**.
|
|
12
|
+
* A query's keys all have to hold, and so do the parts of an `$and`, so each one can go to
|
|
13
|
+
* either side independently. Anything else — a branch of an `$or`, an operator inside a
|
|
14
|
+
* condition — cannot be split without widening or narrowing what is asked, so a conjunct is
|
|
15
|
+
* pushed whole or not at all:
|
|
16
|
+
*
|
|
17
|
+
* pushed ∧ remaining ≡ query
|
|
18
|
+
*
|
|
19
|
+
* The store filters with `pushed` and this engine filters what comes back with `remaining`.
|
|
20
|
+
* Neither side is ever wider than the query, so a caller that runs only `pushed` gets too
|
|
21
|
+
* many items rather than too few — but `complete` says when that is safe, and it is the only
|
|
22
|
+
* thing worth checking before skipping the second pass.
|
|
23
|
+
*/
|
|
24
|
+
export interface Capabilities {
|
|
25
|
+
/**
|
|
26
|
+
* The fields the store can filter on, as the store names them — a path, after any
|
|
27
|
+
* vocabulary alias is resolved. `"all"` for a store that can filter on anything.
|
|
28
|
+
*
|
|
29
|
+
* A vocabulary field that is *computed* is never pushed whatever this says: the store has
|
|
30
|
+
* no way to run a function that lives here.
|
|
31
|
+
*/
|
|
32
|
+
readonly fields?: readonly string[] | "all" | undefined;
|
|
33
|
+
/** The operators the store can answer, or `"all"`. Names are JQL's, not the store's. */
|
|
34
|
+
readonly operators?: readonly string[] | "all" | undefined;
|
|
35
|
+
/** Whether the store can answer `$or`. Default `false`: plenty of key-value stores cannot. */
|
|
36
|
+
readonly or?: boolean | undefined;
|
|
37
|
+
/** Whether it can answer a negation — `$not`, `$nor`, `$ne`, `$nin`, `$exists: false`. Default `false`. */
|
|
38
|
+
readonly not?: boolean | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* A last word on one field's condition, for a store whose limits depend on the values
|
|
41
|
+
* rather than only on the operator names.
|
|
42
|
+
*
|
|
43
|
+
* MongoDB is the example this exists for: it answers `$eq` exactly, but not `$eq` against
|
|
44
|
+
* an embedded document (its equality is key-order sensitive where JQL's is not), and not
|
|
45
|
+
* the case-insensitive form of one. Without a say on the values, a target has to choose
|
|
46
|
+
* between dropping `$eq` altogether and pushing a clause that comes back with the wrong
|
|
47
|
+
* rows.
|
|
48
|
+
*
|
|
49
|
+
* The condition is given as written, with a bare value shown as `{ $eq: value }`, and the
|
|
50
|
+
* field under the name the store knows. Returning `false` keeps the clause here.
|
|
51
|
+
*/
|
|
52
|
+
readonly accepts?: ((field: string, condition: Readonly<Record<string, unknown>>) => boolean) | undefined;
|
|
53
|
+
}
|
|
54
|
+
export interface Kept {
|
|
55
|
+
/** Where the conjunct is in the query. */
|
|
56
|
+
readonly at: string;
|
|
57
|
+
/** The conjunct itself. */
|
|
58
|
+
readonly clause: unknown;
|
|
59
|
+
/** Why the store cannot answer it. */
|
|
60
|
+
readonly why: string;
|
|
61
|
+
}
|
|
62
|
+
export interface Plan {
|
|
63
|
+
/** What the store should filter by, or `undefined` when it can answer nothing. */
|
|
64
|
+
readonly pushed: UntypedQuery | undefined;
|
|
65
|
+
/** What this engine must still filter, or `undefined` when the store answers everything. */
|
|
66
|
+
readonly remaining: UntypedQuery | undefined;
|
|
67
|
+
/** True when `remaining` is `undefined`: the store's answer needs no second pass. */
|
|
68
|
+
readonly complete: boolean;
|
|
69
|
+
/** One entry per conjunct that stayed here, and why. */
|
|
70
|
+
readonly kept: readonly Kept[];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Splits a query into the part a store can answer and the part that stays here.
|
|
74
|
+
*
|
|
75
|
+
* Relative dates in the pushed part are resolved to instants, so what the store receives is
|
|
76
|
+
* self-contained: `{ "$ago": "1h" }` means nothing to a backend, and resolving it here keeps
|
|
77
|
+
* both halves of the split judged against the same moment.
|
|
78
|
+
*/
|
|
79
|
+
export declare function plan<T, Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, capabilities: Capabilities, options?: CompileOptions<Extra>): Plan;
|