@andrew_l/search-query-language 0.3.21 → 0.4.0
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/dist/index.d.mts +259 -268
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +475 -678
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -8
- package/dist/index.cjs +0 -727
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -478
- package/dist/index.d.ts +0 -478
package/dist/index.mjs
CHANGED
|
@@ -1,713 +1,510 @@
|
|
|
1
|
-
import { assert, isFunction,
|
|
2
|
-
import mongoose, { isObjectIdOrHexString } from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
1
|
+
import { arrayable, assert, isFunction, isNumber, isString, withCache } from "@andrew_l/toolkit";
|
|
2
|
+
import mongoose, { isObjectIdOrHexString } from "mongoose";
|
|
3
|
+
var TokenType = class {
|
|
4
|
+
label;
|
|
5
|
+
keyword;
|
|
6
|
+
constructor(label, options) {
|
|
7
|
+
this.label = label;
|
|
8
|
+
this.keyword = options?.keyword;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
13
11
|
const NODE = Object.freeze({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
PROGRAM: "program",
|
|
13
|
+
IDENTIFIER: "identifier",
|
|
14
|
+
LITERAL: "literal",
|
|
15
|
+
LOGICAL_EXPRESSION: "logical-expression",
|
|
16
|
+
BINARY_EXPRESSION: "binary-expression"
|
|
19
17
|
});
|
|
20
18
|
const KEYWORDS = {};
|
|
21
19
|
const TOKEN = Object.freeze({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
STRING: new TokenType("string"),
|
|
38
|
-
/**
|
|
39
|
-
* Identifier token.
|
|
40
|
-
*/
|
|
41
|
-
NAME: new TokenType("identifier"),
|
|
42
|
-
/**
|
|
43
|
-
* Parenthesis token.
|
|
44
|
-
*/
|
|
45
|
-
PAREN_L: new TokenType("("),
|
|
46
|
-
/**
|
|
47
|
-
* Parenthesis token.
|
|
48
|
-
*/
|
|
49
|
-
PAREN_R: new TokenType(")"),
|
|
50
|
-
/**
|
|
51
|
-
* Logical operator token.
|
|
52
|
-
*/
|
|
53
|
-
LOGICAL_OR: kw("OR"),
|
|
54
|
-
/**
|
|
55
|
-
* Logical operator token.
|
|
56
|
-
*/
|
|
57
|
-
LOGICAL_AND: kw("AND"),
|
|
58
|
-
/**
|
|
59
|
-
* Equality operator token.
|
|
60
|
-
*/
|
|
61
|
-
EQUALITY: new TokenType("=/!="),
|
|
62
|
-
/**
|
|
63
|
-
* Relational operator token.
|
|
64
|
-
*/
|
|
65
|
-
RELATIONAL: new TokenType("</>/<=/>="),
|
|
66
|
-
/**
|
|
67
|
-
* Minus operator token.
|
|
68
|
-
*/
|
|
69
|
-
MINUS: new TokenType("-"),
|
|
70
|
-
/**
|
|
71
|
-
* Null literal token.
|
|
72
|
-
*/
|
|
73
|
-
NULL: kw("null"),
|
|
74
|
-
/**
|
|
75
|
-
* True literal tokens.
|
|
76
|
-
*/
|
|
77
|
-
TRUE: kw("true"),
|
|
78
|
-
/**
|
|
79
|
-
* False literal tokens.
|
|
80
|
-
*/
|
|
81
|
-
FALSE: kw("false")
|
|
20
|
+
SOF: new TokenType("sof"),
|
|
21
|
+
EOF: new TokenType("eof"),
|
|
22
|
+
NUM: new TokenType("num"),
|
|
23
|
+
STRING: new TokenType("string"),
|
|
24
|
+
NAME: new TokenType("identifier"),
|
|
25
|
+
PAREN_L: new TokenType("("),
|
|
26
|
+
PAREN_R: new TokenType(")"),
|
|
27
|
+
LOGICAL_OR: kw("OR"),
|
|
28
|
+
LOGICAL_AND: kw("AND"),
|
|
29
|
+
EQUALITY: new TokenType("=/!="),
|
|
30
|
+
RELATIONAL: new TokenType("</>/<=/>="),
|
|
31
|
+
MINUS: new TokenType("-"),
|
|
32
|
+
NULL: kw("null"),
|
|
33
|
+
TRUE: kw("true"),
|
|
34
|
+
FALSE: kw("false")
|
|
82
35
|
});
|
|
83
36
|
function kw(name, options = {}) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
class Token {
|
|
89
|
-
type;
|
|
90
|
-
value;
|
|
91
|
-
start;
|
|
92
|
-
end;
|
|
93
|
-
constructor(p) {
|
|
94
|
-
this.type = p.type;
|
|
95
|
-
this.value = p.value;
|
|
96
|
-
this.start = p.start;
|
|
97
|
-
this.end = p.end;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
class Tokenizer {
|
|
102
|
-
input;
|
|
103
|
-
pos;
|
|
104
|
-
length;
|
|
105
|
-
state;
|
|
106
|
-
prev;
|
|
107
|
-
constructor(input) {
|
|
108
|
-
this.input = input;
|
|
109
|
-
this.length = input.length;
|
|
110
|
-
this.pos = 0;
|
|
111
|
-
this.state = {
|
|
112
|
-
start: 0,
|
|
113
|
-
end: 0,
|
|
114
|
-
type: TOKEN.SOF,
|
|
115
|
-
value: null
|
|
116
|
-
};
|
|
117
|
-
this.prev = { ...this.state };
|
|
118
|
-
}
|
|
119
|
-
[Symbol.iterator]() {
|
|
120
|
-
return {
|
|
121
|
-
next: () => {
|
|
122
|
-
let token = this.getToken();
|
|
123
|
-
return {
|
|
124
|
-
done: token.type === TOKEN.EOF,
|
|
125
|
-
value: token
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Get the next token.
|
|
132
|
-
*/
|
|
133
|
-
getToken() {
|
|
134
|
-
this.nextToken();
|
|
135
|
-
const token = new Token(this.state);
|
|
136
|
-
return token;
|
|
137
|
-
}
|
|
138
|
-
nextToken() {
|
|
139
|
-
this.skipSpace();
|
|
140
|
-
this.state.start = this.pos;
|
|
141
|
-
if (this.pos >= this.length) return this.finishToken(TOKEN.EOF);
|
|
142
|
-
this.readToken(this.charCodeAtPos());
|
|
143
|
-
}
|
|
144
|
-
skipSpace() {
|
|
145
|
-
let ch;
|
|
146
|
-
while (this.pos < this.length) {
|
|
147
|
-
ch = this.charCodeAtPos();
|
|
148
|
-
if (ch === 32 || ch === 160) {
|
|
149
|
-
this.pos++;
|
|
150
|
-
} else {
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
finishToken(type, value) {
|
|
156
|
-
Object.assign(this.prev, this.state);
|
|
157
|
-
this.state.end = this.pos;
|
|
158
|
-
this.state.type = type;
|
|
159
|
-
this.state.value = value;
|
|
160
|
-
}
|
|
161
|
-
finishOp(type, size) {
|
|
162
|
-
let str = this.input.slice(this.pos, this.pos + size);
|
|
163
|
-
this.pos += size;
|
|
164
|
-
return this.finishToken(type, str);
|
|
165
|
-
}
|
|
166
|
-
readToken(code) {
|
|
167
|
-
if (isIdentifierStart(code)) {
|
|
168
|
-
return this.readWord();
|
|
169
|
-
}
|
|
170
|
-
return this.getTokenFromCode(code);
|
|
171
|
-
}
|
|
172
|
-
getTokenFromCode(code) {
|
|
173
|
-
switch (code) {
|
|
174
|
-
case 40:
|
|
175
|
-
++this.pos;
|
|
176
|
-
return this.finishToken(TOKEN.PAREN_L);
|
|
177
|
-
case 41:
|
|
178
|
-
++this.pos;
|
|
179
|
-
return this.finishToken(TOKEN.PAREN_R);
|
|
180
|
-
case 49:
|
|
181
|
-
case 50:
|
|
182
|
-
case 51:
|
|
183
|
-
case 52:
|
|
184
|
-
case 53:
|
|
185
|
-
case 54:
|
|
186
|
-
case 55:
|
|
187
|
-
case 56:
|
|
188
|
-
case 57:
|
|
189
|
-
return this.readNumber();
|
|
190
|
-
case 45:
|
|
191
|
-
++this.pos;
|
|
192
|
-
return this.finishToken(TOKEN.MINUS);
|
|
193
|
-
case 61:
|
|
194
|
-
case 33:
|
|
195
|
-
return this.readEquality(code);
|
|
196
|
-
case 60:
|
|
197
|
-
case 62:
|
|
198
|
-
return this.readLtGt();
|
|
199
|
-
case 34:
|
|
200
|
-
return this.readString();
|
|
201
|
-
}
|
|
202
|
-
this.raise(
|
|
203
|
-
this.pos,
|
|
204
|
-
"Unexpected character '" + String.fromCharCode(code) + "'"
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
charCodeAtPos() {
|
|
208
|
-
return this.input.charCodeAt(this.pos);
|
|
209
|
-
}
|
|
210
|
-
readEquality(code) {
|
|
211
|
-
let next = this.input.charCodeAt(this.pos + 1);
|
|
212
|
-
if (code === 33) {
|
|
213
|
-
if (next === 61) return this.finishOp(TOKEN.EQUALITY, 2);
|
|
214
|
-
} else {
|
|
215
|
-
return this.finishOp(TOKEN.EQUALITY, 1);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
readLtGt() {
|
|
219
|
-
let next = this.input.charCodeAt(this.pos + 1);
|
|
220
|
-
let size = 1;
|
|
221
|
-
if (next === 61) size = 2;
|
|
222
|
-
return this.finishOp(TOKEN.RELATIONAL, size);
|
|
223
|
-
}
|
|
224
|
-
readInt() {
|
|
225
|
-
const radix = 10;
|
|
226
|
-
let start = this.pos;
|
|
227
|
-
let code = 0;
|
|
228
|
-
let total = 0;
|
|
229
|
-
for (; ; ) {
|
|
230
|
-
code = this.input.charCodeAt(this.pos);
|
|
231
|
-
if (code >= 48 && code <= 57) {
|
|
232
|
-
total = total * radix + (code - 48);
|
|
233
|
-
this.pos++;
|
|
234
|
-
} else {
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
if (this.pos === start) return null;
|
|
239
|
-
return total;
|
|
240
|
-
}
|
|
241
|
-
raise(pos, message) {
|
|
242
|
-
throw new SyntaxError(`${message} (${pos})`);
|
|
243
|
-
}
|
|
244
|
-
readString() {
|
|
245
|
-
let out = "";
|
|
246
|
-
let chunkStart = ++this.pos;
|
|
247
|
-
let ch;
|
|
248
|
-
for (; ; ) {
|
|
249
|
-
if (this.pos >= this.length) {
|
|
250
|
-
this.raise(this.state.start, "Unterminated string constant");
|
|
251
|
-
}
|
|
252
|
-
ch = this.input.charCodeAt(this.pos);
|
|
253
|
-
if (ch === 34) break;
|
|
254
|
-
if (ch === 92) {
|
|
255
|
-
out += this.input.slice(chunkStart, this.pos);
|
|
256
|
-
out += this.readEscapedChar();
|
|
257
|
-
chunkStart = this.pos;
|
|
258
|
-
}
|
|
259
|
-
++this.pos;
|
|
260
|
-
}
|
|
261
|
-
out += this.input.slice(chunkStart, this.pos++);
|
|
262
|
-
return this.finishToken(TOKEN.STRING, out);
|
|
263
|
-
}
|
|
264
|
-
readWord() {
|
|
265
|
-
let chunkStart = this.pos;
|
|
266
|
-
let ch;
|
|
267
|
-
let cmp = isIdentifierStart;
|
|
268
|
-
while (this.pos < this.input.length) {
|
|
269
|
-
ch = this.charCodeAtPos();
|
|
270
|
-
if (!cmp(ch)) break;
|
|
271
|
-
cmp = isIdentifierChar;
|
|
272
|
-
this.pos++;
|
|
273
|
-
}
|
|
274
|
-
const word = this.input.slice(chunkStart, this.pos);
|
|
275
|
-
let type = TOKEN.NAME;
|
|
276
|
-
if (word in KEYWORDS) {
|
|
277
|
-
type = KEYWORDS[word];
|
|
278
|
-
}
|
|
279
|
-
return this.finishToken(type, word);
|
|
280
|
-
}
|
|
281
|
-
readEscapedChar() {
|
|
282
|
-
let ch = this.input.charCodeAt(++this.pos);
|
|
283
|
-
++this.pos;
|
|
284
|
-
return String.fromCharCode(ch);
|
|
285
|
-
}
|
|
286
|
-
readNumber() {
|
|
287
|
-
let start = this.pos;
|
|
288
|
-
if (this.readInt() === null) this.raise(start, "Invalid number");
|
|
289
|
-
let next = this.input.charCodeAt(this.pos);
|
|
290
|
-
if (next === 46) {
|
|
291
|
-
++this.pos;
|
|
292
|
-
this.readInt();
|
|
293
|
-
next = this.input.charCodeAt(this.pos);
|
|
294
|
-
}
|
|
295
|
-
let val = Number(this.input.slice(start, this.pos));
|
|
296
|
-
return this.finishToken(TOKEN.NUM, val);
|
|
297
|
-
}
|
|
37
|
+
options.keyword = name;
|
|
38
|
+
return KEYWORDS[name] = new TokenType(name, options);
|
|
298
39
|
}
|
|
40
|
+
var Token = class {
|
|
41
|
+
type;
|
|
42
|
+
value;
|
|
43
|
+
start;
|
|
44
|
+
end;
|
|
45
|
+
constructor(p) {
|
|
46
|
+
this.type = p.type;
|
|
47
|
+
this.value = p.value;
|
|
48
|
+
this.start = p.start;
|
|
49
|
+
this.end = p.end;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var Tokenizer = class {
|
|
53
|
+
input;
|
|
54
|
+
pos;
|
|
55
|
+
length;
|
|
56
|
+
state;
|
|
57
|
+
prev;
|
|
58
|
+
constructor(input) {
|
|
59
|
+
this.input = input;
|
|
60
|
+
this.length = input.length;
|
|
61
|
+
this.pos = 0;
|
|
62
|
+
this.state = {
|
|
63
|
+
start: 0,
|
|
64
|
+
end: 0,
|
|
65
|
+
type: TOKEN.SOF,
|
|
66
|
+
value: null
|
|
67
|
+
};
|
|
68
|
+
this.prev = { ...this.state };
|
|
69
|
+
}
|
|
70
|
+
[Symbol.iterator]() {
|
|
71
|
+
return { next: () => {
|
|
72
|
+
let token = this.getToken();
|
|
73
|
+
return {
|
|
74
|
+
done: token.type === TOKEN.EOF,
|
|
75
|
+
value: token
|
|
76
|
+
};
|
|
77
|
+
} };
|
|
78
|
+
}
|
|
79
|
+
getToken() {
|
|
80
|
+
this.nextToken();
|
|
81
|
+
return new Token(this.state);
|
|
82
|
+
}
|
|
83
|
+
nextToken() {
|
|
84
|
+
this.skipSpace();
|
|
85
|
+
this.state.start = this.pos;
|
|
86
|
+
if (this.pos >= this.length) return this.finishToken(TOKEN.EOF);
|
|
87
|
+
this.readToken(this.charCodeAtPos());
|
|
88
|
+
}
|
|
89
|
+
skipSpace() {
|
|
90
|
+
let ch;
|
|
91
|
+
while (this.pos < this.length) {
|
|
92
|
+
ch = this.charCodeAtPos();
|
|
93
|
+
if (ch === 32 || ch === 160) this.pos++;
|
|
94
|
+
else break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
finishToken(type, value) {
|
|
98
|
+
Object.assign(this.prev, this.state);
|
|
99
|
+
this.state.end = this.pos;
|
|
100
|
+
this.state.type = type;
|
|
101
|
+
this.state.value = value;
|
|
102
|
+
}
|
|
103
|
+
finishOp(type, size) {
|
|
104
|
+
let str = this.input.slice(this.pos, this.pos + size);
|
|
105
|
+
this.pos += size;
|
|
106
|
+
return this.finishToken(type, str);
|
|
107
|
+
}
|
|
108
|
+
readToken(code) {
|
|
109
|
+
if (isIdentifierStart(code)) return this.readWord();
|
|
110
|
+
return this.getTokenFromCode(code);
|
|
111
|
+
}
|
|
112
|
+
getTokenFromCode(code) {
|
|
113
|
+
switch (code) {
|
|
114
|
+
case 40:
|
|
115
|
+
++this.pos;
|
|
116
|
+
return this.finishToken(TOKEN.PAREN_L);
|
|
117
|
+
case 41:
|
|
118
|
+
++this.pos;
|
|
119
|
+
return this.finishToken(TOKEN.PAREN_R);
|
|
120
|
+
case 49:
|
|
121
|
+
case 50:
|
|
122
|
+
case 51:
|
|
123
|
+
case 52:
|
|
124
|
+
case 53:
|
|
125
|
+
case 54:
|
|
126
|
+
case 55:
|
|
127
|
+
case 56:
|
|
128
|
+
case 57: return this.readNumber();
|
|
129
|
+
case 45:
|
|
130
|
+
++this.pos;
|
|
131
|
+
return this.finishToken(TOKEN.MINUS);
|
|
132
|
+
case 61:
|
|
133
|
+
case 33: return this.readEquality(code);
|
|
134
|
+
case 60:
|
|
135
|
+
case 62: return this.readLtGt();
|
|
136
|
+
case 34: return this.readString();
|
|
137
|
+
}
|
|
138
|
+
this.raise(this.pos, "Unexpected character '" + String.fromCharCode(code) + "'");
|
|
139
|
+
}
|
|
140
|
+
charCodeAtPos() {
|
|
141
|
+
return this.input.charCodeAt(this.pos);
|
|
142
|
+
}
|
|
143
|
+
readEquality(code) {
|
|
144
|
+
let next = this.input.charCodeAt(this.pos + 1);
|
|
145
|
+
if (code === 33) {
|
|
146
|
+
if (next === 61) return this.finishOp(TOKEN.EQUALITY, 2);
|
|
147
|
+
} else return this.finishOp(TOKEN.EQUALITY, 1);
|
|
148
|
+
}
|
|
149
|
+
readLtGt() {
|
|
150
|
+
let next = this.input.charCodeAt(this.pos + 1);
|
|
151
|
+
let size = 1;
|
|
152
|
+
if (next === 61) size = 2;
|
|
153
|
+
return this.finishOp(TOKEN.RELATIONAL, size);
|
|
154
|
+
}
|
|
155
|
+
readInt() {
|
|
156
|
+
const radix = 10;
|
|
157
|
+
let start = this.pos;
|
|
158
|
+
let code = 0;
|
|
159
|
+
let total = 0;
|
|
160
|
+
for (;;) {
|
|
161
|
+
code = this.input.charCodeAt(this.pos);
|
|
162
|
+
if (code >= 48 && code <= 57) {
|
|
163
|
+
total = total * radix + (code - 48);
|
|
164
|
+
this.pos++;
|
|
165
|
+
} else break;
|
|
166
|
+
}
|
|
167
|
+
if (this.pos === start) return null;
|
|
168
|
+
return total;
|
|
169
|
+
}
|
|
170
|
+
raise(pos, message) {
|
|
171
|
+
throw new SyntaxError(`${message} (${pos})`);
|
|
172
|
+
}
|
|
173
|
+
readString() {
|
|
174
|
+
let out = "";
|
|
175
|
+
let chunkStart = ++this.pos;
|
|
176
|
+
let ch;
|
|
177
|
+
for (;;) {
|
|
178
|
+
if (this.pos >= this.length) this.raise(this.state.start, "Unterminated string constant");
|
|
179
|
+
ch = this.input.charCodeAt(this.pos);
|
|
180
|
+
if (ch === 34) break;
|
|
181
|
+
if (ch === 92) {
|
|
182
|
+
out += this.input.slice(chunkStart, this.pos);
|
|
183
|
+
out += this.readEscapedChar();
|
|
184
|
+
chunkStart = this.pos;
|
|
185
|
+
}
|
|
186
|
+
++this.pos;
|
|
187
|
+
}
|
|
188
|
+
out += this.input.slice(chunkStart, this.pos++);
|
|
189
|
+
return this.finishToken(TOKEN.STRING, out);
|
|
190
|
+
}
|
|
191
|
+
readWord() {
|
|
192
|
+
let chunkStart = this.pos;
|
|
193
|
+
let ch;
|
|
194
|
+
let cmp = isIdentifierStart;
|
|
195
|
+
while (this.pos < this.input.length) {
|
|
196
|
+
ch = this.charCodeAtPos();
|
|
197
|
+
if (!cmp(ch)) break;
|
|
198
|
+
cmp = isIdentifierChar;
|
|
199
|
+
this.pos++;
|
|
200
|
+
}
|
|
201
|
+
const word = this.input.slice(chunkStart, this.pos);
|
|
202
|
+
let type = TOKEN.NAME;
|
|
203
|
+
if (word in KEYWORDS) type = KEYWORDS[word];
|
|
204
|
+
return this.finishToken(type, word);
|
|
205
|
+
}
|
|
206
|
+
readEscapedChar() {
|
|
207
|
+
let ch = this.input.charCodeAt(++this.pos);
|
|
208
|
+
++this.pos;
|
|
209
|
+
return String.fromCharCode(ch);
|
|
210
|
+
}
|
|
211
|
+
readNumber() {
|
|
212
|
+
let start = this.pos;
|
|
213
|
+
if (this.readInt() === null) this.raise(start, "Invalid number");
|
|
214
|
+
let next = this.input.charCodeAt(this.pos);
|
|
215
|
+
if (next === 46) {
|
|
216
|
+
++this.pos;
|
|
217
|
+
this.readInt();
|
|
218
|
+
next = this.input.charCodeAt(this.pos);
|
|
219
|
+
}
|
|
220
|
+
let val = Number(this.input.slice(start, this.pos));
|
|
221
|
+
return this.finishToken(TOKEN.NUM, val);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
299
224
|
function isIdentifierStart(code) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
225
|
+
if (code < 65) return false;
|
|
226
|
+
if (code < 91) return true;
|
|
227
|
+
if (code < 97) return code === 95;
|
|
228
|
+
if (code < 123) return true;
|
|
229
|
+
return false;
|
|
305
230
|
}
|
|
306
231
|
function isIdentifierChar(code) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
class Expression extends Tokenizer {
|
|
317
|
-
constructor(input) {
|
|
318
|
-
super(input);
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* Parse input into a program AST.
|
|
322
|
-
*/
|
|
323
|
-
parse() {
|
|
324
|
-
let node = this.startNode(NODE.PROGRAM);
|
|
325
|
-
node.body = [];
|
|
326
|
-
this.nextToken();
|
|
327
|
-
return this.parseTopLevel(node);
|
|
328
|
-
}
|
|
329
|
-
finishNode(node) {
|
|
330
|
-
node.end = this.state.start;
|
|
331
|
-
return node;
|
|
332
|
-
}
|
|
333
|
-
parseTopLevel(node) {
|
|
334
|
-
while (this.state.type !== TOKEN.EOF) {
|
|
335
|
-
let stmt = this.parseExpression();
|
|
336
|
-
node.body.push(stmt);
|
|
337
|
-
}
|
|
338
|
-
this.nextToken();
|
|
339
|
-
return this.finishNode(node);
|
|
340
|
-
}
|
|
341
|
-
parseExpression() {
|
|
342
|
-
let left;
|
|
343
|
-
if (this.state.type === TOKEN.PAREN_L) {
|
|
344
|
-
this.nextToken();
|
|
345
|
-
left = this.parseExpression();
|
|
346
|
-
if (this.state.type !== TOKEN.PAREN_R) {
|
|
347
|
-
this.raise(this.pos, "Expected closing paren.");
|
|
348
|
-
}
|
|
349
|
-
this.nextToken();
|
|
350
|
-
if (this.state.type === TOKEN.EOF) {
|
|
351
|
-
return left;
|
|
352
|
-
}
|
|
353
|
-
} else {
|
|
354
|
-
left = this.parseExprAtom();
|
|
355
|
-
}
|
|
356
|
-
let node;
|
|
357
|
-
for (; ; ) {
|
|
358
|
-
node = this.parseExprOp(left);
|
|
359
|
-
node.start = left.start;
|
|
360
|
-
left = node;
|
|
361
|
-
if (this.state.type === TOKEN.EOF || this.state.type === TOKEN.PAREN_R)
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
return node;
|
|
365
|
-
}
|
|
366
|
-
parseExprOp(left) {
|
|
367
|
-
const node = this.startNode(NODE.BINARY_EXPRESSION);
|
|
368
|
-
node.operator = this.state.value;
|
|
369
|
-
node.left = left;
|
|
370
|
-
switch (this.state.type) {
|
|
371
|
-
case TOKEN.LOGICAL_AND:
|
|
372
|
-
case TOKEN.LOGICAL_OR: {
|
|
373
|
-
if (!isNodeExpression(node.left)) {
|
|
374
|
-
this.raise(
|
|
375
|
-
node.left.start,
|
|
376
|
-
"Expected expression as left side of logical expression."
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
this.nextToken();
|
|
380
|
-
node.right = this.maybeLiteral() || this.parseExpression();
|
|
381
|
-
node.type = NODE.LOGICAL_EXPRESSION;
|
|
382
|
-
if (!isNodeExpression(node.right)) {
|
|
383
|
-
this.raise(
|
|
384
|
-
node.right.start,
|
|
385
|
-
`Unexpected ${node.right.type} as right side of logical expression.`
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
case TOKEN.EQUALITY:
|
|
391
|
-
case TOKEN.RELATIONAL: {
|
|
392
|
-
this.nextToken();
|
|
393
|
-
if (node.left.type !== NODE.IDENTIFIER) {
|
|
394
|
-
this.raise(
|
|
395
|
-
node.start,
|
|
396
|
-
`Expected identifier as left side of binary expression.`
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
node.right = this.parseExprAtom();
|
|
400
|
-
if (node.right.type !== NODE.LITERAL) {
|
|
401
|
-
this.raise(
|
|
402
|
-
node.right.start,
|
|
403
|
-
"Expected literal as right side of binary expression."
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
default: {
|
|
409
|
-
if (this.state.type === TOKEN.EOF) {
|
|
410
|
-
this.raise(
|
|
411
|
-
this.pos,
|
|
412
|
-
`Expected operator after ${this.prev.type.label}`
|
|
413
|
-
);
|
|
414
|
-
} else {
|
|
415
|
-
this.raise(this.pos, `Unexpected operator: ${this.state.type.label}`);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
return this.finishNode(node);
|
|
420
|
-
}
|
|
421
|
-
maybeLiteral() {
|
|
422
|
-
switch (this.state.type) {
|
|
423
|
-
case TOKEN.MINUS: {
|
|
424
|
-
const start = this.state.start;
|
|
425
|
-
this.nextToken();
|
|
426
|
-
if (this.state.type !== TOKEN.NUM) {
|
|
427
|
-
this.raise(this.pos, `Expected number after minus operator`);
|
|
428
|
-
}
|
|
429
|
-
const node = this.maybeLiteral();
|
|
430
|
-
node.start = start;
|
|
431
|
-
node.raw = `-${node.raw}`;
|
|
432
|
-
node.value = node.value * -1;
|
|
433
|
-
return node;
|
|
434
|
-
}
|
|
435
|
-
case TOKEN.NUM:
|
|
436
|
-
case TOKEN.STRING: {
|
|
437
|
-
let node = this.startNode(NODE.LITERAL);
|
|
438
|
-
node.raw = this.input.slice(this.state.start, this.state.end);
|
|
439
|
-
node.value = this.state.value;
|
|
440
|
-
this.nextToken();
|
|
441
|
-
return this.finishNode(node);
|
|
442
|
-
}
|
|
443
|
-
case TOKEN.NULL:
|
|
444
|
-
case TOKEN.TRUE:
|
|
445
|
-
case TOKEN.FALSE: {
|
|
446
|
-
let node = this.startNode(NODE.LITERAL);
|
|
447
|
-
node.raw = this.state.type.keyword;
|
|
448
|
-
node.value = this.state.type === TOKEN.NULL ? null : this.state.type === TOKEN.TRUE;
|
|
449
|
-
this.nextToken();
|
|
450
|
-
return this.finishNode(node);
|
|
451
|
-
}
|
|
452
|
-
default: {
|
|
453
|
-
return null;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
maybeIdentifier() {
|
|
458
|
-
switch (this.state.type) {
|
|
459
|
-
case TOKEN.NAME: {
|
|
460
|
-
let node = this.startNode(NODE.IDENTIFIER);
|
|
461
|
-
node.name = String(this.state.value);
|
|
462
|
-
this.nextToken();
|
|
463
|
-
return this.finishNode(node);
|
|
464
|
-
}
|
|
465
|
-
default:
|
|
466
|
-
return null;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
parseExprAtom() {
|
|
470
|
-
const node = this.maybeIdentifier() || this.maybeLiteral();
|
|
471
|
-
if (!node) {
|
|
472
|
-
this.raise(this.pos, `Unexpected token ${this.state.type.label}.`);
|
|
473
|
-
}
|
|
474
|
-
return node;
|
|
475
|
-
}
|
|
476
|
-
startNode(type) {
|
|
477
|
-
const node = { type, start: this.state.start, end: 0 };
|
|
478
|
-
return node;
|
|
479
|
-
}
|
|
232
|
+
if (code < 48) return code === 46;
|
|
233
|
+
if (code < 58) return true;
|
|
234
|
+
if (code < 65) return false;
|
|
235
|
+
if (code < 91) return true;
|
|
236
|
+
if (code < 97) return code === 95;
|
|
237
|
+
if (code < 123) return true;
|
|
238
|
+
return false;
|
|
480
239
|
}
|
|
240
|
+
var Expression = class extends Tokenizer {
|
|
241
|
+
constructor(input) {
|
|
242
|
+
super(input);
|
|
243
|
+
}
|
|
244
|
+
parse() {
|
|
245
|
+
let node = this.startNode(NODE.PROGRAM);
|
|
246
|
+
node.body = [];
|
|
247
|
+
this.nextToken();
|
|
248
|
+
return this.parseTopLevel(node);
|
|
249
|
+
}
|
|
250
|
+
finishNode(node) {
|
|
251
|
+
node.end = this.state.start;
|
|
252
|
+
return node;
|
|
253
|
+
}
|
|
254
|
+
parseTopLevel(node) {
|
|
255
|
+
while (this.state.type !== TOKEN.EOF) {
|
|
256
|
+
let stmt = this.parseExpression();
|
|
257
|
+
node.body.push(stmt);
|
|
258
|
+
}
|
|
259
|
+
this.nextToken();
|
|
260
|
+
return this.finishNode(node);
|
|
261
|
+
}
|
|
262
|
+
parseExpression() {
|
|
263
|
+
let left;
|
|
264
|
+
if (this.state.type === TOKEN.PAREN_L) {
|
|
265
|
+
this.nextToken();
|
|
266
|
+
left = this.parseExpression();
|
|
267
|
+
if (this.state.type !== TOKEN.PAREN_R) this.raise(this.pos, "Expected closing paren.");
|
|
268
|
+
this.nextToken();
|
|
269
|
+
if (this.state.type === TOKEN.EOF) return left;
|
|
270
|
+
} else left = this.parseExprAtom();
|
|
271
|
+
let node;
|
|
272
|
+
for (;;) {
|
|
273
|
+
node = this.parseExprOp(left);
|
|
274
|
+
node.start = left.start;
|
|
275
|
+
left = node;
|
|
276
|
+
if (this.state.type === TOKEN.EOF || this.state.type === TOKEN.PAREN_R) break;
|
|
277
|
+
}
|
|
278
|
+
return node;
|
|
279
|
+
}
|
|
280
|
+
parseExprOp(left) {
|
|
281
|
+
const node = this.startNode(NODE.BINARY_EXPRESSION);
|
|
282
|
+
node.operator = this.state.value;
|
|
283
|
+
node.left = left;
|
|
284
|
+
switch (this.state.type) {
|
|
285
|
+
case TOKEN.LOGICAL_AND:
|
|
286
|
+
case TOKEN.LOGICAL_OR:
|
|
287
|
+
if (!isNodeExpression(node.left)) this.raise(node.left.start, "Expected expression as left side of logical expression.");
|
|
288
|
+
this.nextToken();
|
|
289
|
+
node.right = this.maybeLiteral() || this.parseExpression();
|
|
290
|
+
node.type = NODE.LOGICAL_EXPRESSION;
|
|
291
|
+
if (!isNodeExpression(node.right)) this.raise(node.right.start, `Unexpected ${node.right.type} as right side of logical expression.`);
|
|
292
|
+
break;
|
|
293
|
+
case TOKEN.EQUALITY:
|
|
294
|
+
case TOKEN.RELATIONAL:
|
|
295
|
+
this.nextToken();
|
|
296
|
+
if (node.left.type !== NODE.IDENTIFIER) this.raise(node.start, `Expected identifier as left side of binary expression.`);
|
|
297
|
+
node.right = this.parseExprAtom();
|
|
298
|
+
if (node.right.type !== NODE.LITERAL) this.raise(node.right.start, "Expected literal as right side of binary expression.");
|
|
299
|
+
break;
|
|
300
|
+
default: if (this.state.type === TOKEN.EOF) this.raise(this.pos, `Expected operator after ${this.prev.type.label}`);
|
|
301
|
+
else this.raise(this.pos, `Unexpected operator: ${this.state.type.label}`);
|
|
302
|
+
}
|
|
303
|
+
return this.finishNode(node);
|
|
304
|
+
}
|
|
305
|
+
maybeLiteral() {
|
|
306
|
+
switch (this.state.type) {
|
|
307
|
+
case TOKEN.MINUS: {
|
|
308
|
+
const start = this.state.start;
|
|
309
|
+
this.nextToken();
|
|
310
|
+
if (this.state.type !== TOKEN.NUM) this.raise(this.pos, `Expected number after minus operator`);
|
|
311
|
+
const node = this.maybeLiteral();
|
|
312
|
+
node.start = start;
|
|
313
|
+
node.raw = `-${node.raw}`;
|
|
314
|
+
node.value = node.value * -1;
|
|
315
|
+
return node;
|
|
316
|
+
}
|
|
317
|
+
case TOKEN.NUM:
|
|
318
|
+
case TOKEN.STRING: {
|
|
319
|
+
let node = this.startNode(NODE.LITERAL);
|
|
320
|
+
node.raw = this.input.slice(this.state.start, this.state.end);
|
|
321
|
+
node.value = this.state.value;
|
|
322
|
+
this.nextToken();
|
|
323
|
+
return this.finishNode(node);
|
|
324
|
+
}
|
|
325
|
+
case TOKEN.NULL:
|
|
326
|
+
case TOKEN.TRUE:
|
|
327
|
+
case TOKEN.FALSE: {
|
|
328
|
+
let node = this.startNode(NODE.LITERAL);
|
|
329
|
+
node.raw = this.state.type.keyword;
|
|
330
|
+
node.value = this.state.type === TOKEN.NULL ? null : this.state.type === TOKEN.TRUE;
|
|
331
|
+
this.nextToken();
|
|
332
|
+
return this.finishNode(node);
|
|
333
|
+
}
|
|
334
|
+
default: return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
maybeIdentifier() {
|
|
338
|
+
switch (this.state.type) {
|
|
339
|
+
case TOKEN.NAME: {
|
|
340
|
+
let node = this.startNode(NODE.IDENTIFIER);
|
|
341
|
+
node.name = String(this.state.value);
|
|
342
|
+
this.nextToken();
|
|
343
|
+
return this.finishNode(node);
|
|
344
|
+
}
|
|
345
|
+
default: return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
parseExprAtom() {
|
|
349
|
+
const node = this.maybeIdentifier() || this.maybeLiteral();
|
|
350
|
+
if (!node) this.raise(this.pos, `Unexpected token ${this.state.type.label}.`);
|
|
351
|
+
return node;
|
|
352
|
+
}
|
|
353
|
+
startNode(type) {
|
|
354
|
+
return {
|
|
355
|
+
type,
|
|
356
|
+
start: this.state.start,
|
|
357
|
+
end: 0
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
};
|
|
481
361
|
function isNodeExpression(node) {
|
|
482
|
-
|
|
362
|
+
return node.type === NODE.BINARY_EXPRESSION || node.type === NODE.LOGICAL_EXPRESSION;
|
|
483
363
|
}
|
|
484
|
-
|
|
485
364
|
function parseQuery(value) {
|
|
486
|
-
|
|
365
|
+
return new Expression(value).parse();
|
|
487
366
|
}
|
|
488
|
-
|
|
489
367
|
const OPERATOR_MAP = {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
368
|
+
"=": "$eq",
|
|
369
|
+
"!=": "$ne",
|
|
370
|
+
">": "$gt",
|
|
371
|
+
">=": "$gte",
|
|
372
|
+
"<": "$lt",
|
|
373
|
+
"<=": "$lte"
|
|
496
374
|
};
|
|
497
375
|
const NOOP_TRANSFORM = [];
|
|
498
376
|
function parseToMongo(input, options = {}) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
return result;
|
|
377
|
+
const opts = mergeOptions(options);
|
|
378
|
+
if (input.trim() === "") {
|
|
379
|
+
assert.ok(opts.allowEmpty, "Search query cannot be empty.");
|
|
380
|
+
return {};
|
|
381
|
+
}
|
|
382
|
+
const result = {};
|
|
383
|
+
const ops = new OpsResource(opts.maxOps);
|
|
384
|
+
const exp = new Expression(input).parse();
|
|
385
|
+
for (const node of exp.body) Object.assign(result, reduceNode(node, opts, ops));
|
|
386
|
+
return result;
|
|
511
387
|
}
|
|
512
388
|
function reduceNode(node, options, opsResource) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
case NODE.LOGICAL_EXPRESSION: {
|
|
532
|
-
const op = node.operator === "AND" ? "$and" : "$or";
|
|
533
|
-
const right = reduceNode(node.right, options, opsResource);
|
|
534
|
-
if (node.right.type === NODE.LOGICAL_EXPRESSION && node.right.operator === node.operator) {
|
|
535
|
-
return {
|
|
536
|
-
[op]: [reduceNode(node.left, options, opsResource), ...right[op]]
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
return {
|
|
540
|
-
[op]: [reduceNode(node.left, options, opsResource), right]
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
default: {
|
|
544
|
-
assert.ok(false, `Unexpected ast node: ${node.type}`);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
389
|
+
switch (node.type) {
|
|
390
|
+
case NODE.BINARY_EXPRESSION: {
|
|
391
|
+
opsResource.assert();
|
|
392
|
+
assert.ok(!options.allowedKeys || options.allowedKeys.has(node.left.name), `The search key "${node.left.name}" is not allowed.`);
|
|
393
|
+
const transform = options.transform?.[node.left.name] || options.transform?.["*"] || NOOP_TRANSFORM;
|
|
394
|
+
return { [node.left.name]: node.operator === "=" ? callTransform(transform, node.right.value, node.left.name) : { [OPERATOR_MAP[node.operator]]: callTransform(transform, node.right.value, node.left.name) } };
|
|
395
|
+
}
|
|
396
|
+
case NODE.LOGICAL_EXPRESSION: {
|
|
397
|
+
const op = node.operator === "AND" ? "$and" : "$or";
|
|
398
|
+
const right = reduceNode(node.right, options, opsResource);
|
|
399
|
+
if (node.right.type === NODE.LOGICAL_EXPRESSION && node.right.operator === node.operator) return { [op]: [reduceNode(node.left, options, opsResource), ...right[op]] };
|
|
400
|
+
return { [op]: [reduceNode(node.left, options, opsResource), right] };
|
|
401
|
+
}
|
|
402
|
+
default: assert.ok(false, `Unexpected ast node: ${node.type}`);
|
|
403
|
+
}
|
|
547
404
|
}
|
|
548
405
|
function mergeOptions(...options) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
return result;
|
|
571
|
-
}
|
|
572
|
-
class OpsResource {
|
|
573
|
-
constructor(max) {
|
|
574
|
-
this.max = max;
|
|
575
|
-
assert.ok(
|
|
576
|
-
max === Infinity || isNumber(max) && max > 0,
|
|
577
|
-
"maxOps must be a number greater than zero."
|
|
578
|
-
);
|
|
579
|
-
this.uses = 0;
|
|
580
|
-
}
|
|
581
|
-
uses;
|
|
582
|
-
assert() {
|
|
583
|
-
assert.ok(
|
|
584
|
-
++this.uses <= this.max,
|
|
585
|
-
`Maximum search operations reached: ${this.max}`
|
|
586
|
-
);
|
|
587
|
-
}
|
|
406
|
+
const optsTransform = {};
|
|
407
|
+
const result = {
|
|
408
|
+
transform: optsTransform,
|
|
409
|
+
allowEmpty: false,
|
|
410
|
+
maxOps: Infinity
|
|
411
|
+
};
|
|
412
|
+
for (const { transform, allowedKeys, ...rest } of options) {
|
|
413
|
+
Object.assign(result, rest);
|
|
414
|
+
if (allowedKeys?.length) result.allowedKeys = new Set(allowedKeys);
|
|
415
|
+
if (Array.isArray(transform) || isFunction(transform)) {
|
|
416
|
+
optsTransform["*"] = optsTransform["*"] || [];
|
|
417
|
+
optsTransform["*"].push(...arrayable(transform));
|
|
418
|
+
} else if (transform) for (const [key, value] of Object.entries(transform)) {
|
|
419
|
+
optsTransform[key] = optsTransform[key] || [];
|
|
420
|
+
optsTransform[key].push(...arrayable(value));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return result;
|
|
588
424
|
}
|
|
425
|
+
var OpsResource = class {
|
|
426
|
+
max;
|
|
427
|
+
uses;
|
|
428
|
+
constructor(max) {
|
|
429
|
+
this.max = max;
|
|
430
|
+
assert.ok(max === Infinity || isNumber(max) && max > 0, "maxOps must be a number greater than zero.");
|
|
431
|
+
this.uses = 0;
|
|
432
|
+
}
|
|
433
|
+
assert() {
|
|
434
|
+
assert.ok(++this.uses <= this.max, `Maximum search operations reached: ${this.max}`);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
589
437
|
function callTransform(fns, value, key) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
return value;
|
|
438
|
+
for (const fn of fns) value = fn(value, key);
|
|
439
|
+
return value;
|
|
594
440
|
}
|
|
595
|
-
|
|
596
441
|
const MONGO_TRANSFORM = Object.freeze({
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
return value;
|
|
628
|
-
},
|
|
629
|
-
/**
|
|
630
|
-
* Validates that the value is a boolean.
|
|
631
|
-
* If the value is `null`, it returns `null` without throwing an error.
|
|
632
|
-
*
|
|
633
|
-
* @throws {Error} If the value is not a valid boolean.
|
|
634
|
-
*/
|
|
635
|
-
BOOLEAN: (value, key) => {
|
|
636
|
-
if (value === null) return null;
|
|
637
|
-
assert.boolean(value, `The search key "${key}" is not valid boolean.`);
|
|
638
|
-
return value;
|
|
639
|
-
},
|
|
640
|
-
/**
|
|
641
|
-
* Validates and converts the value to a MongoDB ObjectId.
|
|
642
|
-
* If the value is `null`, it returns `null` without throwing an error.
|
|
643
|
-
*
|
|
644
|
-
* @throws {Error} If the value is not a valid ObjectId.
|
|
645
|
-
*/
|
|
646
|
-
OBJECT_ID: (value, key) => {
|
|
647
|
-
if (value === null) return null;
|
|
648
|
-
assert.ok(
|
|
649
|
-
isObjectIdOrHexString,
|
|
650
|
-
`The search key "${key}" is not valid object id.`
|
|
651
|
-
);
|
|
652
|
-
return new mongoose.Types.ObjectId(value);
|
|
653
|
-
},
|
|
654
|
-
/**
|
|
655
|
-
* Validates and converts the value to a Date.
|
|
656
|
-
* Supports string and number inputs for conversion.
|
|
657
|
-
* If the value is `null`, it returns `null` without throwing an error.
|
|
658
|
-
*
|
|
659
|
-
* @throws {Error} If the value is not a valid date.
|
|
660
|
-
*/
|
|
661
|
-
DATE: (value, key) => {
|
|
662
|
-
if (value === null) return null;
|
|
663
|
-
const result = isString(value) ? new Date(value) : isNumber(value) ? new Date(value) : void 0;
|
|
664
|
-
assert.date(result, `The search key "${key}" is not valid date.`);
|
|
665
|
-
return result;
|
|
666
|
-
}
|
|
442
|
+
NOT_NULLABLE: ((value, key) => {
|
|
443
|
+
assert.ok(value !== null, `The search key "${key}" cannot be null.`);
|
|
444
|
+
return value;
|
|
445
|
+
}),
|
|
446
|
+
NUMBER: ((value, key) => {
|
|
447
|
+
if (value === null) return null;
|
|
448
|
+
assert.number(value, `The search key "${key}" is not valid number.`);
|
|
449
|
+
return value;
|
|
450
|
+
}),
|
|
451
|
+
STRING: ((value, key) => {
|
|
452
|
+
if (value === null) return null;
|
|
453
|
+
assert.string(value, `The search key "${key}" is not valid string.`);
|
|
454
|
+
return value;
|
|
455
|
+
}),
|
|
456
|
+
BOOLEAN: ((value, key) => {
|
|
457
|
+
if (value === null) return null;
|
|
458
|
+
assert.boolean(value, `The search key "${key}" is not valid boolean.`);
|
|
459
|
+
return value;
|
|
460
|
+
}),
|
|
461
|
+
OBJECT_ID: ((value, key) => {
|
|
462
|
+
if (value === null) return null;
|
|
463
|
+
assert.ok(isObjectIdOrHexString, `The search key "${key}" is not valid object id.`);
|
|
464
|
+
return new mongoose.Types.ObjectId(value);
|
|
465
|
+
}),
|
|
466
|
+
DATE: ((value, key) => {
|
|
467
|
+
if (value === null) return null;
|
|
468
|
+
const result = isString(value) ? new Date(value) : isNumber(value) ? new Date(value) : void 0;
|
|
469
|
+
assert.date(result, `The search key "${key}" is not valid date.`);
|
|
470
|
+
return result;
|
|
471
|
+
})
|
|
667
472
|
});
|
|
668
473
|
const INSTANCE_TO_TRANSFORM = {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
474
|
+
Number: [MONGO_TRANSFORM.NUMBER],
|
|
475
|
+
Decimal128: [MONGO_TRANSFORM.NUMBER],
|
|
476
|
+
String: [MONGO_TRANSFORM.STRING],
|
|
477
|
+
UUID: [MONGO_TRANSFORM.STRING],
|
|
478
|
+
ObjectId: [MONGO_TRANSFORM.OBJECT_ID],
|
|
479
|
+
Boolean: [MONGO_TRANSFORM.BOOLEAN],
|
|
480
|
+
Date: [MONGO_TRANSFORM.DATE]
|
|
676
481
|
};
|
|
677
482
|
function parseToMongoose(reference, input, options = {}) {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
const exp = new Expression(input).parse();
|
|
685
|
-
for (const node of exp.body) {
|
|
686
|
-
Object.assign(result, reduceNode(node, opts, ops));
|
|
687
|
-
}
|
|
688
|
-
return result;
|
|
483
|
+
const opts = mergeOptions(extractFromSchema("schema" in reference ? reference.schema : reference), options);
|
|
484
|
+
const result = {};
|
|
485
|
+
const ops = new OpsResource(opts.maxOps);
|
|
486
|
+
const exp = new Expression(input).parse();
|
|
487
|
+
for (const node of exp.body) Object.assign(result, reduceNode(node, opts, ops));
|
|
488
|
+
return result;
|
|
689
489
|
}
|
|
690
|
-
const extractFromSchema = withCache(
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
);
|
|
490
|
+
const extractFromSchema = withCache({ objectStrategy: "ref" }, (schema) => {
|
|
491
|
+
const allowedKeys = [];
|
|
492
|
+
const transform = {};
|
|
493
|
+
eachPath(schema, (path, type) => {
|
|
494
|
+
transform[path] = INSTANCE_TO_TRANSFORM[type.instance];
|
|
495
|
+
allowedKeys.push(path);
|
|
496
|
+
});
|
|
497
|
+
return {
|
|
498
|
+
allowedKeys,
|
|
499
|
+
transform
|
|
500
|
+
};
|
|
501
|
+
});
|
|
702
502
|
function eachPath(schema, fn, prefix = "") {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
fn(`${prefix}${path}`, type);
|
|
708
|
-
}
|
|
709
|
-
});
|
|
503
|
+
schema.eachPath((path, type) => {
|
|
504
|
+
if (type.schema) eachPath(type.schema, fn, `${path}.`);
|
|
505
|
+
else fn(`${prefix}${path}`, type);
|
|
506
|
+
});
|
|
710
507
|
}
|
|
711
|
-
|
|
712
508
|
export { Expression, KEYWORDS, MONGO_TRANSFORM, NODE, TOKEN, Tokenizer, parseQuery, parseToMongo, parseToMongoose };
|
|
713
|
-
|
|
509
|
+
|
|
510
|
+
//# sourceMappingURL=index.mjs.map
|