sensemaking 0.17.0 → 0.18.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 (429) hide show
  1. package/README.md +15 -13
  2. package/dist/cjs/cli/map.js +147 -6
  3. package/dist/cjs/cli/map.js.map +1 -1
  4. package/dist/cjs/cli/named.js +20 -12
  5. package/dist/cjs/cli/named.js.map +1 -1
  6. package/dist/cjs/cli/path.js +190 -29
  7. package/dist/cjs/cli/path.js.map +1 -1
  8. package/dist/cjs/cli/peek.js +148 -7
  9. package/dist/cjs/cli/peek.js.map +1 -1
  10. package/dist/cjs/cli/related.js +5 -5
  11. package/dist/cjs/cli/related.js.map +1 -1
  12. package/dist/cjs/cli/search.js +5 -5
  13. package/dist/cjs/cli/search.js.map +1 -1
  14. package/dist/cjs/cli/shared.d.cts +3 -3
  15. package/dist/cjs/cli/shared.d.ts +3 -3
  16. package/dist/cjs/cli/shared.js +156 -58
  17. package/dist/cjs/cli/shared.js.map +1 -1
  18. package/dist/cjs/cli/sql.js +151 -10
  19. package/dist/cjs/cli/sql.js.map +1 -1
  20. package/dist/cjs/cli/status.js +187 -62
  21. package/dist/cjs/cli/status.js.map +1 -1
  22. package/dist/cjs/commands/map.d.cts +2 -2
  23. package/dist/cjs/commands/map.d.ts +2 -2
  24. package/dist/cjs/commands/map.js +373 -67
  25. package/dist/cjs/commands/map.js.map +1 -1
  26. package/dist/cjs/commands/peek.d.cts +2 -2
  27. package/dist/cjs/commands/peek.d.ts +2 -2
  28. package/dist/cjs/commands/peek.js +321 -79
  29. package/dist/cjs/commands/peek.js.map +1 -1
  30. package/dist/cjs/commands/related.d.cts +2 -2
  31. package/dist/cjs/commands/related.d.ts +2 -2
  32. package/dist/cjs/commands/related.js +55 -10
  33. package/dist/cjs/commands/related.js.map +1 -1
  34. package/dist/cjs/commands/scope.d.cts +7 -7
  35. package/dist/cjs/commands/scope.d.ts +7 -7
  36. package/dist/cjs/commands/scope.js +382 -45
  37. package/dist/cjs/commands/scope.js.map +1 -1
  38. package/dist/cjs/commands/search.d.cts +2 -2
  39. package/dist/cjs/commands/search.d.ts +2 -2
  40. package/dist/cjs/commands/search.js +292 -99
  41. package/dist/cjs/commands/search.js.map +1 -1
  42. package/dist/cjs/commands/signals.d.cts +4 -10
  43. package/dist/cjs/commands/signals.d.ts +4 -10
  44. package/dist/cjs/commands/signals.js +91 -45
  45. package/dist/cjs/commands/signals.js.map +1 -1
  46. package/dist/cjs/commands/status.d.cts +2 -2
  47. package/dist/cjs/commands/status.d.ts +2 -2
  48. package/dist/cjs/commands/status.js +247 -10
  49. package/dist/cjs/commands/status.js.map +1 -1
  50. package/dist/cjs/config/access.d.cts +1 -0
  51. package/dist/cjs/config/access.d.ts +1 -0
  52. package/dist/cjs/config/access.js +7 -0
  53. package/dist/cjs/config/access.js.map +1 -1
  54. package/dist/cjs/config/index.d.cts +1 -1
  55. package/dist/cjs/config/index.d.ts +1 -1
  56. package/dist/cjs/config/index.js +3 -0
  57. package/dist/cjs/config/index.js.map +1 -1
  58. package/dist/cjs/config/types.d.cts +1 -0
  59. package/dist/cjs/config/types.d.ts +1 -0
  60. package/dist/cjs/config/types.js.map +1 -1
  61. package/dist/cjs/config/validate.js +10 -1
  62. package/dist/cjs/config/validate.js.map +1 -1
  63. package/dist/cjs/embed/distribution.d.cts +4 -0
  64. package/dist/cjs/embed/distribution.d.ts +4 -0
  65. package/dist/cjs/embed/distribution.js +186 -0
  66. package/dist/cjs/embed/distribution.js.map +1 -0
  67. package/dist/cjs/embed/langfit.d.cts +2 -5
  68. package/dist/cjs/embed/langfit.d.ts +2 -5
  69. package/dist/cjs/embed/langfit.js +73 -63
  70. package/dist/cjs/embed/langfit.js.map +1 -1
  71. package/dist/cjs/embed/query.d.cts +6 -13
  72. package/dist/cjs/embed/query.d.ts +6 -13
  73. package/dist/cjs/embed/query.js +56 -168
  74. package/dist/cjs/embed/query.js.map +1 -1
  75. package/dist/cjs/embed/static.js +13 -3
  76. package/dist/cjs/embed/static.js.map +1 -1
  77. package/dist/cjs/embed/types.d.cts +1 -0
  78. package/dist/cjs/embed/types.d.ts +1 -0
  79. package/dist/cjs/embed/types.js +11 -2
  80. package/dist/cjs/embed/types.js.map +1 -1
  81. package/dist/cjs/errors.d.cts +1 -1
  82. package/dist/cjs/errors.d.ts +1 -1
  83. package/dist/cjs/errors.js.map +1 -1
  84. package/dist/cjs/features/embed.js +218 -11
  85. package/dist/cjs/features/embed.js.map +1 -1
  86. package/dist/cjs/features/index.d.cts +1 -1
  87. package/dist/cjs/features/index.d.ts +1 -1
  88. package/dist/cjs/features/index.js +6 -0
  89. package/dist/cjs/features/index.js.map +1 -1
  90. package/dist/cjs/features/links.d.cts +7 -2
  91. package/dist/cjs/features/links.d.ts +7 -2
  92. package/dist/cjs/features/links.js +710 -198
  93. package/dist/cjs/features/links.js.map +1 -1
  94. package/dist/cjs/features/rank.js +231 -31
  95. package/dist/cjs/features/rank.js.map +1 -1
  96. package/dist/cjs/features/sections.js +218 -8
  97. package/dist/cjs/features/sections.js.map +1 -1
  98. package/dist/cjs/features/tags.js +236 -40
  99. package/dist/cjs/features/tags.js.map +1 -1
  100. package/dist/cjs/features/types.d.cts +9 -5
  101. package/dist/cjs/features/types.d.ts +9 -5
  102. package/dist/cjs/graph/traverse.d.cts +2 -2
  103. package/dist/cjs/graph/traverse.d.ts +2 -2
  104. package/dist/cjs/graph/traverse.js +321 -42
  105. package/dist/cjs/graph/traverse.js.map +1 -1
  106. package/dist/cjs/index.d.cts +3 -2
  107. package/dist/cjs/index.d.ts +3 -2
  108. package/dist/cjs/index.js +2 -2
  109. package/dist/cjs/index.js.map +1 -1
  110. package/dist/cjs/lib/guarded-tick.d.cts +1 -0
  111. package/dist/cjs/lib/guarded-tick.d.ts +1 -0
  112. package/dist/cjs/lib/guarded-tick.js +19 -0
  113. package/dist/cjs/lib/guarded-tick.js.map +1 -0
  114. package/dist/cjs/output/column-hint.d.cts +1 -2
  115. package/dist/cjs/output/column-hint.d.ts +1 -2
  116. package/dist/cjs/output/column-hint.js +1 -4
  117. package/dist/cjs/output/column-hint.js.map +1 -1
  118. package/dist/cjs/output/output.d.cts +2 -1
  119. package/dist/cjs/output/output.d.ts +2 -1
  120. package/dist/cjs/output/output.js +515 -97
  121. package/dist/cjs/output/output.js.map +1 -1
  122. package/dist/cjs/scan/frontmatter.js.map +1 -1
  123. package/dist/cjs/scan/index.js.map +1 -1
  124. package/dist/cjs/scan/reparse.d.cts +10 -0
  125. package/dist/cjs/scan/reparse.d.ts +10 -0
  126. package/dist/cjs/scan/reparse.js +104 -0
  127. package/dist/cjs/scan/reparse.js.map +1 -0
  128. package/dist/cjs/store/duckdb/batch.d.cts +16 -0
  129. package/dist/cjs/store/duckdb/batch.d.ts +16 -0
  130. package/dist/cjs/store/duckdb/batch.js +208 -0
  131. package/dist/cjs/store/duckdb/batch.js.map +1 -0
  132. package/dist/cjs/store/duckdb/connection.d.cts +5 -0
  133. package/dist/cjs/store/duckdb/connection.d.ts +5 -0
  134. package/dist/cjs/store/duckdb/connection.js +772 -0
  135. package/dist/cjs/store/duckdb/connection.js.map +1 -0
  136. package/dist/cjs/store/duckdb/lexical.d.cts +10 -0
  137. package/dist/cjs/store/duckdb/lexical.d.ts +10 -0
  138. package/dist/cjs/store/duckdb/lexical.js +502 -0
  139. package/dist/cjs/store/duckdb/lexical.js.map +1 -0
  140. package/dist/cjs/store/duckdb/native.d.cts +3 -0
  141. package/dist/cjs/store/duckdb/native.d.ts +3 -0
  142. package/dist/cjs/store/duckdb/native.js +360 -0
  143. package/dist/cjs/store/duckdb/native.js.map +1 -0
  144. package/dist/cjs/store/duckdb/open.d.cts +12 -0
  145. package/dist/cjs/store/duckdb/open.d.ts +12 -0
  146. package/dist/cjs/store/duckdb/open.js +501 -0
  147. package/dist/cjs/store/duckdb/open.js.map +1 -0
  148. package/dist/cjs/store/duckdb/reconcile.d.cts +6 -0
  149. package/dist/cjs/store/duckdb/reconcile.d.ts +6 -0
  150. package/dist/cjs/store/duckdb/reconcile.js +753 -0
  151. package/dist/cjs/store/duckdb/reconcile.js.map +1 -0
  152. package/dist/cjs/store/duckdb/sql-functions.d.cts +2 -0
  153. package/dist/cjs/store/duckdb/sql-functions.d.ts +2 -0
  154. package/dist/cjs/store/duckdb/sql-functions.js +203 -0
  155. package/dist/cjs/store/duckdb/sql-functions.js.map +1 -0
  156. package/dist/cjs/store/duckdb/store.d.cts +5 -0
  157. package/dist/cjs/store/duckdb/store.d.ts +5 -0
  158. package/dist/cjs/store/duckdb/store.js +504 -0
  159. package/dist/cjs/store/duckdb/store.js.map +1 -0
  160. package/dist/cjs/store/duckdb/vectors.d.cts +9 -0
  161. package/dist/cjs/store/duckdb/vectors.d.ts +9 -0
  162. package/dist/cjs/store/duckdb/vectors.js +502 -0
  163. package/dist/cjs/store/duckdb/vectors.js.map +1 -0
  164. package/dist/cjs/store/index.d.cts +7 -0
  165. package/dist/cjs/store/index.d.ts +7 -0
  166. package/dist/cjs/store/index.js +201 -0
  167. package/dist/cjs/store/index.js.map +1 -0
  168. package/dist/cjs/store/meta.d.cts +3 -0
  169. package/dist/cjs/store/meta.d.ts +3 -0
  170. package/dist/cjs/store/meta.js +215 -0
  171. package/dist/cjs/store/meta.js.map +1 -0
  172. package/dist/cjs/store/shared.d.cts +5 -0
  173. package/dist/cjs/store/shared.d.ts +5 -0
  174. package/dist/cjs/store/shared.js +252 -0
  175. package/dist/cjs/store/shared.js.map +1 -0
  176. package/dist/cjs/store/sql-functions.d.cts +2 -0
  177. package/dist/cjs/store/sql-functions.d.ts +2 -0
  178. package/dist/cjs/store/sql-functions.js +48 -0
  179. package/dist/cjs/store/sql-functions.js.map +1 -0
  180. package/dist/cjs/store/sqlite/connection.d.cts +3 -0
  181. package/dist/cjs/store/sqlite/connection.d.ts +3 -0
  182. package/dist/cjs/store/sqlite/connection.js +516 -0
  183. package/dist/cjs/store/sqlite/connection.js.map +1 -0
  184. package/dist/cjs/store/sqlite/lexical.d.cts +2 -0
  185. package/dist/cjs/store/sqlite/lexical.d.ts +2 -0
  186. package/dist/cjs/store/sqlite/lexical.js +177 -0
  187. package/dist/cjs/store/sqlite/lexical.js.map +1 -0
  188. package/dist/cjs/{db → store/sqlite}/open.d.cts +5 -5
  189. package/dist/cjs/{db → store/sqlite}/open.d.ts +5 -5
  190. package/dist/cjs/store/sqlite/open.js +604 -0
  191. package/dist/cjs/store/sqlite/open.js.map +1 -0
  192. package/dist/cjs/store/sqlite/reconcile.d.cts +10 -0
  193. package/dist/cjs/store/sqlite/reconcile.d.ts +10 -0
  194. package/dist/cjs/store/sqlite/reconcile.js +1020 -0
  195. package/dist/cjs/store/sqlite/reconcile.js.map +1 -0
  196. package/dist/cjs/store/sqlite/sql-functions.js +34 -0
  197. package/dist/cjs/store/sqlite/sql-functions.js.map +1 -0
  198. package/dist/cjs/store/sqlite/store.d.cts +5 -0
  199. package/dist/cjs/store/sqlite/store.d.ts +5 -0
  200. package/dist/cjs/store/sqlite/store.js +588 -0
  201. package/dist/cjs/store/sqlite/store.js.map +1 -0
  202. package/dist/cjs/store/sqlite/vectors.d.cts +13 -0
  203. package/dist/cjs/store/sqlite/vectors.d.ts +13 -0
  204. package/dist/cjs/store/sqlite/vectors.js +470 -0
  205. package/dist/cjs/store/sqlite/vectors.js.map +1 -0
  206. package/dist/cjs/store/transaction.d.cts +5 -0
  207. package/dist/cjs/store/transaction.d.ts +5 -0
  208. package/dist/cjs/store/transaction.js +297 -0
  209. package/dist/cjs/store/transaction.js.map +1 -0
  210. package/dist/cjs/store/types.d.cts +91 -0
  211. package/dist/cjs/store/types.d.ts +91 -0
  212. package/dist/cjs/store/types.js +15 -0
  213. package/dist/cjs/store/types.js.map +1 -0
  214. package/dist/cjs/watch.d.cts +3 -0
  215. package/dist/cjs/watch.d.ts +3 -0
  216. package/dist/cjs/watch.js +215 -73
  217. package/dist/cjs/watch.js.map +1 -1
  218. package/dist/esm/cli/map.js +5 -5
  219. package/dist/esm/cli/map.js.map +1 -1
  220. package/dist/esm/cli/named.js +3 -3
  221. package/dist/esm/cli/named.js.map +1 -1
  222. package/dist/esm/cli/path.js +6 -5
  223. package/dist/esm/cli/path.js.map +1 -1
  224. package/dist/esm/cli/peek.js +5 -5
  225. package/dist/esm/cli/peek.js.map +1 -1
  226. package/dist/esm/cli/related.js +3 -3
  227. package/dist/esm/cli/related.js.map +1 -1
  228. package/dist/esm/cli/search.js +2 -2
  229. package/dist/esm/cli/search.js.map +1 -1
  230. package/dist/esm/cli/shared.d.ts +3 -3
  231. package/dist/esm/cli/shared.js +17 -17
  232. package/dist/esm/cli/shared.js.map +1 -1
  233. package/dist/esm/cli/sql.js +2 -2
  234. package/dist/esm/cli/sql.js.map +1 -1
  235. package/dist/esm/cli/status.js +25 -24
  236. package/dist/esm/cli/status.js.map +1 -1
  237. package/dist/esm/commands/map.d.ts +2 -2
  238. package/dist/esm/commands/map.js +51 -41
  239. package/dist/esm/commands/map.js.map +1 -1
  240. package/dist/esm/commands/peek.d.ts +2 -2
  241. package/dist/esm/commands/peek.js +58 -50
  242. package/dist/esm/commands/peek.js.map +1 -1
  243. package/dist/esm/commands/related.d.ts +2 -2
  244. package/dist/esm/commands/related.js +9 -9
  245. package/dist/esm/commands/related.js.map +1 -1
  246. package/dist/esm/commands/scope.d.ts +7 -7
  247. package/dist/esm/commands/scope.js +28 -17
  248. package/dist/esm/commands/scope.js.map +1 -1
  249. package/dist/esm/commands/search.d.ts +2 -2
  250. package/dist/esm/commands/search.js +42 -35
  251. package/dist/esm/commands/search.js.map +1 -1
  252. package/dist/esm/commands/signals.d.ts +4 -10
  253. package/dist/esm/commands/signals.js +17 -13
  254. package/dist/esm/commands/signals.js.map +1 -1
  255. package/dist/esm/commands/status.d.ts +2 -2
  256. package/dist/esm/commands/status.js +13 -7
  257. package/dist/esm/commands/status.js.map +1 -1
  258. package/dist/esm/config/access.d.ts +1 -0
  259. package/dist/esm/config/access.js +5 -0
  260. package/dist/esm/config/access.js.map +1 -1
  261. package/dist/esm/config/index.d.ts +1 -1
  262. package/dist/esm/config/index.js +1 -1
  263. package/dist/esm/config/index.js.map +1 -1
  264. package/dist/esm/config/types.d.ts +1 -0
  265. package/dist/esm/config/types.js.map +1 -1
  266. package/dist/esm/config/validate.js +10 -1
  267. package/dist/esm/config/validate.js.map +1 -1
  268. package/dist/esm/embed/distribution.d.ts +4 -0
  269. package/dist/esm/embed/distribution.js +11 -0
  270. package/dist/esm/embed/distribution.js.map +1 -0
  271. package/dist/esm/embed/langfit.d.ts +2 -5
  272. package/dist/esm/embed/langfit.js +13 -13
  273. package/dist/esm/embed/langfit.js.map +1 -1
  274. package/dist/esm/embed/query.d.ts +6 -13
  275. package/dist/esm/embed/query.js +23 -84
  276. package/dist/esm/embed/query.js.map +1 -1
  277. package/dist/esm/embed/static.js +6 -1
  278. package/dist/esm/embed/static.js.map +1 -1
  279. package/dist/esm/embed/types.d.ts +1 -0
  280. package/dist/esm/embed/types.js +5 -3
  281. package/dist/esm/embed/types.js.map +1 -1
  282. package/dist/esm/errors.d.ts +1 -1
  283. package/dist/esm/errors.js.map +1 -1
  284. package/dist/esm/features/embed.js +22 -10
  285. package/dist/esm/features/embed.js.map +1 -1
  286. package/dist/esm/features/index.d.ts +1 -1
  287. package/dist/esm/features/index.js +1 -1
  288. package/dist/esm/features/index.js.map +1 -1
  289. package/dist/esm/features/links.d.ts +7 -2
  290. package/dist/esm/features/links.js +124 -97
  291. package/dist/esm/features/links.js.map +1 -1
  292. package/dist/esm/features/rank.js +15 -8
  293. package/dist/esm/features/rank.js.map +1 -1
  294. package/dist/esm/features/sections.js +20 -7
  295. package/dist/esm/features/sections.js.map +1 -1
  296. package/dist/esm/features/tags.js +19 -18
  297. package/dist/esm/features/tags.js.map +1 -1
  298. package/dist/esm/features/types.d.ts +9 -5
  299. package/dist/esm/features/types.js +1 -1
  300. package/dist/esm/features/types.js.map +1 -1
  301. package/dist/esm/graph/traverse.d.ts +2 -2
  302. package/dist/esm/graph/traverse.js +26 -23
  303. package/dist/esm/graph/traverse.js.map +1 -1
  304. package/dist/esm/index.d.ts +3 -2
  305. package/dist/esm/index.js +1 -1
  306. package/dist/esm/index.js.map +1 -1
  307. package/dist/esm/lib/guarded-tick.d.ts +1 -0
  308. package/dist/esm/lib/guarded-tick.js +8 -0
  309. package/dist/esm/lib/guarded-tick.js.map +1 -0
  310. package/dist/esm/output/column-hint.d.ts +1 -2
  311. package/dist/esm/output/column-hint.js +3 -2
  312. package/dist/esm/output/column-hint.js.map +1 -1
  313. package/dist/esm/output/output.d.ts +2 -1
  314. package/dist/esm/output/output.js +27 -23
  315. package/dist/esm/output/output.js.map +1 -1
  316. package/dist/esm/scan/frontmatter.js +1 -1
  317. package/dist/esm/scan/frontmatter.js.map +1 -1
  318. package/dist/esm/scan/index.js +1 -1
  319. package/dist/esm/scan/index.js.map +1 -1
  320. package/dist/esm/scan/reparse.d.ts +10 -0
  321. package/dist/esm/scan/reparse.js +30 -0
  322. package/dist/esm/scan/reparse.js.map +1 -0
  323. package/dist/esm/store/duckdb/batch.d.ts +16 -0
  324. package/dist/esm/store/duckdb/batch.js +96 -0
  325. package/dist/esm/store/duckdb/batch.js.map +1 -0
  326. package/dist/esm/store/duckdb/connection.d.ts +5 -0
  327. package/dist/esm/store/duckdb/connection.js +97 -0
  328. package/dist/esm/store/duckdb/connection.js.map +1 -0
  329. package/dist/esm/store/duckdb/lexical.d.ts +10 -0
  330. package/dist/esm/store/duckdb/lexical.js +163 -0
  331. package/dist/esm/store/duckdb/lexical.js.map +1 -0
  332. package/dist/esm/store/duckdb/native.d.ts +3 -0
  333. package/dist/esm/store/duckdb/native.js +96 -0
  334. package/dist/esm/store/duckdb/native.js.map +1 -0
  335. package/dist/esm/store/duckdb/open.d.ts +12 -0
  336. package/dist/esm/store/duckdb/open.js +127 -0
  337. package/dist/esm/store/duckdb/open.js.map +1 -0
  338. package/dist/esm/store/duckdb/reconcile.d.ts +6 -0
  339. package/dist/esm/store/duckdb/reconcile.js +141 -0
  340. package/dist/esm/store/duckdb/reconcile.js.map +1 -0
  341. package/dist/esm/store/duckdb/sql-functions.d.ts +2 -0
  342. package/dist/esm/store/duckdb/sql-functions.js +56 -0
  343. package/dist/esm/store/duckdb/sql-functions.js.map +1 -0
  344. package/dist/esm/store/duckdb/store.d.ts +5 -0
  345. package/dist/esm/store/duckdb/store.js +104 -0
  346. package/dist/esm/store/duckdb/store.js.map +1 -0
  347. package/dist/esm/store/duckdb/vectors.d.ts +9 -0
  348. package/dist/esm/store/duckdb/vectors.js +171 -0
  349. package/dist/esm/store/duckdb/vectors.js.map +1 -0
  350. package/dist/esm/store/index.d.ts +7 -0
  351. package/dist/esm/store/index.js +35 -0
  352. package/dist/esm/store/index.js.map +1 -0
  353. package/dist/esm/store/meta.d.ts +3 -0
  354. package/dist/esm/store/meta.js +17 -0
  355. package/dist/esm/store/meta.js.map +1 -0
  356. package/dist/esm/store/shared.d.ts +5 -0
  357. package/dist/esm/store/shared.js +24 -0
  358. package/dist/esm/store/shared.js.map +1 -0
  359. package/dist/esm/store/sql-functions.d.ts +2 -0
  360. package/dist/esm/store/sql-functions.js +29 -0
  361. package/dist/esm/store/sql-functions.js.map +1 -0
  362. package/dist/esm/store/sqlite/connection.d.ts +3 -0
  363. package/dist/esm/store/sqlite/connection.js +50 -0
  364. package/dist/esm/store/sqlite/connection.js.map +1 -0
  365. package/dist/esm/store/sqlite/lexical.d.ts +2 -0
  366. package/dist/esm/store/sqlite/lexical.js +21 -0
  367. package/dist/esm/store/sqlite/lexical.js.map +1 -0
  368. package/dist/esm/{db → store/sqlite}/open.d.ts +5 -5
  369. package/dist/esm/store/sqlite/open.js +193 -0
  370. package/dist/esm/store/sqlite/open.js.map +1 -0
  371. package/dist/esm/store/sqlite/reconcile.d.ts +10 -0
  372. package/dist/esm/{db → store/sqlite}/reconcile.js +101 -99
  373. package/dist/esm/store/sqlite/reconcile.js.map +1 -0
  374. package/dist/esm/store/sqlite/sql-functions.js +23 -0
  375. package/dist/esm/store/sqlite/sql-functions.js.map +1 -0
  376. package/dist/esm/store/sqlite/store.d.ts +5 -0
  377. package/dist/esm/store/sqlite/store.js +82 -0
  378. package/dist/esm/store/sqlite/store.js.map +1 -0
  379. package/dist/esm/store/sqlite/vectors.d.ts +13 -0
  380. package/dist/esm/store/sqlite/vectors.js +90 -0
  381. package/dist/esm/store/sqlite/vectors.js.map +1 -0
  382. package/dist/esm/store/transaction.d.ts +5 -0
  383. package/dist/esm/store/transaction.js +57 -0
  384. package/dist/esm/store/transaction.js.map +1 -0
  385. package/dist/esm/store/types.d.ts +91 -0
  386. package/dist/esm/store/types.js +11 -0
  387. package/dist/esm/store/types.js.map +1 -0
  388. package/dist/esm/watch.d.ts +3 -0
  389. package/dist/esm/watch.js +39 -22
  390. package/dist/esm/watch.js.map +1 -1
  391. package/package.json +3 -1
  392. package/schema.json +5 -0
  393. package/skills/sense/EXAMPLES.md +3 -3
  394. package/skills/sense/SKILL.md +16 -14
  395. package/skills/sense-bases/EXAMPLES.md +2 -2
  396. package/skills/sense-bases/SKILL.md +6 -6
  397. package/skills/sense-setup/EXAMPLES.md +1 -1
  398. package/skills/sense-setup/SKILL.md +5 -4
  399. package/dist/cjs/db/index.d.cts +0 -4
  400. package/dist/cjs/db/index.d.ts +0 -4
  401. package/dist/cjs/db/index.js +0 -42
  402. package/dist/cjs/db/index.js.map +0 -1
  403. package/dist/cjs/db/open.js +0 -230
  404. package/dist/cjs/db/open.js.map +0 -1
  405. package/dist/cjs/db/reconcile.d.cts +0 -10
  406. package/dist/cjs/db/reconcile.d.ts +0 -10
  407. package/dist/cjs/db/reconcile.js +0 -579
  408. package/dist/cjs/db/reconcile.js.map +0 -1
  409. package/dist/cjs/db/shared.d.cts +0 -5
  410. package/dist/cjs/db/shared.d.ts +0 -5
  411. package/dist/cjs/db/shared.js +0 -45
  412. package/dist/cjs/db/shared.js.map +0 -1
  413. package/dist/cjs/db/sql-functions.js +0 -64
  414. package/dist/cjs/db/sql-functions.js.map +0 -1
  415. package/dist/esm/db/index.d.ts +0 -4
  416. package/dist/esm/db/index.js +0 -5
  417. package/dist/esm/db/index.js.map +0 -1
  418. package/dist/esm/db/open.js +0 -165
  419. package/dist/esm/db/open.js.map +0 -1
  420. package/dist/esm/db/reconcile.d.ts +0 -10
  421. package/dist/esm/db/reconcile.js.map +0 -1
  422. package/dist/esm/db/shared.d.ts +0 -5
  423. package/dist/esm/db/shared.js +0 -20
  424. package/dist/esm/db/shared.js.map +0 -1
  425. package/dist/esm/db/sql-functions.js +0 -47
  426. package/dist/esm/db/sql-functions.js.map +0 -1
  427. /package/dist/cjs/{db → store/sqlite}/sql-functions.d.cts +0 -0
  428. /package/dist/cjs/{db → store/sqlite}/sql-functions.d.ts +0 -0
  429. /package/dist/esm/{db → store/sqlite}/sql-functions.d.ts +0 -0
