@statewalker/cellb 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +323 -2
  3. package/dist/adapters/files-backup.d.ts +16 -0
  4. package/dist/adapters/files-backup.js +32 -0
  5. package/dist/adapters/files-backup.js.map +1 -0
  6. package/dist/adapters/files-changelog.d.ts +33 -0
  7. package/dist/adapters/files-changelog.js +96 -0
  8. package/dist/adapters/files-changelog.js.map +1 -0
  9. package/dist/adapters/files-ltx.d.ts +35 -0
  10. package/dist/adapters/files-ltx.js +9 -0
  11. package/dist/adapters/files-ltx.js.map +1 -0
  12. package/dist/adapters/ltx-vfs.d.ts +21 -0
  13. package/dist/adapters/ltx-vfs.js +286 -0
  14. package/dist/adapters/ltx-vfs.js.map +1 -0
  15. package/dist/adapters/node-sqlite.d.ts +45 -0
  16. package/dist/adapters/node-sqlite.js +254 -0
  17. package/dist/adapters/node-sqlite.js.map +1 -0
  18. package/dist/adapters/wasm-page-hook.d.ts +26 -0
  19. package/dist/adapters/wasm-page-hook.js +55 -0
  20. package/dist/adapters/wasm-page-hook.js.map +1 -0
  21. package/dist/adapters/wasm-session.d.ts +11 -0
  22. package/dist/adapters/wasm-session.js +76 -0
  23. package/dist/adapters/wasm-session.js.map +1 -0
  24. package/dist/adapters/wasm-sqlite.d.ts +89 -0
  25. package/dist/adapters/wasm-sqlite.js +258 -0
  26. package/dist/adapters/wasm-sqlite.js.map +1 -0
  27. package/dist/bindings/assets.d.ts +26 -0
  28. package/dist/bindings/assets.js +260 -0
  29. package/dist/bindings/assets.js.map +1 -0
  30. package/dist/bindings/cache.d.ts +16 -0
  31. package/dist/bindings/cache.js +34 -0
  32. package/dist/bindings/cache.js.map +1 -0
  33. package/dist/bindings/celld-kv.d.ts +26 -0
  34. package/dist/bindings/celld-kv.js +81 -0
  35. package/dist/bindings/celld-kv.js.map +1 -0
  36. package/dist/bindings/celld-queues.d.ts +56 -0
  37. package/dist/bindings/celld-queues.js +223 -0
  38. package/dist/bindings/celld-queues.js.map +1 -0
  39. package/dist/bindings/celld-workflows.d.ts +22 -0
  40. package/dist/bindings/celld-workflows.js +54 -0
  41. package/dist/bindings/celld-workflows.js.map +1 -0
  42. package/dist/bindings/cron.d.ts +27 -0
  43. package/dist/bindings/cron.js +232 -0
  44. package/dist/bindings/cron.js.map +1 -0
  45. package/dist/bindings/d1-migrations.d.ts +22 -0
  46. package/dist/bindings/d1-migrations.js +32 -0
  47. package/dist/bindings/d1-migrations.js.map +1 -0
  48. package/dist/bindings/html-rewriter.d.ts +8 -0
  49. package/dist/bindings/html-rewriter.js +75 -0
  50. package/dist/bindings/html-rewriter.js.map +1 -0
  51. package/dist/bindings/r2-celld.d.ts +3 -0
  52. package/dist/bindings/r2-celld.js +113 -0
  53. package/dist/bindings/r2-celld.js.map +1 -0
  54. package/dist/bindings/r2.d.ts +236 -0
  55. package/dist/bindings/r2.js +419 -0
  56. package/dist/bindings/r2.js.map +1 -0
  57. package/dist/bindings/worker-loader.d.ts +83 -0
  58. package/dist/bindings/worker-loader.js +183 -0
  59. package/dist/bindings/worker-loader.js.map +1 -0
  60. package/dist/bindings/workers-ai.d.ts +39 -0
  61. package/dist/bindings/workers-ai.js +175 -0
  62. package/dist/bindings/workers-ai.js.map +1 -0
  63. package/dist/browser.d.ts +7 -0
  64. package/dist/browser.js +8 -0
  65. package/dist/browser.js.map +1 -0
  66. package/dist/bucket/celld-cells.d.ts +86 -0
  67. package/dist/bucket/celld-cells.js +273 -0
  68. package/dist/bucket/celld-cells.js.map +1 -0
  69. package/dist/bucket/celld-format.d.ts +10 -0
  70. package/dist/bucket/celld-format.js +27 -0
  71. package/dist/bucket/celld-format.js.map +1 -0
  72. package/dist/bucket/idb-object-store.d.ts +9 -0
  73. package/dist/bucket/idb-object-store.js +73 -0
  74. package/dist/bucket/idb-object-store.js.map +1 -0
  75. package/dist/bucket/ltx-log.d.ts +34 -0
  76. package/dist/bucket/ltx-log.js +289 -0
  77. package/dist/bucket/ltx-log.js.map +1 -0
  78. package/dist/bucket/object-store.d.ts +51 -0
  79. package/dist/bucket/object-store.js +114 -0
  80. package/dist/bucket/object-store.js.map +1 -0
  81. package/dist/bucket/s3-object-store.d.ts +25 -0
  82. package/dist/bucket/s3-object-store.js +151 -0
  83. package/dist/bucket/s3-object-store.js.map +1 -0
  84. package/dist/bucket/sqlite-object-store.d.ts +10 -0
  85. package/dist/bucket/sqlite-object-store.js +107 -0
  86. package/dist/bucket/sqlite-object-store.js.map +1 -0
  87. package/dist/core/alarms.d.ts +27 -0
  88. package/dist/core/alarms.js +88 -0
  89. package/dist/core/alarms.js.map +1 -0
  90. package/dist/core/async-context.d.ts +34 -0
  91. package/dist/core/async-context.js +50 -0
  92. package/dist/core/async-context.js.map +1 -0
  93. package/dist/core/celld-schema.d.ts +14 -0
  94. package/dist/core/celld-schema.js +34 -0
  95. package/dist/core/celld-schema.js.map +1 -0
  96. package/dist/core/durable-object.d.ts +28 -0
  97. package/dist/core/durable-object.js +31 -0
  98. package/dist/core/durable-object.js.map +1 -0
  99. package/dist/core/gates.d.ts +33 -0
  100. package/dist/core/gates.js +89 -0
  101. package/dist/core/gates.js.map +1 -0
  102. package/dist/core/handles.d.ts +90 -0
  103. package/dist/core/handles.js +224 -0
  104. package/dist/core/handles.js.map +1 -0
  105. package/dist/core/id.d.ts +19 -0
  106. package/dist/core/id.js +64 -0
  107. package/dist/core/id.js.map +1 -0
  108. package/dist/core/namespace.d.ts +38 -0
  109. package/dist/core/namespace.js +141 -0
  110. package/dist/core/namespace.js.map +1 -0
  111. package/dist/core/object-host.d.ts +91 -0
  112. package/dist/core/object-host.js +616 -0
  113. package/dist/core/object-host.js.map +1 -0
  114. package/dist/core/request-body.d.ts +9 -0
  115. package/dist/core/request-body.js +55 -0
  116. package/dist/core/request-body.js.map +1 -0
  117. package/dist/core/runtime.d.ts +121 -0
  118. package/dist/core/runtime.js +366 -0
  119. package/dist/core/runtime.js.map +1 -0
  120. package/dist/core/shard.d.ts +7 -0
  121. package/dist/core/shard.js +10 -0
  122. package/dist/core/shard.js.map +1 -0
  123. package/dist/core/sql-split.d.ts +15 -0
  124. package/dist/core/sql-split.js +101 -0
  125. package/dist/core/sql-split.js.map +1 -0
  126. package/dist/core/sql-storage.d.ts +53 -0
  127. package/dist/core/sql-storage.js +407 -0
  128. package/dist/core/sql-storage.js.map +1 -0
  129. package/dist/core/state.d.ts +44 -0
  130. package/dist/core/state.js +93 -0
  131. package/dist/core/state.js.map +1 -0
  132. package/dist/core/storage.d.ts +103 -0
  133. package/dist/core/storage.js +351 -0
  134. package/dist/core/storage.js.map +1 -0
  135. package/dist/core/worker-rejections.d.ts +21 -0
  136. package/dist/core/worker-rejections.js +55 -0
  137. package/dist/core/worker-rejections.js.map +1 -0
  138. package/dist/index.d.ts +22 -0
  139. package/dist/index.js +23 -0
  140. package/dist/index.js.map +1 -0
  141. package/dist/isolate/browser.d.ts +16 -0
  142. package/dist/isolate/browser.js +37 -0
  143. package/dist/isolate/browser.js.map +1 -0
  144. package/dist/isolate/host.d.ts +24 -0
  145. package/dist/isolate/host.js +74 -0
  146. package/dist/isolate/host.js.map +1 -0
  147. package/dist/isolate/isolate-worker.d.ts +1 -0
  148. package/dist/isolate/isolate-worker.js +53 -0
  149. package/dist/isolate/isolate-worker.js.map +1 -0
  150. package/dist/isolate/isolate.d.ts +10 -0
  151. package/dist/isolate/isolate.js +169 -0
  152. package/dist/isolate/isolate.js.map +1 -0
  153. package/dist/isolate/node-entry.d.ts +1 -0
  154. package/dist/isolate/node-entry.js +12 -0
  155. package/dist/isolate/node-entry.js.map +1 -0
  156. package/dist/isolate/protocol.d.ts +66 -0
  157. package/dist/isolate/protocol.js +205 -0
  158. package/dist/isolate/protocol.js.map +1 -0
  159. package/dist/ltx/crc64.d.ts +6 -0
  160. package/dist/ltx/crc64.js +46 -0
  161. package/dist/ltx/crc64.js.map +1 -0
  162. package/dist/ltx/ltx-page-server.d.ts +65 -0
  163. package/dist/ltx/ltx-page-server.js +309 -0
  164. package/dist/ltx/ltx-page-server.js.map +1 -0
  165. package/dist/ltx/ltx-pages.d.ts +81 -0
  166. package/dist/ltx/ltx-pages.js +315 -0
  167. package/dist/ltx/ltx-pages.js.map +1 -0
  168. package/dist/ltx/ltx-store-worker.d.ts +1 -0
  169. package/dist/ltx/ltx-store-worker.js +38 -0
  170. package/dist/ltx/ltx-store-worker.js.map +1 -0
  171. package/dist/ltx/ltx.d.ts +45 -0
  172. package/dist/ltx/ltx.js +203 -0
  173. package/dist/ltx/ltx.js.map +1 -0
  174. package/dist/ltx/lz4.d.ts +9 -0
  175. package/dist/ltx/lz4.js +156 -0
  176. package/dist/ltx/lz4.js.map +1 -0
  177. package/dist/ltx/opfs-sync-files.d.ts +23 -0
  178. package/dist/ltx/opfs-sync-files.js +191 -0
  179. package/dist/ltx/opfs-sync-files.js.map +1 -0
  180. package/dist/ltx/xxh32.d.ts +1 -0
  181. package/dist/ltx/xxh32.js +43 -0
  182. package/dist/ltx/xxh32.js.map +1 -0
  183. package/dist/miniflare-compat/blob-service.d.ts +13 -0
  184. package/dist/miniflare-compat/blob-service.js +66 -0
  185. package/dist/miniflare-compat/blob-service.js.map +1 -0
  186. package/dist/miniflare-compat/kv-binding.d.ts +44 -0
  187. package/dist/miniflare-compat/kv-binding.js +99 -0
  188. package/dist/miniflare-compat/kv-binding.js.map +1 -0
  189. package/dist/miniflare-compat/shims/assert.d.ts +15 -0
  190. package/dist/miniflare-compat/shims/assert.js +24 -0
  191. package/dist/miniflare-compat/shims/assert.js.map +1 -0
  192. package/dist/miniflare-compat/shims/workerd-cloudflare.d.ts +7 -0
  193. package/dist/miniflare-compat/shims/workerd-cloudflare.js +6 -0
  194. package/dist/miniflare-compat/shims/workerd-cloudflare.js.map +1 -0
  195. package/dist/miniflare-compat/shims/workerd-globals.d.ts +1 -0
  196. package/dist/miniflare-compat/shims/workerd-globals.js +30 -0
  197. package/dist/miniflare-compat/shims/workerd-globals.js.map +1 -0
  198. package/dist/miniflare-compat/shims/workerd-internal.d.ts +20 -0
  199. package/dist/miniflare-compat/shims/workerd-internal.js +23 -0
  200. package/dist/miniflare-compat/shims/workerd-internal.js.map +1 -0
  201. package/dist/miniflare-compat/shims/workerd-performance.d.ts +4 -0
  202. package/dist/miniflare-compat/shims/workerd-performance.js +17 -0
  203. package/dist/miniflare-compat/shims/workerd-performance.js.map +1 -0
  204. package/dist/miniflare-compat/simulator-fetcher.d.ts +9 -0
  205. package/dist/miniflare-compat/simulator-fetcher.js +14 -0
  206. package/dist/miniflare-compat/simulator-fetcher.js.map +1 -0
  207. package/dist/miniflare-compat/vendored.d.ts +15 -0
  208. package/dist/miniflare-compat/vendored.js +27 -0
  209. package/dist/miniflare-compat/vendored.js.map +1 -0
  210. package/dist/node-compat/async_hooks.d.ts +22 -0
  211. package/dist/node-compat/async_hooks.js +176 -0
  212. package/dist/node-compat/async_hooks.js.map +1 -0
  213. package/dist/node-compat/browser-registry.d.ts +3 -0
  214. package/dist/node-compat/browser-registry.js +39 -0
  215. package/dist/node-compat/browser-registry.js.map +1 -0
  216. package/dist/node-compat/crypto.d.ts +60 -0
  217. package/dist/node-compat/crypto.js +143 -0
  218. package/dist/node-compat/crypto.js.map +1 -0
  219. package/dist/node-compat/diagnostics_channel.d.ts +27 -0
  220. package/dist/node-compat/diagnostics_channel.js +46 -0
  221. package/dist/node-compat/diagnostics_channel.js.map +1 -0
  222. package/dist/node-compat/fs.d.ts +35 -0
  223. package/dist/node-compat/fs.js +40 -0
  224. package/dist/node-compat/fs.js.map +1 -0
  225. package/dist/node-compat/globals.d.ts +3 -0
  226. package/dist/node-compat/globals.js +4 -0
  227. package/dist/node-compat/globals.js.map +1 -0
  228. package/dist/node-compat/install.d.ts +5 -0
  229. package/dist/node-compat/install.js +12 -0
  230. package/dist/node-compat/install.js.map +1 -0
  231. package/dist/node-compat/module.d.ts +12 -0
  232. package/dist/node-compat/module.js +36 -0
  233. package/dist/node-compat/module.js.map +1 -0
  234. package/dist/node-compat/modules.d.ts +8 -0
  235. package/dist/node-compat/modules.js +23 -0
  236. package/dist/node-compat/modules.js.map +1 -0
  237. package/dist/node-compat/os.d.ts +36 -0
  238. package/dist/node-compat/os.js +36 -0
  239. package/dist/node-compat/os.js.map +1 -0
  240. package/dist/node-compat/process.d.ts +14 -0
  241. package/dist/node-compat/process.js +16 -0
  242. package/dist/node-compat/process.js.map +1 -0
  243. package/dist/node-compat/shims.d.ts +4 -0
  244. package/dist/node.d.ts +5 -0
  245. package/dist/node.js +6 -0
  246. package/dist/node.js.map +1 -0
  247. package/dist/peer/celld-peer.d.ts +53 -0
  248. package/dist/peer/celld-peer.js +555 -0
  249. package/dist/peer/celld-peer.js.map +1 -0
  250. package/dist/ports.d.ts +183 -0
  251. package/dist/ports.js +10 -0
  252. package/dist/ports.js.map +1 -0
  253. package/dist/project/browser-bundle.d.ts +17 -0
  254. package/dist/project/browser-bundle.js +125 -0
  255. package/dist/project/browser-bundle.js.map +1 -0
  256. package/dist/project/browser-cookies.d.ts +12 -0
  257. package/dist/project/browser-cookies.js +90 -0
  258. package/dist/project/browser-cookies.js.map +1 -0
  259. package/dist/project/browser-host.d.ts +173 -0
  260. package/dist/project/browser-host.js +200 -0
  261. package/dist/project/browser-host.js.map +1 -0
  262. package/dist/project/browser-modules.d.ts +17 -0
  263. package/dist/project/browser-modules.js +89 -0
  264. package/dist/project/browser-modules.js.map +1 -0
  265. package/dist/project/browser-shards.d.ts +62 -0
  266. package/dist/project/browser-shards.js +210 -0
  267. package/dist/project/browser-shards.js.map +1 -0
  268. package/dist/project/browser-tabs.d.ts +103 -0
  269. package/dist/project/browser-tabs.js +418 -0
  270. package/dist/project/browser-tabs.js.map +1 -0
  271. package/dist/project/cloudflare-email.d.ts +8 -0
  272. package/dist/project/cloudflare-email.js +11 -0
  273. package/dist/project/cloudflare-email.js.map +1 -0
  274. package/dist/project/cloudflare-workers.d.ts +12 -0
  275. package/dist/project/cloudflare-workers.js +16 -0
  276. package/dist/project/cloudflare-workers.js.map +1 -0
  277. package/dist/project/host-modules.d.ts +4 -0
  278. package/dist/project/host-modules.js +40 -0
  279. package/dist/project/host-modules.js.map +1 -0
  280. package/dist/project/jsonc.d.ts +2 -0
  281. package/dist/project/jsonc.js +27 -0
  282. package/dist/project/jsonc.js.map +1 -0
  283. package/dist/project/lower-await.d.ts +2 -0
  284. package/dist/project/lower-await.js +151 -0
  285. package/dist/project/lower-await.js.map +1 -0
  286. package/dist/project/node-project.d.ts +38 -0
  287. package/dist/project/node-project.js +362 -0
  288. package/dist/project/node-project.js.map +1 -0
  289. package/dist/project/own-module.d.ts +6 -0
  290. package/dist/project/own-module.js +13 -0
  291. package/dist/project/own-module.js.map +1 -0
  292. package/dist/project/scheduler.d.ts +2 -0
  293. package/dist/project/scheduler.js +3 -0
  294. package/dist/project/scheduler.js.map +1 -0
  295. package/dist/project/site.d.ts +74 -0
  296. package/dist/project/site.js +206 -0
  297. package/dist/project/site.js.map +1 -0
  298. package/dist/project/virtual-modules.d.ts +3 -0
  299. package/dist/project/worker-scope.d.ts +27 -0
  300. package/dist/project/worker-scope.js +46 -0
  301. package/dist/project/worker-scope.js.map +1 -0
  302. package/dist/project/workerd-message-channel.d.ts +13 -0
  303. package/dist/project/workerd-message-channel.js +137 -0
  304. package/dist/project/workerd-message-channel.js.map +1 -0
  305. package/dist/project/workers-global-names.d.ts +6 -0
  306. package/dist/project/workers-global-names.js +15 -0
  307. package/dist/project/workers-global-names.js.map +1 -0
  308. package/dist/project/workers-globals-module.d.ts +8 -0
  309. package/dist/project/workers-globals-module.js +9 -0
  310. package/dist/project/workers-globals-module.js.map +1 -0
  311. package/dist/project/workers-globals.d.ts +9 -0
  312. package/dist/project/workers-globals.js +19 -0
  313. package/dist/project/workers-globals.js.map +1 -0
  314. package/dist/project/wrangler.d.ts +116 -0
  315. package/dist/project/wrangler.js +342 -0
  316. package/dist/project/wrangler.js.map +1 -0
  317. package/dist/router/cell-router.d.ts +72 -0
  318. package/dist/router/cell-router.js +247 -0
  319. package/dist/router/cell-router.js.map +1 -0
  320. package/dist/router/shard-worker.d.ts +44 -0
  321. package/dist/router/shard-worker.js +163 -0
  322. package/dist/router/shard-worker.js.map +1 -0
  323. package/dist/transport/cells-http.d.ts +25 -0
  324. package/dist/transport/cells-http.js +141 -0
  325. package/dist/transport/cells-http.js.map +1 -0
  326. package/dist/transport/cells-link.d.ts +27 -0
  327. package/dist/transport/cells-link.js +71 -0
  328. package/dist/transport/cells-link.js.map +1 -0
  329. package/dist/transport/port.d.ts +26 -0
  330. package/dist/transport/port.js +100 -0
  331. package/dist/transport/port.js.map +1 -0
  332. package/dist/v8/codec.d.ts +6 -0
  333. package/dist/v8/codec.js +9 -0
  334. package/dist/v8/codec.js.map +1 -0
  335. package/dist/v8/serialize.d.ts +10 -0
  336. package/dist/v8/serialize.js +601 -0
  337. package/dist/v8/serialize.js.map +1 -0
  338. package/dist/ws/client.d.ts +25 -0
  339. package/dist/ws/client.js +203 -0
  340. package/dist/ws/client.js.map +1 -0
  341. package/dist/ws/control.d.ts +48 -0
  342. package/dist/ws/control.js +44 -0
  343. package/dist/ws/control.js.map +1 -0
  344. package/dist/ws/frames.d.ts +38 -0
  345. package/dist/ws/frames.js +39 -0
  346. package/dist/ws/frames.js.map +1 -0
  347. package/dist/ws/server.d.ts +99 -0
  348. package/dist/ws/server.js +295 -0
  349. package/dist/ws/server.js.map +1 -0
  350. package/dist/ws/sw-connector.d.ts +10 -0
  351. package/dist/ws/sw-connector.js +25 -0
  352. package/dist/ws/sw-connector.js.map +1 -0
  353. package/dist/ws/upgrade.d.ts +28 -0
  354. package/dist/ws/upgrade.js +75 -0
  355. package/dist/ws/upgrade.js.map +1 -0
  356. package/package.json +111 -3
  357. package/vendor/celld/LICENSE +202 -0
  358. package/vendor/celld/kv.js +1131 -0
  359. package/vendor/celld/queues.js +1656 -0
  360. package/vendor/celld/workflows.js +1610 -0
  361. package/vendor/html-rewriter-wasm/html_rewriter.js +925 -0
  362. package/vendor/html-rewriter-wasm/html_rewriter_bg.wasm +0 -0
  363. package/vendor/miniflare/d1-api.js +738 -0
  364. package/vendor/miniflare/d1-database.js +6978 -0
  365. package/vendor/miniflare/kv-namespace.js +3061 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-sqlite.js","sourceRoot":"","sources":["../../src/adapters/node-sqlite.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n closeSync,\n fstatSync,\n mkdtempSync,\n openSync,\n readFileSync,\n readSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { DatabaseSync as DatabaseSyncT } from \"node:sqlite\";\n\n// Loaded via require: bundlers (vite/vitest) strip the \"node:\" prefix of the\n// still-experimental node:sqlite builtin and fail to resolve \"sqlite\".\nconst { DatabaseSync } = createRequire(import.meta.url)(\n \"node:sqlite\",\n) as typeof import(\"node:sqlite\");\n\nimport { splitSqlStatements } from \"../core/sql-split.js\";\nimport type { OpenCellSql, SqlResult, SyncSqlDriver } from \"../ports.js\";\n\nconst requireCjs = createRequire(import.meta.url);\nconst KNOWN_EXTENSIONS: Record<string, () => string> = {\n sqlite_vec: () => (requireCjs(\"sqlite-vec\") as { getLoadablePath(): string }).getLoadablePath(),\n};\n\n/** Server-side SyncSqlDriver over node:sqlite (Node >= 22.13). */\nexport class NodeSqliteDriver implements SyncSqlDriver {\n #db: DatabaseSyncT;\n #path: string;\n readonly #exclusive: boolean;\n readonly #pageCapture: boolean;\n constructor(path = \":memory:\", opts: { exclusive?: boolean; pageCapture?: boolean } = {}) {\n this.#path = path;\n this.#exclusive = !!opts.exclusive;\n this.#pageCapture = !!opts.pageCapture && path !== \":memory:\";\n // As celld and workerd: a double-quoted name that is no column is a string literal.\n this.#db = new DatabaseSync(path, {\n allowExtension: true,\n enableDoubleQuotedStringLiterals: true,\n });\n this.#db.enableLoadExtension(false); // only loadExtension() below may load, by name\n // EXCLUSIVE locking: the first write takes an OS lock on the file that is\n // kept until close (or process death) — a second process can neither read\n // nor write the cell. Storage-level fencing on one host, no lease needed.\n if (opts.exclusive) this.#db.exec(\"PRAGMA locking_mode = EXCLUSIVE; PRAGMA busy_timeout = 0;\");\n this.#walSetup();\n }\n\n // ---- page capture through the WAL (what the WASM driver does with its xWrite hook) ----\n // In WAL mode every commit appends its changed pages to `<db>-wal` as frames (24-byte frame\n // header + page; a commit frame carries the database size in pages). Reading the frames\n // appended since the last read gives exactly the committed transaction's pages, O(changed),\n // instead of reading the whole file. Automatic checkpoints are off; take() checkpoints\n // (TRUNCATE) itself once the WAL grows, and restarts from its start.\n #walOffset = 0;\n #walSetup(): void {\n if (!this.#pageCapture) return;\n this.#db.exec(\"PRAGMA journal_mode = WAL; PRAGMA wal_autocheckpoint = 0;\");\n this.#db.exec(\"PRAGMA wal_checkpoint(TRUNCATE)\");\n this.#walOffset = 0;\n }\n /** The pages of the transactions committed since the previous call (WAL frames). */\n takeDirtyPages():\n | { pageSize: number; pageCount: number; pages: Map<number, Uint8Array> }\n | undefined {\n if (!this.#pageCapture) return undefined;\n const walPath = `${this.#path}-wal`;\n let fd: number;\n try {\n fd = openSync(walPath, \"r\");\n } catch {\n return undefined;\n }\n try {\n const size = fstatSync(fd).size;\n if (size < 32) return undefined;\n const header = Buffer.alloc(32);\n readSync(fd, header, 0, 32, 0);\n const pageSize = header.readUInt32BE(8) === 1 ? 65536 : header.readUInt32BE(8);\n const [salt1, salt2] = [header.readUInt32BE(16), header.readUInt32BE(20)];\n const frameSize = 24 + pageSize;\n let offset = Math.max(32, this.#walOffset);\n const pending = new Map<number, Uint8Array>();\n const pages = new Map<number, Uint8Array>();\n let pageCount = 0;\n let committedTo = offset;\n const frame = Buffer.alloc(frameSize);\n while (offset + frameSize <= size) {\n readSync(fd, frame, 0, frameSize, offset);\n if (frame.readUInt32BE(8) !== salt1 || frame.readUInt32BE(12) !== salt2) break; // stale frames\n pending.set(frame.readUInt32BE(0), new Uint8Array(frame.subarray(24)));\n offset += frameSize;\n const commit = frame.readUInt32BE(4);\n if (commit) {\n for (const [p, d] of pending) pages.set(p, d);\n pending.clear();\n pageCount = commit;\n committedTo = offset;\n }\n }\n this.#walOffset = committedTo;\n if (!pageCount)\n return {\n pageSize,\n pageCount: Number(this.exec(\"PRAGMA page_count\", []).rows[0][0]),\n pages,\n };\n for (const p of [...pages.keys()]) if (p > pageCount) pages.delete(p);\n // keep the WAL bounded: fold it into the database and start over\n if (committedTo > 4 * 1024 * 1024) {\n this.#db.exec(\"PRAGMA wal_checkpoint(TRUNCATE)\");\n this.#walOffset = 0;\n }\n return { pageSize, pageCount, pages };\n } finally {\n closeSync(fd);\n }\n }\n exec(sql: string, params: unknown[]): SqlResult {\n const statements = params.length === 0 ? splitSqlStatements(sql) : [sql];\n // Several statements: the result of the last one, with the rows written by all of them.\n let last: SqlResult = { columnNames: [], rows: [], rowsWritten: 0 };\n let written = 0;\n for (const text of statements) {\n last = this.#one(text, params);\n written += last.rowsWritten;\n }\n return { ...last, rowsWritten: written };\n }\n #one(sql: string, params: unknown[]): SqlResult {\n const stmt = this.#db.prepare(sql);\n const columnNames = stmt.columns().map((c) => c.name);\n const args = params.map(toSqlite) as any[];\n if (columnNames.length === 0) {\n const r = stmt.run(...args);\n return { columnNames, rows: [], rowsWritten: Number(r.changes) };\n }\n stmt.setReturnArrays(true);\n const rows = (stmt.all(...args) as unknown as unknown[][]).map((row) => row.map(fromSqlite));\n return { columnNames, rows, rowsWritten: 0 };\n }\n inTransaction(): boolean {\n return (this.#db as unknown as { isTransaction: boolean }).isTransaction;\n }\n databaseSize(): number {\n const [pc] = this.exec(\"PRAGMA page_count\", []).rows[0] as [number];\n const [ps] = this.exec(\"PRAGMA page_size\", []).rows[0] as [number];\n return Number(pc) * Number(ps);\n }\n close(): void {\n this.#db.close();\n }\n\n // ---- change capture (session extension) for transaction logs -------------------------\n #session: { changeset(): Uint8Array; close(): void } | undefined;\n /** node:sqlite exposes no ROWID session option: PK-less tables are logged as statements. */\n readonly captureCoversRowidTables = false;\n captureStart(): void {\n this.#session?.close();\n this.#session = (\n this.#db as unknown as { createSession(): { changeset(): Uint8Array; close(): void } }\n ).createSession();\n }\n captureTake(): Uint8Array {\n const s = this.#session;\n this.#session = undefined;\n if (!s) return new Uint8Array();\n try {\n return s.changeset();\n } finally {\n s.close();\n }\n }\n captureDiscard(): void {\n this.#session?.close();\n this.#session = undefined;\n }\n applyChangeset(changeset: Uint8Array): void {\n if (!changeset.byteLength) return;\n const SQLITE_CHANGESET_REPLACE = 1;\n (this.#db as unknown as { applyChangeset(c: Uint8Array, o: unknown): boolean }).applyChangeset(\n changeset,\n {\n onConflict: () => SQLITE_CHANGESET_REPLACE,\n },\n );\n }\n\n /** Known extensions only: \"sqlite_vec\" → the sqlite-vec npm package's loadable library. */\n loadExtension(name: string): void {\n const path = KNOWN_EXTENSIONS[name]?.();\n if (!path) throw new Error(`Unknown SQLite extension \"${name}\"`);\n this.#db.enableLoadExtension(true);\n try {\n this.#db.loadExtension(path);\n } finally {\n this.#db.enableLoadExtension(false);\n }\n }\n\n /**\n * The database file as SQLite wrote it (page for page), for page-level logs; an in-memory\n * database falls back to image(). Valid between transactions (rollback journal mode).\n */\n pageImage(): Uint8Array {\n if (this.#path === \":memory:\") return this.image();\n // A database restored from celld's files is in WAL mode (litestream needs it): its last\n // commits are in the -wal file until a checkpoint moves them into the main file.\n const mode = String(this.#db.prepare(\"PRAGMA journal_mode\").get()?.journal_mode ?? \"\");\n if (mode.toLowerCase() === \"wal\") {\n this.#db.exec(\"PRAGMA wal_checkpoint(TRUNCATE)\");\n this.#walOffset = 0; // the WAL starts over\n }\n return new Uint8Array(readFileSync(this.#path));\n }\n\n /** `VACUUM INTO` a temp file: a consistent, compact image (no open transaction allowed). */\n image(): Uint8Array {\n const tmp = join(mkdtempSync(join(tmpdir(), \"cell-snap-\")), \"db.sqlite\");\n try {\n this.#db.exec(`VACUUM INTO '${tmp.replace(/'/g, \"''\")}'`);\n return new Uint8Array(readFileSync(tmp));\n } finally {\n rmSync(dirname(tmp), { recursive: true, force: true });\n }\n }\n\n /** Replaces the database file with `image` and reopens it (an in-memory DB becomes file-backed). */\n restore(image: Uint8Array): void {\n this.#db.close();\n if (this.#path === \":memory:\")\n this.#path = join(mkdtempSync(join(tmpdir(), \"cell-mem-\")), \"db.sqlite\");\n for (const suffix of [\"-wal\", \"-shm\", \"-journal\"]) rmSync(this.#path + suffix, { force: true });\n writeFileSync(this.#path, image);\n this.#db = new DatabaseSync(this.#path, { enableDoubleQuotedStringLiterals: true });\n if (this.#exclusive) this.#db.exec(\"PRAGMA locking_mode = EXCLUSIVE; PRAGMA busy_timeout = 0;\");\n this.#walSetup(); // a new file: a new WAL\n }\n}\n\nfunction toSqlite(v: unknown): unknown {\n if (v instanceof ArrayBuffer) return new Uint8Array(v);\n if (typeof v === \"boolean\") return v ? 1 : 0;\n return v === undefined ? null : v;\n}\n/** Workers return BLOBs as ArrayBuffer. */\nfunction fromSqlite(v: unknown): unknown {\n if (v instanceof Uint8Array) return v.buffer.slice(v.byteOffset, v.byteOffset + v.byteLength);\n return v;\n}\n\n/** One file per cell in `dir`, or in-memory when `dir` is omitted. */\n/**\n * `pageCapture` (file-backed only): WAL mode, and each commit's changed pages read from the WAL\n * (takeDirtyPages) — LTX logs then cost O(changed pages) on Node as with SQLite WASM.\n */\nexport function nodeSqliteOpener(\n dir?: string,\n opts: { exclusive?: boolean; pageCapture?: boolean } = {},\n): OpenCellSql {\n return (cellKey) =>\n new NodeSqliteDriver(\n dir ? `${dir}/${cellKey.replace(/[^\\w.-]/g, \"_\")}.sqlite` : \":memory:\",\n opts,\n );\n}\n"]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The page hook for SQLite WASM: wraps the io methods of a connection's MAIN database file
