@mduenas/codegraph 0.4.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/LICENSE +21 -0
- package/README.md +641 -0
- package/dist/bin/codegraph.d.ts +20 -0
- package/dist/bin/codegraph.d.ts.map +1 -0
- package/dist/bin/codegraph.js +704 -0
- package/dist/bin/codegraph.js.map +1 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +291 -0
- package/dist/config.js.map +1 -0
- package/dist/context/formatter.d.ts +30 -0
- package/dist/context/formatter.d.ts.map +1 -0
- package/dist/context/formatter.js +244 -0
- package/dist/context/formatter.js.map +1 -0
- package/dist/context/index.d.ts +86 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +402 -0
- package/dist/context/index.js.map +1 -0
- package/dist/db/index.d.ts +64 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +170 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrations.d.ts +44 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +105 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/queries.d.ts +148 -0
- package/dist/db/queries.d.ts.map +1 -0
- package/dist/db/queries.js +669 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/directory.d.ts +45 -0
- package/dist/directory.d.ts.map +1 -0
- package/dist/directory.js +191 -0
- package/dist/directory.js.map +1 -0
- package/dist/errors.d.ts +136 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +219 -0
- package/dist/errors.js.map +1 -0
- package/dist/extraction/grammars.d.ts +36 -0
- package/dist/extraction/grammars.d.ts.map +1 -0
- package/dist/extraction/grammars.js +181 -0
- package/dist/extraction/grammars.js.map +1 -0
- package/dist/extraction/index.d.ts +91 -0
- package/dist/extraction/index.d.ts.map +1 -0
- package/dist/extraction/index.js +493 -0
- package/dist/extraction/index.js.map +1 -0
- package/dist/extraction/tree-sitter.d.ts +176 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -0
- package/dist/extraction/tree-sitter.js +1798 -0
- package/dist/extraction/tree-sitter.js.map +1 -0
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +13 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/queries.d.ts +106 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +355 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/traversal.d.ts +127 -0
- package/dist/graph/traversal.d.ts.map +1 -0
- package/dist/graph/traversal.js +465 -0
- package/dist/graph/traversal.js.map +1 -0
- package/dist/index.d.ts +496 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +818 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/banner.d.ts +40 -0
- package/dist/installer/banner.d.ts.map +1 -0
- package/dist/installer/banner.js +162 -0
- package/dist/installer/banner.js.map +1 -0
- package/dist/installer/claude-md-template.d.ts +10 -0
- package/dist/installer/claude-md-template.d.ts.map +1 -0
- package/dist/installer/claude-md-template.js +46 -0
- package/dist/installer/claude-md-template.js.map +1 -0
- package/dist/installer/config-writer.d.ts +36 -0
- package/dist/installer/config-writer.d.ts.map +1 -0
- package/dist/installer/config-writer.js +282 -0
- package/dist/installer/config-writer.js.map +1 -0
- package/dist/installer/index.d.ts +13 -0
- package/dist/installer/index.d.ts.map +1 -0
- package/dist/installer/index.js +155 -0
- package/dist/installer/index.js.map +1 -0
- package/dist/installer/prompts.d.ts +18 -0
- package/dist/installer/prompts.d.ts.map +1 -0
- package/dist/installer/prompts.js +113 -0
- package/dist/installer/prompts.js.map +1 -0
- package/dist/mcp/index.d.ts +64 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +207 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/tools.d.ts +93 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +442 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/transport.d.ts +89 -0
- package/dist/mcp/transport.d.ts.map +1 -0
- package/dist/mcp/transport.js +170 -0
- package/dist/mcp/transport.js.map +1 -0
- package/dist/resolution/frameworks/csharp.d.ts +8 -0
- package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
- package/dist/resolution/frameworks/csharp.js +274 -0
- package/dist/resolution/frameworks/csharp.js.map +1 -0
- package/dist/resolution/frameworks/express.d.ts +8 -0
- package/dist/resolution/frameworks/express.d.ts.map +1 -0
- package/dist/resolution/frameworks/express.js +208 -0
- package/dist/resolution/frameworks/express.js.map +1 -0
- package/dist/resolution/frameworks/go.d.ts +8 -0
- package/dist/resolution/frameworks/go.d.ts.map +1 -0
- package/dist/resolution/frameworks/go.js +225 -0
- package/dist/resolution/frameworks/go.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts +33 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -0
- package/dist/resolution/frameworks/index.js +113 -0
- package/dist/resolution/frameworks/index.js.map +1 -0
- package/dist/resolution/frameworks/java.d.ts +8 -0
- package/dist/resolution/frameworks/java.d.ts.map +1 -0
- package/dist/resolution/frameworks/java.js +239 -0
- package/dist/resolution/frameworks/java.js.map +1 -0
- package/dist/resolution/frameworks/laravel.d.ts +13 -0
- package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
- package/dist/resolution/frameworks/laravel.js +198 -0
- package/dist/resolution/frameworks/laravel.js.map +1 -0
- package/dist/resolution/frameworks/python.d.ts +10 -0
- package/dist/resolution/frameworks/python.d.ts.map +1 -0
- package/dist/resolution/frameworks/python.js +331 -0
- package/dist/resolution/frameworks/python.js.map +1 -0
- package/dist/resolution/frameworks/react.d.ts +8 -0
- package/dist/resolution/frameworks/react.d.ts.map +1 -0
- package/dist/resolution/frameworks/react.js +294 -0
- package/dist/resolution/frameworks/react.js.map +1 -0
- package/dist/resolution/frameworks/ruby.d.ts +8 -0
- package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
- package/dist/resolution/frameworks/ruby.js +262 -0
- package/dist/resolution/frameworks/ruby.js.map +1 -0
- package/dist/resolution/frameworks/rust.d.ts +8 -0
- package/dist/resolution/frameworks/rust.d.ts.map +1 -0
- package/dist/resolution/frameworks/rust.js +222 -0
- package/dist/resolution/frameworks/rust.js.map +1 -0
- package/dist/resolution/frameworks/swift.d.ts +10 -0
- package/dist/resolution/frameworks/swift.d.ts.map +1 -0
- package/dist/resolution/frameworks/swift.js +486 -0
- package/dist/resolution/frameworks/swift.js.map +1 -0
- package/dist/resolution/import-resolver.d.ts +20 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -0
- package/dist/resolution/import-resolver.js +445 -0
- package/dist/resolution/import-resolver.js.map +1 -0
- package/dist/resolution/index.d.ts +72 -0
- package/dist/resolution/index.d.ts.map +1 -0
- package/dist/resolution/index.js +301 -0
- package/dist/resolution/index.js.map +1 -0
- package/dist/resolution/name-matcher.d.ts +27 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -0
- package/dist/resolution/name-matcher.js +210 -0
- package/dist/resolution/name-matcher.js.map +1 -0
- package/dist/resolution/types.d.ts +108 -0
- package/dist/resolution/types.d.ts.map +1 -0
- package/dist/resolution/types.js +8 -0
- package/dist/resolution/types.js.map +1 -0
- package/dist/sync/git-hooks.d.ts +66 -0
- package/dist/sync/git-hooks.d.ts.map +1 -0
- package/dist/sync/git-hooks.js +281 -0
- package/dist/sync/git-hooks.js.map +1 -0
- package/dist/sync/index.d.ts +13 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +18 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/types.d.ts +410 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +165 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +116 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +295 -0
- package/dist/utils.js.map +1 -0
- package/dist/vectors/embedder.d.ts +140 -0
- package/dist/vectors/embedder.d.ts.map +1 -0
- package/dist/vectors/embedder.js +336 -0
- package/dist/vectors/embedder.js.map +1 -0
- package/dist/vectors/index.d.ts +9 -0
- package/dist/vectors/index.d.ts.map +1 -0
- package/dist/vectors/index.js +20 -0
- package/dist/vectors/index.js.map +1 -0
- package/dist/vectors/manager.d.ts +119 -0
- package/dist/vectors/manager.d.ts.map +1 -0
- package/dist/vectors/manager.js +274 -0
- package/dist/vectors/manager.js.map +1 -0
- package/dist/vectors/search.d.ts +134 -0
- package/dist/vectors/search.d.ts.map +1 -0
- package/dist/vectors/search.js +409 -0
- package/dist/vectors/search.js.map +1 -0
- package/package.json +67 -0
- package/scripts/postinstall.js +68 -0
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Database Queries
|
|
4
|
+
*
|
|
5
|
+
* Prepared statements for CRUD operations on the knowledge graph.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.QueryBuilder = void 0;
|
|
9
|
+
/**
|
|
10
|
+
* Convert database row to Node object
|
|
11
|
+
*/
|
|
12
|
+
function rowToNode(row) {
|
|
13
|
+
return {
|
|
14
|
+
id: row.id,
|
|
15
|
+
kind: row.kind,
|
|
16
|
+
name: row.name,
|
|
17
|
+
qualifiedName: row.qualified_name,
|
|
18
|
+
filePath: row.file_path,
|
|
19
|
+
language: row.language,
|
|
20
|
+
startLine: row.start_line,
|
|
21
|
+
endLine: row.end_line,
|
|
22
|
+
startColumn: row.start_column,
|
|
23
|
+
endColumn: row.end_column,
|
|
24
|
+
docstring: row.docstring ?? undefined,
|
|
25
|
+
signature: row.signature ?? undefined,
|
|
26
|
+
visibility: row.visibility,
|
|
27
|
+
isExported: row.is_exported === 1,
|
|
28
|
+
isAsync: row.is_async === 1,
|
|
29
|
+
isStatic: row.is_static === 1,
|
|
30
|
+
isAbstract: row.is_abstract === 1,
|
|
31
|
+
decorators: row.decorators ? JSON.parse(row.decorators) : undefined,
|
|
32
|
+
typeParameters: row.type_parameters ? JSON.parse(row.type_parameters) : undefined,
|
|
33
|
+
updatedAt: row.updated_at,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Convert database row to Edge object
|
|
38
|
+
*/
|
|
39
|
+
function rowToEdge(row) {
|
|
40
|
+
return {
|
|
41
|
+
source: row.source,
|
|
42
|
+
target: row.target,
|
|
43
|
+
kind: row.kind,
|
|
44
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
45
|
+
line: row.line ?? undefined,
|
|
46
|
+
column: row.col ?? undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Convert database row to FileRecord object
|
|
51
|
+
*/
|
|
52
|
+
function rowToFileRecord(row) {
|
|
53
|
+
return {
|
|
54
|
+
path: row.path,
|
|
55
|
+
contentHash: row.content_hash,
|
|
56
|
+
language: row.language,
|
|
57
|
+
size: row.size,
|
|
58
|
+
modifiedAt: row.modified_at,
|
|
59
|
+
indexedAt: row.indexed_at,
|
|
60
|
+
nodeCount: row.node_count,
|
|
61
|
+
errors: row.errors ? JSON.parse(row.errors) : undefined,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Query builder for the knowledge graph database
|
|
66
|
+
*/
|
|
67
|
+
class QueryBuilder {
|
|
68
|
+
db;
|
|
69
|
+
// Node cache for frequently accessed nodes (LRU-style, max 1000 entries)
|
|
70
|
+
nodeCache = new Map();
|
|
71
|
+
maxCacheSize = 1000;
|
|
72
|
+
// Prepared statements (lazily initialized)
|
|
73
|
+
stmts = {};
|
|
74
|
+
constructor(db) {
|
|
75
|
+
this.db = db;
|
|
76
|
+
}
|
|
77
|
+
// ===========================================================================
|
|
78
|
+
// Node Operations
|
|
79
|
+
// ===========================================================================
|
|
80
|
+
/**
|
|
81
|
+
* Insert a new node
|
|
82
|
+
*/
|
|
83
|
+
insertNode(node) {
|
|
84
|
+
if (!this.stmts.insertNode) {
|
|
85
|
+
this.stmts.insertNode = this.db.prepare(`
|
|
86
|
+
INSERT INTO nodes (
|
|
87
|
+
id, kind, name, qualified_name, file_path, language,
|
|
88
|
+
start_line, end_line, start_column, end_column,
|
|
89
|
+
docstring, signature, visibility,
|
|
90
|
+
is_exported, is_async, is_static, is_abstract,
|
|
91
|
+
decorators, type_parameters, updated_at
|
|
92
|
+
) VALUES (
|
|
93
|
+
@id, @kind, @name, @qualifiedName, @filePath, @language,
|
|
94
|
+
@startLine, @endLine, @startColumn, @endColumn,
|
|
95
|
+
@docstring, @signature, @visibility,
|
|
96
|
+
@isExported, @isAsync, @isStatic, @isAbstract,
|
|
97
|
+
@decorators, @typeParameters, @updatedAt
|
|
98
|
+
)
|
|
99
|
+
`);
|
|
100
|
+
}
|
|
101
|
+
this.stmts.insertNode.run({
|
|
102
|
+
id: node.id,
|
|
103
|
+
kind: node.kind,
|
|
104
|
+
name: node.name,
|
|
105
|
+
qualifiedName: node.qualifiedName,
|
|
106
|
+
filePath: node.filePath,
|
|
107
|
+
language: node.language,
|
|
108
|
+
startLine: node.startLine,
|
|
109
|
+
endLine: node.endLine,
|
|
110
|
+
startColumn: node.startColumn,
|
|
111
|
+
endColumn: node.endColumn,
|
|
112
|
+
docstring: node.docstring ?? null,
|
|
113
|
+
signature: node.signature ?? null,
|
|
114
|
+
visibility: node.visibility ?? null,
|
|
115
|
+
isExported: node.isExported ? 1 : 0,
|
|
116
|
+
isAsync: node.isAsync ? 1 : 0,
|
|
117
|
+
isStatic: node.isStatic ? 1 : 0,
|
|
118
|
+
isAbstract: node.isAbstract ? 1 : 0,
|
|
119
|
+
decorators: node.decorators ? JSON.stringify(node.decorators) : null,
|
|
120
|
+
typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
|
|
121
|
+
updatedAt: node.updatedAt,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Insert multiple nodes in a transaction
|
|
126
|
+
*/
|
|
127
|
+
insertNodes(nodes) {
|
|
128
|
+
this.db.transaction(() => {
|
|
129
|
+
for (const node of nodes) {
|
|
130
|
+
this.insertNode(node);
|
|
131
|
+
}
|
|
132
|
+
})();
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Update an existing node
|
|
136
|
+
*/
|
|
137
|
+
updateNode(node) {
|
|
138
|
+
if (!this.stmts.updateNode) {
|
|
139
|
+
this.stmts.updateNode = this.db.prepare(`
|
|
140
|
+
UPDATE nodes SET
|
|
141
|
+
kind = @kind,
|
|
142
|
+
name = @name,
|
|
143
|
+
qualified_name = @qualifiedName,
|
|
144
|
+
file_path = @filePath,
|
|
145
|
+
language = @language,
|
|
146
|
+
start_line = @startLine,
|
|
147
|
+
end_line = @endLine,
|
|
148
|
+
start_column = @startColumn,
|
|
149
|
+
end_column = @endColumn,
|
|
150
|
+
docstring = @docstring,
|
|
151
|
+
signature = @signature,
|
|
152
|
+
visibility = @visibility,
|
|
153
|
+
is_exported = @isExported,
|
|
154
|
+
is_async = @isAsync,
|
|
155
|
+
is_static = @isStatic,
|
|
156
|
+
is_abstract = @isAbstract,
|
|
157
|
+
decorators = @decorators,
|
|
158
|
+
type_parameters = @typeParameters,
|
|
159
|
+
updated_at = @updatedAt
|
|
160
|
+
WHERE id = @id
|
|
161
|
+
`);
|
|
162
|
+
}
|
|
163
|
+
// Invalidate cache before update
|
|
164
|
+
this.nodeCache.delete(node.id);
|
|
165
|
+
this.stmts.updateNode.run({
|
|
166
|
+
id: node.id,
|
|
167
|
+
kind: node.kind,
|
|
168
|
+
name: node.name,
|
|
169
|
+
qualifiedName: node.qualifiedName,
|
|
170
|
+
filePath: node.filePath,
|
|
171
|
+
language: node.language,
|
|
172
|
+
startLine: node.startLine,
|
|
173
|
+
endLine: node.endLine,
|
|
174
|
+
startColumn: node.startColumn,
|
|
175
|
+
endColumn: node.endColumn,
|
|
176
|
+
docstring: node.docstring ?? null,
|
|
177
|
+
signature: node.signature ?? null,
|
|
178
|
+
visibility: node.visibility ?? null,
|
|
179
|
+
isExported: node.isExported ? 1 : 0,
|
|
180
|
+
isAsync: node.isAsync ? 1 : 0,
|
|
181
|
+
isStatic: node.isStatic ? 1 : 0,
|
|
182
|
+
isAbstract: node.isAbstract ? 1 : 0,
|
|
183
|
+
decorators: node.decorators ? JSON.stringify(node.decorators) : null,
|
|
184
|
+
typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null,
|
|
185
|
+
updatedAt: node.updatedAt,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Delete a node by ID
|
|
190
|
+
*/
|
|
191
|
+
deleteNode(id) {
|
|
192
|
+
if (!this.stmts.deleteNode) {
|
|
193
|
+
this.stmts.deleteNode = this.db.prepare('DELETE FROM nodes WHERE id = ?');
|
|
194
|
+
}
|
|
195
|
+
// Invalidate cache
|
|
196
|
+
this.nodeCache.delete(id);
|
|
197
|
+
this.stmts.deleteNode.run(id);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Delete all nodes for a file
|
|
201
|
+
*/
|
|
202
|
+
deleteNodesByFile(filePath) {
|
|
203
|
+
if (!this.stmts.deleteNodesByFile) {
|
|
204
|
+
this.stmts.deleteNodesByFile = this.db.prepare('DELETE FROM nodes WHERE file_path = ?');
|
|
205
|
+
}
|
|
206
|
+
// Invalidate cache for nodes in this file
|
|
207
|
+
for (const [id, node] of this.nodeCache) {
|
|
208
|
+
if (node.filePath === filePath) {
|
|
209
|
+
this.nodeCache.delete(id);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
this.stmts.deleteNodesByFile.run(filePath);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get a node by ID
|
|
216
|
+
*/
|
|
217
|
+
getNodeById(id) {
|
|
218
|
+
// Check cache first
|
|
219
|
+
if (this.nodeCache.has(id)) {
|
|
220
|
+
const cached = this.nodeCache.get(id);
|
|
221
|
+
// Move to end to implement LRU (delete and re-add)
|
|
222
|
+
this.nodeCache.delete(id);
|
|
223
|
+
this.nodeCache.set(id, cached);
|
|
224
|
+
return cached;
|
|
225
|
+
}
|
|
226
|
+
if (!this.stmts.getNodeById) {
|
|
227
|
+
this.stmts.getNodeById = this.db.prepare('SELECT * FROM nodes WHERE id = ?');
|
|
228
|
+
}
|
|
229
|
+
const row = this.stmts.getNodeById.get(id);
|
|
230
|
+
if (!row) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
const node = rowToNode(row);
|
|
234
|
+
this.cacheNode(node);
|
|
235
|
+
return node;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Add a node to the cache, evicting oldest if needed
|
|
239
|
+
*/
|
|
240
|
+
cacheNode(node) {
|
|
241
|
+
if (this.nodeCache.size >= this.maxCacheSize) {
|
|
242
|
+
// Evict oldest (first) entry
|
|
243
|
+
const firstKey = this.nodeCache.keys().next().value;
|
|
244
|
+
if (firstKey) {
|
|
245
|
+
this.nodeCache.delete(firstKey);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
this.nodeCache.set(node.id, node);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Clear the node cache
|
|
252
|
+
*/
|
|
253
|
+
clearCache() {
|
|
254
|
+
this.nodeCache.clear();
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get all nodes in a file
|
|
258
|
+
*/
|
|
259
|
+
getNodesByFile(filePath) {
|
|
260
|
+
if (!this.stmts.getNodesByFile) {
|
|
261
|
+
this.stmts.getNodesByFile = this.db.prepare('SELECT * FROM nodes WHERE file_path = ? ORDER BY start_line');
|
|
262
|
+
}
|
|
263
|
+
const rows = this.stmts.getNodesByFile.all(filePath);
|
|
264
|
+
return rows.map(rowToNode);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get all nodes of a specific kind
|
|
268
|
+
*/
|
|
269
|
+
getNodesByKind(kind) {
|
|
270
|
+
if (!this.stmts.getNodesByKind) {
|
|
271
|
+
this.stmts.getNodesByKind = this.db.prepare('SELECT * FROM nodes WHERE kind = ?');
|
|
272
|
+
}
|
|
273
|
+
const rows = this.stmts.getNodesByKind.all(kind);
|
|
274
|
+
return rows.map(rowToNode);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Search nodes by name using FTS with fallback to LIKE for better matching
|
|
278
|
+
*
|
|
279
|
+
* Search strategy:
|
|
280
|
+
* 1. Try FTS5 prefix match (query*) for word-start matching
|
|
281
|
+
* 2. If no results, try LIKE for substring matching (e.g., "signIn" finds "signInWithGoogle")
|
|
282
|
+
* 3. Score results based on match quality
|
|
283
|
+
*/
|
|
284
|
+
searchNodes(query, options = {}) {
|
|
285
|
+
const { kinds, languages, limit = 100, offset = 0 } = options;
|
|
286
|
+
// First try FTS5 with prefix matching
|
|
287
|
+
let results = this.searchNodesFTS(query, { kinds, languages, limit, offset });
|
|
288
|
+
// If no FTS results, try LIKE-based substring search
|
|
289
|
+
if (results.length === 0 && query.length >= 2) {
|
|
290
|
+
results = this.searchNodesLike(query, { kinds, languages, limit, offset });
|
|
291
|
+
}
|
|
292
|
+
return results;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* FTS5 search with prefix matching
|
|
296
|
+
*/
|
|
297
|
+
searchNodesFTS(query, options) {
|
|
298
|
+
const { kinds, languages, limit = 100, offset = 0 } = options;
|
|
299
|
+
// Add prefix wildcard for better matching (e.g., "auth" matches "AuthService", "authenticate")
|
|
300
|
+
// Escape special FTS5 characters and add prefix wildcard
|
|
301
|
+
const ftsQuery = query
|
|
302
|
+
.replace(/['"*()]/g, '') // Remove special chars
|
|
303
|
+
.split(/\s+/)
|
|
304
|
+
.filter(term => term.length > 0)
|
|
305
|
+
.map(term => `"${term}"*`) // Prefix match each term
|
|
306
|
+
.join(' OR ');
|
|
307
|
+
if (!ftsQuery) {
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
let sql = `
|
|
311
|
+
SELECT nodes.*, bm25(nodes_fts) as score
|
|
312
|
+
FROM nodes_fts
|
|
313
|
+
JOIN nodes ON nodes_fts.id = nodes.id
|
|
314
|
+
WHERE nodes_fts MATCH ?
|
|
315
|
+
`;
|
|
316
|
+
const params = [ftsQuery];
|
|
317
|
+
if (kinds && kinds.length > 0) {
|
|
318
|
+
sql += ` AND nodes.kind IN (${kinds.map(() => '?').join(',')})`;
|
|
319
|
+
params.push(...kinds);
|
|
320
|
+
}
|
|
321
|
+
if (languages && languages.length > 0) {
|
|
322
|
+
sql += ` AND nodes.language IN (${languages.map(() => '?').join(',')})`;
|
|
323
|
+
params.push(...languages);
|
|
324
|
+
}
|
|
325
|
+
sql += ' ORDER BY score LIMIT ? OFFSET ?';
|
|
326
|
+
params.push(limit, offset);
|
|
327
|
+
try {
|
|
328
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
329
|
+
return rows.map((row) => ({
|
|
330
|
+
node: rowToNode(row),
|
|
331
|
+
score: Math.abs(row.score), // bm25 returns negative scores
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
// FTS query failed, return empty
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* LIKE-based substring search for cases where FTS doesn't match
|
|
341
|
+
* Useful for camelCase matching (e.g., "signIn" finds "signInWithGoogle")
|
|
342
|
+
*/
|
|
343
|
+
searchNodesLike(query, options) {
|
|
344
|
+
const { kinds, languages, limit = 100, offset = 0 } = options;
|
|
345
|
+
let sql = `
|
|
346
|
+
SELECT nodes.*,
|
|
347
|
+
CASE
|
|
348
|
+
WHEN name = ? THEN 1.0
|
|
349
|
+
WHEN name LIKE ? THEN 0.9
|
|
350
|
+
WHEN name LIKE ? THEN 0.8
|
|
351
|
+
WHEN qualified_name LIKE ? THEN 0.7
|
|
352
|
+
ELSE 0.5
|
|
353
|
+
END as score
|
|
354
|
+
FROM nodes
|
|
355
|
+
WHERE (
|
|
356
|
+
name LIKE ? OR
|
|
357
|
+
qualified_name LIKE ? OR
|
|
358
|
+
name LIKE ?
|
|
359
|
+
)
|
|
360
|
+
`;
|
|
361
|
+
// Pattern variants for better matching
|
|
362
|
+
const exactMatch = query;
|
|
363
|
+
const startsWith = `${query}%`;
|
|
364
|
+
const contains = `%${query}%`;
|
|
365
|
+
const params = [
|
|
366
|
+
exactMatch, // Exact match score
|
|
367
|
+
startsWith, // Starts with score
|
|
368
|
+
contains, // Contains score
|
|
369
|
+
contains, // Qualified name score
|
|
370
|
+
contains, // WHERE: name contains
|
|
371
|
+
contains, // WHERE: qualified_name contains
|
|
372
|
+
startsWith, // WHERE: name starts with
|
|
373
|
+
];
|
|
374
|
+
if (kinds && kinds.length > 0) {
|
|
375
|
+
sql += ` AND kind IN (${kinds.map(() => '?').join(',')})`;
|
|
376
|
+
params.push(...kinds);
|
|
377
|
+
}
|
|
378
|
+
if (languages && languages.length > 0) {
|
|
379
|
+
sql += ` AND language IN (${languages.map(() => '?').join(',')})`;
|
|
380
|
+
params.push(...languages);
|
|
381
|
+
}
|
|
382
|
+
sql += ' ORDER BY score DESC, length(name) ASC LIMIT ? OFFSET ?';
|
|
383
|
+
params.push(limit, offset);
|
|
384
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
385
|
+
return rows.map((row) => ({
|
|
386
|
+
node: rowToNode(row),
|
|
387
|
+
score: row.score,
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
// ===========================================================================
|
|
391
|
+
// Edge Operations
|
|
392
|
+
// ===========================================================================
|
|
393
|
+
/**
|
|
394
|
+
* Insert a new edge
|
|
395
|
+
*/
|
|
396
|
+
insertEdge(edge) {
|
|
397
|
+
if (!this.stmts.insertEdge) {
|
|
398
|
+
this.stmts.insertEdge = this.db.prepare(`
|
|
399
|
+
INSERT INTO edges (source, target, kind, metadata, line, col)
|
|
400
|
+
VALUES (@source, @target, @kind, @metadata, @line, @col)
|
|
401
|
+
`);
|
|
402
|
+
}
|
|
403
|
+
this.stmts.insertEdge.run({
|
|
404
|
+
source: edge.source,
|
|
405
|
+
target: edge.target,
|
|
406
|
+
kind: edge.kind,
|
|
407
|
+
metadata: edge.metadata ? JSON.stringify(edge.metadata) : null,
|
|
408
|
+
line: edge.line ?? null,
|
|
409
|
+
col: edge.column ?? null,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Insert multiple edges in a transaction
|
|
414
|
+
*/
|
|
415
|
+
insertEdges(edges) {
|
|
416
|
+
this.db.transaction(() => {
|
|
417
|
+
for (const edge of edges) {
|
|
418
|
+
this.insertEdge(edge);
|
|
419
|
+
}
|
|
420
|
+
})();
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Delete all edges from a source node
|
|
424
|
+
*/
|
|
425
|
+
deleteEdgesBySource(sourceId) {
|
|
426
|
+
if (!this.stmts.deleteEdgesBySource) {
|
|
427
|
+
this.stmts.deleteEdgesBySource = this.db.prepare('DELETE FROM edges WHERE source = ?');
|
|
428
|
+
}
|
|
429
|
+
this.stmts.deleteEdgesBySource.run(sourceId);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Get outgoing edges from a node
|
|
433
|
+
*/
|
|
434
|
+
getOutgoingEdges(sourceId, kinds) {
|
|
435
|
+
if (kinds && kinds.length > 0) {
|
|
436
|
+
const sql = `SELECT * FROM edges WHERE source = ? AND kind IN (${kinds.map(() => '?').join(',')})`;
|
|
437
|
+
const rows = this.db.prepare(sql).all(sourceId, ...kinds);
|
|
438
|
+
return rows.map(rowToEdge);
|
|
439
|
+
}
|
|
440
|
+
if (!this.stmts.getEdgesBySource) {
|
|
441
|
+
this.stmts.getEdgesBySource = this.db.prepare('SELECT * FROM edges WHERE source = ?');
|
|
442
|
+
}
|
|
443
|
+
const rows = this.stmts.getEdgesBySource.all(sourceId);
|
|
444
|
+
return rows.map(rowToEdge);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Get incoming edges to a node
|
|
448
|
+
*/
|
|
449
|
+
getIncomingEdges(targetId, kinds) {
|
|
450
|
+
if (kinds && kinds.length > 0) {
|
|
451
|
+
const sql = `SELECT * FROM edges WHERE target = ? AND kind IN (${kinds.map(() => '?').join(',')})`;
|
|
452
|
+
const rows = this.db.prepare(sql).all(targetId, ...kinds);
|
|
453
|
+
return rows.map(rowToEdge);
|
|
454
|
+
}
|
|
455
|
+
if (!this.stmts.getEdgesByTarget) {
|
|
456
|
+
this.stmts.getEdgesByTarget = this.db.prepare('SELECT * FROM edges WHERE target = ?');
|
|
457
|
+
}
|
|
458
|
+
const rows = this.stmts.getEdgesByTarget.all(targetId);
|
|
459
|
+
return rows.map(rowToEdge);
|
|
460
|
+
}
|
|
461
|
+
// ===========================================================================
|
|
462
|
+
// File Operations
|
|
463
|
+
// ===========================================================================
|
|
464
|
+
/**
|
|
465
|
+
* Insert or update a file record
|
|
466
|
+
*/
|
|
467
|
+
upsertFile(file) {
|
|
468
|
+
if (!this.stmts.upsertFile) {
|
|
469
|
+
this.stmts.upsertFile = this.db.prepare(`
|
|
470
|
+
INSERT INTO files (path, content_hash, language, size, modified_at, indexed_at, node_count, errors)
|
|
471
|
+
VALUES (@path, @contentHash, @language, @size, @modifiedAt, @indexedAt, @nodeCount, @errors)
|
|
472
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
473
|
+
content_hash = @contentHash,
|
|
474
|
+
language = @language,
|
|
475
|
+
size = @size,
|
|
476
|
+
modified_at = @modifiedAt,
|
|
477
|
+
indexed_at = @indexedAt,
|
|
478
|
+
node_count = @nodeCount,
|
|
479
|
+
errors = @errors
|
|
480
|
+
`);
|
|
481
|
+
}
|
|
482
|
+
this.stmts.upsertFile.run({
|
|
483
|
+
path: file.path,
|
|
484
|
+
contentHash: file.contentHash,
|
|
485
|
+
language: file.language,
|
|
486
|
+
size: file.size,
|
|
487
|
+
modifiedAt: file.modifiedAt,
|
|
488
|
+
indexedAt: file.indexedAt,
|
|
489
|
+
nodeCount: file.nodeCount,
|
|
490
|
+
errors: file.errors ? JSON.stringify(file.errors) : null,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Delete a file record and its nodes
|
|
495
|
+
*/
|
|
496
|
+
deleteFile(filePath) {
|
|
497
|
+
this.db.transaction(() => {
|
|
498
|
+
this.deleteNodesByFile(filePath);
|
|
499
|
+
if (!this.stmts.deleteFile) {
|
|
500
|
+
this.stmts.deleteFile = this.db.prepare('DELETE FROM files WHERE path = ?');
|
|
501
|
+
}
|
|
502
|
+
this.stmts.deleteFile.run(filePath);
|
|
503
|
+
})();
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Get a file record by path
|
|
507
|
+
*/
|
|
508
|
+
getFileByPath(filePath) {
|
|
509
|
+
if (!this.stmts.getFileByPath) {
|
|
510
|
+
this.stmts.getFileByPath = this.db.prepare('SELECT * FROM files WHERE path = ?');
|
|
511
|
+
}
|
|
512
|
+
const row = this.stmts.getFileByPath.get(filePath);
|
|
513
|
+
return row ? rowToFileRecord(row) : null;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Get all tracked files
|
|
517
|
+
*/
|
|
518
|
+
getAllFiles() {
|
|
519
|
+
if (!this.stmts.getAllFiles) {
|
|
520
|
+
this.stmts.getAllFiles = this.db.prepare('SELECT * FROM files ORDER BY path');
|
|
521
|
+
}
|
|
522
|
+
const rows = this.stmts.getAllFiles.all();
|
|
523
|
+
return rows.map(rowToFileRecord);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Get files that need re-indexing (hash changed)
|
|
527
|
+
*/
|
|
528
|
+
getStaleFiles(currentHashes) {
|
|
529
|
+
const files = this.getAllFiles();
|
|
530
|
+
return files.filter((f) => {
|
|
531
|
+
const currentHash = currentHashes.get(f.path);
|
|
532
|
+
return currentHash && currentHash !== f.contentHash;
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
// ===========================================================================
|
|
536
|
+
// Unresolved References
|
|
537
|
+
// ===========================================================================
|
|
538
|
+
/**
|
|
539
|
+
* Insert an unresolved reference
|
|
540
|
+
*/
|
|
541
|
+
insertUnresolvedRef(ref) {
|
|
542
|
+
if (!this.stmts.insertUnresolved) {
|
|
543
|
+
this.stmts.insertUnresolved = this.db.prepare(`
|
|
544
|
+
INSERT INTO unresolved_refs (from_node_id, reference_name, reference_kind, line, col, candidates)
|
|
545
|
+
VALUES (@fromNodeId, @referenceName, @referenceKind, @line, @col, @candidates)
|
|
546
|
+
`);
|
|
547
|
+
}
|
|
548
|
+
this.stmts.insertUnresolved.run({
|
|
549
|
+
fromNodeId: ref.fromNodeId,
|
|
550
|
+
referenceName: ref.referenceName,
|
|
551
|
+
referenceKind: ref.referenceKind,
|
|
552
|
+
line: ref.line,
|
|
553
|
+
col: ref.column,
|
|
554
|
+
candidates: ref.candidates ? JSON.stringify(ref.candidates) : null,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Delete unresolved references from a node
|
|
559
|
+
*/
|
|
560
|
+
deleteUnresolvedByNode(nodeId) {
|
|
561
|
+
if (!this.stmts.deleteUnresolvedByNode) {
|
|
562
|
+
this.stmts.deleteUnresolvedByNode = this.db.prepare('DELETE FROM unresolved_refs WHERE from_node_id = ?');
|
|
563
|
+
}
|
|
564
|
+
this.stmts.deleteUnresolvedByNode.run(nodeId);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Get unresolved references by name (for resolution)
|
|
568
|
+
*/
|
|
569
|
+
getUnresolvedByName(name) {
|
|
570
|
+
if (!this.stmts.getUnresolvedByName) {
|
|
571
|
+
this.stmts.getUnresolvedByName = this.db.prepare('SELECT * FROM unresolved_refs WHERE reference_name = ?');
|
|
572
|
+
}
|
|
573
|
+
const rows = this.stmts.getUnresolvedByName.all(name);
|
|
574
|
+
return rows.map((row) => ({
|
|
575
|
+
fromNodeId: row.from_node_id,
|
|
576
|
+
referenceName: row.reference_name,
|
|
577
|
+
referenceKind: row.reference_kind,
|
|
578
|
+
line: row.line,
|
|
579
|
+
column: row.col,
|
|
580
|
+
candidates: row.candidates ? JSON.parse(row.candidates) : undefined,
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Get all unresolved references
|
|
585
|
+
*/
|
|
586
|
+
getUnresolvedReferences() {
|
|
587
|
+
const rows = this.db.prepare('SELECT * FROM unresolved_refs').all();
|
|
588
|
+
return rows.map((row) => ({
|
|
589
|
+
fromNodeId: row.from_node_id,
|
|
590
|
+
referenceName: row.reference_name,
|
|
591
|
+
referenceKind: row.reference_kind,
|
|
592
|
+
line: row.line,
|
|
593
|
+
column: row.col,
|
|
594
|
+
candidates: row.candidates ? JSON.parse(row.candidates) : undefined,
|
|
595
|
+
}));
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Delete all unresolved references (after resolution)
|
|
599
|
+
*/
|
|
600
|
+
clearUnresolvedReferences() {
|
|
601
|
+
this.db.exec('DELETE FROM unresolved_refs');
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Delete resolved references by their IDs
|
|
605
|
+
*/
|
|
606
|
+
deleteResolvedReferences(fromNodeIds) {
|
|
607
|
+
if (fromNodeIds.length === 0)
|
|
608
|
+
return;
|
|
609
|
+
const placeholders = fromNodeIds.map(() => '?').join(',');
|
|
610
|
+
this.db.prepare(`DELETE FROM unresolved_refs WHERE from_node_id IN (${placeholders})`).run(...fromNodeIds);
|
|
611
|
+
}
|
|
612
|
+
// ===========================================================================
|
|
613
|
+
// Statistics
|
|
614
|
+
// ===========================================================================
|
|
615
|
+
/**
|
|
616
|
+
* Get graph statistics
|
|
617
|
+
*/
|
|
618
|
+
getStats() {
|
|
619
|
+
const nodeCount = this.db.prepare('SELECT COUNT(*) as count FROM nodes').get().count;
|
|
620
|
+
const edgeCount = this.db.prepare('SELECT COUNT(*) as count FROM edges').get().count;
|
|
621
|
+
const fileCount = this.db.prepare('SELECT COUNT(*) as count FROM files').get().count;
|
|
622
|
+
const nodesByKind = {};
|
|
623
|
+
const nodeKindRows = this.db
|
|
624
|
+
.prepare('SELECT kind, COUNT(*) as count FROM nodes GROUP BY kind')
|
|
625
|
+
.all();
|
|
626
|
+
for (const row of nodeKindRows) {
|
|
627
|
+
nodesByKind[row.kind] = row.count;
|
|
628
|
+
}
|
|
629
|
+
const edgesByKind = {};
|
|
630
|
+
const edgeKindRows = this.db
|
|
631
|
+
.prepare('SELECT kind, COUNT(*) as count FROM edges GROUP BY kind')
|
|
632
|
+
.all();
|
|
633
|
+
for (const row of edgeKindRows) {
|
|
634
|
+
edgesByKind[row.kind] = row.count;
|
|
635
|
+
}
|
|
636
|
+
const filesByLanguage = {};
|
|
637
|
+
const languageRows = this.db
|
|
638
|
+
.prepare('SELECT language, COUNT(*) as count FROM files GROUP BY language')
|
|
639
|
+
.all();
|
|
640
|
+
for (const row of languageRows) {
|
|
641
|
+
filesByLanguage[row.language] = row.count;
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
nodeCount,
|
|
645
|
+
edgeCount,
|
|
646
|
+
fileCount,
|
|
647
|
+
nodesByKind,
|
|
648
|
+
edgesByKind,
|
|
649
|
+
filesByLanguage,
|
|
650
|
+
dbSizeBytes: 0, // Set by caller using DatabaseConnection.getSize()
|
|
651
|
+
lastUpdated: Date.now(),
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Clear all data from the database
|
|
656
|
+
*/
|
|
657
|
+
clear() {
|
|
658
|
+
this.nodeCache.clear();
|
|
659
|
+
this.db.transaction(() => {
|
|
660
|
+
this.db.exec('DELETE FROM unresolved_refs');
|
|
661
|
+
this.db.exec('DELETE FROM vectors');
|
|
662
|
+
this.db.exec('DELETE FROM edges');
|
|
663
|
+
this.db.exec('DELETE FROM nodes');
|
|
664
|
+
this.db.exec('DELETE FROM files');
|
|
665
|
+
})();
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
exports.QueryBuilder = QueryBuilder;
|
|
669
|
+
//# sourceMappingURL=queries.js.map
|