@@ -0,0 +1,163 @@
1
+ // D1: fts BM25 for ranking, contains() scans for exact substring / phrase verification /
2
+ // unspaced scripts, JS excerpts for every doc (no snippet()).
3
+ import { SenseError } from '../../errors.js';
4
+ import { UNSPACED_SCRIPTS } from '../../text/segment.js';
5
+ import { withTransaction } from '../transaction.js';
6
+ const FIELDS = [
7
+ 'title',
8
+ 'summary',
9
+ 'text'
10
+ ];
11
+ // Mirrors sqlite's bm25(content, 10.0, 5.0, 1.0, ...) column-weight intent; DuckDB's
12
+ // match_bm25 has no field_weights argument (verified 1.5.5), so each field is scored
13
+ // separately and combined here.
14
+ const FIELD_WEIGHT = {
15
+ title: 10,
16
+ summary: 5,
17
+ text: 1
18
+ };
19
+ const UNSPACED_RUN = new RegExp(`[${UNSPACED_SCRIPTS}]`, 'u');
20
+ // FTS5 operator syntax (sqlite.org/fts5.html sec. 3) that words/substrings below would
21
+ // otherwise silently treat as literal terms instead of honoring (PRINCIPLES.md #6). Checked
22
+ // against terms with quoted spans blanked -- those go through contains() and are supported.
23
+ const FTS5_OPERATORS = [
24
+ {
25
+ label: 'prefix query',
26
+ re: /[\p{L}\p{N}_]+\*(?=\s|$)/u
27
+ },
28
+ {
29
+ label: 'boolean operator',
30
+ re: /(?:^|\s)(?:AND|OR|NOT)(?=\s|$)/
31
+ },
32
+ {
33
+ label: 'NEAR operator',
34
+ re: /(?:^|\s)NEAR\b/
35
+ },
36
+ {
37
+ label: 'initial-token operator',
38
+ re: /(?:^|\s)\^\S+/
39
+ },
40
+ {
41
+ label: 'column filter',
42
+ re: /(?:^|\s)[\p{L}_]\w*\s*:/u
43
+ }
44
+ ];
45
+ function unsupportedOperator(terms) {
46
+ const withoutPhrases = terms.replace(/"[^"]*"/g, ' ');
47
+ for (const { label, re } of FTS5_OPERATORS){
48
+ const m = withoutPhrases.match(re);
49
+ if (m) return {
50
+ label,
51
+ token: m[0].trim()
52
+ };
53
+ }
54
+ return null;
55
+ }
56
+ // A run whose script marks no word boundaries makes match_bm25's whitespace tokenizer index
57
+ // the whole run as one token (same gap FTS5 has without the `_seg` sidecar), so such runs --
58
+ // and any author-quoted phrase, any script -- go through contains() instead of the fts index.
59
+ function splitTerms(terms) {
60
+ const words = [];
61
+ const substrings = [];
62
+ const withoutPhrases = terms.replace(/"([^"]*)"/g, (_m, inner)=>{
63
+ const phrase = inner.trim();
64
+ if (phrase.length === 0) return ' ';
65
+ substrings.push(phrase);
66
+ if (!UNSPACED_RUN.test(phrase)) {
67
+ for (const w of phrase.split(/\s+/))if (w.length > 0) words.push(w);
68
+ }
69
+ return ' ';
70
+ });
71
+ for (const tok of withoutPhrases.split(/\s+/)){
72
+ if (tok.length === 0) continue;
73
+ if (UNSPACED_RUN.test(tok)) substrings.push(tok);
74
+ else words.push(tok);
75
+ }
76
+ return {
77
+ words,
78
+ substrings
79
+ };
80
+ }
81
+ // No incremental update (verified 1.5.5: PRAGMA create_fts_index is rebuild-only), so this
82
+ // pays the full rebuild -- but only once per store instance's first lexical query, and only
83
+ // when content actually changed since (see FtsIndexState / duckdb/store.ts's markStale()).
84
+ async function ensureFtsFresh(conn, state) {
85
+ if (!state.stale) return;
86
+ await conn.exec('INSTALL fts; LOAD fts;');
87
+ await withTransaction(conn, async ()=>{
88
+ // stopwords='none': sqlite's porter/unicode61 tokenizer never removes stopwords either
89
+ // (verified 1.5.5), and the fts extension's default 571-word English list would otherwise
90
+ // silently drop common query words (e.g. "and") from the index but not from match_bm25's
91
+ // conjunctive gate, making a bare multi-word query that contains one match nothing.
92
+ await conn.exec(`PRAGMA create_fts_index('content', 'path', 'title', 'summary', 'text', stopwords='none', overwrite=1)`);
93
+ });
94
+ state.stale = false;
95
+ }
96
+ // Per-word-term and per-substring-term SQL fragments, field-weighted the same way for both
97
+ // (title 10 / summary 5 / text 1), plus the params in the exact left-to-right order they are
98
+ // emitted -- positional `?` binding requires that order to match the assembled SQL text.
99
+ function buildScoreAndGate(words, substrings) {
100
+ const scoreParts = [];
101
+ const gateParts = [];
102
+ const params = [];
103
+ if (words.length > 0) {
104
+ const wordQuery = words.join(' ');
105
+ for (const field of FIELDS){
106
+ scoreParts.push(`${FIELD_WEIGHT[field]}.0 * COALESCE(fts_main_content.match_bm25(content."path", ?, fields := '${field}', conjunctive := false), 0)`);
107
+ params.push(wordQuery);
108
+ }
109
+ gateParts.push(`fts_main_content.match_bm25(content."path", ?, conjunctive := true) IS NOT NULL`);
110
+ params.push(wordQuery);
111
+ }
112
+ for (const raw of substrings){
113
+ const needle = raw.toLowerCase();
114
+ for (const field of FIELDS){
115
+ scoreParts.push(`${FIELD_WEIGHT[field]}.0 * CAST(contains(lower(content.${field}), ?) AS INTEGER)`);
116
+ params.push(needle);
117
+ }
118
+ gateParts.push(`contains(lower(content.title) || ' ' || lower(content.summary) || ' ' || lower(content.text), ?)`);
119
+ params.push(needle);
120
+ }
121
+ return {
122
+ scoreSql: scoreParts.join(' + '),
123
+ gateSql: gateParts.join(' AND '),
124
+ params
125
+ };
126
+ }
127
+ // Ranked word-match query, scoped by the caller-built SQL fragments (same fragments sqlite's
128
+ // queryLexical takes). `hit` is always NULL: no snippet() equivalent exists, so every row goes
129
+ // through the caller's JS excerpt fallback (commands/search.ts) rather than a second excerpt path.
130
+ export async function queryLexical(conn, terms, opts, state) {
131
+ const unsupported = unsupportedOperator(terms);
132
+ if (unsupported !== null) {
133
+ throw new SenseError('STORE_CAPABILITY_MISSING', `store "duckdb" does not implement FTS5's ${unsupported.label} ("${unsupported.token}") in this build; rephrase "${terms.trim()}" without it, or set "store" to "sqlite" in this tree's config to search it as written`);
134
+ }
135
+ const { whereJoin, whereCond, scopeCond, limit } = opts;
136
+ const { words, substrings } = splitTerms(terms);
137
+ if (words.length === 0 && substrings.length === 0) return [];
138
+ if (words.length > 0) await ensureFtsFresh(conn, state);
139
+ const { scoreSql, gateSql, params } = buildScoreAndGate(words, substrings);
140
+ const sql = `SELECT path, NULL AS hit FROM (
141
+ SELECT content."path" AS path, (${scoreSql}) AS score
142
+ FROM content
143
+ ${whereJoin}
144
+ WHERE ${gateSql}
145
+ ${whereCond} ${scopeCond}
146
+ ) sq ORDER BY score DESC, path LIMIT ?`;
147
+ const stmt = await conn.prepare(sql);
148
+ return await stmt.all(...params, limit);
149
+ }
150
+ // One instance per store: `stale` starts true (a fresh connection cannot know whether an
151
+ // on-disk fts schema still matches `content`), and store.ts's reconcile wrapper flips it back
152
+ // to true whenever content changes, so the next query rebuilds before ranking against it.
153
+ export function createLexicalIndex(conn) {
154
+ const state = {
155
+ stale: true
156
+ };
157
+ return {
158
+ query: (terms, opts)=>queryLexical(conn, terms, opts, state),
159
+ markStale: ()=>{
160
+ state.stale = true;
161
+ }
162
+ };
163
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/lexical.ts"],"sourcesContent":["// D1: fts BM25 for ranking, contains() scans for exact substring / phrase verification /\n// unspaced scripts, JS excerpts for every doc (no snippet()).\nimport { SenseError } from '../../errors.ts';\nimport { UNSPACED_SCRIPTS } from '../../text/segment.ts';\nimport { withTransaction } from '../transaction.ts';\nimport type { Connection, LexicalHit, LexicalQueryOptions } from '../types.ts';\n\nconst FIELDS = ['title', 'summary', 'text'] as const;\ntype Field = (typeof FIELDS)[number];\n// Mirrors sqlite's bm25(content, 10.0, 5.0, 1.0, ...) column-weight intent; DuckDB's\n// match_bm25 has no field_weights argument (verified 1.5.5), so each field is scored\n// separately and combined here.\nconst FIELD_WEIGHT: Record<Field, number> = { title: 10, summary: 5, text: 1 };\n\nconst UNSPACED_RUN = new RegExp(`[${UNSPACED_SCRIPTS}]`, 'u');\n\n// FTS5 operator syntax (sqlite.org/fts5.html sec. 3) that words/substrings below would\n// otherwise silently treat as literal terms instead of honoring (PRINCIPLES.md #6). Checked\n// against terms with quoted spans blanked -- those go through contains() and are supported.\nconst FTS5_OPERATORS: Array<{ label: string; re: RegExp }> = [\n { label: 'prefix query', re: /[\\p{L}\\p{N}_]+\\*(?=\\s|$)/u },\n { label: 'boolean operator', re: /(?:^|\\s)(?:AND|OR|NOT)(?=\\s|$)/ },\n { label: 'NEAR operator', re: /(?:^|\\s)NEAR\\b/ },\n { label: 'initial-token operator', re: /(?:^|\\s)\\^\\S+/ },\n { label: 'column filter', re: /(?:^|\\s)[\\p{L}_]\\w*\\s*:/u },\n];\n\nfunction unsupportedOperator(terms: string): { label: string; token: string } | null {\n const withoutPhrases = terms.replace(/\"[^\"]*\"/g, ' ');\n for (const { label, re } of FTS5_OPERATORS) {\n const m = withoutPhrases.match(re);\n if (m) return { label, token: m[0].trim() };\n }\n return null;\n}\n\ninterface FtsIndexState {\n stale: boolean;\n}\n\n// A run whose script marks no word boundaries makes match_bm25's whitespace tokenizer index\n// the whole run as one token (same gap FTS5 has without the `_seg` sidecar), so such runs --\n// and any author-quoted phrase, any script -- go through contains() instead of the fts index.\nfunction splitTerms(terms: string): { words: string[]; substrings: string[] } {\n const words: string[] = [];\n const substrings: string[] = [];\n const withoutPhrases = terms.replace(/\"([^\"]*)\"/g, (_m, inner: string) => {\n const phrase = inner.trim();\n if (phrase.length === 0) return ' ';\n substrings.push(phrase);\n if (!UNSPACED_RUN.test(phrase)) for (const w of phrase.split(/\\s+/)) if (w.length > 0) words.push(w);\n return ' ';\n });\n for (const tok of withoutPhrases.split(/\\s+/)) {\n if (tok.length === 0) continue;\n if (UNSPACED_RUN.test(tok)) substrings.push(tok);\n else words.push(tok);\n }\n return { words, substrings };\n}\n\n// No incremental update (verified 1.5.5: PRAGMA create_fts_index is rebuild-only), so this\n// pays the full rebuild -- but only once per store instance's first lexical query, and only\n// when content actually changed since (see FtsIndexState / duckdb/store.ts's markStale()).\nasync function ensureFtsFresh(conn: Connection, state: FtsIndexState): Promise<void> {\n if (!state.stale) return;\n await conn.exec('INSTALL fts; LOAD fts;');\n await withTransaction(conn, async () => {\n // stopwords='none': sqlite's porter/unicode61 tokenizer never removes stopwords either\n // (verified 1.5.5), and the fts extension's default 571-word English list would otherwise\n // silently drop common query words (e.g. \"and\") from the index but not from match_bm25's\n // conjunctive gate, making a bare multi-word query that contains one match nothing.\n await conn.exec(`PRAGMA create_fts_index('content', 'path', 'title', 'summary', 'text', stopwords='none', overwrite=1)`);\n });\n state.stale = false;\n}\n\n// Per-word-term and per-substring-term SQL fragments, field-weighted the same way for both\n// (title 10 / summary 5 / text 1), plus the params in the exact left-to-right order they are\n// emitted -- positional `?` binding requires that order to match the assembled SQL text.\nfunction buildScoreAndGate(words: string[], substrings: string[]): { scoreSql: string; gateSql: string; params: unknown[] } {\n const scoreParts: string[] = [];\n const gateParts: string[] = [];\n const params: unknown[] = [];\n\n if (words.length > 0) {\n const wordQuery = words.join(' ');\n for (const field of FIELDS) {\n scoreParts.push(`${FIELD_WEIGHT[field]}.0 * COALESCE(fts_main_content.match_bm25(content.\"path\", ?, fields := '${field}', conjunctive := false), 0)`);\n params.push(wordQuery);\n }\n gateParts.push(`fts_main_content.match_bm25(content.\"path\", ?, conjunctive := true) IS NOT NULL`);\n params.push(wordQuery);\n }\n\n for (const raw of substrings) {\n const needle = raw.toLowerCase();\n for (const field of FIELDS) {\n scoreParts.push(`${FIELD_WEIGHT[field]}.0 * CAST(contains(lower(content.${field}), ?) AS INTEGER)`);\n params.push(needle);\n }\n gateParts.push(`contains(lower(content.title) || ' ' || lower(content.summary) || ' ' || lower(content.text), ?)`);\n params.push(needle);\n }\n\n return { scoreSql: scoreParts.join(' + '), gateSql: gateParts.join(' AND '), params };\n}\n\n// Ranked word-match query, scoped by the caller-built SQL fragments (same fragments sqlite's\n// queryLexical takes). `hit` is always NULL: no snippet() equivalent exists, so every row goes\n// through the caller's JS excerpt fallback (commands/search.ts) rather than a second excerpt path.\nexport async function queryLexical(conn: Connection, terms: string, opts: LexicalQueryOptions, state: FtsIndexState): Promise<LexicalHit[]> {\n const unsupported = unsupportedOperator(terms);\n if (unsupported !== null) {\n throw new SenseError('STORE_CAPABILITY_MISSING', `store \"duckdb\" does not implement FTS5's ${unsupported.label} (\"${unsupported.token}\") in this build; rephrase \"${terms.trim()}\" without it, or set \"store\" to \"sqlite\" in this tree's config to search it as written`);\n }\n const { whereJoin, whereCond, scopeCond, limit } = opts;\n const { words, substrings } = splitTerms(terms);\n if (words.length === 0 && substrings.length === 0) return [];\n\n if (words.length > 0) await ensureFtsFresh(conn, state);\n\n const { scoreSql, gateSql, params } = buildScoreAndGate(words, substrings);\n const sql = `SELECT path, NULL AS hit FROM (\n SELECT content.\"path\" AS path, (${scoreSql}) AS score\n FROM content\n ${whereJoin}\n WHERE ${gateSql}\n ${whereCond} ${scopeCond}\n ) sq ORDER BY score DESC, path LIMIT ?`;\n const stmt = await conn.prepare(sql);\n return (await stmt.all(...params, limit)) as unknown as LexicalHit[];\n}\n\n// One instance per store: `stale` starts true (a fresh connection cannot know whether an\n// on-disk fts schema still matches `content`), and store.ts's reconcile wrapper flips it back\n// to true whenever content changes, so the next query rebuilds before ranking against it.\nexport function createLexicalIndex(conn: Connection): { query: (terms: string, opts: LexicalQueryOptions) => Promise<LexicalHit[]>; markStale: () => void } {\n const state: FtsIndexState = { stale: true };\n return {\n query: (terms, opts) => queryLexical(conn, terms, opts, state),\n markStale: () => {\n state.stale = true;\n },\n };\n}\n"],"names":["SenseError","UNSPACED_SCRIPTS","withTransaction","FIELDS","FIELD_WEIGHT","title","summary","text","UNSPACED_RUN","RegExp","FTS5_OPERATORS","label","re","unsupportedOperator","terms","withoutPhrases","replace","m","match","token","trim","splitTerms","words","substrings","_m","inner","phrase","length","push","test","w","split","tok","ensureFtsFresh","conn","state","stale","exec","buildScoreAndGate","scoreParts","gateParts","params","wordQuery","join","field","raw","needle","toLowerCase","scoreSql","gateSql","queryLexical","opts","unsupported","whereJoin","whereCond","scopeCond","limit","sql","stmt","prepare","all","createLexicalIndex","query","markStale"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const DUCKDB_PACKAGE = "@duckdb/node-api";
2
+ export declare function loadOrInstall<T>(importName: string, nodeModulesPath: string): Promise<T>;
3
+ export declare function duckdbApi(): Promise<typeof import('@duckdb/node-api')>;
@@ -0,0 +1,96 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import Module from 'node:module';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath, pathToFileURL } from 'node:url';
5
+ import { SenseError } from '../../errors.js';
6
+ export const DUCKDB_PACKAGE = '@duckdb/node-api';
7
+ // install-module-linked itself is small, but its target (@duckdb/node-api) is not: deferred the
8
+ // same way embed/static.ts defers @huggingface/tokenizers, so requiring it stays inside the
9
+ // already-gated branch below rather than running for every store/index.ts load.
10
+ const _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;
11
+ 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`): `tsds test:node` runs straight
14
+ // off src/, one directory shallower than dist/{cjs,esm}/store/duckdb sits under the built
15
+ // package, so a hardcoded depth is right for one and wrong for the other. Works the same way
16
+ // for a globally-installed `sense`, whose node_modules this resolves to -- never the caller's
17
+ // cwd.
18
+ function packageNodeModules() {
19
+ let dir = _dirname;
20
+ for(;;){
21
+ const candidate = join(dir, 'package.json');
22
+ if (existsSync(candidate)) {
23
+ const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
24
+ if (pkg.name === 'sensemaking') return join(dir, 'node_modules');
25
+ }
26
+ const parent = dirname(dir);
27
+ if (parent === dir) throw new Error('could not locate the sensemaking package root to install @duckdb/node-api into');
28
+ dir = parent;
29
+ }
30
+ }
31
+ const installing = new Map();
32
+ // De-dupes concurrent installs within this process (see spawn-term's loadInk for the shape).
33
+ // The install here is async -- the load site already awaits a dynamic import -- so a bare
34
+ // attempted flag (xz-compat's `installationAttempted`, enough for its synchronous single-target
35
+ // install) isn't enough on its own; concurrent callers instead await the same in-flight promise,
36
+ // keyed by specifier and target so tests exercising several installs in one process don't share
37
+ // one slot. A failed install is not cached, so a later call retries.
38
+ function installOnce(specifier, nodeModulesPath) {
39
+ const key = `${specifier}\n${nodeModulesPath}`;
40
+ let promise = installing.get(key);
41
+ if (!promise) {
42
+ const installModuleLinked = _require('install-module-linked').default;
43
+ promise = installModuleLinked(specifier, nodeModulesPath).then(()=>{}, (err)=>{
44
+ installing.delete(key);
45
+ throw err;
46
+ });
47
+ installing.set(key, promise);
48
+ }
49
+ return promise;
50
+ }
51
+ // Node caches a bare specifier's package resolution -- including a "not found" miss -- for the
52
+ // life of the process, so retrying `import(importName)` right after installing sees the same
53
+ // stale miss and falls through to a bogus index.js guess instead of the package's real entry
54
+ // (verified against a real global install: the plain-specifier retry failed there, this did
55
+ // not). Reading the freshly-installed package's own package.json and importing its entry file
56
+ // by an absolute file: URL sidesteps that stale cache instead of re-resolving the bare name.
57
+ function resolveEntryUrl(importName, nodeModulesPath) {
58
+ var _pkg_main;
59
+ const pkgDir = join(nodeModulesPath, ...importName.split('/'));
60
+ const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8'));
61
+ const entry = typeof pkg.exports === 'string' ? pkg.exports : (_pkg_main = pkg.main) !== null && _pkg_main !== void 0 ? _pkg_main : 'index.js';
62
+ return pathToFileURL(join(pkgDir, entry)).href;
63
+ }
64
+ // Import-then-install-then-retry, over an injectable target so the failure path (import AND
65
+ // install both fail) can be exercised for real in tests without downloading @duckdb/node-api's
66
+ // real ~110MB native binding. Production always passes @duckdb/node-api's own name; the thrown
67
+ // error names @duckdb/node-api regardless, since that is what a real user needs to install.
68
+ export async function loadOrInstall(importName, nodeModulesPath) {
69
+ try {
70
+ return await import(importName);
71
+ } catch {
72
+ console.error(`sense: store "duckdb" needs ${DUCKDB_PACKAGE}; installing it now (one-time download, ~110MB)...`);
73
+ try {
74
+ await installOnce(importName, nodeModulesPath);
75
+ } catch (installErr) {
76
+ throw new SenseError('STORE_DEPENDENCY_MISSING', `store "duckdb" needs ${DUCKDB_PACKAGE}, and installing it automatically failed (${installErr.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 ${DUCKDB_PACKAGE}\` yourself and try again`);
77
+ }
78
+ try {
79
+ return await import(resolveEntryUrl(importName, nodeModulesPath));
80
+ } catch (loadErr) {
81
+ throw new SenseError('STORE_DEPENDENCY_MISSING', `store "duckdb" installed ${DUCKDB_PACKAGE} but it could not be loaded (${loadErr.message}); it may not be available for this platform (${process.platform}-${process.arch}) -- run \`npm install ${DUCKDB_PACKAGE}\` to see the underlying error`);
82
+ }
83
+ }
84
+ }
85
+ let duckdbApiPromise;
86
+ // The one accessor every consumer of @duckdb/node-api (open.ts, sql-functions.ts, vectors.ts)
87
+ // must go through, rather than each doing its own `import('@duckdb/node-api')`: once a fresh
88
+ // install has happened in this process, Node keeps returning that pre-install "not found" miss
89
+ // to any NEW bare-specifier lookup of the same path, so a second independent import elsewhere
90
+ // in the process fails even though the package is now genuinely on disk (the same stale-cache
91
+ // problem loadOrInstall works around for its own retry). Caching the resolved module here means
92
+ // only the first caller ever resolves the bare specifier at all.
93
+ export function duckdbApi() {
94
+ if (!duckdbApiPromise) duckdbApiPromise = loadOrInstall(DUCKDB_PACKAGE, packageNodeModules());
95
+ return duckdbApiPromise;
96
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/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\nexport const DUCKDB_PACKAGE = '@duckdb/node-api';\n\n// install-module-linked itself is small, but its target (@duckdb/node-api) is not: deferred the\n// same way embed/static.ts defers @huggingface/tokenizers, so requiring it stays inside the\n// already-gated branch below rather 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`): `tsds test:node` runs straight\n// off src/, one directory shallower than dist/{cjs,esm}/store/duckdb sits under the built\n// package, so a hardcoded depth is right for one and wrong for the other. Works the same way\n// for a globally-installed `sense`, whose node_modules this resolves to -- never the caller's\n// cwd.\nfunction 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 @duckdb/node-api 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 target so the failure path (import AND\n// install both fail) can be exercised for real in tests without downloading @duckdb/node-api's\n// real ~110MB native binding. Production always passes @duckdb/node-api's own name; the thrown\n// error names @duckdb/node-api regardless, since that is what a real user needs to install.\nexport async function loadOrInstall<T>(importName: string, nodeModulesPath: string): Promise<T> {\n try {\n return (await import(importName)) as T;\n } catch {\n console.error(`sense: store \"duckdb\" needs ${DUCKDB_PACKAGE}; installing it now (one-time download, ~110MB)...`);\n try {\n await installOnce(importName, nodeModulesPath);\n } catch (installErr) {\n throw new SenseError(\n 'STORE_DEPENDENCY_MISSING',\n `store \"duckdb\" needs ${DUCKDB_PACKAGE}, 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 ${DUCKDB_PACKAGE}\\` yourself and try again`\n );\n }\n try {\n return (await import(resolveEntryUrl(importName, nodeModulesPath))) as T;\n } catch (loadErr) {\n throw new SenseError('STORE_DEPENDENCY_MISSING', `store \"duckdb\" installed ${DUCKDB_PACKAGE} 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 ${DUCKDB_PACKAGE}\\` to see the underlying error`);\n }\n }\n}\n\nlet duckdbApiPromise: Promise<typeof import('@duckdb/node-api')> | undefined;\n\n// The one accessor every consumer of @duckdb/node-api (open.ts, sql-functions.ts, vectors.ts)\n// must go through, rather than each doing its own `import('@duckdb/node-api')`: once a fresh\n// install has happened in this process, Node keeps returning that pre-install \"not found\" miss\n// to any NEW bare-specifier lookup of the same path, so a second independent import elsewhere\n// in the process fails even though the package is now genuinely on disk (the same stale-cache\n// problem loadOrInstall works around for its own retry). Caching the resolved module here means\n// only the first caller ever resolves the bare specifier at all.\nexport function duckdbApi(): Promise<typeof import('@duckdb/node-api')> {\n if (!duckdbApiPromise) duckdbApiPromise = loadOrInstall<typeof import('@duckdb/node-api')>(DUCKDB_PACKAGE, packageNodeModules());\n return duckdbApiPromise;\n}\n"],"names":["existsSync","readFileSync","Module","dirname","join","fileURLToPath","pathToFileURL","SenseError","DUCKDB_PACKAGE","_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","console","error","installErr","message","loadErr","process","platform","arch","duckdbApiPromise","duckdbApi"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { ResolvedConfig } from '../../config/index.js';
2
+ import type { Store } from '../types.js';
3
+ export declare const DB_FILENAME = "cache.duckdb";
4
+ export declare const SCHEMA_VERSION = "2";
5
+ export interface OpenResult {
6
+ store: Store;
7
+ cfg: ResolvedConfig;
8
+ dbPath: string;
9
+ parsed: number;
10
+ warnings: string[];
11
+ }
12
+ export declare function openDuckdb(cfg: ResolvedConfig): Promise<OpenResult>;
@@ -0,0 +1,127 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { featureSignature, STATE_DIR } from '../../config/index.js';
4
+ import { STORE_DIMS } from '../../embed/types.js';
5
+ import { SenseError } from '../../errors.js';
6
+ import { activeFeatures, FEATURES } from '../../features/index.js';
7
+ import { getMeta, setMeta } from '../shared.js';
8
+ import { clearCache } from '../sqlite/open.js';
9
+ import { createConnection } from './connection.js';
10
+ import { DUCKDB_PACKAGE, duckdbApi } from './native.js';
11
+ import { reconcile } from './reconcile.js';
12
+ import { registerFunctions } from './sql-functions.js';
13
+ import { createStore } from './store.js';
14
+ export const DB_FILENAME = 'cache.duckdb';
15
+ // Independent of sqlite's SCHEMA_VERSION -- the two stores' cache shapes evolve separately
16
+ // (VARIANT frontmatter columns instead of untyped ones), and the store name already joins the
17
+ // feature signature (see featureSignature), so a config's `store` key switching cleanly
18
+ // rebuilds rather than reusing the other engine's stale cache shape.
19
+ export const SCHEMA_VERSION = '2';
20
+ // `content` is a plain table (not FTS-virtual): D1 builds DuckDB's fts index over it lazily,
21
+ // only when a lexical query actually runs (see duckdb/lexical.ts), and reads it directly for
22
+ // contains() substring/phrase/unspaced-script verification either way. No tokenizer resolution
23
+ // -- this store always uses the fts extension's default (porter) stemmer, unlike sqlite's
24
+ // configurable `content.tokenize` (not read here; D1 does not extend that knob to DuckDB).
25
+ async function ensureSchema(conn, cfg) {
26
+ await conn.exec(`CREATE TABLE IF NOT EXISTS frontmatter ("path" TEXT PRIMARY KEY, "_mtime" DOUBLE, "_ctime" DOUBLE, "_size" INTEGER, "_parse_error" TEXT)`);
27
+ await conn.exec(`CREATE TABLE IF NOT EXISTS content ("path" TEXT PRIMARY KEY, title TEXT, summary TEXT, text TEXT)`);
28
+ await conn.exec(`CREATE TABLE IF NOT EXISTS preset_files ("path" TEXT, preset TEXT, PRIMARY KEY ("path", preset))`);
29
+ await conn.exec('CREATE INDEX IF NOT EXISTS preset_files_preset ON preset_files(preset)');
30
+ for (const feature of activeFeatures(cfg)){
31
+ // Native FLOAT[STORE_DIMS] instead of the embed feature's engine-neutral BLOB+scale DDL
32
+ // (see duckdb/vectors.ts). `scale` is kept, unused, so the feature's shared reconcile-time
33
+ // INSERT/DELETE (src/features/embed.ts) still names a column that exists on both stores.
34
+ if (feature.name === 'embed') {
35
+ await conn.exec(`CREATE TABLE IF NOT EXISTS embeddings ("path" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector FLOAT[${STORE_DIMS}], PRIMARY KEY ("path", chunk))`);
36
+ continue;
37
+ }
38
+ await feature.schema(conn);
39
+ }
40
+ if (await getMeta(conn, 'schema_version') === null) await setMeta(conn, 'schema_version', SCHEMA_VERSION);
41
+ if (await getMeta(conn, 'features') === null) await setMeta(conn, 'features', featureSignature(cfg, FEATURES));
42
+ }
43
+ async function connect(cfg) {
44
+ const stateDir = join(cfg.baseDir, STATE_DIR);
45
+ mkdirSync(stateDir, {
46
+ recursive: true
47
+ });
48
+ const dbPath = join(stateDir, DB_FILENAME);
49
+ // Dynamic, not a top-level import: sqlite trees must never even attempt to resolve this
50
+ // optional peer dependency, so nothing in this store's module graph imports it as a value
51
+ // until a duckdb tree is actually opened (types-only imports elsewhere are erased and cost
52
+ // nothing either way). Installed on first use if missing, and shared with sql-functions.ts
53
+ // and vectors.ts (see native.ts's duckdbApi).
54
+ let DuckDBInstance;
55
+ try {
56
+ ({ DuckDBInstance } = await duckdbApi());
57
+ } catch (err) {
58
+ if (err instanceof SenseError) throw err;
59
+ throw new SenseError('STORE_DEPENDENCY_MISSING', `store "duckdb" needs the ${DUCKDB_PACKAGE} package (${err.message})`);
60
+ }
61
+ let duckdb;
62
+ let instance;
63
+ try {
64
+ // On-disk files default to an older storage format for cross-version compatibility, which
65
+ // rejects VARIANT columns ("VARIANT columns are not supported in storage versions prior to
66
+ // v1.5.0"); this store's dynamic frontmatter columns need VARIANT (see reconcile.ts), so
67
+ // the format floor is pinned explicitly. :memory: databases are unaffected either way.
68
+ instance = await DuckDBInstance.create(dbPath, {
69
+ storage_compatibility_version: 'v1.5.0'
70
+ });
71
+ duckdb = await instance.connect();
72
+ } catch (err) {
73
+ // create() may have succeeded before connect() failed: close it, or its WAL stays open.
74
+ instance === null || instance === void 0 ? void 0 : instance.closeSync();
75
+ throw new SenseError('STORE_DEPENDENCY_MISSING', `store "duckdb" failed to open ${dbPath}: ${err.message}`);
76
+ }
77
+ // A throw below (schema, reconcile's COLUMN_LIMIT) would leak the open instance, whose WAL
78
+ // then locks the .duckdb file undeletable on Windows. closed marks the rebuild branch.
79
+ let closed = false;
80
+ try {
81
+ await registerFunctions(duckdb);
82
+ const conn = createConnection(duckdb);
83
+ await conn.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');
84
+ const version = await getMeta(conn, 'schema_version');
85
+ const features = await getMeta(conn, 'features');
86
+ const wantFeatures = featureSignature(cfg, FEATURES);
87
+ if (version !== null && version !== SCHEMA_VERSION || features !== null && features !== wantFeatures) {
88
+ // Reconcile only reparses changed files, so a stale cache can't be patched incrementally --
89
+ // rebuild instead, same as sqlite's open() (see its comment for why this is announced).
90
+ const reason = version !== null && version !== SCHEMA_VERSION ? 'cache format changed (new sensemaking version)' : 'config change (features, tokenizer, or presets)';
91
+ console.error(`sense: ${reason}; rebuilding the index`);
92
+ // Close before clearCache deletes the files underneath the still-open instance.
93
+ closed = true;
94
+ duckdb.disconnectSync();
95
+ instance.closeSync();
96
+ clearCache(cfg);
97
+ return connect(cfg);
98
+ }
99
+ await ensureSchema(conn, cfg);
100
+ const { parsed, warnings } = await reconcile(conn, cfg, cfg.baseDir);
101
+ return {
102
+ instance,
103
+ duckdb,
104
+ conn,
105
+ cfg,
106
+ dbPath,
107
+ parsed,
108
+ warnings
109
+ };
110
+ } catch (err) {
111
+ if (!closed) {
112
+ duckdb.disconnectSync();
113
+ instance === null || instance === void 0 ? void 0 : instance.closeSync();
114
+ }
115
+ throw err;
116
+ }
117
+ }
118
+ export async function openDuckdb(cfg) {
119
+ const { instance, duckdb, conn, cfg: resolvedCfg, dbPath, parsed, warnings } = await connect(cfg);
120
+ return {
121
+ store: createStore(instance, duckdb, conn, resolvedCfg, resolvedCfg.baseDir),
122
+ cfg: resolvedCfg,
123
+ dbPath,
124
+ parsed,
125
+ warnings
126
+ };
127
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/store/duckdb/open.ts"],"sourcesContent":["import { mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { DuckDBConnection, DuckDBInstance } from '@duckdb/node-api';\nimport type { Config, ResolvedConfig } from '../../config/index.ts';\nimport { featureSignature, STATE_DIR } from '../../config/index.ts';\nimport { STORE_DIMS } from '../../embed/types.ts';\nimport { SenseError } from '../../errors.ts';\nimport { activeFeatures, FEATURES } from '../../features/index.ts';\nimport { getMeta, setMeta } from '../shared.ts';\nimport { clearCache } from '../sqlite/open.ts';\nimport type { Connection, Store } from '../types.ts';\nimport { createConnection } from './connection.ts';\nimport { DUCKDB_PACKAGE, duckdbApi } from './native.ts';\nimport { reconcile } from './reconcile.ts';\nimport { registerFunctions } from './sql-functions.ts';\nimport { createStore } from './store.ts';\n\nexport const DB_FILENAME = 'cache.duckdb';\n// Independent of sqlite's SCHEMA_VERSION -- the two stores' cache shapes evolve separately\n// (VARIANT frontmatter columns instead of untyped ones), and the store name already joins the\n// feature signature (see featureSignature), so a config's `store` key switching cleanly\n// rebuilds rather than reusing the other engine's stale cache shape.\nexport const SCHEMA_VERSION = '2';\n\nexport interface OpenResult {\n store: Store;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\n// `content` is a plain table (not FTS-virtual): D1 builds DuckDB's fts index over it lazily,\n// only when a lexical query actually runs (see duckdb/lexical.ts), and reads it directly for\n// contains() substring/phrase/unspaced-script verification either way. No tokenizer resolution\n// -- this store always uses the fts extension's default (porter) stemmer, unlike sqlite's\n// configurable `content.tokenize` (not read here; D1 does not extend that knob to DuckDB).\nasync function ensureSchema(conn: Connection, cfg: Config): Promise<void> {\n await conn.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" DOUBLE, \"_ctime\" DOUBLE, \"_size\" INTEGER, \"_parse_error\" TEXT)`);\n await conn.exec(`CREATE TABLE IF NOT EXISTS content (\"path\" TEXT PRIMARY KEY, title TEXT, summary TEXT, text TEXT)`);\n await conn.exec(`CREATE TABLE IF NOT EXISTS preset_files (\"path\" TEXT, preset TEXT, PRIMARY KEY (\"path\", preset))`);\n await conn.exec('CREATE INDEX IF NOT EXISTS preset_files_preset ON preset_files(preset)');\n for (const feature of activeFeatures(cfg)) {\n // Native FLOAT[STORE_DIMS] instead of the embed feature's engine-neutral BLOB+scale DDL\n // (see duckdb/vectors.ts). `scale` is kept, unused, so the feature's shared reconcile-time\n // INSERT/DELETE (src/features/embed.ts) still names a column that exists on both stores.\n if (feature.name === 'embed') {\n await conn.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector FLOAT[${STORE_DIMS}], PRIMARY KEY (\"path\", chunk))`);\n continue;\n }\n await feature.schema(conn);\n }\n if ((await getMeta(conn, 'schema_version')) === null) await setMeta(conn, 'schema_version', SCHEMA_VERSION);\n if ((await getMeta(conn, 'features')) === null) await setMeta(conn, 'features', featureSignature(cfg, FEATURES));\n}\n\nasync function connect(cfg: ResolvedConfig): Promise<{ instance: DuckDBInstance; duckdb: DuckDBConnection; conn: Connection; cfg: ResolvedConfig; dbPath: string; parsed: number; warnings: string[] }> {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n // Dynamic, not a top-level import: sqlite trees must never even attempt to resolve this\n // optional peer dependency, so nothing in this store's module graph imports it as a value\n // until a duckdb tree is actually opened (types-only imports elsewhere are erased and cost\n // nothing either way). Installed on first use if missing, and shared with sql-functions.ts\n // and vectors.ts (see native.ts's duckdbApi).\n let DuckDBInstance: typeof import('@duckdb/node-api').DuckDBInstance;\n try {\n ({ DuckDBInstance } = await duckdbApi());\n } catch (err) {\n if (err instanceof SenseError) throw err;\n throw new SenseError('STORE_DEPENDENCY_MISSING', `store \"duckdb\" needs the ${DUCKDB_PACKAGE} package (${(err as Error).message})`);\n }\n\n let duckdb: DuckDBConnection;\n let instance: DuckDBInstance | undefined;\n try {\n // On-disk files default to an older storage format for cross-version compatibility, which\n // rejects VARIANT columns (\"VARIANT columns are not supported in storage versions prior to\n // v1.5.0\"); this store's dynamic frontmatter columns need VARIANT (see reconcile.ts), so\n // the format floor is pinned explicitly. :memory: databases are unaffected either way.\n instance = await DuckDBInstance.create(dbPath, { storage_compatibility_version: 'v1.5.0' });\n duckdb = await instance.connect();\n } catch (err) {\n // create() may have succeeded before connect() failed: close it, or its WAL stays open.\n instance?.closeSync();\n throw new SenseError('STORE_DEPENDENCY_MISSING', `store \"duckdb\" failed to open ${dbPath}: ${(err as Error).message}`);\n }\n // A throw below (schema, reconcile's COLUMN_LIMIT) would leak the open instance, whose WAL\n // then locks the .duckdb file undeletable on Windows. closed marks the rebuild branch.\n let closed = false;\n try {\n await registerFunctions(duckdb);\n const conn = createConnection(duckdb);\n\n await conn.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n\n const version = await getMeta(conn, 'schema_version');\n const features = await getMeta(conn, 'features');\n const wantFeatures = featureSignature(cfg, FEATURES);\n if ((version !== null && version !== SCHEMA_VERSION) || (features !== null && features !== wantFeatures)) {\n // Reconcile only reparses changed files, so a stale cache can't be patched incrementally --\n // rebuild instead, same as sqlite's open() (see its comment for why this is announced).\n const reason = version !== null && version !== SCHEMA_VERSION ? 'cache format changed (new sensemaking version)' : 'config change (features, tokenizer, or presets)';\n console.error(`sense: ${reason}; rebuilding the index`);\n // Close before clearCache deletes the files underneath the still-open instance.\n closed = true;\n duckdb.disconnectSync();\n instance.closeSync();\n clearCache(cfg);\n return connect(cfg);\n }\n\n await ensureSchema(conn, cfg);\n\n const { parsed, warnings } = await reconcile(conn, cfg, cfg.baseDir);\n return { instance, duckdb, conn, cfg, dbPath, parsed, warnings };\n } catch (err) {\n if (!closed) {\n duckdb.disconnectSync();\n instance?.closeSync();\n }\n throw err;\n }\n}\n\nexport async function openDuckdb(cfg: ResolvedConfig): Promise<OpenResult> {\n const { instance, duckdb, conn, cfg: resolvedCfg, dbPath, parsed, warnings } = await connect(cfg);\n return { store: createStore(instance, duckdb, conn, resolvedCfg, resolvedCfg.baseDir), cfg: resolvedCfg, dbPath, parsed, warnings };\n}\n"],"names":["mkdirSync","join","featureSignature","STATE_DIR","STORE_DIMS","SenseError","activeFeatures","FEATURES","getMeta","setMeta","clearCache","createConnection","DUCKDB_PACKAGE","duckdbApi","reconcile","registerFunctions","createStore","DB_FILENAME","SCHEMA_VERSION","ensureSchema","conn","cfg","exec","feature","name","schema","connect","stateDir","baseDir","recursive","dbPath","DuckDBInstance","err","message","duckdb","instance","create","storage_compatibility_version","closeSync","closed","version","features","wantFeatures","reason","console","error","disconnectSync","parsed","warnings","openDuckdb","resolvedCfg","store"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { Config } from '../../config/index.js';
2
+ import type { Connection } from '../types.js';
3
+ export declare function reconcile(conn: Connection, cfg: Config, baseDir: string): Promise<{
4
+ parsed: number;
5
+ warnings: string[];
6
+ }>;
@@ -0,0 +1,141 @@
1
+ import { SenseError } from '../../errors.js';
2
+ import { activeFeatures } from '../../features/index.js';
3
+ import { progress } from '../../output/progress.js';
4
+ import { listFiles, RESERVED_COLUMNS } from '../../scan/index.js';
5
+ import { reparseFiles } from '../../scan/reparse.js';
6
+ import { getColumns, quoteIdent } from '../shared.js';
7
+ import { withTransaction } from '../transaction.js';
8
+ // Mirrors src/store/sqlite/reconcile.ts's frontmatter-upsert shape (dynamic ALTER TABLE per
9
+ // discovered key, ON CONFLICT upsert keeping the row's identity stable). `content` is a plain
10
+ // table, not FTS-virtual (D1: DuckDB's fts index is built lazily, on first lexical query, from
11
+ // this table -- see duckdb/lexical.ts), so its rows are maintained here unconditionally. The one
12
+ // forced divergence for frontmatter itself is the ALTER TABLE type: DuckDB requires a declared
13
+ // column type where sqlite accepts none, so every dynamic frontmatter column is VARIANT (the
14
+ // only DuckDB type that can hold the different JS types mapValue() produces across files for
15
+ // the same key).
16
+ const CORE_FRONTMATTER_COLUMNS = new Set([
17
+ 'path',
18
+ '_mtime',
19
+ '_ctime',
20
+ '_size',
21
+ '_parse_error'
22
+ ]);
23
+ // No rowid coupling needed (unlike sqlite's content, which links to frontmatter's rowid):
24
+ // `path` is content's own primary key, so this is a plain per-doc row.
25
+ const INSERT_CONTENT_SQL = `INSERT INTO content ("path", title, summary, text) VALUES (?, ?, ?, ?)`;
26
+ function contentRow(doc) {
27
+ return [
28
+ doc.relPath,
29
+ doc.search.title,
30
+ doc.search.summary,
31
+ doc.search.text
32
+ ];
33
+ }
34
+ // No compile-time column cap in DuckDB (unlike SQLite's SQLITE_MAX_COLUMN); kept as a sanity
35
+ // fence anyway so a runaway frontmatter generator fails with a clear message instead of an
36
+ // unbounded ALTER TABLE loop.
37
+ const MAX_FRONTMATTER_COLUMNS = 10000;
38
+ export async function reconcile(conn, cfg, baseDir) {
39
+ const files = listFiles(cfg, baseDir);
40
+ const currentSet = new Set(files.map((f)=>f.relPath));
41
+ const existingStmt = await conn.prepare('SELECT "path", "_mtime", "_size" FROM frontmatter');
42
+ const existingRows = await existingStmt.all();
43
+ const existing = new Map(existingRows.map((r)=>[
44
+ r.path,
45
+ r
46
+ ]));
47
+ const vanished = existingRows.filter((r)=>!currentSet.has(r.path)).map((r)=>r.path);
48
+ const toReparse = files.filter((f)=>{
49
+ const row = existing.get(f.relPath);
50
+ return !row || row._mtime !== f.mtimeMs || row._size !== f.size;
51
+ });
52
+ if (vanished.length === 0 && toReparse.length === 0) return {
53
+ parsed: 0,
54
+ warnings: []
55
+ };
56
+ const features = activeFeatures(cfg);
57
+ const seenColumns = await getColumns(conn);
58
+ const report = progress('reparsing files', toReparse.length);
59
+ const { docs: parsedDocs, warnings, newColumns } = reparseFiles(toReparse, features, cfg, seenColumns, report.tick);
60
+ report.finish();
61
+ for (const col of newColumns)seenColumns.add(col);
62
+ const allColumns = [
63
+ ...seenColumns
64
+ ];
65
+ if (allColumns.length > MAX_FRONTMATTER_COLUMNS) {
66
+ throw new SenseError('COLUMN_LIMIT', `frontmatter would need ${allColumns.length} columns, crossing this store's sanity limit (${MAX_FRONTMATTER_COLUMNS}). Narrow the presets' include globs so fewer/other files are indexed, or fix whatever is generating unbounded frontmatter keys.`);
67
+ }
68
+ const writableColumns = allColumns.filter((c)=>CORE_FRONTMATTER_COLUMNS.has(c) || !RESERVED_COLUMNS.has(c));
69
+ const insertSql = `INSERT INTO frontmatter (${writableColumns.map(quoteIdent).join(', ')}) VALUES (${writableColumns.map(()=>'?').join(', ')}) ON CONFLICT("path") DO UPDATE SET ${writableColumns.filter((c)=>c !== 'path').map((c)=>`${quoteIdent(c)} = excluded.${quoteIdent(c)}`).join(', ')}`;
70
+ const added = toReparse.filter((f)=>!existing.has(f.relPath)).map((f)=>f.relPath);
71
+ const delta = {
72
+ files,
73
+ reparsed: parsedDocs.map((d)=>d.relPath),
74
+ added,
75
+ vanished
76
+ };
77
+ const addedSet = new Set(added);
78
+ const reparsedExisting = parsedDocs.map((d)=>d.relPath).filter((p)=>!addedSet.has(p));
79
+ await withTransaction(conn, async ()=>{
80
+ var _feature_remove, _feature_afterReconcile;
81
+ // DuckDB's ALTER TABLE ADD COLUMN requires a type; VARIANT is the only one that can hold
82
+ // the mixed bigint/number/string/null shapes mapValue() produces for one key across files.
83
+ for (const col of newColumns)await conn.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(col)} VARIANT`);
84
+ if (parsedDocs.length > 0) {
85
+ const rows = parsedDocs.map((doc)=>writableColumns.map((col)=>{
86
+ var _doc_data_col;
87
+ if (col === 'path') return doc.relPath;
88
+ if (col === '_mtime') return doc.mtimeMs;
89
+ if (col === '_ctime') return doc.ctimeMs;
90
+ if (col === '_size') return doc.size;
91
+ if (col === '_parse_error') return doc.parseError;
92
+ return (_doc_data_col = doc.data[col]) !== null && _doc_data_col !== void 0 ? _doc_data_col : null;
93
+ }));
94
+ await conn.runBatch(insertSql, rows);
95
+ }
96
+ const contentTouched = [
97
+ ...vanished,
98
+ ...reparsedExisting
99
+ ];
100
+ if (contentTouched.length > 0) await conn.runBatch('DELETE FROM content WHERE "path" = ?', contentTouched.map((p)=>[
101
+ p
102
+ ]));
103
+ if (parsedDocs.length > 0) await conn.runBatch(INSERT_CONTENT_SQL, parsedDocs.map(contentRow));
104
+ if (vanished.length > 0) await conn.runBatch('DELETE FROM frontmatter WHERE "path" = ?', vanished.map((p)=>[
105
+ p
106
+ ]));
107
+ // A preset edit forces a full rebuild, so an unchanged doc's coverage is already correct;
108
+ // new docs have nothing to clear, which keeps cold builds linear.
109
+ const presetTouched = [
110
+ ...vanished,
111
+ ...reparsedExisting
112
+ ];
113
+ if (presetTouched.length > 0) await conn.runBatch('DELETE FROM preset_files WHERE "path" = ?', presetTouched.map((p)=>[
114
+ p
115
+ ]));
116
+ const presetRows = [];
117
+ for (const doc of parsedDocs)for (const presetName of doc.presets)presetRows.push([
118
+ doc.relPath,
119
+ presetName
120
+ ]);
121
+ if (presetRows.length > 0) await conn.runBatch('INSERT INTO preset_files ("path", preset) VALUES (?, ?)', presetRows);
122
+ const removedPaths = [
123
+ ...vanished,
124
+ ...reparsedExisting
125
+ ];
126
+ if (removedPaths.length > 0) for (const feature of features)await ((_feature_remove = feature.remove) === null || _feature_remove === void 0 ? void 0 : _feature_remove.call(feature, conn, removedPaths, delta));
127
+ for (const feature of features){
128
+ var _feature_store;
129
+ const docsForFeature = parsedDocs.map((doc)=>({
130
+ path: doc.relPath,
131
+ extracted: doc.extracted[feature.name]
132
+ }));
133
+ await ((_feature_store = feature.store) === null || _feature_store === void 0 ? void 0 : _feature_store.call(feature, conn, docsForFeature, delta));
134
+ }
135
+ for (const feature of features)await ((_feature_afterReconcile = feature.afterReconcile) === null || _feature_afterReconcile === void 0 ? void 0 : _feature_afterReconcile.call(feature, conn, delta));
136
+ });
137
+ return {
138
+ parsed: parsedDocs.length,
139
+ warnings
140
+ };
141
+ }