@zipbul/gildash 0.0.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.
package/dist/index.js CHANGED
@@ -1,2736 +1,26 @@
1
1
  // @bun
2
- var __defProp = Object.defineProperty;
3
- var __export = (target, all) => {
4
- for (var name in all)
5
- __defProp(target, name, {
6
- get: all[name],
7
- enumerable: true,
8
- configurable: true,
9
- set: (newValue) => all[name] = () => newValue
10
- });
11
- };
12
-
13
- // src/gildash.ts
14
- import path5 from "path";
15
- import { existsSync as existsSync2 } from "fs";
16
-
17
- // src/parser/parse-source.ts
18
- import { parseSync as defaultParseSync } from "oxc-parser";
19
-
20
- // src/errors.ts
21
- class GildashError extends Error {
22
- constructor(message, options) {
23
- super(message, options);
24
- this.name = "GildashError";
25
- }
26
- }
27
-
28
- class WatcherError extends GildashError {
29
- constructor(message, options) {
30
- super(message, options);
31
- this.name = "WatcherError";
32
- }
33
- }
34
-
35
- class ParseError extends GildashError {
36
- constructor(message, options) {
37
- super(message, options);
38
- this.name = "ParseError";
39
- }
40
- }
41
-
42
- class ExtractError extends GildashError {
43
- constructor(message, options) {
44
- super(message, options);
45
- this.name = "ExtractError";
46
- }
47
- }
48
-
49
- class IndexError extends GildashError {
50
- constructor(message, options) {
51
- super(message, options);
52
- this.name = "IndexError";
53
- }
54
- }
55
-
56
- class StoreError extends GildashError {
57
- constructor(message, options) {
58
- super(message, options);
59
- this.name = "StoreError";
60
- }
61
- }
62
-
63
- class SearchError extends GildashError {
64
- constructor(message, options) {
65
- super(message, options);
66
- this.name = "SearchError";
67
- }
68
- }
69
-
70
- // src/parser/parse-source.ts
71
- function parseSource(filePath, sourceText, parseSyncFn = defaultParseSync) {
72
- try {
73
- const { program, errors, comments } = parseSyncFn(filePath, sourceText);
74
- return { filePath, program, errors, comments, sourceText };
75
- } catch (err) {
76
- throw new ParseError(`Failed to parse file: ${filePath}`, { cause: err });
77
- }
78
- }
79
-
80
- // src/common/lru-cache.ts
81
- class LruCache {
82
- #capacity;
83
- #map = new Map;
84
- constructor(capacity) {
85
- this.#capacity = Math.max(1, capacity);
86
- }
87
- get size() {
88
- return this.#map.size;
89
- }
90
- has(key) {
91
- return this.#map.has(key);
92
- }
93
- get(key) {
94
- if (!this.#map.has(key)) {
95
- return;
96
- }
97
- const value = this.#map.get(key);
98
- this.#map.delete(key);
99
- this.#map.set(key, value);
100
- return value;
101
- }
102
- set(key, value) {
103
- if (this.#map.has(key)) {
104
- this.#map.delete(key);
105
- }
106
- this.#map.set(key, value);
107
- if (this.#map.size > this.#capacity) {
108
- const oldestKey = this.#map.keys().next().value;
109
- if (oldestKey !== undefined) {
110
- this.#map.delete(oldestKey);
111
- }
112
- }
113
- }
114
- delete(key) {
115
- return this.#map.delete(key);
116
- }
117
- clear() {
118
- this.#map.clear();
119
- }
120
- }
121
-
122
- // src/parser/parse-cache.ts
123
- class ParseCache {
124
- lru;
125
- constructor(capacity = 500) {
126
- this.lru = new LruCache(capacity);
127
- }
128
- get(filePath) {
129
- return this.lru.get(filePath);
130
- }
131
- set(filePath, parsed) {
132
- this.lru.set(filePath, parsed);
133
- }
134
- invalidate(filePath) {
135
- this.lru.delete(filePath);
136
- }
137
- invalidateAll() {
138
- this.lru.clear();
139
- }
140
- size() {
141
- return this.lru.size;
142
- }
143
- }
144
-
145
- // src/parser/source-position.ts
146
- function buildLineOffsets(sourceText) {
147
- const offsets = [0];
148
- for (let i = 0;i < sourceText.length; i++) {
149
- if (sourceText[i] === `
150
- `) {
151
- offsets.push(i + 1);
152
- }
153
- }
154
- return offsets;
155
- }
156
- function getLineColumn(offsets, offset) {
157
- let lo = 0;
158
- let hi = offsets.length - 1;
159
- while (lo < hi) {
160
- const mid = lo + hi + 1 >> 1;
161
- if (offsets[mid] <= offset) {
162
- lo = mid;
163
- } else {
164
- hi = mid - 1;
165
- }
166
- }
167
- return { line: lo + 1, column: offset - offsets[lo] };
168
- }
169
-
170
- // src/parser/jsdoc-parser.ts
171
- import { parse } from "comment-parser";
172
- function parseJsDoc(commentText) {
173
- try {
174
- let stripped = commentText.trim();
175
- if (stripped.startsWith("/**"))
176
- stripped = stripped.slice(3);
177
- if (stripped.endsWith("*/"))
178
- stripped = stripped.slice(0, -2);
179
- const blocks = parse(`/** ${stripped} */`);
180
- const block = blocks[0] ?? { description: "", tags: [] };
181
- return {
182
- description: (block.description ?? "").trim(),
183
- tags: (block.tags ?? []).map((t) => ({
184
- tag: t.tag ?? "",
185
- name: t.name ?? "",
186
- type: t.type ?? "",
187
- description: t.description ?? "",
188
- optional: t.optional ?? false,
189
- ...t.default !== undefined ? { default: t.default } : {}
190
- }))
191
- };
192
- } catch (err) {
193
- throw new ParseError(`Failed to parse JSDoc comment`, { cause: err });
194
- }
195
- }
196
-
197
- // src/extractor/symbol-extractor.ts
198
- function extractSymbols(parsed) {
199
- const { program, sourceText, comments } = parsed;
200
- const lineOffsets = buildLineOffsets(sourceText);
201
- function span(start, end) {
202
- return {
203
- start: getLineColumn(lineOffsets, start),
204
- end: getLineColumn(lineOffsets, end)
205
- };
206
- }
207
- function findJsDocComment(nodeStart) {
208
- let best = null;
209
- for (const c of comments) {
210
- if (c.type !== "Block")
211
- continue;
212
- if (c.end > nodeStart)
213
- continue;
214
- if (!c.value.startsWith("*"))
215
- continue;
216
- if (!best || c.end > best.end) {
217
- best = { value: `/*${c.value}*/`, end: c.end };
218
- }
219
- }
220
- if (!best)
221
- return;
222
- for (const stmt of program.body) {
223
- const stmtStart = stmt.start ?? 0;
224
- if (stmtStart === nodeStart)
225
- continue;
226
- if (stmtStart > best.end && stmtStart < nodeStart) {
227
- return;
228
- }
229
- }
230
- return best.value;
231
- }
232
- function typeText(typeAnnotation) {
233
- if (!typeAnnotation)
234
- return;
235
- const inner = typeAnnotation.typeAnnotation ?? typeAnnotation;
236
- return sourceText.slice(inner.start, inner.end);
237
- }
238
- function extractDecorators(decorators) {
239
- if (!decorators || decorators.length === 0)
240
- return [];
241
- return decorators.map((d) => {
242
- const expr = d.expression;
243
- if (!expr)
244
- return { name: "unknown" };
245
- if (expr.type === "CallExpression") {
246
- const name = expr.callee?.name ?? expr.callee?.property?.name ?? "unknown";
247
- const args = (expr.arguments ?? []).map((a) => sourceText.slice(a.start, a.end));
248
- return { name, arguments: args.length > 0 ? args : undefined };
249
- }
250
- if (expr.type === "Identifier")
251
- return { name: expr.name ?? "unknown" };
252
- return { name: sourceText.slice(expr.start, expr.end) };
253
- });
254
- }
255
- function extractParam(p) {
256
- const inner = p.type === "TSParameterProperty" ? p.parameter : p;
257
- if (inner?.type === "RestElement") {
258
- const argName = inner.argument?.name ?? "unknown";
259
- const name2 = `...${argName}`;
260
- const typeAnn2 = inner.typeAnnotation;
261
- const type2 = typeAnn2 ? typeText(typeAnn2) : undefined;
262
- const param2 = { name: name2, isOptional: false };
263
- if (type2)
264
- param2.type = type2;
265
- return param2;
266
- }
267
- if (inner?.type === "AssignmentPattern") {
268
- const left = inner.left;
269
- const right = inner.right;
270
- const name2 = left?.name ?? "unknown";
271
- const typeAnn2 = left?.typeAnnotation;
272
- const type2 = typeAnn2 ? typeText(typeAnn2) : undefined;
273
- const defaultValue = sourceText.slice(right.start, right.end);
274
- const decos2 = extractDecorators(left?.decorators ?? []);
275
- const param2 = { name: name2, isOptional: true, defaultValue };
276
- if (type2)
277
- param2.type = type2;
278
- if (decos2.length > 0)
279
- param2.decorators = decos2;
280
- return param2;
281
- }
282
- const name = inner?.name ?? inner?.pattern?.name ?? "unknown";
283
- const optional = !!inner?.optional;
284
- const typeAnn = inner?.typeAnnotation;
285
- const type = typeAnn ? typeText(typeAnn) : undefined;
286
- const decos = extractDecorators(inner?.decorators ?? []);
287
- const param = { name, isOptional: optional };
288
- if (type)
289
- param.type = type;
290
- if (decos.length > 0)
291
- param.decorators = decos;
292
- return param;
293
- }
294
- function extractModifiers(node, fn) {
295
- const mods = [];
296
- if (fn?.async)
297
- mods.push("async");
298
- if (node.static)
299
- mods.push("static");
300
- if (node.abstract)
301
- mods.push("abstract");
302
- if (node.readonly)
303
- mods.push("readonly");
304
- if (node.override)
305
- mods.push("override");
306
- if (node.declare)
307
- mods.push("declare");
308
- if (node.const)
309
- mods.push("const");
310
- const acc = node.accessibility;
311
- if (acc === "private")
312
- mods.push("private");
313
- else if (acc === "protected")
314
- mods.push("protected");
315
- else if (acc === "public")
316
- mods.push("public");
317
- return mods;
318
- }
319
- function classHeritage(node) {
320
- const heritage = [];
321
- if (node.superClass) {
322
- const name = sourceText.slice(node.superClass.start, node.superClass.end);
323
- heritage.push({ kind: "extends", name });
324
- }
325
- const impls = node.implements ?? [];
326
- for (const impl of impls) {
327
- const expr = impl.expression ?? impl;
328
- const name = sourceText.slice(expr.start, expr.end);
329
- heritage.push({ kind: "implements", name });
330
- }
331
- return heritage;
332
- }
333
- function interfaceHeritage(node) {
334
- const heritage = [];
335
- for (const ext of node.extends ?? []) {
336
- const expr = ext.expression ?? ext;
337
- const name = sourceText.slice(expr.start, expr.end);
338
- heritage.push({ kind: "extends", name });
339
- }
340
- return heritage;
341
- }
342
- function extractClassMembers(bodyNodes) {
343
- const members = [];
344
- for (const m of bodyNodes) {
345
- if (m.type === "MethodDefinition") {
346
- const name = m.key?.name ?? "unknown";
347
- const fnValue = m.value;
348
- const rawKind = m.kind ?? "method";
349
- const methodKind = rawKind === "constructor" ? "constructor" : rawKind === "get" ? "getter" : rawKind === "set" ? "setter" : "method";
350
- const mods = extractModifiers(m, fnValue);
351
- const params = (fnValue?.params ?? []).map(extractParam);
352
- const returnType = typeText(fnValue?.returnType);
353
- const s = {
354
- kind: "method",
355
- name,
356
- span: span(m.start, m.end),
357
- isExported: false,
358
- methodKind,
359
- modifiers: mods,
360
- parameters: params.length > 0 ? params : undefined,
361
- returnType
362
- };
363
- members.push(s);
364
- } else if (m.type === "PropertyDefinition") {
365
- const name = m.key?.name ?? "unknown";
366
- const mods = extractModifiers(m);
367
- const s = {
368
- kind: "property",
369
- name,
370
- span: span(m.start, m.end),
371
- isExported: false,
372
- modifiers: mods
373
- };
374
- members.push(s);
375
- }
376
- }
377
- return members;
378
- }
379
- function extractInterfaceMembers(bodyNodes) {
380
- const members = [];
381
- for (const m of bodyNodes) {
382
- if (m.type === "TSMethodSignature") {
383
- const name = m.key?.name ?? "unknown";
384
- const params = (m.params ?? []).map(extractParam);
385
- const returnType = typeText(m.returnType);
386
- members.push({
387
- kind: "method",
388
- name,
389
- span: span(m.start, m.end),
390
- isExported: false,
391
- modifiers: [],
392
- methodKind: "method",
393
- parameters: params.length > 0 ? params : undefined,
394
- returnType
395
- });
396
- } else if (m.type === "TSPropertySignature") {
397
- const name = m.key?.name ?? "unknown";
398
- const typeAnn = typeText(m.typeAnnotation);
399
- const s = {
400
- kind: "property",
401
- name,
402
- span: span(m.start, m.end),
403
- isExported: false,
404
- modifiers: m.readonly ? ["readonly"] : [],
405
- returnType: typeAnn
406
- };
407
- members.push(s);
408
- }
409
- }
410
- return members;
411
- }
412
- function buildSymbol(node, isExported) {
413
- const type = node.type ?? "";
414
- if (type === "FunctionDeclaration") {
415
- const name = node.id?.name ?? "default";
416
- const params = (node.params ?? []).map(extractParam);
417
- const returnType = typeText(node.returnType);
418
- const mods = extractModifiers(node, node);
419
- const decos = extractDecorators(node.decorators ?? []);
420
- const typeParameters = node.typeParameters?.params?.map((p) => p.name?.name).filter(Boolean) || undefined;
421
- const sym = {
422
- kind: "function",
423
- name,
424
- span: span(node.start, node.end),
425
- isExported,
426
- modifiers: mods,
427
- parameters: params.length > 0 ? params : undefined,
428
- returnType,
429
- decorators: decos.length > 0 ? decos : undefined
430
- };
431
- if (typeParameters && typeParameters.length > 0)
432
- sym.typeParameters = typeParameters;
433
- return sym;
434
- }
435
- if (type === "ClassDeclaration" || type === "ClassExpression") {
436
- const name = node.id?.name ?? "default";
437
- const heritage = classHeritage(node);
438
- const members = extractClassMembers(node.body?.body ?? []);
439
- const decos = extractDecorators(node.decorators ?? []);
440
- const mods = extractModifiers(node, node);
441
- const typeParameters = node.typeParameters?.params?.map((p) => p.name?.name).filter(Boolean) || undefined;
442
- const sym = {
443
- kind: "class",
444
- name,
445
- span: span(node.start, node.end),
446
- isExported,
447
- modifiers: mods,
448
- heritage: heritage.length > 0 ? heritage : undefined,
449
- members: members.length > 0 ? members : undefined,
450
- decorators: decos.length > 0 ? decos : undefined
451
- };
452
- if (typeParameters && typeParameters.length > 0)
453
- sym.typeParameters = typeParameters;
454
- return sym;
455
- }
456
- if (type === "VariableDeclaration") {
457
- const symbols = [];
458
- for (const decl of node.declarations ?? []) {
459
- const id = decl.id;
460
- const init = decl.init;
461
- if (id?.type === "ObjectPattern") {
462
- for (const prop of id.properties ?? []) {
463
- const propName = prop.value?.name ?? prop.key?.name ?? "unknown";
464
- symbols.push({
465
- kind: "variable",
466
- name: propName,
467
- span: span(prop.start ?? decl.start, prop.end ?? decl.end),
468
- isExported,
469
- modifiers: []
470
- });
471
- }
472
- continue;
473
- }
474
- if (id?.type === "ArrayPattern") {
475
- for (const elem of id.elements ?? []) {
476
- if (!elem || elem.type !== "Identifier")
477
- continue;
478
- const elemName = elem.name ?? "unknown";
479
- symbols.push({
480
- kind: "variable",
481
- name: elemName,
482
- span: span(elem.start ?? decl.start, elem.end ?? decl.end),
483
- isExported,
484
- modifiers: []
485
- });
486
- }
487
- continue;
488
- }
489
- const name = id?.name ?? "unknown";
490
- let kind = "variable";
491
- let params;
492
- let returnType;
493
- if (init?.type === "FunctionExpression" || init?.type === "ArrowFunctionExpression") {
494
- kind = "function";
495
- const rawParams = init.params ?? [];
496
- params = rawParams.map(extractParam);
497
- returnType = typeText(init.returnType);
498
- }
499
- const mods = [];
500
- symbols.push({
501
- kind,
502
- name,
503
- span: span(decl.start, decl.end),
504
- isExported,
505
- modifiers: mods,
506
- parameters: params,
507
- returnType
508
- });
509
- }
510
- if (symbols.length === 0)
511
- return null;
512
- if (symbols.length === 1)
513
- return symbols[0];
514
- return symbols;
515
- }
516
- if (type === "TSTypeAliasDeclaration") {
517
- const name = node.id?.name ?? "unknown";
518
- return {
519
- kind: "type",
520
- name,
521
- span: span(node.start, node.end),
522
- isExported,
523
- modifiers: []
524
- };
525
- }
526
- if (type === "TSInterfaceDeclaration") {
527
- const name = node.id?.name ?? "unknown";
528
- const heritage = interfaceHeritage(node);
529
- const members = extractInterfaceMembers(node.body?.body ?? []);
530
- const typeParameters = node.typeParameters?.params?.map((p) => p.name?.name).filter(Boolean) || undefined;
531
- const sym = {
532
- kind: "interface",
533
- name,
534
- span: span(node.start, node.end),
535
- isExported,
536
- modifiers: [],
537
- heritage: heritage.length > 0 ? heritage : undefined,
538
- members: members.length > 0 ? members : undefined
539
- };
540
- if (typeParameters && typeParameters.length > 0)
541
- sym.typeParameters = typeParameters;
542
- return sym;
543
- }
544
- if (type === "TSEnumDeclaration") {
545
- const name = node.id?.name ?? "unknown";
546
- const mods = extractModifiers(node);
547
- const rawMembers = node.body?.members ?? [];
548
- const members = rawMembers.map((m) => ({
549
- kind: "property",
550
- name: m.id?.name ?? m.id?.value ?? "unknown",
551
- span: span(m.start, m.end),
552
- isExported: false,
553
- modifiers: []
554
- }));
555
- return {
556
- kind: "enum",
557
- name,
558
- span: span(node.start, node.end),
559
- isExported,
560
- modifiers: mods,
561
- members: members.length > 0 ? members : undefined
562
- };
563
- }
564
- return null;
565
- }
566
- const result = [];
567
- for (const node of program.body) {
568
- let sym = null;
569
- const record = node;
570
- const type = typeof record.type === "string" ? record.type : "";
571
- if (type === "ExportNamedDeclaration") {
572
- const n = node;
573
- if (n.declaration) {
574
- sym = buildSymbol(n.declaration, true);
575
- if (sym && !Array.isArray(sym)) {
576
- sym.span = span(n.start, n.end);
577
- } else if (Array.isArray(sym)) {
578
- for (const s of sym)
579
- s.span = span(n.start, n.end);
580
- }
581
- }
582
- } else if (type === "ExportDefaultDeclaration") {
583
- const n = node;
584
- const decl = n.declaration;
585
- if (decl) {
586
- sym = buildSymbol(decl, true);
587
- if (sym && !Array.isArray(sym)) {
588
- sym.name = decl.id?.name ?? "default";
589
- sym.isExported = true;
590
- sym.span = span(n.start, n.end);
591
- }
592
- }
593
- } else {
594
- sym = buildSymbol(node, false);
595
- }
596
- const syms = Array.isArray(sym) ? sym : sym ? [sym] : [];
597
- for (const s of syms) {
598
- const nodeStart = node.start ?? 0;
599
- const jsdocText = findJsDocComment(nodeStart);
600
- if (jsdocText) {
601
- s.jsDoc = parseJsDoc(jsdocText);
602
- }
603
- result.push(s);
604
- }
605
- }
606
- return result;
607
- }
608
-
609
- // src/extractor/extractor-utils.ts
610
- import { resolve, dirname, extname } from "path";
611
- function resolveImport(currentFilePath, importPath, tsconfigPaths) {
612
- const withTypeScriptCandidates = (resolved) => {
613
- const extension = extname(resolved);
614
- if (extension === "") {
615
- return [
616
- resolved + ".ts",
617
- resolved + "/index.ts",
618
- resolved + ".mts",
619
- resolved + "/index.mts",
620
- resolved + ".cts",
621
- resolved + "/index.cts"
622
- ];
623
- }
624
- if (extension === ".js")
625
- return [resolved.slice(0, -3) + ".ts"];
626
- if (extension === ".mjs")
627
- return [resolved.slice(0, -4) + ".mts"];
628
- if (extension === ".cjs")
629
- return [resolved.slice(0, -4) + ".cts"];
630
- return [resolved];
631
- };
632
- if (importPath.startsWith(".")) {
633
- const resolved = resolve(dirname(currentFilePath), importPath);
634
- return withTypeScriptCandidates(resolved);
635
- }
636
- if (tsconfigPaths) {
637
- for (const [pattern, targets] of tsconfigPaths.paths) {
638
- if (targets.length === 0)
639
- continue;
640
- const starIdx = pattern.indexOf("*");
641
- if (starIdx === -1) {
642
- if (importPath === pattern) {
643
- const candidates = [];
644
- for (const t of targets) {
645
- candidates.push(...withTypeScriptCandidates(resolve(tsconfigPaths.baseUrl, t)));
646
- }
647
- return candidates;
648
- }
649
- } else {
650
- const prefix = pattern.slice(0, starIdx);
651
- const suffix = pattern.slice(starIdx + 1);
652
- if (importPath.startsWith(prefix) && (suffix === "" || importPath.endsWith(suffix))) {
653
- const captured = importPath.slice(prefix.length, suffix === "" ? undefined : importPath.length - suffix.length);
654
- const candidates = [];
655
- for (const t of targets) {
656
- candidates.push(...withTypeScriptCandidates(resolve(tsconfigPaths.baseUrl, t.replace("*", captured))));
657
- }
658
- return candidates;
659
- }
660
- }
661
- }
662
- }
663
- return [];
664
- }
665
- function buildImportMap(ast, currentFilePath, tsconfigPaths, resolveImportFn = resolveImport) {
666
- const map = new Map;
667
- const body = ast.body ?? [];
668
- for (const node of body) {
669
- if (node.type !== "ImportDeclaration")
670
- continue;
671
- const sourcePath = node.source?.value ?? "";
672
- const candidates = resolveImportFn(currentFilePath, sourcePath, tsconfigPaths);
673
- if (candidates.length === 0)
674
- continue;
675
- const resolved = candidates[0];
676
- const specifiers = node.specifiers ?? [];
677
- for (const spec of specifiers) {
678
- switch (spec.type) {
679
- case "ImportSpecifier":
680
- map.set(spec.local.name, {
681
- path: resolved,
682
- importedName: spec.imported.name
683
- });
684
- break;
685
- case "ImportDefaultSpecifier":
686
- map.set(spec.local.name, {
687
- path: resolved,
688
- importedName: "default"
689
- });
690
- break;
691
- case "ImportNamespaceSpecifier":
692
- map.set(spec.local.name, {
693
- path: resolved,
694
- importedName: "*"
695
- });
696
- break;
697
- }
698
- }
699
- }
700
- return map;
701
- }
702
-
703
- // src/parser/ast-utils.ts
704
- var SKIP_KEYS = new Set(["loc", "start", "end", "scope"]);
705
- function visit(node, callback) {
706
- if (!node || typeof node !== "object")
707
- return;
708
- if (Array.isArray(node)) {
709
- for (const item of node)
710
- visit(item, callback);
711
- return;
712
- }
713
- const record = node;
714
- callback(record);
715
- for (const key of Object.keys(record)) {
716
- if (SKIP_KEYS.has(key))
717
- continue;
718
- const child = record[key];
719
- if (child && typeof child === "object") {
720
- visit(child, callback);
721
- }
722
- }
723
- }
724
- function getStringLiteralValue(node) {
725
- if (!node || typeof node !== "object" || Array.isArray(node))
726
- return null;
727
- const record = node;
728
- if ((record.type === "StringLiteral" || record.type === "Literal") && typeof record.value === "string") {
729
- return record.value;
730
- }
731
- return null;
732
- }
733
- function getQualifiedName(expr) {
734
- if (!expr || typeof expr !== "object" || Array.isArray(expr))
735
- return null;
736
- const node = expr;
737
- if (node.type === "Identifier") {
738
- const name = node.name;
739
- return { root: name, parts: [], full: name };
740
- }
741
- if (node.type === "ThisExpression") {
742
- return { root: "this", parts: [], full: "this" };
743
- }
744
- if (node.type === "Super") {
745
- return { root: "super", parts: [], full: "super" };
746
- }
747
- if (node.type === "MemberExpression") {
748
- const parts = [];
749
- let current = node;
750
- while (current.type === "MemberExpression") {
751
- const prop = current.property;
752
- if (!prop || typeof prop.name !== "string")
753
- return null;
754
- parts.unshift(prop.name);
755
- current = current.object;
756
- }
757
- let root;
758
- if (current.type === "Identifier") {
759
- root = current.name;
760
- } else if (current.type === "ThisExpression") {
761
- root = "this";
762
- } else if (current.type === "Super") {
763
- root = "super";
764
- } else {
765
- return null;
766
- }
767
- const full = [root, ...parts].join(".");
768
- return { root, parts, full };
769
- }
770
- return null;
771
- }
772
-
773
- // src/extractor/imports-extractor.ts
774
- function extractImports(ast, filePath, tsconfigPaths, resolveImportFn = resolveImport) {
775
- const relations = [];
776
- const body = ast.body ?? [];
777
- for (const node of body) {
778
- if (node.type === "ImportDeclaration") {
779
- const sourcePath = node.source?.value ?? "";
780
- const candidates = resolveImportFn(filePath, sourcePath, tsconfigPaths);
781
- if (candidates.length === 0)
782
- continue;
783
- const resolved = candidates[0];
784
- const isType = node.importKind === "type";
785
- relations.push({
786
- type: "imports",
787
- srcFilePath: filePath,
788
- srcSymbolName: null,
789
- dstFilePath: resolved,
790
- dstSymbolName: null,
791
- ...isType ? { metaJson: JSON.stringify({ isType: true }) } : {}
792
- });
793
- continue;
794
- }
795
- if (node.type === "ExportAllDeclaration" && node.source) {
796
- const sourcePath = node.source?.value ?? "";
797
- const candidates = resolveImportFn(filePath, sourcePath, tsconfigPaths);
798
- if (candidates.length === 0)
799
- continue;
800
- const resolved = candidates[0];
801
- const isType = node.exportKind === "type";
802
- const meta = { isReExport: true };
803
- if (isType)
804
- meta.isType = true;
805
- relations.push({
806
- type: "imports",
807
- srcFilePath: filePath,
808
- srcSymbolName: null,
809
- dstFilePath: resolved,
810
- dstSymbolName: null,
811
- metaJson: JSON.stringify(meta)
812
- });
813
- continue;
814
- }
815
- if (node.type === "ExportNamedDeclaration" && node.source) {
816
- const sourcePath = node.source?.value ?? "";
817
- const candidates = resolveImportFn(filePath, sourcePath, tsconfigPaths);
818
- if (candidates.length === 0)
819
- continue;
820
- const resolved = candidates[0];
821
- relations.push({
822
- type: "imports",
823
- srcFilePath: filePath,
824
- srcSymbolName: null,
825
- dstFilePath: resolved,
826
- dstSymbolName: null,
827
- metaJson: JSON.stringify({ isReExport: true })
828
- });
829
- }
830
- }
831
- visit(ast, (node) => {
832
- if (node.type !== "ImportExpression")
833
- return;
834
- const sourceValue = getStringLiteralValue(node.source);
835
- if (!sourceValue)
836
- return;
837
- const candidates = resolveImportFn(filePath, sourceValue, tsconfigPaths);
838
- if (candidates.length === 0)
839
- return;
840
- const resolved = candidates[0];
841
- relations.push({
842
- type: "imports",
843
- srcFilePath: filePath,
844
- srcSymbolName: null,
845
- dstFilePath: resolved,
846
- dstSymbolName: null,
847
- metaJson: JSON.stringify({ isDynamic: true })
848
- });
849
- });
850
- return relations;
851
- }
852
-
853
- // src/extractor/calls-extractor.ts
854
- function extractCalls(ast, filePath, importMap) {
855
- const relations = [];
856
- const functionStack = [];
857
- const classStack = [];
858
- function currentCaller() {
859
- if (functionStack.length > 0)
860
- return functionStack[functionStack.length - 1] ?? null;
861
- return null;
862
- }
863
- function resolveCallee(qn) {
864
- if (!qn)
865
- return null;
866
- const ref = importMap.get(qn.root);
867
- if (qn.parts.length === 0) {
868
- if (ref) {
869
- return { dstFilePath: ref.path, dstSymbolName: ref.importedName, resolution: "import" };
870
- }
871
- return { dstFilePath: filePath, dstSymbolName: qn.root, resolution: "local" };
872
- } else {
873
- if (ref && ref.importedName === "*") {
874
- const dstSymbolName = qn.parts[qn.parts.length - 1];
875
- return { dstFilePath: ref.path, dstSymbolName, resolution: "namespace" };
876
- }
877
- return { dstFilePath: filePath, dstSymbolName: qn.full, resolution: "local-member" };
878
- }
879
- }
880
- function walk(node) {
881
- if (!node || typeof node !== "object")
882
- return;
883
- if (Array.isArray(node)) {
884
- for (const item of node)
885
- walk(item);
886
- return;
887
- }
888
- const record = node;
889
- const type = typeof record.type === "string" ? record.type : "";
890
- if (type === "ClassDeclaration" || type === "ClassExpression") {
891
- const classNode = record;
892
- const className = classNode.id?.name ?? "AnonymousClass";
893
- classStack.push(className);
894
- walk(classNode.body);
895
- classStack.pop();
896
- return;
897
- }
898
- if (type === "FunctionDeclaration") {
899
- const functionNode = record;
900
- const name = functionNode.id?.name ?? "anonymous";
901
- functionStack.push(name);
902
- walk(functionNode.body);
903
- functionStack.pop();
904
- return;
905
- }
906
- if (type === "VariableDeclarator" && record.init && (record.init?.type === "FunctionExpression" || record.init?.type === "ArrowFunctionExpression")) {
907
- const declarator = record;
908
- const name = declarator.id?.name ?? "anonymous";
909
- functionStack.push(name);
910
- walk(declarator.init?.body ?? declarator.init);
911
- functionStack.pop();
912
- return;
913
- }
914
- if (type === "MethodDefinition" && record.value) {
915
- const method = record;
916
- const className = classStack[classStack.length - 1] ?? "";
917
- const methodName = method.key?.name ?? "anonymous";
918
- const fullName = className ? `${className}.${methodName}` : methodName;
919
- functionStack.push(fullName);
920
- walk(method.value?.body);
921
- functionStack.pop();
922
- return;
923
- }
924
- if (type === "FunctionExpression" || type === "ArrowFunctionExpression") {
925
- const parentCaller = currentCaller();
926
- const anonymousName = parentCaller ? `${parentCaller}.<anonymous>` : "<anonymous>";
927
- functionStack.push(anonymousName);
928
- walk(record.body);
929
- functionStack.pop();
930
- return;
931
- }
932
- if (type === "CallExpression") {
933
- const call = record;
934
- const qn = getQualifiedName(call.callee);
935
- const dst = resolveCallee(qn);
936
- if (dst) {
937
- const srcSymbolName = currentCaller();
938
- const meta = {};
939
- if (srcSymbolName === null)
940
- meta.scope = "module";
941
- relations.push({
942
- type: "calls",
943
- srcFilePath: filePath,
944
- srcSymbolName,
945
- dstFilePath: dst.dstFilePath,
946
- dstSymbolName: dst.dstSymbolName,
947
- ...Object.keys(meta).length > 0 ? { metaJson: JSON.stringify(meta) } : {}
948
- });
949
- }
950
- walk(call.callee);
951
- for (const arg of call.arguments ?? [])
952
- walk(arg);
953
- return;
954
- }
955
- if (type === "NewExpression") {
956
- const ctorCall = record;
957
- const qn = getQualifiedName(ctorCall.callee);
958
- const dst = resolveCallee(qn);
959
- if (dst) {
960
- const srcSymbolName = currentCaller();
961
- const meta = { isNew: true };
962
- if (srcSymbolName === null)
963
- meta.scope = "module";
964
- relations.push({
965
- type: "calls",
966
- srcFilePath: filePath,
967
- srcSymbolName,
968
- dstFilePath: dst.dstFilePath,
969
- dstSymbolName: dst.dstSymbolName,
970
- metaJson: JSON.stringify(meta)
971
- });
972
- }
973
- for (const arg of ctorCall.arguments ?? [])
974
- walk(arg);
975
- return;
976
- }
977
- for (const key of Object.keys(record)) {
978
- if (key === "loc" || key === "start" || key === "end" || key === "scope")
979
- continue;
980
- const child = record[key];
981
- if (child && typeof child === "object") {
982
- walk(child);
983
- }
984
- }
985
- }
986
- walk(ast);
987
- return relations;
988
- }
989
-
990
- // src/extractor/heritage-extractor.ts
991
- function extractHeritage(ast, filePath, importMap) {
992
- const relations = [];
993
- visit(ast, (node) => {
994
- if (node.type === "TSInterfaceDeclaration") {
995
- const interfaceName = node.id?.name ?? "AnonymousInterface";
996
- const interfaces = node.extends ?? [];
997
- for (const item of interfaces) {
998
- const expr = item.expression ?? item;
999
- const qn = getQualifiedName(expr);
1000
- if (!qn)
1001
- continue;
1002
- const rel = resolveHeritageDst(qn, filePath, importMap);
1003
- relations.push({
1004
- type: "extends",
1005
- srcFilePath: filePath,
1006
- srcSymbolName: interfaceName,
1007
- ...rel
1008
- });
1009
- }
1010
- return;
1011
- }
1012
- if (node.type !== "ClassDeclaration" && node.type !== "ClassExpression")
1013
- return;
1014
- const className = node.id?.name ?? "AnonymousClass";
1015
- if (node.superClass) {
1016
- const qn = getQualifiedName(node.superClass);
1017
- if (qn) {
1018
- const rel = resolveHeritageDst(qn, filePath, importMap);
1019
- relations.push({
1020
- type: "extends",
1021
- srcFilePath: filePath,
1022
- srcSymbolName: className,
1023
- ...rel
1024
- });
1025
- }
1026
- }
1027
- const impls = node.implements ?? [];
1028
- for (const impl of impls) {
1029
- const expr = impl.expression ?? impl;
1030
- const qn = getQualifiedName(expr);
1031
- if (!qn)
1032
- continue;
1033
- const rel = resolveHeritageDst(qn, filePath, importMap);
1034
- relations.push({
1035
- type: "implements",
1036
- srcFilePath: filePath,
1037
- srcSymbolName: className,
1038
- ...rel
1039
- });
1040
- }
1041
- });
1042
- return relations;
1043
- }
1044
- function resolveHeritageDst(qn, currentFilePath, importMap) {
1045
- const ref = importMap.get(qn.root);
1046
- if (ref) {
1047
- if (ref.importedName === "*") {
1048
- const dstSymbolName = qn.parts[qn.parts.length - 1] ?? qn.root;
1049
- return {
1050
- dstFilePath: ref.path,
1051
- dstSymbolName,
1052
- metaJson: JSON.stringify({ isNamespaceImport: true })
1053
- };
1054
- }
1055
- return {
1056
- dstFilePath: ref.path,
1057
- dstSymbolName: qn.parts.length > 0 ? qn.full : ref.importedName
1058
- };
1059
- }
1060
- return {
1061
- dstFilePath: currentFilePath,
1062
- dstSymbolName: qn.full,
1063
- metaJson: JSON.stringify({ isLocal: true })
1064
- };
1065
- }
1066
-
1067
- // src/extractor/relation-extractor.ts
1068
- function extractRelations(ast, filePath, tsconfigPaths) {
1069
- const importMap = buildImportMap(ast, filePath, tsconfigPaths);
1070
- const imports = extractImports(ast, filePath, tsconfigPaths);
1071
- const calls = extractCalls(ast, filePath, importMap);
1072
- const heritage = extractHeritage(ast, filePath, importMap);
1073
- return [...imports, ...calls, ...heritage];
1074
- }
1075
-
1076
- // src/store/connection.ts
1077
- import { Database } from "bun:sqlite";
1078
- import { mkdirSync, unlinkSync, existsSync } from "fs";
1079
- import { dirname as dirname2, join } from "path";
1080
- import { drizzle } from "drizzle-orm/bun-sqlite";
1081
- import { migrate } from "drizzle-orm/bun-sqlite/migrator";
1082
-
1083
- // src/store/schema.ts
1084
- var exports_schema = {};
1085
- __export(exports_schema, {
1086
- watcherOwner: () => watcherOwner,
1087
- symbols: () => symbols,
1088
- relations: () => relations,
1089
- files: () => files,
1090
- FTS_SETUP_SQL: () => FTS_SETUP_SQL
1091
- });
1092
- import { sql } from "drizzle-orm";
1093
- import {
1094
- sqliteTable,
1095
- text,
1096
- integer,
1097
- real,
1098
- index,
1099
- primaryKey,
1100
- foreignKey,
1101
- check
1102
- } from "drizzle-orm/sqlite-core";
1103
- var files = sqliteTable("files", {
1104
- project: text("project").notNull(),
1105
- filePath: text("file_path").notNull(),
1106
- mtimeMs: real("mtime_ms").notNull(),
1107
- size: integer("size").notNull(),
1108
- contentHash: text("content_hash").notNull(),
1109
- updatedAt: text("updated_at").notNull()
1110
- }, (table) => [primaryKey({ columns: [table.project, table.filePath] })]);
1111
- var symbols = sqliteTable("symbols", {
1112
- id: integer("id").primaryKey({ autoIncrement: true }),
1113
- project: text("project").notNull(),
1114
- filePath: text("file_path").notNull(),
1115
- kind: text("kind").notNull(),
1116
- name: text("name").notNull(),
1117
- startLine: integer("start_line").notNull(),
1118
- startColumn: integer("start_column").notNull(),
1119
- endLine: integer("end_line").notNull(),
1120
- endColumn: integer("end_column").notNull(),
1121
- isExported: integer("is_exported").notNull().default(0),
1122
- signature: text("signature"),
1123
- fingerprint: text("fingerprint"),
1124
- detailJson: text("detail_json"),
1125
- contentHash: text("content_hash").notNull(),
1126
- indexedAt: text("indexed_at").notNull()
1127
- }, (table) => [
1128
- index("idx_symbols_project_file").on(table.project, table.filePath),
1129
- index("idx_symbols_project_kind").on(table.project, table.kind),
1130
- index("idx_symbols_project_name").on(table.project, table.name),
1131
- index("idx_symbols_fingerprint").on(table.project, table.fingerprint),
1132
- foreignKey({
1133
- columns: [table.project, table.filePath],
1134
- foreignColumns: [files.project, files.filePath]
1135
- }).onDelete("cascade")
1136
- ]);
1137
- var relations = sqliteTable("relations", {
1138
- id: integer("id").primaryKey({ autoIncrement: true }),
1139
- project: text("project").notNull(),
1140
- type: text("type").notNull(),
1141
- srcFilePath: text("src_file_path").notNull(),
1142
- srcSymbolName: text("src_symbol_name"),
1143
- dstFilePath: text("dst_file_path").notNull(),
1144
- dstSymbolName: text("dst_symbol_name"),
1145
- metaJson: text("meta_json")
1146
- }, (table) => [
1147
- index("idx_relations_src").on(table.project, table.srcFilePath),
1148
- index("idx_relations_dst").on(table.project, table.dstFilePath),
1149
- index("idx_relations_type").on(table.project, table.type),
1150
- foreignKey({
1151
- columns: [table.project, table.srcFilePath],
1152
- foreignColumns: [files.project, files.filePath]
1153
- }).onDelete("cascade"),
1154
- foreignKey({
1155
- columns: [table.project, table.dstFilePath],
1156
- foreignColumns: [files.project, files.filePath]
1157
- }).onDelete("cascade")
1158
- ]);
1159
- var watcherOwner = sqliteTable("watcher_owner", {
1160
- id: integer("id").primaryKey(),
1161
- pid: integer("pid").notNull(),
1162
- startedAt: text("started_at").notNull(),
1163
- heartbeatAt: text("heartbeat_at").notNull()
1164
- }, (table) => [check("watcher_owner_singleton", sql`${table.id} = 1`)]);
1165
- var FTS_SETUP_SQL = [
1166
- `CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(
2
+ var ct=Object.defineProperty;var mt=(e,t)=>{for(var n in t)ct(e,n,{get:t[n],enumerable:!0,configurable:!0,set:(r)=>t[n]=()=>r})};import{err as T,isErr as te}from"@zipbul/result";import ot from"path";import{existsSync as Ut}from"fs";import{err as dt}from"@zipbul/result";import{parseSync as ut}from"oxc-parser";function R(e,t,n){return n!==void 0?{type:e,message:t,cause:n}:{type:e,message:t}}function re(e,t,n=ut){try{let{program:r,errors:i,comments:s}=n(e,t);return{filePath:e,program:r,errors:i,comments:s,sourceText:t}}catch(r){return dt(R("parse",`Failed to parse file: ${e}`,r))}}class he{#t;#e=new Map;constructor(e){this.#t=Math.max(1,e)}get size(){return this.#e.size}has(e){return this.#e.has(e)}get(e){if(!this.#e.has(e))return;let t=this.#e.get(e);return this.#e.delete(e),this.#e.set(e,t),t}set(e,t){if(this.#e.has(e))this.#e.delete(e);if(this.#e.set(e,t),this.#e.size>this.#t){let n=this.#e.keys().next().value;if(n!==void 0)this.#e.delete(n)}}delete(e){return this.#e.delete(e)}clear(){this.#e.clear()}}class ye{lru;constructor(e=500){this.lru=new he(e)}get(e){return this.lru.get(e)}set(e,t){this.lru.set(e,t)}invalidate(e){this.lru.delete(e)}invalidateAll(){this.lru.clear()}size(){return this.lru.size}}function Me(e){let t=[0];for(let n=0;n<e.length;n++)if(e[n]===`
3
+ `)t.push(n+1);return t}function be(e,t){let n=0,r=e.length-1;while(n<r){let i=n+r+1>>1;if(e[i]<=t)n=i;else r=i-1}return{line:n+1,column:t-e[n]}}import{err as pt}from"@zipbul/result";import{parse as ft}from"comment-parser";function Le(e){try{let t=e.trim();if(t.startsWith("/**"))t=t.slice(3);if(t.endsWith("*/"))t=t.slice(0,-2);let r=ft(`/** ${t} */`)[0]??{description:"",tags:[]};return{description:(r.description??"").trim(),tags:(r.tags??[]).map((i)=>({tag:i.tag??"",name:i.name??"",type:i.type??"",description:i.description??"",optional:i.optional??!1,...i.default!==void 0?{default:i.default}:{}}))}}catch(t){return pt(R("parse","Failed to parse JSDoc comment",t))}}import{isErr as gt}from"@zipbul/result";function ae(e){let{program:t,sourceText:n,comments:r}=e,i=Me(n);function s(o,c){return{start:be(i,o),end:be(i,c)}}function u(o){let c=null;for(let a of r){if(a.type!=="Block")continue;if(a.end>o)continue;if(!a.value.startsWith("*"))continue;if(!c||a.end>c.end)c={value:`/*${a.value}*/`,end:a.end}}if(!c)return;for(let a of t.body){let g=a.start??0;if(g===o)continue;if(g>c.end&&g<o)return}return c.value}function m(o){if(!o)return;let c=o.typeAnnotation??o;return n.slice(c.start,c.end)}function l(o){if(!o||o.length===0)return[];return o.map((c)=>{let a=c.expression;if(!a)return{name:"unknown"};if(a.type==="CallExpression"){let g=a.callee?.name??a.callee?.property?.name??"unknown",y=(a.arguments??[]).map((p)=>n.slice(p.start,p.end));return{name:g,arguments:y.length>0?y:void 0}}if(a.type==="Identifier")return{name:a.name??"unknown"};return{name:n.slice(a.start,a.end)}})}function h(o){let c=o.type==="TSParameterProperty"?o.parameter:o;if(c?.type==="RestElement"){let E=`...${c.argument?.name??"unknown"}`,z=c.typeAnnotation,B=z?m(z):void 0,F={name:E,isOptional:!1};if(B)F.type=B;return F}if(c?.type==="AssignmentPattern"){let{left:w,right:E}=c,z=w?.name??"unknown",B=w?.typeAnnotation,F=B?m(B):void 0,K=n.slice(E.start,E.end),j=l(w?.decorators??[]),M={name:z,isOptional:!0,defaultValue:K};if(F)M.type=F;if(j.length>0)M.decorators=j;return M}let a=c?.name??c?.pattern?.name??"unknown",g=!!c?.optional,y=c?.typeAnnotation,p=y?m(y):void 0,S=l(c?.decorators??[]),k={name:a,isOptional:g};if(p)k.type=p;if(S.length>0)k.decorators=S;return k}function f(o,c){let a=[];if(c?.async)a.push("async");if(o.static)a.push("static");if(o.abstract)a.push("abstract");if(o.readonly)a.push("readonly");if(o.override)a.push("override");if(o.declare)a.push("declare");if(o.const)a.push("const");let g=o.accessibility;if(g==="private")a.push("private");else if(g==="protected")a.push("protected");else if(g==="public")a.push("public");return a}function x(o){let c=[];if(o.superClass){let g=n.slice(o.superClass.start,o.superClass.end);c.push({kind:"extends",name:g})}let a=o.implements??[];for(let g of a){let y=g.expression??g,p=n.slice(y.start,y.end);c.push({kind:"implements",name:p})}return c}function P(o){let c=[];for(let a of o.extends??[]){let g=a.expression??a,y=n.slice(g.start,g.end);c.push({kind:"extends",name:y})}return c}function N(o){let c=[];for(let a of o)if(a.type==="MethodDefinition"){let g=a.key?.name??"unknown",y=a.value,p=a.kind??"method",S=p==="constructor"?"constructor":p==="get"?"getter":p==="set"?"setter":"method",k=f(a,y),w=(y?.params??[]).map(h),E=m(y?.returnType),z={kind:"method",name:g,span:s(a.start,a.end),isExported:!1,methodKind:S,modifiers:k,parameters:w.length>0?w:void 0,returnType:E};c.push(z)}else if(a.type==="PropertyDefinition"){let g=a.key?.name??"unknown",y=f(a),p={kind:"property",name:g,span:s(a.start,a.end),isExported:!1,modifiers:y};c.push(p)}return c}function C(o){let c=[];for(let a of o)if(a.type==="TSMethodSignature"){let g=a.key?.name??"unknown",y=(a.params??[]).map(h),p=m(a.returnType);c.push({kind:"method",name:g,span:s(a.start,a.end),isExported:!1,modifiers:[],methodKind:"method",parameters:y.length>0?y:void 0,returnType:p})}else if(a.type==="TSPropertySignature"){let g=a.key?.name??"unknown",y=m(a.typeAnnotation),p={kind:"property",name:g,span:s(a.start,a.end),isExported:!1,modifiers:a.readonly?["readonly"]:[],returnType:y};c.push(p)}return c}function v(o,c){let a=o.type??"";if(a==="FunctionDeclaration"){let g=o.id?.name??"default",y=(o.params??[]).map(h),p=m(o.returnType),S=f(o,o),k=l(o.decorators??[]),w=o.typeParameters?.params?.map((z)=>z.name?.name).filter(Boolean)||void 0,E={kind:"function",name:g,span:s(o.start,o.end),isExported:c,modifiers:S,parameters:y.length>0?y:void 0,returnType:p,decorators:k.length>0?k:void 0};if(w&&w.length>0)E.typeParameters=w;return E}if(a==="ClassDeclaration"||a==="ClassExpression"){let g=o.id?.name??"default",y=x(o),p=N(o.body?.body??[]),S=l(o.decorators??[]),k=f(o,o),w=o.typeParameters?.params?.map((z)=>z.name?.name).filter(Boolean)||void 0,E={kind:"class",name:g,span:s(o.start,o.end),isExported:c,modifiers:k,heritage:y.length>0?y:void 0,members:p.length>0?p:void 0,decorators:S.length>0?S:void 0};if(w&&w.length>0)E.typeParameters=w;return E}if(a==="VariableDeclaration"){let g=[];for(let y of o.declarations??[]){let{id:p,init:S}=y;if(p?.type==="ObjectPattern"){for(let F of p.properties??[]){let K=F.value?.name??F.key?.name??"unknown";g.push({kind:"variable",name:K,span:s(F.start??y.start,F.end??y.end),isExported:c,modifiers:[]})}continue}if(p?.type==="ArrayPattern"){for(let F of p.elements??[]){if(!F||F.type!=="Identifier")continue;let K=F.name??"unknown";g.push({kind:"variable",name:K,span:s(F.start??y.start,F.end??y.end),isExported:c,modifiers:[]})}continue}let k=p?.name??"unknown",w="variable",E,z;if(S?.type==="FunctionExpression"||S?.type==="ArrowFunctionExpression")w="function",E=(S.params??[]).map(h),z=m(S.returnType);let B=[];g.push({kind:w,name:k,span:s(y.start,y.end),isExported:c,modifiers:B,parameters:E,returnType:z})}if(g.length===0)return null;if(g.length===1)return g[0];return g}if(a==="TSTypeAliasDeclaration")return{kind:"type",name:o.id?.name??"unknown",span:s(o.start,o.end),isExported:c,modifiers:[]};if(a==="TSInterfaceDeclaration"){let g=o.id?.name??"unknown",y=P(o),p=C(o.body?.body??[]),S=o.typeParameters?.params?.map((w)=>w.name?.name).filter(Boolean)||void 0,k={kind:"interface",name:g,span:s(o.start,o.end),isExported:c,modifiers:[],heritage:y.length>0?y:void 0,members:p.length>0?p:void 0};if(S&&S.length>0)k.typeParameters=S;return k}if(a==="TSEnumDeclaration"){let g=o.id?.name??"unknown",y=f(o),S=(o.body?.members??[]).map((k)=>({kind:"property",name:k.id?.name??k.id?.value??"unknown",span:s(k.start,k.end),isExported:!1,modifiers:[]}));return{kind:"enum",name:g,span:s(o.start,o.end),isExported:c,modifiers:y,members:S.length>0?S:void 0}}return null}let b=[];for(let o of t.body){let c=null,a=o,g=typeof a.type==="string"?a.type:"";if(g==="ExportNamedDeclaration"){let p=o;if(p.declaration){if(c=v(p.declaration,!0),c&&!Array.isArray(c))c.span=s(p.start,p.end);else if(Array.isArray(c))for(let S of c)S.span=s(p.start,p.end)}}else if(g==="ExportDefaultDeclaration"){let p=o,S=p.declaration;if(S){if(c=v(S,!0),c&&!Array.isArray(c))c.name=S.id?.name??"default",c.isExported=!0,c.span=s(p.start,p.end)}}else c=v(o,!1);let y=Array.isArray(c)?c:c?[c]:[];for(let p of y){let S=o.start??0,k=u(S);if(k){let w=Le(k);if(!gt(w))p.jsDoc=w}b.push(p)}}return b}import{resolve as xe,dirname as ht,extname as yt}from"path";function Se(e,t,n){let r=(i)=>{let s=yt(i);if(s==="")return[i+".ts",i+"/index.ts",i+".mts",i+"/index.mts",i+".cts",i+"/index.cts"];if(s===".js")return[i.slice(0,-3)+".ts"];if(s===".mjs")return[i.slice(0,-4)+".mts"];if(s===".cjs")return[i.slice(0,-4)+".cts"];return[i]};if(t.startsWith(".")){let i=xe(ht(e),t);return r(i)}if(n)for(let[i,s]of n.paths){if(s.length===0)continue;let u=i.indexOf("*");if(u===-1){if(t===i){let m=[];for(let l of s)m.push(...r(xe(n.baseUrl,l)));return m}}else{let m=i.slice(0,u),l=i.slice(u+1);if(t.startsWith(m)&&(l===""||t.endsWith(l))){let h=t.slice(m.length,l===""?void 0:t.length-l.length),f=[];for(let x of s)f.push(...r(xe(n.baseUrl,x.replace("*",h))));return f}}}return[]}function Je(e,t,n,r=Se){let i=new Map,s=e.body??[];for(let u of s){if(u.type!=="ImportDeclaration")continue;let m=u.source?.value??"",l=r(t,m,n);if(l.length===0)continue;let h=l[0],f=u.specifiers??[];for(let x of f)switch(x.type){case"ImportSpecifier":i.set(x.local.name,{path:h,importedName:x.imported.name});break;case"ImportDefaultSpecifier":i.set(x.local.name,{path:h,importedName:"default"});break;case"ImportNamespaceSpecifier":i.set(x.local.name,{path:h,importedName:"*"});break}}return i}var bt=new Set(["loc","start","end","scope"]);function Z(e,t){if(!e||typeof e!=="object")return;if(Array.isArray(e)){for(let r of e)Z(r,t);return}let n=e;t(n);for(let r of Object.keys(n)){if(bt.has(r))continue;let i=n[r];if(i&&typeof i==="object")Z(i,t)}}function Be(e){if(!e||typeof e!=="object"||Array.isArray(e))return null;let t=e;if((t.type==="StringLiteral"||t.type==="Literal")&&typeof t.value==="string")return t.value;return null}function Q(e){if(!e||typeof e!=="object"||Array.isArray(e))return null;let t=e;if(t.type==="Identifier"){let n=t.name;return{root:n,parts:[],full:n}}if(t.type==="ThisExpression")return{root:"this",parts:[],full:"this"};if(t.type==="Super")return{root:"super",parts:[],full:"super"};if(t.type==="MemberExpression"){let n=[],r=t;while(r.type==="MemberExpression"){let u=r.property;if(!u||typeof u.name!=="string")return null;n.unshift(u.name),r=r.object}let i;if(r.type==="Identifier")i=r.name;else if(r.type==="ThisExpression")i="this";else if(r.type==="Super")i="super";else return null;let s=[i,...n].join(".");return{root:i,parts:n,full:s}}return null}function He(e,t,n,r=Se){let i=[],s=e.body??[];for(let u of s){if(u.type==="ImportDeclaration"){let m=u.source?.value??"",l=r(t,m,n);if(l.length===0)continue;let h=l[0],f=u.importKind==="type";i.push({type:"imports",srcFilePath:t,srcSymbolName:null,dstFilePath:h,dstSymbolName:null,...f?{metaJson:JSON.stringify({isType:!0})}:{}});continue}if(u.type==="ExportAllDeclaration"&&u.source){let m=u.source?.value??"",l=r(t,m,n);if(l.length===0)continue;let h=l[0],f=u.exportKind==="type",x={isReExport:!0};if(f)x.isType=!0;i.push({type:"imports",srcFilePath:t,srcSymbolName:null,dstFilePath:h,dstSymbolName:null,metaJson:JSON.stringify(x)});continue}if(u.type==="ExportNamedDeclaration"&&u.source){let m=u.source?.value??"",l=r(t,m,n);if(l.length===0)continue;let h=l[0];i.push({type:"imports",srcFilePath:t,srcSymbolName:null,dstFilePath:h,dstSymbolName:null,metaJson:JSON.stringify({isReExport:!0})})}}return Z(e,(u)=>{if(u.type!=="ImportExpression")return;let m=Be(u.source);if(!m)return;let l=r(t,m,n);if(l.length===0)return;let h=l[0];i.push({type:"imports",srcFilePath:t,srcSymbolName:null,dstFilePath:h,dstSymbolName:null,metaJson:JSON.stringify({isDynamic:!0})})}),i}function $e(e,t,n){let r=[],i=[],s=[];function u(){if(i.length>0)return i[i.length-1]??null;return null}function m(h){if(!h)return null;let f=n.get(h.root);if(h.parts.length===0){if(f)return{dstFilePath:f.path,dstSymbolName:f.importedName,resolution:"import"};return{dstFilePath:t,dstSymbolName:h.root,resolution:"local"}}else{if(f&&f.importedName==="*"){let x=h.parts[h.parts.length-1];return{dstFilePath:f.path,dstSymbolName:x,resolution:"namespace"}}return{dstFilePath:t,dstSymbolName:h.full,resolution:"local-member"}}}function l(h){if(!h||typeof h!=="object")return;if(Array.isArray(h)){for(let P of h)l(P);return}let f=h,x=typeof f.type==="string"?f.type:"";if(x==="ClassDeclaration"||x==="ClassExpression"){let P=f,N=P.id?.name??"AnonymousClass";s.push(N),l(P.body),s.pop();return}if(x==="FunctionDeclaration"){let P=f,N=P.id?.name??"anonymous";i.push(N),l(P.body),i.pop();return}if(x==="VariableDeclarator"&&f.init&&(f.init?.type==="FunctionExpression"||f.init?.type==="ArrowFunctionExpression")){let P=f,N=P.id?.name??"anonymous";i.push(N),l(P.init?.body??P.init),i.pop();return}if(x==="MethodDefinition"&&f.value){let P=f,N=s[s.length-1]??"",C=P.key?.name??"anonymous",v=N?`${N}.${C}`:C;i.push(v),l(P.value?.body),i.pop();return}if(x==="FunctionExpression"||x==="ArrowFunctionExpression"){let P=u(),N=P?`${P}.<anonymous>`:"<anonymous>";i.push(N),l(f.body),i.pop();return}if(x==="CallExpression"){let P=f,N=Q(P.callee),C=m(N);if(C){let v=u(),b={};if(v===null)b.scope="module";r.push({type:"calls",srcFilePath:t,srcSymbolName:v,dstFilePath:C.dstFilePath,dstSymbolName:C.dstSymbolName,...Object.keys(b).length>0?{metaJson:JSON.stringify(b)}:{}})}l(P.callee);for(let v of P.arguments??[])l(v);return}if(x==="NewExpression"){let P=f,N=Q(P.callee),C=m(N);if(C){let v=u(),b={isNew:!0};if(v===null)b.scope="module";r.push({type:"calls",srcFilePath:t,srcSymbolName:v,dstFilePath:C.dstFilePath,dstSymbolName:C.dstSymbolName,metaJson:JSON.stringify(b)})}for(let v of P.arguments??[])l(v);return}for(let P of Object.keys(f)){if(P==="loc"||P==="start"||P==="end"||P==="scope")continue;let N=f[P];if(N&&typeof N==="object")l(N)}}return l(e),r}function Ge(e,t,n){let r=[];return Z(e,(i)=>{if(i.type==="TSInterfaceDeclaration"){let m=i.id?.name??"AnonymousInterface",l=i.extends??[];for(let h of l){let f=h.expression??h,x=Q(f);if(!x)continue;let P=Pe(x,t,n);r.push({type:"extends",srcFilePath:t,srcSymbolName:m,...P})}return}if(i.type!=="ClassDeclaration"&&i.type!=="ClassExpression")return;let s=i.id?.name??"AnonymousClass";if(i.superClass){let m=Q(i.superClass);if(m){let l=Pe(m,t,n);r.push({type:"extends",srcFilePath:t,srcSymbolName:s,...l})}}let u=i.implements??[];for(let m of u){let l=m.expression??m,h=Q(l);if(!h)continue;let f=Pe(h,t,n);r.push({type:"implements",srcFilePath:t,srcSymbolName:s,...f})}}),r}function Pe(e,t,n){let r=n.get(e.root);if(r){if(r.importedName==="*"){let i=e.parts[e.parts.length-1]??e.root;return{dstFilePath:r.path,dstSymbolName:i,metaJson:JSON.stringify({isNamespaceImport:!0})}}return{dstFilePath:r.path,dstSymbolName:e.parts.length>0?e.full:r.importedName}}return{dstFilePath:t,dstSymbolName:e.full,metaJson:JSON.stringify({isLocal:!0})}}function le(e,t,n){let r=Je(e,t,n),i=He(e,t,n),s=$e(e,t,r),u=Ge(e,t,r);return[...i,...s,...u]}import{err as We,isErr as Rt}from"@zipbul/result";import{Database as Nt}from"bun:sqlite";import{mkdirSync as kt,unlinkSync as Ke,existsSync as Ue}from"fs";import{dirname as Ot,join as Ve}from"path";import{drizzle as vt}from"drizzle-orm/bun-sqlite";import{migrate as Ct}from"drizzle-orm/bun-sqlite/migrator";var Re={};mt(Re,{watcherOwner:()=>Ft,symbols:()=>O,relations:()=>d,files:()=>I,FTS_SETUP_SQL:()=>Fe});import{sql as xt}from"drizzle-orm";import{sqliteTable as ce,text as D,integer as J,real as St,index as X,primaryKey as Pt,foreignKey as we,check as wt}from"drizzle-orm/sqlite-core";var I=ce("files",{project:D("project").notNull(),filePath:D("file_path").notNull(),mtimeMs:St("mtime_ms").notNull(),size:J("size").notNull(),contentHash:D("content_hash").notNull(),updatedAt:D("updated_at").notNull()},(e)=>[Pt({columns:[e.project,e.filePath]})]),O=ce("symbols",{id:J("id").primaryKey({autoIncrement:!0}),project:D("project").notNull(),filePath:D("file_path").notNull(),kind:D("kind").notNull(),name:D("name").notNull(),startLine:J("start_line").notNull(),startColumn:J("start_column").notNull(),endLine:J("end_line").notNull(),endColumn:J("end_column").notNull(),isExported:J("is_exported").notNull().default(0),signature:D("signature"),fingerprint:D("fingerprint"),detailJson:D("detail_json"),contentHash:D("content_hash").notNull(),indexedAt:D("indexed_at").notNull()},(e)=>[X("idx_symbols_project_file").on(e.project,e.filePath),X("idx_symbols_project_kind").on(e.project,e.kind),X("idx_symbols_project_name").on(e.project,e.name),X("idx_symbols_fingerprint").on(e.project,e.fingerprint),we({columns:[e.project,e.filePath],foreignColumns:[I.project,I.filePath]}).onDelete("cascade")]),d=ce("relations",{id:J("id").primaryKey({autoIncrement:!0}),project:D("project").notNull(),type:D("type").notNull(),srcFilePath:D("src_file_path").notNull(),srcSymbolName:D("src_symbol_name"),dstFilePath:D("dst_file_path").notNull(),dstSymbolName:D("dst_symbol_name"),metaJson:D("meta_json")},(e)=>[X("idx_relations_src").on(e.project,e.srcFilePath),X("idx_relations_dst").on(e.project,e.dstFilePath),X("idx_relations_type").on(e.project,e.type),we({columns:[e.project,e.srcFilePath],foreignColumns:[I.project,I.filePath]}).onDelete("cascade"),we({columns:[e.project,e.dstFilePath],foreignColumns:[I.project,I.filePath]}).onDelete("cascade")]),Ft=ce("watcher_owner",{id:J("id").primaryKey(),pid:J("pid").notNull(),startedAt:D("started_at").notNull(),heartbeatAt:D("heartbeat_at").notNull()},(e)=>[wt("watcher_owner_singleton",xt`${e.id} = 1`)]),Fe=[`CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(
1167
4
  name,
1168
5
  file_path,
1169
6
  kind,
1170
7
  content=symbols,
1171
8
  content_rowid=id
1172
- )`,
1173
- `CREATE TRIGGER IF NOT EXISTS symbols_ai
9
+ )`,`CREATE TRIGGER IF NOT EXISTS symbols_ai
1174
10
  AFTER INSERT ON symbols BEGIN
