@lowgular/code-graph 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/bin/cli.js +2458 -5
  2. package/lib.js +2430 -0
  3. package/package.json +3 -2
  4. package/code-graph-v2/code.graph.js +0 -37
  5. package/code-graph-v2/config-from-query.js +0 -131
  6. package/code-graph-v2/extractors/extractor.js +0 -27
  7. package/code-graph-v2/extractors/index.js +0 -1
  8. package/code-graph-v2/graph-builder/code-graph.builder.js +0 -49
  9. package/code-graph-v2/graph-builder/index.js +0 -1
  10. package/code-graph-v2/graph-builder/node.processor.js +0 -22
  11. package/code-graph-v2/graph-builder/relationship.processor.js +0 -55
  12. package/code-graph-v2/graph-builder/type.processor.js +0 -21
  13. package/code-graph-v2/index.js +0 -4
  14. package/code-graph-v2/tools/build-code-graph.tool.js +0 -19
  15. package/code-graph-v2/utils.js +0 -34
  16. package/codegular/index.js +0 -5
  17. package/codegular/node.js +0 -71
  18. package/codegular/program.js +0 -100
  19. package/codegular/string.js +0 -121
  20. package/codegular/type-checker.js +0 -133
  21. package/codegular/type.js +0 -356
  22. package/codegular/utils.js +0 -335
  23. package/cypher/index.js +0 -1
  24. package/cypher/lib/executor/condition-evaluator.js +0 -135
  25. package/cypher/lib/executor/executor.js +0 -60
  26. package/cypher/lib/executor/graph.js +0 -0
  27. package/cypher/lib/executor/match-engine.js +0 -130
  28. package/cypher/lib/executor/pattern-matcher.js +0 -86
  29. package/cypher/lib/executor/relationship-navigator.js +0 -41
  30. package/cypher/lib/executor/result-formatter.js +0 -149
  31. package/cypher/lib/executor/traverse-engine.js +0 -141
  32. package/cypher/lib/executor/utils.js +0 -14
  33. package/cypher/lib/graph.stub.js +0 -38
  34. package/cypher/lib/index.js +0 -32
  35. package/cypher/lib/lexer.js +0 -376
  36. package/cypher/lib/parser.js +0 -586
  37. package/cypher/lib/validator/query-validator.js +0 -75
  38. package/cypher/lib/validator/supported-features.config.js +0 -83
  39. package/cypher/lib/validator/unsupported-features.config.js +0 -124
  40. package/cypher-cli.js +0 -41
  41. package/infra/code-graph.js +0 -147
  42. package/main.js +0 -0
  43. package/resources-cli.js +0 -75
  44. package/run-cli.js +0 -43
