@optave/codegraph 3.8.0 → 3.9.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 (310) hide show
  1. package/README.md +13 -8
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +137 -86
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/metrics.d.ts +0 -3
  6. package/dist/ast-analysis/metrics.d.ts.map +1 -1
  7. package/dist/ast-analysis/metrics.js +30 -13
  8. package/dist/ast-analysis/metrics.js.map +1 -1
  9. package/dist/ast-analysis/shared.d.ts.map +1 -1
  10. package/dist/ast-analysis/shared.js +24 -19
  11. package/dist/ast-analysis/shared.js.map +1 -1
  12. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitor-utils.js +55 -39
  14. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  15. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitor.js +91 -70
  17. package/dist/ast-analysis/visitor.js.map +1 -1
  18. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  19. package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
  20. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  21. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  22. package/dist/ast-analysis/visitors/complexity-visitor.js +81 -39
  23. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  24. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  25. package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
  26. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  27. package/dist/cli/commands/branch-compare.d.ts.map +1 -1
  28. package/dist/cli/commands/branch-compare.js +4 -0
  29. package/dist/cli/commands/branch-compare.js.map +1 -1
  30. package/dist/cli/commands/diff-impact.d.ts.map +1 -1
  31. package/dist/cli/commands/diff-impact.js +2 -1
  32. package/dist/cli/commands/diff-impact.js.map +1 -1
  33. package/dist/cli/commands/info.d.ts.map +1 -1
  34. package/dist/cli/commands/info.js +3 -2
  35. package/dist/cli/commands/info.js.map +1 -1
  36. package/dist/cli/commands/watch.d.ts.map +1 -1
  37. package/dist/cli/commands/watch.js +16 -2
  38. package/dist/cli/commands/watch.js.map +1 -1
  39. package/dist/db/connection.d.ts.map +1 -1
  40. package/dist/db/connection.js +29 -26
  41. package/dist/db/connection.js.map +1 -1
  42. package/dist/db/query-builder.d.ts.map +1 -1
  43. package/dist/db/query-builder.js +16 -5
  44. package/dist/db/query-builder.js.map +1 -1
  45. package/dist/db/repository/base.d.ts +16 -0
  46. package/dist/db/repository/base.d.ts.map +1 -1
  47. package/dist/db/repository/base.js +31 -0
  48. package/dist/db/repository/base.js.map +1 -1
  49. package/dist/db/repository/native-repository.d.ts +7 -1
  50. package/dist/db/repository/native-repository.d.ts.map +1 -1
  51. package/dist/db/repository/native-repository.js +100 -1
  52. package/dist/db/repository/native-repository.js.map +1 -1
  53. package/dist/db/repository/nodes.d.ts.map +1 -1
  54. package/dist/db/repository/nodes.js +8 -4
  55. package/dist/db/repository/nodes.js.map +1 -1
  56. package/dist/db/repository/sqlite-repository.d.ts +4 -0
  57. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  58. package/dist/db/repository/sqlite-repository.js +51 -0
  59. package/dist/db/repository/sqlite-repository.js.map +1 -1
  60. package/dist/domain/analysis/brief.d.ts.map +1 -1
  61. package/dist/domain/analysis/brief.js +13 -17
  62. package/dist/domain/analysis/brief.js.map +1 -1
  63. package/dist/domain/analysis/context.d.ts.map +1 -1
  64. package/dist/domain/analysis/context.js +14 -11
  65. package/dist/domain/analysis/context.js.map +1 -1
  66. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  67. package/dist/domain/analysis/dependencies.js +64 -59
  68. package/dist/domain/analysis/dependencies.js.map +1 -1
  69. package/dist/domain/analysis/fn-impact.d.ts +2 -7
  70. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  71. package/dist/domain/analysis/fn-impact.js +33 -31
  72. package/dist/domain/analysis/fn-impact.js.map +1 -1
  73. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  74. package/dist/domain/analysis/implementations.js +11 -19
  75. package/dist/domain/analysis/implementations.js.map +1 -1
  76. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  77. package/dist/domain/analysis/module-map.js +55 -76
  78. package/dist/domain/analysis/module-map.js.map +1 -1
  79. package/dist/domain/analysis/query-helpers.d.ts +7 -0
  80. package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
  81. package/dist/domain/analysis/query-helpers.js +15 -1
  82. package/dist/domain/analysis/query-helpers.js.map +1 -1
  83. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  84. package/dist/domain/graph/builder/pipeline.js +352 -107
  85. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  86. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  87. package/dist/domain/graph/builder/stages/build-edges.js +49 -18
  88. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  89. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  90. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  91. package/dist/domain/graph/builder/stages/finalize.js +2 -2
  92. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  93. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  94. package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
  95. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  96. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  97. package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
  98. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  99. package/dist/domain/graph/cycles.d.ts +6 -0
  100. package/dist/domain/graph/cycles.d.ts.map +1 -1
  101. package/dist/domain/graph/cycles.js +114 -22
  102. package/dist/domain/graph/cycles.js.map +1 -1
  103. package/dist/domain/graph/resolve.js +1 -1
  104. package/dist/domain/graph/resolve.js.map +1 -1
  105. package/dist/domain/graph/watcher.d.ts +2 -0
  106. package/dist/domain/graph/watcher.d.ts.map +1 -1
  107. package/dist/domain/graph/watcher.js +170 -75
  108. package/dist/domain/graph/watcher.js.map +1 -1
  109. package/dist/domain/parser.d.ts +3 -4
  110. package/dist/domain/parser.d.ts.map +1 -1
  111. package/dist/domain/parser.js +141 -89
  112. package/dist/domain/parser.js.map +1 -1
  113. package/dist/domain/search/generator.js +1 -1
  114. package/dist/domain/search/generator.js.map +1 -1
  115. package/dist/domain/search/models.d.ts +4 -3
  116. package/dist/domain/search/models.d.ts.map +1 -1
  117. package/dist/domain/search/models.js +23 -8
  118. package/dist/domain/search/models.js.map +1 -1
  119. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  120. package/dist/domain/search/search/hybrid.js +29 -18
  121. package/dist/domain/search/search/hybrid.js.map +1 -1
  122. package/dist/extractors/go.js +36 -33
  123. package/dist/extractors/go.js.map +1 -1
  124. package/dist/extractors/helpers.d.ts.map +1 -1
  125. package/dist/extractors/helpers.js +40 -29
  126. package/dist/extractors/helpers.js.map +1 -1
  127. package/dist/extractors/java.js +58 -46
  128. package/dist/extractors/java.js.map +1 -1
  129. package/dist/extractors/javascript.js +65 -54
  130. package/dist/extractors/javascript.js.map +1 -1
  131. package/dist/extractors/kotlin.js +84 -78
  132. package/dist/extractors/kotlin.js.map +1 -1
  133. package/dist/extractors/python.js +29 -24
  134. package/dist/extractors/python.js.map +1 -1
  135. package/dist/extractors/rust.js +41 -32
  136. package/dist/extractors/rust.js.map +1 -1
  137. package/dist/extractors/solidity.js +58 -67
  138. package/dist/extractors/solidity.js.map +1 -1
  139. package/dist/extractors/swift.js +83 -81
  140. package/dist/extractors/swift.js.map +1 -1
  141. package/dist/extractors/zig.js +58 -60
  142. package/dist/extractors/zig.js.map +1 -1
  143. package/dist/features/ast.d.ts +16 -14
  144. package/dist/features/ast.d.ts.map +1 -1
  145. package/dist/features/ast.js +83 -81
  146. package/dist/features/ast.js.map +1 -1
  147. package/dist/features/audit.d.ts.map +1 -1
  148. package/dist/features/audit.js +8 -6
  149. package/dist/features/audit.js.map +1 -1
  150. package/dist/features/branch-compare.d.ts.map +1 -1
  151. package/dist/features/branch-compare.js +69 -72
  152. package/dist/features/branch-compare.js.map +1 -1
  153. package/dist/features/communities.d.ts.map +1 -1
  154. package/dist/features/communities.js +19 -7
  155. package/dist/features/communities.js.map +1 -1
  156. package/dist/features/complexity.d.ts.map +1 -1
  157. package/dist/features/complexity.js +120 -125
  158. package/dist/features/complexity.js.map +1 -1
  159. package/dist/features/dataflow.d.ts.map +1 -1
  160. package/dist/features/dataflow.js +136 -137
  161. package/dist/features/dataflow.js.map +1 -1
  162. package/dist/features/flow.d.ts.map +1 -1
  163. package/dist/features/flow.js +84 -79
  164. package/dist/features/flow.js.map +1 -1
  165. package/dist/features/structure-query.d.ts.map +1 -1
  166. package/dist/features/structure-query.js +69 -65
  167. package/dist/features/structure-query.js.map +1 -1
  168. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  169. package/dist/graph/algorithms/leiden/optimiser.js +70 -55
  170. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  171. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  172. package/dist/graph/algorithms/leiden/partition.js +288 -266
  173. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  174. package/dist/graph/model.d.ts.map +1 -1
  175. package/dist/graph/model.js +5 -1
  176. package/dist/graph/model.js.map +1 -1
  177. package/dist/infrastructure/config.d.ts.map +1 -1
  178. package/dist/infrastructure/config.js +6 -4
  179. package/dist/infrastructure/config.js.map +1 -1
  180. package/dist/infrastructure/suppress.d.ts +25 -0
  181. package/dist/infrastructure/suppress.d.ts.map +1 -0
  182. package/dist/infrastructure/suppress.js +43 -0
  183. package/dist/infrastructure/suppress.js.map +1 -0
  184. package/dist/mcp/server.d.ts.map +1 -1
  185. package/dist/mcp/server.js +29 -24
  186. package/dist/mcp/server.js.map +1 -1
  187. package/dist/presentation/dataflow.d.ts.map +1 -1
  188. package/dist/presentation/dataflow.js +47 -38
  189. package/dist/presentation/dataflow.js.map +1 -1
  190. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
  191. package/dist/presentation/diff-impact-mermaid.js +60 -51
  192. package/dist/presentation/diff-impact-mermaid.js.map +1 -1
  193. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  194. package/dist/presentation/queries-cli/exports.js +20 -14
  195. package/dist/presentation/queries-cli/exports.js.map +1 -1
  196. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  197. package/dist/presentation/queries-cli/impact.js +15 -13
  198. package/dist/presentation/queries-cli/impact.js.map +1 -1
  199. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  200. package/dist/presentation/queries-cli/inspect.js +101 -79
  201. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  202. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  203. package/dist/presentation/queries-cli/overview.js +25 -16
  204. package/dist/presentation/queries-cli/overview.js.map +1 -1
  205. package/dist/presentation/queries-cli/path.js +26 -20
  206. package/dist/presentation/queries-cli/path.js.map +1 -1
  207. package/dist/presentation/result-formatter.d.ts +10 -0
  208. package/dist/presentation/result-formatter.d.ts.map +1 -1
  209. package/dist/presentation/result-formatter.js +16 -1
  210. package/dist/presentation/result-formatter.js.map +1 -1
  211. package/dist/presentation/viewer.d.ts.map +1 -1
  212. package/dist/presentation/viewer.js +18 -12
  213. package/dist/presentation/viewer.js.map +1 -1
  214. package/dist/shared/errors.d.ts +5 -0
  215. package/dist/shared/errors.d.ts.map +1 -1
  216. package/dist/shared/errors.js +5 -0
  217. package/dist/shared/errors.js.map +1 -1
  218. package/dist/shared/hierarchy.d.ts +8 -2
  219. package/dist/shared/hierarchy.d.ts.map +1 -1
  220. package/dist/shared/hierarchy.js +42 -1
  221. package/dist/shared/hierarchy.js.map +1 -1
  222. package/dist/shared/normalize.d.ts +6 -1
  223. package/dist/shared/normalize.d.ts.map +1 -1
  224. package/dist/shared/normalize.js +20 -12
  225. package/dist/shared/normalize.js.map +1 -1
  226. package/dist/shared/paginate.d.ts +0 -9
  227. package/dist/shared/paginate.d.ts.map +1 -1
  228. package/dist/shared/paginate.js +0 -15
  229. package/dist/shared/paginate.js.map +1 -1
  230. package/dist/types.d.ts +12 -5
  231. package/dist/types.d.ts.map +1 -1
  232. package/grammars/tree-sitter-erlang.wasm +0 -0
  233. package/grammars/tree-sitter-gleam.wasm +0 -0
  234. package/package.json +9 -9
  235. package/src/ast-analysis/engine.ts +176 -104
  236. package/src/ast-analysis/metrics.ts +33 -11
  237. package/src/ast-analysis/shared.ts +33 -24
  238. package/src/ast-analysis/visitor-utils.ts +52 -32
  239. package/src/ast-analysis/visitor.ts +132 -71
  240. package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
  241. package/src/ast-analysis/visitors/complexity-visitor.ts +89 -40
  242. package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
  243. package/src/cli/commands/branch-compare.ts +4 -0
  244. package/src/cli/commands/diff-impact.ts +2 -1
  245. package/src/cli/commands/info.ts +3 -2
  246. package/src/cli/commands/watch.ts +16 -2
  247. package/src/db/connection.ts +29 -28
  248. package/src/db/query-builder.ts +15 -3
  249. package/src/db/repository/base.ts +34 -0
  250. package/src/db/repository/native-repository.ts +104 -1
  251. package/src/db/repository/nodes.ts +13 -8
  252. package/src/db/repository/sqlite-repository.ts +55 -0
  253. package/src/domain/analysis/brief.ts +15 -25
  254. package/src/domain/analysis/context.ts +17 -10
  255. package/src/domain/analysis/dependencies.ts +77 -81
  256. package/src/domain/analysis/fn-impact.ts +36 -43
  257. package/src/domain/analysis/implementations.ts +11 -17
  258. package/src/domain/analysis/module-map.ts +58 -92
  259. package/src/domain/analysis/query-helpers.ts +18 -1
  260. package/src/domain/graph/builder/pipeline.ts +409 -99
  261. package/src/domain/graph/builder/stages/build-edges.ts +45 -19
  262. package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
  263. package/src/domain/graph/builder/stages/finalize.ts +2 -2
  264. package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
  265. package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
  266. package/src/domain/graph/cycles.ts +110 -23
  267. package/src/domain/graph/resolve.ts +1 -1
  268. package/src/domain/graph/watcher.ts +202 -96
  269. package/src/domain/parser.ts +143 -89
  270. package/src/domain/search/generator.ts +1 -1
  271. package/src/domain/search/models.ts +26 -7
  272. package/src/domain/search/search/hybrid.ts +69 -51
  273. package/src/extractors/go.ts +43 -33
  274. package/src/extractors/helpers.ts +37 -23
  275. package/src/extractors/java.ts +66 -47
  276. package/src/extractors/javascript.ts +66 -54
  277. package/src/extractors/kotlin.ts +84 -77
  278. package/src/extractors/python.ts +31 -25
  279. package/src/extractors/rust.ts +37 -29
  280. package/src/extractors/solidity.ts +57 -61
  281. package/src/extractors/swift.ts +81 -80
  282. package/src/extractors/zig.ts +58 -61
  283. package/src/features/ast.ts +130 -110
  284. package/src/features/audit.ts +8 -6
  285. package/src/features/branch-compare.ts +105 -79
  286. package/src/features/communities.ts +25 -10
  287. package/src/features/complexity.ts +171 -134
  288. package/src/features/dataflow.ts +165 -175
  289. package/src/features/flow.ts +129 -92
  290. package/src/features/structure-query.ts +79 -64
  291. package/src/graph/algorithms/leiden/optimiser.ts +99 -55
  292. package/src/graph/algorithms/leiden/partition.ts +359 -294
  293. package/src/graph/model.ts +6 -1
  294. package/src/infrastructure/config.ts +6 -4
  295. package/src/infrastructure/suppress.ts +47 -0
  296. package/src/mcp/server.ts +53 -37
  297. package/src/presentation/dataflow.ts +50 -44
  298. package/src/presentation/diff-impact-mermaid.ts +104 -62
  299. package/src/presentation/queries-cli/exports.ts +21 -13
  300. package/src/presentation/queries-cli/impact.ts +15 -13
  301. package/src/presentation/queries-cli/inspect.ts +100 -81
  302. package/src/presentation/queries-cli/overview.ts +26 -16
  303. package/src/presentation/queries-cli/path.ts +33 -25
  304. package/src/presentation/result-formatter.ts +19 -1
  305. package/src/presentation/viewer.ts +42 -14
  306. package/src/shared/errors.ts +6 -0
  307. package/src/shared/hierarchy.ts +50 -2
  308. package/src/shared/normalize.ts +31 -12
  309. package/src/shared/paginate.ts +0 -17
  310. package/src/types.ts +26 -5
