@lapis-notes/api 0.1.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 (400) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE.md +4 -0
  3. package/README.md +43 -0
  4. package/dist/__tests__/data-adapter-conformance.js +205 -0
  5. package/dist/__tests__/lapis-runtime-manifest.js +32 -0
  6. package/dist/__tests__/svelte-internal-client.d.ts +3 -0
  7. package/dist/agent-result-views.d.ts +52 -0
  8. package/dist/agent-result-views.js +89 -0
  9. package/dist/agent-skills.d.ts +99 -0
  10. package/dist/agent-skills.js +182 -0
  11. package/dist/agent-tools.d.ts +92 -0
  12. package/dist/agent-tools.js +139 -0
  13. package/dist/app-database-host.d.ts +4 -0
  14. package/dist/app-database-host.js +4 -0
  15. package/dist/app-shell-plugin-persistence.d.ts +15 -0
  16. package/dist/app-shell-plugin-persistence.js +34 -0
  17. package/dist/app-url.d.ts +54 -0
  18. package/dist/app-url.js +269 -0
  19. package/dist/application-compatibility.d.ts +31 -0
  20. package/dist/application-compatibility.js +64 -0
  21. package/dist/application-state.svelte.d.ts +14 -0
  22. package/dist/application-state.svelte.js +30 -0
  23. package/dist/assets/lapis-logo.svg +471 -0
  24. package/dist/bases.d.ts +281 -0
  25. package/dist/bases.js +475 -0
  26. package/dist/cache.svelte.d.ts +569 -0
  27. package/dist/cache.svelte.js +1433 -0
  28. package/dist/command.svelte.d.ts +337 -0
  29. package/dist/command.svelte.js +571 -0
  30. package/dist/compat.d.ts +29 -0
  31. package/dist/compat.js +30 -0
  32. package/dist/components/configuration/configuration.css +199 -0
  33. package/dist/components/configuration/date-setting.svelte +71 -0
  34. package/dist/components/configuration/date-setting.svelte.d.ts +8 -0
  35. package/dist/components/configuration/list-setting.svelte +128 -0
  36. package/dist/components/configuration/list-setting.svelte.d.ts +13 -0
  37. package/dist/components/configuration/object-array-row-grip.svelte +78 -0
  38. package/dist/components/configuration/object-array-row-grip.svelte.d.ts +9 -0
  39. package/dist/components/configuration/object-array-row.svelte +163 -0
  40. package/dist/components/configuration/object-array-row.svelte.d.ts +19 -0
  41. package/dist/components/configuration/object-array-setting.svelte +196 -0
  42. package/dist/components/configuration/object-array-setting.svelte.d.ts +15 -0
  43. package/dist/components/configuration/object-array-table-dnd.d.ts +8 -0
  44. package/dist/components/configuration/object-array-table-dnd.js +10 -0
  45. package/dist/components/configuration/object-array-utils.d.ts +11 -0
  46. package/dist/components/configuration/object-array-utils.js +103 -0
  47. package/dist/components/configuration/object-grid-setting.svelte +79 -0
  48. package/dist/components/configuration/object-grid-setting.svelte.d.ts +10 -0
  49. package/dist/components/configuration/object-map-row.svelte +147 -0
  50. package/dist/components/configuration/object-map-row.svelte.d.ts +15 -0
  51. package/dist/components/configuration/object-map-setting.svelte +137 -0
  52. package/dist/components/configuration/object-map-setting.svelte.d.ts +14 -0
  53. package/dist/components/configuration/object-map-types.d.ts +5 -0
  54. package/dist/components/configuration/object-map-types.js +1 -0
  55. package/dist/components/configuration/options-combobox-setting.svelte +129 -0
  56. package/dist/components/configuration/options-combobox-setting.svelte.d.ts +12 -0
  57. package/dist/components/copy-button/button.svelte +93 -0
  58. package/dist/components/copy-button/button.svelte.d.ts +13 -0
  59. package/dist/components/copy-button/index.d.ts +2 -0
  60. package/dist/components/copy-button/index.js +2 -0
  61. package/dist/components/editor/editor.css +565 -0
  62. package/dist/components/editor/editor.d.ts +33 -0
  63. package/dist/components/editor/editor.js +132 -0
  64. package/dist/components/editor/editor.svelte +176 -0
  65. package/dist/components/editor/editor.svelte.d.ts +15 -0
  66. package/dist/components/editor/embedded-editor-surface.d.ts +12 -0
  67. package/dist/components/editor/embedded-editor-surface.js +22 -0
  68. package/dist/components/editor/embedded-editor-surface.svelte +115 -0
  69. package/dist/components/editor/embedded-editor-surface.svelte.d.ts +17 -0
  70. package/dist/components/editor/extensions/autocomplete/index.d.ts +2 -0
  71. package/dist/components/editor/extensions/autocomplete/index.js +2 -0
  72. package/dist/components/editor/extensions/autocomplete/lapis-code-mirror-autocomplete.d.ts +16 -0
  73. package/dist/components/editor/extensions/autocomplete/lapis-code-mirror-autocomplete.js +34 -0
  74. package/dist/components/editor/extensions/autocomplete/vault-completion-sources.d.ts +27 -0
  75. package/dist/components/editor/extensions/autocomplete/vault-completion-sources.js +55 -0
  76. package/dist/components/editor/extensions/class-highlighter/index.d.ts +1 -0
  77. package/dist/components/editor/extensions/class-highlighter/index.js +47 -0
  78. package/dist/components/editor/extensions/fold-gutter/index.d.ts +8 -0
  79. package/dist/components/editor/extensions/fold-gutter/index.js +40 -0
  80. package/dist/components/editor/extensions/gutter-line-style-sync/index.d.ts +2 -0
  81. package/dist/components/editor/extensions/gutter-line-style-sync/index.js +118 -0
  82. package/dist/components/editor/extensions/lint/index.d.ts +4 -0
  83. package/dist/components/editor/extensions/lint/index.js +4 -0
  84. package/dist/components/editor/extensions/lint/lapis-code-mirror-lint.d.ts +26 -0
  85. package/dist/components/editor/extensions/lint/lapis-code-mirror-lint.js +101 -0
  86. package/dist/components/editor/extensions/lint/lapis-lint-diagnostic-helpers.d.ts +8 -0
  87. package/dist/components/editor/extensions/lint/lapis-lint-diagnostic-helpers.js +7 -0
  88. package/dist/components/editor/extensions/lint/lapis-lint-diagnostic.d.ts +20 -0
  89. package/dist/components/editor/extensions/lint/lapis-lint-diagnostic.js +47 -0
  90. package/dist/components/editor/extensions/lint/lapis-lint-hover-tooltip.d.ts +9 -0
  91. package/dist/components/editor/extensions/lint/lapis-lint-hover-tooltip.js +373 -0
  92. package/dist/components/editor/extensions/lint/lapis-lint-inline-widget.d.ts +31 -0
  93. package/dist/components/editor/extensions/lint/lapis-lint-inline-widget.js +191 -0
  94. package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte +176 -0
  95. package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte.d.ts +15 -0
  96. package/dist/components/editor/extensions/lint/lint-tooltip-actions.d.ts +11 -0
  97. package/dist/components/editor/extensions/lint/lint-tooltip-actions.js +21 -0
  98. package/dist/components/editor/extensions/lint/mount-lint-tooltip.d.ts +16 -0
  99. package/dist/components/editor/extensions/lint/mount-lint-tooltip.js +60 -0
  100. package/dist/components/editor/extensions/search/dom.d.ts +2 -0
  101. package/dist/components/editor/extensions/search/dom.js +6 -0
  102. package/dist/components/editor/extensions/search/index.d.ts +35 -0
  103. package/dist/components/editor/extensions/search/index.js +44 -0
  104. package/dist/components/editor/extensions/search/search.svelte +335 -0
  105. package/dist/components/editor/extensions/search/search.svelte.d.ts +8 -0
  106. package/dist/components/editor/index.d.ts +9 -0
  107. package/dist/components/editor/index.js +9 -0
  108. package/dist/components/editor/language-service/dom.d.ts +1 -0
  109. package/dist/components/editor/language-service/dom.js +6 -0
  110. package/dist/components/editor/language-service/index.d.ts +28 -0
  111. package/dist/components/editor/language-service/index.js +342 -0
  112. package/dist/components/empty-view/empty-view.css +39 -0
  113. package/dist/components/empty-view/empty-view.svelte +26 -0
  114. package/dist/components/empty-view/empty-view.svelte.d.ts +9 -0
  115. package/dist/components/empty-view/index.d.ts +2 -0
  116. package/dist/components/empty-view/index.js +2 -0
  117. package/dist/components/icon/icon-list.css +74 -0
  118. package/dist/components/icon/icon-list.svelte +213 -0
  119. package/dist/components/icon/icon-list.svelte.d.ts +11 -0
  120. package/dist/components/icon/icon.svelte +15 -0
  121. package/dist/components/icon/icon.svelte.d.ts +7 -0
  122. package/dist/components/icon/index.d.ts +3 -0
  123. package/dist/components/icon/index.js +3 -0
  124. package/dist/components/menu/context-menu.svelte +89 -0
  125. package/dist/components/menu/context-menu.svelte.d.ts +11 -0
  126. package/dist/components/menu/drawer-menu.svelte +83 -0
  127. package/dist/components/menu/drawer-menu.svelte.d.ts +8 -0
  128. package/dist/components/menu/dropdown-menu.svelte +89 -0
  129. package/dist/components/menu/dropdown-menu.svelte.d.ts +11 -0
  130. package/dist/components/menu/index.d.ts +6 -0
  131. package/dist/components/menu/index.js +6 -0
  132. package/dist/components/menu/menu.css +139 -0
  133. package/dist/components/menu/menu.svelte +37 -0
  134. package/dist/components/menu/menu.svelte.d.ts +11 -0
  135. package/dist/components/menu/mobile-menu-query.svelte.d.ts +3 -0
  136. package/dist/components/menu/mobile-menu-query.svelte.js +21 -0
  137. package/dist/components/menu/popover-menu.svelte +90 -0
  138. package/dist/components/menu/popover-menu.svelte.d.ts +10 -0
  139. package/dist/components/select/index.d.ts +2 -0
  140. package/dist/components/select/index.js +2 -0
  141. package/dist/components/select/select.svelte +72 -0
  142. package/dist/components/select/select.svelte.d.ts +25 -0
  143. package/dist/configuration-option-source-providers.d.ts +17 -0
  144. package/dist/configuration-option-source-providers.js +59 -0
  145. package/dist/configuration-option-source-registry.d.ts +59 -0
  146. package/dist/configuration-option-source-registry.js +123 -0
  147. package/dist/configuration.svelte.d.ts +258 -0
  148. package/dist/configuration.svelte.js +887 -0
  149. package/dist/context-keys.svelte.d.ts +42 -0
  150. package/dist/context-keys.svelte.js +278 -0
  151. package/dist/context.svelte.d.ts +247 -0
  152. package/dist/context.svelte.js +540 -0
  153. package/dist/daily-document-provider.d.ts +38 -0
  154. package/dist/daily-document-provider.js +176 -0
  155. package/dist/design-core-button-types.d.ts +7 -0
  156. package/dist/design-core-button-types.js +1 -0
  157. package/dist/diagnostics.d.ts +93 -0
  158. package/dist/diagnostics.js +84 -0
  159. package/dist/dialog-portal.d.ts +3 -0
  160. package/dist/dialog-portal.js +3 -0
  161. package/dist/docs-api.d.ts +44 -0
  162. package/dist/docs-api.js +29 -0
  163. package/dist/editor-view-registry.d.ts +35 -0
  164. package/dist/editor-view-registry.js +112 -0
  165. package/dist/editor.svelte.d.ts +126 -0
  166. package/dist/editor.svelte.js +454 -0
  167. package/dist/embed-registry.d.ts +15 -0
  168. package/dist/embed-registry.js +16 -0
  169. package/dist/enhance.d.ts +111 -0
  170. package/dist/enhance.js +309 -0
  171. package/dist/events.d.ts +28 -0
  172. package/dist/events.js +49 -0
  173. package/dist/file-manager.d.ts +90 -0
  174. package/dist/file-manager.js +289 -0
  175. package/dist/file-tools/app-tools.d.ts +3 -0
  176. package/dist/file-tools/app-tools.js +242 -0
  177. package/dist/file-tools/index.d.ts +3 -0
  178. package/dist/file-tools/index.js +3 -0
  179. package/dist/file-tools/paths.d.ts +14 -0
  180. package/dist/file-tools/paths.js +33 -0
  181. package/dist/file-tools/vault-operations.d.ts +3 -0
  182. package/dist/file-tools/vault-operations.js +60 -0
  183. package/dist/generated/plugin-host-modules.schema.generated.d.ts +371 -0
  184. package/dist/generated/plugin-host-modules.schema.generated.js +374 -0
  185. package/dist/glob.d.ts +15 -0
  186. package/dist/glob.js +68 -0
  187. package/dist/history.svelte.d.ts +24 -0
  188. package/dist/history.svelte.js +74 -0
  189. package/dist/hooks/createVirtualizer.svelte.d.ts +7 -0
  190. package/dist/hooks/createVirtualizer.svelte.js +70 -0
  191. package/dist/hooks/index.d.ts +5 -0
  192. package/dist/hooks/index.js +5 -0
  193. package/dist/hooks/is-mobile.svelte.d.ts +5 -0
  194. package/dist/hooks/is-mobile.svelte.js +23 -0
  195. package/dist/hooks/mountComponent.svelte.d.ts +7 -0
  196. package/dist/hooks/mountComponent.svelte.js +16 -0
  197. package/dist/hooks/use-text-highlight.d.ts +17 -0
  198. package/dist/hooks/use-text-highlight.js +82 -0
  199. package/dist/hooks/useBoundingRect.svelte.d.ts +11 -0
  200. package/dist/hooks/useBoundingRect.svelte.js +46 -0
  201. package/dist/icons/index.d.ts +57 -0
  202. package/dist/icons/index.js +288 -0
  203. package/dist/index.d.ts +87 -0
  204. package/dist/index.js +87 -0
  205. package/dist/language-service/index.d.ts +9 -0
  206. package/dist/language-service/index.js +7 -0
  207. package/dist/language-service/manager.d.ts +47 -0
  208. package/dist/language-service/manager.js +404 -0
  209. package/dist/language-service/types.d.ts +92 -0
  210. package/dist/language-service/types.js +1 -0
  211. package/dist/language-service/virtual-document.d.ts +11 -0
  212. package/dist/language-service/virtual-document.js +45 -0
  213. package/dist/language-service/worker-protocol.d.ts +63 -0
  214. package/dist/language-service/worker-protocol.js +1 -0
  215. package/dist/language-service/worker-provider.d.ts +9 -0
  216. package/dist/language-service/worker-provider.js +38 -0
  217. package/dist/lapis-extension.d.ts +244 -0
  218. package/dist/lapis-extension.js +591 -0
  219. package/dist/links.d.ts +34 -0
  220. package/dist/links.js +191 -0
  221. package/dist/loaders.d.ts +9 -0
  222. package/dist/loaders.js +59 -0
  223. package/dist/localization-manager.svelte.d.ts +56 -0
  224. package/dist/localization-manager.svelte.js +157 -0
  225. package/dist/logging.d.ts +172 -0
  226. package/dist/logging.js +352 -0
  227. package/dist/markdown-extension-registry.d.ts +31 -0
  228. package/dist/markdown-extension-registry.js +31 -0
  229. package/dist/markdown-file-surface-registry.d.ts +25 -0
  230. package/dist/markdown-file-surface-registry.js +23 -0
  231. package/dist/markdown.d.ts +205 -0
  232. package/dist/markdown.js +147 -0
  233. package/dist/menu-utils.d.ts +1 -0
  234. package/dist/menu-utils.js +47 -0
  235. package/dist/menu.svelte.d.ts +121 -0
  236. package/dist/menu.svelte.js +295 -0
  237. package/dist/metadata-value.d.ts +10 -0
  238. package/dist/metadata-value.js +173 -0
  239. package/dist/metadata.svelte.d.ts +106 -0
  240. package/dist/metadata.svelte.js +608 -0
  241. package/dist/notifications.d.ts +209 -0
  242. package/dist/notifications.js +338 -0
  243. package/dist/open-file.d.ts +23 -0
  244. package/dist/open-file.js +102 -0
  245. package/dist/platform.d.ts +86 -0
  246. package/dist/platform.js +86 -0
  247. package/dist/plugin-asset-server.d.ts +60 -0
  248. package/dist/plugin-asset-server.js +222 -0
  249. package/dist/plugin-assets.d.ts +4 -0
  250. package/dist/plugin-assets.js +3 -0
  251. package/dist/plugin-capability-facade.d.ts +104 -0
  252. package/dist/plugin-capability-facade.js +61 -0
  253. package/dist/plugin-dependency-resolver.d.ts +36 -0
  254. package/dist/plugin-dependency-resolver.js +54 -0
  255. package/dist/plugin-dependency-scanner.d.ts +9 -0
  256. package/dist/plugin-dependency-scanner.js +274 -0
  257. package/dist/plugin-distribution/canonical-json.d.ts +1 -0
  258. package/dist/plugin-distribution/canonical-json.js +40 -0
  259. package/dist/plugin-distribution/compatibility.d.ts +5 -0
  260. package/dist/plugin-distribution/compatibility.js +38 -0
  261. package/dist/plugin-distribution/errors.d.ts +11 -0
  262. package/dist/plugin-distribution/errors.js +13 -0
  263. package/dist/plugin-distribution/hashes.d.ts +2 -0
  264. package/dist/plugin-distribution/hashes.js +17 -0
  265. package/dist/plugin-distribution/index.d.ts +18 -0
  266. package/dist/plugin-distribution/index.js +18 -0
  267. package/dist/plugin-distribution/installed-plugin-state.d.ts +20 -0
  268. package/dist/plugin-distribution/installed-plugin-state.js +73 -0
  269. package/dist/plugin-distribution/installer.d.ts +89 -0
  270. package/dist/plugin-distribution/installer.js +395 -0
  271. package/dist/plugin-distribution/manager.d.ts +128 -0
  272. package/dist/plugin-distribution/manager.js +358 -0
  273. package/dist/plugin-distribution/path-safety.d.ts +2 -0
  274. package/dist/plugin-distribution/path-safety.js +20 -0
  275. package/dist/plugin-distribution/plugin-bundle-verifier.d.ts +28 -0
  276. package/dist/plugin-distribution/plugin-bundle-verifier.js +96 -0
  277. package/dist/plugin-distribution/plugin-bundle-worker.d.ts +34 -0
  278. package/dist/plugin-distribution/plugin-bundle-worker.js +99 -0
  279. package/dist/plugin-distribution/plugin-bundle.d.ts +16 -0
  280. package/dist/plugin-distribution/plugin-bundle.js +244 -0
  281. package/dist/plugin-distribution/plugin-bundle.worker.d.ts +1 -0
  282. package/dist/plugin-distribution/plugin-bundle.worker.js +38 -0
  283. package/dist/plugin-distribution/registry-cache.d.ts +26 -0
  284. package/dist/plugin-distribution/registry-cache.js +85 -0
  285. package/dist/plugin-distribution/registry-client.d.ts +36 -0
  286. package/dist/plugin-distribution/registry-client.js +115 -0
  287. package/dist/plugin-distribution/reserved-ids.d.ts +4 -0
  288. package/dist/plugin-distribution/reserved-ids.js +10 -0
  289. package/dist/plugin-distribution/runtime-validation.d.ts +14 -0
  290. package/dist/plugin-distribution/runtime-validation.js +388 -0
  291. package/dist/plugin-distribution/schemas.d.ts +4895 -0
  292. package/dist/plugin-distribution/schemas.js +299 -0
  293. package/dist/plugin-distribution/signing.d.ts +5 -0
  294. package/dist/plugin-distribution/signing.js +38 -0
  295. package/dist/plugin-distribution/types.d.ts +217 -0
  296. package/dist/plugin-distribution/types.js +1 -0
  297. package/dist/plugin-install-progress.d.ts +15 -0
  298. package/dist/plugin-install-progress.js +86 -0
  299. package/dist/plugin-manager.d.ts +306 -0
  300. package/dist/plugin-manager.js +2848 -0
  301. package/dist/plugin-runtime-entry.d.ts +19 -0
  302. package/dist/plugin-runtime-entry.js +135 -0
  303. package/dist/plugin.d.ts +350 -0
  304. package/dist/plugin.js +754 -0
  305. package/dist/popover.d.ts +20 -0
  306. package/dist/popover.js +32 -0
  307. package/dist/promise-worker.d.ts +22 -0
  308. package/dist/promise-worker.js +65 -0
  309. package/dist/prompt-confirm.d.ts +9 -0
  310. package/dist/prompt-confirm.js +33 -0
  311. package/dist/search-document-provider.d.ts +45 -0
  312. package/dist/search-document-provider.js +54 -0
  313. package/dist/search-query/index.d.ts +78 -0
  314. package/dist/search-query/index.js +426 -0
  315. package/dist/search-query/parser.d.ts +3 -0
  316. package/dist/search-query/parser.js +16 -0
  317. package/dist/search-query/parser.terms.js +26 -0
  318. package/dist/search-query/search-query.grammar +128 -0
  319. package/dist/search.d.ts +19 -0
  320. package/dist/search.js +104 -0
  321. package/dist/setting-section-layout.d.ts +19 -0
  322. package/dist/setting-section-layout.js +194 -0
  323. package/dist/settings.svelte.d.ts +474 -0
  324. package/dist/settings.svelte.js +1421 -0
  325. package/dist/source-text-file-view.d.ts +25 -0
  326. package/dist/source-text-file-view.js +78 -0
  327. package/dist/status-bar.svelte.d.ts +29 -0
  328. package/dist/status-bar.svelte.js +72 -0
  329. package/dist/storage/app-database.d.ts +860 -0
  330. package/dist/storage/app-database.js +2372 -0
  331. package/dist/storage/blob-store.d.ts +12 -0
  332. package/dist/storage/blob-store.js +1 -0
  333. package/dist/storage/browser-app-database-coordination.d.ts +35 -0
  334. package/dist/storage/browser-app-database-coordination.js +207 -0
  335. package/dist/storage/browser-coordinated-app-database.d.ts +109 -0
  336. package/dist/storage/browser-coordinated-app-database.js +704 -0
  337. package/dist/storage/browser.d.ts +153 -0
  338. package/dist/storage/browser.js +794 -0
  339. package/dist/storage/desktop-native.d.ts +184 -0
  340. package/dist/storage/desktop-native.js +415 -0
  341. package/dist/storage/fs.d.ts +188 -0
  342. package/dist/storage/fs.js +307 -0
  343. package/dist/storage/index-projection.d.ts +236 -0
  344. package/dist/storage/index-projection.js +402 -0
  345. package/dist/storage/index.d.ts +17 -0
  346. package/dist/storage/index.js +17 -0
  347. package/dist/storage/keyval.d.ts +94 -0
  348. package/dist/storage/keyval.js +199 -0
  349. package/dist/storage/memory-vault-adapter.d.ts +44 -0
  350. package/dist/storage/memory-vault-adapter.js +351 -0
  351. package/dist/storage/native-vault-bootstrap-kv.d.ts +22 -0
  352. package/dist/storage/native-vault-bootstrap-kv.js +96 -0
  353. package/dist/storage/path.d.ts +22 -0
  354. package/dist/storage/path.js +110 -0
  355. package/dist/storage/search-embedding-provider.d.ts +75 -0
  356. package/dist/storage/search-embedding-provider.js +446 -0
  357. package/dist/storage/search-embedding-worker.d.ts +1 -0
  358. package/dist/storage/search-embedding-worker.js +63 -0
  359. package/dist/storage/search-query-evaluator.d.ts +14 -0
  360. package/dist/storage/search-query-evaluator.js +678 -0
  361. package/dist/storage/task-projection.d.ts +152 -0
  362. package/dist/storage/task-projection.js +154 -0
  363. package/dist/storage/turso-app-database.d.ts +150 -0
  364. package/dist/storage/turso-app-database.js +2671 -0
  365. package/dist/storage/vault-session.d.ts +37 -0
  366. package/dist/storage/vault-session.js +196 -0
  367. package/dist/storage/vault-state.d.ts +74 -0
  368. package/dist/storage/vault-state.js +181 -0
  369. package/dist/storage/vault.svelte.d.ts +257 -0
  370. package/dist/storage/vault.svelte.js +661 -0
  371. package/dist/suggest.d.ts +193 -0
  372. package/dist/suggest.js +391 -0
  373. package/dist/tasks.d.ts +7 -0
  374. package/dist/tasks.js +18 -0
  375. package/dist/telemetry.d.ts +116 -0
  376. package/dist/telemetry.js +103 -0
  377. package/dist/utils.d.ts +55 -0
  378. package/dist/utils.js +314 -0
  379. package/dist/vault-api.d.ts +5 -0
  380. package/dist/vault-api.js +3 -0
  381. package/dist/vault-maintenance-commands.d.ts +5 -0
  382. package/dist/vault-maintenance-commands.js +32 -0
  383. package/dist/view.svelte.d.ts +215 -0
  384. package/dist/view.svelte.js +381 -0
  385. package/dist/vite-env.d.ts +2 -0
  386. package/dist/workspace-host-internal.d.ts +7 -0
  387. package/dist/workspace-host-internal.js +7 -0
  388. package/dist/workspace-host.d.ts +10 -0
  389. package/dist/workspace-host.js +14 -0
  390. package/dist/workspace-layout-commands.d.ts +3 -0
  391. package/dist/workspace-layout-commands.js +72 -0
  392. package/dist/workspace-layout-normalizer.d.ts +92 -0
  393. package/dist/workspace-layout-normalizer.js +301 -0
  394. package/dist/workspace-layouts.d.ts +7 -0
  395. package/dist/workspace-layouts.js +23 -0
  396. package/dist/workspace-trust.d.ts +32 -0
  397. package/dist/workspace-trust.js +72 -0
  398. package/dist/workspace.svelte.d.ts +796 -0
  399. package/dist/workspace.svelte.js +4380 -0
  400. package/package.json +240 -0
