homegraph 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/README.md +48 -11
  2. package/dist/bin/homegraph.js +295 -72
  3. package/dist/bin/homegraph.js.map +1 -1
  4. package/dist/context/index.d.ts.map +1 -1
  5. package/dist/context/index.js +32 -11
  6. package/dist/context/index.js.map +1 -1
  7. package/dist/db/index.d.ts +143 -1
  8. package/dist/db/index.d.ts.map +1 -1
  9. package/dist/db/index.js +293 -16
  10. package/dist/db/index.js.map +1 -1
  11. package/dist/db/queries.d.ts +43 -4
  12. package/dist/db/queries.d.ts.map +1 -1
  13. package/dist/db/queries.js +138 -14
  14. package/dist/db/queries.js.map +1 -1
  15. package/dist/db/sqlite-adapter.d.ts +24 -17
  16. package/dist/db/sqlite-adapter.d.ts.map +1 -1
  17. package/dist/db/sqlite-adapter.js +292 -36
  18. package/dist/db/sqlite-adapter.js.map +1 -1
  19. package/dist/db/wal-valve.d.ts +15 -2
  20. package/dist/db/wal-valve.d.ts.map +1 -1
  21. package/dist/db/wal-valve.js +88 -6
  22. package/dist/db/wal-valve.js.map +1 -1
  23. package/dist/extraction/arkts-batch-worker.js +3 -2
  24. package/dist/extraction/arkts-batch-worker.js.map +1 -1
  25. package/dist/extraction/default-ignore.d.ts +39 -0
  26. package/dist/extraction/default-ignore.d.ts.map +1 -0
  27. package/dist/extraction/default-ignore.js +221 -0
  28. package/dist/extraction/default-ignore.js.map +1 -0
  29. package/dist/extraction/index.d.ts +9 -10
  30. package/dist/extraction/index.d.ts.map +1 -1
  31. package/dist/extraction/index.js +227 -323
  32. package/dist/extraction/index.js.map +1 -1
  33. package/dist/extraction/languages/arkts.d.ts +90 -6
  34. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  35. package/dist/extraction/languages/arkts.js +1579 -128
  36. package/dist/extraction/languages/arkts.js.map +1 -1
  37. package/dist/extraction/store-worker.d.ts +20 -0
  38. package/dist/extraction/store-worker.d.ts.map +1 -0
  39. package/dist/extraction/store-worker.js +102 -0
  40. package/dist/extraction/store-worker.js.map +1 -0
  41. package/dist/extraction/store-writer.d.ts +48 -0
  42. package/dist/extraction/store-writer.d.ts.map +1 -0
  43. package/dist/extraction/store-writer.js +167 -0
  44. package/dist/extraction/store-writer.js.map +1 -0
  45. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  46. package/dist/extraction/tree-sitter.js +47 -0
  47. package/dist/extraction/tree-sitter.js.map +1 -1
  48. package/dist/extraction/wasm-runtime-flags.d.ts +14 -4
  49. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  50. package/dist/extraction/wasm-runtime-flags.js +55 -7
  51. package/dist/extraction/wasm-runtime-flags.js.map +1 -1
  52. package/dist/index.d.ts +16 -5
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +135 -97
  55. package/dist/index.js.map +1 -1
  56. package/dist/mcp/query-worker.d.ts +2 -2
  57. package/dist/mcp/query-worker.js +2 -2
  58. package/dist/mcp/tools.d.ts.map +1 -1
  59. package/dist/mcp/tools.js +42 -26
  60. package/dist/mcp/tools.js.map +1 -1
  61. package/dist/resolution/callback-synthesizer.d.ts +52 -16
  62. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  63. package/dist/resolution/callback-synthesizer.js +234 -180
  64. package/dist/resolution/callback-synthesizer.js.map +1 -1
  65. package/dist/resolution/cooperative-yield.d.ts +10 -2
  66. package/dist/resolution/cooperative-yield.d.ts.map +1 -1
  67. package/dist/resolution/cooperative-yield.js +6 -4
  68. package/dist/resolution/cooperative-yield.js.map +1 -1
  69. package/dist/resolution/frameworks/go.d.ts.map +1 -1
  70. package/dist/resolution/frameworks/go.js +29 -6
  71. package/dist/resolution/frameworks/go.js.map +1 -1
  72. package/dist/resolution/import-resolver.d.ts +2 -3
  73. package/dist/resolution/import-resolver.d.ts.map +1 -1
  74. package/dist/resolution/import-resolver.js +166 -7
  75. package/dist/resolution/import-resolver.js.map +1 -1
  76. package/dist/resolution/index.d.ts +87 -2
  77. package/dist/resolution/index.d.ts.map +1 -1
  78. package/dist/resolution/index.js +739 -131
  79. package/dist/resolution/index.js.map +1 -1
  80. package/dist/resolution/memory-budget.d.ts +48 -0
  81. package/dist/resolution/memory-budget.d.ts.map +1 -0
  82. package/dist/resolution/memory-budget.js +162 -0
  83. package/dist/resolution/memory-budget.js.map +1 -0
  84. package/dist/resolution/name-matcher.d.ts +12 -1
  85. package/dist/resolution/name-matcher.d.ts.map +1 -1
  86. package/dist/resolution/name-matcher.js +439 -97
  87. package/dist/resolution/name-matcher.js.map +1 -1
  88. package/dist/resolution/resolver-pool.d.ts +106 -0
  89. package/dist/resolution/resolver-pool.d.ts.map +1 -0
  90. package/dist/resolution/resolver-pool.js +344 -0
  91. package/dist/resolution/resolver-pool.js.map +1 -0
  92. package/dist/resolution/resolver-worker.d.ts +17 -0
  93. package/dist/resolution/resolver-worker.d.ts.map +1 -0
  94. package/dist/resolution/resolver-worker.js +141 -0
  95. package/dist/resolution/resolver-worker.js.map +1 -0
  96. package/dist/resolution/types.d.ts +3 -0
  97. package/dist/resolution/types.d.ts.map +1 -1
  98. package/dist/spec/build/diff-parser.d.ts +55 -0
  99. package/dist/spec/build/diff-parser.d.ts.map +1 -0
  100. package/dist/spec/build/diff-parser.js +205 -0
  101. package/dist/spec/build/diff-parser.js.map +1 -0
  102. package/dist/spec/build/git-scanner.d.ts +93 -0
  103. package/dist/spec/build/git-scanner.d.ts.map +1 -0
  104. package/dist/spec/build/git-scanner.js +254 -0
  105. package/dist/spec/build/git-scanner.js.map +1 -0
  106. package/dist/spec/build/pipeline.d.ts +54 -0
  107. package/dist/spec/build/pipeline.d.ts.map +1 -0
  108. package/dist/spec/build/pipeline.js +170 -0
  109. package/dist/spec/build/pipeline.js.map +1 -0
  110. package/dist/spec/build/scan.d.ts +48 -0
  111. package/dist/spec/build/scan.d.ts.map +1 -0
  112. package/dist/spec/build/scan.js +89 -0
  113. package/dist/spec/build/scan.js.map +1 -0
  114. package/dist/spec/build/scope-resolver.d.ts +50 -0
  115. package/dist/spec/build/scope-resolver.d.ts.map +1 -0
  116. package/dist/spec/build/scope-resolver.js +105 -0
  117. package/dist/spec/build/scope-resolver.js.map +1 -0
  118. package/dist/spec/build/spec-extractor.d.ts +74 -0
  119. package/dist/spec/build/spec-extractor.d.ts.map +1 -0
  120. package/dist/spec/build/spec-extractor.js +344 -0
  121. package/dist/spec/build/spec-extractor.js.map +1 -0
  122. package/dist/spec/config.d.ts +35 -1
  123. package/dist/spec/config.d.ts.map +1 -1
  124. package/dist/spec/config.js +30 -5
  125. package/dist/spec/config.js.map +1 -1
  126. package/dist/spec/db/fts.d.ts.map +1 -1
  127. package/dist/spec/db/fts.js +19 -50
  128. package/dist/spec/db/fts.js.map +1 -1
  129. package/dist/spec/db/index.d.ts +5 -3
  130. package/dist/spec/db/index.d.ts.map +1 -1
  131. package/dist/spec/db/index.js +16 -1
  132. package/dist/spec/db/index.js.map +1 -1
  133. package/dist/spec/db/persist.d.ts +45 -0
  134. package/dist/spec/db/persist.d.ts.map +1 -0
  135. package/dist/spec/db/persist.js +66 -0
  136. package/dist/spec/db/persist.js.map +1 -0
  137. package/dist/spec/db/relations.d.ts +80 -1
  138. package/dist/spec/db/relations.d.ts.map +1 -1
  139. package/dist/spec/db/relations.js +175 -4
  140. package/dist/spec/db/relations.js.map +1 -1
  141. package/dist/spec/db/schema.d.ts +4 -0
  142. package/dist/spec/db/schema.d.ts.map +1 -1
  143. package/dist/spec/db/schema.js +4 -0
  144. package/dist/spec/db/schema.js.map +1 -1
  145. package/dist/spec/db/schema.sql +5 -1
  146. package/dist/spec/db/spec-node.d.ts +5 -0
  147. package/dist/spec/db/spec-node.d.ts.map +1 -1
  148. package/dist/spec/db/spec-node.js +11 -5
  149. package/dist/spec/db/spec-node.js.map +1 -1
  150. package/dist/spec/db/sql-utils.d.ts +11 -0
  151. package/dist/spec/db/sql-utils.d.ts.map +1 -0
  152. package/dist/spec/db/sql-utils.js +19 -0
  153. package/dist/spec/db/sql-utils.js.map +1 -0
  154. package/dist/spec/evolve/cluster-context.d.ts +30 -0
  155. package/dist/spec/evolve/cluster-context.d.ts.map +1 -0
  156. package/dist/spec/evolve/cluster-context.js +78 -0
  157. package/dist/spec/evolve/cluster-context.js.map +1 -0
  158. package/dist/spec/evolve/commit-spec-analyzer.d.ts +51 -0
  159. package/dist/spec/evolve/commit-spec-analyzer.d.ts.map +1 -0
  160. package/dist/spec/evolve/commit-spec-analyzer.js +88 -0
  161. package/dist/spec/evolve/commit-spec-analyzer.js.map +1 -0
  162. package/dist/spec/evolve/commit-spec-persister.d.ts +53 -0
  163. package/dist/spec/evolve/commit-spec-persister.d.ts.map +1 -0
  164. package/dist/spec/evolve/commit-spec-persister.js +93 -0
  165. package/dist/spec/evolve/commit-spec-persister.js.map +1 -0
  166. package/dist/spec/evolve/impact-locator.d.ts +23 -7
  167. package/dist/spec/evolve/impact-locator.d.ts.map +1 -1
  168. package/dist/spec/evolve/impact-locator.js +58 -14
  169. package/dist/spec/evolve/impact-locator.js.map +1 -1
  170. package/dist/spec/evolve/pipeline.d.ts +62 -25
  171. package/dist/spec/evolve/pipeline.d.ts.map +1 -1
  172. package/dist/spec/evolve/pipeline.js +436 -457
  173. package/dist/spec/evolve/pipeline.js.map +1 -1
  174. package/dist/spec/evolve/spec-rewriter.d.ts +13 -13
  175. package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
  176. package/dist/spec/evolve/spec-rewriter.js +45 -45
  177. package/dist/spec/evolve/spec-rewriter.js.map +1 -1
  178. package/dist/spec/git/commits.d.ts +85 -0
  179. package/dist/spec/git/commits.d.ts.map +1 -0
  180. package/dist/spec/git/commits.js +218 -0
  181. package/dist/spec/git/commits.js.map +1 -0
  182. package/dist/spec/git/exec.d.ts +13 -0
  183. package/dist/spec/git/exec.d.ts.map +1 -0
  184. package/dist/spec/git/exec.js +19 -0
  185. package/dist/spec/git/exec.js.map +1 -0
  186. package/dist/spec/git/index.d.ts +9 -0
  187. package/dist/spec/git/index.d.ts.map +1 -0
  188. package/dist/spec/git/index.js +22 -0
  189. package/dist/spec/git/index.js.map +1 -0
  190. package/dist/spec/git-utils.d.ts +8 -0
  191. package/dist/spec/git-utils.d.ts.map +1 -0
  192. package/dist/spec/git-utils.js +14 -0
  193. package/dist/spec/git-utils.js.map +1 -0
  194. package/dist/spec/graph/index.d.ts +8 -0
  195. package/dist/spec/graph/index.d.ts.map +1 -0
  196. package/dist/spec/graph/index.js +17 -0
  197. package/dist/spec/graph/index.js.map +1 -0
  198. package/dist/spec/graph/queries.d.ts +6 -35
  199. package/dist/spec/graph/queries.d.ts.map +1 -1
  200. package/dist/spec/graph/queries.js +71 -248
  201. package/dist/spec/graph/queries.js.map +1 -1
  202. package/dist/spec/llm/agent-client.d.ts +59 -0
  203. package/dist/spec/llm/agent-client.d.ts.map +1 -0
  204. package/dist/spec/llm/agent-client.js +213 -0
  205. package/dist/spec/llm/agent-client.js.map +1 -0
  206. package/dist/spec/llm/agents/claude-code.d.ts +32 -0
  207. package/dist/spec/llm/agents/claude-code.d.ts.map +1 -0
  208. package/dist/spec/llm/agents/claude-code.js +125 -0
  209. package/dist/spec/llm/agents/claude-code.js.map +1 -0
  210. package/dist/spec/llm/agents/codex.d.ts +32 -0
  211. package/dist/spec/llm/agents/codex.d.ts.map +1 -0
  212. package/dist/spec/llm/agents/codex.js +145 -0
  213. package/dist/spec/llm/agents/codex.js.map +1 -0
  214. package/dist/spec/llm/agents/detect-utils.d.ts +28 -0
  215. package/dist/spec/llm/agents/detect-utils.d.ts.map +1 -0
  216. package/dist/spec/llm/agents/detect-utils.js +91 -0
  217. package/dist/spec/llm/agents/detect-utils.js.map +1 -0
  218. package/dist/spec/llm/agents/deveco-code.d.ts +33 -0
  219. package/dist/spec/llm/agents/deveco-code.d.ts.map +1 -0
  220. package/dist/spec/llm/agents/deveco-code.js +165 -0
  221. package/dist/spec/llm/agents/deveco-code.js.map +1 -0
  222. package/dist/spec/llm/agents/index.d.ts +31 -0
  223. package/dist/spec/llm/agents/index.d.ts.map +1 -0
  224. package/dist/spec/llm/agents/index.js +65 -0
  225. package/dist/spec/llm/agents/index.js.map +1 -0
  226. package/dist/spec/llm/agents/types.d.ts +76 -0
  227. package/dist/spec/llm/agents/types.d.ts.map +1 -0
  228. package/dist/spec/llm/agents/types.js +15 -0
  229. package/dist/spec/llm/agents/types.js.map +1 -0
  230. package/dist/spec/llm/client.d.ts +31 -11
  231. package/dist/spec/llm/client.d.ts.map +1 -1
  232. package/dist/spec/llm/client.js +143 -81
  233. package/dist/spec/llm/client.js.map +1 -1
  234. package/dist/spec/llm/factory.d.ts +41 -0
  235. package/dist/spec/llm/factory.d.ts.map +1 -0
  236. package/dist/spec/llm/factory.js +80 -0
  237. package/dist/spec/llm/factory.js.map +1 -0
  238. package/dist/spec/llm/prompts.d.ts +24 -8
  239. package/dist/spec/llm/prompts.d.ts.map +1 -1
  240. package/dist/spec/llm/prompts.js +100 -50
  241. package/dist/spec/llm/prompts.js.map +1 -1
  242. package/dist/spec/llm/retry.d.ts +53 -0
  243. package/dist/spec/llm/retry.d.ts.map +1 -0
  244. package/dist/spec/llm/retry.js +153 -0
  245. package/dist/spec/llm/retry.js.map +1 -0
  246. package/dist/spec/mine/clusterer.d.ts +63 -0
  247. package/dist/spec/mine/clusterer.d.ts.map +1 -0
  248. package/dist/spec/mine/clusterer.js +904 -0
  249. package/dist/spec/mine/clusterer.js.map +1 -0
  250. package/dist/spec/mine/clustering/features.d.ts +38 -0
  251. package/dist/spec/mine/clustering/features.d.ts.map +1 -0
  252. package/dist/spec/mine/clustering/features.js +108 -0
  253. package/dist/spec/mine/clustering/features.js.map +1 -0
  254. package/dist/spec/mine/clustering/index.d.ts +62 -0
  255. package/dist/spec/mine/clustering/index.d.ts.map +1 -0
  256. package/dist/spec/mine/clustering/index.js +259 -0
  257. package/dist/spec/mine/clustering/index.js.map +1 -0
  258. package/dist/spec/mine/clustering/leiden.d.ts +61 -0
  259. package/dist/spec/mine/clustering/leiden.d.ts.map +1 -0
  260. package/dist/spec/mine/clustering/leiden.js +489 -0
  261. package/dist/spec/mine/clustering/leiden.js.map +1 -0
  262. package/dist/spec/mine/clustering/text-similarity.d.ts +22 -0
  263. package/dist/spec/mine/clustering/text-similarity.d.ts.map +1 -0
  264. package/dist/spec/mine/clustering/text-similarity.js +98 -0
  265. package/dist/spec/mine/clustering/text-similarity.js.map +1 -0
  266. package/dist/spec/mine/generator.d.ts +54 -0
  267. package/dist/spec/mine/generator.d.ts.map +1 -0
  268. package/dist/spec/mine/generator.js +267 -0
  269. package/dist/spec/mine/generator.js.map +1 -0
  270. package/dist/spec/mine/persist.d.ts +37 -0
  271. package/dist/spec/mine/persist.d.ts.map +1 -0
  272. package/dist/spec/mine/persist.js +170 -0
  273. package/dist/spec/mine/persist.js.map +1 -0
  274. package/dist/spec/mine/pipeline.d.ts +41 -0
  275. package/dist/spec/mine/pipeline.d.ts.map +1 -0
  276. package/dist/spec/mine/pipeline.js +236 -0
  277. package/dist/spec/mine/pipeline.js.map +1 -0
  278. package/dist/spec/mine/progress-handler.d.ts +22 -0
  279. package/dist/spec/mine/progress-handler.d.ts.map +1 -0
  280. package/dist/spec/mine/progress-handler.js +108 -0
  281. package/dist/spec/mine/progress-handler.js.map +1 -0
  282. package/dist/spec/mine/progress.d.ts +23 -0
  283. package/dist/spec/mine/progress.d.ts.map +1 -0
  284. package/dist/spec/mine/progress.js +12 -0
  285. package/dist/spec/mine/progress.js.map +1 -0
  286. package/dist/spec/mine/scanner.d.ts +59 -0
  287. package/dist/spec/mine/scanner.d.ts.map +1 -0
  288. package/dist/spec/mine/scanner.js +383 -0
  289. package/dist/spec/mine/scanner.js.map +1 -0
  290. package/dist/spec/types.d.ts +32 -0
  291. package/dist/spec/types.d.ts.map +1 -1
  292. package/dist/spec/ui/index.d.ts +9 -0
  293. package/dist/spec/ui/index.d.ts.map +1 -0
  294. package/dist/spec/ui/index.js +16 -0
  295. package/dist/spec/ui/index.js.map +1 -0
  296. package/dist/spec/ui/progress-handler.d.ts +33 -0
  297. package/dist/spec/ui/progress-handler.d.ts.map +1 -0
  298. package/dist/spec/ui/progress-handler.js +132 -0
  299. package/dist/spec/ui/progress-handler.js.map +1 -0
  300. package/dist/spec/ui/progress.d.ts +24 -0
  301. package/dist/spec/ui/progress.d.ts.map +1 -0
  302. package/dist/spec/ui/progress.js +13 -0
  303. package/dist/spec/ui/progress.js.map +1 -0
  304. package/dist/spec/utils/fs.d.ts +45 -0
  305. package/dist/spec/utils/fs.d.ts.map +1 -0
  306. package/dist/spec/utils/fs.js +163 -0
  307. package/dist/spec/utils/fs.js.map +1 -0
  308. package/dist/spec/utils/index.d.ts +12 -0
  309. package/dist/spec/utils/index.d.ts.map +1 -0
  310. package/dist/spec/utils/index.js +26 -0
  311. package/dist/spec/utils/index.js.map +1 -0
  312. package/dist/spec/utils/meta.d.ts +32 -0
  313. package/dist/spec/utils/meta.d.ts.map +1 -0
  314. package/dist/spec/utils/meta.js +122 -0
  315. package/dist/spec/utils/meta.js.map +1 -0
  316. package/dist/spec/utils/truncate.d.ts +86 -0
  317. package/dist/spec/utils/truncate.d.ts.map +1 -0
  318. package/dist/spec/utils/truncate.js +172 -0
  319. package/dist/spec/utils/truncate.js.map +1 -0
  320. package/dist/spec/utils.d.ts +4 -16
  321. package/dist/spec/utils.d.ts.map +1 -1
  322. package/dist/spec/utils.js +6 -58
  323. package/dist/spec/utils.js.map +1 -1
  324. package/dist/sync/watcher.d.ts +25 -1
  325. package/dist/sync/watcher.d.ts.map +1 -1
  326. package/dist/sync/watcher.js +76 -3
  327. package/dist/sync/watcher.js.map +1 -1
  328. package/dist/telemetry/index.js +3 -3
  329. package/dist/telemetry/index.js.map +1 -1
  330. package/dist/types.d.ts +3 -0
  331. package/dist/types.d.ts.map +1 -1
  332. package/package.json +7 -3