@@ -1,586 +0,0 @@
1
- import { TokenType } from "./lexer.js";
2
- class Parser {
3
- constructor(tokens) {
4
- this.tokens = [];
5
- this.position = 0;
6
- this.tokens = tokens;
7
- }
8
- current() {
9
- return this.tokens[this.position];
10
- }
11
- advance() {
12
- const token = this.current();
13
- if (this.position < this.tokens.length - 1) {
14
- this.position++;
15
- }
16
- return token;
17
- }
18
- peek() {
19
- return this.tokens[this.position + 1] || this.tokens[this.tokens.length - 1];
20
- }
21
- expect(type) {
22
- const token = this.current();
23
- if (token.type !== type) {
24
- throw new Error(
25
- `Expected ${type}, got ${token.type} at position ${token.position}`
26
- );
27
- }
28
- return this.advance();
29
- }
30
- isKeyword(keyword) {
31
- return this.current().type === keyword;
32
- }
33
- /**
34
- * Parse a property object: { key: value, ... }
35
- * Note: Cypher uses colon (:) for properties in node patterns, not equals (=)
36
- */
37
- parseProperties() {
38
- this.expect(TokenType.LBRACE);
39
- const properties = {};
40
- if (this.current().type === TokenType.RBRACE) {
41
- this.advance();
42
- return properties;
43
- }
44
- while (true) {
45
- const key = this.expect(TokenType.IDENTIFIER).value;
46
- this.expect(TokenType.LABEL);
47
- let value;
48
- if (this.current().type === TokenType.LBRACKET) {
49
- value = this.parseArrayLiteral();
50
- } else if (this.current().type === TokenType.STRING) {
51
- value = this.advance().value;
52
- } else if (this.current().type === TokenType.NUMBER) {
53
- value = parseFloat(this.advance().value);
54
- } else if (this.current().type === TokenType.TRUE) {
55
- value = true;
56
- this.advance();
57
- } else if (this.current().type === TokenType.FALSE) {
58
- value = false;
59
- this.advance();
60
- } else if (this.current().type === TokenType.IDENTIFIER) {
61
- value = this.advance().value;
62
- } else {
63
- throw new Error(
64
- `Unexpected token type for property value: ${this.current().type}`
65
- );
66
- }
67
- properties[key] = value;
68
- if (this.current().type === TokenType.RBRACE) {
69
- this.advance();
70
- break;
71
- }
72
- this.expect(TokenType.COMMA);
73
- }
74
- return properties;
75
- }
76
- /**
77
- * Parse a node pattern: (var:Label {prop: value}) or (:Label {prop: value})
78
- */
79
- parseNodePattern() {
80
- this.expect(TokenType.LPAREN);
81
- const pattern = {
82
- type: "NodePattern",
83
- labels: []
84
- };
85
- if (this.current().type === TokenType.IDENTIFIER) {
86
- const nextToken = this.peek();
87
- if (nextToken.type === TokenType.LABEL || nextToken.type === TokenType.LBRACE || nextToken.type === TokenType.RPAREN) {
88
- pattern.variable = this.advance().value;
89
- }
90
- }
91
- if (this.current().type === TokenType.LABEL) {
92
- this.advance();
93
- if (this.current().type !== TokenType.IDENTIFIER) {
94
- throw new Error(
95
- `Expected identifier after :, got ${this.current().type}`
96
- );
97
- }
98
- pattern.labels.push(this.advance().value);
99
- if (this.current().type === TokenType.LABEL) {
100
- pattern.labelOperator = "AND";
101
- while (this.current().type === TokenType.LABEL) {
102
- this.advance();
103
- if (this.current().type === TokenType.IDENTIFIER) {
104
- pattern.labels.push(this.advance().value);
105
- } else {
106
- throw new Error(
107
- `Expected identifier after :, got ${this.current().type}`
108
- );
109
- }
110
- }
111
- } else if (this.current().type === TokenType.PIPE) {
112
- pattern.labelOperator = "OR";
113
- while (this.current().type === TokenType.PIPE) {
114
- this.advance();
115
- if (this.current().type === TokenType.IDENTIFIER) {
116
- pattern.labels.push(this.advance().value);
117
- } else {
118
- throw new Error(
119
- `Expected identifier after |, got ${this.current().type}`
120
- );
121
- }
122
- }
123
- } else {
124
- pattern.labelOperator = "AND";
125
- }
126
- }
127
- if (this.current().type === TokenType.LBRACE) {
128
- pattern.properties = this.parseProperties();
129
- }
130
- this.expect(TokenType.RPAREN);
131
- return pattern;
132
- }
133
- /**
134
- * Parse relationship pattern: -[:RELATIONSHIP_TYPE]-> or <-[:RELATIONSHIP_TYPE]- or -[r:RELATIONSHIP_TYPE]->
135
- */
136
- parseRelationship() {
137
- const relationship = {
138
- type: "RelationshipPattern",
139
- direction: "outgoing"
140
- };
141
- if (this.current().type === TokenType.ARROW && this.current().value === "<-") {
142
- relationship.direction = "incoming";
143
- this.advance();
144
- } else {
145
- this.expect(TokenType.DASH);
146
- }
147
- if (this.current().type === TokenType.LBRACKET) {
148
- this.advance();
149
- if (this.current().type === TokenType.IDENTIFIER) {
150
- const nextToken = this.peek();
151
- if (nextToken.type === TokenType.LABEL || nextToken.type === TokenType.ASTERISK) {
152
- relationship.variable = this.advance().value;
153
- }
154
- }
155
- if (this.current().type === TokenType.ASTERISK) {
156
- this.advance();
157
- relationship.variableLength = true;
158
- relationship.minLength = 0;
159
- relationship.maxLength = void 0;
160
- } else if (this.current().type === TokenType.LABEL) {
161
- this.advance();
162
- const edgeTypes = [];
163
- edgeTypes.push(this.expect(TokenType.IDENTIFIER).value);
164
- while (this.current().type === TokenType.PIPE) {
165
- this.advance();
166
- edgeTypes.push(this.expect(TokenType.IDENTIFIER).value);
167
- }
168
- relationship.edgeType = edgeTypes.length === 1 ? edgeTypes[0] : edgeTypes;
169
- if (this.current().type === TokenType.ASTERISK) {
170
- this.advance();
171
- relationship.variableLength = true;
172
- relationship.minLength = 0;
173
- relationship.maxLength = void 0;
174
- }
175
- }
176
- this.expect(TokenType.RBRACKET);
177
- }
178
- if (relationship.direction === "incoming") {
179
- this.expect(TokenType.DASH);
180
- } else {
181
- if (this.current().type === TokenType.ARROW && this.current().value === "->") {
182
- this.advance();
183
- } else if (this.current().type === TokenType.DASH) {
184
- relationship.direction = "both";
185
- this.advance();
186
- }
187
- }
188
- return relationship;
189
- }
190
- /**
191
- * Parse MATCH clause (supports OPTIONAL MATCH)
192
- */
193
- parseMatch() {
194
- const isOptional = this.isKeyword(TokenType.OPTIONAL);
195
- if (isOptional) {
196
- this.advance();
197
- }
198
- this.expect(TokenType.MATCH);
199
- const firstPattern = this.parsePatternChain();
200
- const matchClause = {
201
- type: "MatchClause",
202
- patterns: firstPattern.patterns,
203
- relationships: firstPattern.relationships,
204
- additionalMatches: []
205
- };
206
- while (this.current().type === TokenType.COMMA) {
207
- this.advance();
208
- const additionalPattern = this.parsePatternChain();
209
- matchClause.additionalMatches.push(additionalPattern);
210
- }
211
- if (isOptional) {
212
- matchClause.optionalMatches = [
213
- {
214
- type: "MatchStatement",
215
- patterns: firstPattern.patterns,
216
- relationships: firstPattern.relationships
217
- },
218
- ...matchClause.additionalMatches || []
219
- ];
220
- matchClause.patterns = [];
221
- matchClause.relationships = void 0;
222
- matchClause.additionalMatches = void 0;
223
- }
224
- return matchClause;
225
- }
226
- /**
227
- * Parse a pattern chain: (node)-[:rel]->(node)-[:rel]->(node)...
228
- * Returns patterns and relationships for a single connected pattern
229
- */
230
- parsePatternChain() {
231
- const patterns = [];
232
- const relationships = [];
233
- patterns.push(this.parseNodePattern());
234
- while (this.current().type === TokenType.DASH || this.current().type === TokenType.ARROW) {
235
- relationships.push(this.parseRelationship());
236
- patterns.push(this.parseNodePattern());
237
- }
238
- return {
239
- type: "MatchStatement",
240
- patterns,
241
- relationships: relationships.length > 0 ? relationships : void 0
242
- };
243
- }
244
- /**
245
- * Parse WHERE clause
246
- * Example: WHERE m.type = "Command" AND "param" IN m.parameters
247
- * Note: Flat properties only (no nesting, like Neo4j Cypher)
248
- */
249
- parseWhere() {
250
- this.expect(TokenType.WHERE);
251
- const conditions = [];
252
- let logic;
253
- while (true) {
254
- const variable = this.expect(TokenType.IDENTIFIER).value;
255
- let property;
256
- if (this.current().type === TokenType.DOT) {
257
- this.advance();
258
- property = this.expect(TokenType.IDENTIFIER).value;
259
- }
260
- let operator = "=";
261
- if (this.current().type === TokenType.EQUALS) {
262
- operator = "=";
263
- this.advance();
264
- } else if (this.current().type === TokenType.NOT_EQUALS) {
265
- operator = "!=";
266
- this.advance();
267
- } else if (this.current().type === TokenType.GT) {
268
- operator = ">";
269
- this.advance();
270
- } else if (this.current().type === TokenType.LT) {
271
- operator = "<";
272
- this.advance();
273
- } else if (this.current().type === TokenType.GTE) {
274
- operator = ">=";
275
- this.advance();
276
- } else if (this.current().type === TokenType.LTE) {
277
- operator = "<=";
278
- this.advance();
279
- } else if (this.current().type === TokenType.NOT) {
280
- this.advance();
281
- this.expect(TokenType.IN);
282
- operator = "NOT IN";
283
- } else if (this.current().type === TokenType.IN) {
284
- this.advance();
285
- operator = "IN";
286
- } else if (this.current().type === TokenType.IS) {
287
- this.advance();
288
- if (this.isKeyword(TokenType.NULL)) {
289
- this.advance();
290
- operator = "IS NULL";
291
- } else if (this.isKeyword(TokenType.NOT)) {
292
- this.advance();
293
- this.expect(TokenType.NULL);
294
- operator = "IS NOT NULL";
295
- } else {
296
- throw new Error(
297
- `Expected NULL or NOT NULL after IS at position ${this.current().position}`
298
- );
299
- }
300
- } else if (this.current().type === TokenType.STARTS) {
301
- this.advance();
302
- this.expect(TokenType.WITH);
303
- operator = "STARTS WITH";
304
- } else if (this.current().type === TokenType.ENDS) {
305
- this.advance();
306
- this.expect(TokenType.WITH);
307
- operator = "ENDS WITH";
308
- } else if (this.current().type === TokenType.CONTAINS) {
309
- operator = "CONTAINS";
310
- this.advance();
311
- } else if (this.current().type === TokenType.REGEX_MATCH) {
312
- operator = "=~";
313
- this.advance();
314
- } else {
315
- throw new Error(
316
- `Unsupported operator at position ${this.current().position}.`
317
- );
318
- }
319
- let value = null;
320
- if (operator === "IS NULL" || operator === "IS NOT NULL") {
321
- } else if (operator === "STARTS WITH" || operator === "ENDS WITH" || operator === "CONTAINS" || operator === "=~") {
322
- if (this.current().type === TokenType.STRING) {
323
- value = this.advance().value;
324
- } else {
325
- throw new Error(
326
- `String matching operators (${operator}) require a string value at position ${this.current().position}`
327
- );
328
- }
329
- } else if (this.current().type === TokenType.LBRACKET) {
330
- value = this.parseArrayLiteral();
331
- } else if (this.current().type === TokenType.STRING) {
332
- value = this.advance().value;
333
- } else if (this.current().type === TokenType.NUMBER) {
334
- value = parseFloat(this.advance().value);
335
- } else if (this.current().type === TokenType.TRUE) {
336
- value = true;
337
- this.advance();
338
- } else if (this.current().type === TokenType.FALSE) {
339
- value = false;
340
- this.advance();
341
- } else if (this.current().type === TokenType.IDENTIFIER) {
342
- value = this.advance().value;
343
- } else {
344
- throw new Error(
345
- `Unexpected token type for WHERE value: ${this.current().type}`
346
- );
347
- }
348
- conditions.push({ variable, property, operator, value });
349
- if (this.isKeyword(TokenType.AND)) {
350
- if (logic === void 0) {
351
- logic = "AND";
352
- } else if (logic !== "AND") {
353
- throw new Error("Cannot mix AND and OR in WHERE clause");
354
- }
355
- this.advance();
356
- continue;
357
- } else if (this.isKeyword(TokenType.OR)) {
358
- if (logic === void 0) {
359
- logic = "OR";
360
- } else if (logic !== "OR") {
361
- throw new Error("Cannot mix AND and OR in WHERE clause");
362
- }
363
- this.advance();
364
- continue;
365
- }
366
- break;
367
- }
368
- return {
369
- type: "WhereClause",
370
- conditions,
371
- logic
372
- };
373
- }
374
- /**
375
- * Parse array literal: [value1, value2, ...]
376
- */
377
- parseArrayLiteral() {
378
- this.expect(TokenType.LBRACKET);
379
- const array = [];
380
- if (this.current().type === TokenType.RBRACKET) {
381
- this.advance();
382
- return array;
383
- }
384
- while (true) {
385
- if (this.current().type === TokenType.STRING) {
386
- array.push(this.advance().value);
387
- } else if (this.current().type === TokenType.NUMBER) {
388
- array.push(parseFloat(this.advance().value));
389
- } else if (this.current().type === TokenType.TRUE) {
390
- array.push(true);
391
- this.advance();
392
- } else if (this.current().type === TokenType.FALSE) {
393
- array.push(false);
394
- this.advance();
395
- } else if (this.current().type === TokenType.IDENTIFIER) {
396
- array.push(this.advance().value);
397
- } else {
398
- throw new Error(
399
- `Unexpected token type in array literal: ${this.current().type}`
400
- );
401
- }
402
- if (this.current().type === TokenType.COMMA) {
403
- this.advance();
404
- } else if (this.current().type === TokenType.RBRACKET) {
405
- this.advance();
406
- break;
407
- } else {
408
- throw new Error(
409
- `Expected comma or closing bracket in array literal at position ${this.current().position}`
410
- );
411
- }
412
- }
413
- return array;
414
- }
415
- /**
416
- * Parse RETURN clause
417
- * Supports: RETURN *, RETURN DISTINCT, RETURN var1, var2, RETURN var AS alias, LIMIT n
418
- */
419
- parseReturn() {
420
- this.expect(TokenType.RETURN);
421
- const distinct = this.isKeyword(TokenType.DISTINCT);
422
- if (distinct) {
423
- this.advance();
424
- }
425
- if (this.current().type === TokenType.ASTERISK) {
426
- this.advance();
427
- const limit2 = this.parseOptionalLimit();
428
- return {
429
- type: "ReturnClause",
430
- distinct,
431
- returnAll: true,
432
- items: [],
433
- limit: limit2
434
- };
435
- }
436
- const items = [];
437
- while (true) {
438
- const expression = this.expect(TokenType.IDENTIFIER).value;
439
- let alias;
440
- if (this.isKeyword(TokenType.AS)) {
441
- this.advance();
442
- alias = this.expect(TokenType.IDENTIFIER).value;
443
- }
444
- items.push({ expression, alias });
445
- if (this.current().type === TokenType.COMMA) {
446
- this.advance();
447
- continue;
448
- }
449
- break;
450
- }
451
- const limit = this.parseOptionalLimit();
452
- return {
453
- type: "ReturnClause",
454
- distinct,
455
- returnAll: false,
456
- items,
457
- limit
458
- };
459
- }
460
- /**
461
- * Parse optional LIMIT clause
462
- * Returns the limit number or undefined if not present
463
- */
464
- parseOptionalLimit() {
465
- if (this.isKeyword(TokenType.LIMIT)) {
466
- this.advance();
467
- const limitToken = this.expect(TokenType.NUMBER);
468
- return parseInt(limitToken.value, 10);
469
- }
470
- return void 0;
471
- }
472
- /**
473
- * Parse ORDER BY clause
474
- * Supports: ORDER BY n.name, ORDER BY n.name ASC, ORDER BY n.name DESC
475
- * Also supports multiple fields: ORDER BY n.type DESC, n.name ASC
476
- */
477
- parseOrderBy() {
478
- this.expect(TokenType.ORDER);
479
- this.expect(TokenType.BY);
480
- const items = [];
481
- while (true) {
482
- const variable = this.expect(TokenType.IDENTIFIER).value;
483
- let property;
484
- if (this.current().type === TokenType.DOT) {
485
- this.advance();
486
- property = this.expect(TokenType.IDENTIFIER).value;
487
- }
488
- let direction = "ASC";
489
- if (this.isKeyword(TokenType.ASC)) {
490
- this.advance();
491
- direction = "ASC";
492
- } else if (this.isKeyword(TokenType.DESC)) {
493
- this.advance();
494
- direction = "DESC";
495
- }
496
- items.push({ variable, property, direction });
497
- if (this.current().type === TokenType.COMMA) {
498
- this.advance();
499
- continue;
500
- }
501
- break;
502
- }
503
- return { type: "OrderByStatement", items };
504
- }
505
- /**
506
- * Parse entire Cypher query
507
- * Returns statements in execution order
508
- */
509
- parse() {
510
- const statements = [];
511
- while (this.current().type !== TokenType.EOF) {
512
- if (this.isKeyword(TokenType.MATCH) || this.isKeyword(TokenType.OPTIONAL)) {
513
- const isOptional = this.isKeyword(TokenType.OPTIONAL);
514
- const matchClause = this.parseMatch();
515
- if (matchClause.optionalMatches) {
516
- for (const optionalMatch of matchClause.optionalMatches) {
517
- statements.push({
518
- type: "MatchStatement",
519
- patterns: optionalMatch.patterns,
520
- relationships: optionalMatch.relationships,
521
- optional: true
522
- });
523
- }
524
- } else {
525
- if (matchClause.patterns.length > 0) {
526
- statements.push({
527
- type: "MatchStatement",
528
- patterns: matchClause.patterns,
529
- relationships: matchClause.relationships,
530
- optional: false
531
- });
532
- }
533
- if (matchClause.additionalMatches) {
534
- for (const additionalMatch of matchClause.additionalMatches) {
535
- statements.push({
536
- type: "MatchStatement",
537
- patterns: additionalMatch.patterns,
538
- relationships: additionalMatch.relationships,
539
- optional: false
540
- });
541
- }
542
- }
543
- }
544
- continue;
545
- }
546
- if (this.isKeyword(TokenType.WHERE)) {
547
- const whereClause = this.parseWhere();
548
- statements.push({
549
- type: "WhereStatement",
550
- conditions: whereClause.conditions,
551
- logic: whereClause.logic
552
- });
553
- continue;
554
- }
555
- if (this.isKeyword(TokenType.ORDER)) {
556
- const orderByStatement = this.parseOrderBy();
557
- statements.push(orderByStatement);
558
- continue;
559
- }
560
- if (this.isKeyword(TokenType.RETURN)) {
561
- const returnClause = this.parseReturn();
562
- statements.push({
563
- type: "ReturnStatement",
564
- distinct: returnClause.distinct,
565
- returnAll: returnClause.returnAll,
566
- items: returnClause.items,
567
- limit: returnClause.limit
568
- });
569
- continue;
570
- }
571
- break;
572
- }
573
- if (this.current().type !== TokenType.EOF) {
574
- throw new Error(
575
- `Unexpected token: ${this.current().type} at position ${this.current().position}`
576
- );
577
- }
578
- return {
579
- type: "CypherQuery",
580
- statements
581
- };
582
- }
583
- }
584
- export {
585
- Parser
586
- };
@@ -1,75 +0,0 @@
1
- import {
2
- FORBIDDEN_AGGREGATIONS,
3
- FORBIDDEN_KEYWORDS,
4
- FORBIDDEN_MODIFIERS,
5
- FORBIDDEN_PATTERNS
6
- } from "./unsupported-features.config.js";
7
- const UNSUPPORTED_FEATURES = [
8
- ...FORBIDDEN_KEYWORDS,
9
- ...FORBIDDEN_AGGREGATIONS,
10
- ...FORBIDDEN_MODIFIERS,
11
- ...FORBIDDEN_PATTERNS
12
- ];
13
- class UnsupportedFeatureError extends Error {
14
- constructor(feature, query) {
15
- const message = `Unsupported Cypher feature detected: ${feature.name}
16
-
17
- ${feature.errorMessage}${feature.alternative ? `
18
-
19
- Alternative: ${feature.alternative}` : ""}
20
-
21
- Query: ${query}`;
22
- super(message);
23
- this.feature = feature;
24
- this.query = query;
25
- this.name = "UnsupportedFeatureError";
26
- }
27
- }
28
- function validateQuery(query) {
29
- if (!query || typeof query !== "string") {
30
- throw new Error("Query must be a non-empty string");
31
- }
32
- const normalizedQuery = query.trim();
33
- if (normalizedQuery.length === 0) {
34
- throw new Error("Query cannot be empty");
35
- }
36
- for (const feature of UNSUPPORTED_FEATURES) {
37
- const pattern = typeof feature.pattern === "string" ? new RegExp(feature.pattern, "i") : feature.pattern;
38
- if (pattern.test(normalizedQuery)) {
39
- if (feature.name === "WITH clause") {
40
- const withMatches = normalizedQuery.matchAll(/\bWITH\b/gi);
41
- let isAllowed = false;
42
- for (const match of withMatches) {
43
- const beforeMatch = normalizedQuery.substring(
44
- Math.max(0, match.index - 10),
45
- match.index
46
- );
47
- if (/\bSTARTS\s+$/i.test(beforeMatch) || /\bENDS\s+$/i.test(beforeMatch)) {
48
- isAllowed = true;
49
- break;
50
- }
51
- }
52
- if (isAllowed) {
53
- continue;
54
- }
55
- }
56
- throw new UnsupportedFeatureError(feature, normalizedQuery);
57
- }
58
- }
59
- }
60
- function validateQuerySafe(query) {
61
- try {
62
- validateQuery(query);
63
- return { valid: true };
64
- } catch (error) {
65
- if (error instanceof UnsupportedFeatureError) {
66
- return { valid: false, error };
67
- }
68
- throw error;
69
- }
70
- }
71
- export {
72
- UnsupportedFeatureError,
73
- validateQuery,
74
- validateQuerySafe
75
- };