@@ -0,0 +1,2372 @@
1
+ import { canCollectSearchQueryPropertyNames, canCollectSearchQueryTerms, collectSearchQueryPropertyNames, collectSearchQueryTerms, parseSearchQueryAst, } from "../search-query";
2
+ import { cosineSimilarity, createSearchEmbeddingProvider, } from "./search-embedding-provider";
3
+ import { evaluateSearchQueryForDocument, findSearchQueryRangesInText, isStructuredSearchQuery, } from "./search-query-evaluator";
4
+ import { assertProjectionReadAccess, assertProjectionWriteAccess, evaluateProjectionQuery, indexedValuesForRow, MAX_PROJECTION_ROWS_PER_SOURCE, projectionIndexStatus, PUBLIC_TASKS_PROJECTION_ID, sourceIsCurrent, } from "./index-projection";
5
+ import { taskQueryToIndexQuery, TASK_PROJECTION_FIELDS, TASK_PROJECTION_VERSION, } from "./task-projection";
6
+ export function supportsSearchQueryEnhancementRuntime(runtimeTarget) {
7
+ return runtimeTarget === "deno-desktop";
8
+ }
9
+ export const EMPTY_APP_DATABASE_CAPABILITIES = {
10
+ nativeFullTextSearch: false,
11
+ vectorSearch: false,
12
+ approximateNearestNeighbors: false,
13
+ localEmbeddings: false,
14
+ crossTabCoordination: false,
15
+ sync: false,
16
+ };
17
+ const SEARCH_FALLBACK_BREAK_PATTERNS = [
18
+ [/\n#{1}(?!#)/g, 100, "h1"],
19
+ [/\n#{2}(?!#)/g, 90, "h2"],
20
+ [/\n#{3}(?!#)/g, 80, "h3"],
21
+ [/\n#{4}(?!#)/g, 70, "h4"],
22
+ [/\n#{5}(?!#)/g, 60, "h5"],
23
+ [/\n#{6}(?!#)/g, 50, "h6"],
24
+ [/\n```/g, 80, "code"],
25
+ [/\n(?:---|\*\*\*|___)\s*\n/g, 60, "hr"],
26
+ [/\n\s*\n+/g, 20, "blank"],
27
+ [/\n[-*]\s/g, 5, "list"],
28
+ [/\n\d+\.\s/g, 5, "numlist"],
29
+ [/\n/g, 1, "newline"],
30
+ ];
31
+ function processSearchTags(tags) {
32
+ const normalizedTags = [...new Set(tags)]
33
+ .filter((tag) => tag.trim().length)
34
+ .map((tag) => tag.replace(/^#/, ""));
35
+ const tagParts = [];
36
+ const tagHierarchy = [];
37
+ for (const tag of normalizedTags) {
38
+ const parts = tag.split("/").filter((part) => part.trim().length);
39
+ tagParts.push(...parts);
40
+ for (let index = 1; index <= parts.length; index += 1) {
41
+ const hierarchyLevel = parts.slice(0, index).join("/");
42
+ if (!tagHierarchy.includes(hierarchyLevel)) {
43
+ tagHierarchy.push(hierarchyLevel);
44
+ }
45
+ }
46
+ }
47
+ return {
48
+ tags: normalizedTags,
49
+ tagParts: [...new Set(tagParts)],
50
+ tagHierarchy,
51
+ };
52
+ }
53
+ function normalizeChunkKind(kind) {
54
+ switch (kind) {
55
+ case "yaml":
56
+ return "frontmatter";
57
+ case "heading":
58
+ case "paragraph":
59
+ case "list":
60
+ case "blockquote":
61
+ case "code":
62
+ return kind;
63
+ case "callout":
64
+ return "blockquote";
65
+ default:
66
+ return "fallback";
67
+ }
68
+ }
69
+ function headingScore(level) {
70
+ return Math.max(50, 110 - level * 10);
71
+ }
72
+ function sectionBreakpoint(section, heading) {
73
+ switch (section.type) {
74
+ case "heading": {
75
+ const level = heading?.level ?? 1;
76
+ return {
77
+ pos: section.position.start.offset,
78
+ score: headingScore(level),
79
+ kind: `h${level}`,
80
+ };
81
+ }
82
+ case "code":
83
+ return { pos: section.position.start.offset, score: 80, kind: "code" };
84
+ case "thematicBreak":
85
+ return { pos: section.position.start.offset, score: 60, kind: "hr" };
86
+ case "list":
87
+ return { pos: section.position.start.offset, score: 5, kind: "list" };
88
+ case "blockquote":
89
+ case "callout":
90
+ return {
91
+ pos: section.position.start.offset,
92
+ score: 10,
93
+ kind: "blockquote",
94
+ };
95
+ case "paragraph":
96
+ case "text":
97
+ return { pos: section.position.start.offset, score: 20, kind: "blank" };
98
+ default:
99
+ return {
100
+ pos: section.position.start.offset,
101
+ score: 1,
102
+ kind: section.type,
103
+ };
104
+ }
105
+ }
106
+ function sectionKindAtOffset(metadata, offset) {
107
+ const section = metadata.sections.find((entry) => entry.position.start.offset <= offset &&
108
+ entry.position.end.offset >= offset);
109
+ return normalizeChunkKind(section?.type);
110
+ }
111
+ function trimChunkBounds(content, start, end) {
112
+ let nextStart = Math.max(0, start);
113
+ let nextEnd = Math.min(content.length, end);
114
+ while (nextStart < nextEnd && /\s/.test(content[nextStart] ?? "")) {
115
+ nextStart += 1;
116
+ }
117
+ while (nextEnd > nextStart && /\s/.test(content[nextEnd - 1] ?? "")) {
118
+ nextEnd -= 1;
119
+ }
120
+ return { start: nextStart, end: nextEnd };
121
+ }
122
+ function nearestHeading(metadata, offset) {
123
+ let currentHeading;
124
+ for (const heading of [...metadata.headings].sort((left, right) => left.position.start.offset - right.position.start.offset)) {
125
+ if (heading.position.start.offset > offset) {
126
+ break;
127
+ }
128
+ currentHeading = heading.heading;
129
+ }
130
+ return currentHeading;
131
+ }
132
+ function mergeBreakPoints(points) {
133
+ const merged = new Map();
134
+ for (const point of points) {
135
+ const existing = merged.get(point.pos);
136
+ if (!existing || point.score > existing.score) {
137
+ merged.set(point.pos, point);
138
+ }
139
+ }
140
+ return [...merged.values()].sort((left, right) => left.pos - right.pos);
141
+ }
142
+ function buildFallbackBreakPoints(content, startOffset) {
143
+ const text = content.slice(startOffset);
144
+ const points = [];
145
+ for (const [pattern, score, kind] of SEARCH_FALLBACK_BREAK_PATTERNS) {
146
+ for (const match of text.matchAll(pattern)) {
147
+ points.push({
148
+ pos: startOffset + (match.index ?? 0),
149
+ score,
150
+ kind,
151
+ });
152
+ }
153
+ }
154
+ return mergeBreakPoints(points);
155
+ }
156
+ function findCodeFenceRegions(content, metadata, startOffset) {
157
+ const sectionFences = metadata.sections
158
+ .filter((section) => section.type === "code")
159
+ .map((section) => ({
160
+ start: Math.max(startOffset, section.position.start.offset),
161
+ end: section.position.end.offset,
162
+ }));
163
+ if (sectionFences.length) {
164
+ return sectionFences;
165
+ }
166
+ const matches = [...content.matchAll(/(^|\n)```/gm)];
167
+ const fences = [];
168
+ for (let index = 0; index < matches.length; index += 2) {
169
+ const start = matches[index]?.index;
170
+ if (start === undefined || start < startOffset) {
171
+ continue;
172
+ }
173
+ const end = matches[index + 1]?.index ?? content.length;
174
+ fences.push({ start, end });
175
+ }
176
+ return fences;
177
+ }
178
+ function isInsideCodeFence(pos, fences) {
179
+ return fences.some((fence) => pos > fence.start && pos < fence.end);
180
+ }
181
+ function findBestCutoff(breakPoints, chunkStart, targetCharPos, windowChars, decayFactor, codeFences) {
182
+ const windowStart = Math.max(chunkStart + 1, targetCharPos - windowChars);
183
+ let bestPos = targetCharPos;
184
+ let bestScore = -1;
185
+ for (const point of breakPoints) {
186
+ if (point.pos < windowStart) {
187
+ continue;
188
+ }
189
+ if (point.pos > targetCharPos) {
190
+ break;
191
+ }
192
+ if (point.pos <= chunkStart) {
193
+ continue;
194
+ }
195
+ if (isInsideCodeFence(point.pos, codeFences)) {
196
+ continue;
197
+ }
198
+ const distance = targetCharPos - point.pos;
199
+ const normalizedDistance = distance / windowChars;
200
+ const multiplier = 1 - normalizedDistance * normalizedDistance * decayFactor;
201
+ const finalScore = point.score * multiplier;
202
+ if (finalScore > bestScore ||
203
+ (finalScore === bestScore && point.pos > bestPos)) {
204
+ bestScore = finalScore;
205
+ bestPos = point.pos;
206
+ }
207
+ }
208
+ return bestPos;
209
+ }
210
+ function buildSectionBreakPoints(metadata, frontmatterEndOffset) {
211
+ const headingsByOffset = new Map(metadata.headings.map((heading) => [
212
+ heading.position.start.offset,
213
+ heading,
214
+ ]));
215
+ return mergeBreakPoints(metadata.sections
216
+ .filter((section) => section.position.start.offset > frontmatterEndOffset)
217
+ .map((section) => sectionBreakpoint(section, headingsByOffset.get(section.position.start.offset))));
218
+ }
219
+ function createChunk(path, content, metadata, startOffset, endOffset, kind) {
220
+ const trimmed = trimChunkBounds(content, startOffset, endOffset);
221
+ if (trimmed.end <= trimmed.start) {
222
+ return null;
223
+ }
224
+ return {
225
+ id: `${path}#chunk-${trimmed.start}-${trimmed.end}`,
226
+ text: content.slice(trimmed.start, trimmed.end),
227
+ startOffset: trimmed.start,
228
+ endOffset: trimmed.end,
229
+ heading: nearestHeading(metadata, trimmed.start),
230
+ kind: normalizeChunkKind(kind ?? sectionKindAtOffset(metadata, trimmed.start)),
231
+ };
232
+ }
233
+ function buildSearchChunks(path, content, metadata) {
234
+ const frontmatterEndOffset = metadata.frontmatterEndOffset ?? 0;
235
+ const breakPoints = mergeBreakPoints([
236
+ ...buildSectionBreakPoints(metadata, frontmatterEndOffset),
237
+ ...buildFallbackBreakPoints(content, frontmatterEndOffset),
238
+ ]);
239
+ const codeFences = findCodeFenceRegions(content, metadata, frontmatterEndOffset);
240
+ const chunks = [];
241
+ let chunkStart = frontmatterEndOffset;
242
+ while (chunkStart < content.length) {
243
+ const remaining = content.length - chunkStart;
244
+ const targetEnd = Math.min(chunkStart + metadata.chunking.targetChars, content.length);
245
+ const chunkEnd = remaining > metadata.chunking.targetChars
246
+ ? findBestCutoff(breakPoints, chunkStart, targetEnd, metadata.chunking.breakpointWindowChars, metadata.chunking.breakpointDecay, codeFences)
247
+ : content.length;
248
+ const safeEnd = chunkEnd > chunkStart ? chunkEnd : targetEnd;
249
+ const chunk = createChunk(path, content, metadata, chunkStart, safeEnd);
250
+ if (!chunk) {
251
+ if (safeEnd <= chunkStart) {
252
+ break;
253
+ }
254
+ chunkStart = safeEnd;
255
+ continue;
256
+ }
257
+ chunks.push(chunk);
258
+ chunkStart = Math.max(safeEnd, chunk.endOffset);
259
+ }
260
+ return chunks;
261
+ }
262
+ export function normalizeSearchDocument(document) {
263
+ const { sourceMetadata, ...baseDocument } = document;
264
+ if (!sourceMetadata) {
265
+ return clone(baseDocument);
266
+ }
267
+ const tags = processSearchTags(sourceMetadata.rawTags ?? document.tags);
268
+ return {
269
+ ...clone(baseDocument),
270
+ sourceMetadata: clone(sourceMetadata),
271
+ tags: tags.tags,
272
+ tagParts: tags.tagParts,
273
+ tagHierarchy: tags.tagHierarchy,
274
+ metadataText: sourceMetadata.frontmatter === undefined
275
+ ? (document.metadataText ?? "")
276
+ : JSON.stringify(sourceMetadata.frontmatter ?? {}),
277
+ chunks: buildSearchChunks(document.path, document.content, sourceMetadata),
278
+ };
279
+ }
280
+ const NOTIFICATIONS_META_KEY = "notifications.records";
281
+ function memoryRecallSignalKey(record) {
282
+ return JSON.stringify([
283
+ record.targetRef,
284
+ record.queryFingerprint,
285
+ record.day,
286
+ ]);
287
+ }
288
+ export const APP_DATABASE_SCHEMA_VERSION = 6;
289
+ function clone(value) {
290
+ if (value === undefined || value === null)
291
+ return value;
292
+ return JSON.parse(JSON.stringify(value));
293
+ }
294
+ function normalizeIndexedMetadataExtension(extension) {
295
+ return extension.replace(/^\.+/, "").trim().toLowerCase();
296
+ }
297
+ function projectIndexedMetadataRow(row, include) {
298
+ if (include === undefined)
299
+ return row;
300
+ const selected = new Set(include);
301
+ return {
302
+ file: row.file,
303
+ metadata: selected.has("metadata") ? row.metadata : null,
304
+ properties: selected.has("properties") ? row.properties : [],
305
+ tags: selected.has("tags") ? row.tags : [],
306
+ links: selected.has("links") ? row.links : [],
307
+ };
308
+ }
309
+ function normalizeIndexedMetadataPathPrefix(prefix) {
310
+ return prefix.trim().replace(/^\/+/, "").replace(/\/+$/, "");
311
+ }
312
+ function matchesIndexedMetadataPathPrefix(path, prefix) {
313
+ return (prefix.length === 0 || path === prefix || path.startsWith(`${prefix}/`));
314
+ }
315
+ function indexedMetadataTagCandidates(tag) {
316
+ const normalized = tag.trim();
317
+ if (!normalized.length) {
318
+ return [];
319
+ }
320
+ return normalized.startsWith("#")
321
+ ? [normalized, normalized.slice(1)]
322
+ : [normalized, `#${normalized}`];
323
+ }
324
+ function toIndexedMetadataScalar(value) {
325
+ if (value === null) {
326
+ return null;
327
+ }
328
+ if (typeof value === "string" ||
329
+ typeof value === "number" ||
330
+ typeof value === "boolean") {
331
+ return value;
332
+ }
333
+ return undefined;
334
+ }
335
+ function flattenIndexedMetadataPropertyValues(value, path, output = []) {
336
+ if (Array.isArray(value)) {
337
+ value.forEach((entry, index) => flattenIndexedMetadataPropertyValues(entry, `${path}[${index}]`, output));
338
+ return output;
339
+ }
340
+ if (value && typeof value === "object") {
341
+ for (const [key, entry] of Object.entries(value)) {
342
+ flattenIndexedMetadataPropertyValues(entry, `${path}.${key}`, output);
343
+ }
344
+ return output;
345
+ }
346
+ const scalar = toIndexedMetadataScalar(value);
347
+ if (scalar !== undefined)
348
+ output.push({ path, value: scalar });
349
+ return output;
350
+ }
351
+ function normalizeIndexedMetadataPropertyPath(path) {
352
+ return path.toLowerCase().replace(/\[\d+\]/gu, "[]");
353
+ }
354
+ function indexedMetadataPropertyValues(properties, name) {
355
+ const normalizedName = normalizeIndexedMetadataPropertyPath(name);
356
+ const values = [];
357
+ for (const property of properties) {
358
+ if (normalizeIndexedMetadataPropertyPath(property.name) === normalizedName) {
359
+ const scalar = toIndexedMetadataScalar(property.value);
360
+ if (scalar !== undefined)
361
+ values.push(scalar);
362
+ values.push(...flattenIndexedMetadataPropertyValues(property.value, property.name).map((entry) => entry.value));
363
+ continue;
364
+ }
365
+ values.push(...flattenIndexedMetadataPropertyValues(property.value, property.name)
366
+ .filter((entry) => normalizeIndexedMetadataPropertyPath(entry.path) === normalizedName)
367
+ .map((entry) => entry.value));
368
+ }
369
+ return values;
370
+ }
371
+ function compareIndexedMetadataScalars(left, right) {
372
+ if (left === right) {
373
+ return 0;
374
+ }
375
+ if (left === undefined || left === null) {
376
+ return 1;
377
+ }
378
+ if (right === undefined || right === null) {
379
+ return -1;
380
+ }
381
+ if (typeof left === "number" && typeof right === "number") {
382
+ return left - right;
383
+ }
384
+ if (typeof left === "boolean" && typeof right === "boolean") {
385
+ return Number(left) - Number(right);
386
+ }
387
+ return String(left).localeCompare(String(right));
388
+ }
389
+ function matchesIndexedMetadataPropertyFilter(properties, filter) {
390
+ const property = properties.find((entry) => entry.name.toLowerCase() === filter.name.toLowerCase());
391
+ const values = indexedMetadataPropertyValues(properties, filter.name);
392
+ if (filter.op === "exists") {
393
+ return !!property || values.length > 0;
394
+ }
395
+ if (filter.op === "not-exists") {
396
+ return !property && values.length === 0;
397
+ }
398
+ if (!property && values.length === 0) {
399
+ return false;
400
+ }
401
+ const right = filter.value;
402
+ return values.some((left) => {
403
+ const comparison = compareIndexedMetadataScalars(left, right);
404
+ switch (filter.op) {
405
+ case "=":
406
+ return left === right;
407
+ case "!=":
408
+ return left !== right;
409
+ case ">":
410
+ return comparison > 0;
411
+ case ">=":
412
+ return comparison >= 0;
413
+ case "<":
414
+ return comparison < 0;
415
+ case "<=":
416
+ return comparison <= 0;
417
+ default:
418
+ return false;
419
+ }
420
+ });
421
+ }
422
+ function indexedMetadataSortValue(row, sort) {
423
+ if (sort.field.kind === "file") {
424
+ switch (sort.field.field) {
425
+ case "path":
426
+ return row.file.path;
427
+ case "extension":
428
+ return row.file.extension;
429
+ case "mtime":
430
+ return row.file.mtime;
431
+ case "size":
432
+ return row.file.size;
433
+ }
434
+ }
435
+ if (sort.field.kind !== "property") {
436
+ return undefined;
437
+ }
438
+ const propertyName = sort.field.name;
439
+ return toIndexedMetadataScalar(row.properties.find((entry) => entry.name === propertyName)?.value);
440
+ }
441
+ function createAppDatabaseStableId(prefix) {
442
+ if (typeof crypto !== "undefined" &&
443
+ typeof crypto.randomUUID === "function") {
444
+ return `${prefix}-${crypto.randomUUID()}`;
445
+ }
446
+ return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
447
+ }
448
+ function hashAppDatabaseText(value) {
449
+ let hash = 0;
450
+ for (let index = 0; index < value.length; index += 1) {
451
+ hash = (hash << 5) - hash + value.charCodeAt(index);
452
+ hash |= 0;
453
+ }
454
+ return `text-${(hash >>> 0).toString(16)}`;
455
+ }
456
+ function shouldDeduplicateFileHistoryEvent(eventType) {
457
+ return (eventType === "baseline" || eventType === "create" || eventType === "modify");
458
+ }
459
+ function notebookStateMetaKey(sourcePath) {
460
+ return `notebook.state:${sourcePath}`;
461
+ }
462
+ export function searchTerms(query) {
463
+ const ast = parseSearchQueryAst(query);
464
+ if (canCollectSearchQueryTerms(ast)) {
465
+ return collectSearchQueryTerms(ast);
466
+ }
467
+ return query
468
+ .toLowerCase()
469
+ .split(/\s+/)
470
+ .map((part) => part.trim())
471
+ .filter(Boolean);
472
+ }
473
+ export function searchPropertyNames(query) {
474
+ const ast = parseSearchQueryAst(query);
475
+ if (!canCollectSearchQueryPropertyNames(ast)) {
476
+ return [];
477
+ }
478
+ return collectSearchQueryPropertyNames(ast);
479
+ }
480
+ function rootSearchPropertyName(name) {
481
+ return (name.replace(/\[\]/gu, "").split(".")[0]?.toLowerCase() ??
482
+ name.toLowerCase());
483
+ }
484
+ export function hasSearchPropertyNames(properties, requiredNames) {
485
+ if (!requiredNames.length) {
486
+ return true;
487
+ }
488
+ const availableNames = new Set(properties.map((property) => property.name.toLowerCase()));
489
+ return requiredNames.every((name) => {
490
+ const normalizedName = name.toLowerCase();
491
+ return (availableNames.has(normalizedName) ||
492
+ availableNames.has(rootSearchPropertyName(normalizedName)));
493
+ });
494
+ }
495
+ export function searchDocumentProperties(document, indexedProperties) {
496
+ if (!document.metadataText)
497
+ return indexedProperties;
498
+ try {
499
+ const metadata = JSON.parse(document.metadataText);
500
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
501
+ return indexedProperties;
502
+ }
503
+ const properties = new Map(indexedProperties.map((property) => [
504
+ property.name.toLowerCase(),
505
+ property,
506
+ ]));
507
+ for (const [name, value] of Object.entries(metadata)) {
508
+ properties.set(name.toLowerCase(), {
509
+ path: document.path,
510
+ name,
511
+ inferredType: Array.isArray(value) ? "array" : typeof value,
512
+ value,
513
+ });
514
+ }
515
+ return [...properties.values()];
516
+ }
517
+ catch {
518
+ return indexedProperties;
519
+ }
520
+ }
521
+ function searchTextTerms(query) {
522
+ const propertyNames = new Set(searchPropertyNames(query));
523
+ return searchTerms(query).filter((term) => !propertyNames.has(term));
524
+ }
525
+ export function mergeSearchRanges(ranges) {
526
+ const sorted = [...ranges].sort((a, b) => a.start - b.start);
527
+ const merged = [];
528
+ for (const range of sorted) {
529
+ const previous = merged[merged.length - 1];
530
+ if (previous && range.start <= previous.end) {
531
+ previous.end = Math.max(previous.end, range.end);
532
+ }
533
+ else {
534
+ merged.push({ ...range });
535
+ }
536
+ }
537
+ return merged;
538
+ }
539
+ export function findSearchRanges(text, query, options = {}) {
540
+ return findSearchQueryRangesInText(text, query, options);
541
+ }
542
+ function clampRange(range, start, end) {
543
+ const next = {
544
+ start: Math.max(0, range.start - start),
545
+ end: Math.min(end - start, range.end - start),
546
+ };
547
+ if (next.end <= next.start)
548
+ return null;
549
+ return next;
550
+ }
551
+ function renameChunkId(chunkId, oldPath, newPath) {
552
+ if (!chunkId.startsWith(`${oldPath}#`)) {
553
+ return chunkId;
554
+ }
555
+ return `${newPath}${chunkId.slice(oldPath.length)}`;
556
+ }
557
+ function buildFieldSnippet(field, text, ranges, snippetLength) {
558
+ if (!ranges.length || !text.length)
559
+ return [];
560
+ if (text.length <= snippetLength || field === "name" || field === "path") {
561
+ return [
562
+ {
563
+ field,
564
+ text,
565
+ ranges,
566
+ offset: 0,
567
+ },
568
+ ];
569
+ }
570
+ const focus = ranges[0];
571
+ const padding = Math.max(16, Math.floor(snippetLength / 3));
572
+ const start = Math.max(0, focus.start - padding);
573
+ const end = Math.min(text.length, Math.max(start + snippetLength, focus.end + padding));
574
+ return [
575
+ {
576
+ field,
577
+ text: text.slice(start, end),
578
+ ranges: ranges
579
+ .map((range) => clampRange(range, start, end))
580
+ .filter((range) => Boolean(range)),
581
+ offset: start,
582
+ },
583
+ ];
584
+ }
585
+ function buildContentSnippets(text, ranges, snippetLength) {
586
+ if (!ranges.length || !text.length)
587
+ return [];
588
+ const grouped = new Map();
589
+ for (const range of ranges) {
590
+ const lineStart = text.lastIndexOf("\n", Math.max(0, range.start - 1)) + 1;
591
+ const lineEndIndex = text.indexOf("\n", range.end);
592
+ const lineEnd = lineEndIndex === -1 ? text.length : lineEndIndex;
593
+ const key = `${lineStart}:${lineEnd}`;
594
+ const snippet = grouped.get(key) ?? {
595
+ field: "content",
596
+ text: text.slice(lineStart, lineEnd),
597
+ ranges: [],
598
+ offset: lineStart,
599
+ };
600
+ const nextRange = clampRange(range, lineStart, lineEnd);
601
+ if (nextRange) {
602
+ snippet.ranges.push(nextRange);
603
+ }
604
+ grouped.set(key, snippet);
605
+ }
606
+ return [...grouped.values()]
607
+ .sort((a, b) => a.offset - b.offset)
608
+ .slice(0, 8)
609
+ .map((snippet) => {
610
+ const ranges = mergeSearchRanges(snippet.ranges);
611
+ if (snippet.text.length <= snippetLength) {
612
+ return { ...snippet, ranges };
613
+ }
614
+ const focus = ranges[0];
615
+ const start = Math.max(0, focus.start - Math.floor(snippetLength / 3));
616
+ const end = Math.min(snippet.text.length, Math.max(start + snippetLength, focus.end + Math.floor(snippetLength / 3)));
617
+ return {
618
+ field: "content",
619
+ text: snippet.text.slice(start, end),
620
+ ranges: ranges
621
+ .map((range) => clampRange(range, start, end))
622
+ .filter((range) => Boolean(range)),
623
+ offset: snippet.offset + start,
624
+ };
625
+ });
626
+ }
627
+ function buildChunkContentSnippets(chunks, query, snippetLength, options = {}) {
628
+ return chunks
629
+ .flatMap((chunk) => buildContentSnippets(chunk.text, findSearchRanges(chunk.text, query, {
630
+ caseSensitive: options.caseSensitive,
631
+ field: "content",
632
+ }), snippetLength).map((snippet) => ({
633
+ ...snippet,
634
+ offset: chunk.startOffset + snippet.offset,
635
+ chunkId: chunk.id,
636
+ chunkLabel: chunk.heading,
637
+ })))
638
+ .sort((a, b) => a.offset - b.offset)
639
+ .slice(0, 8);
640
+ }
641
+ function buildChunkPreviewSnippets(document, chunkIds, snippetLength) {
642
+ const byId = new Map((document.chunks ?? []).map((chunk) => [chunk.id, chunk]));
643
+ return chunkIds
644
+ .map((chunkId) => byId.get(chunkId))
645
+ .filter((chunk) => Boolean(chunk))
646
+ .slice(0, 3)
647
+ .map((chunk) => ({
648
+ field: "content",
649
+ text: chunk.text.slice(0, snippetLength),
650
+ ranges: [],
651
+ offset: chunk.startOffset,
652
+ chunkId: chunk.id,
653
+ chunkLabel: chunk.heading,
654
+ }));
655
+ }
656
+ export function buildSearchSnippets(document, query, options = {}) {
657
+ const snippetLength = options.snippetLength ?? 160;
658
+ return [
659
+ ...buildFieldSnippet("name", document.name, findSearchRanges(document.name, query, {
660
+ caseSensitive: options.caseSensitive,
661
+ field: "name",
662
+ }), snippetLength),
663
+ ...buildFieldSnippet("path", document.path, findSearchRanges(document.path, query, {
664
+ caseSensitive: options.caseSensitive,
665
+ field: "path",
666
+ }), snippetLength),
667
+ ...buildFieldSnippet("tags", document.tags.map((tag) => `#${tag}`).join(" "), findSearchRanges(document.tags.map((tag) => `#${tag}`).join(" "), query, {
668
+ caseSensitive: options.caseSensitive,
669
+ field: "tags",
670
+ }), snippetLength),
671
+ ...(document.chunks?.length
672
+ ? buildChunkContentSnippets(document.chunks, query, snippetLength, options)
673
+ : buildContentSnippets(document.content, findSearchRanges(document.content, query, {
674
+ caseSensitive: options.caseSensitive,
675
+ field: "content",
676
+ }), snippetLength)),
677
+ ...buildFieldSnippet("metadata", document.metadataText ?? "", findSearchRanges(document.metadataText ?? "", query, {
678
+ caseSensitive: options.caseSensitive,
679
+ field: "metadata",
680
+ }), snippetLength),
681
+ ];
682
+ }
683
+ export function scoreSearchDocument(document, query, properties = [], options = {}) {
684
+ const evaluation = evaluateSearchQueryForDocument(document, query, properties, options);
685
+ if (isStructuredSearchQuery(query) || options.caseSensitive) {
686
+ return evaluation.matched ? evaluation.score : 0;
687
+ }
688
+ const propertyNames = searchPropertyNames(query);
689
+ if (!hasSearchPropertyNames(properties, propertyNames)) {
690
+ return 0;
691
+ }
692
+ const parts = searchTextTerms(query);
693
+ let score = propertyNames.length ? propertyNames.length * 25 : 0;
694
+ if (!parts.length)
695
+ return score;
696
+ const haystacks = [
697
+ document.path,
698
+ document.name,
699
+ document.chunks?.map((chunk) => chunk.text).join("\n") || document.content,
700
+ document.tags.join(" "),
701
+ document.tagParts.join(" "),
702
+ document.tagHierarchy.join(" "),
703
+ document.metadataText ?? "",
704
+ ].map((value) => value.toLowerCase());
705
+ for (const part of parts) {
706
+ const matched = haystacks.some((value, index) => {
707
+ const hit = value.includes(part);
708
+ if (hit)
709
+ score += index <= 1 ? 25 : 10;
710
+ return hit;
711
+ });
712
+ if (!matched)
713
+ return 0;
714
+ }
715
+ return score;
716
+ }
717
+ export const MIN_VECTOR_SEARCH_SCORE = 0.2;
718
+ export const SEARCH_RRF_K = 60;
719
+ export function scoreVectorDocument(document, queryVector) {
720
+ const rankedChunks = (document.chunks ?? [])
721
+ .map((chunk) => {
722
+ const vector = chunk.embedding?.vector ?? [];
723
+ return {
724
+ chunkId: chunk.id,
725
+ score: chunk.embedding?.status === "ready"
726
+ ? cosineSimilarity(vector, queryVector)
727
+ : 0,
728
+ };
729
+ })
730
+ .filter((entry) => entry.score >= MIN_VECTOR_SEARCH_SCORE)
731
+ .sort((left, right) => right.score - left.score)
732
+ .slice(0, 3);
733
+ return {
734
+ score: rankedChunks[0]?.score ?? 0,
735
+ matchedChunkIds: rankedChunks.map((entry) => entry.chunkId),
736
+ };
737
+ }
738
+ function reciprocalRankContribution(rank) {
739
+ if (!rank || rank <= 0 || !Number.isFinite(rank)) {
740
+ return 0;
741
+ }
742
+ return 1 / (SEARCH_RRF_K + rank);
743
+ }
744
+ function buildSearchScoreFusion(lexicalScore, vectorScore, appliedMode, ranks = {}) {
745
+ if (appliedMode === "vector") {
746
+ const score = vectorScore * 100;
747
+ return {
748
+ score,
749
+ fusion: {
750
+ algorithm: "vector-score",
751
+ vectorRank: ranks.vectorRank,
752
+ vectorContribution: score,
753
+ },
754
+ };
755
+ }
756
+ if (appliedMode === "hybrid") {
757
+ const lexicalRank = lexicalScore > 0 ? (ranks.lexicalRank ?? 1) : undefined;
758
+ const vectorRank = vectorScore >= MIN_VECTOR_SEARCH_SCORE
759
+ ? (ranks.vectorRank ?? 1)
760
+ : undefined;
761
+ const lexicalContribution = reciprocalRankContribution(lexicalRank);
762
+ const vectorContribution = reciprocalRankContribution(vectorRank);
763
+ return {
764
+ score: lexicalContribution + vectorContribution,
765
+ fusion: {
766
+ algorithm: "reciprocal-rank-fusion",
767
+ k: SEARCH_RRF_K,
768
+ lexicalRank,
769
+ vectorRank,
770
+ lexicalContribution,
771
+ vectorContribution,
772
+ },
773
+ };
774
+ }
775
+ return {
776
+ score: lexicalScore,
777
+ fusion: {
778
+ algorithm: "lexical-score",
779
+ lexicalRank: ranks.lexicalRank,
780
+ lexicalContribution: lexicalScore,
781
+ },
782
+ };
783
+ }
784
+ export function rankSearchScores(entries, isCandidate = (score) => score > 0) {
785
+ return new Map(entries
786
+ .filter((entry) => Number.isFinite(entry.score) && isCandidate(entry.score))
787
+ .sort((left, right) => right.score - left.score || left.path.localeCompare(right.path))
788
+ .map((entry, index) => [entry.path, index + 1]));
789
+ }
790
+ export function compareSearchResults(left, right) {
791
+ return (right.score - left.score ||
792
+ (right.scoreBreakdown.lexical ?? 0) - (left.scoreBreakdown.lexical ?? 0) ||
793
+ (right.scoreBreakdown.vector ?? 0) - (left.scoreBreakdown.vector ?? 0) ||
794
+ left.document.path.localeCompare(right.document.path));
795
+ }
796
+ export function pathWithinPrefix(path, prefix) {
797
+ return !prefix || path === prefix || path.startsWith(`${prefix}/`);
798
+ }
799
+ const STRONG_QUERY_ENHANCEMENT_LEXICAL_SCORE = 25;
800
+ const STRONG_QUERY_ENHANCEMENT_RRF_SCORE = 1 / (SEARCH_RRF_K + 1) + 1 / (SEARCH_RRF_K + 2);
801
+ function buildSearchQueryEnhancementSignal(results, requestedMode) {
802
+ return {
803
+ requestedMode,
804
+ topLexicalScore: Math.max(0, ...results.map((result) => result.scoreBreakdown.lexical ?? 0)),
805
+ topVectorScore: Math.max(0, ...results.map((result) => result.scoreBreakdown.vector ?? 0)),
806
+ topFusedScore: results[0]?.scoreBreakdown.fused ?? 0,
807
+ resultCount: results.length,
808
+ };
809
+ }
810
+ function shouldSkipSearchQueryEnhancement(results) {
811
+ const topResult = results[0];
812
+ if (!topResult) {
813
+ return false;
814
+ }
815
+ if (topResult.retrievalMode === "lexical" &&
816
+ (topResult.scoreBreakdown.lexical ?? 0) >=
817
+ STRONG_QUERY_ENHANCEMENT_LEXICAL_SCORE) {
818
+ return true;
819
+ }
820
+ return (topResult.retrievalMode === "hybrid" &&
821
+ (topResult.scoreBreakdown.fused ?? 0) >= STRONG_QUERY_ENHANCEMENT_RRF_SCORE);
822
+ }
823
+ export function resolveSearchQueryEnhancementDiagnostics(results, options) {
824
+ const request = options.queryEnhancement;
825
+ if (!request || (!request.expandQuery && !request.rerankResults)) {
826
+ return undefined;
827
+ }
828
+ const signal = buildSearchQueryEnhancementSignal(results, options.mode ?? "auto");
829
+ if (!supportsSearchQueryEnhancementRuntime(request.runtimeTarget)) {
830
+ return {
831
+ runtimeTarget: request.runtimeTarget,
832
+ providerKind: request.provider.kind,
833
+ expandQuery: request.expandQuery,
834
+ rerankResults: request.rerankResults,
835
+ applied: false,
836
+ skipReason: "unsupported-runtime",
837
+ signal,
838
+ };
839
+ }
840
+ if (shouldSkipSearchQueryEnhancement(results)) {
841
+ return {
842
+ runtimeTarget: request.runtimeTarget,
843
+ providerKind: request.provider.kind,
844
+ expandQuery: request.expandQuery,
845
+ rerankResults: request.rerankResults,
846
+ applied: false,
847
+ skipReason: "strong-signal",
848
+ signal,
849
+ };
850
+ }
851
+ return {
852
+ runtimeTarget: request.runtimeTarget,
853
+ providerKind: request.provider.kind,
854
+ expandQuery: request.expandQuery,
855
+ rerankResults: request.rerankResults,
856
+ applied: false,
857
+ skipReason: "provider-unavailable",
858
+ signal,
859
+ };
860
+ }
861
+ function matchedChunkIds(snippets) {
862
+ return [...new Set(snippets.flatMap((snippet) => snippet.chunkId ?? []))];
863
+ }
864
+ function firstEmbeddingModel(document) {
865
+ return document.chunks
866
+ ?.map((chunk) => chunk.embedding)
867
+ .find((embedding) => Boolean(embedding?.modelId));
868
+ }
869
+ export function embeddingErrorState(document, provider, error) {
870
+ const message = error instanceof Error ? error.message : String(error);
871
+ const timestamp = Date.now();
872
+ return {
873
+ ...clone(document),
874
+ chunks: (document.chunks ?? []).map((chunk) => ({
875
+ ...clone(chunk),
876
+ embedding: {
877
+ ...clone(chunk.embedding),
878
+ status: "error",
879
+ modelId: provider.config.modelId ?? "lapis/token-hash-v0",
880
+ modelVersion: provider.config.modelVersion,
881
+ dimensions: provider.config.dimensions,
882
+ dirty: true,
883
+ updatedAt: timestamp,
884
+ error: message,
885
+ },
886
+ })),
887
+ };
888
+ }
889
+ export function buildSearchResult(document, query, options, context) {
890
+ const snippetLength = options.snippetLength ?? 160;
891
+ const lexicalScore = context.lexicalScore ?? scoreSearchDocument(document, query, [], options);
892
+ const vectorScore = context.vectorScore ?? 0;
893
+ const appliedMode = context.appliedMode ?? "lexical";
894
+ const snippets = buildSearchSnippets(document, query, options);
895
+ if (!snippets.length && lexicalScore > 0) {
896
+ snippets.push({
897
+ field: "name",
898
+ text: document.name,
899
+ ranges: [],
900
+ offset: 0,
901
+ });
902
+ }
903
+ if (!snippets.length && context.preferredChunkIds?.length) {
904
+ snippets.push(...buildChunkPreviewSnippets(document, context.preferredChunkIds, snippetLength));
905
+ }
906
+ const { score, fusion } = buildSearchScoreFusion(lexicalScore, vectorScore, appliedMode, {
907
+ lexicalRank: context.lexicalRank,
908
+ vectorRank: context.vectorRank,
909
+ });
910
+ const matchedChunks = [
911
+ ...new Set([
912
+ ...matchedChunkIds(snippets),
913
+ ...(context.preferredChunkIds ?? []),
914
+ ]),
915
+ ];
916
+ const embedding = firstEmbeddingModel(document);
917
+ return {
918
+ document,
919
+ score,
920
+ snippets,
921
+ retrievalMode: appliedMode,
922
+ scoreBreakdown: {
923
+ lexical: lexicalScore,
924
+ vector: vectorScore || undefined,
925
+ fused: score,
926
+ fusion,
927
+ },
928
+ matchedChunkIds: matchedChunks,
929
+ diagnostics: options.includeDiagnostics
930
+ ? {
931
+ requestedMode: options.mode ?? "auto",
932
+ appliedMode,
933
+ backendKind: context.backendKind,
934
+ lexicalCandidateCount: context.lexicalCandidateCount,
935
+ vectorCandidateCount: context.vectorCandidateCount,
936
+ totalCandidateCount: (context.lexicalCandidateCount ?? 0) +
937
+ (context.vectorCandidateCount ?? 0),
938
+ providerKind: context.providerConfig?.kind,
939
+ modelId: embedding?.modelId ?? context.providerConfig?.modelId,
940
+ modelReady: embedding?.status === "ready" || Boolean(context.providerConfig),
941
+ }
942
+ : undefined,
943
+ };
944
+ }
945
+ export class MemoryAppDatabase {
946
+ vaultId;
947
+ kind = "memory";
948
+ get descriptor() {
949
+ return {
950
+ providerId: "memory-test",
951
+ engine: "memory",
952
+ transport: "memory",
953
+ role: "test",
954
+ storageMode: "local",
955
+ capabilities: {
956
+ ...EMPTY_APP_DATABASE_CAPABILITIES,
957
+ localEmbeddings: true,
958
+ },
959
+ };
960
+ }
961
+ meta = {};
962
+ metadataSnapshot = null;
963
+ searchEmbeddingProviderConfig = null;
964
+ searchEmbeddingProvider = null;
965
+ searchIndexStatsByPath = new Map();
966
+ searchIndexStats = {
967
+ documentCount: 0,
968
+ chunkCount: 0,
969
+ readyChunkCount: 0,
970
+ pendingChunkCount: 0,
971
+ errorChunkCount: 0,
972
+ lastError: null,
973
+ };
974
+ searchIndexLastErrorPath = null;
975
+ historyFiles = new Map();
976
+ historyFileIdsByPath = new Map();
977
+ historyRevisions = new Map();
978
+ files = new Map();
979
+ metadata = new Map();
980
+ links = new Map();
981
+ tags = new Map();
982
+ properties = new Map();
983
+ searchDocs = new Map();
984
+ tasks = new Map();
985
+ projections = new Map();
986
+ projectionSources = new Map();
987
+ projectionRows = new Map();
988
+ projectionValues = [];
989
+ projectionEdges = [];
990
+ projectionRevision = 0;
991
+ memorySources = new Map();
992
+ memoryCandidates = new Map();
993
+ memoryCandidateOrigins = new Map();
994
+ memoryRecallSignals = new Map();
995
+ memoryJobs = new Map();
996
+ changeRevision = 0;
997
+ changeListeners = new Set();
998
+ constructor(vaultId) {
999
+ this.vaultId = vaultId;
1000
+ }
1001
+ async open() {
1002
+ await this.migrate();
1003
+ }
1004
+ async migrate() {
1005
+ this.meta["schema.version"] = APP_DATABASE_SCHEMA_VERSION;
1006
+ }
1007
+ async close() {
1008
+ await this.searchEmbeddingProvider?.dispose?.();
1009
+ this.searchEmbeddingProvider = null;
1010
+ }
1011
+ async beginSearchIndexingBatch() { }
1012
+ async endSearchIndexingBatch() { }
1013
+ async configureSearchEmbeddingProvider(provider) {
1014
+ const previousProvider = clone(this.searchEmbeddingProviderConfig);
1015
+ const nextProvider = clone(provider);
1016
+ const providerChanged = JSON.stringify(previousProvider) !== JSON.stringify(nextProvider);
1017
+ if (!providerChanged) {
1018
+ return;
1019
+ }
1020
+ const previousRuntime = this.searchEmbeddingProvider;
1021
+ this.searchEmbeddingProviderConfig = nextProvider;
1022
+ this.searchEmbeddingProvider = createSearchEmbeddingProvider(nextProvider);
1023
+ await previousRuntime?.dispose?.();
1024
+ if (this.searchDocs.size === 0) {
1025
+ this.emitChange(["search"]);
1026
+ return;
1027
+ }
1028
+ this.searchDocs = new Map([...this.searchDocs.values()].map((document) => {
1029
+ const rebound = this.rebindSearchDocumentForProvider(document);
1030
+ return [rebound.path, rebound];
1031
+ }));
1032
+ this.rebuildSearchIndexStats();
1033
+ this.emitChange(["search"]);
1034
+ }
1035
+ async getSearchEmbeddingProvider() {
1036
+ return clone(this.searchEmbeddingProviderConfig);
1037
+ }
1038
+ async getSearchEmbeddingRuntimeStatus() {
1039
+ return this.searchEmbeddingProvider
1040
+ ? clone(this.searchEmbeddingProvider.getRuntimeStatus())
1041
+ : null;
1042
+ }
1043
+ async getSearchIndexStats() {
1044
+ return clone(this.searchIndexStats);
1045
+ }
1046
+ async getMeta(key) {
1047
+ return clone(this.meta[key]);
1048
+ }
1049
+ async setMeta(key, value) {
1050
+ this.meta[key] = clone(value);
1051
+ this.emitChange(["meta"]);
1052
+ }
1053
+ async getNotebookState(sourcePath) {
1054
+ return this.getMeta(notebookStateMetaKey(sourcePath));
1055
+ }
1056
+ async setNotebookState(sourcePath, state) {
1057
+ this.meta[notebookStateMetaKey(sourcePath)] = clone(state);
1058
+ this.emitChange(["notebook"], [sourcePath]);
1059
+ }
1060
+ async deleteNotebookState(sourcePath) {
1061
+ delete this.meta[notebookStateMetaKey(sourcePath)];
1062
+ this.emitChange(["notebook"], [sourcePath]);
1063
+ }
1064
+ async loadMetadataSnapshot() {
1065
+ return clone(this.metadataSnapshot);
1066
+ }
1067
+ async saveMetadataSnapshot(snapshot) {
1068
+ this.metadataSnapshot = clone(snapshot);
1069
+ this.emitChange(["metadata"]);
1070
+ }
1071
+ async getFileHistory(path) {
1072
+ const fileId = this.historyFileIdsByPath.get(path);
1073
+ if (!fileId) {
1074
+ return null;
1075
+ }
1076
+ const file = this.historyFiles.get(fileId);
1077
+ if (!file) {
1078
+ return null;
1079
+ }
1080
+ return clone({
1081
+ file,
1082
+ revisions: this.historyRevisions.get(fileId) ?? [],
1083
+ });
1084
+ }
1085
+ async storeFileHistoryRevision(input) {
1086
+ const existingFile = this.resolveFileHistoryFile(input.path, input.previousPath);
1087
+ const file = existingFile ?? {
1088
+ fileId: createAppDatabaseStableId("history-file"),
1089
+ currentPath: input.path,
1090
+ deleted: false,
1091
+ };
1092
+ const latestRevision = this.historyRevisions.get(file.fileId)?.at(-1);
1093
+ const content = input.content ?? latestRevision?.content ?? "";
1094
+ const contentHash = input.contentHash ??
1095
+ latestRevision?.contentHash ??
1096
+ hashAppDatabaseText(content);
1097
+ const deduplicated = shouldDeduplicateFileHistoryEvent(input.eventType) &&
1098
+ !file.deleted &&
1099
+ latestRevision?.contentHash === contentHash;
1100
+ if (deduplicated) {
1101
+ return {
1102
+ fileId: file.fileId,
1103
+ stored: false,
1104
+ deduplicated: true,
1105
+ };
1106
+ }
1107
+ if (input.previousPath && input.previousPath !== input.path) {
1108
+ this.historyFileIdsByPath.delete(input.previousPath);
1109
+ }
1110
+ const nextFile = {
1111
+ fileId: file.fileId,
1112
+ currentPath: input.path,
1113
+ deleted: input.eventType === "delete",
1114
+ };
1115
+ this.historyFiles.set(nextFile.fileId, nextFile);
1116
+ this.historyFileIdsByPath.set(nextFile.currentPath, nextFile.fileId);
1117
+ const previousRevisions = this.historyRevisions.get(nextFile.fileId) ?? [];
1118
+ const revision = input.replaceLatest && latestRevision
1119
+ ? {
1120
+ ...latestRevision,
1121
+ currentPath: nextFile.currentPath,
1122
+ capturedPath: input.eventType === "rename" && input.previousPath
1123
+ ? input.previousPath
1124
+ : input.path,
1125
+ eventType: input.eventType,
1126
+ createdAt: input.createdAt,
1127
+ sourceMtime: input.sourceMtime,
1128
+ sourceSize: input.sourceSize,
1129
+ contentHash,
1130
+ content,
1131
+ }
1132
+ : {
1133
+ revisionId: createAppDatabaseStableId("history-revision"),
1134
+ fileId: nextFile.fileId,
1135
+ currentPath: nextFile.currentPath,
1136
+ capturedPath: input.eventType === "rename" && input.previousPath
1137
+ ? input.previousPath
1138
+ : input.path,
1139
+ eventType: input.eventType,
1140
+ createdAt: input.createdAt,
1141
+ sourceMtime: input.sourceMtime,
1142
+ sourceSize: input.sourceSize,
1143
+ contentHash,
1144
+ content,
1145
+ };
1146
+ const maxRevisions = input.maxRevisions ?? Number.POSITIVE_INFINITY;
1147
+ const nextRevisions = (input.replaceLatest && latestRevision
1148
+ ? [...previousRevisions.slice(0, -1), revision]
1149
+ : [...previousRevisions, revision]).slice(-Math.max(1, maxRevisions));
1150
+ this.historyRevisions.set(nextFile.fileId, nextRevisions);
1151
+ this.emitChange(["history"], [input.path]);
1152
+ return {
1153
+ fileId: nextFile.fileId,
1154
+ stored: true,
1155
+ deduplicated: false,
1156
+ revision: clone(revision),
1157
+ };
1158
+ }
1159
+ async listNotifications() {
1160
+ const records = (await this.getMeta(NOTIFICATIONS_META_KEY)) ?? [];
1161
+ return records
1162
+ .filter((record) => !record.cleared)
1163
+ .sort((left, right) => right.createdAt - left.createdAt)
1164
+ .map((record) => clone(record));
1165
+ }
1166
+ async upsertNotification(record) {
1167
+ const records = (await this.getMeta(NOTIFICATIONS_META_KEY)) ?? [];
1168
+ const next = new Map(records.map((entry) => [entry.id, entry]));
1169
+ next.set(record.id, clone(record));
1170
+ this.meta[NOTIFICATIONS_META_KEY] = clone([...next.values()]);
1171
+ this.emitChange(["notification"]);
1172
+ }
1173
+ async markNotificationRead(id) {
1174
+ const records = (await this.getMeta(NOTIFICATIONS_META_KEY)) ?? [];
1175
+ const now = Date.now();
1176
+ this.meta[NOTIFICATIONS_META_KEY] = clone(records.map((record) => record.id === id ? { ...record, read: true, updatedAt: now } : record));
1177
+ this.emitChange(["notification"]);
1178
+ }
1179
+ async clearNotification(id) {
1180
+ const records = (await this.getMeta(NOTIFICATIONS_META_KEY)) ?? [];
1181
+ const now = Date.now();
1182
+ this.meta[NOTIFICATIONS_META_KEY] = clone(records.map((record) => record.id === id
1183
+ ? { ...record, cleared: true, updatedAt: now }
1184
+ : record));
1185
+ this.emitChange(["notification"]);
1186
+ }
1187
+ async clearAllNotifications() {
1188
+ const records = (await this.getMeta(NOTIFICATIONS_META_KEY)) ?? [];
1189
+ const now = Date.now();
1190
+ this.meta[NOTIFICATIONS_META_KEY] = clone(records.map((record) => ({ ...record, cleared: true, updatedAt: now })));
1191
+ this.emitChange(["notification"]);
1192
+ }
1193
+ async getMemorySourceState(sourceKey) {
1194
+ return clone(this.memorySources.get(sourceKey));
1195
+ }
1196
+ async listMemorySourceStates() {
1197
+ return [...this.memorySources.values()]
1198
+ .sort((left, right) => left.sourceKey.localeCompare(right.sourceKey))
1199
+ .map((record) => clone(record));
1200
+ }
1201
+ async upsertMemorySourceState(record) {
1202
+ this.memorySources.set(record.sourceKey, clone(record));
1203
+ this.emitChange(["memory"], [record.sourcePath]);
1204
+ }
1205
+ async deleteMemorySourceState(sourceKey) {
1206
+ const existing = this.memorySources.get(sourceKey);
1207
+ this.memorySources.delete(sourceKey);
1208
+ this.emitChange(["memory"], existing ? [existing.sourcePath] : []);
1209
+ }
1210
+ async upsertMemoryCandidate(input) {
1211
+ this.memoryCandidates.set(input.candidate.id, clone(input.candidate));
1212
+ this.memoryCandidateOrigins.set(input.candidate.id, clone(input.origins.filter((origin) => origin.candidateId === input.candidate.id)));
1213
+ this.emitChange(["memory"]);
1214
+ }
1215
+ async queryMemoryCandidates(query = {}) {
1216
+ const states = query.states ? new Set(query.states) : null;
1217
+ const limit = Math.max(0, query.limit ?? Number.POSITIVE_INFINITY);
1218
+ return [...this.memoryCandidates.values()]
1219
+ .filter((candidate) => (!query.scopeKind || candidate.scopeKind === query.scopeKind) &&
1220
+ (query.scopePath === undefined ||
1221
+ candidate.scopePath === query.scopePath) &&
1222
+ (!states || states.has(candidate.state)))
1223
+ .sort((left, right) => right.lastSeenAt - left.lastSeenAt || left.id.localeCompare(right.id))
1224
+ .slice(0, limit)
1225
+ .map((candidate) => ({
1226
+ candidate: clone(candidate),
1227
+ origins: clone(this.memoryCandidateOrigins.get(candidate.id) ?? []),
1228
+ }));
1229
+ }
1230
+ async deleteMemoryCandidatesByConversation(conversationId) {
1231
+ for (const [candidateId, origins] of this.memoryCandidateOrigins) {
1232
+ if (!origins.some((origin) => origin.conversationId === conversationId)) {
1233
+ continue;
1234
+ }
1235
+ this.memoryCandidateOrigins.delete(candidateId);
1236
+ this.memoryCandidates.delete(candidateId);
1237
+ }
1238
+ this.emitChange(["memory"]);
1239
+ }
1240
+ async recordMemoryRecallSignal(record) {
1241
+ const key = memoryRecallSignalKey(record);
1242
+ const existing = this.memoryRecallSignals.get(key);
1243
+ this.memoryRecallSignals.set(key, {
1244
+ ...clone(record),
1245
+ bestScore: Math.max(existing?.bestScore ?? 0, record.bestScore),
1246
+ hitCount: (existing?.hitCount ?? 0) + Math.max(0, record.hitCount),
1247
+ });
1248
+ this.emitChange(["memory"]);
1249
+ }
1250
+ async listMemoryRecallSignals(targetRef) {
1251
+ return [...this.memoryRecallSignals.values()]
1252
+ .filter((record) => targetRef === undefined || record.targetRef === targetRef)
1253
+ .sort((left, right) => right.day.localeCompare(left.day) ||
1254
+ left.queryFingerprint.localeCompare(right.queryFingerprint))
1255
+ .map((record) => clone(record));
1256
+ }
1257
+ async claimMemoryJob(input) {
1258
+ const existing = this.memoryJobs.get(input.job.id);
1259
+ if (existing?.status === "completed")
1260
+ return null;
1261
+ const renewing = existing?.status === "running" && existing.ownerId === input.ownerId;
1262
+ if (existing?.status === "running" &&
1263
+ existing.ownerId !== input.ownerId &&
1264
+ (existing.leaseUntil ?? 0) > input.now) {
1265
+ return null;
1266
+ }
1267
+ const base = existing ?? input.job;
1268
+ const attempts = renewing ? base.attempts : base.attempts + 1;
1269
+ if (attempts > base.maxAttempts)
1270
+ return null;
1271
+ const claimed = {
1272
+ ...clone(base),
1273
+ status: "running",
1274
+ attempts,
1275
+ ownerId: input.ownerId,
1276
+ leaseUntil: input.now + Math.max(1, input.leaseMs),
1277
+ startedAt: renewing ? base.startedAt : input.now,
1278
+ finishedAt: undefined,
1279
+ errorCode: undefined,
1280
+ };
1281
+ this.memoryJobs.set(claimed.id, claimed);
1282
+ this.emitChange(["memory"]);
1283
+ return clone(claimed);
1284
+ }
1285
+ async updateMemoryJob(input) {
1286
+ const existing = this.memoryJobs.get(input.jobId);
1287
+ if (!existing ||
1288
+ existing.status !== "running" ||
1289
+ existing.ownerId !== input.ownerId ||
1290
+ (existing.leaseUntil ?? 0) < input.now) {
1291
+ return null;
1292
+ }
1293
+ const updated = {
1294
+ ...existing,
1295
+ ...clone(input.patch),
1296
+ ownerId: input.patch.status === "running" ? existing.ownerId : undefined,
1297
+ leaseUntil: input.patch.status === "running"
1298
+ ? input.patch.leaseUntil ?? existing.leaseUntil
1299
+ : undefined,
1300
+ };
1301
+ this.memoryJobs.set(updated.id, updated);
1302
+ this.emitChange(["memory"]);
1303
+ return clone(updated);
1304
+ }
1305
+ async listMemoryJobs(scopeKey) {
1306
+ return [...this.memoryJobs.values()]
1307
+ .filter((job) => scopeKey === undefined || job.scopeKey === scopeKey)
1308
+ .sort((left, right) => right.createdAt - left.createdAt || left.id.localeCompare(right.id))
1309
+ .map((job) => clone(job));
1310
+ }
1311
+ async clearMemoryDerivedState() {
1312
+ this.memorySources.clear();
1313
+ this.memoryCandidates.clear();
1314
+ this.memoryCandidateOrigins.clear();
1315
+ this.memoryRecallSignals.clear();
1316
+ this.memoryJobs.clear();
1317
+ this.emitChange(["memory"]);
1318
+ }
1319
+ async getChangeRevision() {
1320
+ return this.changeRevision;
1321
+ }
1322
+ subscribeToChanges(listener) {
1323
+ this.changeListeners.add(listener);
1324
+ return () => this.changeListeners.delete(listener);
1325
+ }
1326
+ async upsertIndexedFile(record) {
1327
+ this.files.set(record.file.path, clone({ ...record.file, deleted: false }));
1328
+ this.metadata.set(record.metadata.path, clone(record.metadata));
1329
+ this.links.set(record.file.path, clone(record.links));
1330
+ this.tags.set(record.file.path, clone(record.tags));
1331
+ this.properties.set(record.file.path, clone(record.properties));
1332
+ if (record.task) {
1333
+ await this.upsertTaskProjection(record.task);
1334
+ }
1335
+ else if (record.task === null) {
1336
+ await this.deleteTaskProjection(record.file.path);
1337
+ }
1338
+ this.emitChange(["metadata"], [record.file.path]);
1339
+ }
1340
+ async deleteIndexedFile(path) {
1341
+ const existing = this.files.get(path);
1342
+ if (existing) {
1343
+ this.files.set(path, { ...existing, deleted: true, indexed: false });
1344
+ }
1345
+ delete this.meta[notebookStateMetaKey(path)];
1346
+ this.metadata.delete(path);
1347
+ this.links.delete(path);
1348
+ this.tags.delete(path);
1349
+ this.properties.delete(path);
1350
+ this.searchDocs.delete(path);
1351
+ this.tasks.delete(path);
1352
+ for (const definition of this.projections.values()) {
1353
+ this.removeProjectionSource(definition.projectionId, path);
1354
+ }
1355
+ this.emitChange(["metadata", "search", "task", "projection"], [path]);
1356
+ }
1357
+ async renameIndexedFile(oldPath, newPath) {
1358
+ const file = this.files.get(oldPath);
1359
+ if (file) {
1360
+ this.files.delete(oldPath);
1361
+ this.files.set(newPath, {
1362
+ ...file,
1363
+ path: newPath,
1364
+ normalizedPath: newPath,
1365
+ });
1366
+ }
1367
+ const notebookState = this.meta[notebookStateMetaKey(oldPath)];
1368
+ if (notebookState !== undefined) {
1369
+ delete this.meta[notebookStateMetaKey(oldPath)];
1370
+ this.meta[notebookStateMetaKey(newPath)] = clone({
1371
+ ...notebookState,
1372
+ sourcePath: newPath,
1373
+ });
1374
+ }
1375
+ const metadata = this.metadata.get(oldPath);
1376
+ if (metadata) {
1377
+ this.metadata.delete(oldPath);
1378
+ this.metadata.set(newPath, { ...metadata, path: newPath });
1379
+ }
1380
+ const links = this.links.get(oldPath);
1381
+ if (links) {
1382
+ this.links.delete(oldPath);
1383
+ this.links.set(newPath, links.map((link) => ({ ...link, sourcePath: newPath })));
1384
+ }
1385
+ const tags = this.tags.get(oldPath);
1386
+ if (tags) {
1387
+ this.tags.delete(oldPath);
1388
+ this.tags.set(newPath, tags.map((tag) => ({ ...tag, path: newPath })));
1389
+ }
1390
+ const properties = this.properties.get(oldPath);
1391
+ if (properties) {
1392
+ this.properties.delete(oldPath);
1393
+ this.properties.set(newPath, properties.map((property) => ({ ...property, path: newPath })));
1394
+ }
1395
+ const task = this.tasks.get(oldPath);
1396
+ if (task) {
1397
+ this.tasks.delete(oldPath);
1398
+ this.tasks.set(newPath, { ...task, documentPath: newPath });
1399
+ }
1400
+ this.renameProjectionSources(oldPath, newPath);
1401
+ const searchDoc = this.searchDocs.get(oldPath);
1402
+ if (searchDoc) {
1403
+ this.searchDocs.delete(oldPath);
1404
+ this.searchDocs.set(newPath, {
1405
+ ...searchDoc,
1406
+ path: newPath,
1407
+ chunks: searchDoc.chunks?.map((chunk) => ({
1408
+ ...chunk,
1409
+ id: renameChunkId(chunk.id, oldPath, newPath),
1410
+ })),
1411
+ });
1412
+ const stats = this.searchIndexStatsByPath.get(oldPath);
1413
+ if (stats) {
1414
+ this.searchIndexStatsByPath.delete(oldPath);
1415
+ this.searchIndexStatsByPath.set(newPath, stats);
1416
+ if (this.searchIndexLastErrorPath === oldPath) {
1417
+ this.searchIndexLastErrorPath = newPath;
1418
+ }
1419
+ }
1420
+ }
1421
+ this.emitChange(["metadata", "search", "task", "projection", "notebook"], [oldPath, newPath], [{ oldPath, newPath }]);
1422
+ }
1423
+ async getIndexedFile(path) {
1424
+ return clone(this.materializeIndexedMetadataRows([path])[0]);
1425
+ }
1426
+ async listIndexedFileManifest(query = {}) {
1427
+ const limit = Math.max(1, query.limit ?? 500);
1428
+ const requestedPaths = query.paths?.length ? new Set(query.paths) : null;
1429
+ const rows = [...this.files.values()]
1430
+ .filter((file) => file.indexed &&
1431
+ !file.deleted &&
1432
+ (!query.after || file.path > query.after) &&
1433
+ (!requestedPaths || requestedPaths.has(file.path)))
1434
+ .sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
1435
+ const page = rows.slice(0, limit).map((file) => ({
1436
+ ...file,
1437
+ parserVersion: this.metadata.get(file.path)?.parserVersion,
1438
+ }));
1439
+ return clone({
1440
+ rows: page,
1441
+ nextCursor: rows.length > page.length ? page.at(-1)?.path : undefined,
1442
+ });
1443
+ }
1444
+ async queryIndexedMetadata(query = {}) {
1445
+ const rows = this.materializeIndexedMetadataRows();
1446
+ return clone(this.applyIndexedMetadataQuery(rows, query));
1447
+ }
1448
+ async queryIndexedMetadataPage(input = {}) {
1449
+ const limit = Math.max(1, input.limit ?? input.query?.limit ?? 100);
1450
+ const query = { ...(input.query ?? {}), limit: undefined };
1451
+ const rows = this.applyIndexedMetadataQuery(this.materializeIndexedMetadataRows(), query).filter((row) => !input.after || row.file.path > input.after);
1452
+ const page = rows
1453
+ .slice(0, limit)
1454
+ .map((row) => projectIndexedMetadataRow(row, input.include));
1455
+ return clone({
1456
+ rows: page,
1457
+ nextCursor: rows.length > page.length ? page.at(-1)?.file.path : undefined,
1458
+ });
1459
+ }
1460
+ async queryMetadataFacets(query) {
1461
+ const matchesPath = (path) => !query.pathPrefixes?.length ||
1462
+ query.pathPrefixes.some((prefix) => matchesIndexedMetadataPathPrefix(path, prefix));
1463
+ const counts = new Map();
1464
+ const types = new Map();
1465
+ const add = (value, valueType, metadataType, topLevel) => {
1466
+ const key = `${valueType}\0${String(value)}`;
1467
+ const current = counts.get(key);
1468
+ counts.set(key, {
1469
+ value,
1470
+ valueType,
1471
+ count: (current?.count ?? 0) + 1,
1472
+ topLevel: topLevel ?? current?.topLevel,
1473
+ });
1474
+ if (metadataType) {
1475
+ const observed = types.get(key) ?? new Set();
1476
+ observed.add(metadataType);
1477
+ types.set(key, observed);
1478
+ }
1479
+ };
1480
+ if (query.kind === "tag") {
1481
+ for (const [path, tags] of this.tags) {
1482
+ if (!matchesPath(path))
1483
+ continue;
1484
+ for (const tag of new Set(tags.flatMap((entry) => entry.hierarchy)))
1485
+ add(tag, "string");
1486
+ }
1487
+ }
1488
+ else if (query.kind === "property-name") {
1489
+ for (const [path, properties] of this.properties) {
1490
+ if (!matchesPath(path))
1491
+ continue;
1492
+ const unique = new Map(properties.map((entry) => [entry.name, entry]));
1493
+ for (const property of unique.values()) {
1494
+ add(property.name, "string", property.declaredType ?? property.inferredType, true);
1495
+ }
1496
+ }
1497
+ }
1498
+ else if (query.kind === "property-path") {
1499
+ for (const [path, properties] of this.properties) {
1500
+ if (!matchesPath(path))
1501
+ continue;
1502
+ const unique = new Map();
1503
+ for (const property of properties) {
1504
+ unique.set(property.name, {
1505
+ type: property.declaredType ?? property.inferredType,
1506
+ topLevel: true,
1507
+ });
1508
+ for (const entry of flattenIndexedMetadataPropertyValues(property.value, property.name)) {
1509
+ const type = entry.value === null
1510
+ ? "unknown"
1511
+ : typeof entry.value === "number"
1512
+ ? "number"
1513
+ : typeof entry.value === "boolean"
1514
+ ? "checkbox"
1515
+ : "text";
1516
+ unique.set(entry.path.replace(/\[\d+\]/gu, "[]"), {
1517
+ type,
1518
+ topLevel: entry.path === property.name,
1519
+ });
1520
+ }
1521
+ }
1522
+ for (const [propertyPath, descriptor] of unique) {
1523
+ add(propertyPath, "string", descriptor.type, descriptor.topLevel);
1524
+ }
1525
+ }
1526
+ }
1527
+ else if (query.propertyName) {
1528
+ for (const [path, properties] of this.properties) {
1529
+ if (!matchesPath(path))
1530
+ continue;
1531
+ const seen = new Set();
1532
+ for (const scalar of indexedMetadataPropertyValues(properties, query.propertyName)) {
1533
+ const scalarKey = `${typeof scalar}\0${String(scalar)}`;
1534
+ if (seen.has(scalarKey))
1535
+ continue;
1536
+ seen.add(scalarKey);
1537
+ const type = scalar === null
1538
+ ? "null"
1539
+ : typeof scalar === "number"
1540
+ ? "number"
1541
+ : typeof scalar === "boolean"
1542
+ ? "boolean"
1543
+ : "string";
1544
+ add(scalar, type);
1545
+ }
1546
+ }
1547
+ }
1548
+ return [...counts.entries()]
1549
+ .map(([key, row]) => ({
1550
+ ...row,
1551
+ metadataTypes: types.get(key)?.size ? [...types.get(key)] : undefined,
1552
+ }))
1553
+ .sort((left, right) => right.count - left.count ||
1554
+ String(left.value).localeCompare(String(right.value)))
1555
+ .slice(0, query.limit ?? 100);
1556
+ }
1557
+ async queryMetadataLinks(query) {
1558
+ const paths = new Set([
1559
+ ...(query.paths ?? []),
1560
+ ...(query.path ? [query.path] : []),
1561
+ ]);
1562
+ if (!paths.size)
1563
+ return [];
1564
+ const links = query.direction === "outgoing"
1565
+ ? [...paths].flatMap((path) => this.links.get(path) ?? [])
1566
+ : [...this.links.values()]
1567
+ .flat()
1568
+ .filter((link) => link.resolvedTargetPath
1569
+ ? paths.has(link.resolvedTargetPath)
1570
+ : false);
1571
+ return clone(links
1572
+ .filter((link) => {
1573
+ if (query.resolution === "resolved")
1574
+ return link.resolvedTargetPath != null;
1575
+ if (query.resolution === "unresolved")
1576
+ return link.resolvedTargetPath == null;
1577
+ return true;
1578
+ })
1579
+ .sort((left, right) => left.sourcePath.localeCompare(right.sourcePath) ||
1580
+ (left.ordinal ?? 0) - (right.ordinal ?? 0))
1581
+ .slice(0, query.limit ?? 1000));
1582
+ }
1583
+ async upsertSearchDocument(document) {
1584
+ const prepared = await this.prepareSearchDocument(document);
1585
+ this.searchDocs.set(prepared.path, clone(prepared));
1586
+ this.updateSearchIndexStatsForDocument(prepared.path, prepared);
1587
+ this.emitChange(["search"], [prepared.path]);
1588
+ }
1589
+ async deleteSearchDocument(path) {
1590
+ this.searchDocs.delete(path);
1591
+ this.deleteSearchIndexStatsForPath(path);
1592
+ this.emitChange(["search"], [path]);
1593
+ }
1594
+ async getSearchDocument(path) {
1595
+ return clone(this.searchDocs.get(path));
1596
+ }
1597
+ async listSearchDocumentManifest(query = {}) {
1598
+ const limit = Math.max(1, query.limit ?? 500);
1599
+ const rows = [...this.searchDocs.values()]
1600
+ .filter((document) => !query.after || document.path > query.after)
1601
+ .sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
1602
+ const page = rows.slice(0, limit).map((document) => ({
1603
+ path: document.path,
1604
+ checksum: document.checksum,
1605
+ sourceProviderId: document.sourceProviderId,
1606
+ metadataHash: document.sourceMetadata?.metadataHash,
1607
+ providerVersion: document.sourceMetadata?.providerVersion,
1608
+ projectionSignature: document.sourceMetadata?.projectionSignature,
1609
+ sourceMtime: document.sourceMetadata?.sourceMtime,
1610
+ sourceSize: document.sourceMetadata?.sourceSize,
1611
+ }));
1612
+ return clone({
1613
+ rows: page,
1614
+ nextCursor: rows.length > page.length ? page.at(-1)?.path : undefined,
1615
+ });
1616
+ }
1617
+ async listSearchDocuments() {
1618
+ return [...this.searchDocs.values()].map((document) => clone(document));
1619
+ }
1620
+ async rebuildSearchIndex() {
1621
+ this.rebuildSearchIndexStats();
1622
+ this.emitChange(["search"]);
1623
+ }
1624
+ async prepareSearchDocument(document) {
1625
+ const normalizedDocument = normalizeSearchDocument(document);
1626
+ const provider = this.searchEmbeddingProvider;
1627
+ if (!provider || !normalizedDocument.chunks?.length) {
1628
+ return clone(normalizedDocument);
1629
+ }
1630
+ try {
1631
+ const ready = await provider.ready();
1632
+ if (!ready) {
1633
+ return {
1634
+ ...clone(normalizedDocument),
1635
+ chunks: normalizedDocument.chunks.map((chunk) => ({
1636
+ ...clone(chunk),
1637
+ embedding: {
1638
+ ...clone(chunk.embedding),
1639
+ status: "pending",
1640
+ modelId: provider.config.modelId ?? "lapis/token-hash-v0",
1641
+ modelVersion: provider.config.modelVersion,
1642
+ dimensions: provider.config.dimensions,
1643
+ },
1644
+ })),
1645
+ };
1646
+ }
1647
+ const embeddings = new Map((await provider.embedDocument(normalizedDocument)).map((entry) => [
1648
+ entry.chunkId,
1649
+ entry,
1650
+ ]));
1651
+ const timestamp = Date.now();
1652
+ return {
1653
+ ...clone(normalizedDocument),
1654
+ chunks: normalizedDocument.chunks.map((chunk) => {
1655
+ const embedding = embeddings.get(chunk.id);
1656
+ if (!embedding) {
1657
+ return clone(chunk);
1658
+ }
1659
+ return {
1660
+ ...clone(chunk),
1661
+ embedding: {
1662
+ status: "ready",
1663
+ modelId: provider.config.modelId ?? "lapis/token-hash-v0",
1664
+ modelVersion: provider.config.modelVersion,
1665
+ dimensions: embedding.vector.length,
1666
+ vector: [...embedding.vector],
1667
+ fingerprint: embedding.fingerprint,
1668
+ dirty: false,
1669
+ updatedAt: timestamp,
1670
+ },
1671
+ };
1672
+ }),
1673
+ };
1674
+ }
1675
+ catch (error) {
1676
+ return embeddingErrorState(normalizedDocument, provider, error);
1677
+ }
1678
+ }
1679
+ async searchDocuments(query, options = {}) {
1680
+ return this.searchDocumentsForPaths(query, options);
1681
+ }
1682
+ async searchDocumentPaths(query, options = {}) {
1683
+ return (await this.searchDocuments(query, options)).map((result) => result.document.path);
1684
+ }
1685
+ async upsertTaskProjection(record) {
1686
+ this.tasks.set(record.documentPath, clone(record));
1687
+ await this.ensureTasksProjectionDefinition();
1688
+ await this.replaceProjectionSource({
1689
+ projectionId: PUBLIC_TASKS_PROJECTION_ID,
1690
+ sourcePath: record.documentPath,
1691
+ sourceHash: this.files.get(record.documentPath)?.hash ?? record.documentId,
1692
+ rows: [{ id: record.documentId, kind: record.kind, data: { ...record } }],
1693
+ });
1694
+ this.emitChange(["task"], [record.documentPath]);
1695
+ }
1696
+ async deleteTaskProjection(path) {
1697
+ this.tasks.delete(path);
1698
+ this.removeProjectionSource(PUBLIC_TASKS_PROJECTION_ID, path);
1699
+ this.emitChange(["task", "projection"], [path]);
1700
+ }
1701
+ async queryTasks(query = {}) {
1702
+ const result = await this.queryProjection(PUBLIC_TASKS_PROJECTION_ID, taskQueryToIndexQuery(query));
1703
+ return clone(result.rows);
1704
+ }
1705
+ async getTaskRow(lookup) {
1706
+ if (lookup.id) {
1707
+ const row = await this.getProjectionRow(PUBLIC_TASKS_PROJECTION_ID, lookup.id);
1708
+ return row ?? undefined;
1709
+ }
1710
+ if (lookup.path) {
1711
+ const result = await this.queryProjection(PUBLIC_TASKS_PROJECTION_ID, {
1712
+ where: {
1713
+ op: "compare",
1714
+ field: "documentPath",
1715
+ comparison: "eq",
1716
+ value: lookup.path,
1717
+ },
1718
+ });
1719
+ return result.rows[0];
1720
+ }
1721
+ return undefined;
1722
+ }
1723
+ async registerProjectionDefinition(definition) {
1724
+ this.projections.set(definition.projectionId, clone({ ...definition, active: true }));
1725
+ this.projectionRevision += 1;
1726
+ this.emitChange(["projection"]);
1727
+ }
1728
+ async unregisterProjectionDefinition(projectionId) {
1729
+ const existing = this.projections.get(projectionId);
1730
+ if (!existing)
1731
+ return;
1732
+ this.projections.set(projectionId, { ...existing, active: false });
1733
+ this.projectionRevision += 1;
1734
+ this.emitChange(["projection"]);
1735
+ }
1736
+ async replaceProjectionSource(input) {
1737
+ assertProjectionWriteAccess(input.projectionId, input.writerPluginId);
1738
+ const definition = this.projections.get(input.projectionId);
1739
+ if (!definition?.active) {
1740
+ throw new Error(`Projection ${input.projectionId} is not registered.`);
1741
+ }
1742
+ if (input.rows.length > MAX_PROJECTION_ROWS_PER_SOURCE) {
1743
+ throw new Error("Projection exceeded the maximum rows per source file.");
1744
+ }
1745
+ this.removeProjectionSource(input.projectionId, input.sourcePath);
1746
+ this.projectionSources.set(this.projectionSourceKey(input.projectionId, input.sourcePath), {
1747
+ projectionId: input.projectionId,
1748
+ sourcePath: input.sourcePath,
1749
+ sourceHash: input.sourceHash,
1750
+ schemaVersion: definition.schemaVersion,
1751
+ configHash: definition.configHash,
1752
+ status: "ready",
1753
+ error: null,
1754
+ indexedAt: Date.now(),
1755
+ });
1756
+ for (const row of input.rows) {
1757
+ const record = {
1758
+ projectionId: input.projectionId,
1759
+ rowId: row.id,
1760
+ sourcePath: input.sourcePath,
1761
+ kind: row.kind,
1762
+ ordinal: row.ordinal ?? 0,
1763
+ data: clone(row.data),
1764
+ };
1765
+ this.projectionRows.set(this.projectionRowKey(input.projectionId, row.id), record);
1766
+ this.projectionValues.push(...indexedValuesForRow(input.projectionId, row, definition.fields));
1767
+ }
1768
+ for (const edge of input.edges ?? []) {
1769
+ this.projectionEdges.push({
1770
+ projectionId: input.projectionId,
1771
+ ...edge,
1772
+ targetProjectionId: edge.targetProjectionId ?? input.projectionId,
1773
+ data: edge.data ?? null,
1774
+ });
1775
+ }
1776
+ this.projectionRevision += 1;
1777
+ this.emitChange(["projection"], [input.sourcePath]);
1778
+ }
1779
+ async markProjectionSourceError(input) {
1780
+ assertProjectionWriteAccess(input.projectionId, input.writerPluginId);
1781
+ const definition = this.projections.get(input.projectionId);
1782
+ this.removeProjectionSource(input.projectionId, input.sourcePath);
1783
+ this.projectionSources.set(this.projectionSourceKey(input.projectionId, input.sourcePath), {
1784
+ projectionId: input.projectionId,
1785
+ sourcePath: input.sourcePath,
1786
+ sourceHash: input.sourceHash,
1787
+ schemaVersion: definition?.schemaVersion ?? 0,
1788
+ configHash: definition?.configHash ?? "",
1789
+ status: "error",
1790
+ error: input.error,
1791
+ indexedAt: Date.now(),
1792
+ });
1793
+ this.projectionRevision += 1;
1794
+ this.emitChange(["projection"], [input.sourcePath]);
1795
+ }
1796
+ async deleteProjectionSource(projectionId, sourcePath, writerPluginId) {
1797
+ assertProjectionWriteAccess(projectionId, writerPluginId);
1798
+ this.removeProjectionSource(projectionId, sourcePath);
1799
+ this.projectionRevision += 1;
1800
+ this.emitChange(["projection"], [sourcePath]);
1801
+ }
1802
+ async queryProjection(projectionId, query = {}, readerPluginId) {
1803
+ const definition = this.projections.get(projectionId);
1804
+ assertProjectionReadAccess(definition, readerPluginId);
1805
+ const sources = [...this.projectionSources.values()].filter((source) => source.projectionId === projectionId);
1806
+ const rows = [...this.projectionRows.values()].filter((row) => {
1807
+ if (row.projectionId !== projectionId)
1808
+ return false;
1809
+ const source = this.projectionSources.get(this.projectionSourceKey(projectionId, row.sourcePath));
1810
+ const file = this.files.get(row.sourcePath);
1811
+ return Boolean(source &&
1812
+ definition &&
1813
+ sourceIsCurrent(source, file?.hash, definition, query.includeStale));
1814
+ });
1815
+ return evaluateProjectionQuery(rows, query, this.projectionRevision, projectionIndexStatus(sources));
1816
+ }
1817
+ async getProjectionRow(projectionId, rowId, readerPluginId) {
1818
+ const definition = this.projections.get(projectionId);
1819
+ assertProjectionReadAccess(definition, readerPluginId);
1820
+ const row = this.projectionRows.get(this.projectionRowKey(projectionId, rowId));
1821
+ if (!row) {
1822
+ const byDocumentId = [...this.projectionRows.values()].find((candidate) => candidate.projectionId === projectionId &&
1823
+ (candidate.data.documentId === rowId || candidate.data.id === rowId));
1824
+ if (!byDocumentId)
1825
+ return null;
1826
+ return this.currentProjectionData(byDocumentId, definition);
1827
+ }
1828
+ return this.currentProjectionData(row, definition);
1829
+ }
1830
+ currentProjectionData(row, definition) {
1831
+ const source = this.projectionSources.get(this.projectionSourceKey(row.projectionId, row.sourcePath));
1832
+ const file = this.files.get(row.sourcePath);
1833
+ if (!source ||
1834
+ !definition ||
1835
+ !sourceIsCurrent(source, file?.hash, definition)) {
1836
+ return null;
1837
+ }
1838
+ return clone(row.data);
1839
+ }
1840
+ async queryRelated(query, readerPluginId) {
1841
+ const definition = this.projections.get(query.projectionId);
1842
+ assertProjectionReadAccess(definition, readerPluginId);
1843
+ const matching = this.projectionEdges
1844
+ .filter((edge) => edge.projectionId === query.projectionId &&
1845
+ edge.relation === query.relation)
1846
+ .filter((edge) => {
1847
+ if (query.direction !== "in")
1848
+ return edge.sourceRowId === query.rowId;
1849
+ if (edge.targetRowId === query.rowId)
1850
+ return true;
1851
+ const target = [...this.projectionRows.values()].find((row) => row.projectionId === query.projectionId &&
1852
+ row.rowId === query.rowId);
1853
+ return Boolean(target && edge.targetPath === target.sourcePath);
1854
+ })
1855
+ .sort((left, right) => left.ordinal - right.ordinal);
1856
+ const rows = [];
1857
+ for (const edge of matching) {
1858
+ const targetProjection = query.direction === "in"
1859
+ ? edge.projectionId
1860
+ : (edge.targetProjectionId ?? query.projectionId);
1861
+ const row = query.direction === "in"
1862
+ ? this.projectionRows.get(this.projectionRowKey(targetProjection, edge.sourceRowId))
1863
+ : edge.targetRowId
1864
+ ? this.projectionRows.get(this.projectionRowKey(targetProjection, edge.targetRowId))
1865
+ : [...this.projectionRows.values()].find((candidate) => candidate.projectionId === targetProjection &&
1866
+ candidate.sourcePath === edge.targetPath);
1867
+ if (!row)
1868
+ continue;
1869
+ if (query.targetWhere &&
1870
+ !evaluateProjectionQuery([row], { where: query.targetWhere }, 0, "ready").rows.length) {
1871
+ continue;
1872
+ }
1873
+ rows.push(row);
1874
+ }
1875
+ return evaluateProjectionQuery(rows, { limit: query.limit }, this.projectionRevision, "ready");
1876
+ }
1877
+ async ensureTasksProjectionDefinition() {
1878
+ if (this.projections.get(PUBLIC_TASKS_PROJECTION_ID)?.active)
1879
+ return;
1880
+ await this.registerProjectionDefinition({
1881
+ projectionId: PUBLIC_TASKS_PROJECTION_ID,
1882
+ ownerPluginId: "tasks",
1883
+ schemaVersion: TASK_PROJECTION_VERSION,
1884
+ configHash: "",
1885
+ visibility: "public",
1886
+ fields: TASK_PROJECTION_FIELDS,
1887
+ active: true,
1888
+ updatedAt: Date.now(),
1889
+ });
1890
+ }
1891
+ projectionSourceKey(projectionId, sourcePath) {
1892
+ return `${projectionId}\0${sourcePath}`;
1893
+ }
1894
+ projectionRowKey(projectionId, rowId) {
1895
+ return `${projectionId}\0${rowId}`;
1896
+ }
1897
+ removeProjectionSource(projectionId, sourcePath) {
1898
+ this.projectionSources.delete(this.projectionSourceKey(projectionId, sourcePath));
1899
+ for (const [key, row] of this.projectionRows) {
1900
+ if (row.projectionId === projectionId && row.sourcePath === sourcePath) {
1901
+ this.projectionRows.delete(key);
1902
+ }
1903
+ }
1904
+ this.projectionValues = this.projectionValues.filter((value) => !(value.projectionId === projectionId &&
1905
+ [...this.projectionRows.values()].every((row) => !(row.projectionId === projectionId &&
1906
+ row.sourcePath === sourcePath &&
1907
+ row.rowId === value.rowId))));
1908
+ this.projectionEdges = this.projectionEdges.filter((edge) => {
1909
+ if (edge.projectionId !== projectionId)
1910
+ return true;
1911
+ const sourceRow = this.projectionRows.get(this.projectionRowKey(projectionId, edge.sourceRowId));
1912
+ return Boolean(sourceRow);
1913
+ });
1914
+ }
1915
+ renameProjectionSources(oldPath, newPath) {
1916
+ for (const [key, source] of this.projectionSources) {
1917
+ if (source.sourcePath !== oldPath)
1918
+ continue;
1919
+ this.projectionSources.delete(key);
1920
+ this.projectionSources.set(this.projectionSourceKey(source.projectionId, newPath), {
1921
+ ...source,
1922
+ sourcePath: newPath,
1923
+ });
1924
+ }
1925
+ for (const [key, row] of this.projectionRows) {
1926
+ if (row.sourcePath !== oldPath)
1927
+ continue;
1928
+ this.projectionRows.delete(key);
1929
+ this.projectionRows.set(key, { ...row, sourcePath: newPath });
1930
+ }
1931
+ this.projectionEdges = this.projectionEdges.map((edge) => edge.targetPath === oldPath ? { ...edge, targetPath: newPath } : edge);
1932
+ }
1933
+ async listChildLinks(query) {
1934
+ const links = this.links.get(query.sourcePath) ?? [];
1935
+ return clone(links
1936
+ .filter((link) => !query.kind || link.kind === query.kind)
1937
+ .sort((left, right) => (left.ordinal ?? 0) - (right.ordinal ?? 0)));
1938
+ }
1939
+ async listTaskDescendants(path) {
1940
+ const projectionId = PUBLIC_TASKS_PROJECTION_ID;
1941
+ const source = [...this.projectionRows.values()].find((row) => row.projectionId === projectionId && row.sourcePath === path);
1942
+ if (!source)
1943
+ return [];
1944
+ const seen = new Set([source.rowId]);
1945
+ const results = [];
1946
+ const visit = (sourceRowId) => {
1947
+ for (const edge of this.projectionEdges) {
1948
+ if (edge.projectionId !== projectionId ||
1949
+ edge.sourceRowId !== sourceRowId ||
1950
+ (edge.relation !== "task-entry" && edge.relation !== "list-entry")) {
1951
+ continue;
1952
+ }
1953
+ const targetProjection = edge.targetProjectionId ?? projectionId;
1954
+ const target = edge.targetRowId
1955
+ ? this.projectionRows.get(this.projectionRowKey(targetProjection, edge.targetRowId))
1956
+ : [...this.projectionRows.values()].find((row) => row.projectionId === targetProjection &&
1957
+ row.sourcePath === edge.targetPath);
1958
+ if (!target || seen.has(target.rowId))
1959
+ continue;
1960
+ const current = this.currentProjectionData(target, this.projections.get(target.projectionId));
1961
+ if (!current)
1962
+ continue;
1963
+ seen.add(target.rowId);
1964
+ results.push(current);
1965
+ visit(target.rowId);
1966
+ }
1967
+ };
1968
+ visit(source.rowId);
1969
+ return results;
1970
+ }
1971
+ async searchDocumentsForPaths(query, options = {}, candidatePaths) {
1972
+ const propertyNames = searchPropertyNames(query);
1973
+ const allowedPaths = candidatePaths ? new Set(candidatePaths) : null;
1974
+ const allowedSourceProviders = options.sourceProviderIds?.length
1975
+ ? new Set(options.sourceProviderIds)
1976
+ : null;
1977
+ const sourceDocuments = [...this.searchDocs.values()]
1978
+ .filter((document) => (!allowedPaths || allowedPaths.has(document.path)) &&
1979
+ pathWithinPrefix(document.path, options.pathPrefix) &&
1980
+ (!allowedSourceProviders ||
1981
+ (document.sourceProviderId != null &&
1982
+ allowedSourceProviders.has(document.sourceProviderId))))
1983
+ .map((document) => ({
1984
+ document,
1985
+ properties: searchDocumentProperties(document, this.properties.get(document.path) ?? []),
1986
+ }))
1987
+ .filter(({ properties }) => hasSearchPropertyNames(properties, propertyNames));
1988
+ const limit = options.limit ?? 100;
1989
+ const requestedMode = options.mode ?? "auto";
1990
+ const queryVector = requestedMode === "lexical" || !this.searchEmbeddingProvider
1991
+ ? null
1992
+ : await this.safeEmbedQuery(query);
1993
+ const vectorScores = new Map(sourceDocuments.map(({ document }) => [
1994
+ document.path,
1995
+ queryVector
1996
+ ? scoreVectorDocument(document, queryVector)
1997
+ : { score: 0, matchedChunkIds: [] },
1998
+ ]));
1999
+ const lexicalScores = new Map(sourceDocuments.map(({ document, properties }) => [
2000
+ document.path,
2001
+ scoreSearchDocument(document, query, properties, options),
2002
+ ]));
2003
+ const vectorCandidateCount = [...vectorScores.values()].filter((entry) => entry.score >= MIN_VECTOR_SEARCH_SCORE).length;
2004
+ const appliedMode = queryVector
2005
+ ? requestedMode === "vector"
2006
+ ? "vector"
2007
+ : vectorCandidateCount > 0
2008
+ ? "hybrid"
2009
+ : "lexical"
2010
+ : "lexical";
2011
+ const lexicalRanks = rankSearchScores([...lexicalScores.entries()].map(([path, score]) => ({ path, score })));
2012
+ const vectorRanks = rankSearchScores([...vectorScores.entries()].map(([path, entry]) => ({
2013
+ path,
2014
+ score: entry.score,
2015
+ })), (score) => score >= MIN_VECTOR_SEARCH_SCORE);
2016
+ const candidateCount = sourceDocuments.length;
2017
+ const results = sourceDocuments
2018
+ .map(({ document }) => buildSearchResult(document, query, options, {
2019
+ backendKind: this.kind,
2020
+ appliedMode,
2021
+ lexicalScore: lexicalScores.get(document.path) ?? 0,
2022
+ vectorScore: vectorScores.get(document.path)?.score ?? 0,
2023
+ lexicalCandidateCount: candidateCount,
2024
+ vectorCandidateCount,
2025
+ providerConfig: this.searchEmbeddingProviderConfig,
2026
+ preferredChunkIds: vectorScores.get(document.path)?.matchedChunkIds ?? [],
2027
+ lexicalRank: lexicalRanks.get(document.path),
2028
+ vectorRank: vectorRanks.get(document.path),
2029
+ }))
2030
+ .filter((result) => {
2031
+ const lexicalHit = (result.scoreBreakdown.lexical ?? 0) > 0;
2032
+ const vectorHit = (result.scoreBreakdown.vector ?? 0) >= MIN_VECTOR_SEARCH_SCORE;
2033
+ if (appliedMode === "vector") {
2034
+ return vectorHit && result.snippets.length > 0;
2035
+ }
2036
+ if (appliedMode === "hybrid") {
2037
+ return (lexicalHit || vectorHit) && result.snippets.length > 0;
2038
+ }
2039
+ return lexicalHit && result.snippets.length > 0;
2040
+ });
2041
+ return this.finalizeSearchResults(results, options, limit);
2042
+ }
2043
+ finalizeSearchResults(results, options, limit = options.limit ?? 100) {
2044
+ const sortedResults = [...results].sort(compareSearchResults);
2045
+ const queryEnhancementDiagnostics = resolveSearchQueryEnhancementDiagnostics(sortedResults, options);
2046
+ return sortedResults.slice(0, limit).map((result) => {
2047
+ const cloned = clone(result);
2048
+ if (cloned.diagnostics && queryEnhancementDiagnostics) {
2049
+ cloned.diagnostics.queryEnhancement = clone(queryEnhancementDiagnostics);
2050
+ }
2051
+ return cloned;
2052
+ });
2053
+ }
2054
+ async safeEmbedQuery(query) {
2055
+ if (!this.searchEmbeddingProvider) {
2056
+ return null;
2057
+ }
2058
+ try {
2059
+ return await this.searchEmbeddingProvider.embedQuery(query);
2060
+ }
2061
+ catch {
2062
+ return null;
2063
+ }
2064
+ }
2065
+ rebindSearchDocumentForProvider(document) {
2066
+ const provider = this.searchEmbeddingProvider;
2067
+ if (!provider || !document.chunks?.length) {
2068
+ return clone(document);
2069
+ }
2070
+ return {
2071
+ ...clone(document),
2072
+ chunks: document.chunks.map((chunk) => {
2073
+ if (!chunk.text.trim().length) {
2074
+ return clone(chunk);
2075
+ }
2076
+ return {
2077
+ ...clone(chunk),
2078
+ embedding: {
2079
+ status: "pending",
2080
+ modelId: provider.config.modelId ?? "lapis/token-hash-v0",
2081
+ modelVersion: provider.config.modelVersion,
2082
+ dimensions: provider.config.dimensions,
2083
+ dirty: true,
2084
+ },
2085
+ };
2086
+ }),
2087
+ };
2088
+ }
2089
+ toState() {
2090
+ return {
2091
+ meta: clone(this.meta),
2092
+ metadataSnapshot: clone(this.metadataSnapshot),
2093
+ searchEmbeddingProvider: clone(this.searchEmbeddingProviderConfig),
2094
+ historyFiles: [...this.historyFiles.values()].map((value) => clone(value)),
2095
+ historyRevisions: [...this.historyRevisions.entries()].map(([key, value]) => [key, clone(value)]),
2096
+ files: [...this.files.values()].map((value) => clone(value)),
2097
+ metadata: [...this.metadata.values()].map((value) => clone(value)),
2098
+ links: [...this.links.entries()].map(([key, value]) => [
2099
+ key,
2100
+ clone(value),
2101
+ ]),
2102
+ tags: [...this.tags.entries()].map(([key, value]) => [key, clone(value)]),
2103
+ properties: [...this.properties.entries()].map(([key, value]) => [
2104
+ key,
2105
+ clone(value),
2106
+ ]),
2107
+ searchDocuments: [...this.searchDocs.values()].map((value) => clone(value)),
2108
+ tasks: [...this.tasks.values()].map((value) => clone(value)),
2109
+ projections: [...this.projections.values()].map((value) => clone(value)),
2110
+ projectionSources: [...this.projectionSources.values()].map((value) => clone(value)),
2111
+ projectionRows: [...this.projectionRows.values()].map((value) => clone(value)),
2112
+ projectionValues: this.projectionValues.map((value) => clone(value)),
2113
+ projectionEdges: this.projectionEdges.map((value) => clone(value)),
2114
+ projectionRevision: this.projectionRevision,
2115
+ memorySources: [...this.memorySources.values()].map((value) => clone(value)),
2116
+ memoryCandidates: [...this.memoryCandidates.values()].map((value) => clone(value)),
2117
+ memoryCandidateOrigins: [
2118
+ ...this.memoryCandidateOrigins.values(),
2119
+ ].flatMap((origins) => origins.map((origin) => clone(origin))),
2120
+ memoryRecallSignals: [...this.memoryRecallSignals.values()].map((value) => clone(value)),
2121
+ memoryJobs: [...this.memoryJobs.values()].map((value) => clone(value)),
2122
+ };
2123
+ }
2124
+ fromState(state) {
2125
+ this.meta = clone(state.meta ?? {});
2126
+ this.metadataSnapshot = clone(state.metadataSnapshot ?? null);
2127
+ this.searchEmbeddingProviderConfig = clone(state.searchEmbeddingProvider ?? null);
2128
+ this.searchEmbeddingProvider = createSearchEmbeddingProvider(this.searchEmbeddingProviderConfig);
2129
+ this.historyFiles = new Map((state.historyFiles ?? []).map((item) => [item.fileId, item]));
2130
+ this.historyFileIdsByPath = new Map([...this.historyFiles.values()].map((item) => [
2131
+ item.currentPath,
2132
+ item.fileId,
2133
+ ]));
2134
+ this.historyRevisions = new Map(state.historyRevisions ?? []);
2135
+ this.files = new Map((state.files ?? []).map((item) => [item.path, item]));
2136
+ this.metadata = new Map((state.metadata ?? []).map((item) => [item.path, item]));
2137
+ this.links = new Map(state.links ?? []);
2138
+ this.tags = new Map(state.tags ?? []);
2139
+ this.properties = new Map(state.properties ?? []);
2140
+ this.searchDocs = new Map((state.searchDocuments ?? []).map((item) => [item.path, item]));
2141
+ this.tasks = new Map((state.tasks ?? []).map((item) => [item.documentPath, item]));
2142
+ this.projections = new Map((state.projections ?? []).map((item) => [item.projectionId, item]));
2143
+ this.projectionSources = new Map((state.projectionSources ?? []).map((item) => [
2144
+ this.projectionSourceKey(item.projectionId, item.sourcePath),
2145
+ item,
2146
+ ]));
2147
+ this.projectionRows = new Map((state.projectionRows ?? []).map((item) => [
2148
+ this.projectionRowKey(item.projectionId, item.rowId),
2149
+ item,
2150
+ ]));
2151
+ this.projectionValues = clone(state.projectionValues ?? []);
2152
+ this.projectionEdges = clone(state.projectionEdges ?? []);
2153
+ this.projectionRevision = state.projectionRevision ?? 0;
2154
+ this.memorySources = new Map((state.memorySources ?? []).map((record) => [record.sourceKey, record]));
2155
+ this.memoryCandidates = new Map((state.memoryCandidates ?? []).map((record) => [record.id, record]));
2156
+ this.memoryCandidateOrigins = new Map();
2157
+ for (const origin of state.memoryCandidateOrigins ?? []) {
2158
+ const origins = this.memoryCandidateOrigins.get(origin.candidateId) ?? [];
2159
+ origins.push(origin);
2160
+ this.memoryCandidateOrigins.set(origin.candidateId, origins);
2161
+ }
2162
+ this.memoryRecallSignals = new Map((state.memoryRecallSignals ?? []).map((record) => [
2163
+ memoryRecallSignalKey(record),
2164
+ record,
2165
+ ]));
2166
+ this.memoryJobs = new Map((state.memoryJobs ?? []).map((record) => [record.id, record]));
2167
+ this.rebuildSearchIndexStats();
2168
+ }
2169
+ rebuildSearchIndexStats() {
2170
+ this.searchIndexStatsByPath = new Map([...this.searchDocs.values()].map((document) => [
2171
+ document.path,
2172
+ this.computeSearchIndexStatsForDocument(document),
2173
+ ]));
2174
+ this.searchIndexStats = {
2175
+ documentCount: 0,
2176
+ chunkCount: 0,
2177
+ readyChunkCount: 0,
2178
+ pendingChunkCount: 0,
2179
+ errorChunkCount: 0,
2180
+ lastError: null,
2181
+ };
2182
+ this.searchIndexLastErrorPath = null;
2183
+ for (const [path, stats] of this.searchIndexStatsByPath) {
2184
+ this.addSearchIndexStats(path, stats);
2185
+ }
2186
+ }
2187
+ computeSearchIndexStatsForDocument(document) {
2188
+ const activeModelId = this.searchEmbeddingProviderConfig?.modelId ?? null;
2189
+ let chunkCount = 0;
2190
+ let readyChunkCount = 0;
2191
+ let pendingChunkCount = 0;
2192
+ let errorChunkCount = 0;
2193
+ let lastError = null;
2194
+ for (const chunk of document.chunks ?? []) {
2195
+ chunkCount += 1;
2196
+ const embedding = chunk.embedding;
2197
+ const staleForActiveProvider = Boolean(this.searchEmbeddingProviderConfig &&
2198
+ ((activeModelId && embedding?.modelId !== activeModelId) ||
2199
+ !embedding));
2200
+ if (staleForActiveProvider) {
2201
+ pendingChunkCount += 1;
2202
+ continue;
2203
+ }
2204
+ switch (embedding?.status) {
2205
+ case "ready":
2206
+ readyChunkCount += 1;
2207
+ break;
2208
+ case "pending":
2209
+ pendingChunkCount += 1;
2210
+ break;
2211
+ case "error":
2212
+ errorChunkCount += 1;
2213
+ lastError ??= embedding.error ?? null;
2214
+ break;
2215
+ }
2216
+ }
2217
+ return {
2218
+ documentCount: 1,
2219
+ chunkCount,
2220
+ readyChunkCount,
2221
+ pendingChunkCount,
2222
+ errorChunkCount,
2223
+ lastError,
2224
+ };
2225
+ }
2226
+ updateSearchIndexStatsForDocument(path, document) {
2227
+ const previousStats = this.searchIndexStatsByPath.get(path);
2228
+ if (previousStats) {
2229
+ this.removeSearchIndexStats(path, previousStats);
2230
+ }
2231
+ const nextStats = this.computeSearchIndexStatsForDocument(document);
2232
+ this.searchIndexStatsByPath.set(path, nextStats);
2233
+ this.addSearchIndexStats(path, nextStats);
2234
+ }
2235
+ deleteSearchIndexStatsForPath(path) {
2236
+ const previousStats = this.searchIndexStatsByPath.get(path);
2237
+ if (!previousStats) {
2238
+ return;
2239
+ }
2240
+ this.removeSearchIndexStats(path, previousStats);
2241
+ this.searchIndexStatsByPath.delete(path);
2242
+ }
2243
+ addSearchIndexStats(path, stats) {
2244
+ this.searchIndexStats.documentCount += stats.documentCount;
2245
+ this.searchIndexStats.chunkCount += stats.chunkCount;
2246
+ this.searchIndexStats.readyChunkCount += stats.readyChunkCount;
2247
+ this.searchIndexStats.pendingChunkCount += stats.pendingChunkCount;
2248
+ this.searchIndexStats.errorChunkCount += stats.errorChunkCount;
2249
+ if (stats.lastError) {
2250
+ this.searchIndexStats.lastError = stats.lastError;
2251
+ this.searchIndexLastErrorPath = path;
2252
+ }
2253
+ }
2254
+ removeSearchIndexStats(path, stats) {
2255
+ this.searchIndexStats.documentCount -= stats.documentCount;
2256
+ this.searchIndexStats.chunkCount -= stats.chunkCount;
2257
+ this.searchIndexStats.readyChunkCount -= stats.readyChunkCount;
2258
+ this.searchIndexStats.pendingChunkCount -= stats.pendingChunkCount;
2259
+ this.searchIndexStats.errorChunkCount -= stats.errorChunkCount;
2260
+ if (this.searchIndexLastErrorPath === path) {
2261
+ this.searchIndexStats.lastError = null;
2262
+ this.searchIndexLastErrorPath = null;
2263
+ for (const [nextPath, nextStats] of this.searchIndexStatsByPath) {
2264
+ if (nextPath === path || !nextStats.lastError) {
2265
+ continue;
2266
+ }
2267
+ this.searchIndexStats.lastError = nextStats.lastError;
2268
+ this.searchIndexLastErrorPath = nextPath;
2269
+ }
2270
+ }
2271
+ }
2272
+ resolveFileHistoryFile(path, previousPath) {
2273
+ const fileId = this.historyFileIdsByPath.get(path) ??
2274
+ (previousPath ? this.historyFileIdsByPath.get(previousPath) : undefined);
2275
+ if (!fileId) {
2276
+ return null;
2277
+ }
2278
+ return this.historyFiles.get(fileId) ?? null;
2279
+ }
2280
+ materializeIndexedMetadataRows(candidatePaths) {
2281
+ const paths = candidatePaths
2282
+ ? [...candidatePaths]
2283
+ : [...this.files.values()].map((entry) => entry.path);
2284
+ return paths
2285
+ .map((path) => {
2286
+ const file = this.files.get(path);
2287
+ if (!file || !file.indexed || file.deleted) {
2288
+ return null;
2289
+ }
2290
+ return {
2291
+ file,
2292
+ metadata: this.metadata.get(path) ?? null,
2293
+ properties: this.properties.get(path) ?? [],
2294
+ tags: this.tags.get(path) ?? [],
2295
+ links: this.links.get(path) ?? [],
2296
+ };
2297
+ })
2298
+ .filter((row) => row !== null);
2299
+ }
2300
+ applyIndexedMetadataQuery(rows, query) {
2301
+ const normalizedExtensions = (query.extensions ?? [])
2302
+ .map(normalizeIndexedMetadataExtension)
2303
+ .filter(Boolean);
2304
+ const normalizedPrefixes = (query.pathPrefixes ?? [])
2305
+ .map(normalizeIndexedMetadataPathPrefix)
2306
+ .filter((prefix) => prefix.length > 0);
2307
+ const propertyFilters = query.propertyFilters ?? [];
2308
+ const requiredTags = (query.requiredTags ?? [])
2309
+ .map((tag) => indexedMetadataTagCandidates(tag))
2310
+ .filter((candidates) => candidates.length > 0);
2311
+ const resolvedTargetPaths = (query.resolvedTargetPaths ?? []).filter(Boolean);
2312
+ let filtered = rows.filter((row) => {
2313
+ if (query.excludeHiddenPaths &&
2314
+ row.file.path.split("/").some((part) => part.startsWith("."))) {
2315
+ return false;
2316
+ }
2317
+ if (normalizedExtensions.length > 0 &&
2318
+ !normalizedExtensions.includes(normalizeIndexedMetadataExtension(row.file.extension))) {
2319
+ return false;
2320
+ }
2321
+ if (normalizedPrefixes.length > 0 &&
2322
+ !normalizedPrefixes.some((prefix) => matchesIndexedMetadataPathPrefix(row.file.path, prefix))) {
2323
+ return false;
2324
+ }
2325
+ if (propertyFilters.length > 0 &&
2326
+ !propertyFilters.every((filter) => matchesIndexedMetadataPropertyFilter(row.properties, filter))) {
2327
+ return false;
2328
+ }
2329
+ if (requiredTags.length > 0 &&
2330
+ !requiredTags.every((candidates) => row.tags.some((tag) => candidates.includes(tag.tag)))) {
2331
+ return false;
2332
+ }
2333
+ if (resolvedTargetPaths.length > 0 &&
2334
+ !resolvedTargetPaths.every((targetPath) => row.links.some((link) => link.resolvedTargetPath === targetPath))) {
2335
+ return false;
2336
+ }
2337
+ return true;
2338
+ });
2339
+ if ((query.sort?.length ?? 0) > 0) {
2340
+ filtered = filtered.slice().sort((left, right) => {
2341
+ for (const sort of query.sort ?? []) {
2342
+ const comparison = compareIndexedMetadataScalars(indexedMetadataSortValue(left, sort), indexedMetadataSortValue(right, sort));
2343
+ if (comparison === 0) {
2344
+ continue;
2345
+ }
2346
+ return sort.direction === "DESC" ? -comparison : comparison;
2347
+ }
2348
+ return left.file.path < right.file.path
2349
+ ? -1
2350
+ : left.file.path > right.file.path
2351
+ ? 1
2352
+ : 0;
2353
+ });
2354
+ }
2355
+ if (query.limit && query.limit > 0) {
2356
+ filtered = filtered.slice(0, query.limit);
2357
+ }
2358
+ return filtered;
2359
+ }
2360
+ emitChange(domains, paths = [], renamed) {
2361
+ const change = {
2362
+ revision: ++this.changeRevision,
2363
+ domains: [...new Set(domains)],
2364
+ paths: [...new Set(paths)],
2365
+ renamed: renamed?.map((entry) => ({ ...entry })),
2366
+ committedAt: Date.now(),
2367
+ };
2368
+ for (const listener of this.changeListeners)
2369
+ listener(clone(change));
2370
+ return change;
2371
+ }
2372
+ }