@@ -154,6 +154,32 @@ export class SqliteRepository extends Repository {
154
154
  return findCallers(this.#db, nodeId);
155
155
  }
156
156
 
157
+ findCallersBatch(nodeIds: number[]): Map<number, RelatedNodeRow[]> {
158
+ if (nodeIds.length === 0) return new Map();
159
+ const placeholders = nodeIds.map(() => '?').join(',');
160
+ const rows = this.#db
161
+ .prepare(
162
+ `SELECT e.target_id AS queried_id, n.id, n.name, n.kind, n.file, n.line, n.end_line
163
+ FROM edges e JOIN nodes n ON e.source_id = n.id
164
+ WHERE e.target_id IN (${placeholders}) AND e.kind = 'calls'`,
165
+ )
166
+ .all(...nodeIds) as Array<RelatedNodeRow & { queried_id: number }>;
167
+ const result = new Map<number, RelatedNodeRow[]>();
168
+ for (const row of rows) {
169
+ const qid = row.queried_id;
170
+ if (!result.has(qid)) result.set(qid, []);
171
+ result.get(qid)!.push({
172
+ id: row.id,
173
+ name: row.name,
174
+ kind: row.kind,
175
+ file: row.file,
176
+ line: row.line,
177
+ end_line: row.end_line,
178
+ });
179
+ }
180
+ return result;
181
+ }
182
+
157
183
  findDistinctCallers(nodeId: number): RelatedNodeRow[] {
158
184
  return findDistinctCallers(this.#db, nodeId);
159
185
  }
@@ -245,4 +271,33 @@ export class SqliteRepository extends Repository {
245
271
  getComplexityForNode(nodeId: number): ComplexityMetrics | undefined {
246
272
  return getComplexityForNode(this.#db, nodeId);
247
273
  }
274
+
275
+ // ── Convenience queries ────────────────────────────────────────────
276
+
277
+ getFileHash(file: string): string | null {
278
+ const row = this.#db.prepare('SELECT hash FROM file_hashes WHERE file = ?').get(file) as
279
+ | { hash: string }
280
+ | undefined;
281
+ return row?.hash ?? null;
282
+ }
283
+
284
+ #implementsEdgesCache?: boolean;
285
+ hasImplementsEdges(): boolean {
286
+ if (this.#implementsEdgesCache !== undefined) return this.#implementsEdgesCache;
287
+ this.#implementsEdgesCache = !!this.#db
288
+ .prepare("SELECT 1 FROM edges WHERE kind = 'implements' LIMIT 1")
289
+ .get();
290
+ return this.#implementsEdgesCache;
291
+ }
292
+
293
+ #coChangesTableCache?: boolean;
294
+ hasCoChangesTable(): boolean {
295
+ if (this.#coChangesTableCache !== undefined) return this.#coChangesTableCache;
296
+ try {
297
+ this.#coChangesTableCache = !!this.#db.prepare('SELECT 1 FROM co_changes LIMIT 1').get();
298
+ } catch {
299
+ this.#coChangesTableCache = false;
300
+ }
301
+ return this.#coChangesTableCache;
302
+ }
248
303
  }
@@ -1,15 +1,8 @@
1
- import {
2
- findDistinctCallers,
3
- findFileNodes,
4
- findImportDependents,
5
- findImportSources,
6
- findImportTargets,
7
- findNodesByFile,
8
- openReadonlyOrFail,
9
- } from '../../db/index.js';
1
+ import type { Repository } from '../../db/index.js';
10
2
  import { loadConfig } from '../../infrastructure/config.js';
11
3
  import { isTestFile } from '../../infrastructure/test-filter.js';
12
- import type { BetterSqlite3Database, ImportEdgeRow, NodeRow, RelatedNodeRow } from '../../types.js';
4
+ import type { ImportEdgeRow, NodeRow, RelatedNodeRow } from '../../types.js';
5
+ import { withRepo } from './query-helpers.js';
13
6
 
14
7
  /** Symbol kinds meaningful for a file brief — excludes parameters, properties, constants. */
15
8
  const BRIEF_KINDS = new Set([
@@ -49,7 +42,7 @@ function computeRiskTier(
49
42
  * Lightweight variant — only counts, does not collect details.
50
43
  */
51
44
  function countTransitiveCallers(
52
- db: BetterSqlite3Database,
45
+ repo: InstanceType<typeof Repository>,
53
46
  startId: number,
54
47
  noTests: boolean,
55
48
  maxDepth = 5,
@@ -60,7 +53,7 @@ function countTransitiveCallers(
60
53
  for (let d = 1; d <= maxDepth; d++) {
61
54
  const nextFrontier: number[] = [];
62
55
  for (const fid of frontier) {
63
- const callers = findDistinctCallers(db, fid) as RelatedNodeRow[];
56
+ const callers = repo.findDistinctCallers(fid) as RelatedNodeRow[];
64
57
  for (const c of callers) {
65
58
  if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
66
59
  visited.add(c.id);
@@ -80,7 +73,7 @@ function countTransitiveCallers(
80
73
  * Depth-bounded to match countTransitiveCallers and keep hook latency predictable.
81
74
  */
82
75
  function countTransitiveImporters(
83
- db: BetterSqlite3Database,
76
+ repo: InstanceType<typeof Repository>,
84
77
  fileNodeIds: number[],
85
78
  noTests: boolean,
86
79
  maxDepth = 5,
@@ -91,7 +84,7 @@ function countTransitiveImporters(
91
84
  for (let d = 1; d <= maxDepth; d++) {
92
85
  const nextFrontier: number[] = [];
93
86
  for (const current of frontier) {
94
- const dependents = findImportDependents(db, current) as RelatedNodeRow[];
87
+ const dependents = repo.findImportDependents(current) as RelatedNodeRow[];
95
88
  for (const dep of dependents) {
96
89
  if (!visited.has(dep.id) && (!noTests || !isTestFile(dep.file))) {
97
90
  visited.add(dep.id);
@@ -115,38 +108,37 @@ export function briefData(
115
108
  customDbPath: string,
116
109
  opts: { noTests?: boolean; config?: any } = {},
117
110
  ) {
118
- const db = openReadonlyOrFail(customDbPath);
119
- try {
111
+ return withRepo(customDbPath, (repo) => {
120
112
  const noTests = opts.noTests || false;
121
113
  const config = opts.config || loadConfig();
122
114
  const callerDepth = config.analysis?.briefCallerDepth ?? 5;
123
115
  const importerDepth = config.analysis?.briefImporterDepth ?? 5;
124
116
  const highRiskCallers = config.analysis?.briefHighRiskCallers ?? 10;
125
117
  const mediumRiskCallers = config.analysis?.briefMediumRiskCallers ?? 3;
126
- const fileNodes = findFileNodes(db, `%${file}%`) as NodeRow[];
118
+ const fileNodes = repo.findFileNodes(`%${file}%`) as NodeRow[];
127
119
  if (fileNodes.length === 0) {
128
120
  return { file, results: [] };
129
121
  }
130
122
 
131
123
  const results = fileNodes.map((fn) => {
132
124
  // Direct importers
133
- let importedBy = findImportSources(db, fn.id) as ImportEdgeRow[];
125
+ let importedBy = repo.findImportSources(fn.id) as ImportEdgeRow[];
134
126
  if (noTests) importedBy = importedBy.filter((i) => !isTestFile(i.file));
135
127
  const directImporters = [...new Set(importedBy.map((i) => i.file))];
136
128
 
137
129
  // Transitive importer count
138
- const totalImporterCount = countTransitiveImporters(db, [fn.id], noTests, importerDepth);
130
+ const totalImporterCount = countTransitiveImporters(repo, [fn.id], noTests, importerDepth);
139
131
 
140
132
  // Direct imports
141
- let importsTo = findImportTargets(db, fn.id) as ImportEdgeRow[];
133
+ let importsTo = repo.findImportTargets(fn.id) as ImportEdgeRow[];
142
134
  if (noTests) importsTo = importsTo.filter((i) => !isTestFile(i.file));
143
135
 
144
136
  // Symbol definitions with roles and caller counts
145
- const defs = (findNodesByFile(db, fn.file) as NodeRow[]).filter((d) =>
137
+ const defs = (repo.findNodesByFile(fn.file) as NodeRow[]).filter((d) =>
146
138
  BRIEF_KINDS.has(d.kind),
147
139
  );
148
140
  const symbols = defs.map((d) => {
149
- const callerCount = countTransitiveCallers(db, d.id, noTests, callerDepth);
141
+ const callerCount = countTransitiveCallers(repo, d.id, noTests, callerDepth);
150
142
  return {
151
143
  name: d.name,
152
144
  kind: d.kind,
@@ -169,7 +161,5 @@ export function briefData(
169
161
  });
170
162
 
171
163
  return { file, results };
172
- } finally {
173
- db.close();
174
- }
164
+ });
175
165
  }
@@ -264,6 +264,22 @@ function getNodeChildrenSafe(db: BetterSqlite3Database, nodeId: number) {
264
264
  }
265
265
  }
266
266
 
267
+ function buildIntraFileDataFlow(
268
+ db: BetterSqlite3Database,
269
+ file: string,
270
+ ): Array<{ caller: string; callees: string[] }> {
271
+ const intraEdges = findIntraFileCallEdges(db, file) as IntraFileCallEdge[];
272
+ const dataFlowMap = new Map<string, string[]>();
273
+ for (const edge of intraEdges) {
274
+ if (!dataFlowMap.has(edge.caller_name)) dataFlowMap.set(edge.caller_name, []);
275
+ dataFlowMap.get(edge.caller_name)!.push(edge.callee_name);
276
+ }
277
+ return [...dataFlowMap.entries()].map(([caller, callees]) => ({
278
+ caller,
279
+ callees,
280
+ }));
281
+ }
282
+
267
283
  function explainFileImpl(
268
284
  db: BetterSqlite3Database,
269
285
  target: string,
@@ -299,16 +315,7 @@ function explainFileImpl(
299
315
  file: r.file,
300
316
  }));
301
317
 
302
- const intraEdges = findIntraFileCallEdges(db, fn.file) as IntraFileCallEdge[];
303
- const dataFlowMap = new Map<string, string[]>();
304
- for (const edge of intraEdges) {
305
- if (!dataFlowMap.has(edge.caller_name)) dataFlowMap.set(edge.caller_name, []);
306
- dataFlowMap.get(edge.caller_name)!.push(edge.callee_name);
307
- }
308
- const dataFlow = [...dataFlowMap.entries()].map(([caller, callees]) => ({
309
- caller,
310
- callees,
311
- }));
318
+ const dataFlow = buildIntraFileDataFlow(db, fn.file);
312
319
 
313
320
  const metric = getLineCountForNode(db, fn.id) as { line_count: number } | undefined;
314
321
  let lineCount: number | null = metric?.line_count || null;
@@ -1,11 +1,4 @@
1
- import {
2
- findCallees,
3
- findCallers,
4
- findFileNodes,
5
- findImportSources,
6
- findImportTargets,
7
- findNodesByFile,
8
- } from '../../db/index.js';
1
+ import { findFileNodes, type Repository } from '../../db/index.js';
9
2
  import { cachedStmt } from '../../db/repository/cached-stmt.js';
10
3
  import { isTestFile } from '../../infrastructure/test-filter.js';
11
4
  import { resolveMethodViaHierarchy } from '../../shared/hierarchy.js';
@@ -18,13 +11,11 @@ import type {
18
11
  RelatedNodeRow,
19
12
  StmtCache,
20
13
  } from '../../types.js';
21
- import { withReadonlyDb } from './query-helpers.js';
14
+ import { withReadonlyDb, withRepo } from './query-helpers.js';
22
15
  import { findMatchingNodes } from './symbol-lookup.js';
23
16
 
24
- type UpstreamRow = { id: number; name: string; kind: string; file: string; line: number };
25
17
  type NodeByIdRow = { name: string; kind: string; file: string; line: number };
26
18
 
27
- const _upstreamStmtCache: StmtCache<UpstreamRow> = new WeakMap();
28
19
  const _nodeByIdStmtCache: StmtCache<NodeByIdRow> = new WeakMap();
29
20
 
30
21
  export function fileDepsData(
@@ -32,21 +23,21 @@ export function fileDepsData(
32
23
  customDbPath: string,
33
24
  opts: { noTests?: boolean; limit?: number; offset?: number } = {},
34
25
  ) {
35
- return withReadonlyDb(customDbPath, (db) => {
26
+ return withRepo(customDbPath, (repo) => {
36
27
  const noTests = opts.noTests || false;
37
- const fileNodes = findFileNodes(db, `%${file}%`) as NodeRow[];
28
+ const fileNodes = repo.findFileNodes(`%${file}%`) as NodeRow[];
38
29
  if (fileNodes.length === 0) {
39
30
  return { file, results: [] };
40
31
  }
41
32
 
42
33
  const results = fileNodes.map((fn) => {
43
- let importsTo = findImportTargets(db, fn.id) as ImportEdgeRow[];
34
+ let importsTo = repo.findImportTargets(fn.id) as ImportEdgeRow[];
44
35
  if (noTests) importsTo = importsTo.filter((i) => !isTestFile(i.file));
45
36
 
46
- let importedBy = findImportSources(db, fn.id) as ImportEdgeRow[];
37
+ let importedBy = repo.findImportSources(fn.id) as ImportEdgeRow[];
47
38
  if (noTests) importedBy = importedBy.filter((i) => !isTestFile(i.file));
48
39
 
49
- const defs = findNodesByFile(db, fn.file) as NodeRow[];
40
+ const defs = repo.findNodesByFile(fn.file) as NodeRow[];
50
41
 
51
42
  return {
52
43
  file: fn.file,
@@ -64,9 +55,11 @@ export function fileDepsData(
64
55
  /**
65
56
  * BFS transitive caller traversal starting from `callers` of `nodeId`.
66
57
  * Returns an object keyed by depth (2..depth) -> array of caller descriptors.
58
+ *
59
+ * Uses Repository.findCallers() so it works with both native and WASM engines.
67
60
  */
68
61
  function buildTransitiveCallers(
69
- db: BetterSqlite3Database,
62
+ repo: InstanceType<typeof Repository>,
70
63
  callers: Array<{ id: number; name: string; kind: string; file: string; line: number }>,
71
64
  nodeId: number,
72
65
  depth: number,
@@ -81,31 +74,21 @@ function buildTransitiveCallers(
81
74
  const visited = new Set([nodeId]);
82
75
  let frontier = callers;
83
76
 
84
- const upstreamStmt = cachedStmt(
85
- _upstreamStmtCache,
86
- db,
87
- `
88
- SELECT n.id, n.name, n.kind, n.file, n.line
89
- FROM edges e JOIN nodes n ON e.source_id = n.id
90
- WHERE e.target_id = ? AND e.kind = 'calls'
91
- `,
92
- );
93
-
94
77
  for (let d = 2; d <= depth; d++) {
78
+ // Collect unvisited frontier IDs for a single batched query per depth
79
+ const unvisited = frontier.filter((f) => !visited.has(f.id));
80
+ for (const f of unvisited) visited.add(f.id);
81
+ if (unvisited.length === 0) break;
82
+
83
+ const batchCallers = repo.findCallersBatch(unvisited.map((f) => f.id));
95
84
  const nextFrontier: typeof frontier = [];
96
- for (const f of frontier) {
97
- if (visited.has(f.id)) continue;
98
- visited.add(f.id);
99
- const upstream = upstreamStmt.all(f.id) as Array<{
100
- id: number;
101
- name: string;
102
- kind: string;
103
- file: string;
104
- line: number;
105
- }>;
85
+ const nextFrontierIds = new Set<number>();
86
+ for (const f of unvisited) {
87
+ const upstream = batchCallers.get(f.id) || [];
106
88
  for (const u of upstream) {
107
89
  if (noTests && isTestFile(u.file)) continue;
108
- if (!visited.has(u.id)) {
90
+ if (!visited.has(u.id) && !nextFrontierIds.has(u.id)) {
91
+ nextFrontierIds.add(u.id);
109
92
  nextFrontier.push(u);
110
93
  }
111
94
  }
@@ -119,12 +102,64 @@ function buildTransitiveCallers(
119
102
  }));
120
103
  }
121
104
  frontier = nextFrontier;
122
- if (frontier.length === 0) break;
123
105
  }
124
106
 
125
107
  return transitiveCallers;
126
108
  }
127
109
 
110
+ function collectCallersWithHierarchy(
111
+ repo: InstanceType<typeof Repository>,
112
+ node: NodeRow,
113
+ noTests: boolean,
114
+ ): Array<RelatedNodeRow & { viaHierarchy?: string }> {
115
+ let callers: Array<RelatedNodeRow & { viaHierarchy?: string }> = repo.findCallers(
116
+ node.id,
117
+ ) as RelatedNodeRow[];
118
+
119
+ if (node.kind === 'method' && node.name.includes('.')) {
120
+ const methodName = node.name.split('.').pop()!;
121
+ const relatedMethods = resolveMethodViaHierarchy(repo, methodName);
122
+ for (const rm of relatedMethods) {
123
+ if (rm.id === node.id) continue;
124
+ const extraCallers = repo.findCallers(rm.id) as RelatedNodeRow[];
125
+ callers.push(...extraCallers.map((c) => ({ ...c, viaHierarchy: rm.name })));
126
+ }
127
+ }
128
+ if (noTests) callers = callers.filter((c) => !isTestFile(c.file));
129
+ return callers;
130
+ }
131
+
132
+ function buildNodeDepsResult(
133
+ repo: InstanceType<typeof Repository>,
134
+ node: NodeRow,
135
+ hc: Map<string, string | null>,
136
+ depth: number,
137
+ noTests: boolean,
138
+ ) {
139
+ const callees = repo.findCallees(node.id) as RelatedNodeRow[];
140
+ const filteredCallees = noTests ? callees.filter((c) => !isTestFile(c.file)) : callees;
141
+ const callers = collectCallersWithHierarchy(repo, node, noTests);
142
+ const transitiveCallers = buildTransitiveCallers(repo, callers, node.id, depth, noTests);
143
+
144
+ return {
145
+ ...normalizeSymbol(node, repo, hc),
146
+ callees: filteredCallees.map((c) => ({
147
+ name: c.name,
148
+ kind: c.kind,
149
+ file: c.file,
150
+ line: c.line,
151
+ })),
152
+ callers: callers.map((c) => ({
153
+ name: c.name,
154
+ kind: c.kind,
155
+ file: c.file,
156
+ line: c.line,
157
+ viaHierarchy: c.viaHierarchy || undefined,
158
+ })),
159
+ transitiveCallers,
160
+ };
161
+ }
162
+
128
163
  export function fnDepsData(
129
164
  name: string,
130
165
  customDbPath: string,
@@ -137,56 +172,17 @@ export function fnDepsData(
137
172
  offset?: number;
138
173
  } = {},
139
174
  ) {
140
- return withReadonlyDb(customDbPath, (db) => {
175
+ return withRepo(customDbPath, (repo) => {
141
176
  const depth = opts.depth || 3;
142
177
  const noTests = opts.noTests || false;
143
178
  const hc = new Map();
144
179
 
145
- const nodes = findMatchingNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
180
+ const nodes = findMatchingNodes(repo, name, { noTests, file: opts.file, kind: opts.kind });
146
181
  if (nodes.length === 0) {
147
182
  return { name, results: [] };
148
183
  }
149
184
 
150
- const results = nodes.map((node) => {
151
- const callees = findCallees(db, node.id) as RelatedNodeRow[];
152
- const filteredCallees = noTests ? callees.filter((c) => !isTestFile(c.file)) : callees;
153
-
154
- let callers: Array<RelatedNodeRow & { viaHierarchy?: string }> = findCallers(
155
- db,
156
- node.id,
157
- ) as RelatedNodeRow[];
158
-
159
- if (node.kind === 'method' && node.name.includes('.')) {
160
- const methodName = node.name.split('.').pop()!;
161
- const relatedMethods = resolveMethodViaHierarchy(db, methodName);
162
- for (const rm of relatedMethods) {
163
- if (rm.id === node.id) continue;
164
- const extraCallers = findCallers(db, rm.id) as RelatedNodeRow[];
165
- callers.push(...extraCallers.map((c) => ({ ...c, viaHierarchy: rm.name })));
166
- }
167
- }
168
- if (noTests) callers = callers.filter((c) => !isTestFile(c.file));
169
-
170
- const transitiveCallers = buildTransitiveCallers(db, callers, node.id, depth, noTests);
171
-
172
- return {
173
- ...normalizeSymbol(node, db, hc),
174
- callees: filteredCallees.map((c) => ({
175
- name: c.name,
176
- kind: c.kind,
177
- file: c.file,
178
- line: c.line,
179
- })),
180
- callers: callers.map((c) => ({
181
- name: c.name,
182
- kind: c.kind,
183
- file: c.file,
184
- line: c.line,
185
- viaHierarchy: c.viaHierarchy || undefined,
186
- })),
187
- transitiveCallers,
188
- };
189
- });
185
+ const results = nodes.map((node) => buildNodeDepsResult(repo, node, hc, depth, noTests));
190
186
 
191
187
  const base = { name, results };
192
188
  return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
@@ -1,40 +1,32 @@
1
- import {
2
- findDistinctCallers,
3
- findFileNodes,
4
- findImplementors,
5
- findImportDependents,
6
- findNodeById,
7
- } from '../../db/index.js';
1
+ import { Repository, SqliteRepository } from '../../db/index.js';
8
2
  import { isTestFile } from '../../infrastructure/test-filter.js';
9
3
  import { normalizeSymbol } from '../../shared/normalize.js';
10
4
  import { paginateResult } from '../../shared/paginate.js';
11
5
  import type { BetterSqlite3Database, NodeRow, RelatedNodeRow } from '../../types.js';
12
- import { resolveAnalysisOpts, withReadonlyDb } from './query-helpers.js';
6
+ import { resolveAnalysisOpts, withRepo } from './query-helpers.js';
13
7
  import { findMatchingNodes } from './symbol-lookup.js';
14
8
 
9
+ /** Cache so repeated raw-db calls reuse the same SqliteRepository (preserves per-instance memoization). */
10
+ const repoCache = new WeakMap<BetterSqlite3Database, InstanceType<typeof SqliteRepository>>();
11
+
12
+ /** Coerce a raw db handle or Repository into a Repository instance. */
13
+ function toRepo(
14
+ dbOrRepo: BetterSqlite3Database | InstanceType<typeof Repository>,
15
+ ): InstanceType<typeof Repository> {
16
+ if (dbOrRepo instanceof Repository) return dbOrRepo;
17
+ const db = dbOrRepo as BetterSqlite3Database;
18
+ let repo = repoCache.get(db);
19
+ if (!repo) {
20
+ repo = new SqliteRepository(db);
21
+ repoCache.set(db, repo);
22
+ }
23
+ return repo;
24
+ }
25
+
15
26
  // --- Shared BFS: transitive callers ---
16
27
 
17
28
  const INTERFACE_LIKE_KINDS = new Set(['interface', 'trait']);
18
29
 
19
- /**
20
- * Check whether the graph contains any 'implements' edges.
21
- * Cached per db handle so the query runs at most once per connection.
22
- */
23
- const _hasImplementsCache: WeakMap<BetterSqlite3Database, boolean> = new WeakMap();
24
- function hasImplementsEdges(db: BetterSqlite3Database): boolean {
25
- if (_hasImplementsCache.has(db)) return _hasImplementsCache.get(db)!;
26
- const row = db.prepare("SELECT 1 FROM edges WHERE kind = 'implements' LIMIT 1").get();
27
- const result = !!row;
28
- _hasImplementsCache.set(db, result);
29
- return result;
30
- }
31
-
32
- /**
33
- * BFS traversal to find transitive callers of a node.
34
- * When an interface/trait node is encountered (either as the start node or
35
- * during traversal), its concrete implementors are also added to the frontier
36
- * so that changes to an interface signature propagate to all implementors.
37
- */
38
30
  type BfsLevel = Array<{
39
31
  name: string;
40
32
  kind: string;
@@ -76,7 +68,7 @@ function recordImplementor(
76
68
 
77
69
  /** Expand implementors for an interface/trait node into the BFS frontier. */
78
70
  function expandImplementors(
79
- db: BetterSqlite3Database,
71
+ repo: InstanceType<typeof Repository>,
80
72
  nodeId: number,
81
73
  depth: number,
82
74
  visited: Set<number>,
@@ -85,14 +77,14 @@ function expandImplementors(
85
77
  noTests: boolean,
86
78
  onVisit?: BfsOnVisit,
87
79
  ): void {
88
- const impls = findImplementors(db, nodeId) as RelatedNodeRow[];
80
+ const impls = repo.findImplementors(nodeId) as RelatedNodeRow[];
89
81
  for (const impl of impls) {
90
82
  recordImplementor(impl, nodeId, depth, visited, frontier, levels, noTests, onVisit);
91
83
  }
92
84
  }
93
85
 
94
86
  export function bfsTransitiveCallers(
95
- db: BetterSqlite3Database,
87
+ dbOrRepo: BetterSqlite3Database | InstanceType<typeof Repository>,
96
88
  startId: number,
97
89
  {
98
90
  noTests = false,
@@ -106,7 +98,8 @@ export function bfsTransitiveCallers(
106
98
  onVisit?: BfsOnVisit;
107
99
  } = {},
108
100
  ) {
109
- const resolveImplementors = includeImplementors && hasImplementsEdges(db);
101
+ const repo = toRepo(dbOrRepo);
102
+ const resolveImplementors = includeImplementors && repo.hasImplementsEdges();
110
103
  const visited = new Set([startId]);
111
104
  const levels: BfsLevels = {};
112
105
  let frontier = [startId];
@@ -114,9 +107,9 @@ export function bfsTransitiveCallers(
114
107
  // Seed: if start node is an interface/trait, include its implementors at depth 1
115
108
  const implNextFrontier: number[] = [];
116
109
  if (resolveImplementors) {
117
- const startNode = findNodeById(db, startId) as NodeRow | undefined;
110
+ const startNode = repo.findNodeById(startId) as NodeRow | undefined;
118
111
  if (startNode && INTERFACE_LIKE_KINDS.has(startNode.kind)) {
119
- expandImplementors(db, startId, 1, visited, implNextFrontier, levels, noTests, onVisit);
112
+ expandImplementors(repo, startId, 1, visited, implNextFrontier, levels, noTests, onVisit);
120
113
  }
121
114
  }
122
115
 
@@ -126,7 +119,7 @@ export function bfsTransitiveCallers(
126
119
  }
127
120
  const nextFrontier: number[] = [];
128
121
  for (const fid of frontier) {
129
- const callers = findDistinctCallers(db, fid) as RelatedNodeRow[];
122
+ const callers = repo.findDistinctCallers(fid) as RelatedNodeRow[];
130
123
  for (const c of callers) {
131
124
  if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
132
125
  visited.add(c.id);
@@ -136,7 +129,7 @@ export function bfsTransitiveCallers(
136
129
  if (onVisit) onVisit(c, fid, d);
137
130
  }
138
131
  if (resolveImplementors && INTERFACE_LIKE_KINDS.has(c.kind)) {
139
- expandImplementors(db, c.id, d + 1, visited, nextFrontier, levels, noTests, onVisit);
132
+ expandImplementors(repo, c.id, d + 1, visited, nextFrontier, levels, noTests, onVisit);
140
133
  }
141
134
  }
142
135
  }
@@ -152,9 +145,9 @@ export function impactAnalysisData(
152
145
  customDbPath: string,
153
146
  opts: { noTests?: boolean } = {},
154
147
  ) {
155
- return withReadonlyDb(customDbPath, (db) => {
148
+ return withRepo(customDbPath, (repo) => {
156
149
  const noTests = opts.noTests || false;
157
- const fileNodes = findFileNodes(db, `%${file}%`) as NodeRow[];
150
+ const fileNodes = repo.findFileNodes(`%${file}%`) as NodeRow[];
158
151
  if (fileNodes.length === 0) {
159
152
  return { file, sources: [], levels: {}, totalDependents: 0 };
160
153
  }
@@ -172,7 +165,7 @@ export function impactAnalysisData(
172
165
  while (queue.length > 0) {
173
166
  const current = queue.shift()!;
174
167
  const level = levels.get(current)!;
175
- const dependents = findImportDependents(db, current) as RelatedNodeRow[];
168
+ const dependents = repo.findImportDependents(current) as RelatedNodeRow[];
176
169
  for (const dep of dependents) {
177
170
  if (!visited.has(dep.id) && (!noTests || !isTestFile(dep.file))) {
178
171
  visited.add(dep.id);
@@ -186,7 +179,7 @@ export function impactAnalysisData(
186
179
  for (const [id, level] of levels) {
187
180
  if (level === 0) continue;
188
181
  if (!byLevel[level]) byLevel[level] = [];
189
- const node = findNodeById(db, id) as NodeRow | undefined;
182
+ const node = repo.findNodeById(id) as NodeRow | undefined;
190
183
  if (node) byLevel[level].push({ file: node.file });
191
184
  }
192
185
 
@@ -213,12 +206,12 @@ export function fnImpactData(
213
206
  config?: any;
214
207
  } = {},
215
208
  ) {
216
- return withReadonlyDb(customDbPath, (db) => {
209
+ return withRepo(customDbPath, (repo) => {
217
210
  const { noTests, config } = resolveAnalysisOpts(opts);
218
211
  const maxDepth = opts.depth || config.analysis?.fnImpactDepth || 5;
219
212
  const hc = new Map();
220
213
 
221
- const nodes = findMatchingNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
214
+ const nodes = findMatchingNodes(repo, name, { noTests, file: opts.file, kind: opts.kind });
222
215
  if (nodes.length === 0) {
223
216
  return { name, results: [] };
224
217
  }
@@ -226,13 +219,13 @@ export function fnImpactData(
226
219
  const includeImplementors = opts.includeImplementors !== false;
227
220
 
228
221
  const results = nodes.map((node) => {
229
- const { levels, totalDependents } = bfsTransitiveCallers(db, node.id, {
222
+ const { levels, totalDependents } = bfsTransitiveCallers(repo, node.id, {
230
223
  noTests,
231
224
  maxDepth,
232
225
  includeImplementors,
233
226
  });
234
227
  return {
235
- ...normalizeSymbol(node, db, hc),
228
+ ...normalizeSymbol(node, repo, hc),
236
229
  levels,
237
230
  totalDependents,
238
231
  };