@statewalker/cellb 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +373 -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,289 @@
1
+ import { scopeOf } from "../core/celld-schema.js";
2
+ import { decodeLtx, encodeLtx, ltxFileName } from "../ltx/ltx.js";
3
+ import { equal, pageSizeOf, toRollback } from "./celld-format.js";
4
+ const enc = new TextEncoder();
5
+ const dec = new TextDecoder();
6
+ const range = (key) => {
7
+ const m = /\/ltx\/e(\d+)\/\d+\/([0-9a-f]{16})-([0-9a-f]{16})\.ltx$/.exec(key);
8
+ return m
9
+ ? { epoch: Number(m[1]), min: BigInt(`0x${m[2]}`), max: BigInt(`0x${m[3]}`) }
10
+ : undefined;
11
+ };
12
+ export function ltxLog(store, o = {}) {
13
+ const now = o.now ?? (() => Date.now());
14
+ // Per cell: the epoch it writes into, its last TXID and the pages last written.
15
+ const cells = new Map();
16
+ /** The highest TXID already written in a cell's epoch (0n: none). */
17
+ const lastTxid = async (cellKey, epoch) => {
18
+ let max = 0n;
19
+ for (const key of await store.list(`cells/${scopeOf(cellKey)}/ltx/e${epoch}/`)) {
20
+ const r = range(key);
21
+ if (r && r.epoch === epoch && r.max > max)
22
+ max = r.max;
23
+ }
24
+ return max;
25
+ };
26
+ // celld's wake index (logic/wake.rs, wake_format.rs): one immutable entry per alarm
27
+ // installation, filed under the UTC minute it is due; a per-cell retirement certificate
28
+ // marks older installations (and a cancelled one) as void.
29
+ const hex16 = (n) => n.toString(16).padStart(16, "0");
30
+ let wakeFormat;
31
+ const ensureWakeFormat = () => (wakeFormat ??= store
32
+ .put("wake/format.json", enc.encode(JSON.stringify({ format: 2 })), { ifNoneMatch: "*" })
33
+ .catch(() => { }));
34
+ // Commits of one cell are logged one after the other, so TXIDs and the page mirror advance
35
+ // in commit order. (Without this, two quick commits both saw TXID 0 and raced to PUT the
36
+ // same snapshot key — the older image could land last; seen in Chromium against celld.)
37
+ const queues = new Map();
38
+ const inOrder = (cellKey, run) => {
39
+ const next = (queues.get(cellKey) ?? Promise.resolve()).catch(() => { }).then(run);
40
+ queues.set(cellKey, next);
41
+ next.then(() => queues.get(cellKey) === next && queues.delete(cellKey), () => queues.get(cellKey) === next && queues.delete(cellKey));
42
+ return next;
43
+ };
44
+ const durability = {
45
+ committed(cellKey, info) {
46
+ return inOrder(cellKey, () => durability.logCommit(cellKey, info));
47
+ },
48
+ capturePages: true,
49
+ async publishWake(cellKey, w) {
50
+ await ensureWakeFormat();
51
+ const cell = scopeOf(cellKey);
52
+ const identity = `${hex16(w.epoch)}-${hex16(w.sequence)}`;
53
+ // 1. the new installation's entry (none for a cancelled or consumed alarm)
54
+ if (w.at != null) {
55
+ const minute = new Date(w.at).toISOString().slice(0, 16);
56
+ await store.put(`wake/entries/${minute}/${cell}/${identity}`, enc.encode(JSON.stringify({ cell, due_ms: w.at, format: 2, identity })));
57
+ }
58
+ // 2. the retirement certificate, once the replacement is published (celld's order):
59
+ // every older identity is void, and this one too when the alarm is gone.
60
+ await store.put(`wake/retired/${cell}.json`, enc.encode(JSON.stringify({ format: 2, identity, at_ms: w.at })));
61
+ // 3. cleanup below the certificate (celld: only below a proved frontier, which this is)
62
+ for (const k of await store.list("wake/entries/")) {
63
+ const m = /\/([0-9a-f]{16}-[0-9a-f]{16})$/.exec(k);
64
+ if (!m ||
65
+ !k.includes(`/${cell}/`) ||
66
+ k.slice(0, -m[0].length).endsWith(`/${cell}`) === false)
67
+ continue;
68
+ const id = m[1];
69
+ if (id < identity || (id === identity && w.at == null))
70
+ await store.delete?.(k).catch(() => { });
71
+ }
72
+ },
73
+ async wakes() {
74
+ const retired = new Map();
75
+ for (const k of await store.list("wake/retired/")) {
76
+ const o = await store.get(k);
77
+ if (!o)
78
+ continue;
79
+ const r = JSON.parse(dec.decode(o.body));
80
+ retired.set(k.slice("wake/retired/".length, -".json".length), {
81
+ id: r.identity,
82
+ none: r.at_ms == null,
83
+ });
84
+ }
85
+ const out = [];
86
+ const latest = new Map();
87
+ for (const k of await store.list("wake/entries/")) {
88
+ const m = /^wake\/entries\/[^/]+\/(.+)\/([0-9a-f]{16}-[0-9a-f]{16})$/.exec(k);
89
+ if (!m)
90
+ continue;
91
+ const [, cell, id] = m;
92
+ if (cell.includes("/"))
93
+ continue; // facets wake through their root
94
+ const r = retired.get(cell);
95
+ if (r && (id < r.id || (id === r.id && r.none)))
96
+ continue;
97
+ const o = await store.get(k);
98
+ if (!o)
99
+ continue;
100
+ const at = JSON.parse(dec.decode(o.body)).due_ms;
101
+ const prev = latest.get(cell);
102
+ if (!prev || id > prev.id)
103
+ latest.set(cell, { id, at });
104
+ }
105
+ for (const [cell, { at }] of latest) {
106
+ const [className] = cell.split(":");
107
+ out.push({ cellKey: cell.replace(":", "/"), className, at });
108
+ }
109
+ return out;
110
+ },
111
+ async logCommit(cellKey, info) {
112
+ if (o.authorized && !o.authorized())
113
+ throw new Error(`${cellKey}: this node's lease has lapsed; the write is refused (self-fencing)`);
114
+ let cell = cells.get(cellKey);
115
+ if (!cell) {
116
+ // First commit of this cell in this process: continue the epoch's TXIDs if it already
117
+ // has files (a restart without a new epoch), with a whole-database file.
118
+ const epoch = info.epoch ?? o.defaultEpoch ?? 1;
119
+ cell = { epoch, txid: await lastTxid(cellKey, epoch), pages: undefined };
120
+ cells.set(cellKey, cell);
121
+ }
122
+ // The page hook (SQLite WASM) hands over exactly the changed pages: no image needed.
123
+ if (info.pages && cell.pages) {
124
+ if (!info.pages.pages.size)
125
+ return;
126
+ const txid = cell.txid + 1n;
127
+ await store.put(`cells/${scopeOf(cellKey)}/ltx/e${cell.epoch}/0000/${ltxFileName(txid, txid)}`, encodeLtx({
128
+ header: {
129
+ pageSize: info.pages.pageSize,
130
+ commit: info.pages.pageCount,
131
+ minTXID: txid,
132
+ maxTXID: txid,
133
+ timestamp: now(),
134
+ preApplyChecksum: 0n,
135
+ },
136
+ pages: info.pages.pages,
137
+ postApplyChecksum: 0n,
138
+ }));
139
+ cell.txid = txid;
140
+ for (const [p, d] of info.pages.pages)
141
+ cell.pages.set(p, d.slice());
142
+ for (const p of [...cell.pages.keys()])
143
+ if (p > info.pages.pageCount)
144
+ cell.pages.delete(p);
145
+ return;
146
+ }
147
+ const image = typeof info.image === "function" ? await info.image() : info.image;
148
+ if (!image)
149
+ return;
150
+ const pageSize = pageSizeOf(image);
151
+ const commit = image.length / pageSize;
152
+ const page = (p) => image.subarray((p - 1) * pageSize, p * pageSize);
153
+ const changed = new Map();
154
+ for (let p = 1; p <= commit; p++) {
155
+ const prev = cell.pages?.get(p);
156
+ const cur = page(p);
157
+ if (!prev || !equal(prev, cur))
158
+ changed.set(p, cur.slice());
159
+ }
160
+ if (cell.pages && changed.size === 0 && cell.pages.size === commit)
161
+ return;
162
+ const txid = cell.txid + 1n;
163
+ const pages = cell.pages
164
+ ? changed
165
+ : new Map([...Array(commit)].map((_, i) => [i + 1, page(i + 1).slice()]));
166
+ const file = encodeLtx({
167
+ header: {
168
+ pageSize,
169
+ commit,
170
+ minTXID: txid,
171
+ maxTXID: txid,
172
+ timestamp: now(),
173
+ preApplyChecksum: 0n,
174
+ },
175
+ pages,
176
+ postApplyChecksum: 0n, // celld 0.6.2 leaves the rolling checksums at 0
177
+ });
178
+ await store.put(`cells/${scopeOf(cellKey)}/ltx/e${cell.epoch}/0000/${ltxFileName(txid, txid)}`, file);
179
+ cell.txid = txid;
180
+ cell.pages ??= new Map();
181
+ for (const [p, d] of pages)
182
+ cell.pages.set(p, d);
183
+ for (const p of [...cell.pages.keys()])
184
+ if (p > commit)
185
+ cell.pages.delete(p);
186
+ },
187
+ async save(cellKey, image, _info) {
188
+ // Every transaction is already in the bucket; compaction (opt-in) folds an epoch into one
189
+ // snapshot file 1..N — celld reads it as any snapshot.
190
+ if (!o.compact)
191
+ return;
192
+ await inOrder(cellKey, async () => {
193
+ const cell = cells.get(cellKey);
194
+ const epoch = cell?.epoch ?? o.defaultEpoch ?? 1;
195
+ const txid = cell?.txid ?? (await lastTxid(cellKey, epoch));
196
+ if (!txid)
197
+ return;
198
+ const pageSize = pageSizeOf(image);
199
+ const commit = image.length / pageSize;
200
+ const pages = new Map([...Array(commit)].map((_, n) => [n + 1, image.slice(n * pageSize, (n + 1) * pageSize)]));
201
+ const prefix = `cells/${scopeOf(cellKey)}/ltx/e${epoch}/`;
202
+ const before = await store.list(prefix);
203
+ const snapshot = `${prefix}0000/${ltxFileName(1n, txid)}`;
204
+ await store.put(snapshot, encodeLtx({
205
+ header: {
206
+ pageSize,
207
+ commit,
208
+ minTXID: 1n,
209
+ maxTXID: txid,
210
+ timestamp: now(),
211
+ preApplyChecksum: 0n,
212
+ },
213
+ pages,
214
+ postApplyChecksum: 0n,
215
+ }));
216
+ for (const key of before) {
217
+ const r = range(key);
218
+ if (key !== snapshot && r && r.max <= txid)
219
+ await store.delete(key);
220
+ }
221
+ });
222
+ },
223
+ async load(cellKey) {
224
+ const scope = scopeOf(cellKey);
225
+ const byEpoch = new Map();
226
+ for (const key of await store.list(`cells/${scope}/ltx/e`)) {
227
+ const r = range(key);
228
+ if (!r)
229
+ continue;
230
+ const list = byEpoch.get(r.epoch) ?? [];
231
+ list.push({ key, min: r.min, max: r.max });
232
+ byEpoch.set(r.epoch, list);
233
+ }
234
+ // The newest epoch whose files chain from a snapshot (TXID 1): files in TXID order; a
235
+ // snapshot restarts the image, a file already covered is skipped, a gap ends the chain.
236
+ for (const epoch of [...byEpoch.keys()].sort((a, b) => b - a)) {
237
+ const files = byEpoch
238
+ .get(epoch)
239
+ .sort((a, b) => (a.max !== b.max ? (a.max < b.max ? -1 : 1) : a.min < b.min ? -1 : 1));
240
+ if (!files.some((f) => f.min === 1n))
241
+ continue;
242
+ const pages = new Map();
243
+ let pageSize = 0;
244
+ let commit = 0;
245
+ let next = 1n;
246
+ let started = false;
247
+ for (const f of files) {
248
+ if (f.max < next)
249
+ continue;
250
+ if (f.min !== 1n && (!started || f.min !== next)) {
251
+ if (started)
252
+ break;
253
+ continue;
254
+ }
255
+ const ltx = decodeLtx((await store.get(f.key)).body);
256
+ if (f.min === 1n)
257
+ pages.clear();
258
+ for (const [p, d] of ltx.pages)
259
+ pages.set(p, d);
260
+ pageSize = ltx.header.pageSize;
261
+ commit = ltx.header.commit;
262
+ next = f.max + 1n;
263
+ started = true;
264
+ }
265
+ if (!started)
266
+ continue;
267
+ const image = new Uint8Array(commit * pageSize);
268
+ for (let p = 1; p <= commit; p++) {
269
+ const d = pages.get(p);
270
+ if (d)
271
+ image.set(d, (p - 1) * pageSize);
272
+ }
273
+ return toRollback(image);
274
+ }
275
+ return undefined;
276
+ },
277
+ };
278
+ return Object.assign(durability, {
279
+ /** A new owner epoch for the cell: its next commit opens the generation with a snapshot. */
280
+ begin(cellKey, epoch) {
281
+ cells.set(cellKey, { epoch, txid: 0n, pages: undefined });
282
+ },
283
+ /** The cell is no longer ours (lease lost): forget its writer state. */
284
+ forget(cellKey) {
285
+ cells.delete(cellKey);
286
+ },
287
+ });
288
+ }
289
+ //# sourceMappingURL=ltx-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ltx-log.js","sourceRoot":"","sources":["../../src/bucket/ltx-log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAElE,OAAO,EAAE,KAAK,EAAS,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGzE,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC;AAC9B,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC;AA2B9B,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,EAAE;IAC5B,MAAM,CAAC,GAAG,yDAAyD,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9E,OAAO,CAAC;QACN,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;QAC7E,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,UAAU,MAAM,CAAC,KAAkB,EAAE,CAAC,GAAkB,EAAE;IAC9D,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACxC,gFAAgF;IAChF,MAAM,KAAK,GAAG,IAAI,GAAG,EAGlB,CAAC;IACJ,qEAAqE;IACrE,MAAM,QAAQ,GAAG,KAAK,EAAE,OAAe,EAAE,KAAa,EAAE,EAAE;QACxD,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,KAAK,MAAM,GAAG,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,OAAO,CAAC,SAAS,KAAK,GAAG,CAAC,EAAE,CAAC;YAC/E,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;YACrB,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG;gBAAE,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;QACzD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,oFAAoF;IACpF,wFAAwF;IACxF,2DAA2D;IAC3D,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC9D,IAAI,UAAwC,CAAC;IAC7C,MAAM,gBAAgB,GAAG,GAAG,EAAE,CAC5B,CAAC,UAAU,KAAK,KAAK;SAClB,GAAG,CAAC,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC;SACxF,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC;IAEtB,2FAA2F;IAC3F,yFAAyF;IACzF,wFAAwF;IACxF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChD,MAAM,OAAO,GAAG,CAAC,OAAe,EAAE,GAAwB,EAAiB,EAAE;QAC3E,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClF,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,IAAI,CACP,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAC5D,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAC7D,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,UAAU,GAGV;QACJ,SAAS,CAAC,OAAO,EAAE,IAAI;YACrB,OAAO,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QACrE,CAAC;QACD,YAAY,EAAE,IAAI;QAElB,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,gBAAgB,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;YAC9B,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1D,2EAA2E;YAC3E,IAAI,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;gBACjB,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACzD,MAAM,KAAK,CAAC,GAAG,CACb,gBAAgB,MAAM,IAAI,IAAI,IAAI,QAAQ,EAAE,EAC5C,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CACxE,CAAC;YACJ,CAAC;YACD,oFAAoF;YACpF,4EAA4E;YAC5E,MAAM,KAAK,CAAC,GAAG,CACb,gBAAgB,IAAI,OAAO,EAC3B,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CACjE,CAAC;YACF,wFAAwF;YACxF,KAAK,MAAM,CAAC,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;gBAClD,MAAM,CAAC,GAAG,gCAAgC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACnD,IACE,CAAC,CAAC;oBACF,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,CAAC;oBACxB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK,KAAK;oBAEvD,SAAS;gBACX,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBAChB,IAAI,EAAE,GAAG,QAAQ,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC;oBACpD,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;QAED,KAAK,CAAC,KAAK;YACT,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyC,CAAC;YACjE,KAAK,MAAM,CAAC,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;gBAClD,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC7B,IAAI,CAAC,CAAC;oBAAE,SAAS;gBACjB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAA+C,CAAC;gBACvF,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;oBAC5D,EAAE,EAAE,CAAC,CAAC,QAAQ;oBACd,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI;iBACtB,CAAC,CAAC;YACL,CAAC;YACD,MAAM,GAAG,GAAyD,EAAE,CAAC;YACrE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAsC,CAAC;YAC7D,KAAK,MAAM,CAAC,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;gBAClD,MAAM,CAAC,GAAG,2DAA2D,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAC9E,IAAI,CAAC,CAAC;oBAAE,SAAS;gBACjB,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;gBACvB,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;oBAAE,SAAS,CAAC,iCAAiC;gBACnE,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC5B,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;oBAAE,SAAS;gBAC1D,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC7B,IAAI,CAAC,CAAC;oBAAE,SAAS;gBACjB,MAAM,EAAE,GAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAwB,CAAC,MAAM,CAAC;gBACzE,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC9B,IAAI,CAAC,IAAI,IAAI,EAAE,GAAG,IAAI,CAAC,EAAE;oBAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1D,CAAC;YACD,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC;gBACpC,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBACpC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;YAC/D,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI;YAC3B,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE;gBACjC,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,qEAAqE,CAChF,CAAC;YACJ,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,sFAAsF;gBACtF,yEAAyE;gBACzE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC;gBAChD,IAAI,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;gBACzE,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC3B,CAAC;YACD,qFAAqF;YACrF,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC7B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI;oBAAE,OAAO;gBACnC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;gBAC5B,MAAM,KAAK,CAAC,GAAG,CACb,SAAS,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,KAAK,SAAS,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAC9E,SAAS,CAAC;oBACR,MAAM,EAAE;wBACN,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ;wBAC7B,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS;wBAC5B,OAAO,EAAE,IAAI;wBACb,OAAO,EAAE,IAAI;wBACb,SAAS,EAAE,GAAG,EAAE;wBAChB,gBAAgB,EAAE,EAAE;qBACrB;oBACD,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK;oBACvB,iBAAiB,EAAE,EAAE;iBACtB,CAAC,CACH,CAAC;gBACF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;gBACjB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK;oBAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;gBACpE,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;oBAAE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS;wBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAC3F,OAAO;YACT,CAAC;YACD,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;YACjF,IAAI,CAAC,KAAK;gBAAE,OAAO;YACnB,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC;YACvC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC;YAC7E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;YAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACjC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;gBAChC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBACpB,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC;oBAAE,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;YAC9D,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM;gBAAE,OAAO;YAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK;gBACtB,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;YAC5E,MAAM,IAAI,GAAG,SAAS,CAAC;gBACrB,MAAM,EAAE;oBACN,QAAQ;oBACR,MAAM;oBACN,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE,IAAI;oBACb,SAAS,EAAE,GAAG,EAAE;oBAChB,gBAAgB,EAAE,EAAE;iBACrB;gBACD,KAAK;gBACL,iBAAiB,EAAE,EAAE,EAAE,gDAAgD;aACxE,CAAC,CAAC;YACH,MAAM,KAAK,CAAC,GAAG,CACb,SAAS,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,KAAK,SAAS,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAC9E,IAAI,CACL,CAAC;YACF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;YACjB,IAAI,CAAC,KAAK,KAAK,IAAI,GAAG,EAAE,CAAC;YACzB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK;gBAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACjD,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;gBAAE,IAAI,CAAC,GAAG,MAAM;oBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC/E,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,OAAe,EAAE,KAAiB,EAAE,KAAiB;YAC9D,0FAA0F;YAC1F,uDAAuD;YACvD,IAAI,CAAC,CAAC,CAAC,OAAO;gBAAE,OAAO;YACvB,MAAM,OAAO,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;gBAChC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAChC,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC;gBACjD,MAAM,IAAI,GAAG,IAAI,EAAE,IAAI,IAAI,CAAC,MAAM,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;gBAC5D,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAClB,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;gBACnC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC;gBACvC,MAAM,KAAK,GAAG,IAAI,GAAG,CACnB,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CACzF,CAAC;gBACF,MAAM,MAAM,GAAG,SAAS,OAAO,CAAC,OAAO,CAAC,SAAS,KAAK,GAAG,CAAC;gBAC1D,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACxC,MAAM,QAAQ,GAAG,GAAG,MAAM,QAAQ,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;gBAC1D,MAAM,KAAK,CAAC,GAAG,CACb,QAAQ,EACR,SAAS,CAAC;oBACR,MAAM,EAAE;wBACN,QAAQ;wBACR,MAAM;wBACN,OAAO,EAAE,EAAE;wBACX,OAAO,EAAE,IAAI;wBACb,SAAS,EAAE,GAAG,EAAE;wBAChB,gBAAgB,EAAE,EAAE;qBACrB;oBACD,KAAK;oBACL,iBAAiB,EAAE,EAAE;iBACtB,CAAC,CACH,CAAC;gBACF,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;oBACzB,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;oBACrB,IAAI,GAAG,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI;wBAAE,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACtE,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,OAAe;YACxB,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;YAC/B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuD,CAAC;YAC/E,KAAK,MAAM,GAAG,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,EAAE,CAAC;gBAC3D,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;gBACrB,IAAI,CAAC,CAAC;oBAAE,SAAS;gBACjB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;gBACxC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;gBAC3C,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC7B,CAAC;YACD,sFAAsF;YACtF,wFAAwF;YACxF,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC9D,MAAM,KAAK,GAAG,OAAO;qBAClB,GAAG,CAAC,KAAK,CAAE;qBACX,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACzF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC;oBAAE,SAAS;gBAC/C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;gBAC5C,IAAI,QAAQ,GAAG,CAAC,CAAC;gBACjB,IAAI,MAAM,GAAG,CAAC,CAAC;gBACf,IAAI,IAAI,GAAG,EAAE,CAAC;gBACd,IAAI,OAAO,GAAG,KAAK,CAAC;gBACpB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;oBACtB,IAAI,CAAC,CAAC,GAAG,GAAG,IAAI;wBAAE,SAAS;oBAC3B,IAAI,CAAC,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,EAAE,CAAC;wBACjD,IAAI,OAAO;4BAAE,MAAM;wBACnB,SAAS;oBACX,CAAC;oBACD,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC;oBACtD,IAAI,CAAC,CAAC,GAAG,KAAK,EAAE;wBAAE,KAAK,CAAC,KAAK,EAAE,CAAC;oBAChC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK;wBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBAChD,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;oBAC/B,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;oBAC3B,IAAI,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC;oBAClB,OAAO,GAAG,IAAI,CAAC;gBACjB,CAAC;gBACD,IAAI,CAAC,OAAO;oBAAE,SAAS;gBACvB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC;gBAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACjC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACvB,IAAI,CAAC;wBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;gBAC1C,CAAC;gBACD,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE;QAC/B,4FAA4F;QAC5F,KAAK,CAAC,OAAe,EAAE,KAAa;YAClC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,wEAAwE;QACxE,MAAM,CAAC,OAAe;YACpB,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { scopeOf } from \"../core/celld-schema.js\";\nimport { decodeLtx, encodeLtx, ltxFileName } from \"../ltx/ltx.js\";\nimport type { BackupInfo, CellBackup, CellDurability } from \"../ports.js\";\nimport { equal, pad16, pageSizeOf, toRollback } from \"./celld-format.js\";\nimport type { ObjectStore } from \"./object-store.js\";\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\n/**\n * THE LTX writer of cellb, in celld's bucket layout, on any ObjectStore:\n *\n * cells/<Class>:<idHex>/ltx/e<epoch>/<level>/<min>-<max>.ltx LTX v3 (src/ltx)\n * wake/entries/…, wake/retired/… celld's alarm wake index\n *\n * Each committed transaction becomes one LTX file in the cell's epoch — the first one of an\n * epoch a snapshot (TXID 1), then only the changed pages (from the SQLite WASM page hook, or a\n * diff of the image) — and the output gate waits for the PUT; a cell's commits are written\n * strictly in order. Restore: the newest epoch whose files form a chain from a snapshot.\n * `compact` makes save() replace an epoch's files with one snapshot file 1..N.\n *\n * Used by celldBucket (with celld's ownership protocol: begin()/forget() per acquisition) and by\n * filesApiLtxLog (a FilesApi, a fixed epoch).\n */\nexport interface LtxLogOptions {\n now?: () => number;\n /** Epoch used when a commit names none (no ownership). */\n defaultEpoch?: number;\n /** save() compacts an epoch into one snapshot file 1..N. */\n compact?: boolean;\n /** Checked before each write: false refuses it (this node's authority has lapsed). */\n authorized?: () => boolean;\n}\n\nconst range = (key: string) => {\n const m = /\\/ltx\\/e(\\d+)\\/\\d+\\/([0-9a-f]{16})-([0-9a-f]{16})\\.ltx$/.exec(key);\n return m\n ? { epoch: Number(m[1]), min: BigInt(`0x${m[2]}`), max: BigInt(`0x${m[3]}`) }\n : undefined;\n};\n\nexport function ltxLog(store: ObjectStore, o: LtxLogOptions = {}) {\n const now = o.now ?? (() => Date.now());\n // Per cell: the epoch it writes into, its last TXID and the pages last written.\n const cells = new Map<\n string,\n { epoch: number; txid: bigint; pages: Map<number, Uint8Array> | undefined }\n >();\n /** The highest TXID already written in a cell's epoch (0n: none). */\n const lastTxid = async (cellKey: string, epoch: number) => {\n let max = 0n;\n for (const key of await store.list(`cells/${scopeOf(cellKey)}/ltx/e${epoch}/`)) {\n const r = range(key);\n if (r && r.epoch === epoch && r.max > max) max = r.max;\n }\n return max;\n };\n\n // celld's wake index (logic/wake.rs, wake_format.rs): one immutable entry per alarm\n // installation, filed under the UTC minute it is due; a per-cell retirement certificate\n // marks older installations (and a cancelled one) as void.\n const hex16 = (n: number) => n.toString(16).padStart(16, \"0\");\n let wakeFormat: Promise<unknown> | undefined;\n const ensureWakeFormat = () =>\n (wakeFormat ??= store\n .put(\"wake/format.json\", enc.encode(JSON.stringify({ format: 2 })), { ifNoneMatch: \"*\" })\n .catch(() => {}));\n\n // Commits of one cell are logged one after the other, so TXIDs and the page mirror advance\n // in commit order. (Without this, two quick commits both saw TXID 0 and raced to PUT the\n // same snapshot key — the older image could land last; seen in Chromium against celld.)\n const queues = new Map<string, Promise<void>>();\n const inOrder = (cellKey: string, run: () => Promise<void>): Promise<void> => {\n const next = (queues.get(cellKey) ?? Promise.resolve()).catch(() => {}).then(run);\n queues.set(cellKey, next);\n next.then(\n () => queues.get(cellKey) === next && queues.delete(cellKey),\n () => queues.get(cellKey) === next && queues.delete(cellKey),\n );\n return next;\n };\n\n const durability: CellDurability &\n CellBackup & {\n logCommit: CellDurability[\"committed\"];\n } = {\n committed(cellKey, info) {\n return inOrder(cellKey, () => durability.logCommit(cellKey, info));\n },\n capturePages: true,\n\n async publishWake(cellKey, w) {\n await ensureWakeFormat();\n const cell = scopeOf(cellKey);\n const identity = `${hex16(w.epoch)}-${hex16(w.sequence)}`;\n // 1. the new installation's entry (none for a cancelled or consumed alarm)\n if (w.at != null) {\n const minute = new Date(w.at).toISOString().slice(0, 16);\n await store.put(\n `wake/entries/${minute}/${cell}/${identity}`,\n enc.encode(JSON.stringify({ cell, due_ms: w.at, format: 2, identity })),\n );\n }\n // 2. the retirement certificate, once the replacement is published (celld's order):\n // every older identity is void, and this one too when the alarm is gone.\n await store.put(\n `wake/retired/${cell}.json`,\n enc.encode(JSON.stringify({ format: 2, identity, at_ms: w.at })),\n );\n // 3. cleanup below the certificate (celld: only below a proved frontier, which this is)\n for (const k of await store.list(\"wake/entries/\")) {\n const m = /\\/([0-9a-f]{16}-[0-9a-f]{16})$/.exec(k);\n if (\n !m ||\n !k.includes(`/${cell}/`) ||\n k.slice(0, -m[0].length).endsWith(`/${cell}`) === false\n )\n continue;\n const id = m[1];\n if (id < identity || (id === identity && w.at == null))\n await store.delete?.(k).catch(() => {});\n }\n },\n\n async wakes() {\n const retired = new Map<string, { id: string; none: boolean }>();\n for (const k of await store.list(\"wake/retired/\")) {\n const o = await store.get(k);\n if (!o) continue;\n const r = JSON.parse(dec.decode(o.body)) as { identity: string; at_ms: number | null };\n retired.set(k.slice(\"wake/retired/\".length, -\".json\".length), {\n id: r.identity,\n none: r.at_ms == null,\n });\n }\n const out: { cellKey: string; className: string; at: number }[] = [];\n const latest = new Map<string, { id: string; at: number }>();\n for (const k of await store.list(\"wake/entries/\")) {\n const m = /^wake\\/entries\\/[^/]+\\/(.+)\\/([0-9a-f]{16}-[0-9a-f]{16})$/.exec(k);\n if (!m) continue;\n const [, cell, id] = m;\n if (cell.includes(\"/\")) continue; // facets wake through their root\n const r = retired.get(cell);\n if (r && (id < r.id || (id === r.id && r.none))) continue;\n const o = await store.get(k);\n if (!o) continue;\n const at = (JSON.parse(dec.decode(o.body)) as { due_ms: number }).due_ms;\n const prev = latest.get(cell);\n if (!prev || id > prev.id) latest.set(cell, { id, at });\n }\n for (const [cell, { at }] of latest) {\n const [className] = cell.split(\":\");\n out.push({ cellKey: cell.replace(\":\", \"/\"), className, at });\n }\n return out;\n },\n\n async logCommit(cellKey, info) {\n if (o.authorized && !o.authorized())\n throw new Error(\n `${cellKey}: this node's lease has lapsed; the write is refused (self-fencing)`,\n );\n let cell = cells.get(cellKey);\n if (!cell) {\n // First commit of this cell in this process: continue the epoch's TXIDs if it already\n // has files (a restart without a new epoch), with a whole-database file.\n const epoch = info.epoch ?? o.defaultEpoch ?? 1;\n cell = { epoch, txid: await lastTxid(cellKey, epoch), pages: undefined };\n cells.set(cellKey, cell);\n }\n // The page hook (SQLite WASM) hands over exactly the changed pages: no image needed.\n if (info.pages && cell.pages) {\n if (!info.pages.pages.size) return;\n const txid = cell.txid + 1n;\n await store.put(\n `cells/${scopeOf(cellKey)}/ltx/e${cell.epoch}/0000/${ltxFileName(txid, txid)}`,\n encodeLtx({\n header: {\n pageSize: info.pages.pageSize,\n commit: info.pages.pageCount,\n minTXID: txid,\n maxTXID: txid,\n timestamp: now(),\n preApplyChecksum: 0n,\n },\n pages: info.pages.pages,\n postApplyChecksum: 0n,\n }),\n );\n cell.txid = txid;\n for (const [p, d] of info.pages.pages) cell.pages.set(p, d.slice());\n for (const p of [...cell.pages.keys()]) if (p > info.pages.pageCount) cell.pages.delete(p);\n return;\n }\n const image = typeof info.image === \"function\" ? await info.image() : info.image;\n if (!image) return;\n const pageSize = pageSizeOf(image);\n const commit = image.length / pageSize;\n const page = (p: number) => image.subarray((p - 1) * pageSize, p * pageSize);\n const changed = new Map<number, Uint8Array>();\n for (let p = 1; p <= commit; p++) {\n const prev = cell.pages?.get(p);\n const cur = page(p);\n if (!prev || !equal(prev, cur)) changed.set(p, cur.slice());\n }\n if (cell.pages && changed.size === 0 && cell.pages.size === commit) return;\n const txid = cell.txid + 1n;\n const pages = cell.pages\n ? changed\n : new Map([...Array(commit)].map((_, i) => [i + 1, page(i + 1).slice()]));\n const file = encodeLtx({\n header: {\n pageSize,\n commit,\n minTXID: txid,\n maxTXID: txid,\n timestamp: now(),\n preApplyChecksum: 0n,\n },\n pages,\n postApplyChecksum: 0n, // celld 0.6.2 leaves the rolling checksums at 0\n });\n await store.put(\n `cells/${scopeOf(cellKey)}/ltx/e${cell.epoch}/0000/${ltxFileName(txid, txid)}`,\n file,\n );\n cell.txid = txid;\n cell.pages ??= new Map();\n for (const [p, d] of pages) cell.pages.set(p, d);\n for (const p of [...cell.pages.keys()]) if (p > commit) cell.pages.delete(p);\n },\n\n async save(cellKey: string, image: Uint8Array, _info: BackupInfo) {\n // Every transaction is already in the bucket; compaction (opt-in) folds an epoch into one\n // snapshot file 1..N — celld reads it as any snapshot.\n if (!o.compact) return;\n await inOrder(cellKey, async () => {\n const cell = cells.get(cellKey);\n const epoch = cell?.epoch ?? o.defaultEpoch ?? 1;\n const txid = cell?.txid ?? (await lastTxid(cellKey, epoch));\n if (!txid) return;\n const pageSize = pageSizeOf(image);\n const commit = image.length / pageSize;\n const pages = new Map(\n [...Array(commit)].map((_, n) => [n + 1, image.slice(n * pageSize, (n + 1) * pageSize)]),\n );\n const prefix = `cells/${scopeOf(cellKey)}/ltx/e${epoch}/`;\n const before = await store.list(prefix);\n const snapshot = `${prefix}0000/${ltxFileName(1n, txid)}`;\n await store.put(\n snapshot,\n encodeLtx({\n header: {\n pageSize,\n commit,\n minTXID: 1n,\n maxTXID: txid,\n timestamp: now(),\n preApplyChecksum: 0n,\n },\n pages,\n postApplyChecksum: 0n,\n }),\n );\n for (const key of before) {\n const r = range(key);\n if (key !== snapshot && r && r.max <= txid) await store.delete(key);\n }\n });\n },\n\n async load(cellKey: string) {\n const scope = scopeOf(cellKey);\n const byEpoch = new Map<number, { key: string; min: bigint; max: bigint }[]>();\n for (const key of await store.list(`cells/${scope}/ltx/e`)) {\n const r = range(key);\n if (!r) continue;\n const list = byEpoch.get(r.epoch) ?? [];\n list.push({ key, min: r.min, max: r.max });\n byEpoch.set(r.epoch, list);\n }\n // The newest epoch whose files chain from a snapshot (TXID 1): files in TXID order; a\n // snapshot restarts the image, a file already covered is skipped, a gap ends the chain.\n for (const epoch of [...byEpoch.keys()].sort((a, b) => b - a)) {\n const files = byEpoch\n .get(epoch)!\n .sort((a, b) => (a.max !== b.max ? (a.max < b.max ? -1 : 1) : a.min < b.min ? -1 : 1));\n if (!files.some((f) => f.min === 1n)) continue;\n const pages = new Map<number, Uint8Array>();\n let pageSize = 0;\n let commit = 0;\n let next = 1n;\n let started = false;\n for (const f of files) {\n if (f.max < next) continue;\n if (f.min !== 1n && (!started || f.min !== next)) {\n if (started) break;\n continue;\n }\n const ltx = decodeLtx((await store.get(f.key))!.body);\n if (f.min === 1n) pages.clear();\n for (const [p, d] of ltx.pages) pages.set(p, d);\n pageSize = ltx.header.pageSize;\n commit = ltx.header.commit;\n next = f.max + 1n;\n started = true;\n }\n if (!started) continue;\n const image = new Uint8Array(commit * pageSize);\n for (let p = 1; p <= commit; p++) {\n const d = pages.get(p);\n if (d) image.set(d, (p - 1) * pageSize);\n }\n return toRollback(image);\n }\n return undefined;\n },\n };\n\n return Object.assign(durability, {\n /** A new owner epoch for the cell: its next commit opens the generation with a snapshot. */\n begin(cellKey: string, epoch: number) {\n cells.set(cellKey, { epoch, txid: 0n, pages: undefined });\n },\n /** The cell is no longer ours (lease lost): forget its writer state. */\n forget(cellKey: string) {\n cells.delete(cellKey);\n },\n });\n}\n"]}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The bucket celld needs (docs/guarantees.md): conditional create, conditional overwrite,
3
+ * read-after-write, ranged reads, listing. One interface, several stores: memory, a webrun
4
+ * FilesApi (OPFS in browsers), celld dev's local `objects.sqlite3` (Node), and S3-compatible
5
+ * HTTP (SigV4 over fetch — works from a browser when the bucket allows CORS).
6
+ */
7
+ export interface StoredObject {
8
+ body: Uint8Array;
9
+ etag: string;
10
+ /** Content headers and user metadata, as the bucket keeps them beside the bytes. */
11
+ attrs?: ObjectAttributes;
12
+ /** Last modification, ms since the epoch (R2's `uploaded`). */
13
+ modifiedMs?: number;
14
+ }
15
+ /**
16
+ * What a bucket stores beside an object's bytes (celld's BlobAttributes): the five content
17
+ * headers, and flat user metadata (S3 `x-amz-meta-*`). celld's R2 record lives in the
18
+ * user-metadata entry `celld-r2`.
19
+ */
20
+ export interface ObjectAttributes {
21
+ contentType?: string;
22
+ contentLanguage?: string;
23
+ contentDisposition?: string;
24
+ contentEncoding?: string;
25
+ cacheControl?: string;
26
+ metadata?: Record<string, string>;
27
+ }
28
+ export interface PutConditions {
29
+ /** "*": create only (fail if the key exists). */
30
+ ifNoneMatch?: "*";
31
+ /** Overwrite only if the current object has this etag. */
32
+ ifMatch?: string;
33
+ }
34
+ export interface ObjectStore {
35
+ get(key: string): Promise<StoredObject | undefined>;
36
+ /** Returns the new etag; throws PreconditionFailedError when a condition fails. */
37
+ put(key: string, body: Uint8Array, cond?: PutConditions, attrs?: ObjectAttributes): Promise<string>;
38
+ /** Keys under a prefix, ascending. */
39
+ list(prefix: string): Promise<string[]>;
40
+ delete(key: string): Promise<void>;
41
+ }
42
+ export declare class PreconditionFailedError extends Error {
43
+ constructor(key: string);
44
+ }
45
+ export declare function memoryObjectStore(): ObjectStore;
46
+ /**
47
+ * A FilesApi as a bucket (OPFS, node fs, …). Conditions are checked then written — atomic
48
+ * only within one writer (a browser tab, or one process): for several writers use a store
49
+ * with real conditional writes (S3, celld dev's SQLite store).
50
+ */
51
+ export declare function filesApiObjectStore(files: import("@statewalker/webrun-files").FilesApi, root?: string): ObjectStore;
@@ -0,0 +1,114 @@
1
+ export class PreconditionFailedError extends Error {
2
+ constructor(key) {
3
+ super(`precondition failed for ${key}`);
4
+ this.name = "PreconditionFailedError";
5
+ }
6
+ }
7
+ const checkConditions = (key, cur, cond) => {
8
+ if (cond?.ifNoneMatch === "*" && cur)
9
+ throw new PreconditionFailedError(key);
10
+ if (cond?.ifMatch !== undefined && cur?.etag !== cond.ifMatch)
11
+ throw new PreconditionFailedError(key);
12
+ };
13
+ export function memoryObjectStore() {
14
+ const objects = new Map();
15
+ let n = 0;
16
+ return {
17
+ async get(key) {
18
+ return objects.get(key);
19
+ },
20
+ async put(key, body, cond, attrs) {
21
+ checkConditions(key, objects.get(key), cond);
22
+ const etag = String(++n);
23
+ objects.set(key, {
24
+ body: body.slice(),
25
+ etag,
26
+ attrs: attrs ? structuredClone(attrs) : undefined,
27
+ modifiedMs: Date.now(),
28
+ });
29
+ return etag;
30
+ },
31
+ async list(prefix) {
32
+ return [...objects.keys()].filter((k) => k.startsWith(prefix)).sort();
33
+ },
34
+ async delete(key) {
35
+ objects.delete(key);
36
+ },
37
+ };
38
+ }
39
+ /**
40
+ * A FilesApi as a bucket (OPFS, node fs, …). Conditions are checked then written — atomic
41
+ * only within one writer (a browser tab, or one process): for several writers use a store
42
+ * with real conditional writes (S3, celld dev's SQLite store).
43
+ */
44
+ export function filesApiObjectStore(files, root = "/bucket") {
45
+ const path = (key) => `${root}/${key}`;
46
+ const etagPath = (key) => `${root}/.etags/${key}`;
47
+ const attrsPath = (key) => `${root}/.attrs/${key}.json`;
48
+ const read = async (p) => {
49
+ if (!(await files.exists(p)))
50
+ return undefined;
51
+ const chunks = [];
52
+ for await (const c of files.read(p))
53
+ chunks.push(c);
54
+ const out = new Uint8Array(chunks.reduce((n, c) => n + c.byteLength, 0));
55
+ let o = 0;
56
+ for (const c of chunks) {
57
+ out.set(c, o);
58
+ o += c.byteLength;
59
+ }
60
+ return out;
61
+ };
62
+ let lock = Promise.resolve();
63
+ const serial = (fn) => {
64
+ const r = lock.then(fn, fn);
65
+ lock = r.catch(() => { });
66
+ return r;
67
+ };
68
+ return {
69
+ async get(key) {
70
+ const body = await read(path(key));
71
+ if (!body)
72
+ return undefined;
73
+ const side = await read(attrsPath(key));
74
+ const extra = side
75
+ ? JSON.parse(new TextDecoder().decode(side))
76
+ : {};
77
+ return {
78
+ body,
79
+ etag: new TextDecoder().decode((await read(etagPath(key))) ?? new Uint8Array()),
80
+ ...extra,
81
+ };
82
+ },
83
+ put(key, body, cond, attrs) {
84
+ return serial(async () => {
85
+ checkConditions(key, await this.get(key), cond);
86
+ const etag = `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;
87
+ await files.write(path(key), [body]);
88
+ await files.write(etagPath(key), [new TextEncoder().encode(etag)]);
89
+ await files.write(attrsPath(key), [
90
+ new TextEncoder().encode(JSON.stringify({ attrs, modifiedMs: Date.now() })),
91
+ ]);
92
+ return etag;
93
+ });
94
+ },
95
+ async list(prefix) {
96
+ const out = [];
97
+ if (!(await files.exists(root)))
98
+ return out;
99
+ for await (const f of files.list(root, { recursive: true }))
100
+ if (f.kind === "file") {
101
+ const key = f.path.slice(root.length + 1);
102
+ if (!key.startsWith(".etags/") && !key.startsWith(".attrs/") && key.startsWith(prefix))
103
+ out.push(key);
104
+ }
105
+ return out.sort();
106
+ },
107
+ async delete(key) {
108
+ await files.remove(path(key));
109
+ await files.remove(etagPath(key));
110
+ await files.remove(attrsPath(key));
111
+ },
112
+ };
113
+ }
114
+ //# sourceMappingURL=object-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"object-store.js","sourceRoot":"","sources":["../../src/bucket/object-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The bucket celld needs (docs/guarantees.md): conditional create, conditional overwrite,\n * read-after-write, ranged reads, listing. One interface, several stores: memory, a webrun\n * FilesApi (OPFS in browsers), celld dev's local `objects.sqlite3` (Node), and S3-compatible\n * HTTP (SigV4 over fetch — works from a browser when the bucket allows CORS).\n */\nexport interface StoredObject {\n body: Uint8Array;\n etag: string;\n /** Content headers and user metadata, as the bucket keeps them beside the bytes. */\n attrs?: ObjectAttributes;\n /** Last modification, ms since the epoch (R2's `uploaded`). */\n modifiedMs?: number;\n}\n\n/**\n * What a bucket stores beside an object's bytes (celld's BlobAttributes): the five content\n * headers, and flat user metadata (S3 `x-amz-meta-*`). celld's R2 record lives in the\n * user-metadata entry `celld-r2`.\n */\nexport interface ObjectAttributes {\n contentType?: string;\n contentLanguage?: string;\n contentDisposition?: string;\n contentEncoding?: string;\n cacheControl?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface PutConditions {\n /** \"*\": create only (fail if the key exists). */\n ifNoneMatch?: \"*\";\n /** Overwrite only if the current object has this etag. */\n ifMatch?: string;\n}\n\nexport interface ObjectStore {\n get(key: string): Promise<StoredObject | undefined>;\n /** Returns the new etag; throws PreconditionFailedError when a condition fails. */\n put(\n key: string,\n body: Uint8Array,\n cond?: PutConditions,\n attrs?: ObjectAttributes,\n ): Promise<string>;\n /** Keys under a prefix, ascending. */\n list(prefix: string): Promise<string[]>;\n delete(key: string): Promise<void>;\n}\n\nexport class PreconditionFailedError extends Error {\n constructor(key: string) {\n super(`precondition failed for ${key}`);\n this.name = \"PreconditionFailedError\";\n }\n}\n\nconst checkConditions = (key: string, cur: StoredObject | undefined, cond?: PutConditions) => {\n if (cond?.ifNoneMatch === \"*\" && cur) throw new PreconditionFailedError(key);\n if (cond?.ifMatch !== undefined && cur?.etag !== cond.ifMatch)\n throw new PreconditionFailedError(key);\n};\n\nexport function memoryObjectStore(): ObjectStore {\n const objects = new Map<string, StoredObject>();\n let n = 0;\n return {\n async get(key) {\n return objects.get(key);\n },\n async put(key, body, cond, attrs) {\n checkConditions(key, objects.get(key), cond);\n const etag = String(++n);\n objects.set(key, {\n body: body.slice(),\n etag,\n attrs: attrs ? structuredClone(attrs) : undefined,\n modifiedMs: Date.now(),\n });\n return etag;\n },\n async list(prefix) {\n return [...objects.keys()].filter((k) => k.startsWith(prefix)).sort();\n },\n async delete(key) {\n objects.delete(key);\n },\n };\n}\n\n/**\n * A FilesApi as a bucket (OPFS, node fs, …). Conditions are checked then written — atomic\n * only within one writer (a browser tab, or one process): for several writers use a store\n * with real conditional writes (S3, celld dev's SQLite store).\n */\nexport function filesApiObjectStore(\n files: import(\"@statewalker/webrun-files\").FilesApi,\n root = \"/bucket\",\n): ObjectStore {\n const path = (key: string) => `${root}/${key}`;\n const etagPath = (key: string) => `${root}/.etags/${key}`;\n const attrsPath = (key: string) => `${root}/.attrs/${key}.json`;\n const read = async (p: string) => {\n if (!(await files.exists(p))) return undefined;\n const chunks: Uint8Array[] = [];\n for await (const c of files.read(p)) chunks.push(c);\n const out = new Uint8Array(chunks.reduce((n, c) => n + c.byteLength, 0));\n let o = 0;\n for (const c of chunks) {\n out.set(c, o);\n o += c.byteLength;\n }\n return out;\n };\n let lock: Promise<unknown> = Promise.resolve();\n const serial = <T>(fn: () => Promise<T>): Promise<T> => {\n const r = lock.then(fn, fn);\n lock = r.catch(() => {});\n return r;\n };\n return {\n async get(key) {\n const body = await read(path(key));\n if (!body) return undefined;\n const side = await read(attrsPath(key));\n const extra = side\n ? (JSON.parse(new TextDecoder().decode(side)) as {\n attrs?: ObjectAttributes;\n modifiedMs?: number;\n })\n : {};\n return {\n body,\n etag: new TextDecoder().decode((await read(etagPath(key))) ?? new Uint8Array()),\n ...extra,\n };\n },\n put(key, body, cond, attrs) {\n return serial(async () => {\n checkConditions(key, await this.get(key), cond);\n const etag = `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;\n await files.write(path(key), [body]);\n await files.write(etagPath(key), [new TextEncoder().encode(etag)]);\n await files.write(attrsPath(key), [\n new TextEncoder().encode(JSON.stringify({ attrs, modifiedMs: Date.now() })),\n ]);\n return etag;\n });\n },\n async list(prefix) {\n const out: string[] = [];\n if (!(await files.exists(root))) return out;\n for await (const f of files.list(root, { recursive: true }))\n if (f.kind === \"file\") {\n const key = f.path.slice(root.length + 1);\n if (!key.startsWith(\".etags/\") && !key.startsWith(\".attrs/\") && key.startsWith(prefix))\n out.push(key);\n }\n return out.sort();\n },\n async delete(key) {\n await files.remove(path(key));\n await files.remove(etagPath(key));\n await files.remove(attrsPath(key));\n },\n };\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import type { ObjectStore } from "./object-store.js";
2
+ /**
3
+ * An S3-compatible bucket over fetch with AWS Signature V4 (WebCrypto): Amazon S3, R2,
4
+ * Tigris, MinIO… — the stores celld qualifies. Runs in browsers (the bucket needs a CORS
5
+ * rule exposing ETag and allowing If-Match / If-None-Match) and on Node. Path-style URLs:
6
+ * `${endpoint}/${bucket}/${key}`.
7
+ */
8
+ export interface S3Options {
9
+ endpoint: string;
10
+ bucket: string;
11
+ region?: string;
12
+ accessKeyId: string;
13
+ secretAccessKey: string;
14
+ sessionToken?: string;
15
+ /** Key prefix inside the bucket (celld's `CELLD_BUCKET` path part). */
16
+ prefix?: string;
17
+ /**
18
+ * The S3 endpoint the requests are signed for, when `endpoint` is a proxy in front of it
19
+ * (a browser page reaching a local S3 through its dev server): SigV4 covers host and path.
20
+ */
21
+ signAs?: string;
22
+ fetch?: typeof fetch;
23
+ }
24
+ export declare function signV4(method: string, url: URL, headers: Record<string, string>, body: Uint8Array | undefined, o: Pick<S3Options, "region" | "accessKeyId" | "secretAccessKey" | "sessionToken">, now?: Date): Promise<Record<string, string>>;
25
+ export declare function s3ObjectStore(o: S3Options): ObjectStore;