wiki-graph-core 0.3.1

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 (576) hide show
  1. package/LICENSE +201 -0
  2. package/data/editor/clue_reviewer.jinja +410 -0
  3. package/data/editor/clue_reviewer_generator.jinja +428 -0
  4. package/data/editor/revision_feedback.jinja +5 -0
  5. package/data/editor/text_compressor.jinja +127 -0
  6. package/data/guaranteed/response_intent_classifier.jinja +8 -0
  7. package/data/help/commands/archive/next.jinja +29 -0
  8. package/data/help/commands/gc.jinja +38 -0
  9. package/data/help/commands/legacy/migrate.jinja +20 -0
  10. package/data/help/commands/legacy.jinja +19 -0
  11. package/data/help/commands/maintenance/chapter/add.jinja +41 -0
  12. package/data/help/commands/maintenance/chapter/list.jinja +33 -0
  13. package/data/help/commands/maintenance/chapter/move.jinja +36 -0
  14. package/data/help/commands/maintenance/chapter/remove.jinja +26 -0
  15. package/data/help/commands/maintenance/chapter/reset.jinja +29 -0
  16. package/data/help/commands/maintenance/chapter/set-source.jinja +34 -0
  17. package/data/help/commands/maintenance/chapter/set-summary.jinja +31 -0
  18. package/data/help/commands/maintenance/chapter/set-title.jinja +29 -0
  19. package/data/help/commands/maintenance/chapter/tree.jinja +58 -0
  20. package/data/help/commands/maintenance/chapter.jinja +59 -0
  21. package/data/help/commands/maintenance/cover.jinja +28 -0
  22. package/data/help/commands/maintenance/meta.jinja +55 -0
  23. package/data/help/commands/predicate.jinja +460 -0
  24. package/data/help/commands/root.jinja +83 -0
  25. package/data/help/commands/transform.jinja +34 -0
  26. package/data/help/commands/uri.jinja +274 -0
  27. package/data/help/topics/config.jinja +88 -0
  28. package/data/help/topics/format.jinja +72 -0
  29. package/data/help/topics/readiness.jinja +99 -0
  30. package/data/help/topics/recipe.jinja +151 -0
  31. package/data/help/topics/runtime.jinja +62 -0
  32. package/data/help/topics/uri.jinja +178 -0
  33. package/data/output/epub/cover.xhtml.jinja +13 -0
  34. package/data/output/epub/nav.xhtml.jinja +12 -0
  35. package/data/output/epub/package.opf.xml.jinja +39 -0
  36. package/data/output/epub/section.xhtml.jinja +12 -0
  37. package/data/topologization/book_coherence_extraction.jinja +223 -0
  38. package/data/topologization/chunk_extraction.jinja +233 -0
  39. package/data/topologization/evidence_choice.jinja +38 -0
  40. package/data/topologization/translate_chunks.jinja +138 -0
  41. package/data/topologization/user_focused_extraction.jinja +378 -0
  42. package/dist/archive/query/archive-view.d.ts +26 -0
  43. package/dist/archive/query/archive-view.js +4540 -0
  44. package/dist/archive/query/archive-view.js.map +1 -0
  45. package/dist/archive/query/continuation-cursor.d.ts +61 -0
  46. package/dist/archive/query/continuation-cursor.js +363 -0
  47. package/dist/archive/query/continuation-cursor.js.map +1 -0
  48. package/dist/archive/query/index.d.ts +29 -0
  49. package/dist/archive/query/index.js +63 -0
  50. package/dist/archive/query/index.js.map +1 -0
  51. package/dist/archive/query/lexical-search.d.ts +22 -0
  52. package/dist/archive/query/lexical-search.js +137 -0
  53. package/dist/archive/query/lexical-search.js.map +1 -0
  54. package/dist/archive/query/search-cache.d.ts +192 -0
  55. package/dist/archive/query/search-cache.js +1248 -0
  56. package/dist/archive/query/search-cache.js.map +1 -0
  57. package/dist/archive/search-index/index.d.ts +26 -0
  58. package/dist/archive/search-index/index.js +29 -0
  59. package/dist/archive/search-index/index.js.map +1 -0
  60. package/dist/archive/search-index/search-index.d.ts +26 -0
  61. package/dist/archive/search-index/search-index.js +555 -0
  62. package/dist/archive/search-index/search-index.js.map +1 -0
  63. package/dist/archive/search-index/search-tokenizer.d.ts +16 -0
  64. package/dist/archive/search-index/search-tokenizer.js +225 -0
  65. package/dist/archive/search-index/search-tokenizer.js.map +1 -0
  66. package/dist/common/cli-command.d.ts +4 -0
  67. package/dist/common/cli-command.js +7 -0
  68. package/dist/common/cli-command.js.map +1 -0
  69. package/dist/common/data-dir.d.ts +3 -0
  70. package/dist/common/data-dir.js +48 -0
  71. package/dist/common/data-dir.js.map +1 -0
  72. package/dist/common/language.d.ts +55 -0
  73. package/dist/common/language.js +208 -0
  74. package/dist/common/language.js.map +1 -0
  75. package/dist/common/llm-scope.d.ts +19 -0
  76. package/dist/common/llm-scope.js +31 -0
  77. package/dist/common/llm-scope.js.map +1 -0
  78. package/dist/common/logging.d.ts +22 -0
  79. package/dist/common/logging.js +141 -0
  80. package/dist/common/logging.js.map +1 -0
  81. package/dist/common/template.d.ts +8 -0
  82. package/dist/common/template.js +54 -0
  83. package/dist/common/template.js.map +1 -0
  84. package/dist/common/tinyld-language.d.ts +6 -0
  85. package/dist/common/tinyld-language.js +34 -0
  86. package/dist/common/tinyld-language.js.map +1 -0
  87. package/dist/common/wiki-graph-dir.d.ts +10 -0
  88. package/dist/common/wiki-graph-dir.js +41 -0
  89. package/dist/common/wiki-graph-dir.js.map +1 -0
  90. package/dist/common/wiki-graph-temp.d.ts +6 -0
  91. package/dist/common/wiki-graph-temp.js +21 -0
  92. package/dist/common/wiki-graph-temp.js.map +1 -0
  93. package/dist/common/wiki-graph-uri.d.ts +28 -0
  94. package/dist/common/wiki-graph-uri.js +171 -0
  95. package/dist/common/wiki-graph-uri.js.map +1 -0
  96. package/dist/context/index.d.ts +1 -0
  97. package/dist/context/index.js +13 -0
  98. package/dist/context/index.js.map +1 -0
  99. package/dist/context/task-context.d.ts +41 -0
  100. package/dist/context/task-context.js +111 -0
  101. package/dist/context/task-context.js.map +1 -0
  102. package/dist/document/database.d.ts +30 -0
  103. package/dist/document/database.js +273 -0
  104. package/dist/document/database.js.map +1 -0
  105. package/dist/document/document.d.ts +153 -0
  106. package/dist/document/document.js +615 -0
  107. package/dist/document/document.js.map +1 -0
  108. package/dist/document/fragments.d.ts +63 -0
  109. package/dist/document/fragments.js +371 -0
  110. package/dist/document/fragments.js.map +1 -0
  111. package/dist/document/index.d.ts +13 -0
  112. package/dist/document/index.js +62 -0
  113. package/dist/document/index.js.map +1 -0
  114. package/dist/document/schema.d.ts +8 -0
  115. package/dist/document/schema.js +472 -0
  116. package/dist/document/schema.js.map +1 -0
  117. package/dist/document/shared-state-database.d.ts +9 -0
  118. package/dist/document/shared-state-database.js +193 -0
  119. package/dist/document/shared-state-database.js.map +1 -0
  120. package/dist/document/stores.d.ts +203 -0
  121. package/dist/document/stores.js +1678 -0
  122. package/dist/document/stores.js.map +1 -0
  123. package/dist/document/text-streams.d.ts +86 -0
  124. package/dist/document/text-streams.js +515 -0
  125. package/dist/document/text-streams.js.map +1 -0
  126. package/dist/document/types.d.ts +148 -0
  127. package/dist/document/types.js +69 -0
  128. package/dist/document/types.js.map +1 -0
  129. package/dist/editor/clue.d.ts +27 -0
  130. package/dist/editor/clue.js +158 -0
  131. package/dist/editor/clue.js.map +1 -0
  132. package/dist/editor/compressor.d.ts +18 -0
  133. package/dist/editor/compressor.js +68 -0
  134. package/dist/editor/compressor.js.map +1 -0
  135. package/dist/editor/editor.d.ts +39 -0
  136. package/dist/editor/editor.js +253 -0
  137. package/dist/editor/editor.js.map +1 -0
  138. package/dist/editor/feedback.d.ts +16 -0
  139. package/dist/editor/feedback.js +104 -0
  140. package/dist/editor/feedback.js.map +1 -0
  141. package/dist/editor/index.d.ts +17 -0
  142. package/dist/editor/index.js +5 -0
  143. package/dist/editor/index.js.map +1 -0
  144. package/dist/editor/language-review.d.ts +14 -0
  145. package/dist/editor/language-review.js +35 -0
  146. package/dist/editor/language-review.js.map +1 -0
  147. package/dist/editor/log.d.ts +37 -0
  148. package/dist/editor/log.js +202 -0
  149. package/dist/editor/log.js.map +1 -0
  150. package/dist/editor/markup.d.ts +20 -0
  151. package/dist/editor/markup.js +294 -0
  152. package/dist/editor/markup.js.map +1 -0
  153. package/dist/editor/prompt-templates.d.ts +6 -0
  154. package/dist/editor/prompt-templates.js +11 -0
  155. package/dist/editor/prompt-templates.js.map +1 -0
  156. package/dist/editor/response.d.ts +5 -0
  157. package/dist/editor/response.js +31 -0
  158. package/dist/editor/response.js.map +1 -0
  159. package/dist/editor/review.d.ts +34 -0
  160. package/dist/editor/review.js +189 -0
  161. package/dist/editor/review.js.map +1 -0
  162. package/dist/editor/types.d.ts +31 -0
  163. package/dist/editor/types.js +21 -0
  164. package/dist/editor/types.js.map +1 -0
  165. package/dist/evidence-selection/anchor-resolver.d.ts +43 -0
  166. package/dist/evidence-selection/anchor-resolver.js +783 -0
  167. package/dist/evidence-selection/anchor-resolver.js.map +1 -0
  168. package/dist/evidence-selection/anchor-types.d.ts +27 -0
  169. package/dist/evidence-selection/anchor-types.js +1 -0
  170. package/dist/evidence-selection/anchor-types.js.map +1 -0
  171. package/dist/evidence-selection/index.d.ts +8 -0
  172. package/dist/evidence-selection/index.js +32 -0
  173. package/dist/evidence-selection/index.js.map +1 -0
  174. package/dist/evidence-selection/prompt.d.ts +18 -0
  175. package/dist/evidence-selection/prompt.js +54 -0
  176. package/dist/evidence-selection/prompt.js.map +1 -0
  177. package/dist/evidence-selection/quote-score.d.ts +29 -0
  178. package/dist/evidence-selection/quote-score.js +331 -0
  179. package/dist/evidence-selection/quote-score.js.map +1 -0
  180. package/dist/evidence-selection/selection-resolver.d.ts +19 -0
  181. package/dist/evidence-selection/selection-resolver.js +303 -0
  182. package/dist/evidence-selection/selection-resolver.js.map +1 -0
  183. package/dist/evidence-selection/text.d.ts +4 -0
  184. package/dist/evidence-selection/text.js +66 -0
  185. package/dist/evidence-selection/text.js.map +1 -0
  186. package/dist/evidence-selection/types.d.ts +36 -0
  187. package/dist/evidence-selection/types.js +1 -0
  188. package/dist/evidence-selection/types.js.map +1 -0
  189. package/dist/facade/app.d.ts +75 -0
  190. package/dist/facade/app.js +141 -0
  191. package/dist/facade/app.js.map +1 -0
  192. package/dist/facade/build-queue.d.ts +181 -0
  193. package/dist/facade/build-queue.js +1442 -0
  194. package/dist/facade/build-queue.js.map +1 -0
  195. package/dist/facade/chapter-build.d.ts +60 -0
  196. package/dist/facade/chapter-build.js +1020 -0
  197. package/dist/facade/chapter-build.js.map +1 -0
  198. package/dist/facade/chapter.d.ts +168 -0
  199. package/dist/facade/chapter.js +944 -0
  200. package/dist/facade/chapter.js.map +1 -0
  201. package/dist/facade/digest.d.ts +55 -0
  202. package/dist/facade/digest.js +168 -0
  203. package/dist/facade/digest.js.map +1 -0
  204. package/dist/facade/graph.d.ts +60 -0
  205. package/dist/facade/graph.js +203 -0
  206. package/dist/facade/graph.js.map +1 -0
  207. package/dist/facade/import.d.ts +49 -0
  208. package/dist/facade/import.js +262 -0
  209. package/dist/facade/import.js.map +1 -0
  210. package/dist/facade/index.d.ts +39 -0
  211. package/dist/facade/index.js +171 -0
  212. package/dist/facade/index.js.map +1 -0
  213. package/dist/facade/knowledge-graph-build.d.ts +75 -0
  214. package/dist/facade/knowledge-graph-build.js +897 -0
  215. package/dist/facade/knowledge-graph-build.js.map +1 -0
  216. package/dist/facade/llm-sampling.d.ts +11 -0
  217. package/dist/facade/llm-sampling.js +58 -0
  218. package/dist/facade/llm-sampling.js.map +1 -0
  219. package/dist/facade/types.d.ts +8 -0
  220. package/dist/facade/types.js +1 -0
  221. package/dist/facade/types.js.map +1 -0
  222. package/dist/facade/wiki-graph-archive.d.ts +41 -0
  223. package/dist/facade/wiki-graph-archive.js +130 -0
  224. package/dist/facade/wiki-graph-archive.js.map +1 -0
  225. package/dist/gc/files.d.ts +15 -0
  226. package/dist/gc/files.js +141 -0
  227. package/dist/gc/files.js.map +1 -0
  228. package/dist/gc/index.d.ts +2 -0
  229. package/dist/gc/index.js +5 -0
  230. package/dist/gc/index.js.map +1 -0
  231. package/dist/gc/lock.d.ts +3 -0
  232. package/dist/gc/lock.js +137 -0
  233. package/dist/gc/lock.js.map +1 -0
  234. package/dist/gc/runner.d.ts +9 -0
  235. package/dist/gc/runner.js +129 -0
  236. package/dist/gc/runner.js.map +1 -0
  237. package/dist/gc/tmp-gc.d.ts +5 -0
  238. package/dist/gc/tmp-gc.js +57 -0
  239. package/dist/gc/tmp-gc.js.map +1 -0
  240. package/dist/gc/types.d.ts +24 -0
  241. package/dist/gc/types.js +1 -0
  242. package/dist/gc/types.js.map +1 -0
  243. package/dist/gc.cjs +10041 -0
  244. package/dist/gc.cjs.map +1 -0
  245. package/dist/gc.d.cts +10 -0
  246. package/dist/gc.d.ts +2 -0
  247. package/dist/gc.js +5 -0
  248. package/dist/gc.js.map +1 -0
  249. package/dist/guaranteed/classifier.d.ts +10 -0
  250. package/dist/guaranteed/classifier.js +88 -0
  251. package/dist/guaranteed/classifier.js.map +1 -0
  252. package/dist/guaranteed/errors.d.ts +38 -0
  253. package/dist/guaranteed/errors.js +73 -0
  254. package/dist/guaranteed/errors.js.map +1 -0
  255. package/dist/guaranteed/index.d.ts +7 -0
  256. package/dist/guaranteed/index.js +21 -0
  257. package/dist/guaranteed/index.js.map +1 -0
  258. package/dist/guaranteed/request.d.ts +8 -0
  259. package/dist/guaranteed/request.js +206 -0
  260. package/dist/guaranteed/request.js.map +1 -0
  261. package/dist/guaranteed/response.d.ts +12 -0
  262. package/dist/guaranteed/response.js +98 -0
  263. package/dist/guaranteed/response.js.map +1 -0
  264. package/dist/guaranteed/types.d.ts +20 -0
  265. package/dist/guaranteed/types.js +1 -0
  266. package/dist/guaranteed/types.js.map +1 -0
  267. package/dist/index-B8NNnQ8P.d.ts +424 -0
  268. package/dist/index.cjs +34774 -0
  269. package/dist/index.cjs.map +1 -0
  270. package/dist/index.d.cts +2462 -0
  271. package/dist/index.d.ts +79 -0
  272. package/dist/index.js +168 -0
  273. package/dist/index.js.map +1 -0
  274. package/dist/legacy-sdpub/upgrade.d.ts +7 -0
  275. package/dist/legacy-sdpub/upgrade.js +795 -0
  276. package/dist/legacy-sdpub/upgrade.js.map +1 -0
  277. package/dist/llm/cache.d.ts +13 -0
  278. package/dist/llm/cache.js +35 -0
  279. package/dist/llm/cache.js.map +1 -0
  280. package/dist/llm/client.d.ts +26 -0
  281. package/dist/llm/client.js +572 -0
  282. package/dist/llm/client.js.map +1 -0
  283. package/dist/llm/context.d.ts +22 -0
  284. package/dist/llm/context.js +78 -0
  285. package/dist/llm/context.js.map +1 -0
  286. package/dist/llm/errors.d.ts +7 -0
  287. package/dist/llm/errors.js +12 -0
  288. package/dist/llm/errors.js.map +1 -0
  289. package/dist/llm/index.d.ts +7 -0
  290. package/dist/llm/index.js +17 -0
  291. package/dist/llm/index.js.map +1 -0
  292. package/dist/llm/request-log.d.ts +9 -0
  293. package/dist/llm/request-log.js +35 -0
  294. package/dist/llm/request-log.js.map +1 -0
  295. package/dist/llm/sampling.d.ts +11 -0
  296. package/dist/llm/sampling.js +67 -0
  297. package/dist/llm/sampling.js.map +1 -0
  298. package/dist/llm/types.d.ts +50 -0
  299. package/dist/llm/types.js +1 -0
  300. package/dist/llm/types.js.map +1 -0
  301. package/dist/output/epub/archive.d.ts +9 -0
  302. package/dist/output/epub/archive.js +81 -0
  303. package/dist/output/epub/archive.js.map +1 -0
  304. package/dist/output/epub/book.d.ts +15 -0
  305. package/dist/output/epub/book.js +76 -0
  306. package/dist/output/epub/book.js.map +1 -0
  307. package/dist/output/epub/content.d.ts +9 -0
  308. package/dist/output/epub/content.js +35 -0
  309. package/dist/output/epub/content.js.map +1 -0
  310. package/dist/output/epub/index.d.ts +20 -0
  311. package/dist/output/epub/index.js +14 -0
  312. package/dist/output/epub/index.js.map +1 -0
  313. package/dist/output/epub/model.d.ts +24 -0
  314. package/dist/output/epub/model.js +1 -0
  315. package/dist/output/epub/model.js.map +1 -0
  316. package/dist/output/epub/navigation.d.ts +10 -0
  317. package/dist/output/epub/navigation.js +39 -0
  318. package/dist/output/epub/navigation.js.map +1 -0
  319. package/dist/output/epub/package.d.ts +17 -0
  320. package/dist/output/epub/package.js +29 -0
  321. package/dist/output/epub/package.js.map +1 -0
  322. package/dist/output/epub/shared.d.ts +4 -0
  323. package/dist/output/epub/shared.js +12 -0
  324. package/dist/output/epub/shared.js.map +1 -0
  325. package/dist/output/epub/templates.d.ts +35 -0
  326. package/dist/output/epub/templates.js +26 -0
  327. package/dist/output/epub/templates.js.map +1 -0
  328. package/dist/output/index.d.ts +14 -0
  329. package/dist/output/index.js +11 -0
  330. package/dist/output/index.js.map +1 -0
  331. package/dist/output/plain-text.d.ts +18 -0
  332. package/dist/output/plain-text.js +56 -0
  333. package/dist/output/plain-text.js.map +1 -0
  334. package/dist/progress/index.d.ts +3 -0
  335. package/dist/progress/index.js +14 -0
  336. package/dist/progress/index.js.map +1 -0
  337. package/dist/progress/reporter.d.ts +10 -0
  338. package/dist/progress/reporter.js +79 -0
  339. package/dist/progress/reporter.js.map +1 -0
  340. package/dist/progress/tracker.d.ts +37 -0
  341. package/dist/progress/tracker.js +119 -0
  342. package/dist/progress/tracker.js.map +1 -0
  343. package/dist/progress/types.d.ts +28 -0
  344. package/dist/progress/types.js +1 -0
  345. package/dist/progress/types.js.map +1 -0
  346. package/dist/reader/attention/attention.d.ts +39 -0
  347. package/dist/reader/attention/attention.js +123 -0
  348. package/dist/reader/attention/attention.js.map +1 -0
  349. package/dist/reader/attention/index.d.ts +9 -0
  350. package/dist/reader/attention/index.js +5 -0
  351. package/dist/reader/attention/index.js.map +1 -0
  352. package/dist/reader/attention/wave-reflection.d.ts +21 -0
  353. package/dist/reader/attention/wave-reflection.js +153 -0
  354. package/dist/reader/attention/wave-reflection.js.map +1 -0
  355. package/dist/reader/attention/working-memory.d.ts +28 -0
  356. package/dist/reader/attention/working-memory.js +62 -0
  357. package/dist/reader/attention/working-memory.js.map +1 -0
  358. package/dist/reader/chunk-batch/extract.d.ts +13 -0
  359. package/dist/reader/chunk-batch/extract.js +14 -0
  360. package/dist/reader/chunk-batch/extract.js.map +1 -0
  361. package/dist/reader/chunk-batch/extractor.d.ts +17 -0
  362. package/dist/reader/chunk-batch/extractor.js +337 -0
  363. package/dist/reader/chunk-batch/extractor.js.map +1 -0
  364. package/dist/reader/chunk-batch/fragment-projection.d.ts +34 -0
  365. package/dist/reader/chunk-batch/fragment-projection.js +94 -0
  366. package/dist/reader/chunk-batch/fragment-projection.js.map +1 -0
  367. package/dist/reader/chunk-batch/index.d.ts +12 -0
  368. package/dist/reader/chunk-batch/index.js +13 -0
  369. package/dist/reader/chunk-batch/index.js.map +1 -0
  370. package/dist/reader/chunk-batch/language.d.ts +9 -0
  371. package/dist/reader/chunk-batch/language.js +23 -0
  372. package/dist/reader/chunk-batch/language.js.map +1 -0
  373. package/dist/reader/chunk-batch/parser.d.ts +94 -0
  374. package/dist/reader/chunk-batch/parser.js +640 -0
  375. package/dist/reader/chunk-batch/parser.js.map +1 -0
  376. package/dist/reader/chunk-batch/prompt-templates.d.ts +6 -0
  377. package/dist/reader/chunk-batch/prompt-templates.js +11 -0
  378. package/dist/reader/chunk-batch/prompt-templates.js.map +1 -0
  379. package/dist/reader/chunk-batch/types.d.ts +85 -0
  380. package/dist/reader/chunk-batch/types.js +1 -0
  381. package/dist/reader/chunk-batch/types.js.map +1 -0
  382. package/dist/reader/index.d.ts +13 -0
  383. package/dist/reader/index.js +7 -0
  384. package/dist/reader/index.js.map +1 -0
  385. package/dist/reader/reader.d.ts +37 -0
  386. package/dist/reader/reader.js +82 -0
  387. package/dist/reader/reader.js.map +1 -0
  388. package/dist/reader/segment/index.d.ts +2 -0
  389. package/dist/reader/segment/index.js +5 -0
  390. package/dist/reader/segment/index.js.map +1 -0
  391. package/dist/reader/segment/intl-segmenter.d.ts +5 -0
  392. package/dist/reader/segment/intl-segmenter.js +102 -0
  393. package/dist/reader/segment/intl-segmenter.js.map +1 -0
  394. package/dist/reader/segment/segment.d.ts +5 -0
  395. package/dist/reader/segment/segment.js +9 -0
  396. package/dist/reader/segment/segment.js.map +1 -0
  397. package/dist/reader/segment/types.d.ts +14 -0
  398. package/dist/reader/segment/types.js +1 -0
  399. package/dist/reader/segment/types.js.map +1 -0
  400. package/dist/reader/types.d.ts +29 -0
  401. package/dist/reader/types.js +1 -0
  402. package/dist/reader/types.js.map +1 -0
  403. package/dist/serial.d.ts +89 -0
  404. package/dist/serial.js +476 -0
  405. package/dist/serial.js.map +1 -0
  406. package/dist/source/adapter.d.ts +15 -0
  407. package/dist/source/adapter.js +1 -0
  408. package/dist/source/adapter.js.map +1 -0
  409. package/dist/source/epub/archive.d.ts +27 -0
  410. package/dist/source/epub/archive.js +189 -0
  411. package/dist/source/epub/archive.js.map +1 -0
  412. package/dist/source/epub/content.d.ts +22 -0
  413. package/dist/source/epub/content.js +183 -0
  414. package/dist/source/epub/content.js.map +1 -0
  415. package/dist/source/epub/document.d.ts +55 -0
  416. package/dist/source/epub/document.js +179 -0
  417. package/dist/source/epub/document.js.map +1 -0
  418. package/dist/source/epub/index.d.ts +10 -0
  419. package/dist/source/epub/index.js +6 -0
  420. package/dist/source/epub/index.js.map +1 -0
  421. package/dist/source/epub/navigation.d.ts +16 -0
  422. package/dist/source/epub/navigation.js +215 -0
  423. package/dist/source/epub/navigation.js.map +1 -0
  424. package/dist/source/epub/package.d.ts +33 -0
  425. package/dist/source/epub/package.js +237 -0
  426. package/dist/source/epub/package.js.map +1 -0
  427. package/dist/source/epub/xml.d.ts +16 -0
  428. package/dist/source/epub/xml.js +111 -0
  429. package/dist/source/epub/xml.js.map +1 -0
  430. package/dist/source/index.d.ts +12 -0
  431. package/dist/source/index.js +32 -0
  432. package/dist/source/index.js.map +1 -0
  433. package/dist/source/meta.d.ts +30 -0
  434. package/dist/source/meta.js +22 -0
  435. package/dist/source/meta.js.map +1 -0
  436. package/dist/source/plain-text.d.ts +16 -0
  437. package/dist/source/plain-text.js +86 -0
  438. package/dist/source/plain-text.js.map +1 -0
  439. package/dist/source/toc.d.ts +16 -0
  440. package/dist/source/toc.js +17 -0
  441. package/dist/source/toc.js.map +1 -0
  442. package/dist/source/types.d.ts +16 -0
  443. package/dist/source/types.js +1 -0
  444. package/dist/source/types.js.map +1 -0
  445. package/dist/topology/grouping.d.ts +15 -0
  446. package/dist/topology/grouping.js +18 -0
  447. package/dist/topology/grouping.js.map +1 -0
  448. package/dist/topology/index.d.ts +21 -0
  449. package/dist/topology/index.js +5 -0
  450. package/dist/topology/index.js.map +1 -0
  451. package/dist/topology/resource-segmentation.d.ts +13 -0
  452. package/dist/topology/resource-segmentation.js +111 -0
  453. package/dist/topology/resource-segmentation.js.map +1 -0
  454. package/dist/topology/segment-incision.d.ts +18 -0
  455. package/dist/topology/segment-incision.js +197 -0
  456. package/dist/topology/segment-incision.js.map +1 -0
  457. package/dist/topology/snake-detector.d.ts +13 -0
  458. package/dist/topology/snake-detector.js +520 -0
  459. package/dist/topology/snake-detector.js.map +1 -0
  460. package/dist/topology/snake-graph-builder.d.ts +14 -0
  461. package/dist/topology/snake-graph-builder.js +76 -0
  462. package/dist/topology/snake-graph-builder.js.map +1 -0
  463. package/dist/topology/topology.d.ts +29 -0
  464. package/dist/topology/topology.js +348 -0
  465. package/dist/topology/topology.js.map +1 -0
  466. package/dist/topology/weights.d.ts +10 -0
  467. package/dist/topology/weights.js +84 -0
  468. package/dist/topology/weights.js.map +1 -0
  469. package/dist/types-FVIqiLC9.d.cts +24 -0
  470. package/dist/utils/async-semaphore.d.ts +7 -0
  471. package/dist/utils/async-semaphore.js +40 -0
  472. package/dist/utils/async-semaphore.js.map +1 -0
  473. package/dist/utils/enum.d.ts +5 -0
  474. package/dist/utils/enum.js +18 -0
  475. package/dist/utils/enum.js.map +1 -0
  476. package/dist/utils/hash.d.ts +3 -0
  477. package/dist/utils/hash.js +132 -0
  478. package/dist/utils/hash.js.map +1 -0
  479. package/dist/utils/node-error.d.ts +4 -0
  480. package/dist/utils/node-error.js +59 -0
  481. package/dist/utils/node-error.js.map +1 -0
  482. package/dist/wikg/archive-uri.d.ts +1 -0
  483. package/dist/wikg/archive-uri.js +37 -0
  484. package/dist/wikg/archive-uri.js.map +1 -0
  485. package/dist/wikg/archive.d.ts +28 -0
  486. package/dist/wikg/archive.js +496 -0
  487. package/dist/wikg/archive.js.map +1 -0
  488. package/dist/wikg/index.d.ts +31 -0
  489. package/dist/wikg/index.js +60 -0
  490. package/dist/wikg/index.js.map +1 -0
  491. package/dist/wikg/wikg-coordinator.d.ts +45 -0
  492. package/dist/wikg/wikg-coordinator.js +1687 -0
  493. package/dist/wikg/wikg-coordinator.js.map +1 -0
  494. package/dist/wikg/wiki-graph-archive-file.d.ts +42 -0
  495. package/dist/wikg/wiki-graph-archive-file.js +78 -0
  496. package/dist/wikg/wiki-graph-archive-file.js.map +1 -0
  497. package/dist/wikilink/index.d.ts +10 -0
  498. package/dist/wikilink/index.js +7 -0
  499. package/dist/wikilink/index.js.map +1 -0
  500. package/dist/wikilink/relation-discovery.d.ts +31 -0
  501. package/dist/wikilink/relation-discovery.js +477 -0
  502. package/dist/wikilink/relation-discovery.js.map +1 -0
  503. package/dist/wikilink/types.d.ts +23 -0
  504. package/dist/wikilink/types.js +1 -0
  505. package/dist/wikilink/types.js.map +1 -0
  506. package/dist/wikilink/window.d.ts +7 -0
  507. package/dist/wikilink/window.js +53 -0
  508. package/dist/wikilink/window.js.map +1 -0
  509. package/dist/wikimatch/enrichment.d.ts +10 -0
  510. package/dist/wikimatch/enrichment.js +55 -0
  511. package/dist/wikimatch/enrichment.js.map +1 -0
  512. package/dist/wikimatch/index.d.ts +16 -0
  513. package/dist/wikimatch/index.js +66 -0
  514. package/dist/wikimatch/index.js.map +1 -0
  515. package/dist/wikimatch/option-narrowing.d.ts +17 -0
  516. package/dist/wikimatch/option-narrowing.js +208 -0
  517. package/dist/wikimatch/option-narrowing.js.map +1 -0
  518. package/dist/wikimatch/options.d.ts +14 -0
  519. package/dist/wikimatch/options.js +155 -0
  520. package/dist/wikimatch/options.js.map +1 -0
  521. package/dist/wikimatch/policy-judge.d.ts +17 -0
  522. package/dist/wikimatch/policy-judge.js +430 -0
  523. package/dist/wikimatch/policy-judge.js.map +1 -0
  524. package/dist/wikimatch/range-suppression.d.ts +9 -0
  525. package/dist/wikimatch/range-suppression.js +27 -0
  526. package/dist/wikimatch/range-suppression.js.map +1 -0
  527. package/dist/wikimatch/surface-screening.d.ts +16 -0
  528. package/dist/wikimatch/surface-screening.js +168 -0
  529. package/dist/wikimatch/surface-screening.js.map +1 -0
  530. package/dist/wikimatch/surface-window.d.ts +6 -0
  531. package/dist/wikimatch/surface-window.js +44 -0
  532. package/dist/wikimatch/surface-window.js.map +1 -0
  533. package/dist/wikimatch/types.d.ts +151 -0
  534. package/dist/wikimatch/types.js +1 -0
  535. package/dist/wikimatch/types.js.map +1 -0
  536. package/dist/wikimatch/wikispine.d.ts +43 -0
  537. package/dist/wikimatch/wikispine.js +399 -0
  538. package/dist/wikimatch/wikispine.js.map +1 -0
  539. package/dist/wikimatch/window.d.ts +6 -0
  540. package/dist/wikimatch/window.js +83 -0
  541. package/dist/wikimatch/window.js.map +1 -0
  542. package/dist/wikimatch/words.d.ts +16 -0
  543. package/dist/wikimatch/words.js +66 -0
  544. package/dist/wikimatch/words.js.map +1 -0
  545. package/dist/wikipage/cache.d.ts +18 -0
  546. package/dist/wikipage/cache.js +404 -0
  547. package/dist/wikipage/cache.js.map +1 -0
  548. package/dist/wikipage/fetch-log.d.ts +17 -0
  549. package/dist/wikipage/fetch-log.js +143 -0
  550. package/dist/wikipage/fetch-log.js.map +1 -0
  551. package/dist/wikipage/index.d.ts +14 -0
  552. package/dist/wikipage/index.js +17 -0
  553. package/dist/wikipage/index.js.map +1 -0
  554. package/dist/wikipage/normalizer.d.ts +14 -0
  555. package/dist/wikipage/normalizer.js +147 -0
  556. package/dist/wikipage/normalizer.js.map +1 -0
  557. package/dist/wikipage/rate-limiter.d.ts +13 -0
  558. package/dist/wikipage/rate-limiter.js +84 -0
  559. package/dist/wikipage/rate-limiter.js.map +1 -0
  560. package/dist/wikipage/resolver.d.ts +26 -0
  561. package/dist/wikipage/resolver.js +325 -0
  562. package/dist/wikipage/resolver.js.map +1 -0
  563. package/dist/wikipage/types.d.ts +96 -0
  564. package/dist/wikipage/types.js +1 -0
  565. package/dist/wikipage/types.js.map +1 -0
  566. package/dist/wikipage/wikimedia-client.d.ts +48 -0
  567. package/dist/wikipage/wikimedia-client.js +397 -0
  568. package/dist/wikipage/wikimedia-client.js.map +1 -0
  569. package/dist/worker-BgQ0Xl8T.d.cts +181 -0
  570. package/dist/worker.cjs +5259 -0
  571. package/dist/worker.cjs.map +1 -0
  572. package/dist/worker.d.cts +2 -0
  573. package/dist/worker.d.ts +2 -0
  574. package/dist/worker.js +5 -0
  575. package/dist/worker.js.map +1 -0
  576. package/package.json +78 -0