3
+ * (whatever VFS opened it — opfs-sahpool in browsers, memdb, unix) so every page SQLite
4
+ * writes is recorded. After a commit the recorded pages ARE the transaction's changed pages:
5
+ * an LTX file costs O(changed pages) instead of a whole-database image.
6
+ *
7
+ * How: SQLITE_FCNTL_FILE_POINTER gives the sqlite3_file; a copy of its sqlite3_io_methods
8
+ * gets a JS xWrite (wasm.installFunction, signature "i(ppij)") that records (offset → bytes)
9
+ * and calls the original xWrite (wasm.functionEntry); the file's pMethods then points to the
10
+ * copy. No new VFS is registered. A connection whose main database has no file (":memory:")
11
+ * cannot be hooked: callers fall back to images.
12
+ */
13
+ export interface DirtyPages {
14
+ pageSize: number;
15
+ pageCount: number;
16
+ pages: Map<number, Uint8Array>;
17
+ }
18
+ export declare function installPageHook(sqlite3: {
19
+ capi: any;
20
+ wasm: any;
21
+ }, dbPointer: number): {
22
+ /** The pages written since the last call (whole pages: SQLite writes the main file page by page). */
23
+ take(pageSize: number, pageCount: number): DirtyPages;
24
+ discard(): void;
25
+ uninstall(): void;
26
+ } | undefined;
@@ -0,0 +1,55 @@
1
+ export function installPageHook(sqlite3, dbPointer) {
2
+ const { capi, wasm } = sqlite3;
3
+ const stack = wasm.pstack.pointer;
4
+ let pFile = 0;
5
+ try {
6
+ const pp = wasm.pstack.allocPtr();
7
+ if (capi.sqlite3_file_control(dbPointer, "main", capi.SQLITE_FCNTL_FILE_POINTER, pp) !== 0)
8
+ return undefined;
9
+ pFile = wasm.peekPtr(pp);
10
+ }
11
+ finally {
12
+ wasm.pstack.restore(stack);
13
+ }
14
+ if (!pFile)
15
+ return undefined;
16
+ const file = new capi.sqlite3_file(pFile);
17
+ if (!file.$pMethods)
18
+ return undefined; // no real file behind the database
19
+ const orig = new capi.sqlite3_io_methods(file.$pMethods);
20
+ const mine = new capi.sqlite3_io_methods();
21
+ for (const m of Object.keys(capi.sqlite3_io_methods.structInfo.members))
22
+ mine[`$${m}`] = orig[`$${m}`];
23
+ const xWrite = wasm.functionEntry(orig.$xWrite);
24
+ const written = new Map(); // byte offset → last bytes written there
25
+ const fnPtr = wasm.installFunction("i(ppij)", (pF, pBuf, iAmt, iOfst) => {
26
+ if (pF === pFile)
27
+ written.set(Number(iOfst), wasm.heap8u().slice(pBuf, pBuf + iAmt));
28
+ return xWrite(pF, pBuf, iAmt, iOfst);
29
+ });
30
+ mine.$xWrite = fnPtr;
31
+ file.$pMethods = mine.pointer;
32
+ return {
33
+ /** The pages written since the last call (whole pages: SQLite writes the main file page by page). */
34
+ take(pageSize, pageCount) {
35
+ const pages = new Map();
36
+ for (const [offset, bytes] of written)
37
+ for (let o = 0; o + pageSize <= bytes.length; o += pageSize) {
38
+ const pgno = (offset + o) / pageSize + 1;
39
+ if (Number.isInteger(pgno) && pgno <= pageCount)
40
+ pages.set(pgno, bytes.subarray(o, o + pageSize));
41
+ }
42
+ written.clear();
43
+ return { pageSize, pageCount, pages };
44
+ },
45
+ discard() {
46
+ written.clear();
47
+ },
48
+ uninstall() {
49
+ file.$pMethods = orig.pointer;
50
+ wasm.uninstallFunction(fnPtr);
51
+ mine.dispose();
52
+ },
53
+ };
54
+ }
55
+ //# sourceMappingURL=wasm-page-hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasm-page-hook.js","sourceRoot":"","sources":["../../src/adapters/wasm-page-hook.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The page hook for SQLite WASM: wraps the io methods of a connection's MAIN database file\n * (whatever VFS opened it — opfs-sahpool in browsers, memdb, unix) so every page SQLite\n * writes is recorded. After a commit the recorded pages ARE the transaction's changed pages:\n * an LTX file costs O(changed pages) instead of a whole-database image.\n *\n * How: SQLITE_FCNTL_FILE_POINTER gives the sqlite3_file; a copy of its sqlite3_io_methods\n * gets a JS xWrite (wasm.installFunction, signature \"i(ppij)\") that records (offset → bytes)\n * and calls the original xWrite (wasm.functionEntry); the file's pMethods then points to the\n * copy. No new VFS is registered. A connection whose main database has no file (\":memory:\")\n * cannot be hooked: callers fall back to images.\n */\nexport interface DirtyPages {\n pageSize: number;\n pageCount: number;\n pages: Map<number, Uint8Array>;\n}\n\nexport function installPageHook(sqlite3: { capi: any; wasm: any }, dbPointer: number) {\n const { capi, wasm } = sqlite3;\n const stack = wasm.pstack.pointer;\n let pFile = 0;\n try {\n const pp = wasm.pstack.allocPtr();\n if (capi.sqlite3_file_control(dbPointer, \"main\", capi.SQLITE_FCNTL_FILE_POINTER, pp) !== 0)\n return undefined;\n pFile = wasm.peekPtr(pp);\n } finally {\n wasm.pstack.restore(stack);\n }\n if (!pFile) return undefined;\n const file = new capi.sqlite3_file(pFile);\n if (!file.$pMethods) return undefined; // no real file behind the database\n const orig = new capi.sqlite3_io_methods(file.$pMethods);\n const mine = new capi.sqlite3_io_methods();\n for (const m of Object.keys(capi.sqlite3_io_methods.structInfo.members))\n mine[`$${m}`] = orig[`$${m}`];\n const xWrite = wasm.functionEntry(orig.$xWrite);\n const written = new Map<number, Uint8Array>(); // byte offset → last bytes written there\n const fnPtr = wasm.installFunction(\n \"i(ppij)\",\n (pF: number, pBuf: number, iAmt: number, iOfst: bigint) => {\n if (pF === pFile) written.set(Number(iOfst), wasm.heap8u().slice(pBuf, pBuf + iAmt));\n return xWrite(pF, pBuf, iAmt, iOfst);\n },\n );\n mine.$xWrite = fnPtr;\n file.$pMethods = mine.pointer;\n return {\n /** The pages written since the last call (whole pages: SQLite writes the main file page by page). */\n take(pageSize: number, pageCount: number): DirtyPages {\n const pages = new Map<number, Uint8Array>();\n for (const [offset, bytes] of written)\n for (let o = 0; o + pageSize <= bytes.length; o += pageSize) {\n const pgno = (offset + o) / pageSize + 1;\n if (Number.isInteger(pgno) && pgno <= pageCount)\n pages.set(pgno, bytes.subarray(o, o + pageSize));\n }\n written.clear();\n return { pageSize, pageCount, pages };\n },\n discard(): void {\n written.clear();\n },\n uninstall(): void {\n file.$pMethods = orig.pointer;\n wasm.uninstallFunction(fnPtr);\n mine.dispose();\n },\n };\n}\n"]}
@@ -0,0 +1,11 @@
1
+ export interface WasmSqlite3 {
2
+ capi: any;
3
+ wasm: any;
4
+ }
5
+ export declare function wasmSessionCapture(sqlite3: WasmSqlite3, getDbPointer: () => number): {
6
+ captureCoversRowidTables: true;
7
+ captureStart(): void;
8
+ captureTake(): Uint8Array;
9
+ captureDiscard(): void;
10
+ applyChangeset(changeset: Uint8Array): void;
11
+ };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * SQLite session extension over the official WASM build (C API through `sqlite3.capi`):
3
+ * the change capture behind transaction logs. With SQLITE_SESSION_OBJCONFIG_ROWID, tables
4
+ * without a PRIMARY KEY are captured too (node:sqlite cannot do this).
5
+ */
6
+ const SESSION_OBJCONFIG_ROWID = 2; // not exported as a constant by the JS bindings
7
+ const CHANGESET_REPLACE = 1;
8
+ export function wasmSessionCapture(sqlite3, getDbPointer) {
9
+ const { capi, wasm } = sqlite3;
10
+ let session = 0;
11
+ const free = () => {
12
+ if (session)
13
+ capi.sqlite3session_delete(session);
14
+ session = 0;
15
+ };
16
+ return {
17
+ captureCoversRowidTables: true,
18
+ captureStart() {
19
+ free();
20
+ const stack = wasm.pstack.pointer;
21
+ try {
22
+ const pp = wasm.pstack.allocPtr();
23
+ const rc = capi.sqlite3session_create(getDbPointer(), "main", pp);
24
+ if (rc)
25
+ throw new Error(`sqlite3session_create failed: ${rc}`);
26
+ session = wasm.peekPtr(pp);
27
+ const pInt = wasm.pstack.alloc(4);
28
+ wasm.poke32(pInt, 1);
29
+ capi.sqlite3session_object_config(session, SESSION_OBJCONFIG_ROWID, pInt);
30
+ capi.sqlite3session_attach(session, null);
31
+ }
32
+ finally {
33
+ wasm.pstack.restore(stack);
34
+ }
35
+ },
36
+ captureTake() {
37
+ if (!session)
38
+ return new Uint8Array();
39
+ const stack = wasm.pstack.pointer;
40
+ try {
41
+ const pN = wasm.pstack.alloc(4);
42
+ const pBuf = wasm.pstack.allocPtr();
43
+ const rc = capi.sqlite3session_changeset(session, pN, pBuf);
44
+ if (rc)
45
+ throw new Error(`sqlite3session_changeset failed: ${rc}`);
46
+ const n = wasm.peek32(pN);
47
+ const buf = wasm.peekPtr(pBuf);
48
+ const bytes = n ? wasm.heap8u().slice(buf, buf + n) : new Uint8Array();
49
+ if (buf)
50
+ capi.sqlite3_free(buf);
51
+ return bytes;
52
+ }
53
+ finally {
54
+ wasm.pstack.restore(stack);
55
+ free();
56
+ }
57
+ },
58
+ captureDiscard() {
59
+ free();
60
+ },
61
+ applyChangeset(changeset) {
62
+ if (!changeset.byteLength)
63
+ return;
64
+ const p = wasm.allocFromTypedArray(changeset);
65
+ try {
66
+ const rc = capi.sqlite3changeset_apply(getDbPointer(), changeset.byteLength, p, 0, () => CHANGESET_REPLACE, 0);
67
+ if (rc)
68
+ throw new Error(`sqlite3changeset_apply failed: ${rc}`);
69
+ }
70
+ finally {
71
+ wasm.dealloc(p);
72
+ }
73
+ },
74
+ };
75
+ }
76
+ //# sourceMappingURL=wasm-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasm-session.js","sourceRoot":"","sources":["../../src/adapters/wasm-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQLite session extension over the official WASM build (C API through `sqlite3.capi`):\n * the change capture behind transaction logs. With SQLITE_SESSION_OBJCONFIG_ROWID, tables\n * without a PRIMARY KEY are captured too (node:sqlite cannot do this).\n */\nconst SESSION_OBJCONFIG_ROWID = 2; // not exported as a constant by the JS bindings\nconst CHANGESET_REPLACE = 1;\n\nexport interface WasmSqlite3 {\n capi: any;\n wasm: any;\n}\n\nexport function wasmSessionCapture(sqlite3: WasmSqlite3, getDbPointer: () => number) {\n const { capi, wasm } = sqlite3;\n let session = 0;\n const free = () => {\n if (session) capi.sqlite3session_delete(session);\n session = 0;\n };\n return {\n captureCoversRowidTables: true as const,\n captureStart(): void {\n free();\n const stack = wasm.pstack.pointer;\n try {\n const pp = wasm.pstack.allocPtr();\n const rc = capi.sqlite3session_create(getDbPointer(), \"main\", pp);\n if (rc) throw new Error(`sqlite3session_create failed: ${rc}`);\n session = wasm.peekPtr(pp);\n const pInt = wasm.pstack.alloc(4);\n wasm.poke32(pInt, 1);\n capi.sqlite3session_object_config(session, SESSION_OBJCONFIG_ROWID, pInt);\n capi.sqlite3session_attach(session, null);\n } finally {\n wasm.pstack.restore(stack);\n }\n },\n captureTake(): Uint8Array {\n if (!session) return new Uint8Array();\n const stack = wasm.pstack.pointer;\n try {\n const pN = wasm.pstack.alloc(4);\n const pBuf = wasm.pstack.allocPtr();\n const rc = capi.sqlite3session_changeset(session, pN, pBuf);\n if (rc) throw new Error(`sqlite3session_changeset failed: ${rc}`);\n const n = wasm.peek32(pN);\n const buf = wasm.peekPtr(pBuf);\n const bytes = n ? wasm.heap8u().slice(buf, buf + n) : new Uint8Array();\n if (buf) capi.sqlite3_free(buf);\n return bytes;\n } finally {\n wasm.pstack.restore(stack);\n free();\n }\n },\n captureDiscard(): void {\n free();\n },\n applyChangeset(changeset: Uint8Array): void {\n if (!changeset.byteLength) return;\n const p = wasm.allocFromTypedArray(changeset);\n try {\n const rc = capi.sqlite3changeset_apply(\n getDbPointer(),\n changeset.byteLength,\n p,\n 0,\n () => CHANGESET_REPLACE,\n 0,\n );\n if (rc) throw new Error(`sqlite3changeset_apply failed: ${rc}`);\n } finally {\n wasm.dealloc(p);\n }\n },\n };\n}\n"]}
@@ -0,0 +1,89 @@
1
+ import type { OpenCellSql, SqlResult, SyncSqlDriver } from "../ports.js";
2
+ import { type DirtyPages } from "./wasm-page-hook.js";
3
+ import { type WasmSqlite3 } from "./wasm-session.js";
4
+ /** Minimal slice of the @sqlite.org/sqlite-wasm `oo1.DB` API we rely on. */
5
+ export interface Oo1Db {
6
+ prepare(sql: string): Oo1Stmt;
7
+ exec(sql: string): unknown;
8
+ changes(): number;
9
+ close(): void;
10
+ }
11
+ interface Oo1Stmt {
12
+ bind(values: unknown[]): Oo1Stmt;
13
+ getColumnNames(): string[];
14
+ columnCount: number;
15
+ step(): boolean;
16
+ get(target: unknown[]): unknown[];
17
+ finalize(): void;
18
+ }
19
+ /**
20
+ * SyncSqlDriver over the official SQLite WASM build (oo1 API). Synchronous,
21
+ * so it satisfies `ctx.storage.sql`. Works in Node (in-memory, for tests) and
22
+ * in browsers; persistence comes from the VFS behind the DB (see sahpoolOpener).
23
+ */
24
+ /** Backup hooks: how to export an image of the DB and how to replace it (returns the reopened DB). */
25
+ export interface WasmImageHooks {
26
+ image(db: Oo1Db): Uint8Array;
27
+ restore(db: Oo1Db, image: Uint8Array): Oo1Db;
28
+ }
29
+ export declare class WasmSqliteDriver implements SyncSqlDriver {
30
+ #private;
31
+ captureStart?: () => void;
32
+ captureTake?: () => Uint8Array;
33
+ captureDiscard?: () => void;
34
+ applyChangeset?: (changeset: Uint8Array) => void;
35
+ captureCoversRowidTables?: boolean;
36
+ constructor(db: Oo1Db, hooks?: WasmImageHooks, sqlite3?: WasmSqlite3,
37
+ /** SQLite's C API, for `inTransaction()` alone (when `sqlite3` is not given). */
38
+ capi?: {
39
+ sqlite3_get_autocommit(db: number): number;
40
+ });
41
+ image(): Uint8Array;
42
+ /** Pages written since the previous call — a committed transaction's changed pages. */
43
+ takeDirtyPages(): DirtyPages | undefined;
44
+ discardDirtyPages(): void;
45
+ /**
46
+ * WASM builds cannot load extensions at run time; an extension must be compiled into the
47
+ * build (e.g. the `sqlite-vec-wasm-demo` package for "sqlite_vec"). This only checks it is.
48
+ */
49
+ loadExtension(name: string): void;
50
+ restore(image: Uint8Array): void;
51
+ exec(sql: string, params: unknown[]): SqlResult;
52
+ inTransaction(): boolean;
53
+ databaseSize(): number;
54
+ close(): void;
55
+ }
56
+ /** In-memory WASM databases (tests, ephemeral cells). `sqlite3` = await sqlite3InitModule(). */
57
+ /** Image hooks for an in-memory DB: `sqlite3_js_db_export` / `sqlite3_deserialize`. */
58
+ export declare function memoryImageHooks(sqlite3: any): WasmImageHooks;
59
+ export declare function wasmMemoryOpener(sqlite3: {
60
+ oo1: {
61
+ DB: new (path: string, mode?: string) => Oo1Db;
62
+ };
63
+ }): OpenCellSql;
64
+ /**
65
+ * Browser persistence: one OPFS "SyncAccessHandle pool" database per cell.
66
+ * Must run in a Dedicated Worker; needs neither COOP/COEP nor SharedArrayBuffer.
67
+ * Not runnable in Node (installOpfsSAHPoolVfs is browser-only) — covered by P04's Playwright spec.
68
+ */
69
+ export declare function sahpoolOpener(sqlite3: {
70
+ installOpfsSAHPoolVfs(opts: {
71
+ name?: string;
72
+ initialCapacity?: number;
73
+ }): Promise<{
74
+ OpfsSAHPoolDb: new (path: string) => Oo1Db;
75
+ reserveMinimumCapacity(n: number): Promise<number>;
76
+ getCapacity(): number;
77
+ getFileCount(): number;
78
+ pauseVfs?(): unknown;
79
+ isPaused?(): boolean;
80
+ unpauseVfs?(): Promise<unknown>;
81
+ }>;
82
+ }, opts?: {
83
+ name?: string;
84
+ initialCapacity?: number;
85
+ }): Promise<OpenCellSql & {
86
+ reserve(n: number): Promise<number>;
87
+ release(): void;
88
+ }>;
89
+ export {};
@@ -0,0 +1,258 @@
1
+ import { splitSqlStatements } from "../core/sql-split.js";
2
+ import { installPageHook } from "./wasm-page-hook.js";
3
+ import { wasmSessionCapture } from "./wasm-session.js";
4
+ export class WasmSqliteDriver {
5
+ captureStart;
6
+ captureTake;
7
+ captureDiscard;
8
+ applyChangeset;
9
+ captureCoversRowidTables;
10
+ #db;
11
+ #hooks;
12
+ #sqlite3;
13
+ #capi;
14
+ #pageHook;
15
+ constructor(db, hooks, sqlite3,
16
+ /** SQLite's C API, for `inTransaction()` alone (when `sqlite3` is not given). */
17
+ capi) {
18
+ this.#db = db;
19
+ this.#hooks = hooks;
20
+ this.#sqlite3 = sqlite3;
21
+ this.#capi = capi ?? sqlite3?.capi;
22
+ if (!this.#capi?.sqlite3_get_autocommit)
23
+ this.inTransaction = undefined;
24
+ this.#enableDqs();
25
+ this.#hookPages();
26
+ if (sqlite3?.capi?.sqlite3session_create) {
27
+ // Transaction-log capture (session extension), following the current DB handle.
28
+ Object.assign(this, wasmSessionCapture(sqlite3, () => this.#db.pointer));
29
+ }
30
+ if (!hooks) {
31
+ // No image support for this driver: hide the optional port methods.
32
+ this.image = undefined;
33
+ this.restore = undefined;
34
+ }
35
+ }
36
+ image() {
37
+ return this.#hooks.image(this.#db);
38
+ }
39
+ /**
40
+ * As celld and workerd: a double-quoted name that is no column is a string literal (SQLite's
41
+ * DQS misfeature, off in this build).
42
+ */
43
+ #enableDqs() {
44
+ const capi = this.#sqlite3?.capi;
45
+ if (!capi?.sqlite3_db_config)
46
+ return;
47
+ const db = this.#db.pointer;
48
+ capi.sqlite3_db_config(db, capi.SQLITE_DBCONFIG_DQS_DML, 1, 0);
49
+ capi.sqlite3_db_config(db, capi.SQLITE_DBCONFIG_DQS_DDL, 1, 0);
50
+ }
51
+ /** Installs the page hook on the current main database file (see wasm-page-hook.ts). */
52
+ #hookPages() {
53
+ this.#pageHook = undefined;
54
+ if (!this.#sqlite3?.capi?.sqlite3_file_control)
55
+ return;
56
+ try {
57
+ this.#pageHook = installPageHook(this.#sqlite3, this.#db.pointer);
58
+ }
59
+ catch {
60
+ this.#pageHook = undefined;
61
+ }
62
+ }
63
+ /** Pages written since the previous call — a committed transaction's changed pages. */
64
+ takeDirtyPages() {
65
+ if (!this.#pageHook)
66
+ return undefined;
67
+ const pageSize = Number(this.exec("PRAGMA page_size", []).rows[0][0]);
68
+ const pageCount = Number(this.exec("PRAGMA page_count", []).rows[0][0]);
69
+ return this.#pageHook.take(pageSize, pageCount);
70
+ }
71
+ discardDirtyPages() {
72
+ this.#pageHook?.discard();
73
+ }
74
+ /**
75
+ * WASM builds cannot load extensions at run time; an extension must be compiled into the
76
+ * build (e.g. the `sqlite-vec-wasm-demo` package for "sqlite_vec"). This only checks it is.
77
+ */
78
+ loadExtension(name) {
79
+ const probe = { sqlite_vec: "SELECT vec_version()" };
80
+ if (!probe[name])
81
+ throw new Error(`Unknown SQLite extension "${name}"`);
82
+ try {
83
+ this.exec(probe[name], []);
84
+ }
85
+ catch {
86
+ throw new Error(`SQLite extension "${name}" is not compiled into this SQLite WASM build`);
87
+ }
88
+ }
89
+ restore(image) {
90
+ // celld's databases are in WAL mode (header bytes 18–19 = 2); the WASM VFSes (memdb,
91
+ // opfs-sahpool) have no shared memory for WAL and refuse to open such a file. The pages
92
+ // are the same in rollback-journal mode: switch the header to 1 (celld sets WAL again
93
+ // when it opens the database).
94
+ if (image.length > 19 && image[18] === 2 && image[19] === 2) {
95
+ image = image.slice();
96
+ image[18] = 1;
97
+ image[19] = 1;
98
+ }
99
+ this.#db = this.#hooks.restore(this.#db, image);
100
+ this.#enableDqs();
101
+ this.#hookPages(); // a new database file behind the connection
102
+ }
103
+ exec(sql, params) {
104
+ const statements = params.length === 0 ? splitSqlStatements(sql) : [sql];
105
+ // Several statements: the result of the last one, with the rows written by all of them.
106
+ let last = { columnNames: [], rows: [], rowsWritten: 0 };
107
+ let written = 0;
108
+ for (const text of statements) {
109
+ last = this.#one(text, params);
110
+ written += last.rowsWritten;
111
+ }
112
+ return { ...last, rowsWritten: written };
113
+ }
114
+ #one(sql, params) {
115
+ const st = this.#db.prepare(sql);
116
+ try {
117
+ if (params.length)
118
+ st.bind(params.map(toSqlite));
119
+ const columnNames = st.columnCount ? st.getColumnNames() : [];
120
+ const rows = [];
121
+ while (st.step())
122
+ if (columnNames.length)
123
+ rows.push(st.get([]).map(fromSqlite));
124
+ return { columnNames, rows, rowsWritten: columnNames.length ? 0 : this.#db.changes() };
125
+ }
126
+ finally {
127
+ st.finalize();
128
+ }
129
+ }
130
+ inTransaction() {
131
+ const pointer = this.#db.pointer;
132
+ return this.#capi.sqlite3_get_autocommit(pointer) === 0;
133
+ }
134
+ databaseSize() {
135
+ const pc = this.exec("PRAGMA page_count", []).rows[0][0];
136
+ const ps = this.exec("PRAGMA page_size", []).rows[0][0];
137
+ return Number(pc) * Number(ps);
138
+ }
139
+ close() {
140
+ this.#db.close();
141
+ }
142
+ }
143
+ function toSqlite(v) {
144
+ if (v instanceof ArrayBuffer)
145
+ return new Uint8Array(v);
146
+ if (typeof v === "boolean")
147
+ return v ? 1 : 0;
148
+ return v === undefined ? null : v;
149
+ }
150
+ function fromSqlite(v) {
151
+ if (v instanceof Uint8Array)
152
+ return v.buffer.slice(v.byteOffset, v.byteOffset + v.byteLength);
153
+ return v;
154
+ }
155
+ /** In-memory WASM databases (tests, ephemeral cells). `sqlite3` = await sqlite3InitModule(). */
156
+ /** Image hooks for an in-memory DB: `sqlite3_js_db_export` / `sqlite3_deserialize`. */
157
+ export function memoryImageHooks(sqlite3) {
158
+ const { capi, wasm } = sqlite3;
159
+ return {
160
+ image: (db) => sqlite3.capi.sqlite3_js_db_export(db),
161
+ restore(db, image) {
162
+ const p = wasm.allocFromTypedArray(image);
163
+ const rc = capi.sqlite3_deserialize(db.pointer, "main", p, image.byteLength, image.byteLength, capi.SQLITE_DESERIALIZE_FREEONCLOSE | capi.SQLITE_DESERIALIZE_RESIZEABLE);
164
+ if (rc)
165
+ throw new Error(`sqlite3_deserialize failed: ${capi.sqlite3_js_rc_str?.(rc) ?? rc}`);
166
+ return db;
167
+ },
168
+ };
169
+ }
170
+ let memdbCounter = 0;
171
+ export function wasmMemoryOpener(sqlite3) {
172
+ return () => new WasmSqliteDriver(
173
+ // memdb (not ":memory:"): an in-memory database that IS a VFS file, so the page hook works.
174
+ new sqlite3.oo1.DB(`file:/cells-mem-${++memdbCounter}?vfs=memdb`, "c"), memoryImageHooks(sqlite3), sqlite3);
175
+ }
176
+ /**
177
+ * Browser persistence: one OPFS "SyncAccessHandle pool" database per cell.
178
+ * Must run in a Dedicated Worker; needs neither COOP/COEP nor SharedArrayBuffer.
179
+ * Not runnable in Node (installOpfsSAHPoolVfs is browser-only) — covered by P04's Playwright spec.
180
+ */
181
+ export async function sahpoolOpener(sqlite3, opts = {}) {
182
+ const pool = await sqlite3.installOpfsSAHPoolVfs({
183
+ name: opts.name ?? "cellb",
184
+ initialCapacity: opts.initialCapacity ?? 16,
185
+ });
186
+ // The same pool again in this worker (SQLite WASM keeps one per name), after a `release()`.
187
+ if (pool.isPaused?.())
188
+ await pool.unpauseVfs?.();
189
+ // A full pool fails the open (`SAH pool is full`), and opening is synchronous while growing
190
+ // the pool is not. A cell about to open reserves room first (`room`, awaited by the runtime
191
+ // before the cell's first event); the pool grows when the reservations would not fit, and in
192
+ // the background whenever less than half of it is free.
193
+ const SLOTS_PER_CELL = 3; // the database, its journal, and one spare
194
+ const reserved = new Set();
195
+ let growing;
196
+ const grow = (min) => {
197
+ growing ??= pool
198
+ .reserveMinimumCapacity(Math.max(min, pool.getCapacity() * 2))
199
+ .then(() => { })
200
+ .finally(() => {
201
+ growing = undefined;
202
+ });
203
+ return growing;
204
+ };
205
+ const needed = () => pool.getFileCount() + SLOTS_PER_CELL * reserved.size;
206
+ const room = (cellKey) => {
207
+ reserved.add(cellKey);
208
+ if (needed() <= pool.getCapacity())
209
+ return undefined;
210
+ // Grow until this reservation (and every other one) fits; the open fails visibly if not.
211
+ const fit = async () => {
212
+ while (needed() > pool.getCapacity()) {
213
+ const before = pool.getCapacity();
214
+ await grow(needed());
215
+ if (pool.getCapacity() <= before)
216
+ return; // it did not grow: the open will say why
217
+ }
218
+ };
219
+ return fit();
220
+ };
221
+ const keepRoom = () => {
222
+ const capacity = pool.getCapacity();
223
+ if (!growing && capacity - needed() < capacity / 2)
224
+ grow(capacity * 2).catch(() => { });
225
+ };
226
+ // The rollback journal stays in the pool between transactions (PERSIST): DELETE gives its pool
227
+ // slot back and takes it again on every commit, which costs a header write each time. The
228
+ // database file is the same in both modes (only WAL marks it).
229
+ const openDb = (path) => {
230
+ const db = new pool.OpfsSAHPoolDb(path);
231
+ db.exec("PRAGMA journal_mode=PERSIST");
232
+ return db;
233
+ };
234
+ const open = (cellKey) => {
235
+ const path = `/${cellKey.replace(/[^\w.-]/g, "_")}.sqlite`;
236
+ reserved.delete(cellKey);
237
+ queueMicrotask(keepRoom); // after this open (and its first slot) is counted
238
+ return new WasmSqliteDriver(openDb(path), {
239
+ image: (db) => sqlite3.capi.sqlite3_js_db_export(db),
240
+ // Replace the file inside the pool, then reopen it.
241
+ restore(db, image) {
242
+ db.close();
243
+ pool.importDb(path, image);
244
+ return openDb(path);
245
+ },
246
+ }, sqlite3);
247
+ };
248
+ return Object.assign(open, {
249
+ room,
250
+ reserve: (n) => pool.reserveMinimumCapacity(n),
251
+ /**
252
+ * Gives the pool's OPFS files back once every database is closed, so that another worker
253
+ * (another tab's) can open the pool at once instead of when this worker is gone.
254
+ */
255
+ release: () => void pool.pauseVfs?.(),
256
+ });
257
+ }
258
+ //# sourceMappingURL=wasm-sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wasm-sqlite.js","sourceRoot":"","sources":["../../src/adapters/wasm-sqlite.ts"],"names":[],"mappings":"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","sourcesContent":["import { splitSqlStatements } from \"../core/sql-split.js\";\nimport type { OpenCellSql, SqlResult, SyncSqlDriver } from \"../ports.js\";\nimport { type DirtyPages, installPageHook } from \"./wasm-page-hook.js\";\nimport { type WasmSqlite3, wasmSessionCapture } from \"./wasm-session.js\";\n\n/** Minimal slice of the @sqlite.org/sqlite-wasm `oo1.DB` API we rely on. */\nexport interface Oo1Db {\n prepare(sql: string): Oo1Stmt;\n exec(sql: string): unknown;\n changes(): number;\n close(): void;\n}\ninterface Oo1Stmt {\n bind(values: unknown[]): Oo1Stmt;\n getColumnNames(): string[];\n columnCount: number;\n step(): boolean;\n get(target: unknown[]): unknown[];\n finalize(): void;\n}\n\n/**\n * SyncSqlDriver over the official SQLite WASM build (oo1 API). Synchronous,\n * so it satisfies `ctx.storage.sql`. Works in Node (in-memory, for tests) and\n * in browsers; persistence comes from the VFS behind the DB (see sahpoolOpener).\n */\n/** Backup hooks: how to export an image of the DB and how to replace it (returns the reopened DB). */\nexport interface WasmImageHooks {\n image(db: Oo1Db): Uint8Array;\n restore(db: Oo1Db, image: Uint8Array): Oo1Db;\n}\n\nexport class WasmSqliteDriver implements SyncSqlDriver {\n captureStart?: () => void;\n captureTake?: () => Uint8Array;\n captureDiscard?: () => void;\n applyChangeset?: (changeset: Uint8Array) => void;\n captureCoversRowidTables?: boolean;\n #db: Oo1Db;\n readonly #hooks?: WasmImageHooks;\n readonly #sqlite3?: WasmSqlite3;\n readonly #capi?: { sqlite3_get_autocommit(db: number): number };\n #pageHook?: ReturnType<typeof installPageHook>;\n constructor(\n db: Oo1Db,\n hooks?: WasmImageHooks,\n sqlite3?: WasmSqlite3,\n /** SQLite's C API, for `inTransaction()` alone (when `sqlite3` is not given). */\n capi?: { sqlite3_get_autocommit(db: number): number },\n ) {\n this.#db = db;\n this.#hooks = hooks;\n this.#sqlite3 = sqlite3;\n this.#capi = capi ?? sqlite3?.capi;\n if (!this.#capi?.sqlite3_get_autocommit)\n (this as { inTransaction?: unknown }).inTransaction = undefined;\n this.#enableDqs();\n this.#hookPages();\n if (sqlite3?.capi?.sqlite3session_create) {\n // Transaction-log capture (session extension), following the current DB handle.\n Object.assign(\n this,\n wasmSessionCapture(sqlite3, () => (this.#db as unknown as { pointer: number }).pointer),\n );\n }\n if (!hooks) {\n // No image support for this driver: hide the optional port methods.\n (this as { image?: unknown }).image = undefined;\n (this as { restore?: unknown }).restore = undefined;\n }\n }\n image(): Uint8Array {\n return this.#hooks!.image(this.#db);\n }\n\n /**\n * As celld and workerd: a double-quoted name that is no column is a string literal (SQLite's\n * DQS misfeature, off in this build).\n */\n #enableDqs(): void {\n const capi = this.#sqlite3?.capi;\n if (!capi?.sqlite3_db_config) return;\n const db = (this.#db as unknown as { pointer: number }).pointer;\n capi.sqlite3_db_config(db, capi.SQLITE_DBCONFIG_DQS_DML, 1, 0);\n capi.sqlite3_db_config(db, capi.SQLITE_DBCONFIG_DQS_DDL, 1, 0);\n }\n\n /** Installs the page hook on the current main database file (see wasm-page-hook.ts). */\n #hookPages(): void {\n this.#pageHook = undefined;\n if (!this.#sqlite3?.capi?.sqlite3_file_control) return;\n try {\n this.#pageHook = installPageHook(\n this.#sqlite3,\n (this.#db as unknown as { pointer: number }).pointer,\n );\n } catch {\n this.#pageHook = undefined;\n }\n }\n /** Pages written since the previous call — a committed transaction's changed pages. */\n takeDirtyPages(): DirtyPages | undefined {\n if (!this.#pageHook) return undefined;\n const pageSize = Number(this.exec(\"PRAGMA page_size\", []).rows[0][0]);\n const pageCount = Number(this.exec(\"PRAGMA page_count\", []).rows[0][0]);\n return this.#pageHook.take(pageSize, pageCount);\n }\n discardDirtyPages(): void {\n this.#pageHook?.discard();\n }\n /**\n * WASM builds cannot load extensions at run time; an extension must be compiled into the\n * build (e.g. the `sqlite-vec-wasm-demo` package for \"sqlite_vec\"). This only checks it is.\n */\n loadExtension(name: string): void {\n const probe: Record<string, string> = { sqlite_vec: \"SELECT vec_version()\" };\n if (!probe[name]) throw new Error(`Unknown SQLite extension \"${name}\"`);\n try {\n this.exec(probe[name], []);\n } catch {\n throw new Error(`SQLite extension \"${name}\" is not compiled into this SQLite WASM build`);\n }\n }\n restore(image: Uint8Array): void {\n // celld's databases are in WAL mode (header bytes 18–19 = 2); the WASM VFSes (memdb,\n // opfs-sahpool) have no shared memory for WAL and refuse to open such a file. The pages\n // are the same in rollback-journal mode: switch the header to 1 (celld sets WAL again\n // when it opens the database).\n if (image.length > 19 && image[18] === 2 && image[19] === 2) {\n image = image.slice();\n image[18] = 1;\n image[19] = 1;\n }\n this.#db = this.#hooks!.restore(this.#db, image);\n this.#enableDqs();\n this.#hookPages(); // a new database file behind the connection\n }\n exec(sql: string, params: unknown[]): SqlResult {\n const statements = params.length === 0 ? splitSqlStatements(sql) : [sql];\n // Several statements: the result of the last one, with the rows written by all of them.\n let last: SqlResult = { columnNames: [], rows: [], rowsWritten: 0 };\n let written = 0;\n for (const text of statements) {\n last = this.#one(text, params);\n written += last.rowsWritten;\n }\n return { ...last, rowsWritten: written };\n }\n #one(sql: string, params: unknown[]): SqlResult {\n const st = this.#db.prepare(sql);\n try {\n if (params.length) st.bind(params.map(toSqlite));\n const columnNames = st.columnCount ? st.getColumnNames() : [];\n const rows: unknown[][] = [];\n while (st.step()) if (columnNames.length) rows.push(st.get([]).map(fromSqlite));\n return { columnNames, rows, rowsWritten: columnNames.length ? 0 : this.#db.changes() };\n } finally {\n st.finalize();\n }\n }\n inTransaction(): boolean {\n const pointer = (this.#db as unknown as { pointer: number }).pointer;\n return this.#capi!.sqlite3_get_autocommit(pointer) === 0;\n }\n databaseSize(): number {\n const pc = this.exec(\"PRAGMA page_count\", []).rows[0][0];\n const ps = this.exec(\"PRAGMA page_size\", []).rows[0][0];\n return Number(pc) * Number(ps);\n }\n close(): void {\n this.#db.close();\n }\n}\n\nfunction toSqlite(v: unknown): unknown {\n if (v instanceof ArrayBuffer) return new Uint8Array(v);\n if (typeof v === \"boolean\") return v ? 1 : 0;\n return v === undefined ? null : v;\n}\nfunction fromSqlite(v: unknown): unknown {\n if (v instanceof Uint8Array) return v.buffer.slice(v.byteOffset, v.byteOffset + v.byteLength);\n return v;\n}\n\n/** In-memory WASM databases (tests, ephemeral cells). `sqlite3` = await sqlite3InitModule(). */\n/** Image hooks for an in-memory DB: `sqlite3_js_db_export` / `sqlite3_deserialize`. */\nexport function memoryImageHooks(sqlite3: any): WasmImageHooks {\n const { capi, wasm } = sqlite3;\n return {\n image: (db) => sqlite3.capi.sqlite3_js_db_export(db),\n restore(db, image) {\n const p = wasm.allocFromTypedArray(image);\n const rc = capi.sqlite3_deserialize(\n (db as any).pointer,\n \"main\",\n p,\n image.byteLength,\n image.byteLength,\n capi.SQLITE_DESERIALIZE_FREEONCLOSE | capi.SQLITE_DESERIALIZE_RESIZEABLE,\n );\n if (rc) throw new Error(`sqlite3_deserialize failed: ${capi.sqlite3_js_rc_str?.(rc) ?? rc}`);\n return db;\n },\n };\n}\n\nlet memdbCounter = 0;\nexport function wasmMemoryOpener(sqlite3: {\n oo1: { DB: new (path: string, mode?: string) => Oo1Db };\n}): OpenCellSql {\n return () =>\n new WasmSqliteDriver(\n // memdb (not \":memory:\"): an in-memory database that IS a VFS file, so the page hook works.\n new sqlite3.oo1.DB(`file:/cells-mem-${++memdbCounter}?vfs=memdb`, \"c\"),\n memoryImageHooks(sqlite3),\n sqlite3 as never,\n );\n}\n\n/**\n * Browser persistence: one OPFS \"SyncAccessHandle pool\" database per cell.\n * Must run in a Dedicated Worker; needs neither COOP/COEP nor SharedArrayBuffer.\n * Not runnable in Node (installOpfsSAHPoolVfs is browser-only) — covered by P04's Playwright spec.\n */\nexport async function sahpoolOpener(\n sqlite3: {\n installOpfsSAHPoolVfs(opts: { name?: string; initialCapacity?: number }): Promise<{\n OpfsSAHPoolDb: new (path: string) => Oo1Db;\n reserveMinimumCapacity(n: number): Promise<number>;\n getCapacity(): number;\n getFileCount(): number;\n // Not in older SQLite WASM builds (sqlite-vec's): there `release()` does nothing.\n pauseVfs?(): unknown;\n isPaused?(): boolean;\n unpauseVfs?(): Promise<unknown>;\n }>;\n },\n opts: { name?: string; initialCapacity?: number } = {},\n): Promise<OpenCellSql & { reserve(n: number): Promise<number>; release(): void }> {\n const pool = await sqlite3.installOpfsSAHPoolVfs({\n name: opts.name ?? \"cellb\",\n initialCapacity: opts.initialCapacity ?? 16,\n });\n // The same pool again in this worker (SQLite WASM keeps one per name), after a `release()`.\n if (pool.isPaused?.()) await pool.unpauseVfs?.();\n // A full pool fails the open (`SAH pool is full`), and opening is synchronous while growing\n // the pool is not. A cell about to open reserves room first (`room`, awaited by the runtime\n // before the cell's first event); the pool grows when the reservations would not fit, and in\n // the background whenever less than half of it is free.\n const SLOTS_PER_CELL = 3; // the database, its journal, and one spare\n const reserved = new Set<string>();\n let growing: Promise<void> | undefined;\n const grow = (min: number): Promise<void> => {\n growing ??= pool\n .reserveMinimumCapacity(Math.max(min, pool.getCapacity() * 2))\n .then(() => {})\n .finally(() => {\n growing = undefined;\n });\n return growing;\n };\n const needed = () => pool.getFileCount() + SLOTS_PER_CELL * reserved.size;\n const room = (cellKey: string): Promise<void> | undefined => {\n reserved.add(cellKey);\n if (needed() <= pool.getCapacity()) return undefined;\n // Grow until this reservation (and every other one) fits; the open fails visibly if not.\n const fit = async (): Promise<void> => {\n while (needed() > pool.getCapacity()) {\n const before = pool.getCapacity();\n await grow(needed());\n if (pool.getCapacity() <= before) return; // it did not grow: the open will say why\n }\n };\n return fit();\n };\n const keepRoom = () => {\n const capacity = pool.getCapacity();\n if (!growing && capacity - needed() < capacity / 2) grow(capacity * 2).catch(() => {});\n };\n // The rollback journal stays in the pool between transactions (PERSIST): DELETE gives its pool\n // slot back and takes it again on every commit, which costs a header write each time. The\n // database file is the same in both modes (only WAL marks it).\n const openDb = (path: string) => {\n const db = new pool.OpfsSAHPoolDb(path);\n db.exec(\"PRAGMA journal_mode=PERSIST\");\n return db;\n };\n const open: OpenCellSql = (cellKey) => {\n const path = `/${cellKey.replace(/[^\\w.-]/g, \"_\")}.sqlite`;\n reserved.delete(cellKey);\n queueMicrotask(keepRoom); // after this open (and its first slot) is counted\n return new WasmSqliteDriver(\n openDb(path),\n {\n image: (db) => (sqlite3 as any).capi.sqlite3_js_db_export(db),\n // Replace the file inside the pool, then reopen it.\n restore(db, image) {\n db.close();\n (pool as any).importDb(path, image);\n return openDb(path);\n },\n },\n sqlite3 as never,\n );\n };\n return Object.assign(open, {\n room,\n reserve: (n: number) => pool.reserveMinimumCapacity(n),\n /**\n * Gives the pool's OPFS files back once every database is closed, so that another worker\n * (another tab's) can open the pool at once instead of when this worker is gone.\n */\n release: () => void pool.pauseVfs?.(),\n });\n}\n"]}