sensemaking 0.18.4 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/README.md +22 -4
  2. package/dist/cjs/chunk/group.js +10 -23
  3. package/dist/cjs/chunk/group.js.map +1 -1
  4. package/dist/cjs/chunk/parse.js +2 -5
  5. package/dist/cjs/chunk/parse.js.map +1 -1
  6. package/dist/cjs/cli/index.js.map +1 -1
  7. package/dist/cjs/cli/shared.js +6 -10
  8. package/dist/cjs/cli/shared.js.map +1 -1
  9. package/dist/cjs/cli/status.js +44 -10
  10. package/dist/cjs/cli/status.js.map +1 -1
  11. package/dist/cjs/cli.js +2 -8
  12. package/dist/cjs/cli.js.map +1 -1
  13. package/dist/cjs/commands/map.js +1 -3
  14. package/dist/cjs/commands/map.js.map +1 -1
  15. package/dist/cjs/commands/peek.js.map +1 -1
  16. package/dist/cjs/commands/related.js +3 -5
  17. package/dist/cjs/commands/related.js.map +1 -1
  18. package/dist/cjs/commands/scope.js.map +1 -1
  19. package/dist/cjs/commands/search.js +6 -8
  20. package/dist/cjs/commands/search.js.map +1 -1
  21. package/dist/cjs/commands/signals.js.map +1 -1
  22. package/dist/cjs/config/access.js.map +1 -1
  23. package/dist/cjs/config/index.d.cts +1 -1
  24. package/dist/cjs/config/index.d.ts +1 -1
  25. package/dist/cjs/config/index.js +3 -0
  26. package/dist/cjs/config/index.js.map +1 -1
  27. package/dist/cjs/config/load.js +10 -24
  28. package/dist/cjs/config/load.js.map +1 -1
  29. package/dist/cjs/config/resolve.js.map +1 -1
  30. package/dist/cjs/config/signals.js +2 -3
  31. package/dist/cjs/config/signals.js.map +1 -1
  32. package/dist/cjs/config/types.d.cts +2 -1
  33. package/dist/cjs/config/types.d.ts +2 -1
  34. package/dist/cjs/config/types.js +8 -0
  35. package/dist/cjs/config/types.js.map +1 -1
  36. package/dist/cjs/config/validate.d.cts +1 -1
  37. package/dist/cjs/config/validate.d.ts +1 -1
  38. package/dist/cjs/config/validate.js +9 -10
  39. package/dist/cjs/config/validate.js.map +1 -1
  40. package/dist/cjs/embed/cohere.js +2 -2
  41. package/dist/cjs/embed/cohere.js.map +1 -1
  42. package/dist/cjs/embed/distribution.js +3 -3
  43. package/dist/cjs/embed/distribution.js.map +1 -1
  44. package/dist/cjs/embed/handoff.d.cts +3 -0
  45. package/dist/cjs/embed/handoff.d.ts +3 -0
  46. package/dist/cjs/embed/handoff.js +39 -0
  47. package/dist/cjs/embed/handoff.js.map +1 -0
  48. package/dist/cjs/embed/identity.d.cts +11 -10
  49. package/dist/cjs/embed/identity.d.ts +11 -10
  50. package/dist/cjs/embed/identity.js +30 -15
  51. package/dist/cjs/embed/identity.js.map +1 -1
  52. package/dist/cjs/embed/langfit.js +2 -3
  53. package/dist/cjs/embed/langfit.js.map +1 -1
  54. package/dist/cjs/embed/languages.js +2 -4
  55. package/dist/cjs/embed/languages.js.map +1 -1
  56. package/dist/cjs/embed/openai.js +2 -2
  57. package/dist/cjs/embed/openai.js.map +1 -1
  58. package/dist/cjs/embed/query.js +58 -20
  59. package/dist/cjs/embed/query.js.map +1 -1
  60. package/dist/cjs/embed/registry.js +1 -2
  61. package/dist/cjs/embed/registry.js.map +1 -1
  62. package/dist/cjs/embed/static.d.cts +1 -1
  63. package/dist/cjs/embed/static.d.ts +1 -1
  64. package/dist/cjs/embed/static.js +8 -10
  65. package/dist/cjs/embed/static.js.map +1 -1
  66. package/dist/cjs/embed/store.d.cts +2 -2
  67. package/dist/cjs/embed/store.d.ts +2 -2
  68. package/dist/cjs/embed/store.js +48 -34
  69. package/dist/cjs/embed/store.js.map +1 -1
  70. package/dist/cjs/embed/types.js +1 -3
  71. package/dist/cjs/embed/types.js.map +1 -1
  72. package/dist/cjs/errors.d.cts +1 -1
  73. package/dist/cjs/errors.d.ts +1 -1
  74. package/dist/cjs/errors.js.map +1 -1
  75. package/dist/cjs/features/embed.js +19 -17
  76. package/dist/cjs/features/embed.js.map +1 -1
  77. package/dist/cjs/features/fences.js +5 -10
  78. package/dist/cjs/features/fences.js.map +1 -1
  79. package/dist/cjs/features/links.js +20 -37
  80. package/dist/cjs/features/links.js.map +1 -1
  81. package/dist/cjs/features/sections.js +21 -19
  82. package/dist/cjs/features/sections.js.map +1 -1
  83. package/dist/cjs/features/tags.js +5 -8
  84. package/dist/cjs/features/tags.js.map +1 -1
  85. package/dist/cjs/graph/traverse.js +1 -2
  86. package/dist/cjs/graph/traverse.js.map +1 -1
  87. package/dist/cjs/index.d.cts +1 -1
  88. package/dist/cjs/index.d.ts +1 -1
  89. package/dist/cjs/index.js +3 -0
  90. package/dist/cjs/index.js.map +1 -1
  91. package/dist/cjs/output/column-hint.js +3 -6
  92. package/dist/cjs/output/column-hint.js.map +1 -1
  93. package/dist/cjs/output/output.js +7 -14
  94. package/dist/cjs/output/output.js.map +1 -1
  95. package/dist/cjs/output/search-error.js +2 -5
  96. package/dist/cjs/output/search-error.js.map +1 -1
  97. package/dist/cjs/scan/frontmatter.d.cts +1 -0
  98. package/dist/cjs/scan/frontmatter.d.ts +1 -0
  99. package/dist/cjs/scan/frontmatter.js +14 -18
  100. package/dist/cjs/scan/frontmatter.js.map +1 -1
  101. package/dist/cjs/scan/list.js +2 -3
  102. package/dist/cjs/scan/list.js.map +1 -1
  103. package/dist/cjs/scan/reparse.js +12 -26
  104. package/dist/cjs/scan/reparse.js.map +1 -1
  105. package/dist/cjs/scan/worker-error.js.map +1 -1
  106. package/dist/cjs/store/cache.d.cts +2 -0
  107. package/dist/cjs/store/cache.d.ts +2 -0
  108. package/dist/cjs/store/cache.js +20 -0
  109. package/dist/cjs/store/cache.js.map +1 -0
  110. package/dist/cjs/store/duckdb/batch.js +4 -16
  111. package/dist/cjs/store/duckdb/batch.js.map +1 -1
  112. package/dist/cjs/store/duckdb/connection.js +8 -20
  113. package/dist/cjs/store/duckdb/connection.js.map +1 -1
  114. package/dist/cjs/store/duckdb/fieldStats.js +2 -8
  115. package/dist/cjs/store/duckdb/fieldStats.js.map +1 -1
  116. package/dist/cjs/store/duckdb/lexical.js +14 -28
  117. package/dist/cjs/store/duckdb/lexical.js.map +1 -1
  118. package/dist/cjs/store/duckdb/native.js.map +1 -1
  119. package/dist/cjs/store/duckdb/open.js +12 -13
  120. package/dist/cjs/store/duckdb/open.js.map +1 -1
  121. package/dist/cjs/store/duckdb/reconcile.js +4 -11
  122. package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
  123. package/dist/cjs/store/duckdb/sql-functions.js +2 -4
  124. package/dist/cjs/store/duckdb/sql-functions.js.map +1 -1
  125. package/dist/cjs/store/duckdb/store.js +2 -3
  126. package/dist/cjs/store/duckdb/store.js.map +1 -1
  127. package/dist/cjs/store/duckdb/vectors.js +8 -18
  128. package/dist/cjs/store/duckdb/vectors.js.map +1 -1
  129. package/dist/cjs/store/index.d.cts +3 -2
  130. package/dist/cjs/store/index.d.ts +3 -2
  131. package/dist/cjs/store/index.js +7 -8
  132. package/dist/cjs/store/index.js.map +1 -1
  133. package/dist/cjs/store/native.js +6 -16
  134. package/dist/cjs/store/native.js.map +1 -1
  135. package/dist/cjs/store/sql-functions.js.map +1 -1
  136. package/dist/cjs/store/sqlite/connection.js +5 -9
  137. package/dist/cjs/store/sqlite/connection.js.map +1 -1
  138. package/dist/cjs/store/sqlite/fieldStats.js.map +1 -1
  139. package/dist/cjs/store/sqlite/lexical.js +3 -5
  140. package/dist/cjs/store/sqlite/lexical.js.map +1 -1
  141. package/dist/cjs/store/sqlite/open.d.cts +0 -1
  142. package/dist/cjs/store/sqlite/open.d.ts +0 -1
  143. package/dist/cjs/store/sqlite/open.js +47 -45
  144. package/dist/cjs/store/sqlite/open.js.map +1 -1
  145. package/dist/cjs/store/sqlite/reconcile.js +113 -104
  146. package/dist/cjs/store/sqlite/reconcile.js.map +1 -1
  147. package/dist/cjs/store/sqlite/sql-functions.js +2 -4
  148. package/dist/cjs/store/sqlite/sql-functions.js.map +1 -1
  149. package/dist/cjs/store/sqlite/store.js.map +1 -1
  150. package/dist/cjs/store/transaction.d.cts +2 -1
  151. package/dist/cjs/store/transaction.d.ts +2 -1
  152. package/dist/cjs/store/transaction.js +17 -9
  153. package/dist/cjs/store/transaction.js.map +1 -1
  154. package/dist/cjs/store/turso/connection.js +3 -7
  155. package/dist/cjs/store/turso/connection.js.map +1 -1
  156. package/dist/cjs/store/turso/fieldStats.js.map +1 -1
  157. package/dist/cjs/store/turso/lexical.d.cts +2 -0
  158. package/dist/cjs/store/turso/lexical.d.ts +2 -0
  159. package/dist/cjs/store/turso/lexical.js +347 -0
  160. package/dist/cjs/store/turso/lexical.js.map +1 -0
  161. package/dist/cjs/store/turso/native.js.map +1 -1
  162. package/dist/cjs/store/turso/open.d.cts +3 -1
  163. package/dist/cjs/store/turso/open.d.ts +3 -1
  164. package/dist/cjs/store/turso/open.js +150 -66
  165. package/dist/cjs/store/turso/open.js.map +1 -1
  166. package/dist/cjs/store/turso/reconcile.js +286 -152
  167. package/dist/cjs/store/turso/reconcile.js.map +1 -1
  168. package/dist/cjs/store/turso/store.js +18 -41
  169. package/dist/cjs/store/turso/store.js.map +1 -1
  170. package/dist/cjs/store/turso/vectors.d.cts +8 -0
  171. package/dist/cjs/store/turso/vectors.d.ts +8 -0
  172. package/dist/cjs/store/turso/vectors.js +435 -0
  173. package/dist/cjs/store/turso/vectors.js.map +1 -0
  174. package/dist/cjs/store/types.js +2 -3
  175. package/dist/cjs/store/types.js.map +1 -1
  176. package/dist/cjs/store/vectors.js.map +1 -1
  177. package/dist/cjs/text/segment.d.cts +1 -0
  178. package/dist/cjs/text/segment.d.ts +1 -0
  179. package/dist/cjs/text/segment.js +10 -14
  180. package/dist/cjs/text/segment.js.map +1 -1
  181. package/dist/cjs/watch.js +2 -5
  182. package/dist/cjs/watch.js.map +1 -1
  183. package/dist/cjs/workers/parse.js.map +1 -1
  184. package/dist/esm/chunk/group.js +10 -23
  185. package/dist/esm/chunk/group.js.map +1 -1
  186. package/dist/esm/chunk/parse.js +4 -8
  187. package/dist/esm/chunk/parse.js.map +1 -1
  188. package/dist/esm/cli/index.js +3 -5
  189. package/dist/esm/cli/index.js.map +1 -1
  190. package/dist/esm/cli/shared.js +12 -22
  191. package/dist/esm/cli/shared.js.map +1 -1
  192. package/dist/esm/cli/status.js +43 -9
  193. package/dist/esm/cli/status.js.map +1 -1
  194. package/dist/esm/cli.js +2 -8
  195. package/dist/esm/cli.js.map +1 -1
  196. package/dist/esm/commands/map.js +1 -3
  197. package/dist/esm/commands/map.js.map +1 -1
  198. package/dist/esm/commands/peek.js.map +1 -1
  199. package/dist/esm/commands/related.js +3 -5
  200. package/dist/esm/commands/related.js.map +1 -1
  201. package/dist/esm/commands/scope.js +3 -5
  202. package/dist/esm/commands/scope.js.map +1 -1
  203. package/dist/esm/commands/search.js +10 -16
  204. package/dist/esm/commands/search.js.map +1 -1
  205. package/dist/esm/commands/signals.js +3 -7
  206. package/dist/esm/commands/signals.js.map +1 -1
  207. package/dist/esm/config/access.js +8 -13
  208. package/dist/esm/config/access.js.map +1 -1
  209. package/dist/esm/config/index.d.ts +1 -1
  210. package/dist/esm/config/index.js +1 -1
  211. package/dist/esm/config/index.js.map +1 -1
  212. package/dist/esm/config/load.js +10 -24
  213. package/dist/esm/config/load.js.map +1 -1
  214. package/dist/esm/config/resolve.js +2 -3
  215. package/dist/esm/config/resolve.js.map +1 -1
  216. package/dist/esm/config/signals.js +3 -5
  217. package/dist/esm/config/signals.js.map +1 -1
  218. package/dist/esm/config/types.d.ts +2 -1
  219. package/dist/esm/config/types.js +8 -2
  220. package/dist/esm/config/types.js.map +1 -1
  221. package/dist/esm/config/validate.d.ts +1 -1
  222. package/dist/esm/config/validate.js +7 -10
  223. package/dist/esm/config/validate.js.map +1 -1
  224. package/dist/esm/embed/cohere.js +2 -2
  225. package/dist/esm/embed/cohere.js.map +1 -1
  226. package/dist/esm/embed/distribution.js +1 -1
  227. package/dist/esm/embed/distribution.js.map +1 -1
  228. package/dist/esm/embed/handoff.d.ts +3 -0
  229. package/dist/esm/embed/handoff.js +21 -0
  230. package/dist/esm/embed/handoff.js.map +1 -0
  231. package/dist/esm/embed/identity.d.ts +11 -10
  232. package/dist/esm/embed/identity.js +29 -26
  233. package/dist/esm/embed/identity.js.map +1 -1
  234. package/dist/esm/embed/langfit.js +2 -3
  235. package/dist/esm/embed/langfit.js.map +1 -1
  236. package/dist/esm/embed/languages.js +2 -4
  237. package/dist/esm/embed/languages.js.map +1 -1
  238. package/dist/esm/embed/openai.js +2 -2
  239. package/dist/esm/embed/openai.js.map +1 -1
  240. package/dist/esm/embed/query.js +21 -6
  241. package/dist/esm/embed/query.js.map +1 -1
  242. package/dist/esm/embed/registry.js +1 -2
  243. package/dist/esm/embed/registry.js.map +1 -1
  244. package/dist/esm/embed/static.d.ts +1 -1
  245. package/dist/esm/embed/static.js +8 -10
  246. package/dist/esm/embed/static.js.map +1 -1
  247. package/dist/esm/embed/store.d.ts +2 -2
  248. package/dist/esm/embed/store.js +28 -23
  249. package/dist/esm/embed/store.js.map +1 -1
  250. package/dist/esm/embed/types.js +1 -3
  251. package/dist/esm/embed/types.js.map +1 -1
  252. package/dist/esm/errors.d.ts +1 -1
  253. package/dist/esm/errors.js.map +1 -1
  254. package/dist/esm/features/embed.js +16 -16
  255. package/dist/esm/features/embed.js.map +1 -1
  256. package/dist/esm/features/fences.js +9 -19
  257. package/dist/esm/features/fences.js.map +1 -1
  258. package/dist/esm/features/links.js +24 -49
  259. package/dist/esm/features/links.js.map +1 -1
  260. package/dist/esm/features/sections.js +20 -20
  261. package/dist/esm/features/sections.js.map +1 -1
  262. package/dist/esm/features/tags.js +5 -8
  263. package/dist/esm/features/tags.js.map +1 -1
  264. package/dist/esm/features/types.js.map +1 -1
  265. package/dist/esm/graph/traverse.js +1 -2
  266. package/dist/esm/graph/traverse.js.map +1 -1
  267. package/dist/esm/index.d.ts +1 -1
  268. package/dist/esm/index.js +1 -1
  269. package/dist/esm/index.js.map +1 -1
  270. package/dist/esm/output/column-hint.js +3 -6
  271. package/dist/esm/output/column-hint.js.map +1 -1
  272. package/dist/esm/output/output.js +9 -20
  273. package/dist/esm/output/output.js.map +1 -1
  274. package/dist/esm/output/search-error.js +2 -5
  275. package/dist/esm/output/search-error.js.map +1 -1
  276. package/dist/esm/scan/frontmatter.d.ts +1 -0
  277. package/dist/esm/scan/frontmatter.js +13 -18
  278. package/dist/esm/scan/frontmatter.js.map +1 -1
  279. package/dist/esm/scan/list.js +2 -3
  280. package/dist/esm/scan/list.js.map +1 -1
  281. package/dist/esm/scan/reparse.js +16 -34
  282. package/dist/esm/scan/reparse.js.map +1 -1
  283. package/dist/esm/scan/worker-error.js.map +1 -1
  284. package/dist/esm/store/cache.d.ts +2 -0
  285. package/dist/esm/store/cache.js +11 -0
  286. package/dist/esm/store/cache.js.map +1 -0
  287. package/dist/esm/store/duckdb/batch.js +4 -16
  288. package/dist/esm/store/duckdb/batch.js.map +1 -1
  289. package/dist/esm/store/duckdb/connection.js +8 -20
  290. package/dist/esm/store/duckdb/connection.js.map +1 -1
  291. package/dist/esm/store/duckdb/fieldStats.js +4 -12
  292. package/dist/esm/store/duckdb/fieldStats.js.map +1 -1
  293. package/dist/esm/store/duckdb/lexical.js +19 -35
  294. package/dist/esm/store/duckdb/lexical.js.map +1 -1
  295. package/dist/esm/store/duckdb/native.js +2 -7
  296. package/dist/esm/store/duckdb/native.js.map +1 -1
  297. package/dist/esm/store/duckdb/open.js +16 -23
  298. package/dist/esm/store/duckdb/open.js.map +1 -1
  299. package/dist/esm/store/duckdb/reconcile.js +4 -11
  300. package/dist/esm/store/duckdb/reconcile.js.map +1 -1
  301. package/dist/esm/store/duckdb/sql-functions.js +4 -9
  302. package/dist/esm/store/duckdb/sql-functions.js.map +1 -1
  303. package/dist/esm/store/duckdb/store.js +6 -14
  304. package/dist/esm/store/duckdb/store.js.map +1 -1
  305. package/dist/esm/store/duckdb/vectors.js +14 -31
  306. package/dist/esm/store/duckdb/vectors.js.map +1 -1
  307. package/dist/esm/store/index.d.ts +3 -2
  308. package/dist/esm/store/index.js +5 -6
  309. package/dist/esm/store/index.js.map +1 -1
  310. package/dist/esm/store/native.js +10 -25
  311. package/dist/esm/store/native.js.map +1 -1
  312. package/dist/esm/store/sql-functions.js +3 -5
  313. package/dist/esm/store/sql-functions.js.map +1 -1
  314. package/dist/esm/store/sqlite/connection.js +6 -10
  315. package/dist/esm/store/sqlite/connection.js.map +1 -1
  316. package/dist/esm/store/sqlite/fieldStats.js +2 -6
  317. package/dist/esm/store/sqlite/fieldStats.js.map +1 -1
  318. package/dist/esm/store/sqlite/lexical.js +5 -9
  319. package/dist/esm/store/sqlite/lexical.js.map +1 -1
  320. package/dist/esm/store/sqlite/open.d.ts +0 -1
  321. package/dist/esm/store/sqlite/open.js +45 -47
  322. package/dist/esm/store/sqlite/open.js.map +1 -1
  323. package/dist/esm/store/sqlite/reconcile.js +13 -17
  324. package/dist/esm/store/sqlite/reconcile.js.map +1 -1
  325. package/dist/esm/store/sqlite/sql-functions.js +2 -4
  326. package/dist/esm/store/sqlite/sql-functions.js.map +1 -1
  327. package/dist/esm/store/sqlite/store.js +1 -2
  328. package/dist/esm/store/sqlite/store.js.map +1 -1
  329. package/dist/esm/store/transaction.d.ts +2 -1
  330. package/dist/esm/store/transaction.js +12 -10
  331. package/dist/esm/store/transaction.js.map +1 -1
  332. package/dist/esm/store/turso/connection.js +4 -8
  333. package/dist/esm/store/turso/connection.js.map +1 -1
  334. package/dist/esm/store/turso/fieldStats.js +2 -5
  335. package/dist/esm/store/turso/fieldStats.js.map +1 -1
  336. package/dist/esm/store/turso/lexical.d.ts +2 -0
  337. package/dist/esm/store/turso/lexical.js +128 -0
  338. package/dist/esm/store/turso/lexical.js.map +1 -0
  339. package/dist/esm/store/turso/native.js +2 -7
  340. package/dist/esm/store/turso/native.js.map +1 -1
  341. package/dist/esm/store/turso/open.d.ts +3 -1
  342. package/dist/esm/store/turso/open.js +45 -29
  343. package/dist/esm/store/turso/open.js.map +1 -1
  344. package/dist/esm/store/turso/reconcile.js +38 -24
  345. package/dist/esm/store/turso/reconcile.js.map +1 -1
  346. package/dist/esm/store/turso/store.js +17 -28
  347. package/dist/esm/store/turso/store.js.map +1 -1
  348. package/dist/esm/store/turso/vectors.d.ts +8 -0
  349. package/dist/esm/store/turso/vectors.js +98 -0
  350. package/dist/esm/store/turso/vectors.js.map +1 -0
  351. package/dist/esm/store/types.js +2 -3
  352. package/dist/esm/store/types.js.map +1 -1
  353. package/dist/esm/store/vectors.js +2 -3
  354. package/dist/esm/store/vectors.js.map +1 -1
  355. package/dist/esm/text/segment.d.ts +1 -0
  356. package/dist/esm/text/segment.js +13 -21
  357. package/dist/esm/text/segment.js.map +1 -1
  358. package/dist/esm/watch.js +2 -5
  359. package/dist/esm/watch.js.map +1 -1
  360. package/dist/esm/workers/parse.js.map +1 -1
  361. package/package.json +3 -1
  362. package/schema.json +2 -2
  363. package/skills/sense/SKILL.md +2 -2
  364. package/skills/sense-setup/SKILL.md +3 -3
  365. package/dist/cjs/store/meta.d.cts +0 -3
  366. package/dist/cjs/store/meta.d.ts +0 -3
  367. package/dist/cjs/store/meta.js +0 -215
  368. package/dist/cjs/store/meta.js.map +0 -1
  369. package/dist/esm/store/meta.d.ts +0 -3
  370. package/dist/esm/store/meta.js +0 -17
  371. package/dist/esm/store/meta.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/index.ts"],"sourcesContent":["// Rows -> a backing store: registry + openStore(cfg). Parsing lives in scan.ts; everything\n// beyond frontmatter + content lives in src/features/.\n\nimport type { ResolvedConfig, StoreName } from '../config/index.ts';\nimport { anyPresetEmbeds, storeName } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { openDuckdb } from './duckdb/open.ts';\nimport { CAPABILITIES as DUCKDB_CAPABILITIES } from './duckdb/store.ts';\nimport type { OpenResult } from './sqlite/open.ts';\nimport { openSqlite } from './sqlite/open.ts';\nimport { CAPABILITIES as SQLITE_CAPABILITIES } from './sqlite/store.ts';\nimport { openTurso } from './turso/open.ts';\nimport { CAPABILITIES as TURSO_CAPABILITIES } from './turso/store.ts';\nimport type { Capability } from './types.ts';\n\ninterface StoreEntry {\n capabilities: ReadonlySet<Capability>;\n open: (cfg: ResolvedConfig) => Promise<OpenResult>;\n}\n\n// Capabilities are checked against the registry entry before opening (no wasted connect/reconcile\n// work for a config that cannot be satisfied), then the store itself opens.\nconst REGISTRY: Record<StoreName, StoreEntry> = {\n sqlite: { capabilities: SQLITE_CAPABILITIES, open: openSqlite },\n duckdb: { capabilities: DUCKDB_CAPABILITIES, open: openDuckdb },\n turso: { capabilities: TURSO_CAPABILITIES, open: openTurso },\n};\n\nfunction entryFor(name: StoreName): StoreEntry {\n const entry = REGISTRY[name];\n if (!entry) throw new SenseError('STORE_UNKNOWN', `unknown backing store \"${name}\"; available: ${Object.keys(REGISTRY).join(', ')}`);\n return entry;\n}\n\nexport async function openStore(cfg: ResolvedConfig): Promise<OpenResult> {\n const name = storeName(cfg);\n const entry = entryFor(name);\n\n // The one unambiguous, config-level capability need: an embed block some preset actually\n // uses for vectors. Word/lexical search has no equivalent unambiguous signal at this level\n // (every preset defaults to wanting \"words\"), so a store lacking 'lexical' fails loudly at\n // first use instead (see duckdb/store.ts's lexical.query()).\n if (anyPresetEmbeds(cfg) && !entry.capabilities.has('vectors')) {\n throw new SenseError('STORE_CAPABILITY_MISSING', `store \"${name}\" does not implement \"vectors\" in this build, but this config's \"embed\" block is in use by at least one preset; remove or narrow it, or set \"store\" to a store that supports vectors (sqlite)`);\n }\n\n return entry.open(cfg);\n}\n\n// watch keeps its connection open for the run, so a file-locking store would fail every\n// other command on the tree; check before open, as openStore does for \"vectors\".\nexport function requireWatchConcurrency(cfg: ResolvedConfig): void {\n const name = storeName(cfg);\n if (!entryFor(name).capabilities.has('watch-concurrency')) {\n throw new SenseError('STORE_CAPABILITY_MISSING', `store \"${name}\" does not implement \"watch-concurrency\" in this build; sense watch holds the store open for its whole run, and this store locks the cache file, so every other command on this tree would fail to open; set \"store\" to \"sqlite\" in sense.config.json`);\n }\n}\n\nexport { getMeta, setMeta } from './meta.ts';\nexport type { OpenResult } from './sqlite/open.ts';\nexport { clearCache, DB_FILENAME, docCount, SCHEMA_VERSION } from './sqlite/open.ts';\nexport type { Capability, Connection, DocumentStore, LexicalHit, LexicalIndex, LexicalQueryOptions, RawStatement, RunResult, SqlSession, Statement, Store, VectorCandidate, VectorSimilar, VectorStore, VectorWriteRow } from './types.ts';\n"],"names":["anyPresetEmbeds","storeName","SenseError","openDuckdb","CAPABILITIES","DUCKDB_CAPABILITIES","openSqlite","SQLITE_CAPABILITIES","openTurso","TURSO_CAPABILITIES","REGISTRY","sqlite","capabilities","open","duckdb","turso","entryFor","name","entry","Object","keys","join","openStore","cfg","has","requireWatchConcurrency","getMeta","setMeta","clearCache","DB_FILENAME","docCount","SCHEMA_VERSION"],"mappings":"AAAA,2FAA2F;AAC3F,uDAAuD;AAGvD,SAASA,eAAe,EAAEC,SAAS,QAAQ,qBAAqB;AAChE,SAASC,UAAU,QAAQ,eAAe;AAC1C,SAASC,UAAU,QAAQ,mBAAmB;AAC9C,SAASC,gBAAgBC,mBAAmB,QAAQ,oBAAoB;AAExE,SAASC,UAAU,QAAQ,mBAAmB;AAC9C,SAASF,gBAAgBG,mBAAmB,QAAQ,oBAAoB;AACxE,SAASC,SAAS,QAAQ,kBAAkB;AAC5C,SAASJ,gBAAgBK,kBAAkB,QAAQ,mBAAmB;AAQtE,kGAAkG;AAClG,4EAA4E;AAC5E,MAAMC,WAA0C;IAC9CC,QAAQ;QAAEC,cAAcL;QAAqBM,MAAMP;IAAW;IAC9DQ,QAAQ;QAAEF,cAAcP;QAAqBQ,MAAMV;IAAW;IAC9DY,OAAO;QAAEH,cAAcH;QAAoBI,MAAML;IAAU;AAC7D;AAEA,SAASQ,SAASC,IAAe;IAC/B,MAAMC,QAAQR,QAAQ,CAACO,KAAK;IAC5B,IAAI,CAACC,OAAO,MAAM,IAAIhB,WAAW,iBAAiB,CAAC,uBAAuB,EAAEe,KAAK,cAAc,EAAEE,OAAOC,IAAI,CAACV,UAAUW,IAAI,CAAC,OAAO;IACnI,OAAOH;AACT;AAEA,OAAO,eAAeI,UAAUC,GAAmB;IACjD,MAAMN,OAAOhB,UAAUsB;IACvB,MAAML,QAAQF,SAASC;IAEvB,yFAAyF;IACzF,2FAA2F;IAC3F,2FAA2F;IAC3F,6DAA6D;IAC7D,IAAIjB,gBAAgBuB,QAAQ,CAACL,MAAMN,YAAY,CAACY,GAAG,CAAC,YAAY;QAC9D,MAAM,IAAItB,WAAW,4BAA4B,CAAC,OAAO,EAAEe,KAAK,6LAA6L,CAAC;IAChQ;IAEA,OAAOC,MAAML,IAAI,CAACU;AACpB;AAEA,wFAAwF;AACxF,iFAAiF;AACjF,OAAO,SAASE,wBAAwBF,GAAmB;IACzD,MAAMN,OAAOhB,UAAUsB;IACvB,IAAI,CAACP,SAASC,MAAML,YAAY,CAACY,GAAG,CAAC,sBAAsB;QACzD,MAAM,IAAItB,WAAW,4BAA4B,CAAC,OAAO,EAAEe,KAAK,qPAAqP,CAAC;IACxT;AACF;AAEA,SAASS,OAAO,EAAEC,OAAO,QAAQ,YAAY;AAE7C,SAASC,UAAU,EAAEC,WAAW,EAAEC,QAAQ,EAAEC,cAAc,QAAQ,mBAAmB"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/index.ts"],"sourcesContent":["// Rows -> a backing store: registry + openStore(cfg). Parsing lives in scan.ts; everything\n// beyond frontmatter + content lives in src/features/.\n\nimport type { ResolvedConfig, StoreName } from '../config/index.ts';\nimport { anyPresetEmbeds, storeName } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { openDuckdb } from './duckdb/open.ts';\nimport { CAPABILITIES as DUCKDB_CAPABILITIES } from './duckdb/store.ts';\nimport type { OpenResult } from './sqlite/open.ts';\nimport { openSqlite } from './sqlite/open.ts';\nimport { CAPABILITIES as SQLITE_CAPABILITIES } from './sqlite/store.ts';\nimport { openTurso } from './turso/open.ts';\nimport { CAPABILITIES as TURSO_CAPABILITIES } from './turso/store.ts';\nimport type { Capability } from './types.ts';\n\ninterface StoreEntry {\n capabilities: ReadonlySet<Capability>;\n open: (cfg: ResolvedConfig) => Promise<OpenResult>;\n}\n\n// Capabilities are checked against the registry entry before opening (no wasted connect/reconcile\n// work for a config that cannot be satisfied), then the store itself opens.\nconst REGISTRY: Record<StoreName, StoreEntry> = {\n sqlite: { capabilities: SQLITE_CAPABILITIES, open: openSqlite },\n duckdb: { capabilities: DUCKDB_CAPABILITIES, open: openDuckdb },\n turso: { capabilities: TURSO_CAPABILITIES, open: openTurso },\n};\n\nfunction entryFor(name: StoreName): StoreEntry {\n const entry = REGISTRY[name];\n if (!entry) throw new SenseError('STORE_UNKNOWN', `unknown backing store \"${name}\"; available: ${Object.keys(REGISTRY).join(', ')}`);\n return entry;\n}\n\nexport async function openStore(cfg: ResolvedConfig): Promise<OpenResult> {\n const name = storeName(cfg);\n const entry = entryFor(name);\n\n // The one unambiguous, config-level capability need: an embed block some preset actually uses\n // for vectors. Lexical search has no such signal at this level (every preset defaults to \"words\"), so a store lacking it fails loudly at first use.\n if (anyPresetEmbeds(cfg) && !entry.capabilities.has('vectors')) {\n throw new SenseError('STORE_CAPABILITY_MISSING', `store \"${name}\" does not implement \"vectors\" in this build, but this config's \"embed\" block is in use by at least one preset; remove or narrow it, or set \"store\" to a store that supports vectors (sqlite)`);\n }\n\n return entry.open(cfg);\n}\n\n// watch keeps its connection open for the run, so a file-locking store would fail every\n// other command on the tree; check before open, as openStore does for \"vectors\".\nexport function requireWatchConcurrency(cfg: ResolvedConfig): void {\n const name = storeName(cfg);\n if (!entryFor(name).capabilities.has('watch-concurrency')) {\n throw new SenseError('STORE_CAPABILITY_MISSING', `store \"${name}\" does not implement \"watch-concurrency\" in this build; sense watch holds the store open for its whole run, and this store locks the cache file, so every other command on this tree would fail to open; set \"store\" to \"sqlite\" in sense.config.json`);\n }\n}\n\nexport { clearCache } from './cache.ts';\nexport { getMeta, setMeta } from './shared.ts';\nexport type { OpenResult } from './sqlite/open.ts';\nexport { DB_FILENAME, docCount, SCHEMA_VERSION } from './sqlite/open.ts';\nexport type { Capability, Connection, DocumentStore, LexicalHit, LexicalIndex, LexicalQueryOptions, RawStatement, RunResult, SqlSession, Statement, Store, VectorCandidate, VectorSimilar, VectorStore, VectorWriteRow } from './types.ts';\n"],"names":["anyPresetEmbeds","storeName","SenseError","openDuckdb","CAPABILITIES","DUCKDB_CAPABILITIES","openSqlite","SQLITE_CAPABILITIES","openTurso","TURSO_CAPABILITIES","REGISTRY","sqlite","capabilities","open","duckdb","turso","entryFor","name","entry","Object","keys","join","openStore","cfg","has","requireWatchConcurrency","clearCache","getMeta","setMeta","DB_FILENAME","docCount","SCHEMA_VERSION"],"mappings":"AAAA,2FAA2F;AAC3F,uDAAuD;AAGvD,SAASA,eAAe,EAAEC,SAAS,QAAQ,qBAAqB;AAChE,SAASC,UAAU,QAAQ,eAAe;AAC1C,SAASC,UAAU,QAAQ,mBAAmB;AAC9C,SAASC,gBAAgBC,mBAAmB,QAAQ,oBAAoB;AAExE,SAASC,UAAU,QAAQ,mBAAmB;AAC9C,SAASF,gBAAgBG,mBAAmB,QAAQ,oBAAoB;AACxE,SAASC,SAAS,QAAQ,kBAAkB;AAC5C,SAASJ,gBAAgBK,kBAAkB,QAAQ,mBAAmB;AAQtE,kGAAkG;AAClG,4EAA4E;AAC5E,MAAMC,WAA0C;IAC9CC,QAAQ;QAAEC,cAAcL;QAAqBM,MAAMP;IAAW;IAC9DQ,QAAQ;QAAEF,cAAcP;QAAqBQ,MAAMV;IAAW;IAC9DY,OAAO;QAAEH,cAAcH;QAAoBI,MAAML;IAAU;AAC7D;AAEA,SAASQ,SAASC,IAAe;IAC/B,MAAMC,QAAQR,QAAQ,CAACO,KAAK;IAC5B,IAAI,CAACC,OAAO,MAAM,IAAIhB,WAAW,iBAAiB,CAAC,uBAAuB,EAAEe,KAAK,cAAc,EAAEE,OAAOC,IAAI,CAACV,UAAUW,IAAI,CAAC,OAAO;IACnI,OAAOH;AACT;AAEA,OAAO,eAAeI,UAAUC,GAAmB;IACjD,MAAMN,OAAOhB,UAAUsB;IACvB,MAAML,QAAQF,SAASC;IAEvB,8FAA8F;IAC9F,oJAAoJ;IACpJ,IAAIjB,gBAAgBuB,QAAQ,CAACL,MAAMN,YAAY,CAACY,GAAG,CAAC,YAAY;QAC9D,MAAM,IAAItB,WAAW,4BAA4B,CAAC,OAAO,EAAEe,KAAK,6LAA6L,CAAC;IAChQ;IAEA,OAAOC,MAAML,IAAI,CAACU;AACpB;AAEA,wFAAwF;AACxF,iFAAiF;AACjF,OAAO,SAASE,wBAAwBF,GAAmB;IACzD,MAAMN,OAAOhB,UAAUsB;IACvB,IAAI,CAACP,SAASC,MAAML,YAAY,CAACY,GAAG,CAAC,sBAAsB;QACzD,MAAM,IAAItB,WAAW,4BAA4B,CAAC,OAAO,EAAEe,KAAK,qPAAqP,CAAC;IACxT;AACF;AAEA,SAASS,UAAU,QAAQ,aAAa;AACxC,SAASC,OAAO,EAAEC,OAAO,QAAQ,cAAc;AAE/C,SAASC,WAAW,EAAEC,QAAQ,EAAEC,cAAc,QAAQ,mBAAmB"}