@@ -0,0 +1,1687 @@
1
+ import { createHash, randomUUID } from "crypto";
2
+ import {
3
+ mkdir,
4
+ mkdtemp,
5
+ readFile,
6
+ readdir,
7
+ rename,
8
+ rm,
9
+ stat,
10
+ writeFile
11
+ } from "fs/promises";
12
+ import { tmpdir } from "os";
13
+ import {
14
+ basename,
15
+ dirname,
16
+ isAbsolute,
17
+ join,
18
+ posix,
19
+ relative,
20
+ resolve
21
+ } from "path";
22
+ import { resolveWikiGraphStagingDirectoryPath } from "../common/wiki-graph-dir.js";
23
+ import { createWikiGraphTempDirectory } from "../common/wiki-graph-temp.js";
24
+ import {
25
+ Database as DocumentDatabase,
26
+ DirectoryDocument,
27
+ openSharedStateDatabase
28
+ } from "../document/index.js";
29
+ import { createArchiveSearchIndexFingerprint } from "../archive/query/index.js";
30
+ import { readSearchIndexFingerprintFromDatabase } from "../archive/search-index/index.js";
31
+ import {
32
+ isDisposableDirectoryEntry,
33
+ readPathSize,
34
+ removeDisposableChildDirectories,
35
+ removeDisposableDescendantDirectories,
36
+ removeDisposableDirectory
37
+ } from "../gc/files.js";
38
+ import { AsyncSemaphore } from "../utils/async-semaphore.js";
39
+ import { isNodeError } from "../utils/node-error.js";
40
+ import {
41
+ extractWikgArchive,
42
+ listWikgArchiveEntries,
43
+ readWikgArchiveMutationToken,
44
+ readWikgArchiveEntry,
45
+ WikgArchiveReader,
46
+ writeWikgArchiveWithOverlays
47
+ } from "./archive.js";
48
+ const STATE_SCHEMA_SQL = `
49
+ CREATE TABLE IF NOT EXISTS entry_overlays (
50
+ archive_key TEXT NOT NULL,
51
+ archive_path TEXT NOT NULL,
52
+ entry_path TEXT NOT NULL,
53
+ kind TEXT NOT NULL,
54
+ workspace_path TEXT,
55
+ archive_signature TEXT,
56
+ mutation_token TEXT,
57
+ updated_at INTEGER NOT NULL,
58
+ PRIMARY KEY (archive_key, entry_path)
59
+ );
60
+
61
+ CREATE TABLE IF NOT EXISTS entry_locks (
62
+ archive_key TEXT NOT NULL,
63
+ entry_path TEXT NOT NULL,
64
+ mode TEXT NOT NULL,
65
+ owner_id TEXT NOT NULL,
66
+ owner_pid INTEGER NOT NULL,
67
+ heartbeat_at INTEGER NOT NULL,
68
+ created_at INTEGER NOT NULL,
69
+ PRIMARY KEY (archive_key, entry_path, owner_id)
70
+ );
71
+
72
+ CREATE TABLE IF NOT EXISTS archive_owners (
73
+ archive_key TEXT NOT NULL,
74
+ owner_id TEXT NOT NULL,
75
+ owner_pid INTEGER NOT NULL,
76
+ heartbeat_at INTEGER NOT NULL,
77
+ created_at INTEGER NOT NULL,
78
+ PRIMARY KEY (archive_key, owner_id)
79
+ );
80
+
81
+ CREATE TABLE IF NOT EXISTS entry_sqlite_leases (
82
+ archive_key TEXT NOT NULL,
83
+ entry_path TEXT NOT NULL,
84
+ mode TEXT NOT NULL DEFAULT 'read',
85
+ owner_id TEXT NOT NULL,
86
+ owner_pid INTEGER NOT NULL,
87
+ heartbeat_at INTEGER NOT NULL,
88
+ created_at INTEGER NOT NULL,
89
+ PRIMARY KEY (archive_key, entry_path, owner_id)
90
+ );
91
+
92
+ CREATE TABLE IF NOT EXISTS archive_commit_locks (
93
+ archive_key TEXT PRIMARY KEY,
94
+ owner_id TEXT NOT NULL,
95
+ owner_pid INTEGER NOT NULL,
96
+ heartbeat_at INTEGER NOT NULL,
97
+ created_at INTEGER NOT NULL
98
+ );
99
+ `;
100
+ const DATABASE_ENTRY_PATH = "database.db";
101
+ const SEARCH_INDEX_DATABASE_ENTRY_PATH = "fts.db";
102
+ const LOCK_POLL_INTERVAL_MS = 100;
103
+ const LOCK_STALE_TIMEOUT_MS = 6e4;
104
+ const OWNER_HEARTBEAT_INTERVAL_MS = 2e4;
105
+ const SQLITE_CACHE_TTL_MS = 60 * 60 * 1e3;
106
+ const STATE_DATABASE_SEMAPHORE = new AsyncSemaphore(1);
107
+ const ARCHIVE_SESSION_CONSTRUCTOR_TOKEN = /* @__PURE__ */ Symbol(
108
+ "WikgArchiveSession constructor token"
109
+ );
110
+ class WikgCoordinator {
111
+ createFileStore(archivePath, options = {}) {
112
+ return new WikgDocumentFileStore(resolve(archivePath), options);
113
+ }
114
+ async withArchiveSession(archivePath, operation) {
115
+ const session = await WikgArchiveSession.open(resolve(archivePath));
116
+ try {
117
+ return await operation(session);
118
+ } finally {
119
+ await session.close();
120
+ }
121
+ }
122
+ async withReadWorkspace(archivePath, operation, options = {}) {
123
+ const directoryPath = options.documentDirPath === void 0 ? await createWikiGraphTempDirectory("archive-open") : resolve(options.documentDirPath);
124
+ try {
125
+ await extractWikgArchive(resolve(archivePath), directoryPath);
126
+ return await operation(directoryPath);
127
+ } finally {
128
+ if (options.documentDirPath === void 0) {
129
+ await rm(directoryPath, { force: true, recursive: true });
130
+ }
131
+ }
132
+ }
133
+ async withWriteWorkspace(archivePath, operation) {
134
+ const directoryPath = await createWikiGraphTempDirectory("archive-write");
135
+ try {
136
+ await extractWikgArchive(resolve(archivePath), directoryPath);
137
+ return await operation(directoryPath);
138
+ } finally {
139
+ await rm(directoryPath, { force: true, recursive: true });
140
+ }
141
+ }
142
+ }
143
+ class WikgArchiveSession {
144
+ #archiveKey;
145
+ #archivePath;
146
+ #ownerId = createOwnerId();
147
+ #heartbeat;
148
+ #observedDirtyEntryPaths = /* @__PURE__ */ new Set();
149
+ #modifiedEntryPaths = /* @__PURE__ */ new Set();
150
+ #closed = false;
151
+ constructor(archivePath, token) {
152
+ if (token !== ARCHIVE_SESSION_CONSTRUCTOR_TOKEN) {
153
+ throw new Error("Use WikgCoordinator.withArchiveSession().");
154
+ }
155
+ this.#archivePath = resolve(archivePath);
156
+ this.#archiveKey = createArchiveKey(this.#archivePath);
157
+ this.#heartbeat = setInterval(() => {
158
+ void heartbeatArchiveOwner({
159
+ archiveKey: this.#archiveKey,
160
+ ownerId: this.#ownerId
161
+ }).catch(() => void 0);
162
+ }, OWNER_HEARTBEAT_INTERVAL_MS);
163
+ }
164
+ static async open(archivePath) {
165
+ const session = new WikgArchiveSession(
166
+ archivePath,
167
+ ARCHIVE_SESSION_CONSTRUCTOR_TOKEN
168
+ );
169
+ try {
170
+ await registerArchiveOwner({
171
+ archiveKey: session.#archiveKey,
172
+ ownerId: session.#ownerId
173
+ });
174
+ return session;
175
+ } catch (error) {
176
+ clearInterval(session.#heartbeat);
177
+ throw error;
178
+ }
179
+ }
180
+ get archiveKey() {
181
+ return this.#archiveKey;
182
+ }
183
+ get archivePath() {
184
+ return this.#archivePath;
185
+ }
186
+ get ownerId() {
187
+ return this.#ownerId;
188
+ }
189
+ observeDirtyEntry(entryPath) {
190
+ this.#observedDirtyEntryPaths.add(entryPath);
191
+ }
192
+ modifyEntry(entryPath) {
193
+ this.#modifiedEntryPaths.add(entryPath);
194
+ }
195
+ createFileStore(options = {}) {
196
+ return new WikgDocumentFileStore(this.#archivePath, {
197
+ ...options,
198
+ session: this
199
+ });
200
+ }
201
+ async materializeReadWorkspace(directoryPath, operation) {
202
+ const ownsDirectoryPath = directoryPath === void 0;
203
+ const resolvedDirectoryPath = ownsDirectoryPath ? await createWikiGraphTempDirectory("archive-open") : resolve(directoryPath);
204
+ if (!ownsDirectoryPath) {
205
+ await ensureEmptyDirectory(resolvedDirectoryPath);
206
+ }
207
+ const fileStore = this.createFileStore({ readonlyDatabase: true });
208
+ const entries = await listVisibleEntryPaths(
209
+ await listWikgArchiveEntries(this.#archivePath),
210
+ {
211
+ archiveKey: this.#archiveKey,
212
+ prefix: ""
213
+ }
214
+ );
215
+ try {
216
+ for (const entryPath of entries) {
217
+ const content = await fileStore.readFile(entryPath);
218
+ if (content === void 0) {
219
+ continue;
220
+ }
221
+ const targetPath = resolve(resolvedDirectoryPath, entryPath);
222
+ const relativeTargetPath = relative(resolvedDirectoryPath, targetPath);
223
+ if (relativeTargetPath.startsWith("..") || isAbsolute(relativeTargetPath)) {
224
+ throw new Error(`Archive entry escapes read workspace: ${entryPath}`);
225
+ }
226
+ await mkdir(dirname(targetPath), { recursive: true });
227
+ await writeFile(targetPath, content);
228
+ }
229
+ return await operation(resolvedDirectoryPath);
230
+ } finally {
231
+ await fileStore.close();
232
+ if (ownsDirectoryPath) {
233
+ await rm(resolvedDirectoryPath, { force: true, recursive: true });
234
+ }
235
+ }
236
+ }
237
+ async close() {
238
+ if (this.#closed) {
239
+ return;
240
+ }
241
+ this.#closed = true;
242
+ clearInterval(this.#heartbeat);
243
+ try {
244
+ await this.#settle();
245
+ await reapArchive(this.#archiveKey);
246
+ } finally {
247
+ await unregisterArchiveOwner({
248
+ archiveKey: this.#archiveKey,
249
+ ownerId: this.#ownerId
250
+ });
251
+ }
252
+ await import("../gc/index.js").then(async ({ tryRunWikiGraphGc }) => {
253
+ await tryRunWikiGraphGc({ opportunistic: true });
254
+ }).catch(() => void 0);
255
+ }
256
+ async #settle() {
257
+ const entryPaths = /* @__PURE__ */ new Set([
258
+ ...this.#observedDirtyEntryPaths,
259
+ ...this.#modifiedEntryPaths
260
+ ]);
261
+ if (entryPaths.size === 0) {
262
+ return;
263
+ }
264
+ await flushArchiveOverlays(this.#archiveKey, entryPaths);
265
+ }
266
+ }
267
+ async function tryStartWikgFlusher(archivePath) {
268
+ if (archivePath !== void 0) {
269
+ await flushArchiveOverlays(createArchiveKey(resolve(archivePath)));
270
+ return;
271
+ }
272
+ const archiveKeys = await withStateDatabase(async (state) => {
273
+ await cleanupStaleState(state);
274
+ return await state.queryAll(
275
+ `
276
+ SELECT DISTINCT archive_key
277
+ FROM entry_overlays
278
+ ORDER BY archive_key
279
+ `,
280
+ void 0,
281
+ (row) => getString(row, "archive_key")
282
+ );
283
+ });
284
+ for (const archiveKey of archiveKeys) {
285
+ await flushArchiveOverlays(archiveKey);
286
+ }
287
+ }
288
+ async function runWikgCoordinatorGc(context) {
289
+ const candidates = await listSqliteCacheGcCandidates();
290
+ let removed = 0;
291
+ let freedBytes = 0;
292
+ for (const candidate of candidates) {
293
+ const result = await tryRemoveSqliteCacheCandidate(candidate, context);
294
+ if (result.removed) {
295
+ removed += 1;
296
+ freedBytes += result.freedBytes;
297
+ }
298
+ }
299
+ const orphanedFiles = await removeOrphanedWorkspaceFiles(context);
300
+ const childDirectories = context.dryRun ? { freedBytes: 0, removed: 0, scanned: 0 } : await removeDisposableChildDirectories(getCoordinatorWorkspaceRootPath());
301
+ return {
302
+ freedBytes: freedBytes + orphanedFiles.freedBytes + childDirectories.freedBytes,
303
+ removed: removed + orphanedFiles.removed + childDirectories.removed,
304
+ scanned: candidates.length + orphanedFiles.scanned + childDirectories.scanned
305
+ };
306
+ }
307
+ async function listSqliteCacheGcCandidates() {
308
+ return await withStateDatabase(async (state) => {
309
+ await cleanupStaleState(state);
310
+ return await state.queryAll(
311
+ `
312
+ SELECT overlay.*
313
+ FROM entry_overlays AS overlay
314
+ WHERE overlay.entry_path IN (?, ?)
315
+ AND overlay.kind = 'file'
316
+ AND overlay.workspace_path IS NOT NULL
317
+ ORDER BY overlay.updated_at ASC
318
+ `,
319
+ [DATABASE_ENTRY_PATH, SEARCH_INDEX_DATABASE_ENTRY_PATH],
320
+ mapEntryOverlay
321
+ );
322
+ });
323
+ }
324
+ async function tryRemoveSqliteCacheCandidate(candidate, context) {
325
+ const releaseWriteLock = await acquireEntryLock(
326
+ candidate.archiveKey,
327
+ candidate.entryPath,
328
+ "write"
329
+ );
330
+ try {
331
+ const releaseStateLock = await acquireEntryLock(
332
+ candidate.archiveKey,
333
+ candidate.entryPath,
334
+ "state"
335
+ );
336
+ try {
337
+ const overlay = await readOverlay(
338
+ candidate.archiveKey,
339
+ candidate.entryPath
340
+ );
341
+ if (overlay?.workspacePath === void 0 || overlay.workspacePath !== candidate.workspacePath || !await canRemoveSqliteCacheOverlay(overlay, context)) {
342
+ return { freedBytes: 0, removed: false };
343
+ }
344
+ let freedBytes = await readPathSize(overlay.workspacePath);
345
+ if (!context.dryRun) {
346
+ await deleteOverlay(overlay.archiveKey, overlay.entryPath);
347
+ await rm(overlay.workspacePath, { force: true });
348
+ freedBytes += await removeDisposableDirectory(
349
+ dirname(overlay.workspacePath)
350
+ );
351
+ }
352
+ return { freedBytes, removed: true };
353
+ } finally {
354
+ await releaseStateLock();
355
+ }
356
+ } finally {
357
+ await releaseWriteLock();
358
+ }
359
+ }
360
+ async function canRemoveSqliteCacheOverlay(overlay, context) {
361
+ if (await hasActiveArchiveOwnerOrSqliteLease(
362
+ overlay.archiveKey,
363
+ overlay.entryPath
364
+ )) {
365
+ return false;
366
+ }
367
+ if (overlay.workspacePath === void 0) {
368
+ return false;
369
+ }
370
+ if (await isSqliteCacheOverlayDirty(overlay)) {
371
+ return true;
372
+ }
373
+ if (!context.force && context.now - overlay.updatedAt < SQLITE_CACHE_TTL_MS) {
374
+ return false;
375
+ }
376
+ if (overlay.entryPath === SEARCH_INDEX_DATABASE_ENTRY_PATH && !context.force) {
377
+ return false;
378
+ }
379
+ return true;
380
+ }
381
+ async function isSqliteCacheOverlayDirty(overlay) {
382
+ if (overlay.workspacePath === void 0) {
383
+ return true;
384
+ }
385
+ if (!await pathExists(overlay.workspacePath)) {
386
+ return true;
387
+ }
388
+ if (!await pathExists(overlay.archivePath)) {
389
+ return true;
390
+ }
391
+ if (overlay.entryPath !== SEARCH_INDEX_DATABASE_ENTRY_PATH) {
392
+ return false;
393
+ }
394
+ return await readSearchIndexCacheStatus(overlay) !== "current";
395
+ }
396
+ async function readSearchIndexCacheStatus(overlay) {
397
+ if (overlay.workspacePath === void 0) {
398
+ return "missing";
399
+ }
400
+ if (!await pathExists(overlay.workspacePath)) {
401
+ return "missing";
402
+ }
403
+ if (!await pathExists(overlay.archivePath)) {
404
+ return "dirty";
405
+ }
406
+ try {
407
+ const [indexedFingerprint, currentFingerprint] = await Promise.all([
408
+ readSearchIndexCacheFingerprint(overlay.workspacePath),
409
+ createCurrentArchiveSearchIndexFingerprint(overlay.archivePath)
410
+ ]);
411
+ if (indexedFingerprint === void 0) {
412
+ return "dirty";
413
+ }
414
+ return indexedFingerprint === currentFingerprint ? "current" : "dirty";
415
+ } catch {
416
+ return "dirty";
417
+ }
418
+ }
419
+ async function readSearchIndexCacheFingerprint(databasePath) {
420
+ const database = await DocumentDatabase.open(databasePath, "", {
421
+ readonly: true
422
+ });
423
+ try {
424
+ return await readSearchIndexFingerprintFromDatabase(database);
425
+ } finally {
426
+ await database.close();
427
+ }
428
+ }
429
+ async function createCurrentArchiveSearchIndexFingerprint(archivePath) {
430
+ const directoryPath = await createWikiGraphTempDirectory("archive-open");
431
+ try {
432
+ await extractWikgArchive(archivePath, directoryPath);
433
+ const document = await DirectoryDocument.open(directoryPath);
434
+ try {
435
+ return await createArchiveSearchIndexFingerprint(document);
436
+ } finally {
437
+ await document.release();
438
+ }
439
+ } finally {
440
+ await rm(directoryPath, { force: true, recursive: true });
441
+ }
442
+ }
443
+ async function hasActiveArchiveOwnerOrSqliteLease(archiveKey, entryPath) {
444
+ return await withStateDatabase(async (state) => {
445
+ await cleanupStaleState(state);
446
+ const ownerCount = await state.queryOne(
447
+ "SELECT COUNT(*) AS count FROM archive_owners WHERE archive_key = ?",
448
+ [archiveKey],
449
+ (row) => getNumber(row, "count")
450
+ );
451
+ const leaseCount = await state.queryOne(
452
+ `
453
+ SELECT COUNT(*) AS count
454
+ FROM entry_sqlite_leases
455
+ WHERE archive_key = ?
456
+ AND entry_path = ?
457
+ `,
458
+ [archiveKey, entryPath],
459
+ (row) => getNumber(row, "count")
460
+ );
461
+ return (ownerCount ?? 0) > 0 || (leaseCount ?? 0) > 0;
462
+ });
463
+ }
464
+ async function hasActiveWorkspaceUse(archiveKey) {
465
+ return await withStateDatabase(async (state) => {
466
+ await cleanupStaleState(state);
467
+ const ownerCount = await state.queryOne(
468
+ "SELECT COUNT(*) AS count FROM archive_owners WHERE archive_key = ?",
469
+ [archiveKey],
470
+ (row) => getNumber(row, "count")
471
+ );
472
+ const leaseCount = await state.queryOne(
473
+ "SELECT COUNT(*) AS count FROM entry_sqlite_leases WHERE archive_key = ?",
474
+ [archiveKey],
475
+ (row) => getNumber(row, "count")
476
+ );
477
+ const lockCount = await state.queryOne(
478
+ "SELECT COUNT(*) AS count FROM entry_locks WHERE archive_key = ?",
479
+ [archiveKey],
480
+ (row) => getNumber(row, "count")
481
+ );
482
+ return (ownerCount ?? 0) > 0 || (leaseCount ?? 0) > 0 || (lockCount ?? 0) > 0;
483
+ });
484
+ }
485
+ async function removeOrphanedWorkspaceFiles(context) {
486
+ const rootPath = getCoordinatorWorkspaceRootPath();
487
+ let archiveKeyEntries;
488
+ try {
489
+ archiveKeyEntries = await readdir(rootPath, { withFileTypes: true });
490
+ } catch (error) {
491
+ if (isNodeError(error) && error.code === "ENOENT") {
492
+ return { freedBytes: 0, removed: 0, scanned: 0 };
493
+ }
494
+ throw error;
495
+ }
496
+ let freedBytes = 0;
497
+ let removed = 0;
498
+ let scanned = 0;
499
+ for (const archiveKeyEntry of archiveKeyEntries) {
500
+ if (!archiveKeyEntry.isDirectory()) {
501
+ continue;
502
+ }
503
+ const archiveKey = archiveKeyEntry.name;
504
+ if (await hasActiveWorkspaceUse(archiveKey)) {
505
+ continue;
506
+ }
507
+ const referencedWorkspacePaths = new Set(
508
+ (await listOverlays(archiveKey)).map((overlay) => overlay.workspacePath).filter((path) => path !== void 0)
509
+ );
510
+ for (const workspacePath of await listWorkspaceFiles(
511
+ join(rootPath, archiveKey)
512
+ )) {
513
+ scanned += 1;
514
+ if (isDisposableDirectoryEntry(basename(workspacePath))) {
515
+ continue;
516
+ }
517
+ if (referencedWorkspacePaths.has(workspacePath)) {
518
+ continue;
519
+ }
520
+ const size = await readPathSize(workspacePath);
521
+ if (!context.dryRun) {
522
+ await rm(workspacePath, { force: true });
523
+ }
524
+ freedBytes += size;
525
+ removed += 1;
526
+ }
527
+ if (!context.dryRun) {
528
+ const disposableDirectories = await removeDisposableDescendantDirectories(
529
+ join(rootPath, archiveKey)
530
+ );
531
+ freedBytes += disposableDirectories.freedBytes;
532
+ removed += disposableDirectories.removed;
533
+ scanned += disposableDirectories.scanned;
534
+ }
535
+ }
536
+ return { freedBytes, removed, scanned };
537
+ }
538
+ class WikgDocumentFileStore {
539
+ #archiveKey;
540
+ #archivePath;
541
+ #archiveReader;
542
+ #entrySourceByPath;
543
+ #readonlyDatabase;
544
+ #searchIndexWritebackPolicy;
545
+ #sqliteLeaseOwnerId = createOwnerId();
546
+ constructor(archivePath, options) {
547
+ this.#archivePath = resolve(archivePath);
548
+ this.#archiveKey = createArchiveKey(this.#archivePath);
549
+ this.#readonlyDatabase = options.readonlyDatabase === true;
550
+ this.#searchIndexWritebackPolicy = options.searchIndexWritebackPolicy ?? "archive";
551
+ this.#session = options.session;
552
+ }
553
+ async #readOverlay(entryPath) {
554
+ const overlay = await readOverlay(this.#archiveKey, entryPath);
555
+ if (overlay === void 0) {
556
+ return void 0;
557
+ }
558
+ const signature = await createArchiveSignature(this.#archivePath);
559
+ if (overlay.archiveSignature === signature) {
560
+ return overlay;
561
+ }
562
+ await deleteOverlay(this.#archiveKey, entryPath);
563
+ if (overlay.workspacePath !== void 0) {
564
+ await rm(overlay.workspacePath, { force: true }).catch(() => void 0);
565
+ }
566
+ this.#entrySourceByPath?.delete(entryPath);
567
+ return void 0;
568
+ }
569
+ #session;
570
+ async close() {
571
+ if (this.#archiveReader !== void 0) {
572
+ (await this.#archiveReader).close();
573
+ this.#archiveReader = void 0;
574
+ }
575
+ await releaseSqliteLease({
576
+ archiveKey: this.#archiveKey,
577
+ entryPath: DATABASE_ENTRY_PATH,
578
+ ownerId: this.#sqliteLeaseOwnerId
579
+ });
580
+ await releaseSqliteLease({
581
+ archiveKey: this.#archiveKey,
582
+ entryPath: SEARCH_INDEX_DATABASE_ENTRY_PATH,
583
+ ownerId: this.#sqliteLeaseOwnerId
584
+ });
585
+ }
586
+ async deleteFile(path) {
587
+ const entryPath = this.#toEntryPath(path);
588
+ await withEntryLock(this.#archiveKey, entryPath, "write", async () => {
589
+ await withEntryLock(this.#archiveKey, entryPath, "state", async () => {
590
+ const overlay = await this.#readOverlay(entryPath);
591
+ await upsertOverlay({
592
+ archiveKey: this.#archiveKey,
593
+ archivePath: this.#archivePath,
594
+ entryPath,
595
+ kind: "deleted"
596
+ });
597
+ if (overlay?.workspacePath !== void 0) {
598
+ await rm(overlay.workspacePath, { force: true }).catch(
599
+ () => void 0
600
+ );
601
+ }
602
+ this.#entrySourceByPath?.set(entryPath, { kind: "deleted" });
603
+ this.#session?.modifyEntry(entryPath);
604
+ });
605
+ });
606
+ }
607
+ async deleteTree(path) {
608
+ const rootEntryPath = this.#toEntryPath(path);
609
+ const entries = await listVisibleEntryPaths(
610
+ await this.#listArchiveEntries(),
611
+ {
612
+ archiveKey: this.#archiveKey,
613
+ prefix: `${rootEntryPath}/`
614
+ }
615
+ );
616
+ for (const entryPath of entries) {
617
+ await this.deleteFile(entryPath);
618
+ }
619
+ }
620
+ ensureDirectory() {
621
+ return Promise.resolve();
622
+ }
623
+ initializeDatabaseSchema() {
624
+ return false;
625
+ }
626
+ markDatabaseDirty() {
627
+ if (!this.#readonlyDatabase) {
628
+ this.#session?.observeDirtyEntry(DATABASE_ENTRY_PATH);
629
+ }
630
+ }
631
+ markSearchIndexDatabaseDirty() {
632
+ if (!this.#readonlyDatabase && this.#searchIndexWritebackPolicy === "archive") {
633
+ this.#session?.observeDirtyEntry(SEARCH_INDEX_DATABASE_ENTRY_PATH);
634
+ }
635
+ }
636
+ openDatabaseReadonly() {
637
+ return this.#readonlyDatabase;
638
+ }
639
+ async listFiles(path) {
640
+ const directoryEntryPath = this.#toEntryPath(path);
641
+ const prefix = directoryEntryPath === "" ? "" : `${directoryEntryPath}/`;
642
+ const entries = (await this.#listDirectoryEntryPaths(prefix)).map(
643
+ ([entryPath]) => entryPath
644
+ );
645
+ return entries.map((entryPath) => entryPath.slice(prefix.length)).filter((entryPath) => !entryPath.includes("/")).map((entryPath) => posix.basename(entryPath)).sort((left, right) => left.localeCompare(right));
646
+ }
647
+ async listFileContents(path) {
648
+ const directoryEntryPath = this.#toEntryPath(path);
649
+ const prefix = directoryEntryPath === "" ? "" : `${directoryEntryPath}/`;
650
+ return await withEntryLock(
651
+ this.#archiveKey,
652
+ normalizeEntryDirectoryPrefix(prefix),
653
+ "read",
654
+ async () => {
655
+ const contents = /* @__PURE__ */ new Map();
656
+ for (const [entryPath, source] of await this.#listDirectoryEntryPaths(
657
+ prefix
658
+ )) {
659
+ const name = entryPath.slice(prefix.length);
660
+ if (name.includes("/")) {
661
+ continue;
662
+ }
663
+ if (source.kind === "workspace") {
664
+ contents.set(name, await readFile(source.path));
665
+ continue;
666
+ }
667
+ const content = await this.#readArchiveEntry(entryPath);
668
+ if (content !== void 0) {
669
+ contents.set(name, content);
670
+ }
671
+ }
672
+ return contents;
673
+ }
674
+ );
675
+ }
676
+ async readFile(path) {
677
+ const entryPath = this.#toEntryPath(path);
678
+ return await withEntryLock(
679
+ this.#archiveKey,
680
+ entryPath,
681
+ "read",
682
+ async () => {
683
+ const source = (await this.#getEntrySources()).get(entryPath) ?? await withEntryLock(
684
+ this.#archiveKey,
685
+ entryPath,
686
+ "state",
687
+ async () => await resolveEntrySource({
688
+ archiveKey: this.#archiveKey,
689
+ entryPath
690
+ })
691
+ );
692
+ if (source.kind === "deleted") {
693
+ this.#session?.observeDirtyEntry(entryPath);
694
+ return void 0;
695
+ }
696
+ if (source.kind === "workspace") {
697
+ this.#session?.observeDirtyEntry(entryPath);
698
+ return await readFile(source.path);
699
+ }
700
+ return await this.#readArchiveEntry(entryPath);
701
+ }
702
+ );
703
+ }
704
+ async resolveDatabasePath() {
705
+ return await this.#resolveSqliteDatabasePath(DATABASE_ENTRY_PATH, {
706
+ createIfMissing: true,
707
+ readonly: this.#readonlyDatabase
708
+ });
709
+ }
710
+ async resolveSearchIndexDatabasePath() {
711
+ return await this.#resolveSqliteDatabasePath(
712
+ SEARCH_INDEX_DATABASE_ENTRY_PATH,
713
+ {
714
+ createIfMissing: !this.#readonlyDatabase,
715
+ readonly: this.#readonlyDatabase
716
+ }
717
+ );
718
+ }
719
+ async #resolveSqliteDatabasePath(entryPath, options) {
720
+ return await withEntryLock(
721
+ this.#archiveKey,
722
+ entryPath,
723
+ "state",
724
+ async () => {
725
+ let overlay = await this.#readOverlay(entryPath);
726
+ if (entryPath === SEARCH_INDEX_DATABASE_ENTRY_PATH && overlay?.kind !== "file") {
727
+ await tryAdoptSearchIndexCacheOverlay({
728
+ targetArchiveKey: this.#archiveKey,
729
+ targetArchivePath: this.#archivePath
730
+ });
731
+ overlay = await this.#readOverlay(entryPath);
732
+ }
733
+ if (overlay?.kind !== "file") {
734
+ const content = await readWikgArchiveEntry(
735
+ this.#archivePath,
736
+ entryPath
737
+ );
738
+ if (content === void 0 && !options.createIfMissing) {
739
+ throw new Error(`Archive SQLite entry is missing: ${entryPath}`);
740
+ }
741
+ const workspacePath = await createWorkspaceFilePath(
742
+ this.#archiveKey,
743
+ entryPath
744
+ );
745
+ await mkdir(dirname(workspacePath), { recursive: true });
746
+ await writeFile(workspacePath, content ?? new Uint8Array());
747
+ await upsertOverlay({
748
+ archiveKey: this.#archiveKey,
749
+ archivePath: this.#archivePath,
750
+ entryPath,
751
+ kind: "file",
752
+ workspacePath
753
+ });
754
+ overlay = await this.#readOverlay(entryPath);
755
+ }
756
+ if (overlay?.workspacePath === void 0) {
757
+ throw new Error("Could not materialize SQLite database.");
758
+ }
759
+ await acquireSqliteLease({
760
+ archiveKey: this.#archiveKey,
761
+ entryPath,
762
+ mode: options.readonly ? "read" : "write",
763
+ ownerId: this.#sqliteLeaseOwnerId
764
+ });
765
+ return overlay.workspacePath;
766
+ }
767
+ );
768
+ }
769
+ async writeFile(path, content, options) {
770
+ const entryPath = this.#toEntryPath(path);
771
+ await withEntryLock(this.#archiveKey, entryPath, "write", async () => {
772
+ const source = await withEntryLock(
773
+ this.#archiveKey,
774
+ entryPath,
775
+ "state",
776
+ async () => await resolveEntrySource({
777
+ archiveKey: this.#archiveKey,
778
+ entryPath
779
+ })
780
+ );
781
+ const archiveEntryExists = source.kind === "archive" && await this.#readArchiveEntry(entryPath) !== void 0;
782
+ if (options.overwrite !== true && (source.kind === "workspace" || archiveEntryExists)) {
783
+ throw new Error(`File already exists: ${path}`);
784
+ }
785
+ await withEntryLock(this.#archiveKey, entryPath, "state", async () => {
786
+ const workspacePath = await createWorkspaceFilePath(
787
+ this.#archiveKey,
788
+ entryPath
789
+ );
790
+ await mkdir(dirname(workspacePath), { recursive: true });
791
+ const temporaryWorkspacePath = `${workspacePath}.${process.pid}.${Date.now()}.tmp`;
792
+ try {
793
+ await writeFile(temporaryWorkspacePath, content);
794
+ await rename(temporaryWorkspacePath, workspacePath);
795
+ } finally {
796
+ await rm(temporaryWorkspacePath, { force: true }).catch(
797
+ () => void 0
798
+ );
799
+ }
800
+ await upsertOverlay({
801
+ archiveKey: this.#archiveKey,
802
+ archivePath: this.#archivePath,
803
+ entryPath,
804
+ kind: "file",
805
+ workspacePath
806
+ });
807
+ this.#entrySourceByPath?.set(entryPath, {
808
+ kind: "workspace",
809
+ path: workspacePath
810
+ });
811
+ if (source.kind === "workspace" && source.path !== workspacePath) {
812
+ await rm(source.path, { force: true }).catch(() => void 0);
813
+ }
814
+ this.#session?.modifyEntry(entryPath);
815
+ });
816
+ });
817
+ }
818
+ #toEntryPath(path) {
819
+ const resolvedPath = resolve(path);
820
+ const rootPrefix = this.#archivePath.endsWith("/") ? this.#archivePath : `${this.#archivePath}/`;
821
+ if (resolvedPath.startsWith(rootPrefix)) {
822
+ return normalizeEntryPath(resolvedPath.slice(rootPrefix.length));
823
+ }
824
+ return normalizeEntryPath(path);
825
+ }
826
+ async #listArchiveEntries() {
827
+ return (await this.#getArchiveReader()).listEntries();
828
+ }
829
+ async #readArchiveEntry(entryPath) {
830
+ return await (await this.#getArchiveReader()).readEntry(entryPath);
831
+ }
832
+ async #getArchiveReader() {
833
+ this.#archiveReader ??= WikgArchiveReader.open(this.#archivePath);
834
+ return await this.#archiveReader;
835
+ }
836
+ async #getEntrySources() {
837
+ if (this.#entrySourceByPath !== void 0) {
838
+ return this.#entrySourceByPath;
839
+ }
840
+ const entries = /* @__PURE__ */ new Map();
841
+ for (const entryPath of await this.#listArchiveEntries()) {
842
+ entries.set(entryPath, { kind: "archive" });
843
+ }
844
+ for (const overlay of await listOverlays(this.#archiveKey)) {
845
+ if (overlay.kind === "deleted") {
846
+ entries.set(overlay.entryPath, { kind: "deleted" });
847
+ } else if (overlay.workspacePath !== void 0) {
848
+ entries.set(overlay.entryPath, {
849
+ kind: "workspace",
850
+ path: overlay.workspacePath
851
+ });
852
+ }
853
+ }
854
+ this.#entrySourceByPath = entries;
855
+ return entries;
856
+ }
857
+ async #listDirectoryEntryPaths(prefix) {
858
+ const entries = [];
859
+ for (const [entryPath, source] of await this.#getEntrySources()) {
860
+ if (source.kind === "deleted" || !entryPath.startsWith(prefix)) {
861
+ continue;
862
+ }
863
+ entries.push([entryPath, source]);
864
+ }
865
+ return entries.sort(([left], [right]) => left.localeCompare(right));
866
+ }
867
+ }
868
+ async function flushArchiveOverlays(archiveKey, requestedEntryPaths) {
869
+ const overlays = (await listOverlays(archiveKey)).filter(
870
+ (overlay) => requestedEntryPaths === void 0 || requestedEntryPaths.has(overlay.entryPath)
871
+ );
872
+ const archivePath = await resolveArchivePathFromKey(archiveKey);
873
+ if (overlays.length === 0 || archivePath === void 0) {
874
+ return;
875
+ }
876
+ const entryPaths = overlays.map((overlay) => overlay.entryPath).sort((left, right) => left.localeCompare(right));
877
+ const lockedEntryPaths = new Set(entryPaths);
878
+ const releaseLocks = [];
879
+ try {
880
+ for (const entryPath of entryPaths) {
881
+ releaseLocks.push(await acquireEntryLock(archiveKey, entryPath, "write"));
882
+ }
883
+ for (const entryPath of entryPaths) {
884
+ releaseLocks.push(await acquireEntryLock(archiveKey, entryPath, "state"));
885
+ }
886
+ if (entryPaths.includes(DATABASE_ENTRY_PATH)) {
887
+ await waitForSqliteLeasesToDrain(archiveKey, DATABASE_ENTRY_PATH);
888
+ }
889
+ if (entryPaths.includes(SEARCH_INDEX_DATABASE_ENTRY_PATH)) {
890
+ await waitForSqliteLeasesToDrain(
891
+ archiveKey,
892
+ SEARCH_INDEX_DATABASE_ENTRY_PATH
893
+ );
894
+ }
895
+ const currentOverlays = (await listOverlays(archiveKey)).filter(
896
+ (overlay) => lockedEntryPaths.has(overlay.entryPath)
897
+ );
898
+ if (currentOverlays.length === 0) {
899
+ return;
900
+ }
901
+ const releaseCommit = await acquireArchiveCommitLock(archiveKey);
902
+ try {
903
+ const temporaryDirectoryPath = await mkdtemp(
904
+ join(tmpdir(), "wikigraph-flush-")
905
+ );
906
+ const temporaryArchivePath = join(
907
+ temporaryDirectoryPath,
908
+ basename(archivePath)
909
+ );
910
+ try {
911
+ await writeWikgArchiveWithOverlays(
912
+ archivePath,
913
+ temporaryArchivePath,
914
+ currentOverlays.map(toArchiveOverlay)
915
+ );
916
+ await mkdir(dirname(archivePath), { recursive: true });
917
+ await rename(temporaryArchivePath, archivePath);
918
+ } finally {
919
+ await rm(temporaryDirectoryPath, { force: true, recursive: true });
920
+ }
921
+ } finally {
922
+ await releaseCommit();
923
+ }
924
+ for (const overlay of currentOverlays) {
925
+ await deleteOverlay(archiveKey, overlay.entryPath);
926
+ if (overlay.workspacePath !== void 0) {
927
+ await rm(overlay.workspacePath, { force: true });
928
+ }
929
+ }
930
+ } finally {
931
+ for (const release of releaseLocks.reverse()) {
932
+ await release();
933
+ }
934
+ }
935
+ }
936
+ async function acquireArchiveCommitLock(archiveKey) {
937
+ const ownerId = createOwnerId();
938
+ while (true) {
939
+ const acquired = await withStateDatabase(async (state) => {
940
+ await cleanupStaleState(state);
941
+ return await state.transaction(async () => {
942
+ const existing = await state.queryOne(
943
+ "SELECT * FROM archive_commit_locks WHERE archive_key = ?",
944
+ [archiveKey],
945
+ mapArchiveCommitLock
946
+ );
947
+ if (existing !== void 0) {
948
+ return false;
949
+ }
950
+ await state.run(
951
+ `
952
+ INSERT INTO archive_commit_locks (
953
+ archive_key, owner_id, owner_pid, heartbeat_at, created_at
954
+ ) VALUES (?, ?, ?, ?, ?)
955
+ `,
956
+ [archiveKey, ownerId, process.pid, Date.now(), Date.now()]
957
+ );
958
+ return true;
959
+ });
960
+ });
961
+ if (acquired) {
962
+ return async () => {
963
+ await withStateDatabase(async (releaseState) => {
964
+ await releaseState.run(
965
+ "DELETE FROM archive_commit_locks WHERE archive_key = ? AND owner_id = ?",
966
+ [archiveKey, ownerId]
967
+ );
968
+ });
969
+ };
970
+ }
971
+ await delay(LOCK_POLL_INTERVAL_MS);
972
+ }
973
+ }
974
+ async function registerArchiveOwner(input) {
975
+ await withStateDatabase(async (state) => {
976
+ await cleanupStaleState(state);
977
+ await state.run(
978
+ `
979
+ INSERT OR REPLACE INTO archive_owners (
980
+ archive_key, owner_id, owner_pid, heartbeat_at, created_at
981
+ ) VALUES (?, ?, ?, ?, ?)
982
+ `,
983
+ [input.archiveKey, input.ownerId, process.pid, Date.now(), Date.now()]
984
+ );
985
+ });
986
+ }
987
+ async function heartbeatArchiveOwner(input) {
988
+ await withStateDatabase(async (state) => {
989
+ await state.run(
990
+ `
991
+ UPDATE archive_owners
992
+ SET heartbeat_at = ?, owner_pid = ?
993
+ WHERE archive_key = ? AND owner_id = ?
994
+ `,
995
+ [Date.now(), process.pid, input.archiveKey, input.ownerId]
996
+ );
997
+ });
998
+ }
999
+ async function unregisterArchiveOwner(input) {
1000
+ await withStateDatabase(async (state) => {
1001
+ await state.transaction(async () => {
1002
+ await state.run(
1003
+ "DELETE FROM entry_sqlite_leases WHERE archive_key = ? AND owner_id = ?",
1004
+ [input.archiveKey, input.ownerId]
1005
+ );
1006
+ await state.run(
1007
+ "DELETE FROM archive_owners WHERE archive_key = ? AND owner_id = ?",
1008
+ [input.archiveKey, input.ownerId]
1009
+ );
1010
+ });
1011
+ });
1012
+ }
1013
+ async function reapArchive(archiveKey) {
1014
+ await withStateDatabase(async (state) => {
1015
+ await cleanupStaleState(state);
1016
+ });
1017
+ const staleOwnerIds = await withStateDatabase(
1018
+ async (state) => await state.queryAll(
1019
+ `
1020
+ SELECT owner_id
1021
+ FROM archive_owners
1022
+ WHERE archive_key = ?
1023
+ AND heartbeat_at <= ?
1024
+ `,
1025
+ [archiveKey, Date.now() - LOCK_STALE_TIMEOUT_MS],
1026
+ (row) => getString(row, "owner_id")
1027
+ )
1028
+ );
1029
+ if (staleOwnerIds.length === 0) {
1030
+ return;
1031
+ }
1032
+ await withStateDatabase(async (state) => {
1033
+ await state.transaction(async () => {
1034
+ await state.run(
1035
+ `
1036
+ DELETE FROM entry_sqlite_leases
1037
+ WHERE archive_key = ?
1038
+ AND owner_id IN (${createPlaceholders(staleOwnerIds.length)})
1039
+ `,
1040
+ [archiveKey, ...staleOwnerIds]
1041
+ );
1042
+ await state.run(
1043
+ `
1044
+ DELETE FROM archive_owners
1045
+ WHERE archive_key = ?
1046
+ AND owner_id IN (${createPlaceholders(staleOwnerIds.length)})
1047
+ `,
1048
+ [archiveKey, ...staleOwnerIds]
1049
+ );
1050
+ });
1051
+ });
1052
+ await flushArchiveOverlays(archiveKey);
1053
+ }
1054
+ async function acquireEntryLock(archiveKey, entryPath, mode) {
1055
+ const ownerId = createOwnerId();
1056
+ while (true) {
1057
+ const acquired = await withStateDatabase(async (state) => {
1058
+ await cleanupStaleState(state);
1059
+ return await state.transaction(async () => {
1060
+ const conflicts = await state.queryAll(
1061
+ `
1062
+ SELECT *
1063
+ FROM entry_locks
1064
+ WHERE archive_key = ?
1065
+ `,
1066
+ [archiveKey],
1067
+ mapEntryLock
1068
+ );
1069
+ if (conflicts.some(
1070
+ (lock) => lock.ownerId !== ownerId && lockPathsConflict(entryPath, lock.entryPath) && locksConflict(mode, lock.mode)
1071
+ )) {
1072
+ return false;
1073
+ }
1074
+ await state.run(
1075
+ `
1076
+ INSERT INTO entry_locks (
1077
+ archive_key, entry_path, mode, owner_id, owner_pid, heartbeat_at, created_at
1078
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
1079
+ `,
1080
+ [
1081
+ archiveKey,
1082
+ entryPath,
1083
+ mode,
1084
+ ownerId,
1085
+ process.pid,
1086
+ Date.now(),
1087
+ Date.now()
1088
+ ]
1089
+ );
1090
+ return true;
1091
+ });
1092
+ });
1093
+ if (acquired) {
1094
+ return async () => {
1095
+ await withStateDatabase(async (releaseState) => {
1096
+ await releaseState.run(
1097
+ `
1098
+ DELETE FROM entry_locks
1099
+ WHERE archive_key = ? AND entry_path = ? AND owner_id = ?
1100
+ `,
1101
+ [archiveKey, entryPath, ownerId]
1102
+ );
1103
+ });
1104
+ };
1105
+ }
1106
+ await delay(LOCK_POLL_INTERVAL_MS);
1107
+ }
1108
+ }
1109
+ async function withEntryLock(archiveKey, entryPath, mode, operation) {
1110
+ const release = await acquireEntryLock(archiveKey, entryPath, mode);
1111
+ try {
1112
+ return await operation();
1113
+ } finally {
1114
+ await release();
1115
+ }
1116
+ }
1117
+ function locksConflict(requested, existing) {
1118
+ if (requested === "state" || existing === "state") {
1119
+ return requested === "state" && existing === "state";
1120
+ }
1121
+ if (requested === "read" && existing === "read") {
1122
+ return false;
1123
+ }
1124
+ return true;
1125
+ }
1126
+ function lockPathsConflict(requested, existing) {
1127
+ return requested === existing || lockPathContains(requested, existing) || lockPathContains(existing, requested);
1128
+ }
1129
+ function lockPathContains(parent, child) {
1130
+ return parent.endsWith("/") && child.startsWith(parent);
1131
+ }
1132
+ async function acquireSqliteLease(input) {
1133
+ await withStateDatabase(async (state) => {
1134
+ await cleanupStaleState(state);
1135
+ await state.run(
1136
+ `
1137
+ INSERT OR REPLACE INTO entry_sqlite_leases (
1138
+ archive_key, entry_path, mode, owner_id, owner_pid, heartbeat_at, created_at
1139
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
1140
+ `,
1141
+ [
1142
+ input.archiveKey,
1143
+ input.entryPath,
1144
+ input.mode,
1145
+ input.ownerId,
1146
+ process.pid,
1147
+ Date.now(),
1148
+ Date.now()
1149
+ ]
1150
+ );
1151
+ });
1152
+ }
1153
+ async function releaseSqliteLease(input) {
1154
+ await withStateDatabase(async (state) => {
1155
+ await state.run(
1156
+ `
1157
+ DELETE FROM entry_sqlite_leases
1158
+ WHERE archive_key = ? AND entry_path = ? AND owner_id = ?
1159
+ `,
1160
+ [input.archiveKey, input.entryPath, input.ownerId]
1161
+ );
1162
+ });
1163
+ }
1164
+ async function waitForSqliteLeasesToDrain(archiveKey, entryPath) {
1165
+ while (true) {
1166
+ const count = await withStateDatabase(async (state) => {
1167
+ await cleanupStaleState(state);
1168
+ return await state.queryOne(
1169
+ `
1170
+ SELECT COUNT(*) AS count
1171
+ FROM entry_sqlite_leases AS lease
1172
+ LEFT JOIN archive_owners AS owner
1173
+ ON owner.archive_key = lease.archive_key
1174
+ AND owner.owner_id = lease.owner_id
1175
+ WHERE lease.archive_key = ?
1176
+ AND lease.entry_path = ?
1177
+ AND (
1178
+ owner.owner_id IS NULL
1179
+ OR owner.heartbeat_at > ?
1180
+ )
1181
+ `,
1182
+ [archiveKey, entryPath, Date.now() - LOCK_STALE_TIMEOUT_MS],
1183
+ (row) => getNumber(row, "count")
1184
+ );
1185
+ });
1186
+ if (count === 0) {
1187
+ return;
1188
+ }
1189
+ await delay(LOCK_POLL_INTERVAL_MS);
1190
+ }
1191
+ }
1192
+ async function listVisibleEntryPaths(archiveEntries, input) {
1193
+ const matchingArchiveEntries = archiveEntries.filter(
1194
+ (entryPath) => entryPath.startsWith(input.prefix)
1195
+ );
1196
+ const overlays = await withStateDatabase(
1197
+ async (state) => await state.queryAll(
1198
+ `
1199
+ SELECT *
1200
+ FROM entry_overlays
1201
+ WHERE archive_key = ?
1202
+ `,
1203
+ [input.archiveKey],
1204
+ mapEntryOverlay
1205
+ )
1206
+ );
1207
+ const entries = new Set(matchingArchiveEntries);
1208
+ for (const overlay of overlays) {
1209
+ if (!overlay.entryPath.startsWith(input.prefix)) {
1210
+ continue;
1211
+ }
1212
+ if (overlay.kind === "deleted") {
1213
+ entries.delete(overlay.entryPath);
1214
+ } else {
1215
+ entries.add(overlay.entryPath);
1216
+ }
1217
+ }
1218
+ return [...entries].sort((left, right) => left.localeCompare(right));
1219
+ }
1220
+ async function resolveEntrySource(input) {
1221
+ const overlay = await readOverlay(input.archiveKey, input.entryPath);
1222
+ if (overlay?.kind === "deleted") {
1223
+ return { kind: "deleted" };
1224
+ }
1225
+ if (overlay?.workspacePath !== void 0) {
1226
+ return { kind: "workspace", path: overlay.workspacePath };
1227
+ }
1228
+ return { kind: "archive" };
1229
+ }
1230
+ async function readOverlay(archiveKey, entryPath) {
1231
+ return await withStateDatabase(
1232
+ async (state) => await state.queryOne(
1233
+ "SELECT * FROM entry_overlays WHERE archive_key = ? AND entry_path = ?",
1234
+ [archiveKey, entryPath],
1235
+ mapEntryOverlay
1236
+ )
1237
+ );
1238
+ }
1239
+ async function listOverlays(archiveKey) {
1240
+ return await withStateDatabase(
1241
+ async (state) => await state.queryAll(
1242
+ `
1243
+ SELECT *
1244
+ FROM entry_overlays
1245
+ WHERE archive_key = ?
1246
+ ORDER BY entry_path ASC
1247
+ `,
1248
+ [archiveKey],
1249
+ mapEntryOverlay
1250
+ )
1251
+ );
1252
+ }
1253
+ async function upsertOverlay(input) {
1254
+ const mutationToken = input.entryPath === SEARCH_INDEX_DATABASE_ENTRY_PATH ? await readWikgArchiveMutationToken(input.archivePath) : void 0;
1255
+ await withStateDatabase(async (state) => {
1256
+ await state.run(
1257
+ `
1258
+ INSERT INTO entry_overlays (
1259
+ archive_key, archive_path, entry_path, kind, workspace_path,
1260
+ archive_signature, mutation_token, updated_at
1261
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1262
+ ON CONFLICT(archive_key, entry_path)
1263
+ DO UPDATE SET kind = excluded.kind,
1264
+ archive_path = excluded.archive_path,
1265
+ workspace_path = excluded.workspace_path,
1266
+ archive_signature = excluded.archive_signature,
1267
+ mutation_token = excluded.mutation_token,
1268
+ updated_at = excluded.updated_at
1269
+ `,
1270
+ [
1271
+ input.archiveKey,
1272
+ input.archivePath,
1273
+ input.entryPath,
1274
+ input.kind,
1275
+ input.workspacePath ?? null,
1276
+ await createArchiveSignature(input.archivePath),
1277
+ mutationToken ?? null,
1278
+ Date.now()
1279
+ ]
1280
+ );
1281
+ });
1282
+ }
1283
+ async function tryAdoptSearchIndexCacheOverlay(input) {
1284
+ const mutationToken = await readWikgArchiveMutationToken(
1285
+ input.targetArchivePath
1286
+ );
1287
+ const candidates = await listSearchIndexAdoptionCandidates({
1288
+ mutationToken,
1289
+ targetArchiveKey: input.targetArchiveKey
1290
+ });
1291
+ const adoptable = [];
1292
+ for (const candidate2 of candidates) {
1293
+ if (candidate2.workspacePath === void 0 || await pathExists(candidate2.archivePath) || await hasActiveWorkspaceUse(candidate2.archiveKey)) {
1294
+ continue;
1295
+ }
1296
+ adoptable.push(candidate2);
1297
+ }
1298
+ if (adoptable.length !== 1) {
1299
+ return;
1300
+ }
1301
+ const candidate = adoptable[0];
1302
+ const releaseWriteLock = await acquireEntryLock(
1303
+ candidate.archiveKey,
1304
+ SEARCH_INDEX_DATABASE_ENTRY_PATH,
1305
+ "write"
1306
+ );
1307
+ try {
1308
+ const releaseStateLock = await acquireEntryLock(
1309
+ candidate.archiveKey,
1310
+ SEARCH_INDEX_DATABASE_ENTRY_PATH,
1311
+ "state"
1312
+ );
1313
+ try {
1314
+ const current = await readOverlay(
1315
+ candidate.archiveKey,
1316
+ SEARCH_INDEX_DATABASE_ENTRY_PATH
1317
+ );
1318
+ const target = await readOverlay(
1319
+ input.targetArchiveKey,
1320
+ SEARCH_INDEX_DATABASE_ENTRY_PATH
1321
+ );
1322
+ if (current?.workspacePath === void 0 || current.workspacePath !== candidate.workspacePath || current.mutationToken !== mutationToken || target !== void 0 || await pathExists(candidate.archivePath) || await hasActiveArchiveOwnerOrSqliteLease(
1323
+ candidate.archiveKey,
1324
+ SEARCH_INDEX_DATABASE_ENTRY_PATH
1325
+ )) {
1326
+ return;
1327
+ }
1328
+ const targetWorkspacePath = await createWorkspaceFilePath(
1329
+ input.targetArchiveKey,
1330
+ SEARCH_INDEX_DATABASE_ENTRY_PATH
1331
+ );
1332
+ await rm(targetWorkspacePath, { force: true }).catch(() => void 0);
1333
+ await mkdir(dirname(targetWorkspacePath), { recursive: true });
1334
+ await rename(current.workspacePath, targetWorkspacePath);
1335
+ await withStateDatabase(async (state) => {
1336
+ await state.run(
1337
+ `
1338
+ UPDATE entry_overlays
1339
+ SET archive_key = ?,
1340
+ archive_path = ?,
1341
+ workspace_path = ?,
1342
+ archive_signature = ?,
1343
+ updated_at = ?
1344
+ WHERE archive_key = ?
1345
+ AND entry_path = ?
1346
+ `,
1347
+ [
1348
+ input.targetArchiveKey,
1349
+ input.targetArchivePath,
1350
+ targetWorkspacePath,
1351
+ await createArchiveSignature(input.targetArchivePath),
1352
+ Date.now(),
1353
+ candidate.archiveKey,
1354
+ SEARCH_INDEX_DATABASE_ENTRY_PATH
1355
+ ]
1356
+ );
1357
+ });
1358
+ } finally {
1359
+ await releaseStateLock();
1360
+ }
1361
+ } finally {
1362
+ await releaseWriteLock();
1363
+ }
1364
+ }
1365
+ async function listSearchIndexAdoptionCandidates(input) {
1366
+ return await withStateDatabase(async (state) => {
1367
+ await cleanupStaleState(state);
1368
+ return await state.queryAll(
1369
+ `
1370
+ SELECT *
1371
+ FROM entry_overlays
1372
+ WHERE entry_path = ?
1373
+ AND kind = 'file'
1374
+ AND workspace_path IS NOT NULL
1375
+ AND mutation_token = ?
1376
+ AND archive_key <> ?
1377
+ ORDER BY updated_at ASC
1378
+ `,
1379
+ [
1380
+ SEARCH_INDEX_DATABASE_ENTRY_PATH,
1381
+ input.mutationToken,
1382
+ input.targetArchiveKey
1383
+ ],
1384
+ mapEntryOverlay
1385
+ );
1386
+ });
1387
+ }
1388
+ async function deleteOverlay(archiveKey, entryPath) {
1389
+ await withStateDatabase(async (state) => {
1390
+ await state.run(
1391
+ "DELETE FROM entry_overlays WHERE archive_key = ? AND entry_path = ?",
1392
+ [archiveKey, entryPath]
1393
+ );
1394
+ });
1395
+ }
1396
+ async function resolveArchivePathFromKey(archiveKey) {
1397
+ return await withStateDatabase(
1398
+ async (state) => await state.queryOne(
1399
+ "SELECT archive_path FROM entry_overlays WHERE archive_key = ? LIMIT 1",
1400
+ [archiveKey],
1401
+ (row) => getString(row, "archive_path")
1402
+ )
1403
+ );
1404
+ }
1405
+ async function cleanupStaleState(state) {
1406
+ const now = Date.now();
1407
+ const staleOwnerPids = /* @__PURE__ */ new Set();
1408
+ for (const tableName of [
1409
+ "archive_owners",
1410
+ "entry_locks",
1411
+ "entry_sqlite_leases",
1412
+ "archive_commit_locks"
1413
+ ]) {
1414
+ const rows = await state.queryAll(
1415
+ `
1416
+ SELECT DISTINCT owner_pid
1417
+ FROM ${tableName}
1418
+ WHERE owner_pid IS NOT NULL
1419
+ AND heartbeat_at <= ?
1420
+ `,
1421
+ [now - LOCK_STALE_TIMEOUT_MS],
1422
+ (row) => getNumber(row, "owner_pid")
1423
+ );
1424
+ for (const ownerPid of rows) {
1425
+ if (!isProcessAlive(ownerPid)) {
1426
+ staleOwnerPids.add(ownerPid);
1427
+ }
1428
+ }
1429
+ }
1430
+ if (staleOwnerPids.size === 0) {
1431
+ return;
1432
+ }
1433
+ await state.run(
1434
+ `
1435
+ DELETE FROM archive_owners
1436
+ WHERE owner_pid IN (${createPlaceholders(staleOwnerPids.size)})
1437
+ `,
1438
+ [...staleOwnerPids]
1439
+ );
1440
+ await state.run(
1441
+ `
1442
+ DELETE FROM entry_locks
1443
+ WHERE owner_pid IN (${createPlaceholders(staleOwnerPids.size)})
1444
+ `,
1445
+ [...staleOwnerPids]
1446
+ );
1447
+ await state.run(
1448
+ `
1449
+ DELETE FROM entry_sqlite_leases
1450
+ WHERE owner_pid IN (${createPlaceholders(staleOwnerPids.size)})
1451
+ `,
1452
+ [...staleOwnerPids]
1453
+ );
1454
+ await state.run(
1455
+ `
1456
+ DELETE FROM archive_commit_locks
1457
+ WHERE owner_pid IN (${createPlaceholders(staleOwnerPids.size)})
1458
+ `,
1459
+ [...staleOwnerPids]
1460
+ );
1461
+ }
1462
+ function createPlaceholders(count) {
1463
+ return Array.from({ length: count }, () => "?").join(", ");
1464
+ }
1465
+ function isProcessAlive(pid) {
1466
+ try {
1467
+ process.kill(pid, 0);
1468
+ return true;
1469
+ } catch (error) {
1470
+ if (error instanceof Error && "code" in error && error.code === "ESRCH") {
1471
+ return false;
1472
+ }
1473
+ return true;
1474
+ }
1475
+ }
1476
+ async function withStateDatabase(operation) {
1477
+ return await STATE_DATABASE_SEMAPHORE.use(async () => {
1478
+ const state = await openStateDatabase();
1479
+ try {
1480
+ return await operation(state);
1481
+ } finally {
1482
+ await state.close();
1483
+ }
1484
+ });
1485
+ }
1486
+ async function openStateDatabase() {
1487
+ const database = await openSharedStateDatabase(
1488
+ join(getCoordinatorStateDirectoryPath(), "staging.sqlite"),
1489
+ STATE_SCHEMA_SQL
1490
+ );
1491
+ await migrateStateDatabase(database);
1492
+ return database;
1493
+ }
1494
+ async function migrateStateDatabase(database) {
1495
+ const overlayColumns = await database.queryAll(
1496
+ "PRAGMA table_info(entry_overlays)",
1497
+ void 0,
1498
+ (row) => getString(row, "name")
1499
+ );
1500
+ if (!overlayColumns.includes("archive_signature")) {
1501
+ await database.run(`
1502
+ ALTER TABLE entry_overlays
1503
+ ADD COLUMN archive_signature TEXT
1504
+ `);
1505
+ }
1506
+ if (!overlayColumns.includes("mutation_token")) {
1507
+ await database.run(`
1508
+ ALTER TABLE entry_overlays
1509
+ ADD COLUMN mutation_token TEXT
1510
+ `);
1511
+ }
1512
+ const sqliteLeaseColumns = await database.queryAll(
1513
+ "PRAGMA table_info(entry_sqlite_leases)",
1514
+ void 0,
1515
+ (row) => getString(row, "name")
1516
+ );
1517
+ if (!sqliteLeaseColumns.includes("mode")) {
1518
+ await database.run(`
1519
+ ALTER TABLE entry_sqlite_leases
1520
+ ADD COLUMN mode TEXT NOT NULL DEFAULT 'read'
1521
+ `);
1522
+ }
1523
+ }
1524
+ async function createWorkspaceFilePath(archiveKey, entryPath) {
1525
+ const directoryPath = join(
1526
+ getCoordinatorWorkspaceRootPath(),
1527
+ archiveKey,
1528
+ dirname(entryPath)
1529
+ );
1530
+ await mkdir(directoryPath, { recursive: true });
1531
+ return join(directoryPath, basename(entryPath));
1532
+ }
1533
+ function getCoordinatorWorkspaceRootPath() {
1534
+ return join(getCoordinatorStateDirectoryPath(), "work");
1535
+ }
1536
+ async function ensureEmptyDirectory(directoryPath) {
1537
+ await mkdir(directoryPath, { recursive: true });
1538
+ const entries = await readdir(directoryPath);
1539
+ if (entries.length > 0) {
1540
+ throw new Error(`Read workspace directory is not empty: ${directoryPath}`);
1541
+ }
1542
+ }
1543
+ async function listWorkspaceFiles(directoryPath) {
1544
+ let entries;
1545
+ try {
1546
+ entries = await readdir(directoryPath, { withFileTypes: true });
1547
+ } catch (error) {
1548
+ if (isNodeError(error) && error.code === "ENOENT") {
1549
+ return [];
1550
+ }
1551
+ throw error;
1552
+ }
1553
+ const files = [];
1554
+ for (const entry of entries) {
1555
+ const path = join(directoryPath, entry.name);
1556
+ if (entry.isDirectory()) {
1557
+ files.push(...await listWorkspaceFiles(path));
1558
+ } else if (entry.isFile()) {
1559
+ files.push(path);
1560
+ }
1561
+ }
1562
+ return files;
1563
+ }
1564
+ function toArchiveOverlay(overlay) {
1565
+ if (overlay.kind === "deleted") {
1566
+ return {
1567
+ entryPath: overlay.entryPath,
1568
+ kind: "deleted"
1569
+ };
1570
+ }
1571
+ if (overlay.workspacePath === void 0) {
1572
+ throw new Error(`Missing workspace path for ${overlay.entryPath}.`);
1573
+ }
1574
+ return {
1575
+ entryPath: overlay.entryPath,
1576
+ kind: "file",
1577
+ workspacePath: overlay.workspacePath
1578
+ };
1579
+ }
1580
+ function normalizeEntryPath(path) {
1581
+ return path.replaceAll("\\", "/").replace(/^\/+/u, "");
1582
+ }
1583
+ function normalizeEntryDirectoryPrefix(path) {
1584
+ const entryPath = normalizeEntryPath(path);
1585
+ return entryPath === "" || entryPath.endsWith("/") ? entryPath : `${entryPath}/`;
1586
+ }
1587
+ function getCoordinatorStateDirectoryPath() {
1588
+ return resolveWikiGraphStagingDirectoryPath();
1589
+ }
1590
+ function createArchiveKey(archivePath) {
1591
+ return createHash("sha256").update(resolve(archivePath)).digest("hex");
1592
+ }
1593
+ async function createArchiveSignature(archivePath) {
1594
+ const stats = await stat(archivePath);
1595
+ return `${stats.size}:${stats.mtimeMs}`;
1596
+ }
1597
+ function createOwnerId() {
1598
+ return `${process.pid}-${randomUUID()}`;
1599
+ }
1600
+ async function delay(ms) {
1601
+ await new Promise((resolveDelay) => {
1602
+ setTimeout(resolveDelay, ms);
1603
+ });
1604
+ }
1605
+ async function pathExists(path) {
1606
+ try {
1607
+ await stat(path);
1608
+ return true;
1609
+ } catch (error) {
1610
+ if (isNodeError(error) && error.code === "ENOENT") {
1611
+ return false;
1612
+ }
1613
+ throw error;
1614
+ }
1615
+ }
1616
+ function mapEntryOverlay(row) {
1617
+ const workspacePath = getOptionalString(row, "workspace_path");
1618
+ const archiveSignature = getOptionalString(row, "archive_signature");
1619
+ const mutationToken = getOptionalString(row, "mutation_token");
1620
+ return {
1621
+ archiveKey: getString(row, "archive_key"),
1622
+ archivePath: getString(row, "archive_path"),
1623
+ ...archiveSignature === void 0 ? {} : { archiveSignature },
1624
+ entryPath: getString(row, "entry_path"),
1625
+ kind: getOverlayKind(row),
1626
+ ...mutationToken === void 0 ? {} : { mutationToken },
1627
+ updatedAt: getNumber(row, "updated_at"),
1628
+ ...workspacePath === void 0 ? {} : { workspacePath }
1629
+ };
1630
+ }
1631
+ function mapEntryLock(row) {
1632
+ return {
1633
+ entryPath: getString(row, "entry_path"),
1634
+ mode: getEntryLockMode(row),
1635
+ ownerId: getString(row, "owner_id")
1636
+ };
1637
+ }
1638
+ function mapArchiveCommitLock(row) {
1639
+ return {
1640
+ ownerId: getString(row, "owner_id")
1641
+ };
1642
+ }
1643
+ function getEntryLockMode(row) {
1644
+ const mode = getString(row, "mode");
1645
+ if (mode === "read" || mode === "state" || mode === "write") {
1646
+ return mode;
1647
+ }
1648
+ throw new Error(`Unsupported entry lock mode: ${mode}.`);
1649
+ }
1650
+ function getOverlayKind(row) {
1651
+ const kind = getString(row, "kind");
1652
+ if (kind === "deleted" || kind === "file") {
1653
+ return kind;
1654
+ }
1655
+ throw new Error(`Unsupported entry overlay kind: ${kind}.`);
1656
+ }
1657
+ function getString(row, key) {
1658
+ const value = row[key];
1659
+ if (typeof value !== "string") {
1660
+ throw new TypeError(`Expected ${key} to be a string`);
1661
+ }
1662
+ return value;
1663
+ }
1664
+ function getNumber(row, key) {
1665
+ const value = row[key];
1666
+ if (typeof value !== "number") {
1667
+ throw new TypeError(`Expected ${key} to be a number`);
1668
+ }
1669
+ return value;
1670
+ }
1671
+ function getOptionalString(row, key) {
1672
+ const value = row[key];
1673
+ if (value === null || value === void 0) {
1674
+ return void 0;
1675
+ }
1676
+ if (typeof value !== "string") {
1677
+ throw new TypeError(`Expected ${key} to be a string`);
1678
+ }
1679
+ return value;
1680
+ }
1681
+ export {
1682
+ WikgArchiveSession,
1683
+ WikgCoordinator,
1684
+ runWikgCoordinatorGc,
1685
+ tryStartWikgFlusher
1686
+ };
1687
+ //# sourceMappingURL=wikg-coordinator.js.map