@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.
Files changed (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +641 -0
  3. package/dist/bin/codegraph.d.ts +20 -0
  4. package/dist/bin/codegraph.d.ts.map +1 -0
  5. package/dist/bin/codegraph.js +704 -0
  6. package/dist/bin/codegraph.js.map +1 -0
  7. package/dist/config.d.ts +51 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +291 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/context/formatter.d.ts +30 -0
  12. package/dist/context/formatter.d.ts.map +1 -0
  13. package/dist/context/formatter.js +244 -0
  14. package/dist/context/formatter.js.map +1 -0
  15. package/dist/context/index.d.ts +86 -0
  16. package/dist/context/index.d.ts.map +1 -0
  17. package/dist/context/index.js +402 -0
  18. package/dist/context/index.js.map +1 -0
  19. package/dist/db/index.d.ts +64 -0
  20. package/dist/db/index.d.ts.map +1 -0
  21. package/dist/db/index.js +170 -0
  22. package/dist/db/index.js.map +1 -0
  23. package/dist/db/migrations.d.ts +44 -0
  24. package/dist/db/migrations.d.ts.map +1 -0
  25. package/dist/db/migrations.js +105 -0
  26. package/dist/db/migrations.js.map +1 -0
  27. package/dist/db/queries.d.ts +148 -0
  28. package/dist/db/queries.d.ts.map +1 -0
  29. package/dist/db/queries.js +669 -0
  30. package/dist/db/queries.js.map +1 -0
  31. package/dist/directory.d.ts +45 -0
  32. package/dist/directory.d.ts.map +1 -0
  33. package/dist/directory.js +191 -0
  34. package/dist/directory.js.map +1 -0
  35. package/dist/errors.d.ts +136 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +219 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/extraction/grammars.d.ts +36 -0
  40. package/dist/extraction/grammars.d.ts.map +1 -0
  41. package/dist/extraction/grammars.js +181 -0
  42. package/dist/extraction/grammars.js.map +1 -0
  43. package/dist/extraction/index.d.ts +91 -0
  44. package/dist/extraction/index.d.ts.map +1 -0
  45. package/dist/extraction/index.js +493 -0
  46. package/dist/extraction/index.js.map +1 -0
  47. package/dist/extraction/tree-sitter.d.ts +176 -0
  48. package/dist/extraction/tree-sitter.d.ts.map +1 -0
  49. package/dist/extraction/tree-sitter.js +1798 -0
  50. package/dist/extraction/tree-sitter.js.map +1 -0
  51. package/dist/graph/index.d.ts +8 -0
  52. package/dist/graph/index.d.ts.map +1 -0
  53. package/dist/graph/index.js +13 -0
  54. package/dist/graph/index.js.map +1 -0
  55. package/dist/graph/queries.d.ts +106 -0
  56. package/dist/graph/queries.d.ts.map +1 -0
  57. package/dist/graph/queries.js +355 -0
  58. package/dist/graph/queries.js.map +1 -0
  59. package/dist/graph/traversal.d.ts +127 -0
  60. package/dist/graph/traversal.d.ts.map +1 -0
  61. package/dist/graph/traversal.js +465 -0
  62. package/dist/graph/traversal.js.map +1 -0
  63. package/dist/index.d.ts +496 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +818 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/installer/banner.d.ts +40 -0
  68. package/dist/installer/banner.d.ts.map +1 -0
  69. package/dist/installer/banner.js +162 -0
  70. package/dist/installer/banner.js.map +1 -0
  71. package/dist/installer/claude-md-template.d.ts +10 -0
  72. package/dist/installer/claude-md-template.d.ts.map +1 -0
  73. package/dist/installer/claude-md-template.js +46 -0
  74. package/dist/installer/claude-md-template.js.map +1 -0
  75. package/dist/installer/config-writer.d.ts +36 -0
  76. package/dist/installer/config-writer.d.ts.map +1 -0
  77. package/dist/installer/config-writer.js +282 -0
  78. package/dist/installer/config-writer.js.map +1 -0
  79. package/dist/installer/index.d.ts +13 -0
  80. package/dist/installer/index.d.ts.map +1 -0
  81. package/dist/installer/index.js +155 -0
  82. package/dist/installer/index.js.map +1 -0
  83. package/dist/installer/prompts.d.ts +18 -0
  84. package/dist/installer/prompts.d.ts.map +1 -0
  85. package/dist/installer/prompts.js +113 -0
  86. package/dist/installer/prompts.js.map +1 -0
  87. package/dist/mcp/index.d.ts +64 -0
  88. package/dist/mcp/index.d.ts.map +1 -0
  89. package/dist/mcp/index.js +207 -0
  90. package/dist/mcp/index.js.map +1 -0
  91. package/dist/mcp/tools.d.ts +93 -0
  92. package/dist/mcp/tools.d.ts.map +1 -0
  93. package/dist/mcp/tools.js +442 -0
  94. package/dist/mcp/tools.js.map +1 -0
  95. package/dist/mcp/transport.d.ts +89 -0
  96. package/dist/mcp/transport.d.ts.map +1 -0
  97. package/dist/mcp/transport.js +170 -0
  98. package/dist/mcp/transport.js.map +1 -0
  99. package/dist/resolution/frameworks/csharp.d.ts +8 -0
  100. package/dist/resolution/frameworks/csharp.d.ts.map +1 -0
  101. package/dist/resolution/frameworks/csharp.js +274 -0
  102. package/dist/resolution/frameworks/csharp.js.map +1 -0
  103. package/dist/resolution/frameworks/express.d.ts +8 -0
  104. package/dist/resolution/frameworks/express.d.ts.map +1 -0
  105. package/dist/resolution/frameworks/express.js +208 -0
  106. package/dist/resolution/frameworks/express.js.map +1 -0
  107. package/dist/resolution/frameworks/go.d.ts +8 -0
  108. package/dist/resolution/frameworks/go.d.ts.map +1 -0
  109. package/dist/resolution/frameworks/go.js +225 -0
  110. package/dist/resolution/frameworks/go.js.map +1 -0
  111. package/dist/resolution/frameworks/index.d.ts +33 -0
  112. package/dist/resolution/frameworks/index.d.ts.map +1 -0
  113. package/dist/resolution/frameworks/index.js +113 -0
  114. package/dist/resolution/frameworks/index.js.map +1 -0
  115. package/dist/resolution/frameworks/java.d.ts +8 -0
  116. package/dist/resolution/frameworks/java.d.ts.map +1 -0
  117. package/dist/resolution/frameworks/java.js +239 -0
  118. package/dist/resolution/frameworks/java.js.map +1 -0
  119. package/dist/resolution/frameworks/laravel.d.ts +13 -0
  120. package/dist/resolution/frameworks/laravel.d.ts.map +1 -0
  121. package/dist/resolution/frameworks/laravel.js +198 -0
  122. package/dist/resolution/frameworks/laravel.js.map +1 -0
  123. package/dist/resolution/frameworks/python.d.ts +10 -0
  124. package/dist/resolution/frameworks/python.d.ts.map +1 -0
  125. package/dist/resolution/frameworks/python.js +331 -0
  126. package/dist/resolution/frameworks/python.js.map +1 -0
  127. package/dist/resolution/frameworks/react.d.ts +8 -0
  128. package/dist/resolution/frameworks/react.d.ts.map +1 -0
  129. package/dist/resolution/frameworks/react.js +294 -0
  130. package/dist/resolution/frameworks/react.js.map +1 -0
  131. package/dist/resolution/frameworks/ruby.d.ts +8 -0
  132. package/dist/resolution/frameworks/ruby.d.ts.map +1 -0
  133. package/dist/resolution/frameworks/ruby.js +262 -0
  134. package/dist/resolution/frameworks/ruby.js.map +1 -0
  135. package/dist/resolution/frameworks/rust.d.ts +8 -0
  136. package/dist/resolution/frameworks/rust.d.ts.map +1 -0
  137. package/dist/resolution/frameworks/rust.js +222 -0
  138. package/dist/resolution/frameworks/rust.js.map +1 -0
  139. package/dist/resolution/frameworks/swift.d.ts +10 -0
  140. package/dist/resolution/frameworks/swift.d.ts.map +1 -0
  141. package/dist/resolution/frameworks/swift.js +486 -0
  142. package/dist/resolution/frameworks/swift.js.map +1 -0
  143. package/dist/resolution/import-resolver.d.ts +20 -0
  144. package/dist/resolution/import-resolver.d.ts.map +1 -0
  145. package/dist/resolution/import-resolver.js +445 -0
  146. package/dist/resolution/import-resolver.js.map +1 -0
  147. package/dist/resolution/index.d.ts +72 -0
  148. package/dist/resolution/index.d.ts.map +1 -0
  149. package/dist/resolution/index.js +301 -0
  150. package/dist/resolution/index.js.map +1 -0
  151. package/dist/resolution/name-matcher.d.ts +27 -0
  152. package/dist/resolution/name-matcher.d.ts.map +1 -0
  153. package/dist/resolution/name-matcher.js +210 -0
  154. package/dist/resolution/name-matcher.js.map +1 -0
  155. package/dist/resolution/types.d.ts +108 -0
  156. package/dist/resolution/types.d.ts.map +1 -0
  157. package/dist/resolution/types.js +8 -0
  158. package/dist/resolution/types.js.map +1 -0
  159. package/dist/sync/git-hooks.d.ts +66 -0
  160. package/dist/sync/git-hooks.d.ts.map +1 -0
  161. package/dist/sync/git-hooks.js +281 -0
  162. package/dist/sync/git-hooks.js.map +1 -0
  163. package/dist/sync/index.d.ts +13 -0
  164. package/dist/sync/index.d.ts.map +1 -0
  165. package/dist/sync/index.js +18 -0
  166. package/dist/sync/index.js.map +1 -0
  167. package/dist/types.d.ts +410 -0
  168. package/dist/types.d.ts.map +1 -0
  169. package/dist/types.js +165 -0
  170. package/dist/types.js.map +1 -0
  171. package/dist/utils.d.ts +116 -0
  172. package/dist/utils.d.ts.map +1 -0
  173. package/dist/utils.js +295 -0
  174. package/dist/utils.js.map +1 -0
  175. package/dist/vectors/embedder.d.ts +140 -0
  176. package/dist/vectors/embedder.d.ts.map +1 -0
  177. package/dist/vectors/embedder.js +336 -0
  178. package/dist/vectors/embedder.js.map +1 -0
  179. package/dist/vectors/index.d.ts +9 -0
  180. package/dist/vectors/index.d.ts.map +1 -0
  181. package/dist/vectors/index.js +20 -0
  182. package/dist/vectors/index.js.map +1 -0
  183. package/dist/vectors/manager.d.ts +119 -0
  184. package/dist/vectors/manager.d.ts.map +1 -0
  185. package/dist/vectors/manager.js +274 -0
  186. package/dist/vectors/manager.js.map +1 -0
  187. package/dist/vectors/search.d.ts +134 -0
  188. package/dist/vectors/search.d.ts.map +1 -0
  189. package/dist/vectors/search.js +409 -0
  190. package/dist/vectors/search.js.map +1 -0
  191. package/package.json +67 -0
  192. 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