@andrew_l/search-query-language 0.2.13
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/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/index.cjs +711 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +473 -0
- package/dist/index.d.mts +473 -0
- package/dist/index.d.ts +473 -0
- package/dist/index.mjs +697 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +47 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { Arrayable } from '@andrew_l/toolkit';
|
|
2
|
+
import mongoose from 'mongoose';
|
|
3
|
+
|
|
4
|
+
interface TokenTypeOptions {
|
|
5
|
+
keyword?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* @group Utils
|
|
9
|
+
*/
|
|
10
|
+
declare class TokenType {
|
|
11
|
+
readonly label: string;
|
|
12
|
+
readonly keyword: string | undefined;
|
|
13
|
+
constructor(label: string, options?: TokenTypeOptions);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Node types
|
|
18
|
+
* @group Constants
|
|
19
|
+
*/
|
|
20
|
+
declare const NODE: Readonly<{
|
|
21
|
+
PROGRAM: "program";
|
|
22
|
+
IDENTIFIER: "identifier";
|
|
23
|
+
LITERAL: "literal";
|
|
24
|
+
LOGICAL_EXPRESSION: "logical-expression";
|
|
25
|
+
BINARY_EXPRESSION: "binary-expression";
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Keyword tokens.
|
|
29
|
+
* @group Constants
|
|
30
|
+
*/
|
|
31
|
+
declare const KEYWORDS: Record<string, TokenType>;
|
|
32
|
+
/**
|
|
33
|
+
* Token types
|
|
34
|
+
* @group Constants
|
|
35
|
+
*/
|
|
36
|
+
declare const TOKEN: Readonly<{
|
|
37
|
+
/**
|
|
38
|
+
* Start of File token.
|
|
39
|
+
*/
|
|
40
|
+
readonly SOF: TokenType;
|
|
41
|
+
/**
|
|
42
|
+
* End of File token.
|
|
43
|
+
*/
|
|
44
|
+
readonly EOF: TokenType;
|
|
45
|
+
/**
|
|
46
|
+
* Number token.
|
|
47
|
+
*/
|
|
48
|
+
readonly NUM: TokenType;
|
|
49
|
+
/**
|
|
50
|
+
* String token.
|
|
51
|
+
*/
|
|
52
|
+
readonly STRING: TokenType;
|
|
53
|
+
/**
|
|
54
|
+
* Identifier token.
|
|
55
|
+
*/
|
|
56
|
+
readonly NAME: TokenType;
|
|
57
|
+
/**
|
|
58
|
+
* Parenthesis token.
|
|
59
|
+
*/
|
|
60
|
+
readonly PAREN_L: TokenType;
|
|
61
|
+
/**
|
|
62
|
+
* Parenthesis token.
|
|
63
|
+
*/
|
|
64
|
+
readonly PAREN_R: TokenType;
|
|
65
|
+
/**
|
|
66
|
+
* Logical operator token.
|
|
67
|
+
*/
|
|
68
|
+
readonly LOGICAL_OR: TokenType;
|
|
69
|
+
/**
|
|
70
|
+
* Logical operator token.
|
|
71
|
+
*/
|
|
72
|
+
readonly LOGICAL_AND: TokenType;
|
|
73
|
+
/**
|
|
74
|
+
* Equality operator token.
|
|
75
|
+
*/
|
|
76
|
+
readonly EQUALITY: TokenType;
|
|
77
|
+
/**
|
|
78
|
+
* Relational operator token.
|
|
79
|
+
*/
|
|
80
|
+
readonly RELATIONAL: TokenType;
|
|
81
|
+
/**
|
|
82
|
+
* Minus operator token.
|
|
83
|
+
*/
|
|
84
|
+
readonly MINUS: TokenType;
|
|
85
|
+
/**
|
|
86
|
+
* Null literal token.
|
|
87
|
+
*/
|
|
88
|
+
readonly NULL: TokenType;
|
|
89
|
+
/**
|
|
90
|
+
* True literal tokens.
|
|
91
|
+
*/
|
|
92
|
+
readonly TRUE: TokenType;
|
|
93
|
+
/**
|
|
94
|
+
* False literal tokens.
|
|
95
|
+
*/
|
|
96
|
+
readonly FALSE: TokenType;
|
|
97
|
+
}>;
|
|
98
|
+
|
|
99
|
+
interface TokenOptions {
|
|
100
|
+
type: TokenType;
|
|
101
|
+
value: unknown;
|
|
102
|
+
start: number;
|
|
103
|
+
end: number;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* @group Utils
|
|
107
|
+
*/
|
|
108
|
+
declare class Token {
|
|
109
|
+
readonly type: TokenType;
|
|
110
|
+
readonly value: unknown;
|
|
111
|
+
readonly start: number;
|
|
112
|
+
readonly end: number;
|
|
113
|
+
constructor(p: TokenOptions);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @group Utils
|
|
118
|
+
*/
|
|
119
|
+
declare class Tokenizer implements Iterable<Token> {
|
|
120
|
+
protected input: string;
|
|
121
|
+
protected pos: number;
|
|
122
|
+
protected length: number;
|
|
123
|
+
protected state: TokenOptions;
|
|
124
|
+
protected prev: TokenOptions;
|
|
125
|
+
constructor(input: string);
|
|
126
|
+
[Symbol.iterator](): {
|
|
127
|
+
next: () => {
|
|
128
|
+
done: boolean;
|
|
129
|
+
value: Token;
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Get the next token.
|
|
134
|
+
*/
|
|
135
|
+
getToken(): Token;
|
|
136
|
+
protected nextToken(): void;
|
|
137
|
+
protected skipSpace(): void;
|
|
138
|
+
protected finishToken(type: TokenType, value?: unknown): void;
|
|
139
|
+
protected finishOp(type: TokenType, size: number): void;
|
|
140
|
+
protected readToken(code: number): void;
|
|
141
|
+
protected getTokenFromCode(code: number): void;
|
|
142
|
+
protected charCodeAtPos(): number;
|
|
143
|
+
protected readEquality(code: number): void;
|
|
144
|
+
protected readLtGt(): void;
|
|
145
|
+
protected readInt(): number | null;
|
|
146
|
+
protected raise(pos: number, message: string): never;
|
|
147
|
+
protected readString(): void;
|
|
148
|
+
protected readWord(): void;
|
|
149
|
+
protected readEscapedChar(): string;
|
|
150
|
+
protected readNumber(): void;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @group Types
|
|
155
|
+
*/
|
|
156
|
+
interface Node {
|
|
157
|
+
/**
|
|
158
|
+
* Start positions of the node in the source code.
|
|
159
|
+
*/
|
|
160
|
+
start: number;
|
|
161
|
+
/**
|
|
162
|
+
* End positions of the node in the source code.
|
|
163
|
+
*/
|
|
164
|
+
end: number;
|
|
165
|
+
/**
|
|
166
|
+
* Type of the node.
|
|
167
|
+
*/
|
|
168
|
+
type: string;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* @group Types
|
|
172
|
+
*/
|
|
173
|
+
type NodeMap = {
|
|
174
|
+
[NODE.PROGRAM]: NodeProgram;
|
|
175
|
+
[NODE.LITERAL]: NodeLiteral;
|
|
176
|
+
[NODE.BINARY_EXPRESSION]: NodeBinaryExpression;
|
|
177
|
+
[NODE.LOGICAL_EXPRESSION]: NodeLogicalExpression;
|
|
178
|
+
[NODE.IDENTIFIER]: NodeIdentifier;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* The node type.
|
|
182
|
+
* @group Types
|
|
183
|
+
*/
|
|
184
|
+
type NodeType = (typeof NODE)[keyof typeof NODE];
|
|
185
|
+
/**
|
|
186
|
+
* The node expression.
|
|
187
|
+
* @group Types
|
|
188
|
+
*/
|
|
189
|
+
type NodeExpression = NodeBinaryExpression | NodeLogicalExpression;
|
|
190
|
+
/**
|
|
191
|
+
* Root node of the AST.
|
|
192
|
+
* @group Types
|
|
193
|
+
*/
|
|
194
|
+
interface NodeProgram extends Node {
|
|
195
|
+
type: typeof NODE.PROGRAM;
|
|
196
|
+
body: NodeExpression[];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Literal node of the AST.
|
|
200
|
+
* @group Types
|
|
201
|
+
*/
|
|
202
|
+
interface NodeLiteral extends Node {
|
|
203
|
+
type: typeof NODE.LITERAL;
|
|
204
|
+
/**
|
|
205
|
+
* Value of the literal.
|
|
206
|
+
*/
|
|
207
|
+
value: string | boolean | null | number;
|
|
208
|
+
/**
|
|
209
|
+
* Raw value of the literal.
|
|
210
|
+
*/
|
|
211
|
+
raw?: string;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Identifier node of the AST.
|
|
215
|
+
* @group Types
|
|
216
|
+
*/
|
|
217
|
+
interface NodeIdentifier extends Node {
|
|
218
|
+
type: typeof NODE.IDENTIFIER;
|
|
219
|
+
/**
|
|
220
|
+
* Name of the identifier.
|
|
221
|
+
*/
|
|
222
|
+
name: string;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Binary expression node of the AST.
|
|
226
|
+
* @group Types
|
|
227
|
+
*/
|
|
228
|
+
interface NodeBinaryExpression extends Node {
|
|
229
|
+
type: typeof NODE.BINARY_EXPRESSION;
|
|
230
|
+
/**
|
|
231
|
+
* Operator of the binary expression.
|
|
232
|
+
*/
|
|
233
|
+
operator: BinaryOperator;
|
|
234
|
+
/**
|
|
235
|
+
* Left operand of the binary expression.
|
|
236
|
+
*/
|
|
237
|
+
left: NodeIdentifier;
|
|
238
|
+
/**
|
|
239
|
+
* Right operand of the binary expression.
|
|
240
|
+
*/
|
|
241
|
+
right: NodeLiteral;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Binary operator.
|
|
245
|
+
* @group Types
|
|
246
|
+
*/
|
|
247
|
+
type BinaryOperator = '=' | '!=' | '<' | '<=' | '>' | '>=';
|
|
248
|
+
/**
|
|
249
|
+
* Logical expression node of the AST.
|
|
250
|
+
* @group Types
|
|
251
|
+
*/
|
|
252
|
+
/**
|
|
253
|
+
* @group Types
|
|
254
|
+
*/
|
|
255
|
+
interface NodeLogicalExpression extends Node {
|
|
256
|
+
type: typeof NODE.LOGICAL_EXPRESSION;
|
|
257
|
+
/**
|
|
258
|
+
* Operator of the logical expression.
|
|
259
|
+
*/
|
|
260
|
+
operator: LogicalOperator;
|
|
261
|
+
/**
|
|
262
|
+
* Left operand of the logical expression.
|
|
263
|
+
*/
|
|
264
|
+
left: NodeExpression;
|
|
265
|
+
/**
|
|
266
|
+
* Right operand of the logical expression.
|
|
267
|
+
*/
|
|
268
|
+
right: NodeExpression;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Logical operator.
|
|
272
|
+
* @group Types
|
|
273
|
+
*/
|
|
274
|
+
type LogicalOperator = 'OR' | 'AND';
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Parse an expression class.
|
|
278
|
+
*
|
|
279
|
+
* @group Utils
|
|
280
|
+
*/
|
|
281
|
+
declare class Expression extends Tokenizer {
|
|
282
|
+
constructor(input: string);
|
|
283
|
+
/**
|
|
284
|
+
* Parse input into a program AST.
|
|
285
|
+
*/
|
|
286
|
+
parse(): NodeProgram;
|
|
287
|
+
protected finishNode<T extends Node>(node: T): T;
|
|
288
|
+
protected parseTopLevel(node: NodeProgram): NodeProgram;
|
|
289
|
+
protected parseExpression(): NodeExpression;
|
|
290
|
+
protected parseExprOp(left: NodeExpression | NodeIdentifier): NodeBinaryExpression | NodeLogicalExpression;
|
|
291
|
+
protected maybeLiteral(): NodeLiteral | null;
|
|
292
|
+
protected maybeIdentifier(): NodeIdentifier | null;
|
|
293
|
+
protected parseExprAtom(): NodeIdentifier | NodeLiteral;
|
|
294
|
+
protected startNode<T extends NodeType>(type: T): NodeMap[T];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Parses a query string into a NodeProgram representation.
|
|
299
|
+
*
|
|
300
|
+
* @param {string} value - The query string to be parsed.
|
|
301
|
+
* @returns {NodeProgram} - The parsed representation of the query.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* const program = parseQuery('age > 30');
|
|
305
|
+
* console.log(program);
|
|
306
|
+
* // {
|
|
307
|
+
* // type: 'program',
|
|
308
|
+
* // body: [
|
|
309
|
+
* // {
|
|
310
|
+
* // type: 'binary-expression',
|
|
311
|
+
* // operator: '>',
|
|
312
|
+
* // left: { type: 'identifier', name: 'age' },
|
|
313
|
+
* // right: { type: 'literal', value: 30 }
|
|
314
|
+
* // }
|
|
315
|
+
* // ]
|
|
316
|
+
* // }
|
|
317
|
+
*
|
|
318
|
+
* @group Main
|
|
319
|
+
*/
|
|
320
|
+
declare function parseQuery(value: string): NodeProgram;
|
|
321
|
+
|
|
322
|
+
type ParseToMongoTransformFn = (value: unknown, key: string) => unknown;
|
|
323
|
+
interface ParseToMongoOptions {
|
|
324
|
+
/**
|
|
325
|
+
* Determines whether empty search queries are allowed.
|
|
326
|
+
* If `true`, an empty query will return an unfiltered result.
|
|
327
|
+
* If `false`, an empty query will be rejected.
|
|
328
|
+
*
|
|
329
|
+
* @default false
|
|
330
|
+
*/
|
|
331
|
+
allowEmpty?: boolean;
|
|
332
|
+
/**
|
|
333
|
+
* A list of allowed keys that can be used in the search query.
|
|
334
|
+
* If provided, any query using keys outside this list will be rejected.
|
|
335
|
+
*/
|
|
336
|
+
allowedKeys?: string[];
|
|
337
|
+
/**
|
|
338
|
+
* A transformation function or a mapping of transformation functions
|
|
339
|
+
* to modify query values before they are converted into a MongoDB query.
|
|
340
|
+
*
|
|
341
|
+
* - If an array is provided, all functions in the array will be applied.
|
|
342
|
+
* - If a record object is provided, transformations will be applied
|
|
343
|
+
* based on the corresponding field key.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* {
|
|
347
|
+
'age': MONGO_TRANSFORM.NUMBER
|
|
348
|
+
'customer._id': [MONGO_TRANSFORM.OBJECT_ID, MONGO_TRANSFORM.NOT_NULLABLE]
|
|
349
|
+
}
|
|
350
|
+
*/
|
|
351
|
+
transform?: Readonly<Arrayable<ParseToMongoTransformFn>> | Readonly<Record<string, Readonly<Arrayable<ParseToMongoTransformFn>>>>;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Parses a query string and converts it into a MongoDB-compatible query object.
|
|
355
|
+
*
|
|
356
|
+
* @param {string} input - The query string to be parsed.
|
|
357
|
+
* @returns {Record<string, any>} - The MongoDB query representation.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* const query = parseToMongo('age > 30');
|
|
361
|
+
* console.log(query);
|
|
362
|
+
* // { age: { $gt: 30 } }
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* const query = parseToMongo('name = "Alice" AND age > 18');
|
|
366
|
+
* console.log(query);
|
|
367
|
+
* // { $and: [{ name: 'Alice' }, { age: { $gt: 18 } }] }
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* const query = parseToMongo('_id = "67d737b73af3ff3e00a3bbf1"', {
|
|
371
|
+
* transform: {
|
|
372
|
+
* _id: [MONGO_TRANSFORM.OBJECT_ID, MONGO_TRANSFORM.NOT_NULLABLE]
|
|
373
|
+
* }
|
|
374
|
+
* });
|
|
375
|
+
* console.log(query);
|
|
376
|
+
* // { $and: [{ name: 'Alice' }, { age: { $gt: 18 } }] }
|
|
377
|
+
*
|
|
378
|
+
* @group Main
|
|
379
|
+
*/
|
|
380
|
+
declare function parseToMongo(input: string, options?: ParseToMongoOptions): Record<string, any>;
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Utility transform functions
|
|
384
|
+
* @group Constants
|
|
385
|
+
*/
|
|
386
|
+
declare const MONGO_TRANSFORM: Readonly<{
|
|
387
|
+
/**
|
|
388
|
+
* Ensures the value is not null.
|
|
389
|
+
* Throws an error if the value is null.
|
|
390
|
+
*
|
|
391
|
+
* @throws {Error} If the value is null.
|
|
392
|
+
*/
|
|
393
|
+
readonly NOT_NULLABLE: ParseToMongoTransformFn;
|
|
394
|
+
/**
|
|
395
|
+
* Validates that the value is a number.
|
|
396
|
+
* If the value is `null`, it returns `null` without throwing an error.
|
|
397
|
+
*
|
|
398
|
+
* @throws {Error} If the value is not a valid number.
|
|
399
|
+
*/
|
|
400
|
+
readonly NUMBER: ParseToMongoTransformFn;
|
|
401
|
+
/**
|
|
402
|
+
* Validates that the value is a string.
|
|
403
|
+
* If the value is `null`, it returns `null` without throwing an error.
|
|
404
|
+
*
|
|
405
|
+
* @throws {Error} If the value is not a valid string.
|
|
406
|
+
*/
|
|
407
|
+
readonly STRING: ParseToMongoTransformFn;
|
|
408
|
+
/**
|
|
409
|
+
* Validates that the value is a boolean.
|
|
410
|
+
* If the value is `null`, it returns `null` without throwing an error.
|
|
411
|
+
*
|
|
412
|
+
* @throws {Error} If the value is not a valid boolean.
|
|
413
|
+
*/
|
|
414
|
+
readonly BOOLEAN: ParseToMongoTransformFn;
|
|
415
|
+
/**
|
|
416
|
+
* Validates and converts the value to a MongoDB ObjectId.
|
|
417
|
+
* If the value is `null`, it returns `null` without throwing an error.
|
|
418
|
+
*
|
|
419
|
+
* @throws {Error} If the value is not a valid ObjectId.
|
|
420
|
+
*/
|
|
421
|
+
readonly OBJECT_ID: ParseToMongoTransformFn;
|
|
422
|
+
/**
|
|
423
|
+
* Validates and converts the value to a Date.
|
|
424
|
+
* Supports string and number inputs for conversion.
|
|
425
|
+
* If the value is `null`, it returns `null` without throwing an error.
|
|
426
|
+
*
|
|
427
|
+
* @throws {Error} If the value is not a valid date.
|
|
428
|
+
*/
|
|
429
|
+
readonly DATE: ParseToMongoTransformFn;
|
|
430
|
+
}>;
|
|
431
|
+
type MongooseSchema = mongoose.Schema;
|
|
432
|
+
type MongooseModel = mongoose.Model<any>;
|
|
433
|
+
/**
|
|
434
|
+
* Parses a query string and converts it into a MongoDB-compatible query object,
|
|
435
|
+
* using a provided Mongoose schema or model for field validation and transformation.
|
|
436
|
+
*
|
|
437
|
+
* @param {MongooseSchema | MongooseModel} reference - The Mongoose schema or model
|
|
438
|
+
* used to infer field types and transformations.
|
|
439
|
+
* @param {string} input - The query string to be parsed.
|
|
440
|
+
* @param {ParseToMongoOptions} [options={}] - Optional configuration for parsing behavior.
|
|
441
|
+
* @returns {Record<string, any>} - The MongoDB query representation.
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* // Type transformations are automatically inferred from the schema.
|
|
445
|
+
* const schema = new mongoose.Schema({
|
|
446
|
+
* age: { type: Number },
|
|
447
|
+
* });
|
|
448
|
+
*
|
|
449
|
+
* const query = parseToMongoose(schema, '_id = "67d737b73af3ff3e00a3bbf1"');
|
|
450
|
+
* console.log(query);
|
|
451
|
+
* // Output: { _id: new ObjectId("67d737b73af3ff3e00a3bbf1") }
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* // Complex queries with logical operators
|
|
455
|
+
* const schema = new mongoose.Schema({
|
|
456
|
+
* age: { type: Number },
|
|
457
|
+
* customer: {
|
|
458
|
+
* name: { type: String },
|
|
459
|
+
* active: { type: Boolean },
|
|
460
|
+
* }
|
|
461
|
+
* });
|
|
462
|
+
*
|
|
463
|
+
* const query = parseToMongoose(schema, 'customer.active = true AND age >= 18');
|
|
464
|
+
* console.log(query);
|
|
465
|
+
* // Output: { $and: [{ 'customer.active': true }, { age: { $gte: 18 } }] }
|
|
466
|
+
*
|
|
467
|
+
* @throws {Error} If the input query contains invalid syntax or references disallowed fields.
|
|
468
|
+
*
|
|
469
|
+
* @group Main
|
|
470
|
+
*/
|
|
471
|
+
declare function parseToMongoose(reference: MongooseSchema | MongooseModel, input: string, options?: ParseToMongoOptions): Record<string, any>;
|
|
472
|
+
|
|
473
|
+
export { type BinaryOperator, Expression, KEYWORDS, type LogicalOperator, MONGO_TRANSFORM, NODE, type Node, type NodeBinaryExpression, type NodeExpression, type NodeIdentifier, type NodeLiteral, type NodeLogicalExpression, type NodeMap, type NodeProgram, type NodeType, type ParseToMongoOptions, type ParseToMongoTransformFn, TOKEN, Tokenizer, parseQuery, parseToMongo, parseToMongoose };
|