@optave/codegraph 3.4.1 → 3.5.0
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/README.md +22 -21
- package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
- package/dist/ast-analysis/rules/javascript.js +1 -0
- package/dist/ast-analysis/rules/javascript.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +103 -35
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/db/better-sqlite3.d.ts +3 -0
- package/dist/db/better-sqlite3.d.ts.map +1 -0
- package/dist/db/better-sqlite3.js +19 -0
- package/dist/db/better-sqlite3.js.map +1 -0
- package/dist/db/connection.d.ts +13 -2
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +76 -2
- package/dist/db/connection.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +2 -0
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/query-builder.d.ts +5 -5
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +20 -4
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/repository/index.d.ts +1 -0
- package/dist/db/repository/index.d.ts.map +1 -1
- package/dist/db/repository/index.js +1 -0
- package/dist/db/repository/index.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +58 -0
- package/dist/db/repository/native-repository.d.ts.map +1 -0
- package/dist/db/repository/native-repository.js +261 -0
- package/dist/db/repository/native-repository.js.map +1 -0
- package/dist/db/repository/nodes.d.ts +4 -4
- package/dist/db/repository/nodes.d.ts.map +1 -1
- package/dist/db/repository/nodes.js +6 -6
- package/dist/db/repository/nodes.js.map +1 -1
- package/dist/domain/graph/builder/context.d.ts +2 -1
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js +1 -0
- package/dist/domain/graph/builder/context.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +29 -7
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +103 -15
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +33 -5
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +71 -7
- package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +36 -14
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +43 -20
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +104 -9
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +1 -0
- package/dist/domain/parser.js.map +1 -1
- package/dist/extractors/javascript.js +53 -38
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/rust.js +2 -1
- package/dist/extractors/rust.js.map +1 -1
- package/dist/features/ast.d.ts +14 -1
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +38 -1
- package/dist/features/ast.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +3 -1
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/snapshot.d.ts.map +1 -1
- package/dist/features/snapshot.js +2 -1
- package/dist/features/snapshot.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +2 -9
- package/dist/mcp/server.js.map +1 -1
- package/dist/types.d.ts +228 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +61 -11
- package/src/ast-analysis/rules/javascript.ts +1 -0
- package/src/ast-analysis/visitors/ast-store-visitor.ts +102 -33
- package/src/db/better-sqlite3.ts +20 -0
- package/src/db/connection.ts +94 -3
- package/src/db/index.ts +3 -1
- package/src/db/migrations.ts +2 -0
- package/src/db/query-builder.ts +30 -5
- package/src/db/repository/index.ts +1 -0
- package/src/db/repository/native-repository.ts +361 -0
- package/src/db/repository/nodes.ts +7 -3
- package/src/domain/graph/builder/context.ts +2 -0
- package/src/domain/graph/builder/pipeline.ts +30 -6
- package/src/domain/graph/builder/stages/build-edges.ts +117 -19
- package/src/domain/graph/builder/stages/build-structure.ts +47 -11
- package/src/domain/graph/builder/stages/collect-files.ts +84 -7
- package/src/domain/graph/builder/stages/detect-changes.ts +39 -21
- package/src/domain/graph/builder/stages/finalize.ts +49 -20
- package/src/domain/graph/builder/stages/insert-nodes.ts +129 -10
- package/src/domain/parser.ts +1 -0
- package/src/extractors/javascript.ts +54 -36
- package/src/extractors/rust.ts +2 -1
- package/src/features/ast.ts +66 -1
- package/src/features/branch-compare.ts +3 -1
- package/src/features/snapshot.ts +2 -1
- package/src/mcp/server.ts +2 -10
- package/src/types.ts +273 -0
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Batch-inserts file nodes, definitions, exports, children, and contains/parameter_of edges.
|
|
5
5
|
* Updates file hashes for incremental builds.
|
|
6
|
+
*
|
|
7
|
+
* When the native engine is available, delegates all SQLite writes to Rust via
|
|
8
|
+
* `bulkInsertNodes` — eliminating JS↔C boundary overhead. Falls back to the
|
|
9
|
+
* JS implementation on failure or when native is unavailable.
|
|
6
10
|
*/
|
|
7
11
|
import path from 'node:path';
|
|
8
12
|
import { performance } from 'node:perf_hooks';
|
|
@@ -32,7 +36,112 @@ interface PrecomputedFileData {
|
|
|
32
36
|
_reverseDepOnly?: boolean;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
// ──
|
|
39
|
+
// ── Native fast-path ─────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
function tryNativeInsert(ctx: PipelineContext): boolean {
|
|
42
|
+
// Use NativeDatabase persistent connection (Phase 6.15+).
|
|
43
|
+
// Standalone napi functions were removed in 6.17 — falls through to JS if nativeDb unavailable.
|
|
44
|
+
if (!ctx.nativeDb?.bulkInsertNodes) return false;
|
|
45
|
+
|
|
46
|
+
const { allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
|
|
47
|
+
|
|
48
|
+
// Marshal allSymbols → InsertNodesBatch[]
|
|
49
|
+
const batches: Array<{
|
|
50
|
+
file: string;
|
|
51
|
+
definitions: Array<{
|
|
52
|
+
name: string;
|
|
53
|
+
kind: string;
|
|
54
|
+
line: number;
|
|
55
|
+
endLine?: number | null;
|
|
56
|
+
visibility?: string | null;
|
|
57
|
+
children: Array<{
|
|
58
|
+
name: string;
|
|
59
|
+
kind: string;
|
|
60
|
+
line: number;
|
|
61
|
+
endLine?: number | null;
|
|
62
|
+
visibility?: string | null;
|
|
63
|
+
}>;
|
|
64
|
+
}>;
|
|
65
|
+
exports: Array<{ name: string; kind: string; line: number }>;
|
|
66
|
+
}> = [];
|
|
67
|
+
|
|
68
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
69
|
+
batches.push({
|
|
70
|
+
file: relPath,
|
|
71
|
+
definitions: symbols.definitions.map((def) => ({
|
|
72
|
+
name: def.name,
|
|
73
|
+
kind: def.kind,
|
|
74
|
+
line: def.line,
|
|
75
|
+
endLine: def.endLine ?? null,
|
|
76
|
+
visibility: def.visibility ?? null,
|
|
77
|
+
children: (def.children ?? []).map((c) => ({
|
|
78
|
+
name: c.name,
|
|
79
|
+
kind: c.kind,
|
|
80
|
+
line: c.line,
|
|
81
|
+
endLine: c.endLine ?? null,
|
|
82
|
+
visibility: c.visibility ?? null,
|
|
83
|
+
})),
|
|
84
|
+
})),
|
|
85
|
+
exports: symbols.exports.map((exp) => ({
|
|
86
|
+
name: exp.name,
|
|
87
|
+
kind: exp.kind,
|
|
88
|
+
line: exp.line,
|
|
89
|
+
})),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Build file hash entries
|
|
94
|
+
const precomputedData = new Map<string, PrecomputedFileData>();
|
|
95
|
+
for (const item of filesToParse) {
|
|
96
|
+
if (item.relPath) precomputedData.set(item.relPath, item as PrecomputedFileData);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const fileHashes: Array<{ file: string; hash: string; mtime: number; size: number }> = [];
|
|
100
|
+
for (const [relPath] of allSymbols) {
|
|
101
|
+
const precomputed = precomputedData.get(relPath);
|
|
102
|
+
if (precomputed?._reverseDepOnly) {
|
|
103
|
+
continue; // file unchanged, hash already correct
|
|
104
|
+
}
|
|
105
|
+
if (precomputed?.hash) {
|
|
106
|
+
let mtime: number;
|
|
107
|
+
let size: number;
|
|
108
|
+
if (precomputed.stat) {
|
|
109
|
+
mtime = precomputed.stat.mtime;
|
|
110
|
+
size = precomputed.stat.size;
|
|
111
|
+
} else {
|
|
112
|
+
const rawStat = fileStat(path.join(rootDir, relPath));
|
|
113
|
+
mtime = rawStat ? Math.floor(rawStat.mtimeMs) : 0;
|
|
114
|
+
size = rawStat ? rawStat.size : 0;
|
|
115
|
+
}
|
|
116
|
+
fileHashes.push({ file: relPath, hash: precomputed.hash, mtime, size });
|
|
117
|
+
} else {
|
|
118
|
+
const absPath = path.join(rootDir, relPath);
|
|
119
|
+
let code: string | null;
|
|
120
|
+
try {
|
|
121
|
+
code = readFileSafe(absPath);
|
|
122
|
+
} catch {
|
|
123
|
+
code = null;
|
|
124
|
+
}
|
|
125
|
+
if (code !== null) {
|
|
126
|
+
const stat = fileStat(absPath);
|
|
127
|
+
const mtime = stat ? Math.floor(stat.mtimeMs) : 0;
|
|
128
|
+
const size = stat ? stat.size : 0;
|
|
129
|
+
fileHashes.push({ file: relPath, hash: fileHash(code), mtime, size });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Also include metadata-only updates (self-heal mtime/size without re-parse)
|
|
135
|
+
for (const item of metadataUpdates) {
|
|
136
|
+
const mtime = item.stat ? Math.floor(item.stat.mtime) : 0;
|
|
137
|
+
const size = item.stat ? item.stat.size : 0;
|
|
138
|
+
fileHashes.push({ file: item.relPath, hash: item.hash, mtime, size });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return ctx.nativeDb.bulkInsertNodes(batches, fileHashes, removed);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── JS fallback: Phase 1 ────────────────────────────────────────────
|
|
36
145
|
|
|
37
146
|
function insertDefinitionsAndExports(
|
|
38
147
|
db: BetterSqlite3Database,
|
|
@@ -90,7 +199,7 @@ function insertDefinitionsAndExports(
|
|
|
90
199
|
}
|
|
91
200
|
}
|
|
92
201
|
|
|
93
|
-
// ── Phase 2+3
|
|
202
|
+
// ── JS fallback: Phase 2+3 ──────────────────────────────────────────
|
|
94
203
|
|
|
95
204
|
function insertChildrenAndEdges(
|
|
96
205
|
db: BetterSqlite3Database,
|
|
@@ -165,7 +274,7 @@ function insertChildrenAndEdges(
|
|
|
165
274
|
batchInsertEdges(db, edgeRows);
|
|
166
275
|
}
|
|
167
276
|
|
|
168
|
-
// ──
|
|
277
|
+
// ── JS fallback: Phase 4 ────────────────────────────────────────────
|
|
169
278
|
|
|
170
279
|
function updateFileHashes(
|
|
171
280
|
_db: BetterSqlite3Database,
|
|
@@ -218,11 +327,27 @@ function updateFileHashes(
|
|
|
218
327
|
}
|
|
219
328
|
}
|
|
220
329
|
|
|
221
|
-
// ── Main entry point
|
|
330
|
+
// ── Main entry point ────────────────────────────────────────────────
|
|
222
331
|
|
|
223
332
|
export async function insertNodes(ctx: PipelineContext): Promise<void> {
|
|
224
333
|
const { db, allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
|
|
225
334
|
|
|
335
|
+
// Populate fileSymbols before any DB writes (used by later stages)
|
|
336
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
337
|
+
ctx.fileSymbols.set(relPath, symbols);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const t0 = performance.now();
|
|
341
|
+
|
|
342
|
+
// Try native Rust path first — single transaction, no JS↔C overhead
|
|
343
|
+
if (ctx.engineName === 'native' && tryNativeInsert(ctx)) {
|
|
344
|
+
ctx.timing.insertMs = performance.now() - t0;
|
|
345
|
+
|
|
346
|
+
// Removed-file hash cleanup is handled inside the native call
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// JS fallback
|
|
226
351
|
const precomputedData = new Map<string, PrecomputedFileData>();
|
|
227
352
|
for (const item of filesToParse) {
|
|
228
353
|
if (item.relPath) precomputedData.set(item.relPath, item as PrecomputedFileData);
|
|
@@ -237,18 +362,12 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
|
|
|
237
362
|
upsertHash = null;
|
|
238
363
|
}
|
|
239
364
|
|
|
240
|
-
// Populate fileSymbols before the transaction so it is a pure input
|
|
241
|
-
for (const [relPath, symbols] of allSymbols) {
|
|
242
|
-
ctx.fileSymbols.set(relPath, symbols);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
365
|
const insertAll = db.transaction(() => {
|
|
246
366
|
insertDefinitionsAndExports(db, allSymbols);
|
|
247
367
|
insertChildrenAndEdges(db, allSymbols);
|
|
248
368
|
updateFileHashes(db, allSymbols, precomputedData, metadataUpdates, rootDir, upsertHash);
|
|
249
369
|
});
|
|
250
370
|
|
|
251
|
-
const t0 = performance.now();
|
|
252
371
|
insertAll();
|
|
253
372
|
ctx.timing.insertMs = performance.now() - t0;
|
|
254
373
|
|
package/src/domain/parser.ts
CHANGED
|
@@ -85,6 +85,7 @@ const COMMON_QUERY_PATTERNS: string[] = [
|
|
|
85
85
|
'(variable_declarator name: (identifier) @varfn_name value: (arrow_function) @varfn_value)',
|
|
86
86
|
'(variable_declarator name: (identifier) @varfn_name value: (function_expression) @varfn_value)',
|
|
87
87
|
'(method_definition name: (property_identifier) @meth_name) @meth_node',
|
|
88
|
+
'(method_definition name: (private_property_identifier) @meth_name) @meth_node',
|
|
88
89
|
'(import_statement source: (string) @imp_source) @imp_node',
|
|
89
90
|
'(export_statement) @exp_node',
|
|
90
91
|
'(call_expression function: (identifier) @callfn_name) @callfn_node',
|
|
@@ -260,51 +260,69 @@ function extractSymbolsQuery(tree: TreeSitterTree, query: TreeSitterQuery): Extr
|
|
|
260
260
|
return { definitions, calls, imports, classes, exports: exps, typeMap };
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
/** Node types that define a function scope — constants inside these are skipped. */
|
|
264
|
+
const FUNCTION_SCOPE_TYPES = new Set([
|
|
265
|
+
'function_declaration',
|
|
266
|
+
'arrow_function',
|
|
267
|
+
'function_expression',
|
|
268
|
+
'method_definition',
|
|
269
|
+
'generator_function_declaration',
|
|
270
|
+
'generator_function',
|
|
271
|
+
]);
|
|
272
|
+
|
|
263
273
|
/**
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
274
|
+
* Recursively walk the AST to extract `const x = <literal>` as constants.
|
|
275
|
+
* Skips nodes inside function scopes so only file-level / block-level constants
|
|
276
|
+
* are captured — matching the native engine's behaviour.
|
|
267
277
|
*/
|
|
268
|
-
function extractConstantsWalk(
|
|
269
|
-
for (let i = 0; i <
|
|
270
|
-
const
|
|
271
|
-
if (!
|
|
278
|
+
function extractConstantsWalk(node: TreeSitterNode, definitions: Definition[]): void {
|
|
279
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
280
|
+
const child = node.child(i);
|
|
281
|
+
if (!child) continue;
|
|
282
|
+
|
|
283
|
+
// Don't descend into function scopes
|
|
284
|
+
if (FUNCTION_SCOPE_TYPES.has(child.type)) continue;
|
|
272
285
|
|
|
273
|
-
let declNode =
|
|
286
|
+
let declNode = child;
|
|
274
287
|
// Handle `export const …` — unwrap the export_statement to its declaration child
|
|
275
|
-
if (
|
|
276
|
-
const inner =
|
|
277
|
-
if (
|
|
278
|
-
declNode = inner;
|
|
288
|
+
if (child.type === 'export_statement') {
|
|
289
|
+
const inner = child.childForFieldName('declaration');
|
|
290
|
+
if (inner) declNode = inner;
|
|
279
291
|
}
|
|
280
292
|
|
|
281
293
|
const t = declNode.type;
|
|
282
|
-
if (t
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
294
|
+
if (t === 'lexical_declaration' || t === 'variable_declaration') {
|
|
295
|
+
if (declNode.text.startsWith('const ')) {
|
|
296
|
+
for (let j = 0; j < declNode.childCount; j++) {
|
|
297
|
+
const declarator = declNode.child(j);
|
|
298
|
+
if (!declarator || declarator.type !== 'variable_declarator') continue;
|
|
299
|
+
const nameN = declarator.childForFieldName('name');
|
|
300
|
+
const valueN = declarator.childForFieldName('value');
|
|
301
|
+
if (!nameN || nameN.type !== 'identifier' || !valueN) continue;
|
|
302
|
+
// Skip functions — already captured by query patterns
|
|
303
|
+
const valType = valueN.type;
|
|
304
|
+
if (
|
|
305
|
+
valType === 'arrow_function' ||
|
|
306
|
+
valType === 'function_expression' ||
|
|
307
|
+
valType === 'function'
|
|
308
|
+
)
|
|
309
|
+
continue;
|
|
310
|
+
if (isConstantValue(valueN)) {
|
|
311
|
+
definitions.push({
|
|
312
|
+
name: nameN.text,
|
|
313
|
+
kind: 'constant',
|
|
314
|
+
line: declNode.startPosition.row + 1,
|
|
315
|
+
endLine: nodeEndLine(declNode),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
306
319
|
}
|
|
307
320
|
}
|
|
321
|
+
|
|
322
|
+
// Recurse into non-function, non-export-statement children (blocks, if-statements, etc.)
|
|
323
|
+
if (child.type !== 'export_statement') {
|
|
324
|
+
extractConstantsWalk(child, definitions);
|
|
325
|
+
}
|
|
308
326
|
}
|
|
309
327
|
}
|
|
310
328
|
|
package/src/extractors/rust.ts
CHANGED
|
@@ -227,7 +227,8 @@ function extractRustParameters(paramListNode: TreeSitterNode | null): SubDeclara
|
|
|
227
227
|
const param = paramListNode.child(i);
|
|
228
228
|
if (!param) continue;
|
|
229
229
|
if (param.type === 'self_parameter') {
|
|
230
|
-
|
|
230
|
+
// Skip self parameters — matches native engine behaviour
|
|
231
|
+
continue;
|
|
231
232
|
} else if (param.type === 'parameter') {
|
|
232
233
|
const pattern = param.childForFieldName('pattern');
|
|
233
234
|
if (pattern) {
|
package/src/features/ast.ts
CHANGED
|
@@ -65,8 +65,73 @@ export async function buildAstNodes(
|
|
|
65
65
|
db: BetterSqlite3Database,
|
|
66
66
|
fileSymbols: Map<string, FileSymbols>,
|
|
67
67
|
_rootDir: string,
|
|
68
|
-
|
|
68
|
+
engineOpts?: {
|
|
69
|
+
nativeDb?: {
|
|
70
|
+
bulkInsertAstNodes(
|
|
71
|
+
batches: Array<{
|
|
72
|
+
file: string;
|
|
73
|
+
nodes: Array<{
|
|
74
|
+
line: number;
|
|
75
|
+
kind: string;
|
|
76
|
+
name: string;
|
|
77
|
+
text?: string | null;
|
|
78
|
+
receiver?: string | null;
|
|
79
|
+
}>;
|
|
80
|
+
}>,
|
|
81
|
+
): number;
|
|
82
|
+
};
|
|
83
|
+
},
|
|
69
84
|
): Promise<void> {
|
|
85
|
+
// ── Native bulk-insert fast path ──────────────────────────────────────
|
|
86
|
+
// Uses NativeDatabase persistent connection (Phase 6.15+).
|
|
87
|
+
// Standalone napi functions were removed in 6.17.
|
|
88
|
+
const nativeDb = engineOpts?.nativeDb;
|
|
89
|
+
if (nativeDb?.bulkInsertAstNodes) {
|
|
90
|
+
let needsJsFallback = false;
|
|
91
|
+
const batches: Array<{
|
|
92
|
+
file: string;
|
|
93
|
+
nodes: Array<{
|
|
94
|
+
line: number;
|
|
95
|
+
kind: string;
|
|
96
|
+
name: string;
|
|
97
|
+
text?: string | null;
|
|
98
|
+
receiver?: string | null;
|
|
99
|
+
}>;
|
|
100
|
+
}> = [];
|
|
101
|
+
|
|
102
|
+
for (const [relPath, symbols] of fileSymbols) {
|
|
103
|
+
if (Array.isArray(symbols.astNodes)) {
|
|
104
|
+
batches.push({
|
|
105
|
+
file: relPath,
|
|
106
|
+
nodes: symbols.astNodes.map((n) => ({
|
|
107
|
+
line: n.line,
|
|
108
|
+
kind: n.kind,
|
|
109
|
+
name: n.name,
|
|
110
|
+
text: n.text,
|
|
111
|
+
receiver: n.receiver,
|
|
112
|
+
})),
|
|
113
|
+
});
|
|
114
|
+
} else if (symbols.calls || symbols._tree) {
|
|
115
|
+
needsJsFallback = true;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!needsJsFallback) {
|
|
121
|
+
const expectedNodes = batches.reduce((s, b) => s + b.nodes.length, 0);
|
|
122
|
+
const inserted = nativeDb.bulkInsertAstNodes(batches);
|
|
123
|
+
if (inserted === expectedNodes) {
|
|
124
|
+
debug(`AST extraction (native bulk): ${inserted} nodes stored`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
debug(
|
|
128
|
+
`AST extraction (native bulk): expected ${expectedNodes}, got ${inserted} — falling back to JS`,
|
|
129
|
+
);
|
|
130
|
+
// fall through to JS path
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── JS fallback path ──────────────────────────────────────────────────
|
|
70
135
|
let insertStmt: ReturnType<BetterSqlite3Database['prepare']>;
|
|
71
136
|
try {
|
|
72
137
|
insertStmt = db.prepare(
|
|
@@ -2,7 +2,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import
|
|
5
|
+
import { getDatabase } from '../db/better-sqlite3.js';
|
|
6
6
|
import { buildGraph } from '../domain/graph/builder.js';
|
|
7
7
|
import { kindIcon } from '../domain/queries.js';
|
|
8
8
|
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
@@ -105,6 +105,7 @@ function loadSymbolsFromDb(
|
|
|
105
105
|
changedFiles: string[],
|
|
106
106
|
noTests: boolean,
|
|
107
107
|
): Map<string, SymbolInfo> {
|
|
108
|
+
const Database = getDatabase();
|
|
108
109
|
const db = new Database(dbPath, { readonly: true });
|
|
109
110
|
try {
|
|
110
111
|
const symbols = new Map<string, SymbolInfo>();
|
|
@@ -174,6 +175,7 @@ function loadCallersFromDb(
|
|
|
174
175
|
): CallerInfo[] {
|
|
175
176
|
if (nodeIds.length === 0) return [];
|
|
176
177
|
|
|
178
|
+
const Database = getDatabase();
|
|
177
179
|
const db = new Database(dbPath, { readonly: true });
|
|
178
180
|
try {
|
|
179
181
|
const allCallers = new Set<string>();
|
package/src/features/snapshot.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import
|
|
3
|
+
import { getDatabase } from '../db/better-sqlite3.js';
|
|
4
4
|
import { findDbPath } from '../db/index.js';
|
|
5
5
|
import { debug } from '../infrastructure/logger.js';
|
|
6
6
|
import { ConfigError, DbError } from '../shared/errors.js';
|
|
@@ -47,6 +47,7 @@ export function snapshotSave(
|
|
|
47
47
|
|
|
48
48
|
fs.mkdirSync(dir, { recursive: true });
|
|
49
49
|
|
|
50
|
+
const Database = getDatabase();
|
|
50
51
|
const db = new Database(dbPath, { readonly: true });
|
|
51
52
|
try {
|
|
52
53
|
db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
package/src/mcp/server.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { createRequire } from 'node:module';
|
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
11
11
|
const { version: PKG_VERSION } = require('../../package.json') as { version: string };
|
|
12
12
|
|
|
13
|
+
import { getDatabase } from '../db/better-sqlite3.js';
|
|
13
14
|
import { findDbPath } from '../db/index.js';
|
|
14
15
|
import { loadConfig } from '../infrastructure/config.js';
|
|
15
16
|
import { CodegraphError, ConfigError } from '../shared/errors.js';
|
|
@@ -64,22 +65,13 @@ async function loadMCPSdk(): Promise<{
|
|
|
64
65
|
|
|
65
66
|
function createLazyLoaders(): {
|
|
66
67
|
getQueries(): Promise<unknown>;
|
|
67
|
-
getDatabase(): unknown;
|
|
68
68
|
} {
|
|
69
69
|
let _queries: unknown;
|
|
70
|
-
let _Database: unknown;
|
|
71
70
|
return {
|
|
72
71
|
async getQueries(): Promise<unknown> {
|
|
73
72
|
if (!_queries) _queries = await import('../domain/queries.js');
|
|
74
73
|
return _queries;
|
|
75
74
|
},
|
|
76
|
-
getDatabase(): unknown {
|
|
77
|
-
if (!_Database) {
|
|
78
|
-
const require = createRequire(import.meta.url);
|
|
79
|
-
_Database = require('better-sqlite3');
|
|
80
|
-
}
|
|
81
|
-
return _Database;
|
|
82
|
-
},
|
|
83
75
|
};
|
|
84
76
|
}
|
|
85
77
|
|
|
@@ -135,7 +127,7 @@ export async function startMCPServer(
|
|
|
135
127
|
// `initialize` request while heavy modules (queries, better-sqlite3)
|
|
136
128
|
// are still loading. These are lazy-loaded on the first tool call
|
|
137
129
|
// and cached for subsequent calls.
|
|
138
|
-
const { getQueries
|
|
130
|
+
const { getQueries } = createLazyLoaders();
|
|
139
131
|
|
|
140
132
|
const server = new (Server as any)(
|
|
141
133
|
{ name: 'codegraph', version: PKG_VERSION },
|