@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,309 @@
1
+ import { scopeOf } from "../core/celld-schema.js";
2
+ import { ltxFileName } from "./ltx.js";
3
+ import { compactPagedCellTo, encodeLtxStream, LtxShortReadError, openPagedCell, } from "./ltx-pages.js";
4
+ /** Bytes of page data a cell's files hold (what a compaction may write, at most). */
5
+ const pageBytes = (c) => c.files.reduce((n, f) => n + [...f.index.values()].reduce((m, [, size]) => m + size, 0), 0);
6
+ /** Free bytes in this origin's storage, or undefined where the estimate is not available. */
7
+ async function room() {
8
+ const storage = globalThis.navigator?.storage;
9
+ if (!storage?.estimate)
10
+ return undefined;
11
+ const { usage = 0, quota } = await storage.estimate();
12
+ return quota === undefined ? undefined : quota - usage;
13
+ }
14
+ export function ltxCellStore(files, sab, opts = {}) {
15
+ const ctl = new Int32Array(sab, 0, 2);
16
+ const payload = new Uint8Array(sab, 8);
17
+ const root = (opts.root ?? "").replace(/\/$/, "");
18
+ const compactAfter = opts.compactAfter ?? 64;
19
+ // celld's cell directory: `<Class>:<idHex>` (scopeOf), as the LTX log and celld name it.
20
+ const dirOf = (cell) => `${root}/cells/${scopeOf(cell).replace(/[\\/]/g, "_")}/ltx/e1/0000`;
21
+ const cells = new Map();
22
+ /** Where the store's time goes (ms), for traces. */
23
+ const stats = {
24
+ commits: 0,
25
+ writeMs: 0,
26
+ appendMs: 0,
27
+ compactions: 0,
28
+ compactMs: 0,
29
+ reads: 0,
30
+ readMs: 0,
31
+ };
32
+ const list = async (dir, cleanUp = false) => {
33
+ const out = [];
34
+ if (!(await files.exists(dir)))
35
+ return out;
36
+ for await (const e of files.list(dir)) {
37
+ if (e.kind !== "file")
38
+ continue;
39
+ if (e.name.endsWith(".ltx"))
40
+ out.push(e.path);
41
+ // On this store's first open of the cell: a compaction or a write that did not finish
42
+ // (a closed tab, a full disk), its space back.
43
+ else if (cleanUp && /\.ltx\.(tmp|part-\w+)$/.test(e.name))
44
+ await files.remove(e.path).catch(() => false);
45
+ }
46
+ return out;
47
+ };
48
+ /**
49
+ * One writer per cell in the origin: the store holds a Web Lock per cell directory while the
50
+ * cell is open, so another tab's store (or worker) cannot write the same cell meanwhile; it
51
+ * fails instead. (Across machines, celld's epochs and ownership records do this.)
52
+ */
53
+ const owned = new Set();
54
+ const own = async (dir) => {
55
+ const locks = globalThis.navigator?.locks;
56
+ if (!locks || owned.has(dir))
57
+ return;
58
+ // Waits a while for it: on a reload, the previous page's store holds it until its worker
59
+ // goes, a moment later (failing at once broke the cell's Durable Object on every reload).
60
+ const got = await new Promise((resolve) => {
61
+ void locks
62
+ .request(`cellb-ltx:${opts.lockScope ?? ""}:${dir}`, { signal: AbortSignal.timeout(20_000) }, () => {
63
+ resolve(true);
64
+ return new Promise(() => { }); // held while this store lives
65
+ })
66
+ .catch(() => resolve(false));
67
+ });
68
+ if (!got)
69
+ throw new Error(`ltx: cell ${dir} is open in another tab or worker (waited 20 s)`);
70
+ owned.add(dir);
71
+ };
72
+ const cellOf = async (cell) => {
73
+ if (!cells.has(cell)) {
74
+ const dir = dirOf(cell);
75
+ const first = !owned.has(dir);
76
+ await own(dir);
77
+ const paths = await list(dir, first);
78
+ cells.set(cell, paths.length ? await openPagedCell(files, paths) : undefined);
79
+ }
80
+ return cells.get(cell);
81
+ };
82
+ const answer = (bytes, state = 1) => {
83
+ payload.set(bytes);
84
+ ctl[1] = bytes.length;
85
+ Atomics.store(ctl, 0, state);
86
+ Atomics.notify(ctl, 0);
87
+ };
88
+ const enc = new TextEncoder();
89
+ let queue = Promise.resolve();
90
+ /** The request being served and its stage: a watchdog names the one that does not end. */
91
+ const current = { op: "", cell: "", stage: "", since: 0 };
92
+ const stage = (s) => {
93
+ current.stage = s;
94
+ };
95
+ const watchdog = setInterval(() => {
96
+ if (current.op && performance.now() - current.since > 10_000)
97
+ console.warn(`cellb ltx-store: ${current.op} of ${current.cell} at "${current.stage}" for ${Math.round((performance.now() - current.since) / 1000)} s`);
98
+ }, 10_000);
99
+ watchdog.unref?.();
100
+ /**
101
+ * Folds a cell's files into one snapshot WITHOUT holding up its requests: the snapshot is
102
+ * written from a frozen view of the files as they are now (later commits add files after
103
+ * them), then, between two requests, the files it replaces are removed and the cell reopened
104
+ * (the snapshot plus the files written meanwhile). One compaction per cell at a time.
105
+ */
106
+ const compacting = new Map();
107
+ const compact = (cell) => {
108
+ const running = compacting.get(cell);
109
+ if (running)
110
+ return running;
111
+ const job = (async () => {
112
+ const c = await cellOf(cell);
113
+ if (!c || c.files.length < 2)
114
+ return c?.files.length ?? 0;
115
+ const dir = dirOf(cell);
116
+ const replaced = c.files.map((f) => f.path);
117
+ // The new snapshot exists beside the files it replaces until the swap: without the room
118
+ // for it, the compaction would only fill the disk (and fail the commits after it).
119
+ const needed = pageBytes(c);
120
+ const free = await room();
121
+ if (free !== undefined && free < needed * 1.25 + (64 << 20)) {
122
+ console.warn(`cellb ltx-store: ${cell}: no compaction, ${Math.round(free / 1e6)} MB free for a snapshot of up to ${Math.round(needed / 1e6)} MB`);
123
+ return c.files.length;
124
+ }
125
+ const frozen = await openPagedCell(files, replaced);
126
+ const final = `${dir}/${ltxFileName(1n, frozen.maxTXID)}`;
127
+ const tmp = `${final}.tmp`;
128
+ try {
129
+ await compactPagedCellTo(frozen, files, tmp); // page runs: memory stays at a run + index
130
+ }
131
+ catch (err) {
132
+ await files.remove(tmp).catch(() => false);
133
+ throw err;
134
+ }
135
+ await new Promise((resolve, reject) => {
136
+ queue = queue.then(async () => {
137
+ try {
138
+ for (const p of replaced)
139
+ await files.remove(p);
140
+ await files.move(tmp, final);
141
+ cells.delete(cell);
142
+ resolve();
143
+ }
144
+ catch (err) {
145
+ reject(err);
146
+ }
147
+ });
148
+ });
149
+ return (await cellOf(cell))?.files.length ?? 0;
150
+ })().finally(() => compacting.delete(cell));
151
+ compacting.set(cell, job);
152
+ return job;
153
+ };
154
+ /**
155
+ * When to compact: past `compactAfter` files, or once the files after the snapshot hold more
156
+ * bytes than half of it (a large cell is not rewritten for a few small commits).
157
+ */
158
+ const deltaBytes = new Map();
159
+ const wantsCompaction = (cell, c) => {
160
+ if (opts.autoCompact === false)
161
+ return false;
162
+ if (c.files.length >= compactAfter)
163
+ return true;
164
+ const snapshot = c.files[0];
165
+ const snapshotBytes = [...snapshot.index.values()].reduce((n, [, size]) => n + size, 0);
166
+ return c.files.length > 1 && (deltaBytes.get(cell) ?? 0) > Math.max(1 << 20, snapshotBytes / 2);
167
+ };
168
+ return {
169
+ /** Answers one request of a VFS (requests are served in order). */
170
+ handle(msg) {
171
+ queue = queue.then(async () => {
172
+ Object.assign(current, {
173
+ op: msg.op,
174
+ cell: msg.cell,
175
+ stage: "open",
176
+ since: performance.now(),
177
+ });
178
+ try {
179
+ const c = await cellOf(msg.cell);
180
+ stage("serve");
181
+ if (msg.op === "meta")
182
+ return answer(enc.encode(JSON.stringify({ pageSize: c?.pageSize ?? 0, pageCount: c?.pageCount ?? 0 })));
183
+ // A read of a file that changed under the cell's view: reopen the cell, read again.
184
+ const retried = async (read) => {
185
+ try {
186
+ return await read(c);
187
+ }
188
+ catch (err) {
189
+ if (!(err instanceof LtxShortReadError))
190
+ throw err;
191
+ console.warn(`cellb ltx-store: ${msg.cell}: ${err.message}; reopening the cell`);
192
+ cells.delete(msg.cell);
193
+ stage("reopen");
194
+ return read((await cellOf(msg.cell)));
195
+ }
196
+ };
197
+ if (msg.op === "read")
198
+ return answer(await retried((c) => c.readPage(msg.pgno)));
199
+ if (msg.op === "readRun") {
200
+ stats.reads++;
201
+ const tr = performance.now();
202
+ try {
203
+ const run = await retried((c) => c.readRun(msg.pgno, msg.count));
204
+ const size = c.pageSize;
205
+ const out = new Uint8Array(run.size * size);
206
+ const pgnos = [...run.keys()].sort((a, b) => a - b);
207
+ for (const [i, p] of pgnos.entries())
208
+ out.set(run.get(p), i * size);
209
+ return answer(out);
210
+ }
211
+ finally {
212
+ stats.readMs += performance.now() - tr;
213
+ }
214
+ }
215
+ if (msg.op === "stats")
216
+ return answer(enc.encode(JSON.stringify(stats)));
217
+ if (msg.op === "commit") {
218
+ const t0 = performance.now();
219
+ const txid = (c?.maxTXID ?? 0n) + 1n;
220
+ const path = `${dirOf(msg.cell)}/${ltxFileName(txid, txid)}`;
221
+ const pages = msg.pages;
222
+ const written = { index: new Map() };
223
+ const header = {
224
+ pageSize: msg.pageSize,
225
+ commit: msg.pageCount,
226
+ minTXID: txid,
227
+ maxTXID: txid,
228
+ timestamp: Date.now(),
229
+ preApplyChecksum: 0n,
230
+ };
231
+ stage(`write ${path}`);
232
+ await files.write(path, encodeLtxStream(header, (async function* () {
233
+ yield* pages;
234
+ })(), 0n, written));
235
+ const t1 = performance.now();
236
+ stage("append");
237
+ // The first commit is the snapshot; later ones extend the overlay by one file.
238
+ if (c)
239
+ await c.append(path, { header: { flags: 2, ...header }, index: written.index });
240
+ else
241
+ cells.delete(msg.cell);
242
+ answer(new Uint8Array());
243
+ stats.commits++;
244
+ stats.writeMs += t1 - t0;
245
+ stats.appendMs += performance.now() - t1;
246
+ // Past the threshold, fold the cell into one snapshot (after answering the commit).
247
+ deltaBytes.set(msg.cell, (deltaBytes.get(msg.cell) ?? 0) +
248
+ [...written.index.values()].reduce((n, [, size]) => n + size, 0));
249
+ if (c && wantsCompaction(msg.cell, c) && !compacting.has(msg.cell)) {
250
+ const t2 = performance.now();
251
+ deltaBytes.set(msg.cell, 0);
252
+ void compact(msg.cell).then(() => {
253
+ stats.compactMs += performance.now() - t2;
254
+ }, (err) => console.warn(`cellb ltx-store: compaction of ${msg.cell} failed:`, err));
255
+ stats.compactions++;
256
+ }
257
+ return;
258
+ }
259
+ throw new Error(`unknown op ${msg.op}`);
260
+ }
261
+ catch (err) {
262
+ console.warn(`cellb ltx-store: ${msg.op} of ${msg.cell} failed at "${current.stage}":`, err);
263
+ answer(enc.encode(String(err?.message ?? err)), 2);
264
+ }
265
+ finally {
266
+ current.op = "";
267
+ }
268
+ });
269
+ },
270
+ /** Folds a cell's files into one snapshot; answers its file count after. */
271
+ /** Folds a cell's files into one snapshot (not through the request queue: it takes its
272
+ * own turn there to swap the files); answers the cell's file count after. */
273
+ compact: (cell) => compact(cell),
274
+ stats,
275
+ /** The paths of a cell's current files. */
276
+ files: async (cell) => (await cellOf(cell))?.files.map((f) => f.path) ?? [],
277
+ };
278
+ }
279
+ /** One cell at `dir` (the earlier single-cell form): messages may omit `cell`. */
280
+ export function ltxPageServer(files, dir, sab) {
281
+ const m = /^(.*)\/cells\/([^/]+)\/ltx\/e1\/0000$/.exec(dir);
282
+ const root = m ? m[1] : dir;
283
+ const name = m ? m[2] : "cell";
284
+ const store = ltxCellStore(files, sab, { root, autoCompact: false });
285
+ let count = 0;
286
+ const refresh = async () => {
287
+ count = (await store.files(name)).length;
288
+ };
289
+ return {
290
+ // A cell another store owns fails its requests (the VFS gets the error), not the start.
291
+ ready: refresh().catch(() => { }),
292
+ handle: (msg) => {
293
+ store.handle({ ...msg, cell: name });
294
+ void refresh();
295
+ },
296
+ compact: async () => {
297
+ const n = await store.compact(name);
298
+ count = n;
299
+ return n;
300
+ },
301
+ get files() {
302
+ return Array.from({ length: count });
303
+ },
304
+ stats: store.stats,
305
+ /** The cell's file count, after the requests sent so far. */
306
+ fileCount: async () => (await store.files(name)).length,
307
+ };
308
+ }
309
+ //# sourceMappingURL=ltx-page-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ltx-page-server.js","sourceRoot":"","sources":["../../src/ltx/ltx-page-server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The asynchronous side of the LTX VFS (src/adapters/ltx-vfs.ts): serves cells' pages from their\n * LTX files on a FilesApi and turns each commit into one LTX file there, in celld's layout\n * (`cells/<cell>/ltx/e1/0000/<min>-<max>.ltx`, a snapshot 1..N first). Runs in any thread that\n * is not one blocked on reads (a worker of its own, or a page): `handle(msg)` answers into the\n * shared buffer. A cell whose files pass `compactAfter` is folded into one snapshot (streamed).\n */\nimport type { FilesApi } from \"@statewalker/webrun-files\";\nimport { scopeOf } from \"../core/celld-schema.js\";\nimport { ltxFileName } from \"./ltx.js\";\nimport {\n compactPagedCellTo,\n encodeLtxStream,\n LtxShortReadError,\n openPagedCell,\n type PagedCell,\n} from \"./ltx-pages.js\";\n\n/** Bytes of page data a cell's files hold (what a compaction may write, at most). */\nconst pageBytes = (c: PagedCell) =>\n c.files.reduce((n, f) => n + [...f.index.values()].reduce((m, [, size]) => m + size, 0), 0);\n\n/** Free bytes in this origin's storage, or undefined where the estimate is not available. */\nasync function room(): Promise<number | undefined> {\n const storage = (globalThis.navigator as { storage?: StorageManager } | undefined)?.storage;\n if (!storage?.estimate) return undefined;\n const { usage = 0, quota } = await storage.estimate();\n return quota === undefined ? undefined : quota - usage;\n}\n\nexport interface LtxStoreMessage {\n op: \"meta\" | \"read\" | \"readRun\" | \"commit\" | \"stats\";\n cell: string;\n pgno?: number;\n count?: number;\n pageSize?: number;\n pageCount?: number;\n pages?: [number, Uint8Array][];\n}\n\nexport function ltxCellStore(\n files: FilesApi,\n sab: SharedArrayBuffer,\n opts: {\n root?: string;\n compactAfter?: number;\n autoCompact?: boolean;\n /**\n * What the files are (the OPFS directory's name, say): part of each cell's Web Lock, so the\n * stores of two directories (two projects in one tab) do not take each other's locks.\n */\n lockScope?: string;\n } = {},\n) {\n const ctl = new Int32Array(sab, 0, 2);\n const payload = new Uint8Array(sab, 8);\n const root = (opts.root ?? \"\").replace(/\\/$/, \"\");\n const compactAfter = opts.compactAfter ?? 64;\n // celld's cell directory: `<Class>:<idHex>` (scopeOf), as the LTX log and celld name it.\n const dirOf = (cell: string) =>\n `${root}/cells/${scopeOf(cell).replace(/[\\\\/]/g, \"_\")}/ltx/e1/0000`;\n const cells = new Map<string, PagedCell | undefined>();\n /** Where the store's time goes (ms), for traces. */\n const stats = {\n commits: 0,\n writeMs: 0,\n appendMs: 0,\n compactions: 0,\n compactMs: 0,\n reads: 0,\n readMs: 0,\n };\n const list = async (dir: string, cleanUp = false) => {\n const out: string[] = [];\n if (!(await files.exists(dir))) return out;\n for await (const e of files.list(dir)) {\n if (e.kind !== \"file\") continue;\n if (e.name.endsWith(\".ltx\")) out.push(e.path);\n // On this store's first open of the cell: a compaction or a write that did not finish\n // (a closed tab, a full disk), its space back.\n else if (cleanUp && /\\.ltx\\.(tmp|part-\\w+)$/.test(e.name))\n await files.remove(e.path).catch(() => false);\n }\n return out;\n };\n /**\n * One writer per cell in the origin: the store holds a Web Lock per cell directory while the\n * cell is open, so another tab's store (or worker) cannot write the same cell meanwhile; it\n * fails instead. (Across machines, celld's epochs and ownership records do this.)\n */\n const owned = new Set<string>();\n const own = async (dir: string) => {\n const locks = (globalThis.navigator as { locks?: LockManager } | undefined)?.locks;\n if (!locks || owned.has(dir)) return;\n // Waits a while for it: on a reload, the previous page's store holds it until its worker\n // goes, a moment later (failing at once broke the cell's Durable Object on every reload).\n const got = await new Promise<boolean>((resolve) => {\n void locks\n .request(\n `cellb-ltx:${opts.lockScope ?? \"\"}:${dir}`,\n { signal: AbortSignal.timeout(20_000) },\n () => {\n resolve(true);\n return new Promise<void>(() => {}); // held while this store lives\n },\n )\n .catch(() => resolve(false));\n });\n if (!got) throw new Error(`ltx: cell ${dir} is open in another tab or worker (waited 20 s)`);\n owned.add(dir);\n };\n const cellOf = async (cell: string) => {\n if (!cells.has(cell)) {\n const dir = dirOf(cell);\n const first = !owned.has(dir);\n await own(dir);\n const paths = await list(dir, first);\n cells.set(cell, paths.length ? await openPagedCell(files, paths) : undefined);\n }\n return cells.get(cell);\n };\n const answer = (bytes: Uint8Array, state = 1) => {\n payload.set(bytes);\n ctl[1] = bytes.length;\n Atomics.store(ctl, 0, state);\n Atomics.notify(ctl, 0);\n };\n const enc = new TextEncoder();\n let queue = Promise.resolve();\n /** The request being served and its stage: a watchdog names the one that does not end. */\n const current = { op: \"\", cell: \"\", stage: \"\", since: 0 };\n const stage = (s: string) => {\n current.stage = s;\n };\n const watchdog = setInterval(() => {\n if (current.op && performance.now() - current.since > 10_000)\n console.warn(\n `cellb ltx-store: ${current.op} of ${current.cell} at \"${current.stage}\" for ${Math.round((performance.now() - current.since) / 1000)} s`,\n );\n }, 10_000);\n (watchdog as { unref?: () => void }).unref?.();\n\n /**\n * Folds a cell's files into one snapshot WITHOUT holding up its requests: the snapshot is\n * written from a frozen view of the files as they are now (later commits add files after\n * them), then, between two requests, the files it replaces are removed and the cell reopened\n * (the snapshot plus the files written meanwhile). One compaction per cell at a time.\n */\n const compacting = new Map<string, Promise<number>>();\n const compact = (cell: string): Promise<number> => {\n const running = compacting.get(cell);\n if (running) return running;\n const job = (async () => {\n const c = await cellOf(cell);\n if (!c || c.files.length < 2) return c?.files.length ?? 0;\n const dir = dirOf(cell);\n const replaced = c.files.map((f) => f.path);\n // The new snapshot exists beside the files it replaces until the swap: without the room\n // for it, the compaction would only fill the disk (and fail the commits after it).\n const needed = pageBytes(c);\n const free = await room();\n if (free !== undefined && free < needed * 1.25 + (64 << 20)) {\n console.warn(\n `cellb ltx-store: ${cell}: no compaction, ${Math.round(free / 1e6)} MB free for a snapshot of up to ${Math.round(needed / 1e6)} MB`,\n );\n return c.files.length;\n }\n const frozen = await openPagedCell(files, replaced);\n const final = `${dir}/${ltxFileName(1n, frozen.maxTXID)}`;\n const tmp = `${final}.tmp`;\n try {\n await compactPagedCellTo(frozen, files, tmp); // page runs: memory stays at a run + index\n } catch (err) {\n await files.remove(tmp).catch(() => false);\n throw err;\n }\n await new Promise<void>((resolve, reject) => {\n queue = queue.then(async () => {\n try {\n for (const p of replaced) await files.remove(p);\n await files.move(tmp, final);\n cells.delete(cell);\n resolve();\n } catch (err) {\n reject(err);\n }\n });\n });\n return (await cellOf(cell))?.files.length ?? 0;\n })().finally(() => compacting.delete(cell));\n compacting.set(cell, job);\n return job;\n };\n\n /**\n * When to compact: past `compactAfter` files, or once the files after the snapshot hold more\n * bytes than half of it (a large cell is not rewritten for a few small commits).\n */\n const deltaBytes = new Map<string, number>();\n const wantsCompaction = (cell: string, c: PagedCell) => {\n if (opts.autoCompact === false) return false;\n if (c.files.length >= compactAfter) return true;\n const snapshot = c.files[0];\n const snapshotBytes = [...snapshot.index.values()].reduce((n, [, size]) => n + size, 0);\n return c.files.length > 1 && (deltaBytes.get(cell) ?? 0) > Math.max(1 << 20, snapshotBytes / 2);\n };\n\n return {\n /** Answers one request of a VFS (requests are served in order). */\n handle(msg: LtxStoreMessage) {\n queue = queue.then(async () => {\n Object.assign(current, {\n op: msg.op,\n cell: msg.cell,\n stage: \"open\",\n since: performance.now(),\n });\n try {\n const c = await cellOf(msg.cell);\n stage(\"serve\");\n if (msg.op === \"meta\")\n return answer(\n enc.encode(\n JSON.stringify({ pageSize: c?.pageSize ?? 0, pageCount: c?.pageCount ?? 0 }),\n ),\n );\n // A read of a file that changed under the cell's view: reopen the cell, read again.\n const retried = async <T>(read: (c: PagedCell) => Promise<T>): Promise<T> => {\n try {\n return await read(c as PagedCell);\n } catch (err) {\n if (!(err instanceof LtxShortReadError)) throw err;\n console.warn(`cellb ltx-store: ${msg.cell}: ${err.message}; reopening the cell`);\n cells.delete(msg.cell);\n stage(\"reopen\");\n return read((await cellOf(msg.cell)) as PagedCell);\n }\n };\n if (msg.op === \"read\")\n return answer(await retried((c) => c.readPage(msg.pgno as number)));\n if (msg.op === \"readRun\") {\n stats.reads++;\n const tr = performance.now();\n try {\n const run = await retried((c) => c.readRun(msg.pgno as number, msg.count as number));\n const size = (c as PagedCell).pageSize;\n const out = new Uint8Array(run.size * size);\n const pgnos = [...run.keys()].sort((a, b) => a - b);\n for (const [i, p] of pgnos.entries()) out.set(run.get(p) as Uint8Array, i * size);\n return answer(out);\n } finally {\n stats.readMs += performance.now() - tr;\n }\n }\n if (msg.op === \"stats\") return answer(enc.encode(JSON.stringify(stats)));\n if (msg.op === \"commit\") {\n const t0 = performance.now();\n const txid = (c?.maxTXID ?? 0n) + 1n;\n const path = `${dirOf(msg.cell)}/${ltxFileName(txid, txid)}`;\n const pages = msg.pages as [number, Uint8Array][];\n const written = { index: new Map<number, [number, number]>() };\n const header = {\n pageSize: msg.pageSize as number,\n commit: msg.pageCount as number,\n minTXID: txid,\n maxTXID: txid,\n timestamp: Date.now(),\n preApplyChecksum: 0n,\n };\n stage(`write ${path}`);\n await files.write(\n path,\n encodeLtxStream(\n header,\n (async function* () {\n yield* pages;\n })(),\n 0n,\n written,\n ),\n );\n const t1 = performance.now();\n stage(\"append\");\n // The first commit is the snapshot; later ones extend the overlay by one file.\n if (c) await c.append(path, { header: { flags: 2, ...header }, index: written.index });\n else cells.delete(msg.cell);\n answer(new Uint8Array());\n stats.commits++;\n stats.writeMs += t1 - t0;\n stats.appendMs += performance.now() - t1;\n // Past the threshold, fold the cell into one snapshot (after answering the commit).\n deltaBytes.set(\n msg.cell,\n (deltaBytes.get(msg.cell) ?? 0) +\n [...written.index.values()].reduce((n, [, size]) => n + size, 0),\n );\n if (c && wantsCompaction(msg.cell, c) && !compacting.has(msg.cell)) {\n const t2 = performance.now();\n deltaBytes.set(msg.cell, 0);\n void compact(msg.cell).then(\n () => {\n stats.compactMs += performance.now() - t2;\n },\n (err) => console.warn(`cellb ltx-store: compaction of ${msg.cell} failed:`, err),\n );\n stats.compactions++;\n }\n return;\n }\n throw new Error(`unknown op ${msg.op}`);\n } catch (err) {\n console.warn(\n `cellb ltx-store: ${msg.op} of ${msg.cell} failed at \"${current.stage}\":`,\n err,\n );\n answer(enc.encode(String((err as Error)?.message ?? err)), 2);\n } finally {\n current.op = \"\";\n }\n });\n },\n /** Folds a cell's files into one snapshot; answers its file count after. */\n /** Folds a cell's files into one snapshot (not through the request queue: it takes its\n * own turn there to swap the files); answers the cell's file count after. */\n compact: (cell: string) => compact(cell),\n stats,\n /** The paths of a cell's current files. */\n files: async (cell: string) => (await cellOf(cell))?.files.map((f) => f.path) ?? [],\n };\n}\n\n/** One cell at `dir` (the earlier single-cell form): messages may omit `cell`. */\nexport function ltxPageServer(files: FilesApi, dir: string, sab: SharedArrayBuffer) {\n const m = /^(.*)\\/cells\\/([^/]+)\\/ltx\\/e1\\/0000$/.exec(dir);\n const root = m ? m[1] : dir;\n const name = m ? m[2] : \"cell\";\n const store = ltxCellStore(files, sab, { root, autoCompact: false });\n let count = 0;\n const refresh = async () => {\n count = (await store.files(name)).length;\n };\n return {\n // A cell another store owns fails its requests (the VFS gets the error), not the start.\n ready: refresh().catch(() => {}),\n handle: (msg: Omit<LtxStoreMessage, \"cell\"> & { cell?: string }) => {\n store.handle({ ...msg, cell: name } as LtxStoreMessage);\n void refresh();\n },\n compact: async () => {\n const n = await store.compact(name);\n count = n;\n return n;\n },\n get files() {\n return Array.from({ length: count });\n },\n stats: store.stats,\n /** The cell's file count, after the requests sent so far. */\n fileCount: async () => (await store.files(name)).length,\n };\n}\n"]}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Page-level random access to a cell kept as celld keeps it: LTX files in a bucket
3
+ * (`…/ltx/e<epoch>/<level>/<min>-<max>.ltx`), a snapshot (TXID 1..N) and the incremental files
4
+ * after it. Nothing is read whole: an LTX file ends with a page index (pgno → offset, size), so
5
+ * opening a file reads its 100-byte header and its index, and a page is ONE range read plus an
6
+ * LZ4 decode. The newest file holding a page wins; `compact` folds them into a new snapshot.
7
+ *
8
+ * This is the read side a SQLite VFS needs to serve a database from any FilesApi (OPFS, File
9
+ * System Access, IndexedDB, S3…) without loading it: xRead → `readPage` (through a page cache);
10
+ * the write side is the existing LTX log (one incremental file per commit).
11
+ */
12
+ import type { FilesApi } from "@statewalker/webrun-files";
13
+ import { type LtxHeader } from "./ltx.js";
14
+ /** A range read that got fewer bytes than asked: the file changed or was removed meanwhile. */
15
+ export declare class LtxShortReadError extends Error {
16
+ name: string;
17
+ }
18
+ export interface LtxPages {
19
+ path: string;
20
+ header: LtxHeader;
21
+ /** pgno → [offset of the page's 6-byte header, size including it]. */
22
+ index: Map<number, [number, number]>;
23
+ /** One range read and an LZ4 decode. */
24
+ readPage(pgno: number): Promise<Uint8Array | undefined>;
25
+ /** Several pages of this file with ONE range read (they are stored in pgno order). */
26
+ readPages(pgnos: number[]): Promise<Map<number, Uint8Array>>;
27
+ }
28
+ /** Opens an LTX file for page reads: reads its header and its page index only. */
29
+ export declare function openLtxPages(files: FilesApi, path: string,
30
+ /** The header and index of a file this process just wrote: nothing is read back. */
31
+ known?: {
32
+ header: LtxHeader;
33
+ index: Map<number, [number, number]>;
34
+ }): Promise<LtxPages>;
35
+ export interface PagedCell {
36
+ pageSize: number;
37
+ /** Database size in pages, after the newest file. */
38
+ pageCount: number;
39
+ maxTXID: bigint;
40
+ /** The files, oldest first (the snapshot, then the incremental ones). */
41
+ files: LtxPages[];
42
+ /** The page as of the newest file holding it (zeros past the database end). */
43
+ readPage(pgno: number): Promise<Uint8Array>;
44
+ /** Pages `from`..`from+count-1` (within the database), one range read per file they live in. */
45
+ readRun(from: number, count: number): Promise<Map<number, Uint8Array>>;
46
+ /** Which file serves each page (for tests and traces). */
47
+ source(pgno: number): string | undefined;
48
+ /**
49
+ * Adds the cell's next file (the one just written after `maxTXID`): reads its header and index
50
+ * only, so a commit costs one file whatever the number of files before it.
51
+ */
52
+ append(path: string, known?: {
53
+ header: LtxHeader;
54
+ index: Map<number, [number, number]>;
55
+ }): Promise<void>;
56
+ }
57
+ /**
58
+ * A cell's pages over its LTX files: the newest snapshot (min TXID 1) and the files after it,
59
+ * in TXID order. Only headers and page indexes are read.
60
+ */
61
+ export declare function openPagedCell(files: FilesApi, paths: string[]): Promise<PagedCell>;
62
+ /**
63
+ * An LTX file produced page by page, as celld writes it (block format: size-prefixed LZ4 page
64
+ * blocks, page index, trailer, CRC-64/ISO over the uncompressed pages): the bytes come out as an
65
+ * async stream while `pages` is consumed, so a writer holds one page at a time plus the index
66
+ * (a few bytes per page), never the database.
67
+ */
68
+ export declare function encodeLtxStream(header: LtxHeader, pages: AsyncIterable<[number, Uint8Array]>, postApplyChecksum?: bigint,
69
+ /** Filled while encoding: the file's page index (what `openLtxPages` would read back). */
70
+ written?: {
71
+ index: Map<number, [number, number]>;
72
+ }): AsyncGenerator<Uint8Array>;
73
+ /**
74
+ * Folds a cell's files into one snapshot (TXID 1..maxTXID) written to `files` at `path`, page by
75
+ * page: each page is read by range from the file that holds it and streamed into the new file,
76
+ * so memory stays at one page (plus the index), whatever the database size. The caller removes
77
+ * the files it replaces once this resolves.
78
+ */
79
+ export declare function compactPagedCellTo(cell: PagedCell, files: FilesApi, path: string, timestamp?: number): Promise<void>;
80
+ /** The compacted snapshot as bytes (small cells, tests); compactPagedCellTo streams it. */
81
+ export declare function compactPagedCell(cell: PagedCell, timestamp?: number): Promise<Uint8Array>;