1175
11
  INSERT INTO symbols_fts(rowid, name, file_path, kind)
1176
12
  VALUES (new.id, new.name, new.file_path, new.kind);
1177
- END`,
1178
- `CREATE TRIGGER IF NOT EXISTS symbols_ad
13
+ END`,`CREATE TRIGGER IF NOT EXISTS symbols_ad
1179
14
  AFTER DELETE ON symbols BEGIN
1180
15
  INSERT INTO symbols_fts(symbols_fts, rowid, name, file_path, kind)
1181
16
  VALUES ('delete', old.id, old.name, old.file_path, old.kind);
1182
- END`,
1183
- `CREATE TRIGGER IF NOT EXISTS symbols_au
17
+ END`,`CREATE TRIGGER IF NOT EXISTS symbols_au
1184
18
  AFTER UPDATE ON symbols BEGIN
1185
19
  INSERT INTO symbols_fts(symbols_fts, rowid, name, file_path, kind)
1186
20
  VALUES ('delete', old.id, old.name, old.file_path, old.kind);
1187
21
  INSERT INTO symbols_fts(rowid, name, file_path, kind)
1188
22
  VALUES (new.id, new.name, new.file_path, new.kind);
1189
- END`
1190
- ];
1191
-
1192
- // src/store/connection.ts
1193
- class DbConnection {
1194
- client = null;
1195
- drizzle = null;
1196
- dbPath;
1197
- txDepth = 0;
1198
- constructor(opts) {
1199
- this.dbPath = join(opts.projectRoot, ".zipbul", "gildash.db");
1200
- }
1201
- get drizzleDb() {
1202
- if (!this.drizzle)
1203
- throw new StoreError("Database is not open. Call open() first.");
1204
- return this.drizzle;
1205
- }
1206
- open() {
1207
- try {
1208
- mkdirSync(dirname2(this.dbPath), { recursive: true });
1209
- this.client = new Database(this.dbPath);
1210
- this.client.run("PRAGMA journal_mode = WAL");
1211
- this.client.run("PRAGMA foreign_keys = ON");
1212
- this.client.run("PRAGMA busy_timeout = 5000");
1213
- this.drizzle = drizzle(this.client, { schema: exports_schema });
1214
- migrate(this.drizzle, {
1215
- migrationsFolder: join(import.meta.dirname, "migrations")
1216
- });
1217
- for (const sql2 of FTS_SETUP_SQL) {
1218
- this.client.run(sql2);
1219
- }
1220
- } catch (err) {
1221
- if (this.isCorruptionError(err) && existsSync(this.dbPath)) {
1222
- this.closeClient();
1223
- unlinkSync(this.dbPath);
1224
- for (const ext of ["-wal", "-shm"]) {
1225
- const p = this.dbPath + ext;
1226
- if (existsSync(p))
1227
- unlinkSync(p);
1228
- }
1229
- try {
1230
- this.open();
1231
- return;
1232
- } catch (retryErr) {
1233
- throw new StoreError(`Failed to recover database at ${this.dbPath}`, { cause: retryErr });
1234
- }
1235
- }
1236
- if (err instanceof StoreError)
1237
- throw err;
1238
- throw new StoreError(`Failed to open database at ${this.dbPath}`, { cause: err });
1239
- }
1240
- }
1241
- close() {
1242
- this.closeClient();
1243
- this.drizzle = null;
1244
- }
1245
- transaction(fn) {
1246
- const db = this.requireClient();
1247
- if (this.txDepth === 0) {
1248
- this.txDepth++;
1249
- try {
1250
- return db.transaction(() => fn(this))();
1251
- } finally {
1252
- this.txDepth--;
1253
- }
1254
- }
1255
- const sp = `sp_${this.txDepth++}`;
1256
- db.run(`SAVEPOINT "${sp}"`);
1257
- try {
1258
- const result = fn(this);
1259
- db.run(`RELEASE SAVEPOINT "${sp}"`);
1260
- return result;
1261
- } catch (err) {
1262
- db.run(`ROLLBACK TO SAVEPOINT "${sp}"`);
1263
- db.run(`RELEASE SAVEPOINT "${sp}"`);
1264
- throw err;
1265
- } finally {
1266
- this.txDepth--;
1267
- }
1268
- }
1269
- immediateTransaction(fn) {
1270
- const db = this.requireClient();
1271
- this.txDepth++;
1272
- db.run("BEGIN IMMEDIATE");
1273
- try {
1274
- const result = fn();
1275
- db.run("COMMIT");
1276
- return result;
1277
- } catch (err) {
1278
- db.run("ROLLBACK");
1279
- throw err;
1280
- } finally {
1281
- this.txDepth--;
1282
- }
1283
- }
1284
- query(sql2) {
1285
- const row = this.requireClient().prepare(sql2).get();
1286
- if (!row)
1287
- return null;
1288
- return Object.values(row)[0];
1289
- }
1290
- getTableNames() {
1291
- const rows = this.requireClient().query("SELECT name FROM sqlite_master WHERE type = 'table'").all();
1292
- return rows.map((r) => r.name);
1293
- }
1294
- selectOwner() {
1295
- const row = this.requireClient().prepare("SELECT pid, heartbeat_at FROM watcher_owner WHERE id = 1").get();
1296
- return row ?? undefined;
1297
- }
1298
- insertOwner(pid) {
1299
- const now = new Date().toISOString();
1300
- this.requireClient().prepare("INSERT INTO watcher_owner (id, pid, started_at, heartbeat_at) VALUES (1, ?, ?, ?)").run(pid, now, now);
1301
- }
1302
- replaceOwner(pid) {
1303
- const now = new Date().toISOString();
1304
- this.requireClient().prepare("INSERT OR REPLACE INTO watcher_owner (id, pid, started_at, heartbeat_at) VALUES (1, ?, ?, ?)").run(pid, now, now);
1305
- }
1306
- touchOwner(pid) {
1307
- const now = new Date().toISOString();
1308
- this.requireClient().prepare("UPDATE watcher_owner SET heartbeat_at = ? WHERE id = 1 AND pid = ?").run(now, pid);
1309
- }
1310
- deleteOwner(pid) {
1311
- this.requireClient().prepare("DELETE FROM watcher_owner WHERE id = 1 AND pid = ?").run(pid);
1312
- }
1313
- requireClient() {
1314
- if (!this.client)
1315
- throw new StoreError("Database is not open. Call open() first.");
1316
- return this.client;
1317
- }
1318
- closeClient() {
1319
- if (this.client) {
1320
- this.client.close();
1321
- this.client = null;
1322
- }
1323
- }
1324
- isCorruptionError(err) {
1325
- if (!(err instanceof Error))
1326
- return false;
1327
- const msg = err.message.toLowerCase();
1328
- return msg.includes("malformed") || msg.includes("corrupt") || msg.includes("not a database") || msg.includes("disk i/o error") || msg.includes("sqlite_corrupt");
1329
- }
1330
- }
1331
-
1332
- // src/store/repositories/file.repository.ts
1333
- import { eq, and } from "drizzle-orm";
1334
- class FileRepository {
1335
- db;
1336
- constructor(db) {
1337
- this.db = db;
1338
- }
1339
- getFile(project, filePath) {
1340
- return this.db.drizzleDb.select().from(files).where(and(eq(files.project, project), eq(files.filePath, filePath))).get() ?? null;
1341
- }
1342
- upsertFile(record) {
1343
- this.db.drizzleDb.insert(files).values({
1344
- project: record.project,
1345
- filePath: record.filePath,
1346
- mtimeMs: record.mtimeMs,
1347
- size: record.size,
1348
- contentHash: record.contentHash,
1349
- updatedAt: record.updatedAt
1350
- }).onConflictDoUpdate({
1351
- target: [files.project, files.filePath],
1352
- set: {
1353
- mtimeMs: record.mtimeMs,
1354
- size: record.size,
1355
- contentHash: record.contentHash,
1356
- updatedAt: record.updatedAt
1357
- }
1358
- }).run();
1359
- }
1360
- getAllFiles(project) {
1361
- return this.db.drizzleDb.select().from(files).where(eq(files.project, project)).all();
1362
- }
1363
- getFilesMap(project) {
1364
- const rows = this.getAllFiles(project);
1365
- const map = new Map;
1366
- for (const r of rows)
1367
- map.set(r.filePath, r);
1368
- return map;
1369
- }
1370
- deleteFile(project, filePath) {
1371
- this.db.drizzleDb.delete(files).where(and(eq(files.project, project), eq(files.filePath, filePath))).run();
1372
- }
1373
- }
1374
-
1375
- // src/store/repositories/symbol.repository.ts
1376
- import { eq as eq2, and as and2, sql as sql2, count } from "drizzle-orm";
1377
-
1378
- // src/store/repositories/fts-utils.ts
1379
- function toFtsPrefixQuery(text2) {
1380
- return text2.trim().split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0).map((token) => `"${token.replaceAll('"', '""')}"*`).join(" ");
1381
- }
1382
-
1383
- // src/store/repositories/symbol.repository.ts
1384
- class SymbolRepository {
1385
- db;
1386
- constructor(db) {
1387
- this.db = db;
1388
- }
1389
- replaceFileSymbols(project, filePath, contentHash, syms) {
1390
- this.db.drizzleDb.delete(symbols).where(and2(eq2(symbols.project, project), eq2(symbols.filePath, filePath))).run();
1391
- if (!syms.length)
1392
- return;
1393
- const now = new Date().toISOString();
1394
- for (const sym of syms) {
1395
- this.db.drizzleDb.insert(symbols).values({
1396
- project,
1397
- filePath,
1398
- kind: sym.kind ?? "unknown",
1399
- name: sym.name ?? "",
1400
- startLine: sym.startLine ?? 0,
1401
- startColumn: sym.startColumn ?? 0,
1402
- endLine: sym.endLine ?? 0,
1403
- endColumn: sym.endColumn ?? 0,
1404
- isExported: sym.isExported ?? 0,
1405
- signature: sym.signature ?? null,
1406
- fingerprint: sym.fingerprint ?? null,
1407
- detailJson: sym.detailJson ?? null,
1408
- contentHash,
1409
- indexedAt: sym.indexedAt ?? now
1410
- }).run();
1411
- }
1412
- }
1413
- getFileSymbols(project, filePath) {
1414
- return this.db.drizzleDb.select().from(symbols).where(and2(eq2(symbols.project, project), eq2(symbols.filePath, filePath))).all();
1415
- }
1416
- searchByName(project, query, opts = {}) {
1417
- const limit = opts.limit ?? 50;
1418
- const ftsQuery = toFtsPrefixQuery(query);
1419
- if (!ftsQuery)
1420
- return [];
1421
- let builder = this.db.drizzleDb.select().from(symbols).where(and2(sql2`${symbols.id} IN (SELECT rowid FROM symbols_fts WHERE symbols_fts MATCH ${ftsQuery})`, eq2(symbols.project, project), opts.kind ? eq2(symbols.kind, opts.kind) : undefined)).orderBy(symbols.name).limit(limit);
1422
- return builder.all();
1423
- }
1424
- searchByKind(project, kind) {
1425
- return this.db.drizzleDb.select().from(symbols).where(and2(eq2(symbols.project, project), eq2(symbols.kind, kind))).orderBy(symbols.name).all();
1426
- }
1427
- getStats(project) {
1428
- const row = this.db.drizzleDb.select({
1429
- symbolCount: count(),
1430
- fileCount: sql2`COUNT(DISTINCT ${symbols.filePath})`
1431
- }).from(symbols).where(eq2(symbols.project, project)).get();
1432
- return {
1433
- symbolCount: row?.symbolCount ?? 0,
1434
- fileCount: row?.fileCount ?? 0
1435
- };
1436
- }
1437
- getByFingerprint(project, fingerprint) {
1438
- return this.db.drizzleDb.select().from(symbols).where(and2(eq2(symbols.project, project), eq2(symbols.fingerprint, fingerprint))).all();
1439
- }
1440
- deleteFileSymbols(project, filePath) {
1441
- this.db.drizzleDb.delete(symbols).where(and2(eq2(symbols.project, project), eq2(symbols.filePath, filePath))).run();
1442
- }
1443
- searchByQuery(opts) {
1444
- return this.db.drizzleDb.select().from(symbols).where(and2(opts.ftsQuery ? sql2`${symbols.id} IN (SELECT rowid FROM symbols_fts WHERE symbols_fts MATCH ${opts.ftsQuery})` : undefined, opts.project !== undefined ? eq2(symbols.project, opts.project) : undefined, opts.kind ? eq2(symbols.kind, opts.kind) : undefined, opts.filePath !== undefined ? eq2(symbols.filePath, opts.filePath) : undefined, opts.isExported !== undefined ? eq2(symbols.isExported, opts.isExported ? 1 : 0) : undefined)).orderBy(symbols.name).limit(opts.limit).all();
1445
- }
1446
- }
1447
-
1448
- // src/store/repositories/relation.repository.ts
1449
- import { eq as eq3, and as and3, isNull, or } from "drizzle-orm";
1450
- class RelationRepository {
1451
- db;
1452
- constructor(db) {
1453
- this.db = db;
1454
- }
1455
- replaceFileRelations(project, srcFilePath, rels) {
1456
- this.db.drizzleDb.delete(relations).where(and3(eq3(relations.project, project), eq3(relations.srcFilePath, srcFilePath))).run();
1457
- if (!rels.length)
1458
- return;
1459
- for (const rel of rels) {
1460
- this.db.drizzleDb.insert(relations).values({
1461
- project,
1462
- type: rel.type ?? "unknown",
1463
- srcFilePath: rel.srcFilePath ?? srcFilePath,
1464
- srcSymbolName: rel.srcSymbolName ?? null,
1465
- dstFilePath: rel.dstFilePath ?? "",
1466
- dstSymbolName: rel.dstSymbolName ?? null,
1467
- metaJson: rel.metaJson ?? null
1468
- }).run();
1469
- }
1470
- }
1471
- getOutgoing(project, srcFilePath, srcSymbolName) {
1472
- if (srcSymbolName !== undefined) {
1473
- return this.db.drizzleDb.select({
1474
- project: relations.project,
1475
- type: relations.type,
1476
- srcFilePath: relations.srcFilePath,
1477
- srcSymbolName: relations.srcSymbolName,
1478
- dstFilePath: relations.dstFilePath,
1479
- dstSymbolName: relations.dstSymbolName,
1480
- metaJson: relations.metaJson
1481
- }).from(relations).where(and3(eq3(relations.project, project), eq3(relations.srcFilePath, srcFilePath), or(eq3(relations.srcSymbolName, srcSymbolName), isNull(relations.srcSymbolName)))).all();
1482
- }
1483
- return this.db.drizzleDb.select({
1484
- project: relations.project,
1485
- type: relations.type,
1486
- srcFilePath: relations.srcFilePath,
1487
- srcSymbolName: relations.srcSymbolName,
1488
- dstFilePath: relations.dstFilePath,
1489
- dstSymbolName: relations.dstSymbolName,
1490
- metaJson: relations.metaJson
1491
- }).from(relations).where(and3(eq3(relations.project, project), eq3(relations.srcFilePath, srcFilePath))).all();
1492
- }
1493
- getIncoming(project, dstFilePath) {
1494
- return this.db.drizzleDb.select({
1495
- project: relations.project,
1496
- type: relations.type,
1497
- srcFilePath: relations.srcFilePath,
1498
- srcSymbolName: relations.srcSymbolName,
1499
- dstFilePath: relations.dstFilePath,
1500
- dstSymbolName: relations.dstSymbolName,
1501
- metaJson: relations.metaJson
1502
- }).from(relations).where(and3(eq3(relations.project, project), eq3(relations.dstFilePath, dstFilePath))).all();
1503
- }
1504
- getByType(project, type) {
1505
- return this.db.drizzleDb.select({
1506
- project: relations.project,
1507
- type: relations.type,
1508
- srcFilePath: relations.srcFilePath,
1509
- srcSymbolName: relations.srcSymbolName,
1510
- dstFilePath: relations.dstFilePath,
1511
- dstSymbolName: relations.dstSymbolName,
1512
- metaJson: relations.metaJson
1513
- }).from(relations).where(and3(eq3(relations.project, project), eq3(relations.type, type))).all();
1514
- }
1515
- deleteFileRelations(project, srcFilePath) {
1516
- this.db.drizzleDb.delete(relations).where(and3(eq3(relations.project, project), eq3(relations.srcFilePath, srcFilePath))).run();
1517
- }
1518
- searchRelations(opts) {
1519
- return this.db.drizzleDb.select({
1520
- project: relations.project,
1521
- type: relations.type,
1522
- srcFilePath: relations.srcFilePath,
1523
- srcSymbolName: relations.srcSymbolName,
1524
- dstFilePath: relations.dstFilePath,
1525
- dstSymbolName: relations.dstSymbolName,
1526
- metaJson: relations.metaJson
1527
- }).from(relations).where(and3(opts.project !== undefined ? eq3(relations.project, opts.project) : undefined, opts.srcFilePath !== undefined ? eq3(relations.srcFilePath, opts.srcFilePath) : undefined, opts.srcSymbolName !== undefined ? eq3(relations.srcSymbolName, opts.srcSymbolName) : undefined, opts.dstFilePath !== undefined ? eq3(relations.dstFilePath, opts.dstFilePath) : undefined, opts.dstSymbolName !== undefined ? eq3(relations.dstSymbolName, opts.dstSymbolName) : undefined, opts.type !== undefined ? eq3(relations.type, opts.type) : undefined)).limit(opts.limit).all();
1528
- }
1529
- retargetRelations(project, oldFile, oldSymbol, newFile, newSymbol) {
1530
- const condition = oldSymbol === null ? and3(eq3(relations.project, project), eq3(relations.dstFilePath, oldFile), isNull(relations.dstSymbolName)) : and3(eq3(relations.project, project), eq3(relations.dstFilePath, oldFile), eq3(relations.dstSymbolName, oldSymbol));
1531
- this.db.drizzleDb.update(relations).set({ dstFilePath: newFile, dstSymbolName: newSymbol }).where(condition).run();
1532
- }
1533
- }
1534
-
1535
- // src/watcher/project-watcher.ts
1536
- import { subscribe as parcelSubscribe } from "@parcel/watcher";
1537
- import path from "path";
1538
- var WATCHER_IGNORE_GLOBS = [
1539
- "**/.git/**",
1540
- "**/.zipbul/**",
1541
- "**/dist/**",
1542
- "**/node_modules/**"
1543
- ];
1544
- var CONFIG_FILE_NAMES = new Set(["package.json", "tsconfig.json"]);
1545
- function normalizePath(value) {
1546
- return value.replaceAll("\\", "/");
1547
- }
1548
- function mapEventType(type) {
1549
- if (type === "update") {
1550
- return "change";
1551
- }
1552
- if (type === "create") {
1553
- return "create";
1554
- }
1555
- return "delete";
1556
- }
1557
-
1558
- class ProjectWatcher {
1559
- #subscription;
1560
- #rootPath;
1561
- #ignoreGlobs;
1562
- #extensions;
1563
- #subscribe;
1564
- #logger;
1565
- constructor(options, subscribeFn = parcelSubscribe, logger = console) {
1566
- this.#rootPath = options.projectRoot;
1567
- this.#ignoreGlobs = [...WATCHER_IGNORE_GLOBS, ...options.ignorePatterns ?? []];
1568
- this.#extensions = new Set((options.extensions ?? [".ts", ".mts", ".cts"]).map((ext) => ext.toLowerCase()));
1569
- this.#subscribe = subscribeFn;
1570
- this.#logger = logger;
1571
- }
1572
- async start(onChange) {
1573
- try {
1574
- this.#subscription = await this.#subscribe(this.#rootPath, (error, events) => {
1575
- if (error) {
1576
- this.#logger.error(new WatcherError("Callback error", { cause: error }));
1577
- return;
1578
- }
1579
- try {
1580
- for (const rawEvent of events) {
1581
- const relativePath = normalizePath(path.relative(this.#rootPath, rawEvent.path));
1582
- if (relativePath.startsWith("..")) {
1583
- continue;
1584
- }
1585
- const baseName = path.basename(relativePath);
1586
- const extension = path.extname(relativePath).toLowerCase();
1587
- const isConfigFile = CONFIG_FILE_NAMES.has(baseName);
1588
- if (!isConfigFile && !this.#extensions.has(extension)) {
1589
- continue;
1590
- }
1591
- if (relativePath.endsWith(".d.ts")) {
1592
- continue;
1593
- }
1594
- onChange({
1595
- eventType: mapEventType(rawEvent.type),
1596
- filePath: relativePath
1597
- });
1598
- }
1599
- } catch (callbackError) {
1600
- this.#logger.error(new WatcherError("Callback error", { cause: callbackError }));
1601
- }
1602
- }, {
1603
- ignore: this.#ignoreGlobs
1604
- });
1605
- } catch (error) {
1606
- throw new WatcherError("Failed to subscribe watcher", { cause: error });
1607
- }
1608
- }
1609
- async close() {
1610
- if (!this.#subscription) {
1611
- return;
1612
- }
1613
- try {
1614
- await this.#subscription.unsubscribe();
1615
- this.#subscription = undefined;
1616
- } catch (error) {
1617
- throw new WatcherError("Failed to close watcher", { cause: error });
1618
- }
1619
- }
1620
- }
1621
-
1622
- // src/common/project-discovery.ts
1623
- import path2 from "path";
1624
- import { promises as fs } from "fs";
1625
- var DISCOVERY_EXCLUDE = ["**/node_modules/**", "**/.git/**", "**/.zipbul/**", "**/dist/**"];
1626
- async function discoverProjects(projectRoot) {
1627
- const boundaries = [];
1628
- for await (const relativePackageJson of fs.glob("**/package.json", {
1629
- cwd: projectRoot,
1630
- exclude: DISCOVERY_EXCLUDE
1631
- })) {
1632
- const packageDir = path2.dirname(relativePackageJson).replaceAll("\\", "/");
1633
- const packagePath = path2.join(projectRoot, relativePackageJson);
1634
- const content = await Bun.file(packagePath).json();
1635
- const packageName = typeof content?.name === "string" && content.name.length > 0 ? content.name : path2.basename(packageDir === "." ? projectRoot : packageDir);
1636
- boundaries.push({
1637
- dir: packageDir,
1638
- project: packageName
1639
- });
1640
- }
1641
- boundaries.sort((left, right) => right.dir.length - left.dir.length);
1642
- return boundaries;
1643
- }
1644
- function resolveFileProject(filePath, boundaries, rootProject = "default") {
1645
- const normalizedFilePath = filePath.replaceAll("\\", "/");
1646
- for (const boundary of boundaries) {
1647
- if (boundary.dir === ".") {
1648
- return boundary.project;
1649
- }
1650
- if (normalizedFilePath === boundary.dir || normalizedFilePath.startsWith(`${boundary.dir}/`)) {
1651
- return boundary.project;
1652
- }
1653
- }
1654
- return rootProject;
1655
- }
1656
-
1657
- // src/common/tsconfig-resolver.ts
1658
- import path3 from "path";
1659
- var cache = new Map;
1660
- async function readConfig(configPath) {
1661
- const file = Bun.file(configPath);
1662
- if (!await file.exists()) {
1663
- return null;
1664
- }
1665
- const parsed = await file.json();
1666
- return typeof parsed === "object" && parsed !== null ? parsed : null;
1667
- }
1668
- async function loadTsconfigPaths(projectRoot) {
1669
- if (cache.has(projectRoot)) {
1670
- return cache.get(projectRoot) ?? null;
1671
- }
1672
- const tsconfigPath = path3.join(projectRoot, "tsconfig.json");
1673
- const config = await readConfig(tsconfigPath);
1674
- if (!config) {
1675
- cache.set(projectRoot, null);
1676
- return null;
1677
- }
1678
- const compilerOptions = typeof config.compilerOptions === "object" && config.compilerOptions !== null ? config.compilerOptions : null;
1679
- if (!compilerOptions) {
1680
- cache.set(projectRoot, null);
1681
- return null;
1682
- }
1683
- const rawBaseUrl = typeof compilerOptions.baseUrl === "string" ? compilerOptions.baseUrl : null;
1684
- const rawPaths = typeof compilerOptions.paths === "object" && compilerOptions.paths !== null ? compilerOptions.paths : null;
1685
- if (!rawBaseUrl && !rawPaths) {
1686
- cache.set(projectRoot, null);
1687
- return null;
1688
- }
1689
- const resolvedBaseUrl = rawBaseUrl ? path3.resolve(projectRoot, rawBaseUrl) : projectRoot;
1690
- const paths = new Map;
1691
- if (rawPaths) {
1692
- for (const [pattern, targets] of Object.entries(rawPaths)) {
1693
- if (!Array.isArray(targets)) {
1694
- continue;
1695
- }
1696
- const normalizedTargets = targets.filter((value) => typeof value === "string");
1697
- paths.set(pattern, normalizedTargets);
1698
- }
1699
- }
1700
- const result = {
1701
- baseUrl: resolvedBaseUrl,
1702
- paths
1703
- };
1704
- cache.set(projectRoot, result);
1705
- return result;
1706
- }
1707
- function clearTsconfigPathsCache(projectRoot) {
1708
- if (projectRoot) {
1709
- cache.delete(projectRoot);
1710
- return;
1711
- }
1712
- cache.clear();
1713
- }
1714
-
1715
- // src/common/path-utils.ts
1716
- import path4 from "path";
1717
- function toRelativePath(projectRoot, absolutePath) {
1718
- return path4.relative(projectRoot, absolutePath).replaceAll("\\", "/");
1719
- }
1720
- function toAbsolutePath(projectRoot, relativePath) {
1721
- return path4.resolve(projectRoot, relativePath);
1722
- }
1723
-
1724
- // src/common/hasher.ts
1725
- function hashString(input) {
1726
- const raw = Bun.hash.xxHash64(input);
1727
- const unsigned = BigInt.asUintN(64, BigInt(raw));
1728
- return unsigned.toString(16).padStart(16, "0");
1729
- }
1730
-
1731
- // src/indexer/file-indexer.ts
1732
- import { promises as fsPromises } from "fs";
1733
- import { join as join2 } from "path";
1734
- async function detectChanges(opts) {
1735
- const { projectRoot, extensions, ignorePatterns, fileRepo } = opts;
1736
- const existingMap = fileRepo.getFilesMap();
1737
- const seenPaths = new Set;
1738
- const changed = [];
1739
- const unchanged = [];
1740
- const ignoreGlobs = ignorePatterns.map((p) => new Bun.Glob(p));
1741
- for await (const relativePath of fsPromises.glob("**/*", { cwd: projectRoot })) {
1742
- if (!extensions.some((ext) => relativePath.endsWith(ext)))
1743
- continue;
1744
- if (ignoreGlobs.some((g) => g.match(relativePath)))
1745
- continue;
1746
- seenPaths.add(relativePath);
1747
- const absPath = join2(projectRoot, relativePath);
1748
- const bunFile = Bun.file(absPath);
1749
- const { size, lastModified: mtimeMs } = bunFile;
1750
- const existing = existingMap.get(relativePath);
1751
- if (!existing) {
1752
- const text3 = await bunFile.text();
1753
- const contentHash2 = hashString(text3);
1754
- changed.push({ filePath: relativePath, contentHash: contentHash2, mtimeMs, size });
1755
- continue;
1756
- }
1757
- if (existing.mtimeMs === mtimeMs && existing.size === size) {
1758
- unchanged.push({ filePath: relativePath, contentHash: existing.contentHash, mtimeMs, size });
1759
- continue;
1760
- }
1761
- const text2 = await bunFile.text();
1762
- const contentHash = hashString(text2);
1763
- if (contentHash === existing.contentHash) {
1764
- unchanged.push({ filePath: relativePath, contentHash, mtimeMs, size });
1765
- } else {
1766
- changed.push({ filePath: relativePath, contentHash, mtimeMs, size });
1767
- }
1768
- }
1769
- const deleted = [];
1770
- for (const filePath of existingMap.keys()) {
1771
- if (!seenPaths.has(filePath)) {
1772
- deleted.push(filePath);
1773
- }
1774
- }
1775
- return { changed, unchanged, deleted };
1776
- }
1777
-
1778
- // src/indexer/symbol-indexer.ts
1779
- function buildSignature(sym) {
1780
- if (sym.kind === "function" || sym.kind === "method") {
1781
- const paramCount = sym.parameters?.length ?? 0;
1782
- const isAsync = sym.modifiers.includes("async") ? 1 : 0;
1783
- return `params:${paramCount}|async:${isAsync}`;
1784
- }
1785
- return null;
1786
- }
1787
- function buildDetailJson(sym) {
1788
- const detail = {};
1789
- if (sym.jsDoc)
1790
- detail.jsDoc = sym.jsDoc;
1791
- if (sym.kind === "function" || sym.kind === "method") {
1792
- if (sym.parameters !== undefined)
1793
- detail.parameters = sym.parameters;
1794
- if (sym.returnType !== undefined)
1795
- detail.returnType = sym.returnType;
1796
- }
1797
- if (sym.heritage?.length)
1798
- detail.heritage = sym.heritage;
1799
- if (sym.decorators?.length)
1800
- detail.decorators = sym.decorators;
1801
- if (sym.typeParameters?.length)
1802
- detail.typeParameters = sym.typeParameters;
1803
- if (sym.modifiers?.length)
1804
- detail.modifiers = sym.modifiers;
1805
- if (sym.members?.length)
1806
- detail.members = sym.members.map((m) => m.name);
1807
- return Object.keys(detail).length > 0 ? JSON.stringify(detail) : null;
1808
- }
1809
- function buildRow(sym, name, project, filePath, contentHash) {
1810
- const signature = buildSignature(sym);
1811
- const fingerprint = hashString(`${name}|${sym.kind}|${signature ?? ""}`);
1812
- return {
1813
- project,
1814
- filePath,
1815
- kind: sym.kind,
1816
- name,
1817
- startLine: sym.span.start.line,
1818
- startColumn: sym.span.start.column,
1819
- endLine: sym.span.end.line,
1820
- endColumn: sym.span.end.column,
1821
- isExported: sym.isExported ? 1 : 0,
1822
- signature,
1823
- fingerprint,
1824
- detailJson: buildDetailJson(sym),
1825
- contentHash,
1826
- indexedAt: new Date().toISOString()
1827
- };
1828
- }
1829
- function indexFileSymbols(opts) {
1830
- const { parsed, project, filePath, contentHash, symbolRepo } = opts;
1831
- const extracted = extractSymbols(parsed);
1832
- const rows = [];
1833
- for (const sym of extracted) {
1834
- rows.push(buildRow(sym, sym.name, project, filePath, contentHash));
1835
- for (const member of sym.members ?? []) {
1836
- rows.push(buildRow(member, `${sym.name}.${member.name}`, project, filePath, contentHash));
1837
- }
1838
- }
1839
- symbolRepo.replaceFileSymbols(project, filePath, contentHash, rows);
1840
- }
1841
-
1842
- // src/indexer/relation-indexer.ts
1843
- function indexFileRelations(opts) {
1844
- const { ast, project, filePath, relationRepo, projectRoot, tsconfigPaths } = opts;
1845
- const absFilePath = toAbsolutePath(projectRoot, filePath);
1846
- const rawRelations = extractRelations(ast, absFilePath, tsconfigPaths);
1847
- const rows = [];
1848
- for (const rel of rawRelations) {
1849
- const relDst = toRelativePath(projectRoot, rel.dstFilePath);
1850
- if (relDst.startsWith(".."))
1851
- continue;
1852
- const relSrc = toRelativePath(projectRoot, rel.srcFilePath);
1853
- rows.push({
1854
- project,
1855
- type: rel.type,
1856
- srcFilePath: relSrc,
1857
- srcSymbolName: rel.srcSymbolName ?? null,
1858
- dstFilePath: relDst,
1859
- dstSymbolName: rel.dstSymbolName ?? null,
1860
- metaJson: rel.metaJson ?? null
1861
- });
1862
- }
1863
- relationRepo.replaceFileRelations(project, filePath, rows);
1864
- return rows.length;
1865
- }
1866
-
1867
- // src/indexer/index-coordinator.ts
1868
- var WATCHER_DEBOUNCE_MS = 100;
1869
-
1870
- class IndexCoordinator {
1871
- opts;
1872
- logger;
1873
- callbacks = new Set;
1874
- indexingLock = false;
1875
- pendingEvents = [];
1876
- debounceTimer = null;
1877
- currentIndexing = null;
1878
- pendingFullIndex = false;
1879
- pendingFullIndexWaiters = [];
1880
- tsconfigPathsRaw;
1881
- boundariesRefresh = null;
1882
- constructor(opts) {
1883
- this.opts = opts;
1884
- this.logger = opts.logger ?? console;
1885
- this.tsconfigPathsRaw = loadTsconfigPaths(opts.projectRoot);
1886
- }
1887
- get tsconfigPaths() {
1888
- return this.tsconfigPathsRaw;
1889
- }
1890
- fullIndex() {
1891
- return this.startIndex(undefined, true);
1892
- }
1893
- incrementalIndex(events) {
1894
- return this.startIndex(events, false);
1895
- }
1896
- onIndexed(cb) {
1897
- this.callbacks.add(cb);
1898
- return () => this.callbacks.delete(cb);
1899
- }
1900
- handleWatcherEvent(event) {
1901
- if (event.filePath.endsWith("tsconfig.json")) {
1902
- clearTsconfigPathsCache(this.opts.projectRoot);
1903
- this.tsconfigPathsRaw = loadTsconfigPaths(this.opts.projectRoot);
1904
- this.fullIndex().catch((err) => {
1905
- this.logger.error("[IndexCoordinator] fullIndex failed after tsconfig change:", err);
1906
- });
1907
- return;
1908
- }
1909
- if (event.filePath.endsWith("package.json")) {
1910
- const discover = this.opts.discoverProjectsFn ?? discoverProjects;
1911
- this.boundariesRefresh = discover(this.opts.projectRoot).then((b) => {
1912
- this.opts.boundaries = b;
1913
- });
1914
- }
1915
- this.pendingEvents.push(event);
1916
- if (this.debounceTimer === null) {
1917
- this.debounceTimer = setTimeout(() => {
1918
- this.debounceTimer = null;
1919
- this.flushPending();
1920
- }, WATCHER_DEBOUNCE_MS);
1921
- }
1922
- }
1923
- async shutdown() {
1924
- if (this.debounceTimer !== null) {
1925
- clearTimeout(this.debounceTimer);
1926
- this.debounceTimer = null;
1927
- }
1928
- if (this.currentIndexing) {
1929
- await this.currentIndexing;
1930
- }
1931
- }
1932
- startIndex(events, useTransaction) {
1933
- if (this.indexingLock) {
1934
- if (useTransaction) {
1935
- this.pendingFullIndex = true;
1936
- return new Promise((resolve2, reject) => {
1937
- this.pendingFullIndexWaiters.push({ resolve: resolve2, reject });
1938
- });
1939
- }
1940
- return this.currentIndexing;
1941
- }
1942
- this.indexingLock = true;
1943
- const work = this.doIndex(events, useTransaction).then((result) => {
1944
- this.fireCallbacks(result);
1945
- return result;
1946
- }).finally(() => {
1947
- this.indexingLock = false;
1948
- this.currentIndexing = null;
1949
- if (this.pendingFullIndex) {
1950
- this.pendingFullIndex = false;
1951
- const waiters = this.pendingFullIndexWaiters.splice(0);
1952
- this.startIndex(undefined, true).then((result) => {
1953
- for (const waiter of waiters)
1954
- waiter.resolve(result);
1955
- }).catch((error) => {
1956
- for (const waiter of waiters)
1957
- waiter.reject(error);
1958
- });
1959
- } else if (this.pendingEvents.length > 0) {
1960
- const drained = this.pendingEvents.splice(0);
1961
- this.startIndex(drained, false).catch((err) => this.logger.error("[IndexCoordinator] incremental drain error", err));
1962
- }
1963
- });
1964
- this.currentIndexing = work;
1965
- return work;
1966
- }
1967
- async doIndex(events, useTransaction) {
1968
- const start = Date.now();
1969
- const { fileRepo, symbolRepo, relationRepo, dbConnection } = this.opts;
1970
- if (this.boundariesRefresh) {
1971
- await this.boundariesRefresh;
1972
- this.boundariesRefresh = null;
1973
- }
1974
- let changed;
1975
- let deleted;
1976
- if (events !== undefined) {
1977
- changed = events.filter((e) => e.eventType === "create" || e.eventType === "change").map((e) => ({
1978
- filePath: e.filePath,
1979
- contentHash: "",
1980
- mtimeMs: 0,
1981
- size: 0
1982
- }));
1983
- deleted = events.filter((e) => e.eventType === "delete").map((e) => e.filePath);
1984
- } else {
1985
- const existingMap = new Map;
1986
- for (const boundary of this.opts.boundaries) {
1987
- for (const [key, val] of fileRepo.getFilesMap(boundary.project)) {
1988
- existingMap.set(key, val);
1989
- }
1990
- }
1991
- const result = await detectChanges({
1992
- projectRoot: this.opts.projectRoot,
1993
- extensions: this.opts.extensions,
1994
- ignorePatterns: this.opts.ignorePatterns,
1995
- fileRepo: { getFilesMap: () => existingMap }
1996
- });
1997
- changed = result.changed;
1998
- deleted = result.deleted;
1999
- }
2000
- const tsconfigPaths = await this.tsconfigPathsRaw ?? undefined;
2001
- const deletedSymbols = new Map;
2002
- for (const filePath of deleted) {
2003
- const project = resolveFileProject(filePath, this.opts.boundaries);
2004
- const syms = symbolRepo.getFileSymbols(project, filePath);
2005
- deletedSymbols.set(filePath, syms);
2006
- }
2007
- const processDeleted = () => {
2008
- for (const filePath of deleted) {
2009
- const project = resolveFileProject(filePath, this.opts.boundaries);
2010
- symbolRepo.deleteFileSymbols(project, filePath);
2011
- relationRepo.deleteFileRelations(project, filePath);
2012
- fileRepo.deleteFile(project, filePath);
2013
- }
2014
- };
2015
- const processChanged = async () => {
2016
- let symbols2 = 0;
2017
- let relations2 = 0;
2018
- const failedFiles = [];
2019
- for (const file of changed) {
2020
- try {
2021
- const r = await this.processFile(file.filePath, file.contentHash || undefined, tsconfigPaths);
2022
- symbols2 += r.symbolCount;
2023
- relations2 += r.relCount;
2024
- } catch (err) {
2025
- this.logger.error(`[IndexCoordinator] Failed to index ${file.filePath}:`, err);
2026
- failedFiles.push(file.filePath);
2027
- }
2028
- }
2029
- return { symbols: symbols2, relations: relations2, failedFiles };
2030
- };
2031
- let totalSymbols = 0;
2032
- let totalRelations = 0;
2033
- let allFailedFiles = [];
2034
- if (useTransaction) {
2035
- const { projectRoot, boundaries } = this.opts;
2036
- const { parseCache } = this.opts;
2037
- const prereadResults = await Promise.allSettled(changed.map(async (file) => {
2038
- const absPath = toAbsolutePath(projectRoot, file.filePath);
2039
- const bunFile = Bun.file(absPath);
2040
- const text2 = await bunFile.text();
2041
- const contentHash = file.contentHash || hashString(text2);
2042
- return { filePath: file.filePath, text: text2, contentHash, mtimeMs: bunFile.lastModified, size: bunFile.size };
2043
- }));
2044
- const preread = prereadResults.filter((r) => r.status === "fulfilled").map((r) => r.value);
2045
- for (const r of prereadResults) {
2046
- if (r.status === "rejected") {
2047
- this.logger.error("[IndexCoordinator] Failed to pre-read file:", r.reason);
2048
- }
2049
- }
2050
- const parsedCacheEntries = [];
2051
- dbConnection.transaction(() => {
2052
- for (const boundary of boundaries) {
2053
- const projectFiles = fileRepo.getAllFiles(boundary.project);
2054
- for (const f of projectFiles) {
2055
- fileRepo.deleteFile(f.project, f.filePath);
2056
- }
2057
- }
2058
- const parseFn = this.opts.parseSourceFn ?? parseSource;
2059
- for (const fd of preread) {
2060
- const project = resolveFileProject(fd.filePath, boundaries);
2061
- const parsed = parseFn(toAbsolutePath(projectRoot, fd.filePath), fd.text);
2062
- parsedCacheEntries.push({ filePath: fd.filePath, parsed });
2063
- fileRepo.upsertFile({
2064
- project,
2065
- filePath: fd.filePath,
2066
- mtimeMs: fd.mtimeMs,
2067
- size: fd.size,
2068
- contentHash: fd.contentHash,
2069
- updatedAt: new Date().toISOString()
2070
- });
2071
- indexFileSymbols({ parsed, project, filePath: fd.filePath, contentHash: fd.contentHash, symbolRepo });
2072
- totalRelations += indexFileRelations({
2073
- ast: parsed.program,
2074
- project,
2075
- filePath: fd.filePath,
2076
- relationRepo,
2077
- projectRoot,
2078
- tsconfigPaths
2079
- });
2080
- totalSymbols += symbolRepo.getFileSymbols(project, fd.filePath).length;
2081
- }
2082
- });
2083
- for (const entry of parsedCacheEntries) {
2084
- parseCache.set(entry.filePath, entry.parsed);
2085
- }
2086
- } else {
2087
- processDeleted();
2088
- const counts = await processChanged();
2089
- totalSymbols = counts.symbols;
2090
- totalRelations = counts.relations;
2091
- allFailedFiles = counts.failedFiles;
2092
- }
2093
- if (!useTransaction) {
2094
- for (const [oldFile, syms] of deletedSymbols) {
2095
- for (const sym of syms) {
2096
- if (!sym.fingerprint)
2097
- continue;
2098
- const oldProject = resolveFileProject(oldFile, this.opts.boundaries);
2099
- const matches = symbolRepo.getByFingerprint(oldProject, sym.fingerprint);
2100
- if (matches.length === 1) {
2101
- const newSym = matches[0];
2102
- relationRepo.retargetRelations(oldProject, oldFile, sym.name, newSym.filePath, newSym.name);
2103
- }
2104
- }
2105
- }
2106
- }
2107
- return {
2108
- indexedFiles: changed.length,
2109
- removedFiles: deleted.length,
2110
- totalSymbols,
2111
- totalRelations,
2112
- durationMs: Date.now() - start,
2113
- changedFiles: changed.map((f) => f.filePath),
2114
- deletedFiles: [...deleted],
2115
- failedFiles: allFailedFiles
2116
- };
2117
- }
2118
- async processFile(filePath, knownHash, tsconfigPaths) {
2119
- const { projectRoot, boundaries } = this.opts;
2120
- const { fileRepo, symbolRepo, relationRepo, parseCache } = this.opts;
2121
- const absPath = toAbsolutePath(projectRoot, filePath);
2122
- const bunFile = Bun.file(absPath);
2123
- const text2 = await bunFile.text();
2124
- const contentHash = knownHash || hashString(text2);
2125
- const project = resolveFileProject(filePath, boundaries);
2126
- const parseFn = this.opts.parseSourceFn ?? parseSource;
2127
- const parsed = parseFn(absPath, text2);
2128
- parseCache.set(filePath, parsed);
2129
- fileRepo.upsertFile({
2130
- project,
2131
- filePath,
2132
- mtimeMs: bunFile.lastModified,
2133
- size: bunFile.size,
2134
- contentHash,
2135
- updatedAt: new Date().toISOString()
2136
- });
2137
- indexFileSymbols({ parsed, project, filePath, contentHash, symbolRepo });
2138
- const relCount = indexFileRelations({
2139
- ast: parsed.program,
2140
- project,
2141
- filePath,
2142
- relationRepo,
2143
- projectRoot,
2144
- tsconfigPaths
2145
- });
2146
- const symbolCount = symbolRepo.getFileSymbols(project, filePath).length;
2147
- return { symbolCount, relCount };
2148
- }
2149
- fireCallbacks(result) {
2150
- for (const cb of this.callbacks) {
2151
- try {
2152
- cb(result);
2153
- } catch (err) {
2154
- this.logger.error("[IndexCoordinator] onIndexed callback threw:", err);
2155
- }
2156
- }
2157
- }
2158
- flushPending() {
2159
- if (this.indexingLock) {
2160
- return;
2161
- }
2162
- if (this.pendingEvents.length > 0) {
2163
- const events = this.pendingEvents.splice(0);
2164
- this.startIndex(events, false).catch((err) => this.logger.error("[IndexCoordinator] flushPending startIndex error:", err));
2165
- }
2166
- }
2167
- }
2168
-
2169
- // src/watcher/ownership.ts
2170
- function defaultIsAlive(pid) {
2171
- try {
2172
- process.kill(pid, 0);
2173
- return true;
2174
- } catch (error) {
2175
- if (typeof error === "object" && error && "code" in error) {
2176
- return error.code !== "ESRCH";
2177
- }
2178
- return true;
2179
- }
2180
- }
2181
- function toEpochMs(value) {
2182
- const ms = new Date(value).getTime();
2183
- return Number.isNaN(ms) ? 0 : ms;
2184
- }
2185
- function acquireWatcherRole(db, pid, options = {}) {
2186
- const now = options.now ?? Date.now;
2187
- const isAlive = options.isAlive ?? defaultIsAlive;
2188
- const staleAfterSeconds = options.staleAfterSeconds ?? 90;
2189
- return db.immediateTransaction(() => {
2190
- const owner = db.selectOwner();
2191
- if (!owner) {
2192
- db.insertOwner(pid);
2193
- return "owner";
2194
- }
2195
- const heartbeatAgeSeconds = Math.floor((now() - toEpochMs(owner.heartbeat_at)) / 1000);
2196
- if (isAlive(owner.pid) && heartbeatAgeSeconds < staleAfterSeconds) {
2197
- return "reader";
2198
- }
2199
- db.replaceOwner(pid);
2200
- return "owner";
2201
- });
2202
- }
2203
- function releaseWatcherRole(db, pid) {
2204
- db.deleteOwner(pid);
2205
- }
2206
- function updateHeartbeat(db, pid) {
2207
- db.touchOwner(pid);
2208
- }
2209
-
2210
- // src/search/symbol-search.ts
2211
- function symbolSearch(options) {
2212
- const { symbolRepo, project, query } = options;
2213
- const effectiveProject = query.project ?? project;
2214
- const limit = query.limit ?? 100;
2215
- const opts = {
2216
- kind: query.kind,
2217
- filePath: query.filePath,
2218
- isExported: query.isExported,
2219
- project: effectiveProject,
2220
- limit
2221
- };
2222
- if (query.text) {
2223
- const ftsQuery = toFtsPrefixQuery(query.text);
2224
- if (ftsQuery)
2225
- opts.ftsQuery = ftsQuery;
2226
- }
2227
- const records = symbolRepo.searchByQuery(opts);
2228
- return records.map((r) => ({
2229
- id: r.id,
2230
- filePath: r.filePath,
2231
- kind: r.kind,
2232
- name: r.name,
2233
- span: {
2234
- start: { line: r.startLine, column: r.startColumn },
2235
- end: { line: r.endLine, column: r.endColumn }
2236
- },
2237
- isExported: r.isExported === 1,
2238
- signature: r.signature,
2239
- fingerprint: r.fingerprint,
2240
- detail: r.detailJson ? (() => {
2241
- try {
2242
- return JSON.parse(r.detailJson);
2243
- } catch {
2244
- return {};
2245
- }
2246
- })() : {}
2247
- }));
2248
- }
2249
-
2250
- // src/search/relation-search.ts
2251
- function relationSearch(options) {
2252
- const { relationRepo, project, query } = options;
2253
- const effectiveProject = query.project ?? project;
2254
- const limit = query.limit ?? 500;
2255
- const records = relationRepo.searchRelations({
2256
- srcFilePath: query.srcFilePath,
2257
- srcSymbolName: query.srcSymbolName,
2258
- dstFilePath: query.dstFilePath,
2259
- dstSymbolName: query.dstSymbolName,
2260
- type: query.type,
2261
- project: effectiveProject,
2262
- limit
2263
- });
2264
- return records.map((r) => ({
2265
- type: r.type,
2266
- srcFilePath: r.srcFilePath,
2267
- srcSymbolName: r.srcSymbolName,
2268
- dstFilePath: r.dstFilePath,
2269
- dstSymbolName: r.dstSymbolName,
2270
- metaJson: r.metaJson ?? undefined
2271
- }));
2272
- }
2273
-
2274
- // src/search/dependency-graph.ts
2275
- class DependencyGraph {
2276
- options;
2277
- adjacencyList = new Map;
2278
- reverseAdjacencyList = new Map;
2279
- constructor(options) {
2280
- this.options = options;
2281
- }
2282
- build() {
2283
- this.adjacencyList = new Map;
2284
- this.reverseAdjacencyList = new Map;
2285
- const relations2 = this.options.relationRepo.getByType(this.options.project, "imports");
2286
- for (const rel of relations2) {
2287
- const { srcFilePath, dstFilePath } = rel;
2288
- if (!this.adjacencyList.has(srcFilePath)) {
2289
- this.adjacencyList.set(srcFilePath, new Set);
2290
- }
2291
- this.adjacencyList.get(srcFilePath).add(dstFilePath);
2292
- if (!this.reverseAdjacencyList.has(dstFilePath)) {
2293
- this.reverseAdjacencyList.set(dstFilePath, new Set);
2294
- }
2295
- this.reverseAdjacencyList.get(dstFilePath).add(srcFilePath);
2296
- }
2297
- }
2298
- getDependencies(filePath) {
2299
- return Array.from(this.adjacencyList.get(filePath) ?? []);
2300
- }
2301
- getDependents(filePath) {
2302
- return Array.from(this.reverseAdjacencyList.get(filePath) ?? []);
2303
- }
2304
- getTransitiveDependents(filePath) {
2305
- const visited = new Set;
2306
- const queue = [filePath];
2307
- while (queue.length > 0) {
2308
- const current = queue.shift();
2309
- for (const dependent of this.reverseAdjacencyList.get(current) ?? []) {
2310
- if (!visited.has(dependent)) {
2311
- visited.add(dependent);
2312
- queue.push(dependent);
2313
- }
2314
- }
2315
- }
2316
- return Array.from(visited);
2317
- }
2318
- hasCycle() {
2319
- const visited = new Set;
2320
- const inPath = new Set;
2321
- for (const startNode of this.adjacencyList.keys()) {
2322
- if (visited.has(startNode))
2323
- continue;
2324
- const stack = [{ node: startNode, entered: false }];
2325
- while (stack.length > 0) {
2326
- const current = stack.pop();
2327
- if (current.entered) {
2328
- inPath.delete(current.node);
2329
- continue;
2330
- }
2331
- if (inPath.has(current.node)) {
2332
- return true;
2333
- }
2334
- if (visited.has(current.node)) {
2335
- continue;
2336
- }
2337
- visited.add(current.node);
2338
- inPath.add(current.node);
2339
- stack.push({ node: current.node, entered: true });
2340
- for (const neighbor of this.adjacencyList.get(current.node) ?? []) {
2341
- if (inPath.has(neighbor)) {
2342
- return true;
2343
- }
2344
- if (!visited.has(neighbor)) {
2345
- stack.push({ node: neighbor, entered: false });
2346
- }
2347
- }
2348
- }
2349
- }
2350
- return false;
2351
- }
2352
- getAffectedByChange(changedFiles) {
2353
- const allAffected = new Set;
2354
- for (const file of changedFiles) {
2355
- for (const dep of this.getTransitiveDependents(file)) {
2356
- allAffected.add(dep);
2357
- }
2358
- }
2359
- return Array.from(allAffected);
2360
- }
2361
- }
2362
-
2363
- // src/gildash.ts
2364
- var HEARTBEAT_INTERVAL_MS = 30000;
2365
- var HEALTHCHECK_INTERVAL_MS = 60000;
2366
- var MAX_HEALTHCHECK_RETRIES = 10;
2367
-
2368
- class Gildash {
2369
- projectRoot;
2370
- db;
2371
- symbolRepo;
2372
- relationRepo;
2373
- parseCache;
2374
- coordinator;
2375
- watcher;
2376
- releaseWatcherRoleFn;
2377
- parseSourceFn;
2378
- extractSymbolsFn;
2379
- extractRelationsFn;
2380
- symbolSearchFn;
2381
- relationSearchFn;
2382
- logger;
2383
- defaultProject;
2384
- role;
2385
- timer = null;
2386
- signalHandlers = [];
2387
- closed = false;
2388
- tsconfigPaths = null;
2389
- boundaries = [];
2390
- onIndexedCallbacks = new Set;
2391
- constructor(opts) {
2392
- this.projectRoot = opts.projectRoot;
2393
- this.db = opts.db;
2394
- this.symbolRepo = opts.symbolRepo;
2395
- this.relationRepo = opts.relationRepo;
2396
- this.parseCache = opts.parseCache;
2397
- this.coordinator = opts.coordinator;
2398
- this.watcher = opts.watcher;
2399
- this.releaseWatcherRoleFn = opts.releaseWatcherRoleFn;
2400
- this.parseSourceFn = opts.parseSourceFn;
2401
- this.extractSymbolsFn = opts.extractSymbolsFn;
2402
- this.extractRelationsFn = opts.extractRelationsFn;
2403
- this.symbolSearchFn = opts.symbolSearchFn;
2404
- this.relationSearchFn = opts.relationSearchFn;
2405
- this.logger = opts.logger;
2406
- this.defaultProject = opts.defaultProject;
2407
- this.role = opts.role;
2408
- }
2409
- static async open(options) {
2410
- const {
2411
- projectRoot,
2412
- extensions = [".ts", ".mts", ".cts"],
2413
- ignorePatterns = [],
2414
- parseCacheCapacity = 500,
2415
- logger = console,
2416
- existsSyncFn = existsSync2,
2417
- dbConnectionFactory,
2418
- watcherFactory,
2419
- coordinatorFactory,
2420
- repositoryFactory,
2421
- acquireWatcherRoleFn = acquireWatcherRole,
2422
- releaseWatcherRoleFn = releaseWatcherRole,
2423
- updateHeartbeatFn = updateHeartbeat,
2424
- discoverProjectsFn = discoverProjects,
2425
- parseSourceFn = parseSource,
2426
- extractSymbolsFn = extractSymbols,
2427
- extractRelationsFn = extractRelations,
2428
- symbolSearchFn = symbolSearch,
2429
- relationSearchFn = relationSearch,
2430
- loadTsconfigPathsFn = loadTsconfigPaths
2431
- } = options;
2432
- if (!path5.isAbsolute(projectRoot)) {
2433
- throw new Error(`Gildash: projectRoot must be an absolute path, got: "${projectRoot}"`);
2434
- }
2435
- if (!existsSyncFn(projectRoot)) {
2436
- throw new Error(`Gildash: projectRoot does not exist: "${projectRoot}"`);
2437
- }
2438
- const db = dbConnectionFactory ? dbConnectionFactory() : new DbConnection({ projectRoot });
2439
- db.open();
2440
- try {
2441
- const boundaries = await discoverProjectsFn(projectRoot);
2442
- const defaultProject = boundaries[0]?.project ?? path5.basename(projectRoot);
2443
- const repos = repositoryFactory ? repositoryFactory() : (() => {
2444
- const connection = db;
2445
- return {
2446
- fileRepo: new FileRepository(connection),
2447
- symbolRepo: new SymbolRepository(connection),
2448
- relationRepo: new RelationRepository(connection),
2449
- parseCache: new ParseCache(parseCacheCapacity)
2450
- };
2451
- })();
2452
- const role = await Promise.resolve(acquireWatcherRoleFn(db, process.pid, {}));
2453
- let coordinator = null;
2454
- let watcher = null;
2455
- const instance = new Gildash({
2456
- projectRoot,
2457
- db,
2458
- symbolRepo: repos.symbolRepo,
2459
- relationRepo: repos.relationRepo,
2460
- parseCache: repos.parseCache,
2461
- coordinator,
2462
- watcher,
2463
- releaseWatcherRoleFn,
2464
- parseSourceFn,
2465
- extractSymbolsFn,
2466
- extractRelationsFn,
2467
- symbolSearchFn,
2468
- relationSearchFn,
2469
- logger,
2470
- defaultProject,
2471
- role
2472
- });
2473
- clearTsconfigPathsCache(projectRoot);
2474
- instance.tsconfigPaths = await loadTsconfigPathsFn(projectRoot);
2475
- instance.boundaries = boundaries;
2476
- if (role === "owner") {
2477
- const w = watcherFactory ? watcherFactory() : new ProjectWatcher({ projectRoot, ignorePatterns, extensions }, undefined, logger);
2478
- const c = coordinatorFactory ? coordinatorFactory() : new IndexCoordinator({
2479
- projectRoot,
2480
- boundaries,
2481
- extensions,
2482
- ignorePatterns,
2483
- dbConnection: db,
2484
- parseCache: repos.parseCache,
2485
- fileRepo: repos.fileRepo,
2486
- symbolRepo: repos.symbolRepo,
2487
- relationRepo: repos.relationRepo,
2488
- logger
2489
- });
2490
- instance.coordinator = c;
2491
- instance.watcher = w;
2492
- await w.start((event) => c.handleWatcherEvent?.(event));
2493
- const timer = setInterval(() => {
2494
- updateHeartbeatFn(db, process.pid);
2495
- }, HEARTBEAT_INTERVAL_MS);
2496
- instance.timer = timer;
2497
- await c.fullIndex();
2498
- } else {
2499
- let retryCount = 0;
2500
- const healthcheck = async () => {
2501
- try {
2502
- const newRole = await Promise.resolve(acquireWatcherRoleFn(db, process.pid, {}));
2503
- retryCount = 0;
2504
- if (newRole === "owner") {
2505
- clearInterval(instance.timer);
2506
- instance.timer = null;
2507
- let promotedWatcher = null;
2508
- let promotedCoordinator = null;
2509
- try {
2510
- promotedWatcher = watcherFactory ? watcherFactory() : new ProjectWatcher({ projectRoot, ignorePatterns, extensions }, undefined, logger);
2511
- promotedCoordinator = coordinatorFactory ? coordinatorFactory() : new IndexCoordinator({
2512
- projectRoot,
2513
- boundaries,
2514
- extensions,
2515
- ignorePatterns,
2516
- dbConnection: db,
2517
- parseCache: repos.parseCache,
2518
- fileRepo: repos.fileRepo,
2519
- symbolRepo: repos.symbolRepo,
2520
- relationRepo: repos.relationRepo,
2521
- logger
2522
- });
2523
- for (const cb of instance.onIndexedCallbacks) {
2524
- promotedCoordinator.onIndexed(cb);
2525
- }
2526
- await promotedWatcher.start((event) => promotedCoordinator?.handleWatcherEvent?.(event));
2527
- const hbTimer = setInterval(() => {
2528
- updateHeartbeatFn(db, process.pid);
2529
- }, HEARTBEAT_INTERVAL_MS);
2530
- instance.timer = hbTimer;
2531
- instance.coordinator = promotedCoordinator;
2532
- instance.watcher = promotedWatcher;
2533
- await promotedCoordinator.fullIndex();
2534
- } catch (setupErr) {
2535
- logger.error("[Gildash] owner promotion failed, reverting to reader", setupErr);
2536
- if (promotedWatcher) {
2537
- await promotedWatcher.close().catch((e) => logger.error("[Gildash] watcher close error during promotion rollback", e));
2538
- instance.watcher = null;
2539
- }
2540
- if (promotedCoordinator) {
2541
- await promotedCoordinator.shutdown().catch((e) => logger.error("[Gildash] coordinator shutdown error during promotion rollback", e));
2542
- instance.coordinator = null;
2543
- }
2544
- if (instance.timer === null) {
2545
- instance.timer = setInterval(healthcheck, HEALTHCHECK_INTERVAL_MS);
2546
- }
2547
- }
2548
- }
2549
- } catch (err) {
2550
- retryCount++;
2551
- logger.error("[Gildash] healthcheck error", err);
2552
- if (retryCount >= MAX_HEALTHCHECK_RETRIES) {
2553
- logger.error("[Gildash] healthcheck failed too many times, shutting down");
2554
- clearInterval(instance.timer);
2555
- instance.timer = null;
2556
- instance.close().catch((closeErr) => logger.error("[Gildash] close error during healthcheck shutdown", closeErr));
2557
- }
2558
- }
2559
- };
2560
- const timer = setInterval(healthcheck, HEALTHCHECK_INTERVAL_MS);
2561
- instance.timer = timer;
2562
- }
2563
- const signals = ["SIGTERM", "SIGINT", "beforeExit"];
2564
- for (const sig of signals) {
2565
- const handler = () => {
2566
- instance.close().catch((err) => logger.error("[Gildash] close error during signal", sig, err));
2567
- };
2568
- if (sig === "beforeExit") {
2569
- process.on("beforeExit", handler);
2570
- } else {
2571
- process.on(sig, handler);
2572
- }
2573
- instance.signalHandlers.push([sig, handler]);
2574
- }
2575
- return instance;
2576
- } catch (err) {
2577
- db.close();
2578
- throw err;
2579
- }
2580
- }
2581
- async close() {
2582
- if (this.closed)
2583
- return;
2584
- this.closed = true;
2585
- const closeErrors = [];
2586
- for (const [sig, handler] of this.signalHandlers) {
2587
- if (sig === "beforeExit") {
2588
- process.off("beforeExit", handler);
2589
- } else {
2590
- process.off(sig, handler);
2591
- }
2592
- }
2593
- this.signalHandlers = [];
2594
- if (this.coordinator) {
2595
- try {
2596
- await this.coordinator.shutdown();
2597
- } catch (err) {
2598
- closeErrors.push(err instanceof Error ? err : new Error(String(err)));
2599
- }
2600
- }
2601
- if (this.watcher) {
2602
- try {
2603
- await this.watcher.close();
2604
- } catch (err) {
2605
- closeErrors.push(err instanceof Error ? err : new Error(String(err)));
2606
- }
2607
- }
2608
- if (this.timer !== null) {
2609
- clearInterval(this.timer);
2610
- this.timer = null;
2611
- }
2612
- try {
2613
- this.releaseWatcherRoleFn(this.db, process.pid);
2614
- } catch (err) {
2615
- closeErrors.push(err instanceof Error ? err : new Error(String(err)));
2616
- }
2617
- try {
2618
- this.db.close();
2619
- } catch (err) {
2620
- closeErrors.push(err instanceof Error ? err : new Error(String(err)));
2621
- }
2622
- if (closeErrors.length > 0) {
2623
- throw new AggregateError(closeErrors, "Gildash: one or more errors occurred during close()");
2624
- }
2625
- }
2626
- onIndexed(callback) {
2627
- this.onIndexedCallbacks.add(callback);
2628
- if (!this.coordinator) {
2629
- return () => {
2630
- this.onIndexedCallbacks.delete(callback);
2631
- };
2632
- }
2633
- const unsubscribe = this.coordinator.onIndexed(callback);
2634
- return () => {
2635
- this.onIndexedCallbacks.delete(callback);
2636
- unsubscribe();
2637
- };
2638
- }
2639
- parseSource(filePath, sourceText) {
2640
- if (this.closed)
2641
- throw new Error("Gildash: instance is closed");
2642
- const parsed = this.parseSourceFn(filePath, sourceText);
2643
- this.parseCache.set(filePath, parsed);
2644
- return parsed;
2645
- }
2646
- extractSymbols(parsed) {
2647
- if (this.closed)
2648
- throw new Error("Gildash: instance is closed");
2649
- return this.extractSymbolsFn(parsed);
2650
- }
2651
- extractRelations(parsed) {
2652
- if (this.closed)
2653
- throw new Error("Gildash: instance is closed");
2654
- return this.extractRelationsFn(parsed.program, parsed.filePath, this.tsconfigPaths ?? undefined);
2655
- }
2656
- async reindex() {
2657
- if (this.closed)
2658
- throw new Error("Gildash: instance is closed");
2659
- if (!this.coordinator) {
2660
- throw new Error("Gildash: reindex() is not available for readers");
2661
- }
2662
- return this.coordinator.fullIndex();
2663
- }
2664
- get projects() {
2665
- return [...this.boundaries];
2666
- }
2667
- getStats(project) {
2668
- if (this.closed)
2669
- throw new Error("Gildash: instance is closed");
2670
- return this.symbolRepo.getStats(project ?? this.defaultProject);
2671
- }
2672
- searchSymbols(query) {
2673
- if (this.closed)
2674
- throw new Error("Gildash: instance is closed");
2675
- return this.symbolSearchFn({ symbolRepo: this.symbolRepo, project: this.defaultProject, query });
2676
- }
2677
- searchRelations(query) {
2678
- if (this.closed)
2679
- throw new Error("Gildash: instance is closed");
2680
- return this.relationSearchFn({ relationRepo: this.relationRepo, project: this.defaultProject, query });
2681
- }
2682
- getDependencies(filePath, project, limit = 1e4) {
2683
- if (this.closed)
2684
- throw new Error("Gildash: instance is closed");
2685
- return this.relationSearchFn({
2686
- relationRepo: this.relationRepo,
2687
- project: project ?? this.defaultProject,
2688
- query: { srcFilePath: filePath, type: "imports", project: project ?? this.defaultProject, limit }
2689
- }).map((r) => r.dstFilePath);
2690
- }
2691
- getDependents(filePath, project, limit = 1e4) {
2692
- if (this.closed)
2693
- throw new Error("Gildash: instance is closed");
2694
- return this.relationSearchFn({
2695
- relationRepo: this.relationRepo,
2696
- project: project ?? this.defaultProject,
2697
- query: { dstFilePath: filePath, type: "imports", project: project ?? this.defaultProject, limit }
2698
- }).map((r) => r.srcFilePath);
2699
- }
2700
- async getAffected(changedFiles, project) {
2701
- if (this.closed)
2702
- throw new Error("Gildash: instance is closed");
2703
- const g = new DependencyGraph({
2704
- relationRepo: this.relationRepo,
2705
- project: project ?? this.defaultProject
2706
- });
2707
- await g.build();
2708
- return g.getAffectedByChange(changedFiles);
2709
- }
2710
- async hasCycle(project) {
2711
- if (this.closed)
2712
- throw new Error("Gildash: instance is closed");
2713
- const g = new DependencyGraph({
2714
- relationRepo: this.relationRepo,
2715
- project: project ?? this.defaultProject
2716
- });
2717
- await g.build();
2718
- return g.hasCycle();
2719
- }
2720
- }
2721
- export {
2722
- symbolSearch,
2723
- relationSearch,
2724
- WatcherError,
2725
- StoreError,
2726
- SearchError,
2727
- ParseError,
2728
- IndexError,
2729
- GildashError,
2730
- Gildash,
2731
- ExtractError,
2732
- DependencyGraph
2733
- };
23
+ END`];class Ne{client=null;drizzle=null;dbPath;txDepth=0;constructor(e){this.dbPath=Ve(e.projectRoot,".zipbul","gildash.db")}get drizzleDb(){if(!this.drizzle)throw Error("Database is not open. Call open() first.");return this.drizzle}open(){try{kt(Ot(this.dbPath),{recursive:!0}),this.client=new Nt(this.dbPath),this.client.run("PRAGMA journal_mode = WAL"),this.client.run("PRAGMA foreign_keys = ON"),this.client.run("PRAGMA busy_timeout = 5000"),this.drizzle=vt(this.client,{schema:Re}),Ct(this.drizzle,{migrationsFolder:Ve(import.meta.dirname,"migrations")});for(let e of Fe)this.client.run(e)}catch(e){if(this.isCorruptionError(e)&&Ue(this.dbPath)){this.closeClient(),Ke(this.dbPath);for(let n of["-wal","-shm"]){let r=this.dbPath+n;if(Ue(r))Ke(r)}let t=this.open();if(Rt(t))return We(R("store",`Failed to recover database at ${this.dbPath}`,t.data));return t}return We(R("store",`Failed to open database at ${this.dbPath}`,e))}}close(){this.closeClient(),this.drizzle=null}transaction(e){let t=this.requireClient();if(this.txDepth===0){this.txDepth++;try{return t.transaction(()=>e(this))()}finally{this.txDepth--}}let n=`sp_${this.txDepth++}`;t.run(`SAVEPOINT "${n}"`);try{let r=e(this);return t.run(`RELEASE SAVEPOINT "${n}"`),r}catch(r){throw t.run(`ROLLBACK TO SAVEPOINT "${n}"`),t.run(`RELEASE SAVEPOINT "${n}"`),r}finally{this.txDepth--}}immediateTransaction(e){let t=this.requireClient();this.txDepth++,t.run("BEGIN IMMEDIATE");try{let n=e();return t.run("COMMIT"),n}catch(n){throw t.run("ROLLBACK"),n}finally{this.txDepth--}}query(e){let t=this.requireClient().prepare(e).get();if(!t)return null;return Object.values(t)[0]}getTableNames(){return this.requireClient().query("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((t)=>t.name)}selectOwner(){return this.requireClient().prepare("SELECT pid, heartbeat_at FROM watcher_owner WHERE id = 1").get()??void 0}insertOwner(e){let t=new Date().toISOString();this.requireClient().prepare("INSERT INTO watcher_owner (id, pid, started_at, heartbeat_at) VALUES (1, ?, ?, ?)").run(e,t,t)}replaceOwner(e){let t=new Date().toISOString();this.requireClient().prepare("INSERT OR REPLACE INTO watcher_owner (id, pid, started_at, heartbeat_at) VALUES (1, ?, ?, ?)").run(e,t,t)}touchOwner(e){let t=new Date().toISOString();this.requireClient().prepare("UPDATE watcher_owner SET heartbeat_at = ? WHERE id = 1 AND pid = ?").run(t,e)}deleteOwner(e){this.requireClient().prepare("DELETE FROM watcher_owner WHERE id = 1 AND pid = ?").run(e)}requireClient(){if(!this.client)throw Error("Database is not open. Call open() first.");return this.client}closeClient(){if(this.client)this.client.close(),this.client=null}isCorruptionError(e){if(!(e instanceof Error))return!1;let t=e.message.toLowerCase();return t.includes("malformed")||t.includes("corrupt")||t.includes("not a database")||t.includes("disk i/o error")||t.includes("sqlite_corrupt")}}import{eq as ie,and as Qe}from"drizzle-orm";class ke{db;constructor(e){this.db=e}getFile(e,t){return this.db.drizzleDb.select().from(I).where(Qe(ie(I.project,e),ie(I.filePath,t))).get()??null}upsertFile(e){this.db.drizzleDb.insert(I).values({project:e.project,filePath:e.filePath,mtimeMs:e.mtimeMs,size:e.size,contentHash:e.contentHash,updatedAt:e.updatedAt}).onConflictDoUpdate({target:[I.project,I.filePath],set:{mtimeMs:e.mtimeMs,size:e.size,contentHash:e.contentHash,updatedAt:e.updatedAt}}).run()}getAllFiles(e){return this.db.drizzleDb.select().from(I).where(ie(I.project,e)).all()}getFilesMap(e){let t=this.getAllFiles(e),n=new Map;for(let r of t)n.set(r.filePath,r);return n}deleteFile(e,t){this.db.drizzleDb.delete(I).where(Qe(ie(I.project,e),ie(I.filePath,t))).run()}}import{eq as _,and as Y,sql as Oe,count as Et}from"drizzle-orm";function me(e){return e.trim().split(/\s+/).map((t)=>t.trim()).filter((t)=>t.length>0).map((t)=>`"${t.replaceAll('"','""')}"*`).join(" ")}class ve{db;constructor(e){this.db=e}replaceFileSymbols(e,t,n,r){if(this.db.drizzleDb.delete(O).where(Y(_(O.project,e),_(O.filePath,t))).run(),!r.length)return;let i=new Date().toISOString();for(let s of r)this.db.drizzleDb.insert(O).values({project:e,filePath:t,kind:s.kind??"unknown",name:s.name??"",startLine:s.startLine??0,startColumn:s.startColumn??0,endLine:s.endLine??0,endColumn:s.endColumn??0,isExported:s.isExported??0,signature:s.signature??null,fingerprint:s.fingerprint??null,detailJson:s.detailJson??null,contentHash:n,indexedAt:s.indexedAt??i}).run()}getFileSymbols(e,t){return this.db.drizzleDb.select().from(O).where(Y(_(O.project,e),_(O.filePath,t))).all()}searchByName(e,t,n={}){let r=n.limit??50,i=me(t);if(!i)return[];return this.db.drizzleDb.select().from(O).where(Y(Oe`${O.id} IN (SELECT rowid FROM symbols_fts WHERE symbols_fts MATCH ${i})`,_(O.project,e),n.kind?_(O.kind,n.kind):void 0)).orderBy(O.name).limit(r).all()}searchByKind(e,t){return this.db.drizzleDb.select().from(O).where(Y(_(O.project,e),_(O.kind,t))).orderBy(O.name).all()}getStats(e){let t=this.db.drizzleDb.select({symbolCount:Et(),fileCount:Oe`COUNT(DISTINCT ${O.filePath})`}).from(O).where(_(O.project,e)).get();return{symbolCount:t?.symbolCount??0,fileCount:t?.fileCount??0}}getByFingerprint(e,t){return this.db.drizzleDb.select().from(O).where(Y(_(O.project,e),_(O.fingerprint,t))).all()}deleteFileSymbols(e,t){this.db.drizzleDb.delete(O).where(Y(_(O.project,e),_(O.filePath,t))).run()}searchByQuery(e){return this.db.drizzleDb.select().from(O).where(Y(e.ftsQuery?Oe`${O.id} IN (SELECT rowid FROM symbols_fts WHERE symbols_fts MATCH ${e.ftsQuery})`:void 0,e.project!==void 0?_(O.project,e.project):void 0,e.kind?_(O.kind,e.kind):void 0,e.filePath!==void 0?_(O.filePath,e.filePath):void 0,e.isExported!==void 0?_(O.isExported,e.isExported?1:0):void 0)).orderBy(O.name).limit(e.limit).all()}}import{eq as A,and as H,isNull as Xe,or as At}from"drizzle-orm";class Ce{db;constructor(e){this.db=e}replaceFileRelations(e,t,n){if(this.db.drizzleDb.delete(d).where(H(A(d.project,e),A(d.srcFilePath,t))).run(),!n.length)return;for(let r of n)this.db.drizzleDb.insert(d).values({project:e,type:r.type??"unknown",srcFilePath:r.srcFilePath??t,srcSymbolName:r.srcSymbolName??null,dstFilePath:r.dstFilePath??"",dstSymbolName:r.dstSymbolName??null,metaJson:r.metaJson??null}).run()}getOutgoing(e,t,n){if(n!==void 0)return this.db.drizzleDb.select({project:d.project,type:d.type,srcFilePath:d.srcFilePath,srcSymbolName:d.srcSymbolName,dstFilePath:d.dstFilePath,dstSymbolName:d.dstSymbolName,metaJson:d.metaJson}).from(d).where(H(A(d.project,e),A(d.srcFilePath,t),At(A(d.srcSymbolName,n),Xe(d.srcSymbolName)))).all();return this.db.drizzleDb.select({project:d.project,type:d.type,srcFilePath:d.srcFilePath,srcSymbolName:d.srcSymbolName,dstFilePath:d.dstFilePath,dstSymbolName:d.dstSymbolName,metaJson:d.metaJson}).from(d).where(H(A(d.project,e),A(d.srcFilePath,t))).all()}getIncoming(e,t){return this.db.drizzleDb.select({project:d.project,type:d.type,srcFilePath:d.srcFilePath,srcSymbolName:d.srcSymbolName,dstFilePath:d.dstFilePath,dstSymbolName:d.dstSymbolName,metaJson:d.metaJson}).from(d).where(H(A(d.project,e),A(d.dstFilePath,t))).all()}getByType(e,t){return this.db.drizzleDb.select({project:d.project,type:d.type,srcFilePath:d.srcFilePath,srcSymbolName:d.srcSymbolName,dstFilePath:d.dstFilePath,dstSymbolName:d.dstSymbolName,metaJson:d.metaJson}).from(d).where(H(A(d.project,e),A(d.type,t))).all()}deleteFileRelations(e,t){this.db.drizzleDb.delete(d).where(H(A(d.project,e),A(d.srcFilePath,t))).run()}searchRelations(e){return this.db.drizzleDb.select({project:d.project,type:d.type,srcFilePath:d.srcFilePath,srcSymbolName:d.srcSymbolName,dstFilePath:d.dstFilePath,dstSymbolName:d.dstSymbolName,metaJson:d.metaJson}).from(d).where(H(e.project!==void 0?A(d.project,e.project):void 0,e.srcFilePath!==void 0?A(d.srcFilePath,e.srcFilePath):void 0,e.srcSymbolName!==void 0?A(d.srcSymbolName,e.srcSymbolName):void 0,e.dstFilePath!==void 0?A(d.dstFilePath,e.dstFilePath):void 0,e.dstSymbolName!==void 0?A(d.dstSymbolName,e.dstSymbolName):void 0,e.type!==void 0?A(d.type,e.type):void 0)).limit(e.limit).all()}retargetRelations(e,t,n,r,i){let s=n===null?H(A(d.project,e),A(d.dstFilePath,t),Xe(d.dstSymbolName)):H(A(d.project,e),A(d.dstFilePath,t),A(d.dstSymbolName,n));this.db.drizzleDb.update(d).set({dstFilePath:r,dstSymbolName:i}).where(s).run()}}import{err as Ye}from"@zipbul/result";import{subscribe as Tt}from"@parcel/watcher";import Ee from"path";var Dt=["**/.git/**","**/.zipbul/**","**/dist/**","**/node_modules/**"],It=new Set(["package.json","tsconfig.json"]);function jt(e){return e.replaceAll("\\","/")}function _t(e){if(e==="update")return"change";if(e==="create")return"create";return"delete"}class de{#t;#e;#r;#i;#s;#n;constructor(e,t=Tt,n=console){this.#e=e.projectRoot,this.#r=[...Dt,...e.ignorePatterns??[]],this.#i=new Set((e.extensions??[".ts",".mts",".cts"]).map((r)=>r.toLowerCase())),this.#s=t,this.#n=n}async start(e){try{this.#t=await this.#s(this.#e,(t,n)=>{if(t){this.#n.error(R("watcher","Callback error",t));return}try{for(let r of n){let i=jt(Ee.relative(this.#e,r.path));if(i.startsWith(".."))continue;let s=Ee.basename(i),u=Ee.extname(i).toLowerCase();if(!It.has(s)&&!this.#i.has(u))continue;if(i.endsWith(".d.ts"))continue;e({eventType:_t(r.type),filePath:i})}}catch(r){this.#n.error(R("watcher","Callback error",r))}},{ignore:this.#r})}catch(t){return Ye(R("watcher","Failed to subscribe watcher",t))}}async close(){if(!this.#t)return;try{await this.#t.unsubscribe(),this.#t=void 0}catch(e){return Ye(R("watcher","Failed to close watcher",e))}}}import Ae from"path";import{promises as zt}from"fs";var Mt=["**/node_modules/**","**/.git/**","**/.zipbul/**","**/dist/**"];async function ue(e){let t=[];for await(let n of zt.glob("**/package.json",{cwd:e,exclude:Mt})){let r=Ae.dirname(n).replaceAll("\\","/"),i=Ae.join(e,n),s=await Bun.file(i).json(),u=typeof s?.name==="string"&&s.name.length>0?s.name:Ae.basename(r==="."?e:r);t.push({dir:r,project:u})}return t.sort((n,r)=>r.dir.length-n.dir.length),t}function q(e,t,n="default"){let r=e.replaceAll("\\","/");for(let i of t){if(i.dir===".")return i.project;if(r===i.dir||r.startsWith(`${i.dir}/`))return i.project}return n}import Ze from"path";var G=new Map;async function Lt(e){let t=Bun.file(e);if(!await t.exists())return null;let n=await t.json();return typeof n==="object"&&n!==null?n:null}async function se(e){if(G.has(e))return G.get(e)??null;let t=Ze.join(e,"tsconfig.json"),n=await Lt(t);if(!n)return G.set(e,null),null;let r=typeof n.compilerOptions==="object"&&n.compilerOptions!==null?n.compilerOptions:null;if(!r)return G.set(e,null),null;let i=typeof r.baseUrl==="string"?r.baseUrl:null,s=typeof r.paths==="object"&&r.paths!==null?r.paths:null;if(!i&&!s)return G.set(e,null),null;let u=i?Ze.resolve(e,i):e,m=new Map;if(s)for(let[h,f]of Object.entries(s)){if(!Array.isArray(f))continue;let x=f.filter((P)=>typeof P==="string");m.set(h,x)}let l={baseUrl:u,paths:m};return G.set(e,l),l}function pe(e){if(e){G.delete(e);return}G.clear()}import qe from"path";function Te(e,t){return qe.relative(e,t).replaceAll("\\","/")}function ee(e,t){return qe.resolve(e,t)}function W(e){let t=Bun.hash.xxHash64(e);return BigInt.asUintN(64,BigInt(t)).toString(16).padStart(16,"0")}import{isErr as nt}from"@zipbul/result";import{promises as Jt}from"fs";import{join as Bt}from"path";async function et(e){let{projectRoot:t,extensions:n,ignorePatterns:r,fileRepo:i}=e,s=i.getFilesMap(),u=new Set,m=[],l=[],h=r.map((x)=>new Bun.Glob(x));for await(let x of Jt.glob("**/*",{cwd:t})){if(!n.some((a)=>x.endsWith(a)))continue;if(h.some((a)=>a.match(x)))continue;u.add(x);let P=Bt(t,x),N=Bun.file(P),{size:C,lastModified:v}=N,b=s.get(x);if(!b){let a=await N.text(),g=W(a);m.push({filePath:x,contentHash:g,mtimeMs:v,size:C});continue}if(b.mtimeMs===v&&b.size===C){l.push({filePath:x,contentHash:b.contentHash,mtimeMs:v,size:C});continue}let o=await N.text(),c=W(o);if(c===b.contentHash)l.push({filePath:x,contentHash:c,mtimeMs:v,size:C});else m.push({filePath:x,contentHash:c,mtimeMs:v,size:C})}let f=[];for(let x of s.keys())if(!u.has(x))f.push(x);return{changed:m,unchanged:l,deleted:f}}function Ht(e){if(e.kind==="function"||e.kind==="method"){let t=e.parameters?.length??0,n=e.modifiers.includes("async")?1:0;return`params:${t}|async:${n}`}return null}function $t(e){let t={};if(e.jsDoc)t.jsDoc=e.jsDoc;if(e.kind==="function"||e.kind==="method"){if(e.parameters!==void 0)t.parameters=e.parameters;if(e.returnType!==void 0)t.returnType=e.returnType}if(e.heritage?.length)t.heritage=e.heritage;if(e.decorators?.length)t.decorators=e.decorators;if(e.typeParameters?.length)t.typeParameters=e.typeParameters;if(e.modifiers?.length)t.modifiers=e.modifiers;if(e.members?.length)t.members=e.members.map((n)=>n.name);return Object.keys(t).length>0?JSON.stringify(t):null}function tt(e,t,n,r,i){let s=Ht(e),u=W(`${t}|${e.kind}|${s??""}`);return{project:n,filePath:r,kind:e.kind,name:t,startLine:e.span.start.line,startColumn:e.span.start.column,endLine:e.span.end.line,endColumn:e.span.end.column,isExported:e.isExported?1:0,signature:s,fingerprint:u,detailJson:$t(e),contentHash:i,indexedAt:new Date().toISOString()}}function De(e){let{parsed:t,project:n,filePath:r,contentHash:i,symbolRepo:s}=e,u=ae(t),m=[];for(let l of u){m.push(tt(l,l.name,n,r,i));for(let h of l.members??[])m.push(tt(h,`${l.name}.${h.name}`,n,r,i))}s.replaceFileSymbols(n,r,i,m)}function Ie(e){let{ast:t,project:n,filePath:r,relationRepo:i,projectRoot:s,tsconfigPaths:u}=e,m=ee(s,r),l=le(t,m,u),h=[];for(let f of l){let x=Te(s,f.dstFilePath);if(x.startsWith(".."))continue;let P=Te(s,f.srcFilePath);h.push({project:n,type:f.type,srcFilePath:P,srcSymbolName:f.srcSymbolName??null,dstFilePath:x,dstSymbolName:f.dstSymbolName??null,metaJson:f.metaJson??null})}return i.replaceFileRelations(n,r,h),h.length}var Gt=100;class fe{opts;logger;callbacks=new Set;indexingLock=!1;pendingEvents=[];debounceTimer=null;currentIndexing=null;pendingFullIndex=!1;pendingFullIndexWaiters=[];tsconfigPathsRaw;boundariesRefresh=null;constructor(e){this.opts=e,this.logger=e.logger??console,this.tsconfigPathsRaw=se(e.projectRoot)}get tsconfigPaths(){return this.tsconfigPathsRaw}fullIndex(){return this.startIndex(void 0,!0)}incrementalIndex(e){return this.startIndex(e,!1)}onIndexed(e){return this.callbacks.add(e),()=>this.callbacks.delete(e)}handleWatcherEvent(e){if(e.filePath.endsWith("tsconfig.json")){pe(this.opts.projectRoot),this.tsconfigPathsRaw=se(this.opts.projectRoot),this.fullIndex().catch((t)=>{this.logger.error("[IndexCoordinator] fullIndex failed after tsconfig change:",t)});return}if(e.filePath.endsWith("package.json")){let t=this.opts.discoverProjectsFn??ue;this.boundariesRefresh=t(this.opts.projectRoot).then((n)=>{this.opts.boundaries=n})}if(this.pendingEvents.push(e),this.debounceTimer===null)this.debounceTimer=setTimeout(()=>{this.debounceTimer=null,this.flushPending()},Gt)}async shutdown(){if(this.debounceTimer!==null)clearTimeout(this.debounceTimer),this.debounceTimer=null;if(this.currentIndexing)await this.currentIndexing}startIndex(e,t){if(this.indexingLock){if(t)return this.pendingFullIndex=!0,new Promise((r,i)=>{this.pendingFullIndexWaiters.push({resolve:r,reject:i})});return this.currentIndexing}this.indexingLock=!0;let n=this.doIndex(e,t).then((r)=>{return this.fireCallbacks(r),r}).finally(()=>{if(this.indexingLock=!1,this.currentIndexing=null,this.pendingFullIndex){this.pendingFullIndex=!1;let r=this.pendingFullIndexWaiters.splice(0);this.startIndex(void 0,!0).then((i)=>{for(let s of r)s.resolve(i)}).catch((i)=>{for(let s of r)s.reject(i)})}else if(this.pendingEvents.length>0){let r=this.pendingEvents.splice(0);this.startIndex(r,!1).catch((i)=>this.logger.error("[IndexCoordinator] incremental drain error",i))}});return this.currentIndexing=n,n}async doIndex(e,t){let n=Date.now(),{fileRepo:r,symbolRepo:i,relationRepo:s,dbConnection:u}=this.opts;if(this.boundariesRefresh)await this.boundariesRefresh,this.boundariesRefresh=null;let m,l;if(e!==void 0)m=e.filter((b)=>b.eventType==="create"||b.eventType==="change").map((b)=>({filePath:b.filePath,contentHash:"",mtimeMs:0,size:0})),l=e.filter((b)=>b.eventType==="delete").map((b)=>b.filePath);else{let b=new Map;for(let c of this.opts.boundaries)for(let[a,g]of r.getFilesMap(c.project))b.set(a,g);let o=await et({projectRoot:this.opts.projectRoot,extensions:this.opts.extensions,ignorePatterns:this.opts.ignorePatterns,fileRepo:{getFilesMap:()=>b}});m=o.changed,l=o.deleted}let h=await this.tsconfigPathsRaw??void 0,f=new Map;for(let b of l){let o=q(b,this.opts.boundaries),c=i.getFileSymbols(o,b);f.set(b,c)}let x=()=>{for(let b of l){let o=q(b,this.opts.boundaries);i.deleteFileSymbols(o,b),s.deleteFileRelations(o,b),r.deleteFile(o,b)}},P=async()=>{let b=0,o=0,c=[];for(let a of m)try{let g=await this.processFile(a.filePath,a.contentHash||void 0,h);b+=g.symbolCount,o+=g.relCount}catch(g){this.logger.error(`[IndexCoordinator] Failed to index ${a.filePath}:`,g),c.push(a.filePath)}return{symbols:b,relations:o,failedFiles:c}},N=0,C=0,v=[];if(t){let{projectRoot:b,boundaries:o}=this.opts,{parseCache:c}=this.opts,a=await Promise.allSettled(m.map(async(p)=>{let S=ee(b,p.filePath),k=Bun.file(S),w=await k.text(),E=p.contentHash||W(w);return{filePath:p.filePath,text:w,contentHash:E,mtimeMs:k.lastModified,size:k.size}})),g=a.filter((p)=>p.status==="fulfilled").map((p)=>p.value);for(let p of a)if(p.status==="rejected")this.logger.error("[IndexCoordinator] Failed to pre-read file:",p.reason);let y=[];u.transaction(()=>{for(let S of o){let k=r.getAllFiles(S.project);for(let w of k)r.deleteFile(w.project,w.filePath)}let p=this.opts.parseSourceFn??re;for(let S of g){let k=q(S.filePath,o),w=p(ee(b,S.filePath),S.text);if(nt(w))throw w.data;let E=w;y.push({filePath:S.filePath,parsed:E}),r.upsertFile({project:k,filePath:S.filePath,mtimeMs:S.mtimeMs,size:S.size,contentHash:S.contentHash,updatedAt:new Date().toISOString()}),De({parsed:E,project:k,filePath:S.filePath,contentHash:S.contentHash,symbolRepo:i}),C+=Ie({ast:E.program,project:k,filePath:S.filePath,relationRepo:s,projectRoot:b,tsconfigPaths:h}),N+=i.getFileSymbols(k,S.filePath).length}});for(let p of y)c.set(p.filePath,p.parsed)}else{x();let b=await P();N=b.symbols,C=b.relations,v=b.failedFiles}if(!t)for(let[b,o]of f)for(let c of o){if(!c.fingerprint)continue;let a=q(b,this.opts.boundaries),g=i.getByFingerprint(a,c.fingerprint);if(g.length===1){let y=g[0];s.retargetRelations(a,b,c.name,y.filePath,y.name)}}return{indexedFiles:m.length,removedFiles:l.length,totalSymbols:N,totalRelations:C,durationMs:Date.now()-n,changedFiles:m.map((b)=>b.filePath),deletedFiles:[...l],failedFiles:v}}async processFile(e,t,n){let{projectRoot:r,boundaries:i}=this.opts,{fileRepo:s,symbolRepo:u,relationRepo:m,parseCache:l}=this.opts,h=ee(r,e),f=Bun.file(h),x=await f.text(),P=t||W(x),N=q(e,i),v=(this.opts.parseSourceFn??re)(h,x);if(nt(v))throw v.data;let b=v;l.set(e,b),s.upsertFile({project:N,filePath:e,mtimeMs:f.lastModified,size:f.size,contentHash:P,updatedAt:new Date().toISOString()}),De({parsed:b,project:N,filePath:e,contentHash:P,symbolRepo:u});let o=Ie({ast:b.program,project:N,filePath:e,relationRepo:m,projectRoot:r,tsconfigPaths:n});return{symbolCount:u.getFileSymbols(N,e).length,relCount:o}}fireCallbacks(e){for(let t of this.callbacks)try{t(e)}catch(n){this.logger.error("[IndexCoordinator] onIndexed callback threw:",n)}}flushPending(){if(this.indexingLock)return;if(this.pendingEvents.length>0){let e=this.pendingEvents.splice(0);this.startIndex(e,!1).catch((t)=>this.logger.error("[IndexCoordinator] flushPending startIndex error:",t))}}}function Wt(e){try{return process.kill(e,0),!0}catch(t){if(typeof t==="object"&&t&&"code"in t)return t.code!=="ESRCH";return!0}}function Kt(e){let t=new Date(e).getTime();return Number.isNaN(t)?0:t}function rt(e,t,n={}){let r=n.now??Date.now,i=n.isAlive??Wt,s=n.staleAfterSeconds??90;return e.immediateTransaction(()=>{let u=e.selectOwner();if(!u)return e.insertOwner(t),"owner";let m=Math.floor((r()-Kt(u.heartbeat_at))/1000);if(i(u.pid)&&m<s)return"reader";return e.replaceOwner(t),"owner"})}function it(e,t){e.deleteOwner(t)}function st(e,t){e.touchOwner(t)}function je(e){let{symbolRepo:t,project:n,query:r}=e,i=r.project??n,s=r.limit??100,u={kind:r.kind,filePath:r.filePath,isExported:r.isExported,project:i,limit:s};if(r.text){let l=me(r.text);if(l)u.ftsQuery=l}return t.searchByQuery(u).map((l)=>({id:l.id,filePath:l.filePath,kind:l.kind,name:l.name,span:{start:{line:l.startLine,column:l.startColumn},end:{line:l.endLine,column:l.endColumn}},isExported:l.isExported===1,signature:l.signature,fingerprint:l.fingerprint,detail:l.detailJson?(()=>{try{return JSON.parse(l.detailJson)}catch{return{}}})():{}}))}function _e(e){let{relationRepo:t,project:n,query:r}=e,i=r.project??n,s=r.limit??500;return t.searchRelations({srcFilePath:r.srcFilePath,srcSymbolName:r.srcSymbolName,dstFilePath:r.dstFilePath,dstSymbolName:r.dstSymbolName,type:r.type,project:i,limit:s}).map((m)=>({type:m.type,srcFilePath:m.srcFilePath,srcSymbolName:m.srcSymbolName,dstFilePath:m.dstFilePath,dstSymbolName:m.dstSymbolName,metaJson:m.metaJson??void 0}))}class oe{options;adjacencyList=new Map;reverseAdjacencyList=new Map;constructor(e){this.options=e}build(){this.adjacencyList=new Map,this.reverseAdjacencyList=new Map;let e=this.options.relationRepo.getByType(this.options.project,"imports");for(let t of e){let{srcFilePath:n,dstFilePath:r}=t;if(!this.adjacencyList.has(n))this.adjacencyList.set(n,new Set);if(this.adjacencyList.get(n).add(r),!this.reverseAdjacencyList.has(r))this.reverseAdjacencyList.set(r,new Set);this.reverseAdjacencyList.get(r).add(n)}}getDependencies(e){return Array.from(this.adjacencyList.get(e)??[])}getDependents(e){return Array.from(this.reverseAdjacencyList.get(e)??[])}getTransitiveDependents(e){let t=new Set,n=[e];while(n.length>0){let r=n.shift();for(let i of this.reverseAdjacencyList.get(r)??[])if(!t.has(i))t.add(i),n.push(i)}return Array.from(t)}hasCycle(){let e=new Set,t=new Set;for(let n of this.adjacencyList.keys()){if(e.has(n))continue;let r=[{node:n,entered:!1}];while(r.length>0){let i=r.pop();if(i.entered){t.delete(i.node);continue}if(t.has(i.node))return!0;if(e.has(i.node))continue;e.add(i.node),t.add(i.node),r.push({node:i.node,entered:!0});for(let s of this.adjacencyList.get(i.node)??[]){if(t.has(s))return!0;if(!e.has(s))r.push({node:s,entered:!1})}}}return!1}getAffectedByChange(e){let t=new Set;for(let n of e)for(let r of this.getTransitiveDependents(n))t.add(r);return Array.from(t)}}var at=30000,lt=60000,Vt=10;class ze{projectRoot;db;symbolRepo;relationRepo;parseCache;coordinator;watcher;releaseWatcherRoleFn;parseSourceFn;extractSymbolsFn;extractRelationsFn;symbolSearchFn;relationSearchFn;logger;defaultProject;role;timer=null;signalHandlers=[];closed=!1;tsconfigPaths=null;boundaries=[];onIndexedCallbacks=new Set;constructor(e){this.projectRoot=e.projectRoot,this.db=e.db,this.symbolRepo=e.symbolRepo,this.relationRepo=e.relationRepo,this.parseCache=e.parseCache,this.coordinator=e.coordinator,this.watcher=e.watcher,this.releaseWatcherRoleFn=e.releaseWatcherRoleFn,this.parseSourceFn=e.parseSourceFn,this.extractSymbolsFn=e.extractSymbolsFn,this.extractRelationsFn=e.extractRelationsFn,this.symbolSearchFn=e.symbolSearchFn,this.relationSearchFn=e.relationSearchFn,this.logger=e.logger,this.defaultProject=e.defaultProject,this.role=e.role}static async open(e){let{projectRoot:t,extensions:n=[".ts",".mts",".cts"],ignorePatterns:r=[],parseCacheCapacity:i=500,logger:s=console,existsSyncFn:u=Ut,dbConnectionFactory:m,watcherFactory:l,coordinatorFactory:h,repositoryFactory:f,acquireWatcherRoleFn:x=rt,releaseWatcherRoleFn:P=it,updateHeartbeatFn:N=st,discoverProjectsFn:C=ue,parseSourceFn:v=re,extractSymbolsFn:b=ae,extractRelationsFn:o=le,symbolSearchFn:c=je,relationSearchFn:a=_e,loadTsconfigPathsFn:g=se}=e;if(!ot.isAbsolute(t))return T(R("validation",`Gildash: projectRoot must be an absolute path, got: "${t}"`));if(!u(t))return T(R("validation",`Gildash: projectRoot does not exist: "${t}"`));let y=m?m():new Ne({projectRoot:t}),p=y.open();if(te(p))return p;try{let S=await C(t),k=S[0]?.project??ot.basename(t),w=f?f():(()=>{let j=y;return{fileRepo:new ke(j),symbolRepo:new ve(j),relationRepo:new Ce(j),parseCache:new ye(i)}})(),E=await Promise.resolve(x(y,process.pid,{})),z=null,B=null,F=new ze({projectRoot:t,db:y,symbolRepo:w.symbolRepo,relationRepo:w.relationRepo,parseCache:w.parseCache,coordinator:z,watcher:B,releaseWatcherRoleFn:P,parseSourceFn:v,extractSymbolsFn:b,extractRelationsFn:o,symbolSearchFn:c,relationSearchFn:a,logger:s,defaultProject:k,role:E});if(pe(t),F.tsconfigPaths=await g(t),F.boundaries=S,E==="owner"){let j=l?l():new de({projectRoot:t,ignorePatterns:r,extensions:n},void 0,s),M=h?h():new fe({projectRoot:t,boundaries:S,extensions:n,ignorePatterns:r,dbConnection:y,parseCache:w.parseCache,fileRepo:w.fileRepo,symbolRepo:w.symbolRepo,relationRepo:w.relationRepo,logger:s});F.coordinator=M,F.watcher=j,await j.start(($)=>M.handleWatcherEvent?.($)).then(($)=>{if(te($))throw $.data});let ne=setInterval(()=>{N(y,process.pid)},at);F.timer=ne,await M.fullIndex()}else{let j=0,M=async()=>{try{let $=await Promise.resolve(x(y,process.pid,{}));if(j=0,$==="owner"){clearInterval(F.timer),F.timer=null;let U=null,V=null;try{U=l?l():new de({projectRoot:t,ignorePatterns:r,extensions:n},void 0,s),V=h?h():new fe({projectRoot:t,boundaries:S,extensions:n,ignorePatterns:r,dbConnection:y,parseCache:w.parseCache,fileRepo:w.fileRepo,symbolRepo:w.symbolRepo,relationRepo:w.relationRepo,logger:s});for(let L of F.onIndexedCallbacks)V.onIndexed(L);await U.start((L)=>V?.handleWatcherEvent?.(L)).then((L)=>{if(te(L))throw L.data});let ge=setInterval(()=>{N(y,process.pid)},at);F.timer=ge,F.coordinator=V,F.watcher=U,await V.fullIndex()}catch(ge){if(s.error("[Gildash] owner promotion failed, reverting to reader",ge),U){let L=await U.close();if(te(L))s.error("[Gildash] watcher close error during promotion rollback",L.data);F.watcher=null}if(V)await V.shutdown().catch((L)=>s.error("[Gildash] coordinator shutdown error during promotion rollback",L)),F.coordinator=null;if(F.timer===null)F.timer=setInterval(M,lt)}}}catch($){if(j++,s.error("[Gildash] healthcheck error",$),j>=Vt)s.error("[Gildash] healthcheck failed too many times, shutting down"),clearInterval(F.timer),F.timer=null,F.close().catch((U)=>s.error("[Gildash] close error during healthcheck shutdown",U))}},ne=setInterval(M,lt);F.timer=ne}let K=["SIGTERM","SIGINT","beforeExit"];for(let j of K){let M=()=>{F.close().catch((ne)=>s.error("[Gildash] close error during signal",j,ne))};if(j==="beforeExit")process.on("beforeExit",M);else process.on(j,M);F.signalHandlers.push([j,M])}return F}catch(S){return y.close(),T(R("store","Gildash: initialization failed",S))}}async close(){if(this.closed)return;this.closed=!0;let e=[];for(let[t,n]of this.signalHandlers)if(t==="beforeExit")process.off("beforeExit",n);else process.off(t,n);if(this.signalHandlers=[],this.coordinator)try{await this.coordinator.shutdown()}catch(t){e.push(t instanceof Error?t:Error(String(t)))}if(this.watcher){let t=await this.watcher.close();if(te(t))e.push(t.data)}if(this.timer!==null)clearInterval(this.timer),this.timer=null;try{this.releaseWatcherRoleFn(this.db,process.pid)}catch(t){e.push(t instanceof Error?t:Error(String(t)))}try{this.db.close()}catch(t){e.push(t instanceof Error?t:Error(String(t)))}if(e.length>0)return T(R("close","Gildash: one or more errors occurred during close()",e))}onIndexed(e){if(this.onIndexedCallbacks.add(e),!this.coordinator)return()=>{this.onIndexedCallbacks.delete(e)};let t=this.coordinator.onIndexed(e);return()=>{this.onIndexedCallbacks.delete(e),t()}}parseSource(e,t){if(this.closed)return T(R("closed","Gildash: instance is closed"));let n=this.parseSourceFn(e,t);if(te(n))return n;return this.parseCache.set(e,n),n}extractSymbols(e){if(this.closed)return T(R("closed","Gildash: instance is closed"));return this.extractSymbolsFn(e)}extractRelations(e){if(this.closed)return T(R("closed","Gildash: instance is closed"));return this.extractRelationsFn(e.program,e.filePath,this.tsconfigPaths??void 0)}async reindex(){if(this.closed)return T(R("closed","Gildash: instance is closed"));if(!this.coordinator)return T(R("closed","Gildash: reindex() is not available for readers"));try{return await this.coordinator.fullIndex()}catch(e){return T(R("index","Gildash: reindex failed",e))}}get projects(){return[...this.boundaries]}getStats(e){if(this.closed)return T(R("closed","Gildash: instance is closed"));try{return this.symbolRepo.getStats(e??this.defaultProject)}catch(t){return T(R("store","Gildash: getStats failed",t))}}searchSymbols(e){if(this.closed)return T(R("closed","Gildash: instance is closed"));try{return this.symbolSearchFn({symbolRepo:this.symbolRepo,project:this.defaultProject,query:e})}catch(t){return T(R("search","Gildash: searchSymbols failed",t))}}searchRelations(e){if(this.closed)return T(R("closed","Gildash: instance is closed"));try{return this.relationSearchFn({relationRepo:this.relationRepo,project:this.defaultProject,query:e})}catch(t){return T(R("search","Gildash: searchRelations failed",t))}}getDependencies(e,t,n=1e4){if(this.closed)return T(R("closed","Gildash: instance is closed"));try{return this.relationSearchFn({relationRepo:this.relationRepo,project:t??this.defaultProject,query:{srcFilePath:e,type:"imports",project:t??this.defaultProject,limit:n}}).map((r)=>r.dstFilePath)}catch(r){return T(R("search","Gildash: getDependencies failed",r))}}getDependents(e,t,n=1e4){if(this.closed)return T(R("closed","Gildash: instance is closed"));try{return this.relationSearchFn({relationRepo:this.relationRepo,project:t??this.defaultProject,query:{dstFilePath:e,type:"imports",project:t??this.defaultProject,limit:n}}).map((r)=>r.srcFilePath)}catch(r){return T(R("search","Gildash: getDependents failed",r))}}async getAffected(e,t){if(this.closed)return T(R("closed","Gildash: instance is closed"));try{let n=new oe({relationRepo:this.relationRepo,project:t??this.defaultProject});return await n.build(),n.getAffectedByChange(e)}catch(n){return T(R("search","Gildash: getAffected failed",n))}}async hasCycle(e){if(this.closed)return T(R("closed","Gildash: instance is closed"));try{let t=new oe({relationRepo:this.relationRepo,project:e??this.defaultProject});return await t.build(),t.hasCycle()}catch(t){return T(R("search","Gildash: hasCycle failed",t))}}}export{je as symbolSearch,_e as relationSearch,R as gildashError,ze as Gildash,oe as DependencyGraph};
2734
24
 
2735
- //# debugId=C2664325F1F8637F64756E2164756E21
25
+ //# debugId=CA2B473191EED17D64756E2164756E21
2736
26
  //# sourceMappingURL=index.js.map