@remnic/coding-graph 9.3.759

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 (86) hide show
  1. package/README.md +130 -0
  2. package/dist/chunk-5I2DBHOQ.js +1042 -0
  3. package/dist/chunk-5I2DBHOQ.js.map +1 -0
  4. package/dist/chunk-CPYJACC5.js +1838 -0
  5. package/dist/chunk-CPYJACC5.js.map +1 -0
  6. package/dist/chunk-ZVCMIM4T.js +216 -0
  7. package/dist/chunk-ZVCMIM4T.js.map +1 -0
  8. package/dist/cypher/query-parser.d.ts +253 -0
  9. package/dist/cypher/query-parser.js +17 -0
  10. package/dist/cypher/query-parser.js.map +1 -0
  11. package/dist/graph-schema.d.ts +84 -0
  12. package/dist/graph-schema.js +17 -0
  13. package/dist/graph-schema.js.map +1 -0
  14. package/dist/graph-store.d.ts +938 -0
  15. package/dist/graph-store.js +16 -0
  16. package/dist/graph-store.js.map +1 -0
  17. package/dist/index.d.ts +1953 -0
  18. package/dist/index.js +3509 -0
  19. package/dist/index.js.map +1 -0
  20. package/grammars/tree-sitter-bash.wasm +0 -0
  21. package/grammars/tree-sitter-c.wasm +0 -0
  22. package/grammars/tree-sitter-c_sharp.wasm +0 -0
  23. package/grammars/tree-sitter-cpp.wasm +0 -0
  24. package/grammars/tree-sitter-go.wasm +0 -0
  25. package/grammars/tree-sitter-java.wasm +0 -0
  26. package/grammars/tree-sitter-javascript.wasm +0 -0
  27. package/grammars/tree-sitter-kotlin.wasm +0 -0
  28. package/grammars/tree-sitter-php.wasm +0 -0
  29. package/grammars/tree-sitter-python.wasm +0 -0
  30. package/grammars/tree-sitter-ruby.wasm +0 -0
  31. package/grammars/tree-sitter-rust.wasm +0 -0
  32. package/grammars/tree-sitter-swift.wasm +0 -0
  33. package/grammars/tree-sitter-tsx.wasm +0 -0
  34. package/grammars/tree-sitter-typescript.wasm +0 -0
  35. package/package.json +79 -0
  36. package/src/co-change.test.ts +175 -0
  37. package/src/co-change.ts +167 -0
  38. package/src/cypher/query-parser.test.ts +1107 -0
  39. package/src/cypher/query-parser.ts +1692 -0
  40. package/src/detect-changes.test.ts +533 -0
  41. package/src/detect-changes.ts +367 -0
  42. package/src/engine/emit.ts +556 -0
  43. package/src/engine/engine.test.ts +1417 -0
  44. package/src/engine/engine.ts +182 -0
  45. package/src/engine/extractors.ts +486 -0
  46. package/src/engine/fixtures.ts +364 -0
  47. package/src/engine/language-sniff.ts +56 -0
  48. package/src/engine/parser-backend.ts +206 -0
  49. package/src/engine/utf16-offsets.ts +68 -0
  50. package/src/git-invoker.test.ts +116 -0
  51. package/src/git-invoker.ts +426 -0
  52. package/src/graph-schema.test.ts +541 -0
  53. package/src/graph-schema.ts +383 -0
  54. package/src/graph-store-pr2.test.ts +1879 -0
  55. package/src/graph-store.test.ts +1420 -0
  56. package/src/graph-store.ts +3489 -0
  57. package/src/index-status.test.ts +303 -0
  58. package/src/index-status.ts +135 -0
  59. package/src/index.ts +384 -0
  60. package/src/lsp/byte-position.ts +173 -0
  61. package/src/lsp/characterization.test.ts +174 -0
  62. package/src/lsp/client.test.ts +275 -0
  63. package/src/lsp/client.ts +484 -0
  64. package/src/lsp/config.ts +219 -0
  65. package/src/lsp/degradation.ts +86 -0
  66. package/src/lsp/fixtures/fake-server.mjs +198 -0
  67. package/src/lsp/framing.test.ts +180 -0
  68. package/src/lsp/framing.ts +177 -0
  69. package/src/lsp/resolution.test.ts +497 -0
  70. package/src/lsp/resolution.ts +483 -0
  71. package/src/lsp/status.ts +140 -0
  72. package/src/lsp/types.ts +167 -0
  73. package/src/reindex.test.ts +1038 -0
  74. package/src/reindex.ts +908 -0
  75. package/src/row-types.ts +45 -0
  76. package/src/semantic/canonical-text.test.ts +150 -0
  77. package/src/semantic/canonical-text.ts +219 -0
  78. package/src/semantic/config.ts +235 -0
  79. package/src/semantic/index.ts +78 -0
  80. package/src/semantic/minhash.test.ts +197 -0
  81. package/src/semantic/minhash.ts +261 -0
  82. package/src/semantic/semantic-query.ts +173 -0
  83. package/src/semantic/semantic.test.ts +1315 -0
  84. package/src/semantic/similarity.ts +268 -0
  85. package/src/semantic/types.ts +145 -0
  86. package/src/semantic/vectors.ts +235 -0