@@ -3,17 +3,12 @@ import Module from 'node:module';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
  import { SenseError } from '../errors.js';
6
- // install-module-linked itself is small, but a target native package can be large (@duckdb/node-
7
- // api's ~110MB is the current instance): deferred the same way embed/static.ts defers
8
- // @huggingface/tokenizers, so requiring it stays inside the already-gated branch below rather
9
- // than running for every store/index.ts load.
6
+ // install-module-linked is small, but a target native package can be large (@duckdb/node-api's
7
+ // ~110MB), so it is required lazily inside the gated branch below, not for every store/index.ts load.
10
8
  const _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;
11
9
  const _dirname = dirname(typeof __filename !== 'undefined' ? __filename : fileURLToPath(import.meta.url));
12
- // Walks up from this file's own directory to find the real package.json, rather than a fixed
13
- // dot-count from __dirname (xz-compat's `../../node_modules`): this file sits at a different
14
- // depth under src/ during `tsds test:node` than under the built dist/{cjs,esm}/store output, so a
15
- // hardcoded depth is right for one and wrong for the other. Works the same way for a
16
- // globally-installed `sense`, whose node_modules this resolves to -- never the caller's cwd.
10
+ // Walks up from this file's own directory rather than a fixed dot-count: it sits at a
11
+ // different depth under src/ than under built dist/{cjs,esm}/store output.
17
12
  export function packageNodeModules() {
18
13
  let dir = _dirname;
19
14
  for(;;){
@@ -28,12 +23,8 @@ export function packageNodeModules() {
28
23
  }
29
24
  }
30
25
  const installing = new Map();
31
- // De-dupes concurrent installs within this process (see spawn-term's loadInk for the shape).
32
- // The install here is async -- the load site already awaits a dynamic import -- so a bare
33
- // attempted flag (xz-compat's `installationAttempted`, enough for its synchronous single-target
34
- // install) isn't enough on its own; concurrent callers instead await the same in-flight promise,
35
- // keyed by specifier and target so tests exercising several installs in one process don't share
36
- // one slot. A failed install is not cached, so a later call retries.
26
+ // De-dupes concurrent installs: since the install is async, concurrent callers await the same
27
+ // in-flight promise, keyed by specifier and target. A failed install is not cached, so a later call retries.
37
28
  function installOnce(specifier, nodeModulesPath) {
38
29
  const key = `${specifier}\n${nodeModulesPath}`;
39
30
  let promise = installing.get(key);
@@ -47,12 +38,8 @@ function installOnce(specifier, nodeModulesPath) {
47
38
  }
48
39
  return promise;
49
40
  }
50
- // Node caches a bare specifier's package resolution -- including a "not found" miss -- for the
51
- // life of the process, so retrying `import(importName)` right after installing sees the same
52
- // stale miss and falls through to a bogus index.js guess instead of the package's real entry
53
- // (verified against a real global install: the plain-specifier retry failed there, this did
54
- // not). Reading the freshly-installed package's own package.json and importing its entry file
55
- // by an absolute file: URL sidesteps that stale cache instead of re-resolving the bare name.
41
+ // Node caches a bare specifier's resolution, including a "not found" miss, for the process
42
+ // lifetime; importing the entry by an absolute file: URL instead of the bare name sidesteps that.
56
43
  function resolveEntryUrl(importName, nodeModulesPath) {
57
44
  var _pkg_main;
58
45
  const pkgDir = join(nodeModulesPath, ...importName.split('/'));
@@ -60,10 +47,8 @@ function resolveEntryUrl(importName, nodeModulesPath) {
60
47
  const entry = typeof pkg.exports === 'string' ? pkg.exports : (_pkg_main = pkg.main) !== null && _pkg_main !== void 0 ? _pkg_main : 'index.js';
61
48
  return pathToFileURL(join(pkgDir, entry)).href;
62
49
  }
63
- // Import-then-install-then-retry, over an injectable importName so the failure path (import AND
64
- // install both fail) can be exercised for real in tests without downloading a real store's native
65
- // binding. importName defaults to descriptor.pkg, which is what production always wants; the
66
- // thrown errors name descriptor.pkg regardless, since that is what a real user needs to install.
50
+ // Import-then-install-then-retry, over an injectable importName so the failure path can be
51
+ // exercised in tests without a real native binding. Thrown errors always name descriptor.pkg.
67
52
  export async function loadOrInstall(descriptor, nodeModulesPath, importName = descriptor.pkg) {
68
53
  try {
69
54
  return await import(importName);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/native.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport Module from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { SenseError } from '../errors.ts';\n\n// A store's optional native package, and the strings its error messages and install notice name.\nexport interface NativeDescriptor {\n store: string;\n pkg: string;\n sizeHint: string;\n}\n\n// install-module-linked itself is small, but a target native package can be large (@duckdb/node-\n// api's ~110MB is the current instance): deferred the same way embed/static.ts defers\n// @huggingface/tokenizers, so requiring it stays inside the already-gated branch below rather\n// than running for every store/index.ts load.\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\nconst _dirname = dirname(typeof __filename !== 'undefined' ? __filename : fileURLToPath(import.meta.url));\n\n// Walks up from this file's own directory to find the real package.json, rather than a fixed\n// dot-count from __dirname (xz-compat's `../../node_modules`): this file sits at a different\n// depth under src/ during `tsds test:node` than under the built dist/{cjs,esm}/store output, so a\n// hardcoded depth is right for one and wrong for the other. Works the same way for a\n// globally-installed `sense`, whose node_modules this resolves to -- never the caller's cwd.\nexport function packageNodeModules(): string {\n let dir = _dirname;\n for (;;) {\n const candidate = join(dir, 'package.json');\n if (existsSync(candidate)) {\n const pkg = JSON.parse(readFileSync(candidate, 'utf8')) as { name?: string };\n if (pkg.name === 'sensemaking') return join(dir, 'node_modules');\n }\n const parent = dirname(dir);\n if (parent === dir) throw new Error('could not locate the sensemaking package root to install a native dependency into');\n dir = parent;\n }\n}\n\nconst installing = new Map<string, Promise<void>>();\n\n// De-dupes concurrent installs within this process (see spawn-term's loadInk for the shape).\n// The install here is async -- the load site already awaits a dynamic import -- so a bare\n// attempted flag (xz-compat's `installationAttempted`, enough for its synchronous single-target\n// install) isn't enough on its own; concurrent callers instead await the same in-flight promise,\n// keyed by specifier and target so tests exercising several installs in one process don't share\n// one slot. A failed install is not cached, so a later call retries.\nfunction installOnce(specifier: string, nodeModulesPath: string): Promise<void> {\n const key = `${specifier}\\n${nodeModulesPath}`;\n let promise = installing.get(key);\n if (!promise) {\n const installModuleLinked = (_require('install-module-linked') as { default: (specifier: string, nodeModulesPath: string) => Promise<string> }).default;\n promise = installModuleLinked(specifier, nodeModulesPath).then(\n () => {},\n (err) => {\n installing.delete(key);\n throw err;\n }\n );\n installing.set(key, promise);\n }\n return promise;\n}\n\n// Node caches a bare specifier's package resolution -- including a \"not found\" miss -- for the\n// life of the process, so retrying `import(importName)` right after installing sees the same\n// stale miss and falls through to a bogus index.js guess instead of the package's real entry\n// (verified against a real global install: the plain-specifier retry failed there, this did\n// not). Reading the freshly-installed package's own package.json and importing its entry file\n// by an absolute file: URL sidesteps that stale cache instead of re-resolving the bare name.\nfunction resolveEntryUrl(importName: string, nodeModulesPath: string): string {\n const pkgDir = join(nodeModulesPath, ...importName.split('/'));\n const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')) as { main?: string; exports?: string };\n const entry = typeof pkg.exports === 'string' ? pkg.exports : (pkg.main ?? 'index.js');\n return pathToFileURL(join(pkgDir, entry)).href;\n}\n\n// Import-then-install-then-retry, over an injectable importName so the failure path (import AND\n// install both fail) can be exercised for real in tests without downloading a real store's native\n// binding. importName defaults to descriptor.pkg, which is what production always wants; the\n// thrown errors name descriptor.pkg regardless, since that is what a real user needs to install.\nexport async function loadOrInstall<T>(descriptor: NativeDescriptor, nodeModulesPath: string, importName: string = descriptor.pkg): Promise<T> {\n try {\n return (await import(importName)) as T;\n } catch {\n console.error(`sense: store \"${descriptor.store}\" needs ${descriptor.pkg}; installing it now (one-time download, ${descriptor.sizeHint})...`);\n try {\n await installOnce(importName, nodeModulesPath);\n } catch (installErr) {\n throw new SenseError(\n 'STORE_DEPENDENCY_MISSING',\n `store \"${descriptor.store}\" needs ${descriptor.pkg}, and installing it automatically failed (${(installErr as Error).message}); this can happen offline, in a sandboxed environment, or when node_modules is read-only or owned by another user (e.g. under a global install) -- run \\`npm install ${descriptor.pkg}\\` yourself and try again`\n );\n }\n try {\n return (await import(resolveEntryUrl(importName, nodeModulesPath))) as T;\n } catch (loadErr) {\n throw new SenseError(\n 'STORE_DEPENDENCY_MISSING',\n `store \"${descriptor.store}\" installed ${descriptor.pkg} but it could not be loaded (${(loadErr as Error).message}); it may not be available for this platform (${process.platform}-${process.arch}) -- run \\`npm install ${descriptor.pkg}\\` to see the underlying error`\n );\n }\n }\n}\n"],"names":["existsSync","readFileSync","Module","dirname","join","fileURLToPath","pathToFileURL","SenseError","_require","require","createRequire","url","_dirname","__filename","packageNodeModules","dir","candidate","pkg","JSON","parse","name","parent","Error","installing","Map","installOnce","specifier","nodeModulesPath","key","promise","get","installModuleLinked","default","then","err","delete","set","resolveEntryUrl","importName","pkgDir","split","entry","exports","main","href","loadOrInstall","descriptor","console","error","store","sizeHint","installErr","message","loadErr","process","platform","arch"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/native.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport Module from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { SenseError } from '../errors.ts';\n\n// A store's optional native package, and the strings its error messages and install notice name.\nexport interface NativeDescriptor {\n store: string;\n pkg: string;\n sizeHint: string;\n}\n\n// install-module-linked is small, but a target native package can be large (@duckdb/node-api's\n// ~110MB), so it is required lazily inside the gated branch below, not for every store/index.ts load.\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\nconst _dirname = dirname(typeof __filename !== 'undefined' ? __filename : fileURLToPath(import.meta.url));\n\n// Walks up from this file's own directory rather than a fixed dot-count: it sits at a\n// different depth under src/ than under built dist/{cjs,esm}/store output.\nexport function packageNodeModules(): string {\n let dir = _dirname;\n for (;;) {\n const candidate = join(dir, 'package.json');\n if (existsSync(candidate)) {\n const pkg = JSON.parse(readFileSync(candidate, 'utf8')) as { name?: string };\n if (pkg.name === 'sensemaking') return join(dir, 'node_modules');\n }\n const parent = dirname(dir);\n if (parent === dir) throw new Error('could not locate the sensemaking package root to install a native dependency into');\n dir = parent;\n }\n}\n\nconst installing = new Map<string, Promise<void>>();\n\n// De-dupes concurrent installs: since the install is async, concurrent callers await the same\n// in-flight promise, keyed by specifier and target. A failed install is not cached, so a later call retries.\nfunction installOnce(specifier: string, nodeModulesPath: string): Promise<void> {\n const key = `${specifier}\\n${nodeModulesPath}`;\n let promise = installing.get(key);\n if (!promise) {\n const installModuleLinked = (_require('install-module-linked') as { default: (specifier: string, nodeModulesPath: string) => Promise<string> }).default;\n promise = installModuleLinked(specifier, nodeModulesPath).then(\n () => {},\n (err) => {\n installing.delete(key);\n throw err;\n }\n );\n installing.set(key, promise);\n }\n return promise;\n}\n\n// Node caches a bare specifier's resolution, including a \"not found\" miss, for the process\n// lifetime; importing the entry by an absolute file: URL instead of the bare name sidesteps that.\nfunction resolveEntryUrl(importName: string, nodeModulesPath: string): string {\n const pkgDir = join(nodeModulesPath, ...importName.split('/'));\n const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')) as { main?: string; exports?: string };\n const entry = typeof pkg.exports === 'string' ? pkg.exports : (pkg.main ?? 'index.js');\n return pathToFileURL(join(pkgDir, entry)).href;\n}\n\n// Import-then-install-then-retry, over an injectable importName so the failure path can be\n// exercised in tests without a real native binding. Thrown errors always name descriptor.pkg.\nexport async function loadOrInstall<T>(descriptor: NativeDescriptor, nodeModulesPath: string, importName: string = descriptor.pkg): Promise<T> {\n try {\n return (await import(importName)) as T;\n } catch {\n console.error(`sense: store \"${descriptor.store}\" needs ${descriptor.pkg}; installing it now (one-time download, ${descriptor.sizeHint})...`);\n try {\n await installOnce(importName, nodeModulesPath);\n } catch (installErr) {\n throw new SenseError(\n 'STORE_DEPENDENCY_MISSING',\n `store \"${descriptor.store}\" needs ${descriptor.pkg}, and installing it automatically failed (${(installErr as Error).message}); this can happen offline, in a sandboxed environment, or when node_modules is read-only or owned by another user (e.g. under a global install) -- run \\`npm install ${descriptor.pkg}\\` yourself and try again`\n );\n }\n try {\n return (await import(resolveEntryUrl(importName, nodeModulesPath))) as T;\n } catch (loadErr) {\n throw new SenseError(\n 'STORE_DEPENDENCY_MISSING',\n `store \"${descriptor.store}\" installed ${descriptor.pkg} but it could not be loaded (${(loadErr as Error).message}); it may not be available for this platform (${process.platform}-${process.arch}) -- run \\`npm install ${descriptor.pkg}\\` to see the underlying error`\n );\n }\n }\n}\n"],"names":["existsSync","readFileSync","Module","dirname","join","fileURLToPath","pathToFileURL","SenseError","_require","require","createRequire","url","_dirname","__filename","packageNodeModules","dir","candidate","pkg","JSON","parse","name","parent","Error","installing","Map","installOnce","specifier","nodeModulesPath","key","promise","get","installModuleLinked","default","then","err","delete","set","resolveEntryUrl","importName","pkgDir","split","entry","exports","main","href","loadOrInstall","descriptor","console","error","store","sizeHint","installErr","message","loadErr","process","platform","arch"],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import posix from 'node:path/posix';
2
2
  // has(field, value) / basename(path, suffix): the two sense-supplied SQL functions with no
3
- // engine-specific pieces, shared by sqlite/sql-functions.ts and duckdb/sql-functions.ts so each
4
- // store registers the identical implementation instead of a hand-copied one (DRY).
3
+ // engine-specific pieces, registered identically by each store instead of a hand-copied one.
5
4
  // JSON-array field -> membership, string field -> substring, NULL -> false.
6
5
  export function hasImpl(field, value) {
7
6
  if (field === null || field === undefined) return 0;
@@ -17,9 +16,8 @@ export function hasImpl(field, value) {
17
16
  }
18
17
  return String(field).includes(needle) ? 1 : 0;
19
18
  }
20
- // Unix basename(path, suffix?): the filename, minus suffix when it ends with one. Neither
21
- // engine has a filename function of its own, and LIKE tricks that fake one also match folder
22
- // names.
19
+ // Unix basename(path, suffix?): the filename, minus suffix when it ends with one. Neither engine
20
+ // has a filename function of its own, and LIKE tricks that fake one also match folder names.
23
21
  export function basenameImpl(path, suffix) {
24
22
  if (path === null || path === undefined) return null;
25
23
  const name = posix.basename(String(path));
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/sql-functions.ts"],"sourcesContent":["import posix from 'node:path/posix';\n\n// has(field, value) / basename(path, suffix): the two sense-supplied SQL functions with no\n// engine-specific pieces, shared by sqlite/sql-functions.ts and duckdb/sql-functions.ts so each\n// store registers the identical implementation instead of a hand-copied one (DRY).\n\n// JSON-array field -> membership, string field -> substring, NULL -> false.\nexport function hasImpl(field: unknown, value: unknown): number {\n if (field === null || field === undefined) return 0;\n const needle = String(value);\n if (typeof field === 'string') {\n if (field.startsWith('[')) {\n try {\n const parsed = JSON.parse(field);\n if (Array.isArray(parsed)) return parsed.some((item) => String(item) === needle) ? 1 : 0;\n } catch {}\n }\n return field.includes(needle) ? 1 : 0;\n }\n return String(field).includes(needle) ? 1 : 0;\n}\n\n// Unix basename(path, suffix?): the filename, minus suffix when it ends with one. Neither\n// engine has a filename function of its own, and LIKE tricks that fake one also match folder\n// names.\nexport function basenameImpl(path: unknown, suffix?: unknown): string | null {\n if (path === null || path === undefined) return null;\n const name = posix.basename(String(path));\n const tail = suffix === null || suffix === undefined ? '' : String(suffix);\n // POSIX: a suffix identical to the whole name is not removed (node's own basename strips it).\n return tail !== '' && tail !== name && name.endsWith(tail) ? name.slice(0, -tail.length) : name;\n}\n"],"names":["posix","hasImpl","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","basenameImpl","path","suffix","name","basename","tail","endsWith","slice","length"],"mappings":"AAAA,OAAOA,WAAW,kBAAkB;AAEpC,2FAA2F;AAC3F,gGAAgG;AAChG,mFAAmF;AAEnF,4EAA4E;AAC5E,OAAO,SAASC,QAAQC,KAAc,EAAEC,KAAc;IACpD,IAAID,UAAU,QAAQA,UAAUE,WAAW,OAAO;IAClD,MAAMC,SAASC,OAAOH;IACtB,IAAI,OAAOD,UAAU,UAAU;QAC7B,IAAIA,MAAMK,UAAU,CAAC,MAAM;YACzB,IAAI;gBACF,MAAMC,SAASC,KAAKC,KAAK,CAACR;gBAC1B,IAAIS,MAAMC,OAAO,CAACJ,SAAS,OAAOA,OAAOK,IAAI,CAAC,CAACC,OAASR,OAAOQ,UAAUT,UAAU,IAAI;YACzF,EAAE,OAAM,CAAC;QACX;QACA,OAAOH,MAAMa,QAAQ,CAACV,UAAU,IAAI;IACtC;IACA,OAAOC,OAAOJ,OAAOa,QAAQ,CAACV,UAAU,IAAI;AAC9C;AAEA,0FAA0F;AAC1F,6FAA6F;AAC7F,SAAS;AACT,OAAO,SAASW,aAAaC,IAAa,EAAEC,MAAgB;IAC1D,IAAID,SAAS,QAAQA,SAASb,WAAW,OAAO;IAChD,MAAMe,OAAOnB,MAAMoB,QAAQ,CAACd,OAAOW;IACnC,MAAMI,OAAOH,WAAW,QAAQA,WAAWd,YAAY,KAAKE,OAAOY;IACnE,8FAA8F;IAC9F,OAAOG,SAAS,MAAMA,SAASF,QAAQA,KAAKG,QAAQ,CAACD,QAAQF,KAAKI,KAAK,CAAC,GAAG,CAACF,KAAKG,MAAM,IAAIL;AAC7F"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/sql-functions.ts"],"sourcesContent":["import posix from 'node:path/posix';\n\n// has(field, value) / basename(path, suffix): the two sense-supplied SQL functions with no\n// engine-specific pieces, registered identically by each store instead of a hand-copied one.\n\n// JSON-array field -> membership, string field -> substring, NULL -> false.\nexport function hasImpl(field: unknown, value: unknown): number {\n if (field === null || field === undefined) return 0;\n const needle = String(value);\n if (typeof field === 'string') {\n if (field.startsWith('[')) {\n try {\n const parsed = JSON.parse(field);\n if (Array.isArray(parsed)) return parsed.some((item) => String(item) === needle) ? 1 : 0;\n } catch {}\n }\n return field.includes(needle) ? 1 : 0;\n }\n return String(field).includes(needle) ? 1 : 0;\n}\n\n// Unix basename(path, suffix?): the filename, minus suffix when it ends with one. Neither engine\n// has a filename function of its own, and LIKE tricks that fake one also match folder names.\nexport function basenameImpl(path: unknown, suffix?: unknown): string | null {\n if (path === null || path === undefined) return null;\n const name = posix.basename(String(path));\n const tail = suffix === null || suffix === undefined ? '' : String(suffix);\n // POSIX: a suffix identical to the whole name is not removed (node's own basename strips it).\n return tail !== '' && tail !== name && name.endsWith(tail) ? name.slice(0, -tail.length) : name;\n}\n"],"names":["posix","hasImpl","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","basenameImpl","path","suffix","name","basename","tail","endsWith","slice","length"],"mappings":"AAAA,OAAOA,WAAW,kBAAkB;AAEpC,2FAA2F;AAC3F,6FAA6F;AAE7F,4EAA4E;AAC5E,OAAO,SAASC,QAAQC,KAAc,EAAEC,KAAc;IACpD,IAAID,UAAU,QAAQA,UAAUE,WAAW,OAAO;IAClD,MAAMC,SAASC,OAAOH;IACtB,IAAI,OAAOD,UAAU,UAAU;QAC7B,IAAIA,MAAMK,UAAU,CAAC,MAAM;YACzB,IAAI;gBACF,MAAMC,SAASC,KAAKC,KAAK,CAACR;gBAC1B,IAAIS,MAAMC,OAAO,CAACJ,SAAS,OAAOA,OAAOK,IAAI,CAAC,CAACC,OAASR,OAAOQ,UAAUT,UAAU,IAAI;YACzF,EAAE,OAAM,CAAC;QACX;QACA,OAAOH,MAAMa,QAAQ,CAACV,UAAU,IAAI;IACtC;IACA,OAAOC,OAAOJ,OAAOa,QAAQ,CAACV,UAAU,IAAI;AAC9C;AAEA,iGAAiG;AACjG,6FAA6F;AAC7F,OAAO,SAASW,aAAaC,IAAa,EAAEC,MAAgB;IAC1D,IAAID,SAAS,QAAQA,SAASb,WAAW,OAAO;IAChD,MAAMe,OAAOnB,MAAMoB,QAAQ,CAACd,OAAOW;IACnC,MAAMI,OAAOH,WAAW,QAAQA,WAAWd,YAAY,KAAKE,OAAOY;IACnE,8FAA8F;IAC9F,OAAOG,SAAS,MAAMA,SAASF,QAAQA,KAAKG,QAAQ,CAACD,QAAQF,KAAKI,KAAK,CAAC,GAAG,CAACF,KAAKG,MAAM,IAAIL;AAC7F"}
@@ -1,8 +1,6 @@
1
- import { withTransaction } from '../transaction.js';
2
- // Wraps node:sqlite's synchronous DatabaseSync in the async Connection/Statement shape every
3
- // store presents. Every method below does its real work synchronously and returns an already-
4
- // resolved promise, so reconcile's loop, the feature hooks, and runBatch never actually cross
5
- // an async boundary internally -- no regression against the pre-Store synchronous code.
1
+ import { BEGIN_WRITE, withTransaction } from '../transaction.js';
2
+ // Wraps node:sqlite's synchronous DatabaseSync in the async Connection/Statement shape every store presents. Every method does its real
3
+ // work synchronously and returns an already-resolved promise, so reconcile's loop, the feature hooks, and runBatch never cross an async boundary internally.
6
4
  let SqliteStatement = class SqliteStatement {
7
5
  async run(...params) {
8
6
  return this.stmt.run(...params);
@@ -34,16 +32,14 @@ export function createConnection(db) {
34
32
  async prepare (sql) {
35
33
  return new SqliteStatement(db.prepare(sql));
36
34
  },
37
- // Prepares once and loops synchronously: today's code, unchanged, wrapped as one async call
38
- // so features share one Connection contract with DuckDB. Joins the caller's transaction when
39
- // there is one (reconcile); otherwise opens its own, so a standalone batch (a vector write
40
- // outside any Store.transaction()) stays atomic and commits once, not once per row.
35
+ // Prepares once and loops synchronously, wrapped as one async call so features share one
36
+ // Connection contract with DuckDB. Joins the caller's transaction when there is one (reconcile); otherwise opens its own, so a standalone batch stays atomic and commits once, not per row.
41
37
  async runBatch (sql, paramRows) {
42
38
  if (paramRows.length === 0) return;
43
39
  await withTransaction(conn, async ()=>{
44
40
  const stmt = db.prepare(sql);
45
41
  for (const row of paramRows)stmt.run(...row);
46
- });
42
+ }, BEGIN_WRITE);
47
43
  }
48
44
  };
49
45
  return conn;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/sqlite/connection.ts"],"sourcesContent":["import type { DatabaseSync, StatementSync } from 'node:sqlite';\nimport { withTransaction } from '../transaction.ts';\nimport type { Connection, RunResult, Statement } from '../types.ts';\n\n// Wraps node:sqlite's synchronous DatabaseSync in the async Connection/Statement shape every\n// store presents. Every method below does its real work synchronously and returns an already-\n// resolved promise, so reconcile's loop, the feature hooks, and runBatch never actually cross\n// an async boundary internally -- no regression against the pre-Store synchronous code.\nclass SqliteStatement implements Statement {\n private stmt: StatementSync;\n\n constructor(stmt: StatementSync) {\n this.stmt = stmt;\n }\n\n async run(...params: unknown[]): Promise<RunResult> {\n return this.stmt.run(...(params as Parameters<StatementSync['run']>));\n }\n\n async get(...params: unknown[]): Promise<unknown> {\n return this.stmt.get(...(params as Parameters<StatementSync['get']>));\n }\n\n async all(...params: unknown[]): Promise<unknown[]> {\n return this.stmt.all(...(params as Parameters<StatementSync['all']>));\n }\n\n async *iterate(...params: unknown[]): AsyncIterable<unknown> {\n yield* this.stmt.iterate(...(params as Parameters<StatementSync['iterate']>));\n }\n\n columns(): Array<{ name: string }> {\n return this.stmt.columns();\n }\n\n setReadBigInts(enabled: boolean): void {\n this.stmt.setReadBigInts(enabled);\n }\n}\n\nexport function createConnection(db: DatabaseSync): Connection {\n const conn: Connection = {\n async exec(sql: string): Promise<void> {\n db.exec(sql);\n },\n async prepare(sql: string): Promise<Statement> {\n return new SqliteStatement(db.prepare(sql));\n },\n // Prepares once and loops synchronously: today's code, unchanged, wrapped as one async call\n // so features share one Connection contract with DuckDB. Joins the caller's transaction when\n // there is one (reconcile); otherwise opens its own, so a standalone batch (a vector write\n // outside any Store.transaction()) stays atomic and commits once, not once per row.\n async runBatch(sql: string, paramRows: unknown[][]): Promise<void> {\n if (paramRows.length === 0) return;\n await withTransaction(conn, async () => {\n const stmt = db.prepare(sql);\n for (const row of paramRows) stmt.run(...(row as Parameters<StatementSync['run']>));\n });\n },\n };\n return conn;\n}\n"],"names":["withTransaction","SqliteStatement","run","params","stmt","get","all","iterate","columns","setReadBigInts","enabled","createConnection","db","conn","exec","sql","prepare","runBatch","paramRows","length","row"],"mappings":"AACA,SAASA,eAAe,QAAQ,oBAAoB;AAGpD,6FAA6F;AAC7F,8FAA8F;AAC9F,8FAA8F;AAC9F,wFAAwF;AACxF,IAAA,AAAMC,kBAAN,MAAMA;IAOJ,MAAMC,IAAI,GAAGC,MAAiB,EAAsB;QAClD,OAAO,IAAI,CAACC,IAAI,CAACF,GAAG,IAAKC;IAC3B;IAEA,MAAME,IAAI,GAAGF,MAAiB,EAAoB;QAChD,OAAO,IAAI,CAACC,IAAI,CAACC,GAAG,IAAKF;IAC3B;IAEA,MAAMG,IAAI,GAAGH,MAAiB,EAAsB;QAClD,OAAO,IAAI,CAACC,IAAI,CAACE,GAAG,IAAKH;IAC3B;IAEA,OAAOI,QAAQ,GAAGJ,MAAiB,EAA0B;QAC3D,OAAO,IAAI,CAACC,IAAI,CAACG,OAAO,IAAKJ;IAC/B;IAEAK,UAAmC;QACjC,OAAO,IAAI,CAACJ,IAAI,CAACI,OAAO;IAC1B;IAEAC,eAAeC,OAAgB,EAAQ;QACrC,IAAI,CAACN,IAAI,CAACK,cAAc,CAACC;IAC3B;IA1BA,YAAYN,IAAmB,CAAE;QAC/B,IAAI,CAACA,IAAI,GAAGA;IACd;AAyBF;AAEA,OAAO,SAASO,iBAAiBC,EAAgB;IAC/C,MAAMC,OAAmB;QACvB,MAAMC,MAAKC,GAAW;YACpBH,GAAGE,IAAI,CAACC;QACV;QACA,MAAMC,SAAQD,GAAW;YACvB,OAAO,IAAId,gBAAgBW,GAAGI,OAAO,CAACD;QACxC;QACA,4FAA4F;QAC5F,6FAA6F;QAC7F,2FAA2F;QAC3F,oFAAoF;QACpF,MAAME,UAASF,GAAW,EAAEG,SAAsB;YAChD,IAAIA,UAAUC,MAAM,KAAK,GAAG;YAC5B,MAAMnB,gBAAgBa,MAAM;gBAC1B,MAAMT,OAAOQ,GAAGI,OAAO,CAACD;gBACxB,KAAK,MAAMK,OAAOF,UAAWd,KAAKF,GAAG,IAAKkB;YAC5C;QACF;IACF;IACA,OAAOP;AACT"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/sqlite/connection.ts"],"sourcesContent":["import type { DatabaseSync, StatementSync } from 'node:sqlite';\nimport { BEGIN_WRITE, withTransaction } from '../transaction.ts';\nimport type { Connection, RunResult, Statement } from '../types.ts';\n\n// Wraps node:sqlite's synchronous DatabaseSync in the async Connection/Statement shape every store presents. Every method does its real\n// work synchronously and returns an already-resolved promise, so reconcile's loop, the feature hooks, and runBatch never cross an async boundary internally.\nclass SqliteStatement implements Statement {\n private stmt: StatementSync;\n\n constructor(stmt: StatementSync) {\n this.stmt = stmt;\n }\n\n async run(...params: unknown[]): Promise<RunResult> {\n return this.stmt.run(...(params as Parameters<StatementSync['run']>));\n }\n\n async get(...params: unknown[]): Promise<unknown> {\n return this.stmt.get(...(params as Parameters<StatementSync['get']>));\n }\n\n async all(...params: unknown[]): Promise<unknown[]> {\n return this.stmt.all(...(params as Parameters<StatementSync['all']>));\n }\n\n async *iterate(...params: unknown[]): AsyncIterable<unknown> {\n yield* this.stmt.iterate(...(params as Parameters<StatementSync['iterate']>));\n }\n\n columns(): Array<{ name: string }> {\n return this.stmt.columns();\n }\n\n setReadBigInts(enabled: boolean): void {\n this.stmt.setReadBigInts(enabled);\n }\n}\n\nexport function createConnection(db: DatabaseSync): Connection {\n const conn: Connection = {\n async exec(sql: string): Promise<void> {\n db.exec(sql);\n },\n async prepare(sql: string): Promise<Statement> {\n return new SqliteStatement(db.prepare(sql));\n },\n // Prepares once and loops synchronously, wrapped as one async call so features share one\n // Connection contract with DuckDB. Joins the caller's transaction when there is one (reconcile); otherwise opens its own, so a standalone batch stays atomic and commits once, not per row.\n async runBatch(sql: string, paramRows: unknown[][]): Promise<void> {\n if (paramRows.length === 0) return;\n await withTransaction(\n conn,\n async () => {\n const stmt = db.prepare(sql);\n for (const row of paramRows) stmt.run(...(row as Parameters<StatementSync['run']>));\n },\n BEGIN_WRITE\n );\n },\n };\n return conn;\n}\n"],"names":["BEGIN_WRITE","withTransaction","SqliteStatement","run","params","stmt","get","all","iterate","columns","setReadBigInts","enabled","createConnection","db","conn","exec","sql","prepare","runBatch","paramRows","length","row"],"mappings":"AACA,SAASA,WAAW,EAAEC,eAAe,QAAQ,oBAAoB;AAGjE,wIAAwI;AACxI,6JAA6J;AAC7J,IAAA,AAAMC,kBAAN,MAAMA;IAOJ,MAAMC,IAAI,GAAGC,MAAiB,EAAsB;QAClD,OAAO,IAAI,CAACC,IAAI,CAACF,GAAG,IAAKC;IAC3B;IAEA,MAAME,IAAI,GAAGF,MAAiB,EAAoB;QAChD,OAAO,IAAI,CAACC,IAAI,CAACC,GAAG,IAAKF;IAC3B;IAEA,MAAMG,IAAI,GAAGH,MAAiB,EAAsB;QAClD,OAAO,IAAI,CAACC,IAAI,CAACE,GAAG,IAAKH;IAC3B;IAEA,OAAOI,QAAQ,GAAGJ,MAAiB,EAA0B;QAC3D,OAAO,IAAI,CAACC,IAAI,CAACG,OAAO,IAAKJ;IAC/B;IAEAK,UAAmC;QACjC,OAAO,IAAI,CAACJ,IAAI,CAACI,OAAO;IAC1B;IAEAC,eAAeC,OAAgB,EAAQ;QACrC,IAAI,CAACN,IAAI,CAACK,cAAc,CAACC;IAC3B;IA1BA,YAAYN,IAAmB,CAAE;QAC/B,IAAI,CAACA,IAAI,GAAGA;IACd;AAyBF;AAEA,OAAO,SAASO,iBAAiBC,EAAgB;IAC/C,MAAMC,OAAmB;QACvB,MAAMC,MAAKC,GAAW;YACpBH,GAAGE,IAAI,CAACC;QACV;QACA,MAAMC,SAAQD,GAAW;YACvB,OAAO,IAAId,gBAAgBW,GAAGI,OAAO,CAACD;QACxC;QACA,yFAAyF;QACzF,4LAA4L;QAC5L,MAAME,UAASF,GAAW,EAAEG,SAAsB;YAChD,IAAIA,UAAUC,MAAM,KAAK,GAAG;YAC5B,MAAMnB,gBACJa,MACA;gBACE,MAAMT,OAAOQ,GAAGI,OAAO,CAACD;gBACxB,KAAK,MAAMK,OAAOF,UAAWd,KAAKF,GAAG,IAAKkB;YAC5C,GACArB;QAEJ;IACF;IACA,OAAOc;AACT"}
@@ -1,10 +1,6 @@
1
1
  import { quoteIdent } from '../shared.js';
2
- // One aggregate query, one result row: COUNT + GROUP_CONCAT(DISTINCT typeof()) per column, so
3
- // the query stays O(columns) rather than O(rows x columns). typeof()'s output already is the
4
- // shared vocabulary (integer/real/text -- mapValue() never binds a boolean or blob), so no
5
- // mapping step is needed here (contrast duckdb/fieldStats.ts). GROUP_CONCAT(DISTINCT ...)'s
6
- // order is not guaranteed sorted (spike-verified: 3+ distinct types come back in insertion
7
- // order), so the type set is sorted in JS.
2
+ // One aggregate query per column (COUNT + GROUP_CONCAT(DISTINCT typeof())), O(columns) not O(rows x columns);
3
+ // typeof()'s output is already the shared vocabulary, and GROUP_CONCAT's order is unsorted, so JS sorts the type set.
8
4
  export async function fieldStats(conn, columns, scopeWhere) {
9
5
  const exprs = columns.map((name, i)=>{
10
6
  const quoted = quoteIdent(name);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/sqlite/fieldStats.ts"],"sourcesContent":["import { quoteIdent } from '../shared.ts';\nimport type { Connection, FieldStat } from '../types.ts';\n\n// One aggregate query, one result row: COUNT + GROUP_CONCAT(DISTINCT typeof()) per column, so\n// the query stays O(columns) rather than O(rows x columns). typeof()'s output already is the\n// shared vocabulary (integer/real/text -- mapValue() never binds a boolean or blob), so no\n// mapping step is needed here (contrast duckdb/fieldStats.ts). GROUP_CONCAT(DISTINCT ...)'s\n// order is not guaranteed sorted (spike-verified: 3+ distinct types come back in insertion\n// order), so the type set is sorted in JS.\nexport async function fieldStats(conn: Connection, columns: string[], scopeWhere: string): Promise<FieldStat[]> {\n const exprs = columns.map((name, i) => {\n const quoted = quoteIdent(name);\n return `COUNT(${quoted}) AS n${i}, GROUP_CONCAT(DISTINCT typeof(${quoted})) FILTER (WHERE ${quoted} IS NOT NULL) AS t${i}`;\n });\n const stmt = await conn.prepare(`SELECT ${exprs.join(', ')} FROM frontmatter ${scopeWhere}`);\n const row = (await stmt.get()) as Record<string, number | string | null>;\n return columns.map((name, i) => ({\n field: name,\n coverage: Number(row[`n${i}`] ?? 0),\n type: [...new Set(((row[`t${i}`] as string) ?? '').split(',').filter(Boolean))].sort().join(','),\n }));\n}\n"],"names":["quoteIdent","fieldStats","conn","columns","scopeWhere","exprs","map","name","i","quoted","stmt","prepare","join","row","get","field","coverage","Number","type","Set","split","filter","Boolean","sort"],"mappings":"AAAA,SAASA,UAAU,QAAQ,eAAe;AAG1C,8FAA8F;AAC9F,6FAA6F;AAC7F,2FAA2F;AAC3F,4FAA4F;AAC5F,2FAA2F;AAC3F,2CAA2C;AAC3C,OAAO,eAAeC,WAAWC,IAAgB,EAAEC,OAAiB,EAAEC,UAAkB;IACtF,MAAMC,QAAQF,QAAQG,GAAG,CAAC,CAACC,MAAMC;QAC/B,MAAMC,SAAST,WAAWO;QAC1B,OAAO,CAAC,MAAM,EAAEE,OAAO,MAAM,EAAED,EAAE,+BAA+B,EAAEC,OAAO,iBAAiB,EAAEA,OAAO,kBAAkB,EAAED,GAAG;IAC5H;IACA,MAAME,OAAO,MAAMR,KAAKS,OAAO,CAAC,CAAC,OAAO,EAAEN,MAAMO,IAAI,CAAC,MAAM,kBAAkB,EAAER,YAAY;IAC3F,MAAMS,MAAO,MAAMH,KAAKI,GAAG;IAC3B,OAAOX,QAAQG,GAAG,CAAC,CAACC,MAAMC;YAEPK,OACGA;eAHW;YAC/BE,OAAOR;YACPS,UAAUC,QAAOJ,QAAAA,GAAG,CAAC,CAAC,CAAC,EAAEL,GAAG,CAAC,cAAZK,mBAAAA,QAAgB;YACjCK,MAAM;mBAAI,IAAIC,IAAI,EAAEN,SAAAA,GAAG,CAAC,CAAC,CAAC,EAAEL,GAAG,CAAC,cAAZK,oBAAAA,SAA2B,IAAIO,KAAK,CAAC,KAAKC,MAAM,CAACC;aAAU,CAACC,IAAI,GAAGX,IAAI,CAAC;QAC9F;;AACF"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/sqlite/fieldStats.ts"],"sourcesContent":["import { quoteIdent } from '../shared.ts';\nimport type { Connection, FieldStat } from '../types.ts';\n\n// One aggregate query per column (COUNT + GROUP_CONCAT(DISTINCT typeof())), O(columns) not O(rows x columns);\n// typeof()'s output is already the shared vocabulary, and GROUP_CONCAT's order is unsorted, so JS sorts the type set.\nexport async function fieldStats(conn: Connection, columns: string[], scopeWhere: string): Promise<FieldStat[]> {\n const exprs = columns.map((name, i) => {\n const quoted = quoteIdent(name);\n return `COUNT(${quoted}) AS n${i}, GROUP_CONCAT(DISTINCT typeof(${quoted})) FILTER (WHERE ${quoted} IS NOT NULL) AS t${i}`;\n });\n const stmt = await conn.prepare(`SELECT ${exprs.join(', ')} FROM frontmatter ${scopeWhere}`);\n const row = (await stmt.get()) as Record<string, number | string | null>;\n return columns.map((name, i) => ({\n field: name,\n coverage: Number(row[`n${i}`] ?? 0),\n type: [...new Set(((row[`t${i}`] as string) ?? '').split(',').filter(Boolean))].sort().join(','),\n }));\n}\n"],"names":["quoteIdent","fieldStats","conn","columns","scopeWhere","exprs","map","name","i","quoted","stmt","prepare","join","row","get","field","coverage","Number","type","Set","split","filter","Boolean","sort"],"mappings":"AAAA,SAASA,UAAU,QAAQ,eAAe;AAG1C,8GAA8G;AAC9G,sHAAsH;AACtH,OAAO,eAAeC,WAAWC,IAAgB,EAAEC,OAAiB,EAAEC,UAAkB;IACtF,MAAMC,QAAQF,QAAQG,GAAG,CAAC,CAACC,MAAMC;QAC/B,MAAMC,SAAST,WAAWO;QAC1B,OAAO,CAAC,MAAM,EAAEE,OAAO,MAAM,EAAED,EAAE,+BAA+B,EAAEC,OAAO,iBAAiB,EAAEA,OAAO,kBAAkB,EAAED,GAAG;IAC5H;IACA,MAAME,OAAO,MAAMR,KAAKS,OAAO,CAAC,CAAC,OAAO,EAAEN,MAAMO,IAAI,CAAC,MAAM,kBAAkB,EAAER,YAAY;IAC3F,MAAMS,MAAO,MAAMH,KAAKI,GAAG;IAC3B,OAAOX,QAAQG,GAAG,CAAC,CAACC,MAAMC;YAEPK,OACGA;eAHW;YAC/BE,OAAOR;YACPS,UAAUC,QAAOJ,QAAAA,GAAG,CAAC,CAAC,CAAC,EAAEL,GAAG,CAAC,cAAZK,mBAAAA,QAAgB;YACjCK,MAAM;mBAAI,IAAIC,IAAI,EAAEN,SAAAA,GAAG,CAAC,CAAC,CAAC,EAAEL,GAAG,CAAC,cAAZK,oBAAAA,SAA2B,IAAIO,KAAK,CAAC,KAAKC,MAAM,CAACC;aAAU,CAACC,IAAI,GAAGX,IAAI,CAAC;QAC9F;;AACF"}
@@ -1,19 +1,15 @@
1
1
  import { segmentMatch } from '../../text/segment.js';
2
2
  // snippet() re-tokenizes each candidate doc, superlinearly: ~10s for one 1MB doc
3
- // (benchmark/reports/2026-08-23-hub-release-battery.md). Past this bound, hit stays NULL for
4
- // the caller's JS excerpt fallback.
3
+ // (benchmark/reports/2026-08-23-hub-release-battery.md); past this bound, hit stays NULL for the caller's JS excerpt fallback.
5
4
  const SNIPPET_BOUND = 16384;
6
5
  const WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0, 0, 10.0, 5.0, 1.0)';
7
- // Ranked BM25 word-match query with excerpt, scoped by the caller-built SQL fragments.
8
- // `segmenting` mirrors the predicate that decided whether the `_seg` sidecars were populated
9
- // (contentTokenize(cfg) === undefined, see sqlite/store.ts): an unspaced-script run in `terms`
10
- // becomes a quoted grapheme phrase against those sidecars only when they exist.
6
+ // Ranked BM25 word-match query with excerpt, scoped by the caller-built SQL fragments. `segmenting`
7
+ // mirrors the predicate that populated the `_seg` sidecars (contentTokenize(cfg) === undefined): an unspaced-script run in `terms` becomes a quoted grapheme phrase against them only when they exist.
11
8
  export async function queryLexical(conn, terms, opts, segmenting) {
12
9
  const { whereJoin, whereCond, scopeCond, limit } = opts;
13
10
  const query = segmenting ? segmentMatch(terms) : terms;
14
- // A run of pure unspaced-script punctuation segments to nothing searchable (segmentField
15
- // drops empty groups the same way): an empty MATCH string is an FTS5 syntax error, not zero
16
- // rows, so this returns the zero rows directly rather than issuing it.
11
+ // A run of pure unspaced-script punctuation segments to nothing searchable: an empty MATCH
12
+ // string is an FTS5 syntax error, not zero rows, so return zero rows directly rather than issuing it.
17
13
  if (query.trim() === '') return [];
18
14
  const sql = `SELECT content.path AS path, CASE WHEN length(content.text) <= ${SNIPPET_BOUND} THEN snippet(content, 2, '«', '»', '…', 10) ELSE NULL END AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ${scopeCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${limit}`;
19
15
  const stmt = await conn.prepare(sql);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/sqlite/lexical.ts"],"sourcesContent":["import { segmentMatch } from '../../text/segment.ts';\nimport type { Connection, LexicalHit, LexicalQueryOptions } from '../types.ts';\n\n// snippet() re-tokenizes each candidate doc, superlinearly: ~10s for one 1MB doc\n// (benchmark/reports/2026-08-23-hub-release-battery.md). Past this bound, hit stays NULL for\n// the caller's JS excerpt fallback.\nconst SNIPPET_BOUND = 16_384;\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0, 0, 10.0, 5.0, 1.0)';\n\n// Ranked BM25 word-match query with excerpt, scoped by the caller-built SQL fragments.\n// `segmenting` mirrors the predicate that decided whether the `_seg` sidecars were populated\n// (contentTokenize(cfg) === undefined, see sqlite/store.ts): an unspaced-script run in `terms`\n// becomes a quoted grapheme phrase against those sidecars only when they exist.\nexport async function queryLexical(conn: Connection, terms: string, opts: LexicalQueryOptions, segmenting: boolean): Promise<LexicalHit[]> {\n const { whereJoin, whereCond, scopeCond, limit } = opts;\n const query = segmenting ? segmentMatch(terms) : terms;\n // A run of pure unspaced-script punctuation segments to nothing searchable (segmentField\n // drops empty groups the same way): an empty MATCH string is an FTS5 syntax error, not zero\n // rows, so this returns the zero rows directly rather than issuing it.\n if (query.trim() === '') return [];\n const sql = `SELECT content.path AS path, CASE WHEN length(content.text) <= ${SNIPPET_BOUND} THEN snippet(content, 2, '«', '»', '…', 10) ELSE NULL END AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ${scopeCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${limit}`;\n const stmt = await conn.prepare(sql);\n return (await stmt.all(query)) as unknown as LexicalHit[];\n}\n"],"names":["segmentMatch","SNIPPET_BOUND","WEIGHTED_BM25","queryLexical","conn","terms","opts","segmenting","whereJoin","whereCond","scopeCond","limit","query","trim","sql","stmt","prepare","all"],"mappings":"AAAA,SAASA,YAAY,QAAQ,wBAAwB;AAGrD,iFAAiF;AACjF,6FAA6F;AAC7F,oCAAoC;AACpC,MAAMC,gBAAgB;AACtB,MAAMC,gBAAgB;AAEtB,uFAAuF;AACvF,6FAA6F;AAC7F,+FAA+F;AAC/F,gFAAgF;AAChF,OAAO,eAAeC,aAAaC,IAAgB,EAAEC,KAAa,EAAEC,IAAyB,EAAEC,UAAmB;IAChH,MAAM,EAAEC,SAAS,EAAEC,SAAS,EAAEC,SAAS,EAAEC,KAAK,EAAE,GAAGL;IACnD,MAAMM,QAAQL,aAAaP,aAAaK,SAASA;IACjD,yFAAyF;IACzF,4FAA4F;IAC5F,uEAAuE;IACvE,IAAIO,MAAMC,IAAI,OAAO,IAAI,OAAO,EAAE;IAClC,MAAMC,MAAM,CAAC,+DAA+D,EAAEb,cAAc,+EAA+E,EAAEO,UAAU,uBAAuB,EAAEC,UAAU,CAAC,EAAEC,UAAU,UAAU,EAAER,cAAc,OAAO,EAAES,OAAO;IACjR,MAAMI,OAAO,MAAMX,KAAKY,OAAO,CAACF;IAChC,OAAQ,MAAMC,KAAKE,GAAG,CAACL;AACzB"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/sqlite/lexical.ts"],"sourcesContent":["import { segmentMatch } from '../../text/segment.ts';\nimport type { Connection, LexicalHit, LexicalQueryOptions } from '../types.ts';\n\n// snippet() re-tokenizes each candidate doc, superlinearly: ~10s for one 1MB doc\n// (benchmark/reports/2026-08-23-hub-release-battery.md); past this bound, hit stays NULL for the caller's JS excerpt fallback.\nconst SNIPPET_BOUND = 16_384;\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0, 0, 10.0, 5.0, 1.0)';\n\n// Ranked BM25 word-match query with excerpt, scoped by the caller-built SQL fragments. `segmenting`\n// mirrors the predicate that populated the `_seg` sidecars (contentTokenize(cfg) === undefined): an unspaced-script run in `terms` becomes a quoted grapheme phrase against them only when they exist.\nexport async function queryLexical(conn: Connection, terms: string, opts: LexicalQueryOptions, segmenting: boolean): Promise<LexicalHit[]> {\n const { whereJoin, whereCond, scopeCond, limit } = opts;\n const query = segmenting ? segmentMatch(terms) : terms;\n // A run of pure unspaced-script punctuation segments to nothing searchable: an empty MATCH\n // string is an FTS5 syntax error, not zero rows, so return zero rows directly rather than issuing it.\n if (query.trim() === '') return [];\n const sql = `SELECT content.path AS path, CASE WHEN length(content.text) <= ${SNIPPET_BOUND} THEN snippet(content, 2, '«', '»', '…', 10) ELSE NULL END AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ${scopeCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${limit}`;\n const stmt = await conn.prepare(sql);\n return (await stmt.all(query)) as unknown as LexicalHit[];\n}\n"],"names":["segmentMatch","SNIPPET_BOUND","WEIGHTED_BM25","queryLexical","conn","terms","opts","segmenting","whereJoin","whereCond","scopeCond","limit","query","trim","sql","stmt","prepare","all"],"mappings":"AAAA,SAASA,YAAY,QAAQ,wBAAwB;AAGrD,iFAAiF;AACjF,+HAA+H;AAC/H,MAAMC,gBAAgB;AACtB,MAAMC,gBAAgB;AAEtB,oGAAoG;AACpG,uMAAuM;AACvM,OAAO,eAAeC,aAAaC,IAAgB,EAAEC,KAAa,EAAEC,IAAyB,EAAEC,UAAmB;IAChH,MAAM,EAAEC,SAAS,EAAEC,SAAS,EAAEC,SAAS,EAAEC,KAAK,EAAE,GAAGL;IACnD,MAAMM,QAAQL,aAAaP,aAAaK,SAASA;IACjD,2FAA2F;IAC3F,sGAAsG;IACtG,IAAIO,MAAMC,IAAI,OAAO,IAAI,OAAO,EAAE;IAClC,MAAMC,MAAM,CAAC,+DAA+D,EAAEb,cAAc,+EAA+E,EAAEO,UAAU,uBAAuB,EAAEC,UAAU,CAAC,EAAEC,UAAU,UAAU,EAAER,cAAc,OAAO,EAAES,OAAO;IACjR,MAAMI,OAAO,MAAMX,KAAKY,OAAO,CAACF;IAChC,OAAQ,MAAMC,KAAKE,GAAG,CAACL;AACzB"}
@@ -11,4 +11,3 @@ export interface OpenResult {
11
11
  }
12
12
  export declare function openSqlite(cfg: ResolvedConfig): Promise<OpenResult>;
13
13
  export declare function docCount(store: Store): Promise<number>;
14
- export declare function clearCache(cfg: ResolvedConfig): void;
@@ -1,13 +1,15 @@
1
1
  // The Node floor (>=22.20) is explained here and nowhere else: 22.20 is the first release with
2
2
  // both FTS5 and row-returning INSERT ... RETURNING. Raise it only for a load-bearing capability.
3
- import { mkdirSync, rmSync } from 'node:fs';
3
+ import { mkdirSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
  import { DatabaseSync } from 'node:sqlite';
6
6
  import { contentTokenize, featureSignature, STATE_DIR } from '../../config/index.js';
7
+ import { rekeyChunkText } from '../../embed/handoff.js';
7
8
  import { SenseError } from '../../errors.js';
8
9
  import { activeFeatures, FEATURES } from '../../features/index.js';
10
+ import { clearCache } from '../cache.js';
9
11
  import { getMeta, setMeta } from '../shared.js';
10
- import { withTransaction } from '../transaction.js';
12
+ import { BEGIN_WRITE, withTransaction } from '../transaction.js';
11
13
  import { createConnection } from './connection.js';
12
14
  import { changedSignatureKeys, embedIdentityAdopted, rebuildContentTable, reconcile, signatureDiff } from './reconcile.js';
13
15
  import { registerFunctions } from './sql-functions.js';
@@ -16,16 +18,11 @@ export const DB_FILENAME = 'cache.db';
16
18
  // Cache shape version, independent of the config's own `version`. Bumping it rebuilds
17
19
  // existing trees on first query.
18
20
  export const SCHEMA_VERSION = '18';
19
- // Stemming is English-only, but the segmentation underneath it is what decides coverage:
20
- // unicode61 splits on spaces, so a language written without them (Chinese, Japanese, Thai)
21
- // indexes a whole run as one token and word search finds nothing. `content.tokenize` is how
22
- // such a tree picks trigram instead.
21
+ // unicode61 splits on spaces, so a language written without them indexes a whole run as one
22
+ // token and word search finds nothing; `content.tokenize` is how such a tree picks trigram.
23
23
  const DEFAULT_TOKENIZE = 'porter unicode61';
24
- // FTS5 takes its tokenizer as a string literal inside DDL, where nothing can bind, so a
25
- // configured value has to be concatenated. Probing a throwaway table is what makes that safe
26
- // and is also the whole validation: anything the linked SQLite accepts passes, anything else
27
- // fails here with SQLite's own message rather than against the real table. It means no table
28
- // of which version added which tokenizer has to be maintained.
24
+ // FTS5 takes its tokenizer as a DDL string literal where nothing can bind, so the configured
25
+ // value is concatenated; probing a throwaway table first is both the safety and the validation.
29
26
  function resolveTokenize(db, cfg) {
30
27
  const configured = contentTokenize(cfg);
31
28
  if (configured === undefined) return DEFAULT_TOKENIZE;
@@ -47,11 +44,8 @@ function storedTokenize(db) {
47
44
  const m = row.sql.match(/tokenize = '((?:[^']|'')*)'/);
48
45
  return m ? m[1] : null;
49
46
  }
50
- // The three `_seg` sidecars are appended after path, never inserted: bm25(content, ...) and
51
- // snippet(content, 2, ...) are documented against the first three columns and keep working
52
- // (FTS5 defaults the weights it was not given). Each carries its field's exploded unspaced
53
- // runs for text that needs it, and an empty string for text that does not. Shared by
54
- // ensureSchema and the tokenize-only rebuild in connect(), so both create identical DDL.
47
+ // The `_seg` sidecars are appended after path, never inserted: bm25() and snippet(content, 2)
48
+ // are documented against the first three columns. Each holds its field's exploded unspaced runs.
55
49
  async function createContentTable(conn, tokenize) {
56
50
  await conn.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, title_seg, summary_seg, text_seg, tokenize = '${tokenize}')`);
57
51
  }
@@ -70,6 +64,20 @@ async function ensureSchema(conn, cfg, tokenize) {
70
64
  if (await getMeta(conn, 'schema_version') === null) await setMeta(conn, 'schema_version', SCHEMA_VERSION);
71
65
  if (await getMeta(conn, 'features') === null) await setMeta(conn, 'features', featureSignature(cfg, FEATURES));
72
66
  }
67
+ // Two processes opening the same fresh tree both try to convert it, and the loser gets SQLITE_BUSY
68
+ // with no busy handler behind it. Bounded because a lock held past this is a real problem, not a race.
69
+ function setJournalWal(db) {
70
+ const deadline = Date.now() + 5000;
71
+ for(;;){
72
+ try {
73
+ db.exec('PRAGMA journal_mode = WAL');
74
+ return;
75
+ } catch (err) {
76
+ if (Date.now() >= deadline || !/database is locked|busy/i.test(err.message)) throw err;
77
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
78
+ }
79
+ }
80
+ }
73
81
  async function connect(cfg) {
74
82
  const stateDir = join(cfg.baseDir, STATE_DIR);
75
83
  mkdirSync(stateDir, {
@@ -77,16 +85,17 @@ async function connect(cfg) {
77
85
  });
78
86
  const dbPath = join(stateDir, DB_FILENAME);
79
87
  const db = new DatabaseSync(dbPath);
80
- // A throw below (tokenizer probe, schema, reconcile's COLUMN_LIMIT) must release this handle,
81
- // or on Windows the leaked WAL db is undeletable and scratch cleanup fails with EPERM/EBUSY.
82
- // closed marks the rebuild branches that close-and-recurse, so the catch never double-closes.
88
+ // A throw below must release this handle, or on Windows the leaked WAL db is undeletable and
89
+ // scratch cleanup fails with EPERM/EBUSY. `closed` keeps the catch from double-closing.
83
90
  let closed = false;
84
91
  try {
85
92
  var _ref;
86
- db.exec('PRAGMA journal_mode = WAL');
87
- // Covers a concurrent watcher's bulk reconcile (~5s for 500 files at 26k notes). A query
88
- // that outwaits it still fails loudly.
93
+ // Before journal_mode, not after: converting a fresh database to WAL takes a brief exclusive
94
+ // lock, and with no timeout set yet a second process opening the same tree fails in 1ms.
89
95
  db.exec('PRAGMA busy_timeout = 30000');
96
+ // busy_timeout does not cover the WAL conversion itself: SQLite does not invoke the busy
97
+ // handler for it, so a concurrent cold open needs its own bounded wait.
98
+ setJournalWal(db);
90
99
  registerFunctions(db, contentTokenize(cfg) === undefined);
91
100
  const conn = createConnection(db);
92
101
  db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');
@@ -110,20 +119,16 @@ async function connect(cfg) {
110
119
  return connect(cfg);
111
120
  }
112
121
  const changedKeys = changedSignatureKeys(features !== null && features !== void 0 ? features : '', wantFeatures);
113
- // Only the tokenizer moved: frontmatter, links, sections, and embeddings are file-derived
114
- // and tokenizer-independent, so they don't need re-deriving. Everything else -- a preset
115
- // edit, an embed model change -- still takes the full clear/reopen below.
122
+ // Only the tokenizer moved, and frontmatter, links, sections and embeddings are file-derived
123
+ // and tokenizer-independent. Any other signature change takes the full clear/reopen below.
116
124
  if (changedKeys.size === 1 && changedKeys.has('tokenize')) {
117
125
  console.error('sense: config change (content tokenizer) rebuilds the text index; vectors, links, and sections are kept');
118
- // Drop and recreate as one transaction: a crash before COMMIT rolls back to the table
119
- // that was there before (old tokenizer, still queryable), never to no table at all.
120
- // IF EXISTS also makes a retry after such a crash a no-op instead of a raw SQLite error --
121
- // meta.features is only updated once rebuildContentTable (below) finishes, so a retry
122
- // re-enters this branch and repopulates from scratch either way.
126
+ // One transaction, so a crash before COMMIT rolls back to the old tokenizer's table rather
127
+ // than to no table. IF EXISTS makes a retry after such a crash a no-op, not a raw error.
123
128
  await withTransaction(conn, async ()=>{
124
129
  await conn.exec('DROP TABLE IF EXISTS content');
125
130
  await createContentTable(conn, tokenize);
126
- });
131
+ }, BEGIN_WRITE);
127
132
  tokenizeOnlyRebuild = true;
128
133
  } else if (changedKeys.size === 1 && changedKeys.has('embed') && embedIdentityAdopted(features !== null && features !== void 0 ? features : '', wantFeatures)) {
129
134
  // First sight of a resolved weight identity: the model itself hasn't changed, so
@@ -139,10 +144,8 @@ async function connect(cfg) {
139
144
  return connect(cfg);
140
145
  }
141
146
  }
142
- // Meta can lie after a crash between table creation and the signature write; the table's
143
- // own DDL cannot. A mismatch here rebuilds no matter what meta says. A tokenize-only rebuild
144
- // already recreated content with this tokenize above, so stored already matches and this
145
- // guard is skipped naturally rather than needing its own case.
147
+ // Meta can lie after a crash between table creation and the signature write; the table's own
148
+ // DDL cannot, so a mismatch here rebuilds whatever meta says.
146
149
  const stored = storedTokenize(db);
147
150
  if (stored !== null && stored !== tokenize) {
148
151
  console.error('sense: cache was built with a different content tokenizer; rebuilding the index');
@@ -151,7 +154,9 @@ async function connect(cfg) {
151
154
  clearCache(cfg);
152
155
  return connect(cfg);
153
156
  }
154
- await ensureSchema(conn, cfg, tokenize);
157
+ // One writer at a time: the feature hooks check a column then add it, so two cold opens racing
158
+ // here both see it missing and the second ALTER fails with a duplicate column.
159
+ await withTransaction(conn, ()=>ensureSchema(conn, cfg, tokenize), BEGIN_WRITE);
155
160
  let rebuildWarnings = [];
156
161
  if (tokenizeOnlyRebuild) {
157
162
  rebuildWarnings = await rebuildContentTable(conn, cfg, cfg.baseDir);
@@ -182,8 +187,11 @@ async function connect(cfg) {
182
187
  // resulting connection in the async Store interface.
183
188
  export async function openSqlite(cfg) {
184
189
  const { db, conn, cfg: resolvedCfg, dbPath, parsed, warnings } = await connect(cfg);
190
+ const store = createStore(db, conn, resolvedCfg, resolvedCfg.baseDir);
191
+ // reconcile ran before this object existed, so its chunk text is keyed by the connection.
192
+ rekeyChunkText(conn, store);
185
193
  return {
186
- store: createStore(db, conn, resolvedCfg, resolvedCfg.baseDir),
194
+ store: store,
187
195
  cfg: resolvedCfg,
188
196
  dbPath,
189
197
  parsed,
@@ -194,13 +202,3 @@ export async function docCount(store) {
194
202
  const stmt = await store.prepare('SELECT COUNT(*) AS n FROM frontmatter');
195
203
  return (await stmt.get()).n;
196
204
  }
197
- // Deletes the cache directory, and only that. The rebuild is not this function's job: the next
198
- // open() reconciles, which is what running any command already does, so a verb that bundled the
199
- // two described the half that was not its own. Manual reset for a doubted cache; the schema and
200
- // config-signature mismatches below reset themselves.
201
- export function clearCache(cfg) {
202
- rmSync(join(cfg.baseDir, STATE_DIR), {
203
- recursive: true,
204
- force: true
205
- });
206
- }