@@ -2,39 +2,152 @@
2
2
  /**
3
3
  * SQLite Adapter
4
4
  *
5
- * Thin wrapper over Node's built-in `node:sqlite` (`DatabaseSync`), exposed
6
- * through a small better-sqlite3-shaped interface so the rest of the codebase
7
- * is storage-agnostic.
5
+ * Three-tier selection (first success wins):
6
+ * 1. `node:sqlite` (`DatabaseSync`) real SQLite + WAL + FTS5, Node ≥22.5
7
+ * 2. `better-sqlite3` — native addon (optionalDependency)
8
+ * 3. `node-sqlite3-wasm` — last-resort fallback (no WAL; slower / lock-prone)
8
9
  *
9
- * HomeGraph ships with a bundled Node runtime, so `node:sqlite` (real SQLite,
10
- * with WAL + FTS5) is always available — there is no native build step and no
11
- * wasm fallback. When run from source instead, it requires Node >= 22.5.
10
+ * Library hosts on Node 20–22.4 land on (2) or (3). Node ≥22.5 prefers (1) and
11
+ * skips the native build. Override with `HOMEGRAPH_SQLITE_BACKEND=node-sqlite|native|wasm`.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.WASM_FALLBACK_FIX_RECIPE = void 0;
15
+ exports.buildWasmFallbackBanner = buildWasmFallbackBanner;
16
+ exports.isNodeSqliteAvailable = isNodeSqliteAvailable;
17
+ exports.isNativeSqliteAvailable = isNativeSqliteAvailable;
14
18
  exports.createDatabase = createDatabase;
19
+ /**
20
+ * One-line recovery hint when WASM is active (no WAL — slower / lock-prone).
21
+ */
22
+ exports.WASM_FALLBACK_FIX_RECIPE = 'upgrade to Node.js 22.5+ (uses built-in node:sqlite), or ' +
23
+ '`xcode-select --install` (macOS) / `apt install build-essential` (Debian/Ubuntu) ' +
24
+ 'then `npm rebuild better-sqlite3`.';
25
+ /**
26
+ * Banner shown to stderr when falling back to WASM.
27
+ */
28
+ function buildWasmFallbackBanner(priorErrors) {
29
+ const sep = '─'.repeat(72);
30
+ const lines = [
31
+ sep,
32
+ '[HomeGraph] WASM SQLite fallback active (node:sqlite + better-sqlite3 unavailable)',
33
+ sep,
34
+ 'Indexing and sync will be 5-10x slower than a WAL backend, and concurrent',
35
+ 'reads can hit "database is locked". Prefer Node 22.5+ or better-sqlite3.',
36
+ '',
37
+ 'Fix — use built-in SQLite (best when you can):',
38
+ ' Use Node.js 22.5 or newer, then restart homegraph',
39
+ '',
40
+ 'Fix — native better-sqlite3:',
41
+ ' macOS: xcode-select --install && npm rebuild better-sqlite3',
42
+ ' Linux: sudo apt install build-essential python3 make && npm rebuild better-sqlite3',
43
+ ' Any: npm install better-sqlite3 --save',
44
+ '',
45
+ 'Verify after fix: `homegraph status` should show Backend: node-sqlite or native.',
46
+ ];
47
+ if (priorErrors) {
48
+ lines.push('', `Prior load errors: ${priorErrors}`);
49
+ }
50
+ lines.push(sep);
51
+ return lines.join('\n');
52
+ }
53
+ function translateNamedParams(sql) {
54
+ const paramOrder = [];
55
+ const rewritten = sql.replace(/@(\w+)/g, (_match, name) => {
56
+ paramOrder.push(name);
57
+ return '?';
58
+ });
59
+ if (paramOrder.length === 0) {
60
+ return { sql, paramOrder: null };
61
+ }
62
+ return { sql: rewritten, paramOrder };
63
+ }
64
+ function resolveParams(params, paramOrder) {
65
+ if (params.length === 0)
66
+ return undefined;
67
+ if (paramOrder &&
68
+ params.length === 1 &&
69
+ params[0] !== null &&
70
+ typeof params[0] === 'object' &&
71
+ !Array.isArray(params[0]) &&
72
+ !(params[0] instanceof Buffer) &&
73
+ !(params[0] instanceof Uint8Array)) {
74
+ const obj = params[0];
75
+ return paramOrder.map(name => obj[name]);
76
+ }
77
+ if (params.length === 1)
78
+ return params[0];
79
+ return params;
80
+ }
81
+ function isBusyError(error) {
82
+ const msg = error instanceof Error ? error.message : String(error);
83
+ return /SQLITE_BUSY|database is locked/i.test(msg);
84
+ }
85
+ function sleepSync(ms) {
86
+ try {
87
+ const sab = new SharedArrayBuffer(4);
88
+ const ia = new Int32Array(sab);
89
+ Atomics.wait(ia, 0, 0, ms);
90
+ }
91
+ catch {
92
+ const end = Date.now() + ms;
93
+ while (Date.now() < end) {
94
+ /* busy-wait */
95
+ }
96
+ }
97
+ }
98
+ function withBusyRetry(fn, attempts = 10, baseDelayMs = 50) {
99
+ let last;
100
+ for (let i = 0; i < attempts; i++) {
101
+ try {
102
+ return fn();
103
+ }
104
+ catch (error) {
105
+ last = error;
106
+ if (!isBusyError(error) || i === attempts - 1)
107
+ throw error;
108
+ sleepSync(baseDelayMs * (i + 1));
109
+ }
110
+ }
111
+ throw last;
112
+ }
113
+ /** True when `require('node:sqlite').DatabaseSync` succeeds. */
114
+ function isNodeSqliteAvailable() {
115
+ try {
116
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
117
+ const mod = require('node:sqlite');
118
+ return typeof mod?.DatabaseSync === 'function';
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
124
+ /** True when `require('better-sqlite3')` succeeds. */
125
+ function isNativeSqliteAvailable() {
126
+ try {
127
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
128
+ require('better-sqlite3');
129
+ return true;
130
+ }
131
+ catch {
132
+ return false;
133
+ }
134
+ }
15
135
  /**
16
136
  * Wraps Node's built-in `node:sqlite` (`DatabaseSync`) to match the
17
137
  * better-sqlite3 interface the rest of the code expects.
18
- *
19
- * node:sqlite is real SQLite compiled into Node, so it supports WAL, FTS5,
20
- * mmap, and `@named` params natively — the only shims needed are the
21
- * better-sqlite3 conveniences node:sqlite omits: a `.pragma()` helper, a
22
- * `.transaction()` helper, and `open` (node:sqlite exposes `isOpen`).
23
138
  */
24
139
  class NodeSqliteAdapter {
25
140
  _db;
26
- constructor(dbPath) {
141
+ _txDepth = 0;
142
+ constructor(dbPath, opts) {
27
143
  // eslint-disable-next-line @typescript-eslint/no-require-imports
28
144
  const { DatabaseSync } = require('node:sqlite');
29
- this._db = new DatabaseSync(dbPath);
145
+ this._db = opts?.readOnly ? new DatabaseSync(dbPath, { readOnly: true }) : new DatabaseSync(dbPath);
30
146
  }
31
147
  get open() {
32
148
  return this._db.isOpen;
33
149
  }
34
150
  prepare(sql) {
35
- // node:sqlite matches better-sqlite3's calling convention (variadic
36
- // positional args, or a single object for @named params), so params forward
37
- // through unchanged.
38
151
  const stmt = this._db.prepare(sql);
39
152
  return {
40
153
  run(...params) {
@@ -60,14 +173,10 @@ class NodeSqliteAdapter {
60
173
  }
61
174
  pragma(str, options) {
62
175
  const trimmed = str.trim();
63
- // Write pragma ("key = value"): node:sqlite is real SQLite, so every pragma
64
- // (WAL, mmap, synchronous, …) applies as-is.
65
176
  if (trimmed.includes('=')) {
66
177
  this._db.exec(`PRAGMA ${trimmed}`);
67
178
  return;
68
179
  }
69
- // Read pragma. Default: the row object (e.g. { journal_mode: 'wal' }).
70
- // `{ simple: true }` returns just the single column value, like better-sqlite3.
71
180
  const row = this._db.prepare(`PRAGMA ${trimmed}`).get();
72
181
  if (options?.simple) {
73
182
  return row && typeof row === 'object' ? Object.values(row)[0] : row;
@@ -76,42 +185,189 @@ class NodeSqliteAdapter {
76
185
  }
77
186
  transaction(fn) {
78
187
  return (...args) => {
188
+ if (this._txDepth > 0) {
189
+ this._txDepth++;
190
+ try {
191
+ return fn(...args);
192
+ }
193
+ finally {
194
+ this._txDepth--;
195
+ }
196
+ }
79
197
  this._db.exec('BEGIN');
198
+ this._txDepth = 1;
80
199
  try {
81
200
  const result = fn(...args);
82
201
  this._db.exec('COMMIT');
202
+ this._txDepth = 0;
83
203
  return result;
84
204
  }
85
205
  catch (error) {
86
206
  this._db.exec('ROLLBACK');
207
+ this._txDepth = 0;
87
208
  throw error;
88
209
  }
89
210
  };
90
211
  }
91
212
  close() {
92
- // node:sqlite's DatabaseSync.close() throws if already closed; make it
93
- // idempotent to match better-sqlite3 (callers may close more than once).
94
213
  if (this._db.isOpen)
95
214
  this._db.close();
96
215
  }
97
216
  }
98
- /**
99
- * Create a database connection backed by `node:sqlite`.
100
- *
101
- * Returns the active backend alongside the db so each `DatabaseConnection` can
102
- * report it per-instance — MCP can open multiple project DBs in one process, so
103
- * a process-global would race.
104
- */
105
- function createDatabase(dbPath) {
217
+ class WasmDatabaseAdapter {
218
+ _db;
219
+ _openStmts = new Set();
220
+ _txDepth = 0;
221
+ constructor(dbPath, opts) {
222
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
223
+ const { Database } = require('node-sqlite3-wasm');
224
+ this._db = opts?.readOnly ? new Database(dbPath, 'readonly') : new Database(dbPath);
225
+ }
226
+ get open() {
227
+ return this._db.isOpen;
228
+ }
229
+ prepare(sql) {
230
+ const { sql: rewrittenSql, paramOrder } = translateNamedParams(sql);
231
+ const stmt = this._db.prepare(rewrittenSql);
232
+ this._openStmts.add(stmt);
233
+ return {
234
+ run(...params) {
235
+ const resolved = resolveParams(params, paramOrder);
236
+ const result = resolved !== undefined ? stmt.run(resolved) : stmt.run();
237
+ return {
238
+ changes: result?.changes ?? 0,
239
+ lastInsertRowid: result?.lastInsertRowid ?? 0,
240
+ };
241
+ },
242
+ get(...params) {
243
+ const resolved = resolveParams(params, paramOrder);
244
+ return withBusyRetry(() => resolved !== undefined ? stmt.get(resolved) : stmt.get());
245
+ },
246
+ all(...params) {
247
+ const resolved = resolveParams(params, paramOrder);
248
+ return withBusyRetry(() => resolved !== undefined ? stmt.all(resolved) : stmt.all());
249
+ },
250
+ iterate(...params) {
251
+ const resolved = resolveParams(params, paramOrder);
252
+ if (typeof stmt.iterate === 'function') {
253
+ return withBusyRetry(() => resolved !== undefined ? stmt.iterate(resolved) : stmt.iterate());
254
+ }
255
+ const rows = withBusyRetry(() => resolved !== undefined ? stmt.all(resolved) : stmt.all());
256
+ return rows[Symbol.iterator]();
257
+ },
258
+ };
259
+ }
260
+ exec(sql) {
261
+ this._db.exec(sql);
262
+ }
263
+ pragma(str, options) {
264
+ const trimmed = str.trim();
265
+ if (trimmed.includes('=')) {
266
+ const eqIdx = trimmed.indexOf('=');
267
+ const key = trimmed.substring(0, eqIdx).trim();
268
+ const value = trimmed.substring(eqIdx + 1).trim();
269
+ if (key === 'journal_mode' && value.toUpperCase() === 'WAL') {
270
+ this._db.exec('PRAGMA journal_mode = DELETE');
271
+ return;
272
+ }
273
+ if (key === 'mmap_size')
274
+ return;
275
+ if (key === 'synchronous' && value.toUpperCase() === 'NORMAL') {
276
+ this._db.exec('PRAGMA synchronous = FULL');
277
+ return;
278
+ }
279
+ this._db.exec(`PRAGMA ${key} = ${value}`);
280
+ return;
281
+ }
282
+ const readStmt = this._db.prepare(`PRAGMA ${trimmed}`);
283
+ const result = readStmt.get();
284
+ readStmt.finalize();
285
+ if (options?.simple) {
286
+ return result && typeof result === 'object' ? Object.values(result)[0] : result;
287
+ }
288
+ return result;
289
+ }
290
+ transaction(fn) {
291
+ return (...args) => {
292
+ if (this._txDepth > 0) {
293
+ this._txDepth++;
294
+ try {
295
+ return fn(...args);
296
+ }
297
+ finally {
298
+ this._txDepth--;
299
+ }
300
+ }
301
+ this._db.exec('BEGIN');
302
+ this._txDepth = 1;
303
+ try {
304
+ const result = fn(...args);
305
+ this._db.exec('COMMIT');
306
+ this._txDepth = 0;
307
+ return result;
308
+ }
309
+ catch (error) {
310
+ this._db.exec('ROLLBACK');
311
+ this._txDepth = 0;
312
+ throw error;
313
+ }
314
+ };
315
+ }
316
+ close() {
317
+ for (const stmt of this._openStmts) {
318
+ try {
319
+ stmt.finalize();
320
+ }
321
+ catch {
322
+ /* already finalized */
323
+ }
324
+ }
325
+ this._openStmts.clear();
326
+ this._db.close();
327
+ }
328
+ }
329
+ function resolveBackendOrder() {
330
+ const raw = process.env.HOMEGRAPH_SQLITE_BACKEND?.trim().toLowerCase();
331
+ if (raw === 'node-sqlite' || raw === 'native' || raw === 'wasm') {
332
+ return [raw];
333
+ }
334
+ return ['node-sqlite', 'native', 'wasm'];
335
+ }
336
+ function tryOpenBackend(backend, dbPath, opts) {
106
337
  try {
107
- return { db: new NodeSqliteAdapter(dbPath), backend: 'node-sqlite' };
338
+ if (backend === 'node-sqlite') {
339
+ return { db: new NodeSqliteAdapter(dbPath, opts), backend: 'node-sqlite' };
340
+ }
341
+ if (backend === 'native') {
342
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
343
+ const Database = require('better-sqlite3');
344
+ const db = opts?.readOnly ? new Database(dbPath, { readonly: true }) : new Database(dbPath);
345
+ return { db: db, backend: 'native' };
346
+ }
347
+ return { db: new WasmDatabaseAdapter(dbPath, opts), backend: 'wasm' };
108
348
  }
109
349
  catch (error) {
110
- const msg = error instanceof Error ? error.message : String(error);
111
- throw new Error('Failed to open SQLite via the built-in node:sqlite module.\n' +
112
- 'HomeGraph requires node:sqlite (Node.js 22.5+). Install the self-contained\n' +
113
- 'HomeGraph release (it bundles a compatible Node), or run on Node 22.5+.\n' +
114
- `Underlying error: ${msg}`);
350
+ return { error: error instanceof Error ? error.message : String(error) };
351
+ }
352
+ }
353
+ /**
354
+ * Create a database connection.
355
+ * Order: node:sqlite → better-sqlite3 → node-sqlite3-wasm (unless overridden).
356
+ */
357
+ function createDatabase(dbPath, opts) {
358
+ const order = resolveBackendOrder();
359
+ const errors = [];
360
+ for (const backend of order) {
361
+ const result = tryOpenBackend(backend, dbPath, opts);
362
+ if ('db' in result) {
363
+ if (result.backend === 'wasm') {
364
+ console.warn(buildWasmFallbackBanner(errors.join(' | ') || undefined));
365
+ }
366
+ return result;
367
+ }
368
+ errors.push(`${backend}: ${result.error}`);
115
369
  }
370
+ throw new Error(`Failed to load any SQLite backend (tried ${order.join(' → ')}).\n` +
371
+ errors.map(e => ` ${e}`).join('\n'));
116
372
  }
117
373
  //# sourceMappingURL=sqlite-adapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-adapter.js","sourceRoot":"","sources":["../../src/db/sqlite-adapter.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AA8HH,wCAYC;AA5GD;;;;;;;;GAQG;AACH,MAAM,iBAAiB;IACb,GAAG,CAAM;IAEjB,YAAY,MAAc;QACxB,iEAAiE;QACjE,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;QAChD,IAAI,CAAC,GAAG,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;IACzB,CAAC;IAED,OAAO,CAAC,GAAW;QACjB,oEAAoE;QACpE,4EAA4E;QAC5E,qBAAqB;QACrB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,OAAO;YACL,GAAG,CAAC,GAAG,MAAa;gBAClB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;gBAC9B,OAAO;oBACL,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC;oBAChC,eAAe,EAAE,CAAC,EAAE,eAAe,IAAI,CAAC;iBACzC,CAAC;YACJ,CAAC;YACD,GAAG,CAAC,GAAG,MAAa;gBAClB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;YAC7B,CAAC;YACD,GAAG,CAAC,GAAG,MAAa;gBAClB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;YAC7B,CAAC;YACD,OAAO,CAAC,GAAG,MAAa;gBACtB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC;YACjC,CAAC;SACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,GAAW;QACd,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,CAAC,GAAW,EAAE,OAA8B;QAChD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3B,4EAA4E;QAC5E,6CAA6C;QAC7C,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QACD,uEAAuE;QACvE,gFAAgF;QAChF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;QACxD,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;YACpB,OAAO,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACtE,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,WAAW,CAAI,EAAyB;QACtC,OAAO,CAAC,GAAG,IAAW,EAAE,EAAE;YACxB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;gBAC3B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACxB,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBAC1B,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAED,KAAK;QACH,uEAAuE;QACvE,yEAAyE;QACzE,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;IACxC,CAAC;CACF;AAED;;;;;;GAMG;AACH,SAAgB,cAAc,CAAC,MAAc;IAC3C,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IACvE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnE,MAAM,IAAI,KAAK,CACb,8DAA8D;YAC9D,8EAA8E;YAC9E,2EAA2E;YAC3E,qBAAqB,GAAG,EAAE,CAC3B,CAAC;IACJ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"sqlite-adapter.js","sourceRoot":"","sources":["../../src/db/sqlite-adapter.ts"],"names":[],"mappings":"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"}
@@ -48,11 +48,10 @@ import type { DatabaseConnection } from './index';
48
48
  * Resolve the valve's soft threshold from the `HOMEGRAPH_WAL_VALVE_MB`
49
49
  * override; non-numeric / non-positive values fall back to the default.
50
50
  */
51
- export declare function resolveWalValveMb(envVal: string | undefined): number;
51
+ export declare function resolveWalValveMb(envVal: string | undefined, dbSizeBytes?: number): number;
52
52
  export declare class WalCheckpointValve {
53
53
  private readonly db;
54
54
  private readonly intervalMs;
55
- private readonly log;
56
55
  private timer;
57
56
  private inflight;
58
57
  /** Writer pause in progress (hard cap breached): passes loop until a full backfill. */
@@ -65,7 +64,21 @@ export declare class WalCheckpointValve {
65
64
  private sizeAtLastFullBackfill;
66
65
  private readonly softBytes;
67
66
  private readonly hardBytes;
67
+ private readonly fileCapBytes;
68
+ /**
69
+ * Futility latch: consecutive backfill give-ups (a reader pinning the WAL)
70
+ * disable further writer pauses for a cooldown, so a pinned phase degrades
71
+ * to the pre-valve behavior (unbounded WAL, folded when the pinner exits)
72
+ * instead of burning a 20-pass checkpoint attempt — each pass a worker
73
+ * thread + fresh connection — at EVERY over-cap boundary. That churn is
74
+ * what turned a pinned kernel-scale resolution from slow into OOM-killed
75
+ * (§7a.1 run 1: 22GB WAL, exit 137 at an envelope the pre-fix build
76
+ * survived).
77
+ */
78
+ private consecutiveGiveUps;
79
+ private futileUntil;
68
80
  constructor(db: DatabaseConnection, softMb?: number, intervalMs?: number, log?: (msg: string) => void);
81
+ private readonly log;
69
82
  private mb;
70
83
  /** Un-backfilled growth estimate: bytes the WAL has grown past the last full backfill. */
71
84
  private growthBytes;
@@ -1 +1 @@
1
- {"version":3,"file":"wal-valve.d.ts","sourceRoot":"","sources":["../../src/db/wal-valve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAWlD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAMpE;AAED,qBAAa,kBAAkB;IAe3B,OAAO,CAAC,QAAQ,CAAC,EAAE;IAEnB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG;IAjBtB,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,QAAQ,CAA8B;IAC9C,uFAAuF;IACvF,OAAO,CAAC,KAAK,CAA8B;IAC3C;;;;OAIG;IACH,OAAO,CAAC,sBAAsB,CAAK;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAGhB,EAAE,EAAE,kBAAkB,EACvC,MAAM,GAAE,MAA8D,EACrD,UAAU,GAAE,MAA0B,EACtC,GAAG,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAe;IAMxD,OAAO,CAAC,EAAE;IAIV,0FAA0F;IAC1F,OAAO,CAAC,WAAW;IAInB,+EAA+E;IAC/E,KAAK,IAAI,IAAI;IAMb,6EAA6E;IAC7E,IAAI,IAAI,IAAI;IAOZ,6FAA6F;IAC7F,KAAK,IAAI,IAAI;IAIb;;;;;OAKG;IACH,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAYpC,uDAAuD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;;;;;;;OAOG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ9B;;;;;;OAMG;YACW,aAAa;IAc3B,OAAO,CAAC,IAAI;CAoBb"}
1
+ {"version":3,"file":"wal-valve.d.ts","sourceRoot":"","sources":["../../src/db/wal-valve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAalD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAc1F;AAED,qBAAa,kBAAkB;IA6B3B,OAAO,CAAC,QAAQ,CAAC,EAAE;IAEnB,OAAO,CAAC,QAAQ,CAAC,UAAU;IA9B7B,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,QAAQ,CAA8B;IAC9C,uFAAuF;IACvF,OAAO,CAAC,KAAK,CAA8B;IAC3C;;;;OAIG;IACH,OAAO,CAAC,sBAAsB,CAAK;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC;;;;;;;;;OASG;IACH,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,WAAW,CAAK;gBAGL,EAAE,EAAE,kBAAkB,EACvC,MAAM,GAAE,MAA8D,EACrD,UAAU,GAAE,MAA0B,EACvD,GAAG,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAe;IAavC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAwB;IAE5C,OAAO,CAAC,EAAE;IAIV,0FAA0F;IAC1F,OAAO,CAAC,WAAW;IAInB,+EAA+E;IAC/E,KAAK,IAAI,IAAI;IAkBb,6EAA6E;IAC7E,IAAI,IAAI,IAAI;IAOZ,6FAA6F;IAC7F,KAAK,IAAI,IAAI;IAIb;;;;;OAKG;IACH,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAqBpC,uDAAuD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;;;;;;;OAOG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ9B;;;;;;OAMG;YACW,aAAa;IAkC3B,OAAO,CAAC,IAAI;CA8Bb"}
@@ -51,6 +51,8 @@ exports.resolveWalValveMb = resolveWalValveMb;
51
51
  const DEFAULT_WAL_VALVE_MB = 256;
52
52
  /** Hard cap = this × soft threshold; past it the writer pauses for a full backfill. */
53
53
  const HARD_CAP_MULTIPLIER = 2;
54
+ /** File cap = this × soft threshold; past it the barrier also TRUNCATEs the file. */
55
+ const FILE_CAP_MULTIPLIER = 4;
54
56
  /** Passes attempted per writer pause before giving up (a pinned reader could stall forever). */
55
57
  const MAX_PAUSED_BACKFILL_PASSES = 20;
56
58
  /** How often the timer looks at the WAL file size. */
@@ -59,18 +61,25 @@ const CHECK_INTERVAL_MS = 2000;
59
61
  * Resolve the valve's soft threshold from the `HOMEGRAPH_WAL_VALVE_MB`
60
62
  * override; non-numeric / non-positive values fall back to the default.
61
63
  */
62
- function resolveWalValveMb(envVal) {
64
+ function resolveWalValveMb(envVal, dbSizeBytes) {
63
65
  if (envVal !== undefined && envVal !== '') {
64
66
  const n = Number(envVal);
65
67
  if (Number.isFinite(n) && n > 0)
66
68
  return Math.floor(n);
67
69
  }
70
+ // Scale with the project when the caller knows the DB size: every fold
71
+ // re-writes hot B-tree pages into the main file (the #1231 pathology in
72
+ // bounded form — 111s of a kernel-scale batch loop at the flat 256MB cap,
73
+ // §7a.2), so a big project affords a proportionally bigger transient WAL
74
+ // (~dbSize/4 soft ⇒ file cap ≈ dbSize) in exchange for ~4× fewer folds.
75
+ if (dbSizeBytes !== undefined && dbSizeBytes > 0) {
76
+ return Math.min(2048, Math.max(DEFAULT_WAL_VALVE_MB, Math.floor(dbSizeBytes / 4 / (1024 * 1024))));
77
+ }
68
78
  return DEFAULT_WAL_VALVE_MB;
69
79
  }
70
80
  class WalCheckpointValve {
71
81
  db;
72
82
  intervalMs;
73
- log;
74
83
  timer = null;
75
84
  inflight = null;
76
85
  /** Writer pause in progress (hard cap breached): passes loop until a full backfill. */
@@ -83,13 +92,33 @@ class WalCheckpointValve {
83
92
  sizeAtLastFullBackfill = 0;
84
93
  softBytes;
85
94
  hardBytes;
95
+ fileCapBytes;
96
+ /**
97
+ * Futility latch: consecutive backfill give-ups (a reader pinning the WAL)
98
+ * disable further writer pauses for a cooldown, so a pinned phase degrades
99
+ * to the pre-valve behavior (unbounded WAL, folded when the pinner exits)
100
+ * instead of burning a 20-pass checkpoint attempt — each pass a worker
101
+ * thread + fresh connection — at EVERY over-cap boundary. That churn is
102
+ * what turned a pinned kernel-scale resolution from slow into OOM-killed
103
+ * (§7a.1 run 1: 22GB WAL, exit 137 at an envelope the pre-fix build
104
+ * survived).
105
+ */
106
+ consecutiveGiveUps = 0;
107
+ futileUntil = 0;
86
108
  constructor(db, softMb = resolveWalValveMb(process.env.HOMEGRAPH_WAL_VALVE_MB), intervalMs = CHECK_INTERVAL_MS, log = () => { }) {
87
109
  this.db = db;
88
110
  this.intervalMs = intervalMs;
89
- this.log = log;
90
111
  this.softBytes = softMb * 1024 * 1024;
91
112
  this.hardBytes = this.softBytes * HARD_CAP_MULTIPLIER;
113
+ this.fileCapBytes = this.softBytes * FILE_CAP_MULTIPLIER;
114
+ // HOMEGRAPH_WAL_VALVE_DEBUG=1 surfaces valve decisions to stderr without
115
+ // needing the caller's verbose plumbing — the observability gap that let
116
+ // §7a.1 run 1 fail silently (give-ups were verbose-gated and invisible).
117
+ this.log = process.env.HOMEGRAPH_WAL_VALVE_DEBUG
118
+ ? (m) => console.error(`[wal-valve] ${m}`)
119
+ : log;
92
120
  }
121
+ log;
93
122
  mb(n) {
94
123
  return `${Math.round(n / 1024 / 1024)}MB`;
95
124
  }
@@ -101,7 +130,19 @@ class WalCheckpointValve {
101
130
  start() {
102
131
  if (this.timer)
103
132
  return;
104
- this.timer = setInterval(() => this.check(), this.intervalMs);
133
+ // One armed line per run under either diagnostics env: §7a.1's failed
134
+ // kernel-scale runs burned three 25-minute cycles before "is the valve
135
+ // even alive?" could be answered.
136
+ if (process.env.HOMEGRAPH_SYNTH_TIMINGS || process.env.HOMEGRAPH_WAL_VALVE_DEBUG) {
137
+ console.error(`[wal-valve] armed soft=${this.mb(this.softBytes)} hard=${this.mb(this.hardBytes)} wal=${this.mb(this.db.getWalSizeBytes())}`);
138
+ }
139
+ let ticks = 0;
140
+ this.timer = setInterval(() => {
141
+ if ((++ticks % 15) === 0) {
142
+ this.log(`alive: wal=${this.mb(this.db.getWalSizeBytes())} baseline=${this.mb(this.sizeAtLastFullBackfill)} inflight=${this.inflight ? 'y' : 'n'} paused=${this.pause ? 'y' : 'n'}`);
143
+ }
144
+ this.check();
145
+ }, this.intervalMs);
105
146
  this.timer.unref?.();
106
147
  }
107
148
  /** Stop watching. Any in-flight checkpoint keeps running — await drain(). */
@@ -125,7 +166,17 @@ class WalCheckpointValve {
125
166
  backpressure() {
126
167
  if (this.pause)
127
168
  return this.pause;
128
- if (this.growthBytes() <= this.hardBytes)
169
+ if (Date.now() < this.futileUntil)
170
+ return null; // pinned reader — parking is churn, not progress
171
+ // Two independent triggers:
172
+ // - growth: un-backfilled BACKLOG past the hard cap (the original valve).
173
+ // - file size: a WAL can stay fully backfilled and still grow without
174
+ // bound — the writer only restarts at frame 0 if a commit finds no
175
+ // reader marks, which §7a.1's instrumented run showed never happens
176
+ // in practice (file marched 361→721MB through two COMPLETE
177
+ // backfills). Past the file cap, park and TRUNCATE at the barrier —
178
+ // the backfill part is instant when the backlog is already folded.
179
+ if (this.growthBytes() <= this.hardBytes && this.db.getWalSizeBytes() <= this.fileCapBytes)
129
180
  return null;
130
181
  this.log(`backpressure: wal=${this.mb(this.db.getWalSizeBytes())} baseline=${this.mb(this.sizeAtLastFullBackfill)} — pausing writer for full backfill`);
131
182
  const t0 = Date.now();
@@ -176,16 +227,39 @@ class WalCheckpointValve {
176
227
  return; // checkpoint machinery unavailable — don't spin
177
228
  this.log(`backfill pass ${i + 1}: busy=${res.busy} log=${res.log} checkpointed=${res.checkpointed} wal=${this.mb(this.db.getWalSizeBytes())}`);
178
229
  if (res.busy === 0 && res.log === res.checkpointed) {
230
+ // Backfill complete AND we are at a parked barrier (backfillFully only
231
+ // runs under a writer pause): the no-reader window is guaranteed, so
232
+ // chop the FILE too — a fully-backfilled WAL otherwise keeps growing
233
+ // whenever commits land while pool readers hold marks (§7a.1: 22GB
234
+ // on-disk at kernel scale despite backfills). A racing reader turns
235
+ // this into a no-op (busy=1); the passive result above still stands.
236
+ const trunc = await this.db.checkpointWalTruncate();
237
+ if (trunc)
238
+ this.log(`truncate: busy=${trunc.busy} wal=${this.mb(this.db.getWalSizeBytes())}`);
179
239
  this.sizeAtLastFullBackfill = this.db.getWalSizeBytes();
240
+ this.consecutiveGiveUps = 0;
241
+ this.futileUntil = 0;
180
242
  return;
181
243
  }
182
244
  }
183
- this.log(`backfill gave up after ${MAX_PAUSED_BACKFILL_PASSES} passes — WAL stays unbounded this cycle`);
245
+ this.consecutiveGiveUps++;
246
+ if (this.consecutiveGiveUps >= 2) {
247
+ this.futileUntil = Date.now() + 60_000;
248
+ }
249
+ const msg = `backfill gave up after ${MAX_PAUSED_BACKFILL_PASSES} passes (streak ${this.consecutiveGiveUps}${this.futileUntil ? ', parking disabled 60s' : ''}) — a reader is pinning the WAL`;
250
+ this.log(msg);
251
+ // Give-ups are rare and load-bearing for §7a.1-class diagnosis — surface
252
+ // them on any timing-instrumented run, not just valve-debug ones.
253
+ if (process.env.HOMEGRAPH_SYNTH_TIMINGS && !process.env.HOMEGRAPH_WAL_VALVE_DEBUG) {
254
+ console.error(`[wal-valve] ${msg}`);
255
+ }
184
256
  }
185
257
  fire() {
258
+ this.log(`fire: wal=${this.mb(this.db.getWalSizeBytes())} baseline=${this.mb(this.sizeAtLastFullBackfill)}`);
186
259
  const p = this.db
187
260
  .checkpointWalPassive()
188
261
  .then((res) => {
262
+ this.log(`timer pass: ${res ? `busy=${res.busy} log=${res.log} checkpointed=${res.checkpointed}` : 'null (machinery unavailable)'} wal=${this.mb(this.db.getWalSizeBytes())}`);
189
263
  // Full backfill (busy 0, every log frame checkpointed) ⇒ the writer's
190
264
  // next commit wraps the WAL; the file's current size becomes the new
191
265
  // growth baseline. A partial pass (writer appended during it, or a
@@ -194,6 +268,14 @@ class WalCheckpointValve {
194
268
  // SQLite reports log = checkpointed = -1, which is harmless here.
195
269
  if (res && res.busy === 0 && res.log === res.checkpointed) {
196
270
  this.sizeAtLastFullBackfill = this.db.getWalSizeBytes();
271
+ // NO truncate here. A truncate checkpoint that starts against an
272
+ // ACTIVE writer wins the lock race and then blocks that writer for
273
+ // its entire backfill — after a multi-GB single-transaction burst
274
+ // (edge-index recreate) that exceeds the writer's 5s busy_timeout
275
+ // and fails the index with "database is locked" (§7a.2 record run).
276
+ // The file chop happens exclusively at parked barriers
277
+ // (backpressure/foldNow), where the writer is awaiting us by
278
+ // construction and cannot collide.
197
279
  }
198
280
  })
199
281
  .catch(() => { })