@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,151 @@
1
+ import { bytesToHex } from "@noble/hashes/utils";
2
+ import { PreconditionFailedError } from "./object-store.js";
3
+ const enc = new TextEncoder();
4
+ const sha256 = async (data) => bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", typeof data === "string" ? enc.encode(data) : data)));
5
+ const hmac = async (key, data) => {
6
+ const k = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
7
+ return new Uint8Array(await crypto.subtle.sign("HMAC", k, enc.encode(data)));
8
+ };
9
+ const uriEncode = (s, slash = true) => encodeURIComponent(s)
10
+ .replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)
11
+ .replace(slash ? /%2F/g : /$^/, "/");
12
+ export async function signV4(method, url, headers, body, o, now = new Date()) {
13
+ const region = o.region ?? "auto";
14
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
15
+ const day = amzDate.slice(0, 8);
16
+ const payloadHash = await sha256(body ?? new Uint8Array());
17
+ const h = {
18
+ ...Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v])),
19
+ host: url.host,
20
+ "x-amz-date": amzDate,
21
+ "x-amz-content-sha256": payloadHash,
22
+ };
23
+ if (o.sessionToken)
24
+ h["x-amz-security-token"] = o.sessionToken;
25
+ const names = Object.keys(h).sort();
26
+ const query = [...url.searchParams]
27
+ .map(([k, v]) => [uriEncode(k, false), uriEncode(v, false)])
28
+ .sort(([a, b], [c, d]) => (a === c ? (b < d ? -1 : 1) : a < c ? -1 : 1))
29
+ .map(([k, v]) => `${k}=${v}`)
30
+ .join("&");
31
+ const canonical = [
32
+ method,
33
+ uriEncode(decodeURIComponent(url.pathname)),
34
+ query,
35
+ ...names.map((n) => `${n}:${h[n].trim()}`),
36
+ "",
37
+ names.join(";"),
38
+ payloadHash,
39
+ ].join("\n");
40
+ const scope = `${day}/${region}/s3/aws4_request`;
41
+ const toSign = ["AWS4-HMAC-SHA256", amzDate, scope, await sha256(canonical)].join("\n");
42
+ let key = await hmac(enc.encode(`AWS4${o.secretAccessKey}`), day);
43
+ for (const part of [region, "s3", "aws4_request"])
44
+ key = await hmac(key, part);
45
+ const signature = bytesToHex(await hmac(key, toSign));
46
+ h.authorization = `AWS4-HMAC-SHA256 Credential=${o.accessKeyId}/${scope}, SignedHeaders=${names.join(";")}, Signature=${signature}`;
47
+ delete h.host; // set by fetch
48
+ return h;
49
+ }
50
+ export function s3ObjectStore(o) {
51
+ const f = o.fetch ?? fetch;
52
+ const base = `${o.endpoint.replace(/\/$/, "")}/${o.bucket}`;
53
+ const signBase = `${(o.signAs ?? o.endpoint).replace(/\/$/, "")}/${o.bucket}`;
54
+ const signed = (url) => (o.signAs ? new URL(signBase + url.href.slice(base.length)) : url);
55
+ // celld's `s3://BUCKET/PREFIX` puts keys under "PREFIX/": a prefix is a directory.
56
+ const root = o.prefix ? (o.prefix.endsWith("/") ? o.prefix : `${o.prefix}/`) : "";
57
+ const full = (key) => `${root}${key}`;
58
+ const call = async (method, url, headers = {}, body) => f(url, {
59
+ method,
60
+ headers: await signV4(method, signed(url), headers, body, o),
61
+ body: body,
62
+ });
63
+ return {
64
+ async get(key) {
65
+ const r = await call("GET", new URL(`${base}/${uriEncode(full(key))}`));
66
+ if (r.status === 404)
67
+ return undefined;
68
+ if (!r.ok)
69
+ throw new Error(`S3 GET ${key}: ${r.status} ${await r.text()}`);
70
+ const attrs = {};
71
+ for (const [field, header] of S3_HEADERS) {
72
+ const v = r.headers.get(header);
73
+ if (v !== null)
74
+ attrs[field] = v;
75
+ }
76
+ for (const [h, v] of r.headers)
77
+ if (h.startsWith("x-amz-meta-")) {
78
+ attrs.metadata = attrs.metadata ?? {};
79
+ attrs.metadata[h.slice("x-amz-meta-".length)] = v;
80
+ }
81
+ const modified = r.headers.get("last-modified");
82
+ return {
83
+ body: new Uint8Array(await r.arrayBuffer()),
84
+ etag: r.headers.get("etag") ?? "",
85
+ attrs,
86
+ modifiedMs: modified ? Date.parse(modified) : undefined,
87
+ };
88
+ },
89
+ async put(key, body, cond, attrs) {
90
+ const headers = {};
91
+ // Content headers as headers, user metadata as x-amz-meta-* (celld's R2 record is
92
+ // the ASCII-escaped JSON under x-amz-meta-celld-r2).
93
+ for (const [field, header] of S3_HEADERS)
94
+ if (attrs?.[field])
95
+ headers[header] = attrs[field];
96
+ for (const [name, value] of Object.entries(attrs?.metadata ?? {}))
97
+ headers[`x-amz-meta-${name.toLowerCase()}`] = value;
98
+ if (cond?.ifNoneMatch)
99
+ headers["if-none-match"] = "*";
100
+ if (cond?.ifMatch !== undefined)
101
+ headers["if-match"] = cond.ifMatch;
102
+ const r = await call("PUT", new URL(`${base}/${uriEncode(full(key))}`), headers, body);
103
+ // 409: a concurrent conditional write on some stores (R2, S3) — also a lost race.
104
+ if (r.status === 412 || r.status === 409)
105
+ throw new PreconditionFailedError(key);
106
+ if (!r.ok)
107
+ throw new Error(`S3 PUT ${key}: ${r.status} ${await r.text()}`);
108
+ return r.headers.get("etag") ?? "";
109
+ },
110
+ async list(prefix) {
111
+ const keys = [];
112
+ let token;
113
+ do {
114
+ const url = new URL(`${base}/`);
115
+ url.searchParams.set("list-type", "2");
116
+ url.searchParams.set("prefix", full(prefix));
117
+ if (token)
118
+ url.searchParams.set("continuation-token", token);
119
+ const r = await call("GET", url);
120
+ if (!r.ok)
121
+ throw new Error(`S3 LIST ${prefix}: ${r.status} ${await r.text()}`);
122
+ const xml = await r.text();
123
+ for (const m of xml.matchAll(/<Key>([^<]*)<\/Key>/g))
124
+ keys.push(unxml(m[1]).slice(root.length));
125
+ token = /<IsTruncated>true<\/IsTruncated>/.test(xml)
126
+ ? unxml(/<NextContinuationToken>([^<]*)<\/NextContinuationToken>/.exec(xml)?.[1] ?? "")
127
+ : undefined;
128
+ } while (token);
129
+ return keys.sort();
130
+ },
131
+ async delete(key) {
132
+ const r = await call("DELETE", new URL(`${base}/${uriEncode(full(key))}`));
133
+ if (!r.ok && r.status !== 404)
134
+ throw new Error(`S3 DELETE ${key}: ${r.status}`);
135
+ },
136
+ };
137
+ }
138
+ const unxml = (s) => s
139
+ .replace(/&lt;/g, "<")
140
+ .replace(/&gt;/g, ">")
141
+ .replace(/&quot;/g, '"')
142
+ .replace(/&apos;/g, "'")
143
+ .replace(/&amp;/g, "&");
144
+ const S3_HEADERS = [
145
+ ["contentType", "content-type"],
146
+ ["contentLanguage", "content-language"],
147
+ ["contentDisposition", "content-disposition"],
148
+ ["contentEncoding", "content-encoding"],
149
+ ["cacheControl", "cache-control"],
150
+ ];
151
+ //# sourceMappingURL=s3-object-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3-object-store.js","sourceRoot":"","sources":["../../src/bucket/s3-object-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from \"@noble/hashes/utils\";\nimport type { ObjectStore } from \"./object-store.js\";\nimport { type ObjectAttributes, PreconditionFailedError } from \"./object-store.js\";\n\n/**\n * An S3-compatible bucket over fetch with AWS Signature V4 (WebCrypto): Amazon S3, R2,\n * Tigris, MinIO… — the stores celld qualifies. Runs in browsers (the bucket needs a CORS\n * rule exposing ETag and allowing If-Match / If-None-Match) and on Node. Path-style URLs:\n * `${endpoint}/${bucket}/${key}`.\n */\nexport interface S3Options {\n endpoint: string;\n bucket: string;\n region?: string;\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n /** Key prefix inside the bucket (celld's `CELLD_BUCKET` path part). */\n prefix?: string;\n /**\n * The S3 endpoint the requests are signed for, when `endpoint` is a proxy in front of it\n * (a browser page reaching a local S3 through its dev server): SigV4 covers host and path.\n */\n signAs?: string;\n fetch?: typeof fetch;\n}\n\nconst enc = new TextEncoder();\nconst sha256 = async (data: Uint8Array | string) =>\n bytesToHex(\n new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n typeof data === \"string\" ? enc.encode(data) : (data as BufferSource),\n ),\n ),\n );\nconst hmac = async (key: Uint8Array | ArrayBuffer, data: string) => {\n const k = await crypto.subtle.importKey(\n \"raw\",\n key as BufferSource,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n return new Uint8Array(await crypto.subtle.sign(\"HMAC\", k, enc.encode(data)));\n};\nconst uriEncode = (s: string, slash = true) =>\n encodeURIComponent(s)\n .replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)\n .replace(slash ? /%2F/g : /$^/, \"/\");\n\nexport async function signV4(\n method: string,\n url: URL,\n headers: Record<string, string>,\n body: Uint8Array | undefined,\n o: Pick<S3Options, \"region\" | \"accessKeyId\" | \"secretAccessKey\" | \"sessionToken\">,\n now = new Date(),\n): Promise<Record<string, string>> {\n const region = o.region ?? \"auto\";\n const amzDate = now.toISOString().replace(/[:-]|\\.\\d{3}/g, \"\");\n const day = amzDate.slice(0, 8);\n const payloadHash = await sha256(body ?? new Uint8Array());\n const h: Record<string, string> = {\n ...Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v])),\n host: url.host,\n \"x-amz-date\": amzDate,\n \"x-amz-content-sha256\": payloadHash,\n };\n if (o.sessionToken) h[\"x-amz-security-token\"] = o.sessionToken;\n const names = Object.keys(h).sort();\n const query = [...url.searchParams]\n .map(([k, v]) => [uriEncode(k, false), uriEncode(v, false)])\n .sort(([a, b], [c, d]) => (a === c ? (b < d ? -1 : 1) : a < c ? -1 : 1))\n .map(([k, v]) => `${k}=${v}`)\n .join(\"&\");\n const canonical = [\n method,\n uriEncode(decodeURIComponent(url.pathname)),\n query,\n ...names.map((n) => `${n}:${h[n].trim()}`),\n \"\",\n names.join(\";\"),\n payloadHash,\n ].join(\"\\n\");\n const scope = `${day}/${region}/s3/aws4_request`;\n const toSign = [\"AWS4-HMAC-SHA256\", amzDate, scope, await sha256(canonical)].join(\"\\n\");\n let key: Uint8Array = await hmac(enc.encode(`AWS4${o.secretAccessKey}`), day);\n for (const part of [region, \"s3\", \"aws4_request\"]) key = await hmac(key, part);\n const signature = bytesToHex(await hmac(key, toSign));\n h.authorization = `AWS4-HMAC-SHA256 Credential=${o.accessKeyId}/${scope}, SignedHeaders=${names.join(\";\")}, Signature=${signature}`;\n delete h.host; // set by fetch\n return h;\n}\n\nexport function s3ObjectStore(o: S3Options): ObjectStore {\n const f = o.fetch ?? fetch;\n const base = `${o.endpoint.replace(/\\/$/, \"\")}/${o.bucket}`;\n const signBase = `${(o.signAs ?? o.endpoint).replace(/\\/$/, \"\")}/${o.bucket}`;\n const signed = (url: URL) => (o.signAs ? new URL(signBase + url.href.slice(base.length)) : url);\n // celld's `s3://BUCKET/PREFIX` puts keys under \"PREFIX/\": a prefix is a directory.\n const root = o.prefix ? (o.prefix.endsWith(\"/\") ? o.prefix : `${o.prefix}/`) : \"\";\n const full = (key: string) => `${root}${key}`;\n const call = async (\n method: string,\n url: URL,\n headers: Record<string, string> = {},\n body?: Uint8Array,\n ) =>\n f(url, {\n method,\n headers: await signV4(method, signed(url), headers, body, o),\n body: body as BodyInit | undefined,\n });\n return {\n async get(key) {\n const r = await call(\"GET\", new URL(`${base}/${uriEncode(full(key))}`));\n if (r.status === 404) return undefined;\n if (!r.ok) throw new Error(`S3 GET ${key}: ${r.status} ${await r.text()}`);\n const attrs: ObjectAttributes = {};\n for (const [field, header] of S3_HEADERS) {\n const v = r.headers.get(header);\n if (v !== null) attrs[field] = v;\n }\n for (const [h, v] of r.headers)\n if (h.startsWith(\"x-amz-meta-\")) {\n attrs.metadata = attrs.metadata ?? {};\n attrs.metadata[h.slice(\"x-amz-meta-\".length)] = v;\n }\n const modified = r.headers.get(\"last-modified\");\n return {\n body: new Uint8Array(await r.arrayBuffer()),\n etag: r.headers.get(\"etag\") ?? \"\",\n attrs,\n modifiedMs: modified ? Date.parse(modified) : undefined,\n };\n },\n async put(key, body, cond, attrs) {\n const headers: Record<string, string> = {};\n // Content headers as headers, user metadata as x-amz-meta-* (celld's R2 record is\n // the ASCII-escaped JSON under x-amz-meta-celld-r2).\n for (const [field, header] of S3_HEADERS)\n if (attrs?.[field]) headers[header] = attrs[field] as string;\n for (const [name, value] of Object.entries(attrs?.metadata ?? {}))\n headers[`x-amz-meta-${name.toLowerCase()}`] = value;\n if (cond?.ifNoneMatch) headers[\"if-none-match\"] = \"*\";\n if (cond?.ifMatch !== undefined) headers[\"if-match\"] = cond.ifMatch;\n const r = await call(\"PUT\", new URL(`${base}/${uriEncode(full(key))}`), headers, body);\n // 409: a concurrent conditional write on some stores (R2, S3) — also a lost race.\n if (r.status === 412 || r.status === 409) throw new PreconditionFailedError(key);\n if (!r.ok) throw new Error(`S3 PUT ${key}: ${r.status} ${await r.text()}`);\n return r.headers.get(\"etag\") ?? \"\";\n },\n async list(prefix) {\n const keys: string[] = [];\n let token: string | undefined;\n do {\n const url = new URL(`${base}/`);\n url.searchParams.set(\"list-type\", \"2\");\n url.searchParams.set(\"prefix\", full(prefix));\n if (token) url.searchParams.set(\"continuation-token\", token);\n const r = await call(\"GET\", url);\n if (!r.ok) throw new Error(`S3 LIST ${prefix}: ${r.status} ${await r.text()}`);\n const xml = await r.text();\n for (const m of xml.matchAll(/<Key>([^<]*)<\\/Key>/g))\n keys.push(unxml(m[1]).slice(root.length));\n token = /<IsTruncated>true<\\/IsTruncated>/.test(xml)\n ? unxml(/<NextContinuationToken>([^<]*)<\\/NextContinuationToken>/.exec(xml)?.[1] ?? \"\")\n : undefined;\n } while (token);\n return keys.sort();\n },\n async delete(key) {\n const r = await call(\"DELETE\", new URL(`${base}/${uriEncode(full(key))}`));\n if (!r.ok && r.status !== 404) throw new Error(`S3 DELETE ${key}: ${r.status}`);\n },\n };\n}\n\nconst unxml = (s: string) =>\n s\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, \"'\")\n .replace(/&amp;/g, \"&\");\n\nconst S3_HEADERS: [\n \"contentType\" | \"contentLanguage\" | \"contentDisposition\" | \"contentEncoding\" | \"cacheControl\",\n string,\n][] = [\n [\"contentType\", \"content-type\"],\n [\"contentLanguage\", \"content-language\"],\n [\"contentDisposition\", \"content-disposition\"],\n [\"contentEncoding\", \"content-encoding\"],\n [\"cacheControl\", \"cache-control\"],\n];\n"]}
@@ -0,0 +1,10 @@
1
+ import type { ObjectStore } from "./object-store.js";
2
+ /**
3
+ * celld dev's local bucket: `<project>/.celld/dev/objects.sqlite3` (objects(key, body, etag
4
+ * INTEGER, modified_ms, attributes) + store_sequence). Reading and writing it lets a
5
+ * cellb node and `celld dev` share one bucket, so cells move between them.
6
+ * Conditional writes are real (one SQLite transaction).
7
+ */
8
+ export declare function sqliteObjectStore(file: string): ObjectStore & {
9
+ close(): void;
10
+ };
@@ -0,0 +1,107 @@
1
+ import { createRequire } from "node:module";
2
+ import { PreconditionFailedError } from "./object-store.js";
3
+ const { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
4
+ /**
5
+ * celld dev's local bucket: `<project>/.celld/dev/objects.sqlite3` (objects(key, body, etag
6
+ * INTEGER, modified_ms, attributes) + store_sequence). Reading and writing it lets a
7
+ * cellb node and `celld dev` share one bucket, so cells move between them.
8
+ * Conditional writes are real (one SQLite transaction).
9
+ */
10
+ export function sqliteObjectStore(file) {
11
+ const db = new DatabaseSync(file);
12
+ db.exec(`CREATE TABLE IF NOT EXISTS objects (
13
+ key TEXT PRIMARY KEY, body BLOB NOT NULL, etag INTEGER NOT NULL,
14
+ modified_ms INTEGER NOT NULL, attributes TEXT NOT NULL)`);
15
+ db.exec(`CREATE TABLE IF NOT EXISTS store_sequence (
16
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1), next_etag INTEGER NOT NULL)`);
17
+ db.exec("INSERT OR IGNORE INTO store_sequence (singleton, next_etag) VALUES (1, 1)");
18
+ db.exec("PRAGMA busy_timeout = 5000");
19
+ return {
20
+ async get(key) {
21
+ const r = db
22
+ .prepare("SELECT body, etag, modified_ms, attributes FROM objects WHERE key = ?")
23
+ .get(key);
24
+ return r
25
+ ? {
26
+ body: new Uint8Array(r.body),
27
+ etag: String(r.etag),
28
+ modifiedMs: Number(r.modified_ms),
29
+ attrs: fromAttributes(r.attributes),
30
+ }
31
+ : undefined;
32
+ },
33
+ async put(key, body, cond, attrs) {
34
+ db.exec("BEGIN IMMEDIATE");
35
+ try {
36
+ const cur = db.prepare("SELECT etag FROM objects WHERE key = ?").get(key);
37
+ if (cond?.ifNoneMatch === "*" && cur)
38
+ throw new PreconditionFailedError(key);
39
+ if (cond?.ifMatch !== undefined && String(cur?.etag) !== cond.ifMatch)
40
+ throw new PreconditionFailedError(key);
41
+ const { next_etag } = db
42
+ .prepare("SELECT next_etag FROM store_sequence WHERE singleton = 1")
43
+ .get();
44
+ db.prepare("UPDATE store_sequence SET next_etag = ? WHERE singleton = 1").run(next_etag + 1);
45
+ db.prepare("INSERT OR REPLACE INTO objects (key, body, etag, modified_ms, attributes) VALUES (?, ?, ?, ?, ?)").run(key, body, next_etag, Date.now(), toAttributes(attrs));
46
+ db.exec("COMMIT");
47
+ return String(next_etag);
48
+ }
49
+ catch (e) {
50
+ db.exec("ROLLBACK");
51
+ throw e;
52
+ }
53
+ },
54
+ async list(prefix) {
55
+ return db
56
+ .prepare("SELECT key FROM objects WHERE key >= ? AND key < ? ORDER BY key")
57
+ .all(prefix, `${prefix}\uffff`).map((r) => r.key);
58
+ },
59
+ async delete(key) {
60
+ db.prepare("DELETE FROM objects WHERE key = ?").run(key);
61
+ },
62
+ close() {
63
+ db.close();
64
+ },
65
+ };
66
+ }
67
+ const HEADERS = [
68
+ ["contentType", "content-type"],
69
+ ["contentLanguage", "content-language"],
70
+ ["contentDisposition", "content-disposition"],
71
+ ["contentEncoding", "content-encoding"],
72
+ ["cacheControl", "cache-control"],
73
+ ];
74
+ function toAttributes(attrs) {
75
+ const out = [];
76
+ for (const [name, value] of Object.entries(attrs?.metadata ?? {}))
77
+ out.push({ kind: "metadata", name, value });
78
+ for (const [field, kind] of HEADERS) {
79
+ const v = attrs?.[field];
80
+ if (typeof v === "string")
81
+ out.push({ kind, name: null, value: v });
82
+ }
83
+ return JSON.stringify(out);
84
+ }
85
+ function fromAttributes(raw) {
86
+ let list;
87
+ try {
88
+ list = JSON.parse(raw);
89
+ }
90
+ catch {
91
+ return undefined;
92
+ }
93
+ if (!Array.isArray(list) || !list.length)
94
+ return undefined;
95
+ const attrs = {};
96
+ for (const e of list) {
97
+ if (e.kind === "metadata" && e.name) {
98
+ attrs.metadata = attrs.metadata ?? {};
99
+ attrs.metadata[e.name] = e.value;
100
+ }
101
+ const h = HEADERS.find(([, kind]) => kind === e.kind);
102
+ if (h)
103
+ attrs[h[0]] = e.value;
104
+ }
105
+ return attrs;
106
+ }
107
+ //# sourceMappingURL=sqlite-object-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-object-store.js","sourceRoot":"","sources":["../../src/bucket/sqlite-object-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { createRequire } from \"node:module\";\nimport type { ObjectAttributes, ObjectStore } from \"./object-store.js\";\nimport { PreconditionFailedError } from \"./object-store.js\";\n\nconst { DatabaseSync } = createRequire(import.meta.url)(\n \"node:sqlite\",\n) as typeof import(\"node:sqlite\");\n\n/**\n * celld dev's local bucket: `<project>/.celld/dev/objects.sqlite3` (objects(key, body, etag\n * INTEGER, modified_ms, attributes) + store_sequence). Reading and writing it lets a\n * cellb node and `celld dev` share one bucket, so cells move between them.\n * Conditional writes are real (one SQLite transaction).\n */\nexport function sqliteObjectStore(file: string): ObjectStore & { close(): void } {\n const db = new DatabaseSync(file);\n db.exec(`CREATE TABLE IF NOT EXISTS objects (\n key TEXT PRIMARY KEY, body BLOB NOT NULL, etag INTEGER NOT NULL,\n modified_ms INTEGER NOT NULL, attributes TEXT NOT NULL)`);\n db.exec(`CREATE TABLE IF NOT EXISTS store_sequence (\n singleton INTEGER PRIMARY KEY CHECK (singleton = 1), next_etag INTEGER NOT NULL)`);\n db.exec(\"INSERT OR IGNORE INTO store_sequence (singleton, next_etag) VALUES (1, 1)\");\n db.exec(\"PRAGMA busy_timeout = 5000\");\n return {\n async get(key) {\n const r = db\n .prepare(\"SELECT body, etag, modified_ms, attributes FROM objects WHERE key = ?\")\n .get(key) as\n | { body: Uint8Array; etag: number; modified_ms: number; attributes: string }\n | undefined;\n return r\n ? {\n body: new Uint8Array(r.body),\n etag: String(r.etag),\n modifiedMs: Number(r.modified_ms),\n attrs: fromAttributes(r.attributes),\n }\n : undefined;\n },\n async put(key, body, cond, attrs) {\n db.exec(\"BEGIN IMMEDIATE\");\n try {\n const cur = db.prepare(\"SELECT etag FROM objects WHERE key = ?\").get(key) as\n | { etag: number }\n | undefined;\n if (cond?.ifNoneMatch === \"*\" && cur) throw new PreconditionFailedError(key);\n if (cond?.ifMatch !== undefined && String(cur?.etag) !== cond.ifMatch)\n throw new PreconditionFailedError(key);\n const { next_etag } = db\n .prepare(\"SELECT next_etag FROM store_sequence WHERE singleton = 1\")\n .get() as {\n next_etag: number;\n };\n db.prepare(\"UPDATE store_sequence SET next_etag = ? WHERE singleton = 1\").run(\n next_etag + 1,\n );\n db.prepare(\n \"INSERT OR REPLACE INTO objects (key, body, etag, modified_ms, attributes) VALUES (?, ?, ?, ?, ?)\",\n ).run(key, body, next_etag, Date.now(), toAttributes(attrs));\n db.exec(\"COMMIT\");\n return String(next_etag);\n } catch (e) {\n db.exec(\"ROLLBACK\");\n throw e;\n }\n },\n async list(prefix) {\n return (\n db\n .prepare(\"SELECT key FROM objects WHERE key >= ? AND key < ? ORDER BY key\")\n .all(prefix, `${prefix}\\uffff`) as {\n key: string;\n }[]\n ).map((r) => r.key);\n },\n async delete(key) {\n db.prepare(\"DELETE FROM objects WHERE key = ?\").run(key);\n },\n close() {\n db.close();\n },\n };\n}\n\n// celld dev's `attributes` column: a JSON array of {kind, name, value} — \"metadata\" entries\n// (user metadata, name = key) and one entry per content header (\"content-type\", …).\ntype HeaderField =\n | \"contentType\"\n | \"contentLanguage\"\n | \"contentDisposition\"\n | \"contentEncoding\"\n | \"cacheControl\";\nconst HEADERS: [HeaderField, string][] = [\n [\"contentType\", \"content-type\"],\n [\"contentLanguage\", \"content-language\"],\n [\"contentDisposition\", \"content-disposition\"],\n [\"contentEncoding\", \"content-encoding\"],\n [\"cacheControl\", \"cache-control\"],\n];\nfunction toAttributes(attrs?: ObjectAttributes): string {\n const out: { kind: string; name: string | null; value: string }[] = [];\n for (const [name, value] of Object.entries(attrs?.metadata ?? {}))\n out.push({ kind: \"metadata\", name, value });\n for (const [field, kind] of HEADERS) {\n const v = attrs?.[field];\n if (typeof v === \"string\") out.push({ kind, name: null, value: v });\n }\n return JSON.stringify(out);\n}\nfunction fromAttributes(raw: string): ObjectAttributes | undefined {\n let list: { kind: string; name: string | null; value: string }[];\n try {\n list = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!Array.isArray(list) || !list.length) return undefined;\n const attrs: ObjectAttributes = {};\n for (const e of list) {\n if (e.kind === \"metadata\" && e.name) {\n attrs.metadata = attrs.metadata ?? {};\n attrs.metadata[e.name] = e.value;\n }\n const h = HEADERS.find(([, kind]) => kind === e.kind);\n if (h) (attrs as Record<string, unknown>)[h[0]] = e.value;\n }\n return attrs;\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import type { Clock, SyncSqlDriver } from "../ports.js";
2
+ export interface AlarmEntry {
3
+ cellKey: string;
4
+ className: string;
5
+ idHex: string;
6
+ at: number;
7
+ }
8
+ /**
9
+ * Runtime-wide alarm scheduler. Every object's alarm lives in its own `_cf_METADATA`
10
+ * (the truth); this index — one small SQLite database next to the cells — lets a
11
+ * restarted runtime wake objects whose alarms are due without anyone touching them
12
+ * (catch-up after a crash, a closed tab or a frozen page).
13
+ */
14
+ export declare class AlarmScheduler {
15
+ #private;
16
+ constructor(opts: {
17
+ clock: Clock;
18
+ openIndex?: () => SyncSqlDriver;
19
+ fire: (e: AlarmEntry) => Promise<void>;
20
+ });
21
+ /** Re-arms every alarm recorded in the index (due ones fire at once). */
22
+ restore(): void;
23
+ set(entry: Omit<AlarmEntry, "at">, at: number | null): void;
24
+ stop(): void;
25
+ /** Closes the index database (after the objects that record into it have closed). */
26
+ close(): void;
27
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Runtime-wide alarm scheduler. Every object's alarm lives in its own `_cf_METADATA`
3
+ * (the truth); this index — one small SQLite database next to the cells — lets a
4
+ * restarted runtime wake objects whose alarms are due without anyone touching them
5
+ * (catch-up after a crash, a closed tab or a frozen page).
6
+ */
7
+ export class AlarmScheduler {
8
+ #timers = new Map();
9
+ #clock;
10
+ #openIndex;
11
+ #indexDb;
12
+ #fire;
13
+ constructor(opts) {
14
+ this.#clock = opts.clock;
15
+ this.#openIndex = opts.openIndex;
16
+ this.#fire = opts.fire;
17
+ }
18
+ /**
19
+ * The index is opened lazily and is best effort: if it cannot be opened (e.g. another
20
+ * process holds it in EXCLUSIVE locking mode) alarms still fire for live objects; only
21
+ * the catch-up after a restart is lost for this runtime.
22
+ */
23
+ get #index() {
24
+ if (this.#indexDb === undefined) {
25
+ try {
26
+ const db = this.#openIndex?.() ?? null;
27
+ db?.exec("CREATE TABLE IF NOT EXISTS alarms (cell_key TEXT PRIMARY KEY, class_name TEXT, id_hex TEXT, at INTEGER)", []);
28
+ this.#indexDb = db;
29
+ }
30
+ catch {
31
+ this.#indexDb = null;
32
+ }
33
+ }
34
+ return this.#indexDb ?? undefined;
35
+ }
36
+ /** Re-arms every alarm recorded in the index (due ones fire at once). */
37
+ restore() {
38
+ for (const [cellKey, className, idHex, at] of this.#index?.exec("SELECT * FROM alarms", [])
39
+ .rows ?? [])
40
+ this.#arm({
41
+ cellKey: String(cellKey),
42
+ className: String(className),
43
+ idHex: String(idHex),
44
+ at: Number(at),
45
+ });
46
+ }
47
+ set(entry, at) {
48
+ this.#clear(entry.cellKey);
49
+ if (at === null) {
50
+ this.#index?.exec("DELETE FROM alarms WHERE cell_key = ?", [entry.cellKey]);
51
+ return;
52
+ }
53
+ this.#index?.exec("INSERT OR REPLACE INTO alarms VALUES (?, ?, ?, ?)", [
54
+ entry.cellKey,
55
+ entry.className,
56
+ entry.idHex,
57
+ at,
58
+ ]);
59
+ this.#arm({ ...entry, at });
60
+ }
61
+ #arm(e) {
62
+ this.#clear(e.cellKey);
63
+ const delay = Math.max(0, e.at - this.#clock.now());
64
+ this.#timers.set(e.cellKey, this.#clock.setTimeout(() => {
65
+ this.#timers.delete(e.cellKey);
66
+ void this.#fire(e);
67
+ }, delay));
68
+ }
69
+ #clear(cellKey) {
70
+ const t = this.#timers.get(cellKey);
71
+ if (t !== undefined)
72
+ this.#clock.clearTimeout(t);
73
+ this.#timers.delete(cellKey);
74
+ }
75
+ stop() {
76
+ for (const t of this.#timers.values())
77
+ this.#clock.clearTimeout(t);
78
+ this.#timers.clear();
79
+ }
80
+ /** Closes the index database (after the objects that record into it have closed). */
81
+ close() {
82
+ this.stop();
83
+ const db = this.#indexDb;
84
+ this.#indexDb = null; // stays closed: later records are dropped
85
+ db?.close();
86
+ }
87
+ }
88
+ //# sourceMappingURL=alarms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alarms.js","sourceRoot":"","sources":["../../src/core/alarms.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Clock, SyncSqlDriver } from \"../ports.js\";\n\nexport interface AlarmEntry {\n cellKey: string;\n className: string;\n idHex: string;\n at: number;\n}\n\n/**\n * Runtime-wide alarm scheduler. Every object's alarm lives in its own `_cf_METADATA`\n * (the truth); this index — one small SQLite database next to the cells — lets a\n * restarted runtime wake objects whose alarms are due without anyone touching them\n * (catch-up after a crash, a closed tab or a frozen page).\n */\nexport class AlarmScheduler {\n readonly #timers = new Map<string, unknown>();\n readonly #clock: Clock;\n readonly #openIndex?: () => SyncSqlDriver;\n #indexDb: SyncSqlDriver | null | undefined;\n readonly #fire: (e: AlarmEntry) => Promise<void>;\n\n constructor(opts: {\n clock: Clock;\n openIndex?: () => SyncSqlDriver;\n fire: (e: AlarmEntry) => Promise<void>;\n }) {\n this.#clock = opts.clock;\n this.#openIndex = opts.openIndex;\n this.#fire = opts.fire;\n }\n\n /**\n * The index is opened lazily and is best effort: if it cannot be opened (e.g. another\n * process holds it in EXCLUSIVE locking mode) alarms still fire for live objects; only\n * the catch-up after a restart is lost for this runtime.\n */\n get #index(): SyncSqlDriver | undefined {\n if (this.#indexDb === undefined) {\n try {\n const db = this.#openIndex?.() ?? null;\n db?.exec(\n \"CREATE TABLE IF NOT EXISTS alarms (cell_key TEXT PRIMARY KEY, class_name TEXT, id_hex TEXT, at INTEGER)\",\n [],\n );\n this.#indexDb = db;\n } catch {\n this.#indexDb = null;\n }\n }\n return this.#indexDb ?? undefined;\n }\n\n /** Re-arms every alarm recorded in the index (due ones fire at once). */\n restore(): void {\n for (const [cellKey, className, idHex, at] of this.#index?.exec(\"SELECT * FROM alarms\", [])\n .rows ?? [])\n this.#arm({\n cellKey: String(cellKey),\n className: String(className),\n idHex: String(idHex),\n at: Number(at),\n });\n }\n\n set(entry: Omit<AlarmEntry, \"at\">, at: number | null): void {\n this.#clear(entry.cellKey);\n if (at === null) {\n this.#index?.exec(\"DELETE FROM alarms WHERE cell_key = ?\", [entry.cellKey]);\n return;\n }\n this.#index?.exec(\"INSERT OR REPLACE INTO alarms VALUES (?, ?, ?, ?)\", [\n entry.cellKey,\n entry.className,\n entry.idHex,\n at,\n ]);\n this.#arm({ ...entry, at });\n }\n\n #arm(e: AlarmEntry): void {\n this.#clear(e.cellKey);\n const delay = Math.max(0, e.at - this.#clock.now());\n this.#timers.set(\n e.cellKey,\n this.#clock.setTimeout(() => {\n this.#timers.delete(e.cellKey);\n void this.#fire(e);\n }, delay),\n );\n }\n\n #clear(cellKey: string): void {\n const t = this.#timers.get(cellKey);\n if (t !== undefined) this.#clock.clearTimeout(t);\n this.#timers.delete(cellKey);\n }\n\n stop(): void {\n for (const t of this.#timers.values()) this.#clock.clearTimeout(t);\n this.#timers.clear();\n }\n\n /** Closes the index database (after the objects that record into it have closed). */\n close(): void {\n this.stop();\n const db = this.#indexDb;\n this.#indexDb = null; // stays closed: later records are dropped\n db?.close();\n }\n}\n"]}
@@ -0,0 +1,34 @@
1
+ /** Runs `fn` in an async context captured earlier. */
2
+ export type AsyncRoot = <R>(fn: () => R) => R;
3
+ type Fn<R = unknown> = (...args: any[]) => R;
4
+ /** TC39 AsyncContext (Stage 2), the part cellb uses. */
5
+ export interface NativeAsyncContext {
6
+ Variable: new <T>(options?: {
7
+ name?: string;
8
+ defaultValue?: T;
9
+ }) => {
10
+ get(): T | undefined;
11
+ run<R>(value: T, fn: Fn<R>, ...args: unknown[]): R;
12
+ };
13
+ Snapshot: {
14
+ new (): {
15
+ run<R>(fn: Fn<R>, ...args: unknown[]): R;
16
+ };
17
+ wrap<F extends Fn>(fn: F): F;
18
+ };
19
+ }
20
+ /**
21
+ * The engine's AsyncContext, when the realm has one: async context then follows every `await`
22
+ * natively, as in workerd, celld and Node, and code needs no lowering.
23
+ */
24
+ export declare function nativeAsyncContext(): NativeAsyncContext | undefined;
25
+ /**
26
+ * The async context current now, to start events in: on Cloudflare a call to a Durable Object is
27
+ * a new event, which sees no `AsyncLocalStorage` store its caller set. On Node it is every store
28
+ * (`AsyncLocalStorage.snapshot()`); in a browser with AsyncContext, an `AsyncContext.Snapshot`;
29
+ * in other browsers, the `node:async_hooks` polyfill's frame (a realm's polyfill copies share
30
+ * it), or its root frame if the polyfill loads later. Elsewhere events run in their caller's
31
+ * context, as before.
32
+ */
33
+ export declare function asyncRoot(): AsyncRoot;
34
+ export {};
@@ -0,0 +1,50 @@
1
+ const POLYFILL_STATE = Symbol.for("cellb.asyncContext.state");
2
+ /**
3
+ * The engine's AsyncContext, when the realm has one: async context then follows every `await`
4
+ * natively, as in workerd, celld and Node, and code needs no lowering.
5
+ */
6
+ export function nativeAsyncContext() {
7
+ const ac = globalThis.AsyncContext;
8
+ return typeof ac?.Variable === "function" && typeof ac.Snapshot === "function"
9
+ ? ac
10
+ : undefined;
11
+ }
12
+ /**
13
+ * The async context current now, to start events in: on Cloudflare a call to a Durable Object is
14
+ * a new event, which sees no `AsyncLocalStorage` store its caller set. On Node it is every store
15
+ * (`AsyncLocalStorage.snapshot()`); in a browser with AsyncContext, an `AsyncContext.Snapshot`;
16
+ * in other browsers, the `node:async_hooks` polyfill's frame (a realm's polyfill copies share
17
+ * it), or its root frame if the polyfill loads later. Elsewhere events run in their caller's
18
+ * context, as before.
19
+ */
20
+ export function asyncRoot() {
21
+ const hooks = globalThis.process?.getBuiltinModule?.("node:async_hooks");
22
+ const snapshot = hooks?.AsyncLocalStorage?.snapshot?.();
23
+ const Resource = hooks?.AsyncResource;
24
+ // Each event in a resource of its own, made in the snapshot: it inherits the snapshot's stores,
25
+ // and a store an event sets (`run` writes it on the current resource, before Node's
26
+ // AsyncContextFrame) stays its own instead of reaching the next event started in the snapshot.
27
+ if (snapshot && Resource)
28
+ return (fn) => snapshot(() => new Resource("cellb.event").runInAsyncScope(fn));
29
+ const native = nativeAsyncContext();
30
+ if (native) {
31
+ const captured = new native.Snapshot();
32
+ return (fn) => captured.run(fn);
33
+ }
34
+ const realm = globalThis;
35
+ const captured = realm[POLYFILL_STATE]?.current;
36
+ return (fn) => {
37
+ const state = realm[POLYFILL_STATE];
38
+ if (!state)
39
+ return fn();
40
+ const prev = state.current;
41
+ state.current = captured ?? state.root;
42
+ try {
43
+ return fn();
44
+ }
45
+ finally {
46
+ state.current = prev;
47
+ }
48
+ };
49
+ }
50
+ //# sourceMappingURL=async-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context.js","sourceRoot":"","sources":["../../src/core/async-context.ts"],"names":[],"mappings":"AAGA,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;AAoB9D;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,EAAE,GAAI,UAA6D,CAAC,YAAY,CAAC;IACvF,OAAO,OAAO,EAAE,EAAE,QAAQ,KAAK,UAAU,IAAI,OAAO,EAAE,CAAC,QAAQ,KAAK,UAAU;QAC5E,CAAC,CAAE,EAAyB;QAC5B,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,KAAK,GACT,UAGD,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC,kBAAkB,CAKnC,CAAC;IACd,MAAM,QAAQ,GAAG,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,CAAC;IACxD,MAAM,QAAQ,GAAG,KAAK,EAAE,aAAa,CAAC;IACtC,gGAAgG;IAChG,oFAAoF;IACpF,+FAA+F;IAC/F,IAAI,QAAQ,IAAI,QAAQ;QACtB,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,QAAQ,CAAC,aAAa,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC;IACjF,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAC;IACpC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACvC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;IAED,MAAM,KAAK,GAAG,UAA+C,CAAC;IAC9D,MAAM,QAAQ,GAAG,KAAK,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAChD,OAAO,CAAC,EAAE,EAAE,EAAE;QACZ,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC;QAC3B,KAAK,CAAC,OAAO,GAAG,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC;QACvC,IAAI,CAAC;YACH,OAAO,EAAE,EAAE,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACvB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC","sourcesContent":["/** Runs `fn` in an async context captured earlier. */\nexport type AsyncRoot = <R>(fn: () => R) => R;\n\nconst POLYFILL_STATE = Symbol.for(\"cellb.asyncContext.state\");\n\n// biome-ignore lint/suspicious/noExplicitAny: any function, called with its own arguments\ntype Fn<R = unknown> = (...args: any[]) => R;\n\n/** TC39 AsyncContext (Stage 2), the part cellb uses. */\nexport interface NativeAsyncContext {\n Variable: new <T>(options?: {\n name?: string;\n defaultValue?: T;\n }) => {\n get(): T | undefined;\n run<R>(value: T, fn: Fn<R>, ...args: unknown[]): R;\n };\n Snapshot: {\n new (): { run<R>(fn: Fn<R>, ...args: unknown[]): R };\n wrap<F extends Fn>(fn: F): F;\n };\n}\n\n/**\n * The engine's AsyncContext, when the realm has one: async context then follows every `await`\n * natively, as in workerd, celld and Node, and code needs no lowering.\n */\nexport function nativeAsyncContext(): NativeAsyncContext | undefined {\n const ac = (globalThis as { AsyncContext?: Partial<NativeAsyncContext> }).AsyncContext;\n return typeof ac?.Variable === \"function\" && typeof ac.Snapshot === \"function\"\n ? (ac as NativeAsyncContext)\n : undefined;\n}\n\n/**\n * The async context current now, to start events in: on Cloudflare a call to a Durable Object is\n * a new event, which sees no `AsyncLocalStorage` store its caller set. On Node it is every store\n * (`AsyncLocalStorage.snapshot()`); in a browser with AsyncContext, an `AsyncContext.Snapshot`;\n * in other browsers, the `node:async_hooks` polyfill's frame (a realm's polyfill copies share\n * it), or its root frame if the polyfill loads later. Elsewhere events run in their caller's\n * context, as before.\n */\nexport function asyncRoot(): AsyncRoot {\n const hooks = (\n globalThis as {\n process?: { getBuiltinModule?(id: string): unknown };\n }\n ).process?.getBuiltinModule?.(\"node:async_hooks\") as\n | {\n AsyncLocalStorage?: { snapshot?(): <R>(fn: () => R) => R };\n AsyncResource?: new (type: string) => { runInAsyncScope<R>(fn: () => R): R };\n }\n | undefined;\n const snapshot = hooks?.AsyncLocalStorage?.snapshot?.();\n const Resource = hooks?.AsyncResource;\n // Each event in a resource of its own, made in the snapshot: it inherits the snapshot's stores,\n // and a store an event sets (`run` writes it on the current resource, before Node's\n // AsyncContextFrame) stays its own instead of reaching the next event started in the snapshot.\n if (snapshot && Resource)\n return (fn) => snapshot(() => new Resource(\"cellb.event\").runInAsyncScope(fn));\n const native = nativeAsyncContext();\n if (native) {\n const captured = new native.Snapshot();\n return (fn) => captured.run(fn);\n }\n type State = { root: unknown; current: unknown };\n const realm = globalThis as Record<symbol, State | undefined>;\n const captured = realm[POLYFILL_STATE]?.current;\n return (fn) => {\n const state = realm[POLYFILL_STATE];\n if (!state) return fn();\n const prev = state.current;\n state.current = captured ?? state.root;\n try {\n return fn();\n } finally {\n state.current = prev;\n }\n };\n}\n"]}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * celld's internal tables, statement for statement (read from a database celld 0.6.2
3
+ * wrote): Durable Object KV in `_cf_KV(scope, k, v)` with V8-serialized values, the alarm in
4
+ * `_cf_ALARM`, the object's name in `_cf_METADATA(scope, actor_name)`, facets and wake-up
5
+ * bookkeeping. cellb uses the same tables, so a cell's database (and its LTX log)
6
+ * reads the same in both. `scope` is celld's cell name, `<Class>:<id hex>`.
7
+ */
8
+ export declare const CELLD_SCHEMA: string[];
9
+ /** cellb's own bookkeeping (not in celld): the transaction-log sequence number. */
10
+ export declare const LOG_TABLE = "CREATE TABLE IF NOT EXISTS _cf_LOG (id INTEGER PRIMARY KEY CHECK (id = 1), seq INTEGER NOT NULL)";
11
+ export declare const LOG_SEQ_READ = "SELECT seq FROM _cf_LOG WHERE id = 1";
12
+ export declare const LOG_SEQ_WRITE = "INSERT OR REPLACE INTO _cf_LOG (id, seq) VALUES (1, ?)";
13
+ /** celld's cell name for a cellb cell key ("Class/idhex" → "Class:idhex"). */
14
+ export declare const scopeOf: (cellKey: string) => string;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * celld's internal tables, statement for statement (read from a database celld 0.6.2
3
+ * wrote): Durable Object KV in `_cf_KV(scope, k, v)` with V8-serialized values, the alarm in
4
+ * `_cf_ALARM`, the object's name in `_cf_METADATA(scope, actor_name)`, facets and wake-up
5
+ * bookkeeping. cellb uses the same tables, so a cell's database (and its LTX log)
6
+ * reads the same in both. `scope` is celld's cell name, `<Class>:<id hex>`.
7
+ */
8
+ export const CELLD_SCHEMA = [
9
+ "CREATE TABLE IF NOT EXISTS _cf_KV (scope TEXT, k TEXT, v TEXT, PRIMARY KEY(scope,k))",
10
+ "CREATE TABLE IF NOT EXISTS _cf_ALARM (scope TEXT PRIMARY KEY, at_ms INTEGER, retry INTEGER DEFAULT 0, counted_retry INTEGER NOT NULL DEFAULT 0, generation INTEGER NOT NULL DEFAULT 0)",
11
+ "CREATE TABLE IF NOT EXISTS _cf_METADATA (scope TEXT PRIMARY KEY, actor_name TEXT)",
12
+ "CREATE TABLE IF NOT EXISTS _cf_FACETS (scope TEXT NOT NULL, path TEXT NOT NULL, image BLOB NOT NULL, PRIMARY KEY(scope,path)) WITHOUT ROWID",
13
+ `CREATE TABLE IF NOT EXISTS _cf_WAKE (
14
+ scope TEXT PRIMARY KEY, epoch TEXT NOT NULL CHECK(length(epoch)=16),
15
+ sequence INTEGER NOT NULL CHECK(typeof(sequence)='integer' AND sequence>=0),
16
+ at_ms INTEGER
17
+ )`,
18
+ `CREATE TRIGGER IF NOT EXISTS _cf_WAKE_insert AFTER INSERT ON _cf_ALARM BEGIN
19
+ UPDATE _cf_WAKE SET sequence=sequence+1,at_ms=NEW.at_ms WHERE scope=NEW.scope;
20
+ END`,
21
+ `CREATE TRIGGER IF NOT EXISTS _cf_WAKE_update AFTER UPDATE OF at_ms,generation ON _cf_ALARM BEGIN
22
+ UPDATE _cf_WAKE SET sequence=sequence+1,at_ms=NEW.at_ms WHERE scope=NEW.scope;
23
+ END`,
24
+ `CREATE TRIGGER IF NOT EXISTS _cf_WAKE_delete AFTER DELETE ON _cf_ALARM BEGIN
25
+ UPDATE _cf_WAKE SET sequence=sequence+1,at_ms=NULL WHERE scope=OLD.scope;
26
+ END`,
27
+ ];
28
+ /** cellb's own bookkeeping (not in celld): the transaction-log sequence number. */
29
+ export const LOG_TABLE = "CREATE TABLE IF NOT EXISTS _cf_LOG (id INTEGER PRIMARY KEY CHECK (id = 1), seq INTEGER NOT NULL)";
30
+ export const LOG_SEQ_READ = "SELECT seq FROM _cf_LOG WHERE id = 1";
31
+ export const LOG_SEQ_WRITE = "INSERT OR REPLACE INTO _cf_LOG (id, seq) VALUES (1, ?)";
32
+ /** celld's cell name for a cellb cell key ("Class/idhex" → "Class:idhex"). */
33
+ export const scopeOf = (cellKey) => cellKey.replace("/", ":");
34
+ //# sourceMappingURL=celld-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"celld-schema.js","sourceRoot":"","sources":["../../src/core/celld-schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,sFAAsF;IACtF,wLAAwL;IACxL,mFAAmF;IACnF,6IAA6I;IAC7I;;;;WAIS;IACT;;iBAEe;IACf;;iBAEe;IACf;;iBAEe;CAChB,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,SAAS,GACpB,kGAAkG,CAAC;AACrG,MAAM,CAAC,MAAM,YAAY,GAAG,sCAAsC,CAAC;AACnE,MAAM,CAAC,MAAM,aAAa,GAAG,wDAAwD,CAAC;AAEtF,8EAA8E;AAC9E,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC","sourcesContent":["/**\n * celld's internal tables, statement for statement (read from a database celld 0.6.2\n * wrote): Durable Object KV in `_cf_KV(scope, k, v)` with V8-serialized values, the alarm in\n * `_cf_ALARM`, the object's name in `_cf_METADATA(scope, actor_name)`, facets and wake-up\n * bookkeeping. cellb uses the same tables, so a cell's database (and its LTX log)\n * reads the same in both. `scope` is celld's cell name, `<Class>:<id hex>`.\n */\nexport const CELLD_SCHEMA = [\n \"CREATE TABLE IF NOT EXISTS _cf_KV (scope TEXT, k TEXT, v TEXT, PRIMARY KEY(scope,k))\",\n \"CREATE TABLE IF NOT EXISTS _cf_ALARM (scope TEXT PRIMARY KEY, at_ms INTEGER, retry INTEGER DEFAULT 0, counted_retry INTEGER NOT NULL DEFAULT 0, generation INTEGER NOT NULL DEFAULT 0)\",\n \"CREATE TABLE IF NOT EXISTS _cf_METADATA (scope TEXT PRIMARY KEY, actor_name TEXT)\",\n \"CREATE TABLE IF NOT EXISTS _cf_FACETS (scope TEXT NOT NULL, path TEXT NOT NULL, image BLOB NOT NULL, PRIMARY KEY(scope,path)) WITHOUT ROWID\",\n `CREATE TABLE IF NOT EXISTS _cf_WAKE (\n scope TEXT PRIMARY KEY, epoch TEXT NOT NULL CHECK(length(epoch)=16),\n sequence INTEGER NOT NULL CHECK(typeof(sequence)='integer' AND sequence>=0),\n at_ms INTEGER\n )`,\n `CREATE TRIGGER IF NOT EXISTS _cf_WAKE_insert AFTER INSERT ON _cf_ALARM BEGIN\n UPDATE _cf_WAKE SET sequence=sequence+1,at_ms=NEW.at_ms WHERE scope=NEW.scope;\n END`,\n `CREATE TRIGGER IF NOT EXISTS _cf_WAKE_update AFTER UPDATE OF at_ms,generation ON _cf_ALARM BEGIN\n UPDATE _cf_WAKE SET sequence=sequence+1,at_ms=NEW.at_ms WHERE scope=NEW.scope;\n END`,\n `CREATE TRIGGER IF NOT EXISTS _cf_WAKE_delete AFTER DELETE ON _cf_ALARM BEGIN\n UPDATE _cf_WAKE SET sequence=sequence+1,at_ms=NULL WHERE scope=OLD.scope;\n END`,\n];\n\n/** cellb's own bookkeeping (not in celld): the transaction-log sequence number. */\nexport const LOG_TABLE =\n \"CREATE TABLE IF NOT EXISTS _cf_LOG (id INTEGER PRIMARY KEY CHECK (id = 1), seq INTEGER NOT NULL)\";\nexport const LOG_SEQ_READ = \"SELECT seq FROM _cf_LOG WHERE id = 1\";\nexport const LOG_SEQ_WRITE = \"INSERT OR REPLACE INTO _cf_LOG (id, seq) VALUES (1, ?)\";\n\n/** celld's cell name for a cellb cell key (\"Class/idhex\" → \"Class:idhex\"). */\nexport const scopeOf = (cellKey: string) => cellKey.replace(\"/\", \":\");\n"]}