@@ -0,0 +1,1042 @@
1
+ import {
2
+ MAX_TRAVERSE_PATHS_HOPS
3
+ } from "./chunk-CPYJACC5.js";
4
+
5
+ // src/cypher/query-parser.ts
6
+ var CYPHER_LABEL_TO_DB_LABEL = {
7
+ Project: "project",
8
+ Package: "package",
9
+ Folder: "folder",
10
+ File: "file",
11
+ Module: "module",
12
+ Class: "class",
13
+ Function: "function",
14
+ Method: "method",
15
+ Interface: "interface",
16
+ Enum: "enum",
17
+ Type: "type",
18
+ Route: "route",
19
+ Resource: "resource"
20
+ };
21
+ var VALID_CYPHER_LABELS = Object.keys(
22
+ CYPHER_LABEL_TO_DB_LABEL
23
+ ).sort();
24
+ var KEYWORDS = {
25
+ match: true,
26
+ where: true,
27
+ return: true,
28
+ limit: true,
29
+ and: true,
30
+ or: true,
31
+ true: true,
32
+ false: true,
33
+ null: true
34
+ };
35
+ var WRITE_OR_OUTSIDE_CLAUSES = {
36
+ create: "CREATE is a write clause; this Cypher layer is read-only (MATCH/WHERE/RETURN/LIMIT only).",
37
+ merge: "MERGE is a write clause; this Cypher layer is read-only.",
38
+ set: "SET is a write clause; this Cypher layer is read-only.",
39
+ delete: "DELETE is a write clause; this Cypher layer is read-only.",
40
+ detach: "DETACH DELETE is a write clause; this Cypher layer is read-only.",
41
+ remove: "REMOVE is a write clause; this Cypher layer is read-only.",
42
+ drop: "DROP is a write clause; this Cypher layer is read-only.",
43
+ call: "CALL { ... } subqueries are outside the read subset.",
44
+ yield: "YIELD is outside the read subset (only MATCH/WHERE/RETURN/LIMIT).",
45
+ union: "UNION combinator is outside the read subset.",
46
+ with: "WITH is outside the read subset (only MATCH/WHERE/RETURN/LIMIT).",
47
+ order: "ORDER BY is outside the read subset (use LIMIT, or post-sort in the caller).",
48
+ by: "ORDER BY is outside the read subset.",
49
+ skip: "SKIP is outside the read subset (use LIMIT).",
50
+ optional: "OPTIONAL MATCH is outside the read subset.",
51
+ explain: "EXPLAIN is outside the read subset.",
52
+ profile: "PROFILE is outside the read subset.",
53
+ use: "USE (graph routing) is outside the read subset.",
54
+ foreach: "FOREACH is a write clause; this Cypher layer is read-only.",
55
+ load: "LOAD CSV / LOAD FROM is outside the read subset.",
56
+ constraint: "CONSTRAINT is a DDL clause; this Cypher layer is read-only.",
57
+ index: "INDEX is a DDL clause; this Cypher layer is read-only.",
58
+ graph: "GRAPH is outside the read subset.",
59
+ unwind: "UNWIND is outside the read subset.",
60
+ distinct: "DISTINCT is outside the read subset (this layer does not dedupe).",
61
+ exists: "EXISTS is outside the read subset (use property comparisons).",
62
+ in: "IN is outside the read subset (use a property comparison).",
63
+ is_: "IS NULL / IS NOT NULL is outside the read subset.",
64
+ not: "NOT is outside the read subset (use positive comparisons or <>).",
65
+ starts: "STARTS WITH is outside the read subset.",
66
+ ends: "ENDS WITH is outside the read subset.",
67
+ contains: "CONTAINS is outside the read subset.",
68
+ regex: "=~ regex is outside the read subset.",
69
+ count: "COUNT(...) aggregation is outside the read subset.",
70
+ sum: "SUM(...) aggregation is outside the read subset.",
71
+ min: "MIN(...) aggregation is outside the read subset.",
72
+ max: "MAX(...) aggregation is outside the read subset.",
73
+ avg: "AVG(...) aggregation is outside the read subset.",
74
+ collect: "COLLECT(...) aggregation is outside the read subset.",
75
+ case: "CASE expressions are outside the read subset.",
76
+ when: "CASE expressions are outside the read subset.",
77
+ then: "CASE expressions are outside the read subset.",
78
+ else: "CASE expressions are outside the read subset.",
79
+ end: "CASE ... END is outside the read subset.",
80
+ as: "AS aliasing is outside the read subset (RETURN items project under their literal text).",
81
+ reduce: "REDUCE is outside the read subset.",
82
+ shortestpath: "shortestPath() is outside the read subset.",
83
+ all: "ALL(...) pattern predicate is outside the read subset.",
84
+ any: "ANY(...) pattern predicate is outside the read subset.",
85
+ none: "NONE(...) pattern predicate is outside the read subset.",
86
+ single: "SINGLE(...) pattern predicate is outside the read subset.",
87
+ size: "SIZE() is outside the read subset."
88
+ };
89
+ var Tokenizer = class {
90
+ src;
91
+ i = 0;
92
+ tokens = [];
93
+ constructor(src) {
94
+ this.src = src;
95
+ }
96
+ tokenize() {
97
+ while (this.i < this.src.length) {
98
+ const ch = this.src[this.i];
99
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
100
+ this.i += 1;
101
+ continue;
102
+ }
103
+ if (ch === "/" && this.src[this.i + 1] === "/") {
104
+ const nl = this.src.indexOf("\n", this.i);
105
+ this.i = nl === -1 ? this.src.length : nl + 1;
106
+ continue;
107
+ }
108
+ if (ch === "/" && this.src[this.i + 1] === "*") {
109
+ const end = this.src.indexOf("*/", this.i + 2);
110
+ if (end === -1) {
111
+ throw parseError(
112
+ this.i,
113
+ "Unterminated block comment. Supported grammar: MATCH/WHERE/RETURN/LIMIT."
114
+ );
115
+ }
116
+ this.i = end + 2;
117
+ continue;
118
+ }
119
+ const start = this.i;
120
+ if (ch === '"' || ch === "'") {
121
+ this.readString(ch, start);
122
+ continue;
123
+ }
124
+ if (isDigit(ch) || ch === "." && isDigit(this.src[this.i + 1])) {
125
+ this.readNumber(start);
126
+ continue;
127
+ }
128
+ if (isIdentStart(ch)) {
129
+ this.readWord(start);
130
+ continue;
131
+ }
132
+ const two = this.src.slice(this.i, this.i + 2);
133
+ const three = this.src.slice(this.i, this.i + 3);
134
+ if (three === "-->") {
135
+ this.push("ARROW_R", three, start);
136
+ continue;
137
+ }
138
+ if (three === "<--") {
139
+ this.push("ARROW_L", three, start);
140
+ continue;
141
+ }
142
+ if (two === "->") {
143
+ this.push("ARROW_R", two, start);
144
+ continue;
145
+ }
146
+ if (two === "<-") {
147
+ this.push("ARROW_L", two, start);
148
+ continue;
149
+ }
150
+ if (two === "--") {
151
+ this.push("DASHDASH", two, start);
152
+ continue;
153
+ }
154
+ if (two === "..") {
155
+ this.push("DOTDOT", two, start);
156
+ continue;
157
+ }
158
+ if (two === "<>" || two === "!=") {
159
+ this.push("NE", two, start);
160
+ continue;
161
+ }
162
+ if (two === ">=") {
163
+ this.push("GE", two, start);
164
+ continue;
165
+ }
166
+ if (two === "<=") {
167
+ this.push("LE", two, start);
168
+ continue;
169
+ }
170
+ switch (ch) {
171
+ case "(":
172
+ this.push("LPAREN", ch, start);
173
+ break;
174
+ case ")":
175
+ this.push("RPAREN", ch, start);
176
+ break;
177
+ case "[":
178
+ this.push("LBRACK", ch, start);
179
+ break;
180
+ case "]":
181
+ this.push("RBRACK", ch, start);
182
+ break;
183
+ case "{":
184
+ this.push("LBRACE", ch, start);
185
+ break;
186
+ case "}":
187
+ this.push("RBRACE", ch, start);
188
+ break;
189
+ case ":":
190
+ this.push("COLON", ch, start);
191
+ break;
192
+ case "|":
193
+ this.push("PIPE", ch, start);
194
+ break;
195
+ case ",":
196
+ this.push("COMMA", ch, start);
197
+ break;
198
+ case ".":
199
+ this.push("DOT", ch, start);
200
+ break;
201
+ case "*":
202
+ this.push("STAR", ch, start);
203
+ break;
204
+ case "-":
205
+ this.push("DASH", ch, start);
206
+ break;
207
+ case "=":
208
+ this.push("EQ", ch, start);
209
+ break;
210
+ case ">":
211
+ this.push("GT", ch, start);
212
+ break;
213
+ case "<":
214
+ this.push("LT", ch, start);
215
+ break;
216
+ default:
217
+ throw parseError(
218
+ start,
219
+ `Unexpected character ${JSON.stringify(ch)}. Supported grammar: MATCH (a:Label {p: "v"})-[:TYPE*1..3]->(b) WHERE ... RETURN ... LIMIT n.`
220
+ );
221
+ }
222
+ }
223
+ this.push("EOF", "", this.i);
224
+ return this.tokens;
225
+ }
226
+ push(type, text, pos) {
227
+ this.tokens.push({ type, text, pos });
228
+ this.i = pos + text.length;
229
+ }
230
+ readString(quote, start) {
231
+ let j = start + 1;
232
+ const chars = [];
233
+ while (j < this.src.length) {
234
+ const c = this.src[j];
235
+ if (c === "\\") {
236
+ chars.push(c);
237
+ chars.push(this.src[j + 1] ?? "");
238
+ j += 2;
239
+ continue;
240
+ }
241
+ if (c === quote) {
242
+ const text = chars.join("");
243
+ this.tokens.push({ type: "STRING", text, pos: start });
244
+ this.i = j + 1;
245
+ return;
246
+ }
247
+ chars.push(c);
248
+ j += 1;
249
+ }
250
+ throw parseError(start, "Unterminated string literal.");
251
+ }
252
+ readNumber(start) {
253
+ let j = start;
254
+ while (j < this.src.length && isDigit(this.src[j])) j += 1;
255
+ if (this.src[j] === "." && isDigit(this.src[j + 1])) {
256
+ j += 1;
257
+ while (j < this.src.length && isDigit(this.src[j])) j += 1;
258
+ }
259
+ if (this.src[j] === "e" || this.src[j] === "E") {
260
+ j += 1;
261
+ if (this.src[j] === "+" || this.src[j] === "-") j += 1;
262
+ while (j < this.src.length && isDigit(this.src[j])) j += 1;
263
+ }
264
+ const text = this.src.slice(start, j);
265
+ this.tokens.push({ type: "NUMBER", text, pos: start });
266
+ this.i = j;
267
+ }
268
+ readWord(start) {
269
+ let j = start;
270
+ while (j < this.src.length && isIdentPart(this.src[j])) j += 1;
271
+ const text = this.src.slice(start, j);
272
+ this.tokens.push({ type: "WORD", text, pos: start });
273
+ this.i = j;
274
+ }
275
+ };
276
+ function isDigit(c) {
277
+ return c >= "0" && c <= "9";
278
+ }
279
+ function isIdentStart(c) {
280
+ return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
281
+ }
282
+ function isIdentPart(c) {
283
+ return isIdentStart(c) || isDigit(c);
284
+ }
285
+ var Parser = class {
286
+ tokens;
287
+ i = 0;
288
+ constructor(tokens) {
289
+ this.tokens = tokens;
290
+ }
291
+ parse() {
292
+ this.expectKeyword("match");
293
+ const match = this.parseMatch();
294
+ let where;
295
+ if (this.consumeKeyword("where")) {
296
+ where = this.parseWhere();
297
+ }
298
+ this.expectKeyword("return");
299
+ const returnItems = this.parseReturn();
300
+ let limit;
301
+ if (this.consumeKeyword("limit")) {
302
+ limit = this.parseNonNegativeInt("LIMIT");
303
+ }
304
+ const tok = this.peek();
305
+ if (tok.type !== "EOF") {
306
+ throw this.unsupportedAfter(tok);
307
+ }
308
+ this.validateAst(match, where, returnItems);
309
+ return { match, where, return: returnItems, limit };
310
+ }
311
+ // ── MATCH / pattern ────────────────────────────────────────────────────
312
+ parseMatch() {
313
+ const nodes = [this.parseNode()];
314
+ const rels = [];
315
+ while (this.startsWithRelationship()) {
316
+ const rel = this.parseRelationship();
317
+ nodes.push(this.parseNode());
318
+ rels.push(rel);
319
+ }
320
+ return { nodes, rels };
321
+ }
322
+ startsWithRelationship() {
323
+ const t = this.peek();
324
+ return t.type === "ARROW_L" || t.type === "DASHDASH" || t.type === "DASH";
325
+ }
326
+ parseNode() {
327
+ this.expect("LPAREN", 'a node pattern `(var:Label {p: "v"})`');
328
+ let varName;
329
+ const first = this.peek();
330
+ if (first.type === "WORD" && !this.isKeyword(first.text)) {
331
+ varName = first.text;
332
+ this.advance();
333
+ }
334
+ let label;
335
+ if (this.peek().type === "COLON") {
336
+ this.advance();
337
+ const lt = this.expect("WORD", "a label after `:`");
338
+ label = lt.text;
339
+ }
340
+ const properties = [];
341
+ if (this.peek().type === "LBRACE") {
342
+ this.advance();
343
+ if (this.peek().type !== "RBRACE") {
344
+ properties.push(this.parseProp());
345
+ while (this.peek().type === "COMMA") {
346
+ this.advance();
347
+ properties.push(this.parseProp());
348
+ }
349
+ }
350
+ this.expect("RBRACE", "closing `}` of the property map");
351
+ }
352
+ this.expect("RPAREN", "closing `)` of the node pattern");
353
+ return { varName, label, properties };
354
+ }
355
+ parseProp() {
356
+ const keyTok = this.expect("WORD", "a property key");
357
+ this.expect("COLON", "`:` between property key and value");
358
+ const value = this.parseLiteral();
359
+ return { key: keyTok.text, value };
360
+ }
361
+ /**
362
+ * Parse `<leftDash> [bracket] <rightDash>`. Direction is resolved from
363
+ * the two dash runs: `-[...]->` outgoing, `<-[...]-` incoming,
364
+ * `-[...]-` / `--[...]--` both. The bracket is required.
365
+ */
366
+ parseRelationship() {
367
+ const leftDir = this.consumeLeftDashRun();
368
+ if (this.peek().type !== "LBRACK") {
369
+ throw parseError(
370
+ this.peek().pos,
371
+ "Relationships must use a bracketed form, e.g. `-[:CALLS]->` or `-[:CALLS*1..3]->`. Bare arrows without `[...]` are not in the subset."
372
+ );
373
+ }
374
+ this.advance();
375
+ const types = [];
376
+ if (this.peek().type === "COLON") {
377
+ this.advance();
378
+ types.push(this.expect("WORD", "an edge type after `:[`").text);
379
+ while (this.peek().type === "PIPE") {
380
+ this.advance();
381
+ if (this.peek().type === "COLON") this.advance();
382
+ types.push(this.expect("WORD", "an edge type after `|`").text);
383
+ }
384
+ }
385
+ let minHops = 1;
386
+ let maxHops = 1;
387
+ let isVarLength = false;
388
+ if (this.peek().type === "STAR") {
389
+ this.advance();
390
+ const range = this.parseRange();
391
+ minHops = range.min;
392
+ maxHops = range.max;
393
+ isVarLength = true;
394
+ }
395
+ this.expect("RBRACK", "closing `]` of the relationship bracket");
396
+ const rightDir = this.consumeRightDashRun();
397
+ if (leftDir === "none" && rightDir === "none") {
398
+ throw parseError(
399
+ this.peek().pos,
400
+ "Relationships require dashes around the bracket, e.g. `-[:CALLS]->`, `<-[:CALLS]-`, or `-[:CALLS]-`. Bare `[...]` between nodes is not valid."
401
+ );
402
+ }
403
+ const direction = resolveDirection(leftDir, rightDir);
404
+ if (maxHops < minHops) {
405
+ throw parseError(
406
+ this.peek().pos,
407
+ `Relationship range max (${maxHops}) is less than min (${minHops}).`
408
+ );
409
+ }
410
+ return { direction, types, minHops, maxHops, isVarLength };
411
+ }
412
+ parseRange() {
413
+ const tok = this.peek();
414
+ const hasLower = tok.type === "NUMBER";
415
+ let min = 1;
416
+ let max = Infinity;
417
+ if (hasLower) {
418
+ const n = this.parseNonNegativeInt("the minimum hop count");
419
+ min = n;
420
+ max = n;
421
+ }
422
+ if (this.peek().type === "DOTDOT") {
423
+ this.advance();
424
+ if (this.peek().type === "NUMBER") {
425
+ max = this.parseNonNegativeInt("the maximum hop count");
426
+ } else {
427
+ throw parseError(
428
+ this.peek().pos,
429
+ "Unbounded variable-length `*..` is not supported. Specify a maximum, e.g. `*1..3`. The read subset rejects unbounded traversal.",
430
+ "unsupported_clause"
431
+ );
432
+ }
433
+ } else if (!hasLower) {
434
+ throw parseError(
435
+ tok.pos,
436
+ "Unbounded variable-length `*` is not supported. Specify a range, e.g. `*1..3` or `*2`. The read subset rejects unbounded traversal.",
437
+ "unsupported_clause"
438
+ );
439
+ }
440
+ if (min < 0 || max < 0) {
441
+ throw parseError(tok.pos, "Hop counts must be non-negative integers.");
442
+ }
443
+ return { min, max };
444
+ }
445
+ // ── dash-run consumption ───────────────────────────────────────────────
446
+ /**
447
+ * Left-side dash-run result. `"incoming"` = `<-` arrow; `"dash"` = bare
448
+ * `-`/`--` (direction-neutral — the OTHER side's arrow wins, or undirected
449
+ * if neither side has an arrow); `"none"` = nothing present.
450
+ */
451
+ consumeLeftDashRun() {
452
+ const t = this.peek();
453
+ if (t.type === "ARROW_L") {
454
+ this.advance();
455
+ return "incoming";
456
+ }
457
+ if (t.type === "DASHDASH" || t.type === "DASH") {
458
+ this.advance();
459
+ return "dash";
460
+ }
461
+ return "none";
462
+ }
463
+ /**
464
+ * Right-side dash-run result. `"outgoing"` = `->`/`-->` arrow; `"dash"`
465
+ * = bare `-`/`--` (direction-neutral); `"none"` = nothing present.
466
+ */
467
+ consumeRightDashRun() {
468
+ const t = this.peek();
469
+ if (t.type === "ARROW_R") {
470
+ this.advance();
471
+ return "outgoing";
472
+ }
473
+ if (t.type === "DASHDASH" || t.type === "DASH") {
474
+ this.advance();
475
+ return "dash";
476
+ }
477
+ return "none";
478
+ }
479
+ // ── WHERE ──────────────────────────────────────────────────────────────
480
+ parseWhere() {
481
+ const orGroups = [];
482
+ orGroups.push(this.parseAndGroup());
483
+ while (this.consumeKeyword("or")) {
484
+ orGroups.push(this.parseAndGroup());
485
+ }
486
+ return { orGroups };
487
+ }
488
+ parseAndGroup() {
489
+ const terms = [this.parseComparison()];
490
+ while (this.consumeKeyword("and")) {
491
+ terms.push(this.parseComparison());
492
+ }
493
+ return terms;
494
+ }
495
+ parseComparison() {
496
+ const varTok = this.expect(
497
+ "WORD",
498
+ 'a variable in WHERE (e.g. `a.name = "foo"`)'
499
+ );
500
+ if (this.isKeyword(varTok.text)) {
501
+ throw parseError(
502
+ varTok.pos,
503
+ `Expected a variable name in WHERE but found keyword ${JSON.stringify(varTok.text)}.`
504
+ );
505
+ }
506
+ this.expect("DOT", "`.` between variable and property in WHERE");
507
+ const keyTok = this.expect("WORD", "a property name after `var.`");
508
+ const op = this.parseOp();
509
+ const value = this.parseLiteral();
510
+ return { varName: varTok.text, key: keyTok.text, op, value };
511
+ }
512
+ parseOp() {
513
+ const t = this.peek();
514
+ switch (t.type) {
515
+ case "EQ":
516
+ this.advance();
517
+ return "=";
518
+ case "NE":
519
+ this.advance();
520
+ return "<>";
521
+ case "GT":
522
+ this.advance();
523
+ return ">";
524
+ case "LT":
525
+ this.advance();
526
+ return "<";
527
+ case "GE":
528
+ this.advance();
529
+ return ">=";
530
+ case "LE":
531
+ this.advance();
532
+ return "<=";
533
+ default:
534
+ throw parseError(
535
+ t.pos,
536
+ "Expected a comparison operator (=, <>, !=, >, <, >=, <=) in WHERE."
537
+ );
538
+ }
539
+ }
540
+ // ── RETURN ─────────────────────────────────────────────────────────────
541
+ parseReturn() {
542
+ if (this.peek().type === "STAR") {
543
+ throw parseError(
544
+ this.peek().pos,
545
+ "`RETURN *` is outside the read subset. List the items to project explicitly, e.g. `RETURN a, b.name`.",
546
+ "unsupported_clause"
547
+ );
548
+ }
549
+ const items = [this.parseReturnItem()];
550
+ while (this.peek().type === "COMMA") {
551
+ this.advance();
552
+ items.push(this.parseReturnItem());
553
+ }
554
+ if (items.length === 0) {
555
+ throw parseError(this.peek().pos, "RETURN requires at least one item.");
556
+ }
557
+ return items;
558
+ }
559
+ parseReturnItem() {
560
+ const varTok = this.expect(
561
+ "WORD",
562
+ "a variable (or var.prop) in RETURN"
563
+ );
564
+ if (this.isKeyword(varTok.text)) {
565
+ throw parseError(
566
+ varTok.pos,
567
+ `Expected a variable name in RETURN but found keyword ${JSON.stringify(varTok.text)}.`
568
+ );
569
+ }
570
+ if (this.peek().type === "DOT") {
571
+ this.advance();
572
+ const keyTok = this.expect("WORD", "a property name after `var.`");
573
+ return { kind: "prop", varName: varTok.text, key: keyTok.text };
574
+ }
575
+ return { kind: "var", varName: varTok.text };
576
+ }
577
+ // ── numeric helpers ────────────────────────────────────────────────────
578
+ parseNonNegativeInt(label) {
579
+ const tok = this.peek();
580
+ if (tok.type === "NUMBER") {
581
+ const n = Number(tok.text);
582
+ if (!Number.isInteger(n) || n < 0) {
583
+ throw parseError(
584
+ tok.pos,
585
+ `${label} must be a non-negative integer; got ${JSON.stringify(tok.text)}.`
586
+ );
587
+ }
588
+ this.advance();
589
+ return n;
590
+ }
591
+ throw parseError(tok.pos, `Expected a non-negative integer for ${label}.`);
592
+ }
593
+ // ── literals ───────────────────────────────────────────────────────────
594
+ parseLiteral() {
595
+ const tok = this.peek();
596
+ let negate = false;
597
+ if (tok.type === "DASH") {
598
+ negate = true;
599
+ this.advance();
600
+ }
601
+ const t = negate ? this.peek() : tok;
602
+ if (t.type === "STRING") {
603
+ if (negate) {
604
+ throw parseError(t.pos, "Cannot negate a string literal.");
605
+ }
606
+ this.advance();
607
+ return t.text;
608
+ }
609
+ if (t.type === "NUMBER") {
610
+ this.advance();
611
+ const n = Number(t.text);
612
+ return negate ? -n : n;
613
+ }
614
+ if (t.type === "WORD") {
615
+ if (t.text.toLowerCase() === "true") {
616
+ if (negate) {
617
+ throw parseError(t.pos, "Cannot negate `true`.");
618
+ }
619
+ this.advance();
620
+ return true;
621
+ }
622
+ if (t.text.toLowerCase() === "false") {
623
+ if (negate) {
624
+ throw parseError(t.pos, "Cannot negate `false`.");
625
+ }
626
+ this.advance();
627
+ return false;
628
+ }
629
+ if (t.text.toLowerCase() === "null") {
630
+ if (negate) {
631
+ throw parseError(t.pos, "Cannot negate `null`.");
632
+ }
633
+ this.advance();
634
+ return null;
635
+ }
636
+ const outside = WRITE_OR_OUTSIDE_CLAUSES[t.text.toLowerCase()];
637
+ if (outside) {
638
+ throw parseError(t.pos, outside);
639
+ }
640
+ }
641
+ throw parseError(
642
+ tok.pos,
643
+ "Expected a literal value (string, number, true, false, or null)."
644
+ );
645
+ }
646
+ // ── AST validation ─────────────────────────────────────────────────────
647
+ validateAst(match, where, returnItems) {
648
+ const bound = /* @__PURE__ */ new Set();
649
+ for (const n of match.nodes) {
650
+ if (n.varName) bound.add(n.varName);
651
+ }
652
+ for (const n of match.nodes) {
653
+ if (n.label !== void 0 && !(n.label in CYPHER_LABEL_TO_DB_LABEL)) {
654
+ throw parseError(
655
+ 0,
656
+ `Unknown label ${JSON.stringify(n.label)}. Valid labels: ${VALID_CYPHER_LABELS.join(", ")}.`,
657
+ "unknown_label",
658
+ [...VALID_CYPHER_LABELS]
659
+ );
660
+ }
661
+ }
662
+ if (where) {
663
+ for (const group of where.orGroups) {
664
+ for (const term of group) {
665
+ if (!bound.has(term.varName)) {
666
+ throw parseError(
667
+ 0,
668
+ `WHERE references variable ${JSON.stringify(term.varName)} which is not bound by MATCH. Bound variables: ${[...bound].join(", ") || "(none)"}.`,
669
+ "unbound_variable"
670
+ );
671
+ }
672
+ }
673
+ }
674
+ }
675
+ for (const item of returnItems) {
676
+ if (!bound.has(item.varName)) {
677
+ throw parseError(
678
+ 0,
679
+ `RETURN references variable ${JSON.stringify(item.varName)} which is not bound by MATCH. Bound variables: ${[...bound].join(", ") || "(none)"}.`,
680
+ "unbound_variable"
681
+ );
682
+ }
683
+ }
684
+ }
685
+ // ── token helpers ──────────────────────────────────────────────────────
686
+ peek() {
687
+ return this.tokens[this.i];
688
+ }
689
+ advance() {
690
+ const t = this.tokens[this.i];
691
+ if (this.i < this.tokens.length - 1) this.i += 1;
692
+ return t;
693
+ }
694
+ expect(type, what) {
695
+ const t = this.peek();
696
+ if (t.type !== type) {
697
+ throw parseError(
698
+ t.pos,
699
+ `Expected ${what} but found ${describeToken(t)}. Supported grammar: MATCH (a:Label {p: "v"})-[:TYPE*1..3]->(b) WHERE ... RETURN ... LIMIT n.`
700
+ );
701
+ }
702
+ return this.advance();
703
+ }
704
+ expectKeyword(kw) {
705
+ const t = this.peek();
706
+ if (t.type === "WORD" && t.text.toLowerCase() === kw) {
707
+ return this.advance();
708
+ }
709
+ if (t.type === "WORD") {
710
+ const outside = WRITE_OR_OUTSIDE_CLAUSES[t.text.toLowerCase()];
711
+ if (outside) {
712
+ throw parseError(t.pos, outside, "unsupported_clause");
713
+ }
714
+ }
715
+ throw parseError(
716
+ t.pos,
717
+ `Expected keyword ${kw.toUpperCase()} but found ${describeToken(t)}. Queries must start with MATCH and use only MATCH/WHERE/RETURN/LIMIT.`,
718
+ "parse_error"
719
+ );
720
+ }
721
+ consumeKeyword(kw) {
722
+ const t = this.peek();
723
+ if (t.type === "WORD" && t.text.toLowerCase() === kw) {
724
+ this.advance();
725
+ return true;
726
+ }
727
+ return false;
728
+ }
729
+ isKeyword(text) {
730
+ return KEYWORDS[text.toLowerCase()] === true;
731
+ }
732
+ unsupportedAfter(tok) {
733
+ const outside = WRITE_OR_OUTSIDE_CLAUSES[tok.text.toLowerCase()];
734
+ if (outside) {
735
+ return parseError(tok.pos, outside, "unsupported_clause");
736
+ }
737
+ return parseError(
738
+ tok.pos,
739
+ `Unexpected token ${describeToken(tok)} after the query. Only MATCH/WHERE/RETURN/LIMIT are supported.`,
740
+ "parse_error"
741
+ );
742
+ }
743
+ };
744
+ function resolveDirection(left, right) {
745
+ const leftArrow = left === "incoming";
746
+ const rightArrow = right === "outgoing";
747
+ if (leftArrow && rightArrow) {
748
+ throw parseError(
749
+ 0,
750
+ "Conflicting relationship direction (e.g. `<-[...]->`). Use a consistent direction."
751
+ );
752
+ }
753
+ if (leftArrow) return "incoming";
754
+ if (rightArrow) return "outgoing";
755
+ return "both";
756
+ }
757
+ var CypherParseError = class extends Error {
758
+ code;
759
+ pos;
760
+ validLabels;
761
+ constructor(pos, message, code = "parse_error", validLabels) {
762
+ super(message);
763
+ this.name = "CypherParseError";
764
+ this.pos = pos;
765
+ this.code = code;
766
+ this.validLabels = validLabels;
767
+ }
768
+ };
769
+ function parseError(pos, message, code = "parse_error", validLabels) {
770
+ return new CypherParseError(pos, message, code, validLabels);
771
+ }
772
+ function describeToken(t) {
773
+ if (t.type === "EOF") return "end of input";
774
+ if (t.type === "WORD") return `identifier ${JSON.stringify(t.text)}`;
775
+ if (t.type === "STRING") return `string ${JSON.stringify(t.text)}`;
776
+ if (t.type === "NUMBER") return `number ${t.text}`;
777
+ return JSON.stringify(t.text);
778
+ }
779
+ function parseCypher(query) {
780
+ if (typeof query !== "string") {
781
+ return {
782
+ ok: false,
783
+ code: "invalid_query",
784
+ message: "Cypher query must be a string."
785
+ };
786
+ }
787
+ if (query.length === 0 || query.trim().length === 0) {
788
+ return {
789
+ ok: false,
790
+ code: "parse_error",
791
+ message: 'Empty query. Supported grammar: MATCH (a:Label {p: "v"})-[:TYPE*1..3]->(b) WHERE ... RETURN ... LIMIT n.'
792
+ };
793
+ }
794
+ try {
795
+ const tokens = new Tokenizer(query).tokenize();
796
+ const ast = new Parser(tokens).parse();
797
+ return { ok: true, ast };
798
+ } catch (e) {
799
+ if (e instanceof CypherParseError) {
800
+ return {
801
+ ok: false,
802
+ code: e.code,
803
+ message: e.message,
804
+ ...e.validLabels ? { validLabels: e.validLabels } : {}
805
+ };
806
+ }
807
+ return {
808
+ ok: false,
809
+ code: "parse_error",
810
+ message: e instanceof Error ? e.message : String(e)
811
+ };
812
+ }
813
+ }
814
+ var PROP_ALIASES = {
815
+ name: "name",
816
+ qualifiedname: "qualifiedName",
817
+ qualified_name: "qualifiedName",
818
+ label: "label",
819
+ kind: "label",
820
+ filepath: "filePath",
821
+ file_path: "filePath",
822
+ nodeid: "nodeId",
823
+ node_id: "nodeId",
824
+ id: "nodeId"
825
+ };
826
+ function nodeToValue(hit) {
827
+ return {
828
+ nodeId: hit.nodeId,
829
+ qualifiedName: hit.qualifiedName,
830
+ name: hit.name,
831
+ label: hit.label,
832
+ filePath: hit.filePath
833
+ };
834
+ }
835
+ function readProperty(node, key) {
836
+ const alias = PROP_ALIASES[key.toLowerCase()];
837
+ if (!alias) {
838
+ return null;
839
+ }
840
+ return node[alias];
841
+ }
842
+ function compareValues(a, op, b) {
843
+ if (a === null || b === null) {
844
+ if (op === "=" || op === "<>" || op === "!=") {
845
+ return false;
846
+ }
847
+ return false;
848
+ }
849
+ if (op === "=") return a === b;
850
+ if (op === "<>" || op === "!=") return a !== b;
851
+ if (typeof a !== typeof b) return false;
852
+ if (typeof a === "boolean") {
853
+ return false;
854
+ }
855
+ if (op === ">") return a > b;
856
+ if (op === "<") return a < b;
857
+ if (op === ">=") return a >= b;
858
+ if (op === "<=") return a <= b;
859
+ return false;
860
+ }
861
+ function pushFilterToSearch(query, key, value) {
862
+ if (typeof value !== "string") return;
863
+ if (value.includes("%") || value.includes("_")) return;
864
+ const k = key.toLowerCase();
865
+ if (k === "name" && query.namePattern === void 0) {
866
+ query.namePattern = value;
867
+ } else if ((k === "filepath" || k === "file_path") && query.filePattern === void 0) {
868
+ query.filePattern = value;
869
+ }
870
+ }
871
+ function executeAst(store, ast) {
872
+ const firstNode = ast.match.nodes[0];
873
+ const searchQuery = { limit: 1e3 };
874
+ if (firstNode.label !== void 0) {
875
+ searchQuery.label = CYPHER_LABEL_TO_DB_LABEL[firstNode.label];
876
+ }
877
+ for (const { key, value } of firstNode.properties) {
878
+ pushFilterToSearch(searchQuery, key, value);
879
+ }
880
+ if (ast.where && ast.where.orGroups.length === 1 && firstNode.varName) {
881
+ for (const term of ast.where.orGroups[0]) {
882
+ if (term.varName === firstNode.varName && term.op === "=") {
883
+ pushFilterToSearch(searchQuery, term.key, term.value);
884
+ }
885
+ }
886
+ }
887
+ const search = store.searchGraph(searchQuery);
888
+ if (!search.ok) {
889
+ return storeFailureToCypher(search);
890
+ }
891
+ let bindings = search.hits.map((hit) => nodeToValue(hit)).filter((node) => matchesNodePattern(node, firstNode)).map((node) => {
892
+ const varByName = /* @__PURE__ */ new Map();
893
+ if (firstNode.varName) varByName.set(firstNode.varName, node);
894
+ return { varByName, lastNode: node };
895
+ });
896
+ let truncated = false;
897
+ for (let idx = 0; idx < ast.match.rels.length; idx += 1) {
898
+ const rel = ast.match.rels[idx];
899
+ const nodePattern = ast.match.nodes[idx + 1];
900
+ const nextBindings = [];
901
+ if (rel.isVarLength && rel.maxHops > MAX_TRAVERSE_PATHS_HOPS) {
902
+ const range = rel.minHops === rel.maxHops ? `*${rel.maxHops}` : `*${rel.minHops}..${rel.maxHops}`;
903
+ return {
904
+ ok: false,
905
+ code: "invalid_query",
906
+ message: `Variable-length ${range} exceeds the maximum supported traversal depth (${MAX_TRAVERSE_PATHS_HOPS}). Narrow the range.`
907
+ };
908
+ }
909
+ for (const binding of bindings) {
910
+ const candidates = [];
911
+ if (rel.isVarLength) {
912
+ const tp = store.traversePaths({
913
+ start: binding.lastNode.nodeId,
914
+ direction: rel.direction,
915
+ ...rel.types.length > 0 ? { edgeTypes: rel.types } : {},
916
+ // Push minHops into the store so its maxPaths cap counts only
917
+ // in-range paths, not the shorter prefixes an exact *N must walk
918
+ // (cursor Bugbot: 'Path cap ignores hop minimum').
919
+ minHops: Math.max(1, rel.minHops),
920
+ maxHops: rel.maxHops
921
+ });
922
+ if (!tp.ok) {
923
+ if (tp.code === "unknown_start" || tp.code === "ambiguous_start" || tp.code === "invalid_query") {
924
+ continue;
925
+ }
926
+ return storeFailureToCypher(tp);
927
+ }
928
+ if (tp.truncated) truncated = true;
929
+ if (rel.minHops === 0) candidates.push(binding.lastNode);
930
+ for (const hit of tp.hits) {
931
+ candidates.push(nodeToValue(hit));
932
+ }
933
+ } else {
934
+ const t = store.traverse({
935
+ start: binding.lastNode.nodeId,
936
+ direction: rel.direction,
937
+ ...rel.types.length > 0 ? { edgeTypes: rel.types } : {},
938
+ maxDepth: rel.maxHops
939
+ });
940
+ if (!t.ok) {
941
+ if (t.code === "unknown_start" || t.code === "ambiguous_start" || t.code === "invalid_query") {
942
+ continue;
943
+ }
944
+ return storeFailureToCypher(t);
945
+ }
946
+ for (const hit of t.hits) {
947
+ if (hit.depth < rel.minHops || hit.depth > rel.maxHops) continue;
948
+ candidates.push(nodeToValue(hit));
949
+ }
950
+ }
951
+ const seen = /* @__PURE__ */ new Set();
952
+ for (const node of candidates) {
953
+ if (seen.has(node.nodeId)) continue;
954
+ seen.add(node.nodeId);
955
+ if (!matchesNodePattern(node, nodePattern)) continue;
956
+ const varByName = new Map(binding.varByName);
957
+ if (nodePattern.varName) {
958
+ const existing = varByName.get(nodePattern.varName);
959
+ if (existing && existing.nodeId !== node.nodeId) continue;
960
+ varByName.set(nodePattern.varName, node);
961
+ }
962
+ nextBindings.push({ varByName, lastNode: node });
963
+ }
964
+ }
965
+ bindings = nextBindings;
966
+ if (bindings.length === 0) break;
967
+ }
968
+ if (ast.where) {
969
+ bindings = bindings.filter((b) => matchesWhere(b.varByName, ast.where));
970
+ }
971
+ if (ast.limit !== void 0) {
972
+ bindings = bindings.slice(0, ast.limit);
973
+ }
974
+ const columns = ast.return.map(
975
+ (item) => item.kind === "var" ? item.varName : `${item.varName}.${item.key}`
976
+ );
977
+ const rows = bindings.map((b) => {
978
+ const row = {};
979
+ ast.return.forEach((item) => {
980
+ const col = item.kind === "var" ? item.varName : `${item.varName}.${item.key}`;
981
+ const node = b.varByName.get(item.varName);
982
+ if (!node) {
983
+ row[col] = null;
984
+ return;
985
+ }
986
+ row[col] = item.kind === "var" ? node : readProperty(node, item.key);
987
+ });
988
+ return row;
989
+ });
990
+ return truncated ? { ok: true, columns, rows, truncated: true } : { ok: true, columns, rows };
991
+ }
992
+ function matchesNodePattern(node, pattern) {
993
+ if (pattern.label !== void 0) {
994
+ const dbLabel = CYPHER_LABEL_TO_DB_LABEL[pattern.label];
995
+ if (dbLabel !== void 0 && node.label !== dbLabel) return false;
996
+ }
997
+ for (const { key, value } of pattern.properties) {
998
+ const actual = readProperty(node, key);
999
+ if (!compareValues(actual, "=", value)) return false;
1000
+ }
1001
+ return true;
1002
+ }
1003
+ function matchesWhere(varByName, where) {
1004
+ return where.orGroups.some(
1005
+ (group) => group.every((term) => {
1006
+ const node = varByName.get(term.varName);
1007
+ if (!node) return false;
1008
+ const actual = readProperty(node, term.key);
1009
+ return compareValues(actual, term.op, term.value);
1010
+ })
1011
+ );
1012
+ }
1013
+ function storeFailureToCypher(f) {
1014
+ switch (f.code) {
1015
+ case "db_locked":
1016
+ return { ok: false, code: "db_locked", message: "Database is locked." };
1017
+ case "db_corrupt":
1018
+ return { ok: false, code: "db_corrupt", message: "Database is corrupt." };
1019
+ case "store_closed":
1020
+ return { ok: false, code: "store_closed", message: "The graph store is closed." };
1021
+ default:
1022
+ return {
1023
+ ok: false,
1024
+ code: "db_error",
1025
+ message: `Database error: ${f.code}`
1026
+ };
1027
+ }
1028
+ }
1029
+ function executeCypher(store, query) {
1030
+ const parsed = parseCypher(query);
1031
+ if (!parsed.ok) return parsed;
1032
+ return executeAst(store, parsed.ast);
1033
+ }
1034
+
1035
+ export {
1036
+ CYPHER_LABEL_TO_DB_LABEL,
1037
+ VALID_CYPHER_LABELS,
1038
+ parseCypher,
1039
+ executeAst,
1040
+ executeCypher
1041
+ };
1042
+ //# sourceMappingURL=chunk-5I2DBHOQ.js.map