@andrew_l/search-query-language 0.3.22 → 0.4.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/dist/index.mjs CHANGED
@@ -1,713 +1,510 @@
1
- import { assert, isFunction, arrayable, isNumber, withCache, isString } from '@andrew_l/toolkit';
2
- import mongoose, { isObjectIdOrHexString } from 'mongoose';
3
-
4
- class TokenType {
5
- label;
6
- keyword;
7
- constructor(label, options) {
8
- this.label = label;
9
- this.keyword = options?.keyword;
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
- PROGRAM: "program",
15
- IDENTIFIER: "identifier",
16
- LITERAL: "literal",
17
- LOGICAL_EXPRESSION: "logical-expression",
18
- BINARY_EXPRESSION: "binary-expression"
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
- * Start of File token.
24
- */
25
- SOF: new TokenType("sof"),
26
- /**
27
- * End of File token.
28
- */
29
- EOF: new TokenType("eof"),
30
- /**
31
- * Number token.
32
- */
33
- NUM: new TokenType("num"),
34
- /**
35
- * String token.
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
- options.keyword = name;
85
- return KEYWORDS[name] = new TokenType(name, options);
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
- if (code < 65) return false;
301
- if (code < 91) return true;
302
- if (code < 97) return code === 95;
303
- if (code < 123) return true;
304
- return false;
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
- if (code < 48) return code === 46;
308
- if (code < 58) return true;
309
- if (code < 65) return false;
310
- if (code < 91) return true;
311
- if (code < 97) return code === 95;
312
- if (code < 123) return true;
313
- return false;
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
- return node.type === NODE.BINARY_EXPRESSION || node.type === NODE.LOGICAL_EXPRESSION;
362
+ return node.type === NODE.BINARY_EXPRESSION || node.type === NODE.LOGICAL_EXPRESSION;
483
363
  }
484
-
485
364
  function parseQuery(value) {
486
- return new Expression(value).parse();
365
+ return new Expression(value).parse();
487
366
  }
488
-
489
367
  const OPERATOR_MAP = {
490
- "=": "$eq",
491
- "!=": "$ne",
492
- ">": "$gt",
493
- ">=": "$gte",
494
- "<": "$lt",
495
- "<=": "$lte"
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
- const opts = mergeOptions(options);
500
- if (input.trim() === "") {
501
- assert.ok(opts.allowEmpty, "Search query cannot be empty.");
502
- return {};
503
- }
504
- const result = {};
505
- const ops = new OpsResource(opts.maxOps);
506
- const exp = new Expression(input).parse();
507
- for (const node of exp.body) {
508
- Object.assign(result, reduceNode(node, opts, ops));
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
- switch (node.type) {
514
- case NODE.BINARY_EXPRESSION: {
515
- opsResource.assert();
516
- assert.ok(
517
- !options.allowedKeys || options.allowedKeys.has(node.left.name),
518
- `The search key "${node.left.name}" is not allowed.`
519
- );
520
- const transform = options.transform?.[node.left.name] || options.transform?.["*"] || NOOP_TRANSFORM;
521
- return {
522
- [node.left.name]: node.operator === "=" ? callTransform(transform, node.right.value, node.left.name) : {
523
- [OPERATOR_MAP[node.operator]]: callTransform(
524
- transform,
525
- node.right.value,
526
- node.left.name
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
- const optsTransform = {};
550
- const result = {
551
- transform: optsTransform,
552
- allowEmpty: false,
553
- maxOps: Infinity
554
- };
555
- for (const { transform, allowedKeys, ...rest } of options) {
556
- Object.assign(result, rest);
557
- if (allowedKeys?.length) {
558
- result.allowedKeys = new Set(allowedKeys);
559
- }
560
- if (Array.isArray(transform) || isFunction(transform)) {
561
- optsTransform["*"] = optsTransform["*"] || [];
562
- optsTransform["*"].push(...arrayable(transform));
563
- } else if (transform) {
564
- for (const [key, value] of Object.entries(transform)) {
565
- optsTransform[key] = optsTransform[key] || [];
566
- optsTransform[key].push(...arrayable(value));
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
- for (const fn of fns) {
591
- value = fn(value, key);
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
- * Ensures the value is not null.
599
- * Throws an error if the value is null.
600
- *
601
- * @throws {Error} If the value is null.
602
- */
603
- NOT_NULLABLE: (value, key) => {
604
- assert.ok(value !== null, `The search key "${key}" cannot be null.`);
605
- return value;
606
- },
607
- /**
608
- * Validates that the value is a number.
609
- * If the value is `null`, it returns `null` without throwing an error.
610
- *
611
- * @throws {Error} If the value is not a valid number.
612
- */
613
- NUMBER: (value, key) => {
614
- if (value === null) return null;
615
- assert.number(value, `The search key "${key}" is not valid number.`);
616
- return value;
617
- },
618
- /**
619
- * Validates that the value is a string.
620
- * If the value is `null`, it returns `null` without throwing an error.
621
- *
622
- * @throws {Error} If the value is not a valid string.
623
- */
624
- STRING: (value, key) => {
625
- if (value === null) return null;
626
- assert.string(value, `The search key "${key}" is not valid string.`);
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
- Number: [MONGO_TRANSFORM.NUMBER],
670
- Decimal128: [MONGO_TRANSFORM.NUMBER],
671
- String: [MONGO_TRANSFORM.STRING],
672
- UUID: [MONGO_TRANSFORM.STRING],
673
- ObjectId: [MONGO_TRANSFORM.OBJECT_ID],
674
- Boolean: [MONGO_TRANSFORM.BOOLEAN],
675
- Date: [MONGO_TRANSFORM.DATE]
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
- const opts = mergeOptions(
679
- extractFromSchema("schema" in reference ? reference.schema : reference),
680
- options
681
- );
682
- const result = {};
683
- const ops = new OpsResource(opts.maxOps);
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
- { objectStrategy: "ref" },
692
- (schema) => {
693
- const allowedKeys = [];
694
- const transform = {};
695
- eachPath(schema, (path, type) => {
696
- transform[path] = INSTANCE_TO_TRANSFORM[type.instance];
697
- allowedKeys.push(path);
698
- });
699
- return { allowedKeys, transform };
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
- schema.eachPath((path, type) => {
704
- if (type.schema) {
705
- eachPath(type.schema, fn, `${path}.`);
706
- } else {
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
- //# sourceMappingURL=index.mjs.map
509
+
510
+ //# sourceMappingURL=index.mjs.map