@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,1610 @@
1
+ // GENERATED by scripts/vendor-celld.mjs from denoland/celld@8702f6d186c77698dbfb30cf29e3b1922a88f008 crates/celld/js/harness.js
2
+ // (Apache-2.0): the Workflows section, unchanged, wrapped as workflowService(host). Do not edit.
3
+ export function workflowService(host) {
4
+ const { __cell, __cf, __sc_encode, __als_get, __als_set, __alarm_set, __alarm_delete,
5
+ __registerWaitUntil, __ctxRun } = host;
6
+ const __isolate_condemn = host.condemn ?? (() => {});
7
+ const classes = {};
8
+ const __runtimeClass = (name, cls) => { classes[name] = cls; };
9
+ const __celld = {};
10
+ // ---- Workflows ----------------------------------------------------------
11
+ // A workflow instance is one cell of the runtime-supplied `__Workflow` class,
12
+ // named `<workflow_name>/<instance_id>` through getByName, so it inherits
13
+ // ownership, fencing, LTX replication and the output gate from the cell it
14
+ // already is. Execution is replay: `run()` re-executes from the top on every
15
+ // resume and only steps are memoized, in `ctx.storage` KV under `__wf.*`.
16
+ // This half is the server; `__makeWorkflow` below is the client the binding
17
+ // hands to a Worker. The public contract is in docs/cloudflare-compat.md.
18
+
19
+ // Upstream's limit for a step return, an event payload, and the params. The
20
+ // error is distinct and names the step, because "too large" and "the isolate
21
+ // died" must not read the same (the D1 row-cap precedent).
22
+ const __WF_VALUE_CAP = 1 << 20;
23
+ // Workflow names, instance ids, and event types all share this charset.
24
+ // `/` is outside it, which is what makes the `<name>/<id>` cell-name join
25
+ // unambiguous.
26
+ const __WF_NAME_RE = /^[a-zA-Z0-9_][a-zA-Z0-9-_]*$/;
27
+ const __wfError = (message) => new Error("WORKFLOW_ERROR: " + message);
28
+ // The suspension primitive: a blocked step returns a promise that never
29
+ // settles in this invocation, so user code simply stops progressing. A
30
+ // sentinel thrown through user code was rejected because `Promise.all` in
31
+ // `run()` would observe it as a rejection and user try/catch would eat it.
32
+ const __wfNever = new Promise(() => {});
33
+ const __wfTerminal = (status) =>
34
+ status === "complete" || status === "errored" || status === "terminated";
35
+ // Upstream's hard bounds: 10,000 retries per step, 365 days for any sleep or
36
+ // event wait, one second under a waitForEvent timeout. Enforced loudly at the
37
+ // call site -- a config outside them would otherwise misbehave only after
38
+ // the instance is minutes or days into its run.
39
+ const __WF_RETRY_LIMIT_CAP = 10000;
40
+ const __WF_STEP_NAME_LIMIT = 256;
41
+ const __WF_MAX_WAIT_MS = 365 * 86400000;
42
+ const __WF_MIN_EVENT_TIMEOUT_MS = 1000;
43
+ // celld has no account plans, so it exposes the larger published retention
44
+ // ceiling as one stable application limit. Both an omitted policy and an
45
+ // omitted member of a partial policy use this value.
46
+ const __WF_RETENTION_MAX_MS = 30 * 86400000;
47
+ const __WF_LOCATION_HINTS = new Set([
48
+ "wnam", "enam", "sam", "weur", "eeur", "apac", "apac-ne", "apac-se",
49
+ "oc", "afr", "me",
50
+ ]);
51
+ // How long run() can stay pending on non-step work while no step runs and
52
+ // none is blocked before the instance fails. Upstream permits un-stepped
53
+ // awaits between steps (they are merely non-durable), so erroring at the
54
+ // first quiescent macrotask would kill any run() whose external await
55
+ // outlives one turn of the loop. Sixty seconds is long enough for any
56
+ // sane un-stepped await and short enough that a truly never-settling
57
+ // promise fails the instance instead of wedging the alarm handler.
58
+ const __WF_STALL_GRACE_MS = 60000;
59
+ const __WF_UNITS = {
60
+ second: 1000,
61
+ minute: 60000,
62
+ hour: 3600000,
63
+ day: 86400000,
64
+ week: 604800000,
65
+ // Upstream does not document its month/year arithmetic; fixed 30/365-day
66
+ // units are used so one expression cannot mean two durations depending on
67
+ // when it runs.
68
+ month: 30 * 86400000,
69
+ year: 365 * 86400000,
70
+ };
71
+ const __wfDuration = (value, what) => {
72
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
73
+ if (typeof value !== "string") {
74
+ throw __wfError(
75
+ `invalid duration for ${what}: ${JSON.stringify(value)}; use milliseconds ` +
76
+ 'or "<n> <second|minute|hour|day|week|month|year>[s]"',
77
+ );
78
+ }
79
+ const match = /^\s*(\d+(?:\.\d+)?)\s+(second|minute|hour|day|week|month|year)s?\s*$/
80
+ .exec(value);
81
+ if (!match) {
82
+ throw __wfError(
83
+ `invalid duration for ${what}: ${JSON.stringify(value)}; use milliseconds ` +
84
+ 'or "<n> <second|minute|hour|day|week|month|year>[s]"',
85
+ );
86
+ }
87
+ return Number(match[1]) * __WF_UNITS[match[2]];
88
+ };
89
+ const __wfRetentionDuration = (value, what) => {
90
+ const durationMs = __wfDuration(value, what);
91
+ if (durationMs > __WF_RETENTION_MAX_MS) {
92
+ throw __wfError(
93
+ `${what} is ${durationMs} ms, above celld's 30-day retention limit`,
94
+ );
95
+ }
96
+ return durationMs;
97
+ };
98
+ const __wfOwnObject = (value) =>
99
+ value !== null && typeof value === "object" && !Array.isArray(value);
100
+ const __wfUnknownKey = (value, allowed) =>
101
+ Object.keys(value).find((key) => !allowed.includes(key));
102
+ const __wfStepName = (name) => {
103
+ // The pinned workers-sdk validator has no minimum length. An empty name is
104
+ // therefore valid, although rejecting it looks safer at first glance. A
105
+ // non-empty check made celld reject Worker code that the upstream runtime
106
+ // accepts, so keep only the upstream length and control-character bounds.
107
+ if (
108
+ typeof name !== "string" || name.length > __WF_STEP_NAME_LIMIT ||
109
+ /[\x00-\x1f]/.test(name)
110
+ ) {
111
+ throw __wfError(
112
+ `invalid step name ${JSON.stringify(name)}: use at most ` +
113
+ `${__WF_STEP_NAME_LIMIT} characters and no control characters`,
114
+ );
115
+ }
116
+ };
117
+ const __wfEventType = (type) => {
118
+ if (typeof type !== "string" || type.length > 100 || !__WF_NAME_RE.test(type)) {
119
+ throw __wfError(
120
+ `invalid event type ${JSON.stringify(type)}: use at most 100 letters, ` +
121
+ "digits, hyphens, or underscores",
122
+ );
123
+ }
124
+ };
125
+ const __wfSleepDuration = (duration, name) => {
126
+ const durationMs = __wfDuration(
127
+ duration,
128
+ `the duration of sleep ${JSON.stringify(name)}`,
129
+ );
130
+ if (durationMs > __WF_MAX_WAIT_MS) {
131
+ throw __wfError(
132
+ `sleep ${JSON.stringify(name)} is ${durationMs} ms, above the upstream ` +
133
+ "limit of 365 days",
134
+ );
135
+ }
136
+ return durationMs;
137
+ };
138
+ const __wfSleepUntil = (timestamp, name) => {
139
+ const at = timestamp instanceof Date ? timestamp.getTime() : timestamp;
140
+ if (typeof at !== "number" || !Number.isFinite(at)) {
141
+ throw __wfError(
142
+ `invalid sleepUntil timestamp for ${JSON.stringify(name)}: use a Date ` +
143
+ "or a UNIX timestamp in milliseconds",
144
+ );
145
+ }
146
+ const delay = at - Date.now();
147
+ if (delay > __WF_MAX_WAIT_MS) {
148
+ throw __wfError(
149
+ `sleepUntil ${JSON.stringify(name)} is ${delay} ms away, above the ` +
150
+ "upstream limit of 365 days",
151
+ );
152
+ }
153
+ return at;
154
+ };
155
+ const __wfWaitOptions = (options, name) => {
156
+ if (!__wfOwnObject(options)) {
157
+ throw __wfError(`invalid waitForEvent options for ${JSON.stringify(name)}`);
158
+ }
159
+ __wfEventType(options.type);
160
+ const timeout = options.timeout ?? "24 hours";
161
+ const timeoutMs = __wfDuration(
162
+ timeout,
163
+ `the timeout of waitForEvent ${JSON.stringify(name)}`,
164
+ );
165
+ if (timeoutMs < __WF_MIN_EVENT_TIMEOUT_MS || timeoutMs > __WF_MAX_WAIT_MS) {
166
+ throw __wfError(
167
+ `the timeout of waitForEvent ${JSON.stringify(name)} is ${timeoutMs} ms; ` +
168
+ "upstream allows 1 second to 365 days",
169
+ );
170
+ }
171
+ return { type: options.type, timeout, timeoutMs };
172
+ };
173
+ const __wfEventOptions = (options) => {
174
+ if (!__wfOwnObject(options)) {
175
+ throw __wfError("sendEvent() needs an event object");
176
+ }
177
+ __wfEventType(options.type);
178
+ if (options.payload !== undefined) __wfCheckValue(options.payload, "the event payload");
179
+ return { type: options.type, payload: options.payload };
180
+ };
181
+ const __wfRetentionOptions = (retention, what) => {
182
+ if (retention === undefined) {
183
+ return {
184
+ successMs: __WF_RETENTION_MAX_MS,
185
+ errorMs: __WF_RETENTION_MAX_MS,
186
+ };
187
+ }
188
+ if (!__wfOwnObject(retention)) {
189
+ throw __wfError(`${what} option retention must be an object`);
190
+ }
191
+ const unknown = __wfUnknownKey(retention, ["successRetention", "errorRetention"]);
192
+ if (unknown !== undefined) {
193
+ throw __wfError(
194
+ `${what} option retention has an unknown field ${JSON.stringify(unknown)}`,
195
+ );
196
+ }
197
+ return {
198
+ successMs: retention.successRetention === undefined
199
+ ? __WF_RETENTION_MAX_MS
200
+ : __wfRetentionDuration(
201
+ retention.successRetention,
202
+ `${what} successRetention`,
203
+ ),
204
+ errorMs: retention.errorRetention === undefined
205
+ ? __WF_RETENTION_MAX_MS
206
+ : __wfRetentionDuration(retention.errorRetention, `${what} errorRetention`),
207
+ };
208
+ };
209
+ const __wfLocationHint = (hint, what) => {
210
+ if (hint !== undefined && !__WF_LOCATION_HINTS.has(hint)) {
211
+ throw __wfError(
212
+ `${what} has invalid locationHint ${JSON.stringify(hint)}`,
213
+ );
214
+ }
215
+ return hint;
216
+ };
217
+ const __wfRestartOptions = (options) => {
218
+ if (options === undefined) return {};
219
+ if (!__wfOwnObject(options)) {
220
+ throw __wfError("restart() options must be an object");
221
+ }
222
+ const unknown = __wfUnknownKey(options, ["from"]);
223
+ if (unknown !== undefined) {
224
+ throw __wfError(`restart() has an unknown option ${JSON.stringify(unknown)}`);
225
+ }
226
+ if (options.from === undefined) return {};
227
+ const from = options.from;
228
+ if (!__wfOwnObject(from)) {
229
+ throw __wfError("restart() option from must be an object");
230
+ }
231
+ const fromUnknown = __wfUnknownKey(from, ["name", "count", "type"]);
232
+ if (fromUnknown !== undefined) {
233
+ throw __wfError(
234
+ `restart() option from has an unknown field ${JSON.stringify(fromUnknown)}`,
235
+ );
236
+ }
237
+ __wfStepName(from.name);
238
+ const count = from.count ?? 1;
239
+ if (!Number.isInteger(count) || count < 1) {
240
+ throw __wfError("restart() option from.count must be a positive integer");
241
+ }
242
+ const type = from.type ?? "do";
243
+ if (!["do", "sleep", "waitForEvent"].includes(type)) {
244
+ throw __wfError(
245
+ 'restart() option from.type must be "do", "sleep", or "waitForEvent"',
246
+ );
247
+ }
248
+ return { from: { name: from.name, count, type } };
249
+ };
250
+ const __wfStepConfig = (config, name) => {
251
+ if (!__wfOwnObject(config)) {
252
+ throw __wfError(`invalid config for step ${JSON.stringify(name)}: use an object`);
253
+ }
254
+ let unknown = __wfUnknownKey(config, ["retries", "timeout", "sensitive"]);
255
+ if (unknown !== undefined) {
256
+ throw __wfError(
257
+ `invalid config for step ${JSON.stringify(name)}: unknown option ` +
258
+ JSON.stringify(unknown),
259
+ );
260
+ }
261
+ const retries = config.retries;
262
+ if (retries !== undefined) {
263
+ if (!__wfOwnObject(retries)) {
264
+ throw __wfError(`invalid retries config for step ${JSON.stringify(name)}`);
265
+ }
266
+ unknown = __wfUnknownKey(retries, ["limit", "delay", "backoff"]);
267
+ if (unknown !== undefined) {
268
+ throw __wfError(
269
+ `invalid retries config for step ${JSON.stringify(name)}: unknown option ` +
270
+ JSON.stringify(unknown),
271
+ );
272
+ }
273
+ if (
274
+ typeof retries.limit !== "number" || !Number.isFinite(retries.limit) ||
275
+ retries.limit < 0
276
+ ) {
277
+ throw __wfError(
278
+ `invalid retries config for step ${JSON.stringify(name)}: ` +
279
+ "limit must be a non-negative number",
280
+ );
281
+ }
282
+ if (retries.limit > __WF_RETRY_LIMIT_CAP) {
283
+ throw __wfError(
284
+ `invalid retries config for step ${JSON.stringify(name)}: limit is above ` +
285
+ String(__WF_RETRY_LIMIT_CAP),
286
+ );
287
+ }
288
+ if (typeof retries.delay !== "function") {
289
+ __wfDuration(retries.delay, `the retries config delay of step ${JSON.stringify(name)}`);
290
+ }
291
+ if (
292
+ retries.backoff !== undefined &&
293
+ !["constant", "linear", "exponential"].includes(retries.backoff)
294
+ ) {
295
+ throw __wfError(
296
+ `invalid retries config for step ${JSON.stringify(name)}: ` +
297
+ "backoff must be constant, linear, or exponential",
298
+ );
299
+ }
300
+ }
301
+ if (config.timeout !== undefined) {
302
+ const timeout = __wfDuration(
303
+ config.timeout,
304
+ `the config timeout of step ${JSON.stringify(name)}`,
305
+ );
306
+ if (timeout === 0) {
307
+ throw __wfError(
308
+ `invalid config timeout for step ${JSON.stringify(name)}: use a value above zero`,
309
+ );
310
+ }
311
+ }
312
+ if (config.sensitive !== undefined && config.sensitive !== "output") {
313
+ throw __wfError(
314
+ `invalid config sensitive value for step ${JSON.stringify(name)}: use "output"`,
315
+ );
316
+ }
317
+ return {
318
+ ...config,
319
+ ...(retries === undefined ? {} : { retries: { ...retries } }),
320
+ };
321
+ };
322
+ // Serialization is checked with the same encoder storage and RPC use, so a
323
+ // value that passes here cannot fail later at the isolate boundary. The
324
+ // thrown errors are permanent: retrying cannot make a value cloneable or
325
+ // shrink it.
326
+ const __wfEncodeValue = (value, what) => {
327
+ try {
328
+ return __sc_encode(value);
329
+ } catch (error) {
330
+ const failure = __wfError(
331
+ `${what} is not serializable: ${String(error && error.message || error)}. ` +
332
+ "It must survive structured clone; a ReadableStream return is not implemented in celld.",
333
+ );
334
+ failure.__wfPermanent = true;
335
+ throw failure;
336
+ }
337
+ };
338
+ const __wfCheckEncodedValue = (bytes, what) => {
339
+ if (bytes.byteLength > __WF_VALUE_CAP) {
340
+ const failure = __wfError(
341
+ `${what} is ${bytes.byteLength} bytes, above the 1 MiB limit; ` +
342
+ "store large output externally and return a reference",
343
+ );
344
+ failure.__wfPermanent = true;
345
+ throw failure;
346
+ }
347
+ };
348
+ const __wfCheckValue = (value, what) =>
349
+ __wfCheckEncodedValue(__wfEncodeValue(value, what), what);
350
+ const __wfErrorRecord = (error) => ({
351
+ name: String(error && error.name || "Error"),
352
+ message: String(error && error.message || error),
353
+ });
354
+ // A permanently failed step rejects on every replay, so user try/catch
355
+ // around step.do keeps working after a resume, not only on the attempt that
356
+ // failed live.
357
+ const __wfReviveError = (record) => {
358
+ const error = new Error(record.message);
359
+ error.name = record.name;
360
+ return error;
361
+ };
362
+ const __wfLedgerPrefix = (generation) => `__wf.${generation}.`;
363
+ const __wfEventPrefix = (generation) =>
364
+ __wfLedgerPrefix(generation) + "event.";
365
+ const __wfEventKey = (generation, sequence) =>
366
+ __wfEventPrefix(generation) + String(sequence).padStart(9, "0");
367
+ const __wfRetentionMs = (meta, status) =>
368
+ status === "complete"
369
+ ? (meta.retention?.successMs ?? __WF_RETENTION_MAX_MS)
370
+ : (meta.retention?.errorMs ?? __WF_RETENTION_MAX_MS);
371
+ const __wfFinishRetention = (meta, transaction) => {
372
+ meta.endedMs = Date.now();
373
+ meta.expiresMs = meta.endedMs + __wfRetentionMs(meta, meta.status);
374
+ transaction.setAlarm(meta.expiresMs);
375
+ };
376
+ // A ledger record replays only through the kind of call that wrote it. A
377
+ // `do` renamed into a `sleep`, or two calls reordered under one name, would
378
+ // otherwise read fields the other kind never wrote and continue on
379
+ // undefined -- a deadline of NaN, a value of nothing -- instead of failing.
380
+ const __wfKindCheck = (record, kind, name) => {
381
+ if (record === undefined || record.kind === kind) return record;
382
+ const failure = __wfError(
383
+ `step ${JSON.stringify(name)} replays as ${kind} but its ledger record ` +
384
+ `was written by ${record.kind}; a step keeps one kind across replays`,
385
+ );
386
+ failure.__wfPermanent = true;
387
+ throw failure;
388
+ };
389
+ // Each step callback runs under an async-context frame naming its step (the
390
+ // same CPED-backed frame __ctxRun uses, so it survives the callback's
391
+ // awaits). A step started inside another step's callback would wedge
392
+ // quiescence: the outer callback cannot settle while it awaits the blocked
393
+ // inner step, so `running` never reaches zero and the instance can neither
394
+ // suspend nor finish. The frame is what lets the inner call refuse loudly
395
+ // at the nesting site instead.
396
+ const __wfStepKey = Symbol("celld.wfStep");
397
+ const __wfEnclosingStep = () => {
398
+ const frame = __als_get();
399
+ return frame === undefined ? undefined : frame.get(__wfStepKey);
400
+ };
401
+ const __wfEnterStep = (name, fn) => {
402
+ const prior = __als_get();
403
+ const frame = new Map(prior);
404
+ frame.set(__wfStepKey, name);
405
+ __als_set(frame);
406
+ try {
407
+ return fn();
408
+ } finally {
409
+ __als_set(prior);
410
+ }
411
+ };
412
+
413
+ // Race an attempt against its deadline. The abandoned callback keeps
414
+ // running -- there is no cancellation to send it -- but its result is
415
+ // discarded and the timeout counts as a failed attempt, as upstream.
416
+ const __wfTimeout = (promise, ms, name, attempt) =>
417
+ new Promise((resolve, reject) => {
418
+ const timer = setTimeout(
419
+ () =>
420
+ reject(new Error(
421
+ `step ${JSON.stringify(name)} timed out after ${ms} ms (attempt ${attempt})`,
422
+ )),
423
+ ms,
424
+ );
425
+ promise.then(
426
+ (value) => { clearTimeout(timer); resolve(value); },
427
+ (error) => { clearTimeout(timer); reject(error); },
428
+ );
429
+ });
430
+
431
+ // Storage failures belong to the engine even when workflow code catches the
432
+ // rejected step promise. Record them outside the Error object, so user code
433
+ // cannot suppress a retry or forge one by returning a marked exception.
434
+ const __wfTrackStorageFailures = (storage, driver) => {
435
+ const tracked = Object.create(storage);
436
+ for (const name of ["get", "put", "transactionSync"]) {
437
+ Object.defineProperty(tracked, name, {
438
+ value(...args) {
439
+ try {
440
+ const result = storage[name](...args);
441
+ if (result !== null && typeof result === "object" &&
442
+ typeof result.then === "function") {
443
+ return result.catch((error) => {
444
+ driver.storageFailure ??= error;
445
+ throw error;
446
+ });
447
+ }
448
+ return result;
449
+ } catch (error) {
450
+ driver.storageFailure ??= error;
451
+ throw error;
452
+ }
453
+ },
454
+ });
455
+ }
456
+ return tracked;
457
+ };
458
+
459
+ // The WorkflowStep the driver hands to run(). Each method resolves its step
460
+ // record by (name, occurrence count) -- the count is assigned synchronously
461
+ // at call time, so `Promise.all` of two same-named steps gets deterministic
462
+ // distinct keys in call order, and a repeated name in a loop cannot collide.
463
+ const __wfMakeStep = (driver) => {
464
+ const storage = driver.storage;
465
+ // The shared step frame. `body` resolves to {value} or {block: at, kind};
466
+ // a blocked step leaves its promise unsettled forever in this invocation.
467
+ // The running counter covers the whole body, ledger reads included, so
468
+ // quiescence cannot fire between a step's read and its decision.
469
+ const frame = (name, body) => {
470
+ // A stale replay -- one whose invocation already suspended -- can wake
471
+ // later if user code raced a step against an un-stepped await. It goes
472
+ // quiet instead of executing an attempt beside the fresh replay.
473
+ if (driver.finished) return __wfNever;
474
+ const enclosing = __wfEnclosingStep();
475
+ if (enclosing !== undefined) {
476
+ const failure = __wfError(
477
+ `step ${JSON.stringify(name)} cannot start inside the callback of ` +
478
+ `step ${JSON.stringify(enclosing)}; a step callback must not use ` +
479
+ "the step API",
480
+ );
481
+ failure.__wfPermanent = true;
482
+ return Promise.reject(failure);
483
+ }
484
+ const count = (driver.counts.get(name) ?? 0) + 1;
485
+ driver.counts.set(name, count);
486
+ const ordinal = ++driver.ordinal;
487
+ const key = driver.ledgerPrefix + `step.${count}.${name}`;
488
+ // Step activity restarts the stall clock: a pending grace timer was
489
+ // armed against a run() that looked stuck, and this call is progress.
490
+ driver.clearStall();
491
+ driver.running += 1;
492
+ return new Promise((resolve, reject) => {
493
+ (async () => {
494
+ const meta = await storage.get("__wf.meta");
495
+ if (
496
+ meta === undefined || meta.generation !== driver.generation ||
497
+ __wfTerminal(meta.status) || meta.status === "paused" ||
498
+ meta.status === "waitingForPause"
499
+ ) {
500
+ return { pause: true };
501
+ }
502
+ return await body(key, count, ordinal);
503
+ })().then(
504
+ (result) => {
505
+ driver.running -= 1;
506
+ driver.clearStall();
507
+ if (result.pause === true) {
508
+ driver.pauseRequested = true;
509
+ driver.check();
510
+ return;
511
+ }
512
+ if (result.block !== undefined) {
513
+ if (!driver.finished) {
514
+ driver.blocked.push({ at: result.block, kind: result.kind });
515
+ }
516
+ driver.check();
517
+ return;
518
+ }
519
+ driver.check();
520
+ resolve(result.value);
521
+ },
522
+ (error) => {
523
+ driver.running -= 1;
524
+ driver.clearStall();
525
+ driver.check();
526
+ reject(error);
527
+ },
528
+ );
529
+ });
530
+ };
531
+ const doStep = (name, configOrCallback, callbackOrRollback, maybeRollback) => {
532
+ const hasConfig = typeof configOrCallback !== "function";
533
+ let config = hasConfig ? (configOrCallback ?? {}) : {};
534
+ const callback = hasConfig ? callbackOrRollback : configOrCallback;
535
+ const rollback = hasConfig ? maybeRollback : callbackOrRollback;
536
+ try {
537
+ __wfStepName(name);
538
+ config = __wfStepConfig(config, name);
539
+ } catch (error) {
540
+ return Promise.reject(error);
541
+ }
542
+ if (rollback !== undefined) {
543
+ return Promise.reject(__wfError(
544
+ "step rollbackOptions are not implemented in celld; remove the rollback handler",
545
+ ));
546
+ }
547
+ if (config.sensitive !== undefined) {
548
+ return Promise.reject(__wfError(
549
+ "sensitive step output is not implemented in celld; remove `sensitive`",
550
+ ));
551
+ }
552
+ if (typeof callback !== "function") {
553
+ return Promise.reject(__wfError("step.do needs a callback function"));
554
+ }
555
+ return frame(name, async (key, count, ordinal) => {
556
+ const record = __wfKindCheck(await storage.get(key), "do", name);
557
+ if (record !== undefined && record.status === "completed") {
558
+ return { value: record.value };
559
+ }
560
+ if (record !== undefined && record.status === "failed") {
561
+ throw __wfReviveError(record.error);
562
+ }
563
+ // A pending retry whose backoff has not elapsed blocks without
564
+ // executing; the deadline was persisted when the attempt failed, so a
565
+ // replay cannot reset the backoff.
566
+ if (record !== undefined && record.nextAt > Date.now()) {
567
+ return { block: record.nextAt, kind: "retry" };
568
+ }
569
+ const attempt = (record === undefined ? 0 : record.attempt) + 1;
570
+ const historyOrdinal = record?.ordinal ?? ordinal;
571
+ const retries = config.retries ??
572
+ { limit: 5, delay: 10000, backoff: "exponential" };
573
+ const timeout = config.timeout ?? "10 minutes";
574
+ const timeoutMs = __wfDuration(
575
+ timeout,
576
+ `the timeout of step ${JSON.stringify(name)}`,
577
+ );
578
+ // Upstream omits a dynamic delay from the step context. Copying the
579
+ // config verbatim would expose the callback to itself as retry policy,
580
+ // while callers expect only the serializable limit and backoff fields.
581
+ const contextRetries = typeof retries.delay === "function"
582
+ ? { limit: retries.limit, ...(retries.backoff === undefined
583
+ ? {}
584
+ : { backoff: retries.backoff }) }
585
+ : retries;
586
+ const context = {
587
+ step: { name, count },
588
+ attempt,
589
+ config: { retries: contextRetries, timeout },
590
+ };
591
+ try {
592
+ // Persist the history entry before the callback starts. A concurrent
593
+ // selected restart can therefore name an in-flight occurrence, and
594
+ // its new generation fences the callback's eventual old-ledger write.
595
+ await storage.put(key, {
596
+ kind: "do",
597
+ status: "running",
598
+ attempt: attempt - 1,
599
+ ordinal: historyOrdinal,
600
+ });
601
+ const value = await __wfTimeout(
602
+ __wfEnterStep(name, async () => callback(context)),
603
+ timeoutMs,
604
+ name,
605
+ attempt,
606
+ );
607
+ if (value !== undefined) {
608
+ __wfCheckValue(value, `the return value of step ${JSON.stringify(name)}`);
609
+ }
610
+ await storage.put(key, {
611
+ kind: "do",
612
+ status: "completed",
613
+ value,
614
+ ordinal: historyOrdinal,
615
+ });
616
+ return { value };
617
+ } catch (error) {
618
+ // NonRetryableError skips retries by contract; a value-cap or
619
+ // serialization failure is marked permanent above because another
620
+ // attempt returns the same value.
621
+ const permanent = error instanceof NonRetryableError ||
622
+ (error !== null && typeof error === "object" &&
623
+ (error.name === "NonRetryableError" || error.__wfPermanent === true)) ||
624
+ attempt > retries.limit;
625
+ if (permanent) {
626
+ await storage.put(key, {
627
+ kind: "do",
628
+ status: "failed",
629
+ error: __wfErrorRecord(error),
630
+ ordinal: historyOrdinal,
631
+ });
632
+ throw error;
633
+ }
634
+ // Upstream documents the backoff names, not the arithmetic; the
635
+ // simplest reading is used and recorded in the design page. When
636
+ // `retries` is given without `backoff`, celld uses constant -- the
637
+ // documented exponential default is the whole-config-omitted case.
638
+ const dynamic = typeof retries.delay === "function";
639
+ const delay = dynamic
640
+ ? await __wfEnterStep(name, () => retries.delay({ ctx: context, error }))
641
+ : retries.delay;
642
+ const base = __wfDuration(
643
+ delay,
644
+ `the retry delay of step ${JSON.stringify(name)}`,
645
+ );
646
+ // A delay callback replaces the fixed base duration, not the backoff
647
+ // policy. Skipping this factor makes a dynamic exponential or linear
648
+ // policy retry earlier than the same policy with a fixed base.
649
+ const factor = retries.backoff === "exponential"
650
+ ? 2 ** (attempt - 1)
651
+ : retries.backoff === "linear"
652
+ ? attempt
653
+ : 1;
654
+ const nextAt = Date.now() + base * factor;
655
+ await storage.put(key, {
656
+ kind: "do",
657
+ status: "retrying",
658
+ attempt,
659
+ nextAt,
660
+ ordinal: historyOrdinal,
661
+ });
662
+ return { block: nextAt, kind: "retry" };
663
+ }
664
+ });
665
+ };
666
+ const sleepUntilDeadline = (name, key, ordinal, deadline) => (async () => {
667
+ const record = __wfKindCheck(await storage.get(key), "sleep", name);
668
+ if (record !== undefined && record.status === "completed") {
669
+ return { value: undefined };
670
+ }
671
+ const historyOrdinal = record?.ordinal ?? ordinal;
672
+ let at = record === undefined ? undefined : record.deadline;
673
+ if (at === undefined) {
674
+ // The absolute deadline is computed once and persisted. Recomputing
675
+ // from "now" on replay would extend the sleep by however long the
676
+ // instance was down, so a crash could postpone a deadline forever.
677
+ at = deadline();
678
+ if (at - Date.now() > __WF_MAX_WAIT_MS) {
679
+ throw __wfError(
680
+ `sleep ${JSON.stringify(name)} ends ${at - Date.now()} ms from now, ` +
681
+ "above the upstream limit of 365 days",
682
+ );
683
+ }
684
+ await storage.put(key, {
685
+ kind: "sleep",
686
+ status: "sleeping",
687
+ deadline: at,
688
+ ordinal: historyOrdinal,
689
+ });
690
+ }
691
+ if (at <= Date.now()) {
692
+ await storage.put(key, {
693
+ kind: "sleep",
694
+ status: "completed",
695
+ ordinal: historyOrdinal,
696
+ });
697
+ return { value: undefined };
698
+ }
699
+ return { block: at, kind: "sleep" };
700
+ })();
701
+ return {
702
+ do: doStep,
703
+ sleep: (name, duration) => {
704
+ let durationMs;
705
+ try {
706
+ __wfStepName(name);
707
+ durationMs = __wfSleepDuration(duration, name);
708
+ } catch (error) {
709
+ return Promise.reject(error);
710
+ }
711
+ return frame(name, (key, _count, ordinal) =>
712
+ sleepUntilDeadline(
713
+ name,
714
+ key,
715
+ ordinal,
716
+ () => Date.now() + durationMs,
717
+ ));
718
+ },
719
+ sleepUntil: (name, timestamp) => {
720
+ let at;
721
+ try {
722
+ __wfStepName(name);
723
+ at = __wfSleepUntil(timestamp, name);
724
+ } catch (error) {
725
+ return Promise.reject(error);
726
+ }
727
+ return frame(name, (key, _count, ordinal) =>
728
+ sleepUntilDeadline(name, key, ordinal, () => {
729
+ // A replay uses the persisted deadline after it has passed. Check
730
+ // the moving future bound only while the deadline is first installed,
731
+ // or every successful sleep would fail on the resume that completes it.
732
+ if (at - Date.now() < 0) {
733
+ throw __wfError(
734
+ `sleepUntil ${JSON.stringify(name)} needs a future timestamp`,
735
+ );
736
+ }
737
+ return at;
738
+ }));
739
+ },
740
+ waitForEvent: (name, options) => {
741
+ let checked;
742
+ try {
743
+ __wfStepName(name);
744
+ checked = __wfWaitOptions(options, name);
745
+ } catch (error) {
746
+ return Promise.reject(error);
747
+ }
748
+ return frame(name, async (key, _count, ordinal) => {
749
+ let record = __wfKindCheck(await storage.get(key), "event", name);
750
+ if (record !== undefined && record.status === "completed") {
751
+ return { value: record.value };
752
+ }
753
+ if (record !== undefined && record.status === "failed") {
754
+ throw __wfReviveError(record.error);
755
+ }
756
+ if (record === undefined) {
757
+ record = {
758
+ kind: "event",
759
+ status: "waiting",
760
+ type: checked.type,
761
+ deadline: Date.now() + checked.timeoutMs,
762
+ ordinal,
763
+ };
764
+ await storage.put(key, record);
765
+ }
766
+ // The deadline is checked before the buffer: an event that arrived
767
+ // after the persisted deadline must time the step out, not succeed
768
+ // because the replay that noticed it ran late.
769
+ if (record.deadline <= Date.now()) {
770
+ const error = new Error(
771
+ `waitForEvent ${JSON.stringify(name)} timed out waiting for an event ` +
772
+ `of type ${JSON.stringify(record.type)}`,
773
+ );
774
+ await storage.put(key, {
775
+ kind: "event",
776
+ status: "failed",
777
+ error: __wfErrorRecord(error),
778
+ ordinal: record.ordinal,
779
+ });
780
+ throw error;
781
+ }
782
+ // Delivered events persist until a matching step consumes one, in
783
+ // arrival order -- the zero-padded sequence key makes list order
784
+ // arrival order -- so an event sent before the step is reached is
785
+ // buffered, not lost.
786
+ const consumed = storage.transactionSync(() => {
787
+ const current = __wfKindCheck(storage.kv.get(key), "event", name);
788
+ if (current !== undefined && current.status === "completed") {
789
+ return { value: current.value };
790
+ }
791
+ if (current !== undefined && current.status === "failed") {
792
+ throw __wfReviveError(current.error);
793
+ }
794
+ for (const [eventKey, event] of storage.kv.list({
795
+ prefix: __wfEventPrefix(driver.generation),
796
+ })) {
797
+ if (event.type !== current.type) continue;
798
+ const value = {
799
+ payload: event.payload,
800
+ timestamp: new Date(event.timestampMs),
801
+ type: event.type,
802
+ };
803
+ // The delete and completed ledger record are one SQLite commit.
804
+ // A crash can therefore expose both or neither, so an
805
+ // acknowledged event cannot disappear between them.
806
+ storage.kv.delete(eventKey);
807
+ /*__CELLD_TEST_WORKFLOW_EVENT_CONSUMED__*/
808
+ storage.kv.put(key, {
809
+ kind: "event",
810
+ status: "completed",
811
+ value,
812
+ ordinal: current.ordinal,
813
+ });
814
+ return { value };
815
+ }
816
+ });
817
+ if (consumed !== undefined) return consumed;
818
+ return { block: record.deadline, kind: "event" };
819
+ });
820
+ },
821
+ };
822
+ };
823
+
824
+ const __WorkflowCell = (() => {
825
+ class StorageTransaction {
826
+ constructor(storage) {
827
+ this.kv = storage.kv;
828
+ this._scope = storage._scope;
829
+ }
830
+ setAlarm(t) {
831
+ __alarm_set(this._scope, t instanceof Date ? t.getTime() : Number(t));
832
+ }
833
+ deleteAlarm() {
834
+ __alarm_delete(this._scope);
835
+ }
836
+ }
837
+ const transactionSync = (storage, callback) =>
838
+ storage.transactionSync(() => callback(new StorageTransaction(storage)));
839
+ const clearInstance = (transaction) => {
840
+ for (const [key] of transaction.kv.list({ prefix: "__wf." })) {
841
+ transaction.kv.delete(key);
842
+ }
843
+ transaction.deleteAlarm();
844
+ };
845
+ const expireTerminal = (transaction, meta, now) => {
846
+ if (!__wfTerminal(meta.status)) return false;
847
+ if (meta.expiresMs === undefined) {
848
+ meta.endedMs = now;
849
+ meta.expiresMs = now + __wfRetentionMs(meta, meta.status);
850
+ transaction.kv.put("__wf.meta", meta);
851
+ }
852
+ if (meta.expiresMs > now) return false;
853
+ clearInstance(transaction);
854
+ return true;
855
+ };
856
+
857
+ // The adapter stays in this closure because Workflow needs synchronous
858
+ // alarm writes inside transactionSync(). The public async alarm API runs
859
+ // after that commit, while a global helper would let application code
860
+ // split metadata from its wake or invoke the raw operation after commit.
861
+ return class __WorkflowCell {
862
+ constructor(state) {
863
+ this._state = state;
864
+ }
865
+ async __wfCreate({
866
+ workflowName,
867
+ instanceId,
868
+ params,
869
+ retention,
870
+ locationHint,
871
+ skipExisting = false,
872
+ }) {
873
+ const storage = this._state.storage;
874
+ if (params !== undefined) __wfCheckValue(params, "the workflow params");
875
+ const result = transactionSync(storage, (transaction) => {
876
+ let meta = transaction.kv.get("__wf.meta");
877
+ if (meta !== undefined && expireTerminal(transaction, meta, Date.now())) {
878
+ meta = undefined;
879
+ }
880
+ if (meta !== undefined && skipExisting) return { id: instanceId, created: false };
881
+ if (meta !== undefined) {
882
+ throw __wfError(
883
+ `instance ${JSON.stringify(instanceId)} already exists with status ` +
884
+ JSON.stringify(meta.status),
885
+ );
886
+ }
887
+ const generation = crypto.randomUUID();
888
+ transaction.kv.put("__wf.meta", {
889
+ workflowName,
890
+ instanceId,
891
+ params,
892
+ retention,
893
+ ...(locationHint === undefined ? {} : { locationHint }),
894
+ generation,
895
+ createdMs: Date.now(),
896
+ status: "queued",
897
+ });
898
+ // The metadata and alarm are one SQLite commit. A committed creation
899
+ // therefore always has the wake that can start or recover its drive.
900
+ transaction.setAlarm(Date.now());
901
+ /*__CELLD_TEST_WORKFLOW_META_CREATED__*/
902
+ return { id: instanceId, created: true };
903
+ });
904
+ return result;
905
+ }
906
+ async __wfStatus() {
907
+ const meta = transactionSync(this._state.storage, (transaction) => {
908
+ const current = transaction.kv.get("__wf.meta");
909
+ if (current === undefined) return undefined;
910
+ return expireTerminal(transaction, current, Date.now()) ? undefined : current;
911
+ });
912
+ // A cell exists on first address like any cell, so an absent ledger is
913
+ // the only honest "no such instance" signal. Inventing an instance here
914
+ // would turn every typo into an empty workflow.
915
+ if (meta === undefined) throw __wfError("instance does not exist");
916
+ const status = { status: meta.status, rollback: null };
917
+ if (meta.error !== undefined) status.error = meta.error;
918
+ if (meta.output !== undefined) status.output = meta.output;
919
+ return status;
920
+ }
921
+ async __wfDelete() {
922
+ const storage = this._state.storage;
923
+ const deleted = transactionSync(storage, (transaction) => {
924
+ const meta = transaction.kv.get("__wf.meta");
925
+ if (meta === undefined) return false;
926
+ if (expireTerminal(transaction, meta, Date.now())) return false;
927
+ clearInstance(transaction);
928
+ return true;
929
+ });
930
+ // Throw after the transaction commits. An expiry discovered here must
931
+ // keep its cleanup even though delete() reports that the instance is gone.
932
+ if (!deleted) throw __wfError("instance does not exist");
933
+ // A callback can still be awaiting application I/O when deletion lands.
934
+ // Reset the object after the deletion commit so that callback cannot
935
+ // recreate an old-generation ledger entry after delete() reports success.
936
+ this._state.abort(new Error("WORKFLOW_INSTANCE_DELETED"));
937
+ }
938
+ async __wfPause() {
939
+ const storage = this._state.storage;
940
+ transactionSync(storage, (transaction) => {
941
+ const meta = transaction.kv.get("__wf.meta");
942
+ if (meta === undefined) throw __wfError("instance does not exist");
943
+ if (__wfTerminal(meta.status) || meta.status === "paused" ||
944
+ meta.status === "waitingForPause") return;
945
+ if (meta.status === "running") {
946
+ meta.status = "waitingForPause";
947
+ // A drive lost after this commit must re-enter and finish the pause.
948
+ transaction.setAlarm(Date.now());
949
+ } else {
950
+ meta.status = "paused";
951
+ meta.pausedMs = Date.now();
952
+ transaction.deleteAlarm();
953
+ }
954
+ transaction.kv.put("__wf.meta", meta);
955
+ });
956
+ }
957
+ async __wfResume() {
958
+ const storage = this._state.storage;
959
+ transactionSync(storage, (transaction) => {
960
+ const meta = transaction.kv.get("__wf.meta");
961
+ if (meta === undefined) throw __wfError("instance does not exist");
962
+ if (meta.status === "waitingForPause") {
963
+ meta.status = "running";
964
+ transaction.kv.put("__wf.meta", meta);
965
+ transaction.setAlarm(Date.now());
966
+ return;
967
+ }
968
+ if (meta.status !== "paused") {
969
+ throw __wfError(
970
+ `cannot resume an instance with status ${JSON.stringify(meta.status)}`,
971
+ );
972
+ }
973
+ const offset = Math.max(0, Date.now() - (meta.pausedMs ?? Date.now()));
974
+ for (const [key, record] of transaction.kv.list({
975
+ prefix: __wfLedgerPrefix(meta.generation) + "step.",
976
+ })) {
977
+ if (record.status === "retrying") record.nextAt += offset;
978
+ if (record.status === "sleeping" || record.status === "waiting") {
979
+ record.deadline += offset;
980
+ }
981
+ transaction.kv.put(key, record);
982
+ }
983
+ delete meta.pausedMs;
984
+ meta.status = "queued";
985
+ transaction.kv.put("__wf.meta", meta);
986
+ transaction.setAlarm(Date.now());
987
+ });
988
+ }
989
+ async __wfRestart(options) {
990
+ const checked = __wfRestartOptions(options);
991
+ const storage = this._state.storage;
992
+ transactionSync(storage, (transaction) => {
993
+ const meta = transaction.kv.get("__wf.meta");
994
+ if (meta === undefined) throw __wfError("instance does not exist");
995
+ const oldPrefix = __wfLedgerPrefix(meta.generation);
996
+ let targetOrdinal;
997
+ if (checked.from !== undefined) {
998
+ const expectedKind = checked.from.type === "waitForEvent"
999
+ ? "event"
1000
+ : checked.from.type;
1001
+ let occurrence = 0;
1002
+ const history = [...transaction.kv.list({ prefix: oldPrefix + "step." })]
1003
+ .filter(([, record]) => Number.isInteger(record.ordinal))
1004
+ .sort((left, right) => left[1].ordinal - right[1].ordinal);
1005
+ // The ledger key is `<prefix>step.<per-name-count>.<name>`. Derive
1006
+ // the name from it because a record stores replay state, not identity.
1007
+ for (const [key, record] of history) {
1008
+ const marker = oldPrefix + "step.";
1009
+ const suffix = key.slice(marker.length);
1010
+ const dot = suffix.indexOf(".");
1011
+ const name = dot < 0 ? "" : suffix.slice(dot + 1);
1012
+ if (record.kind !== expectedKind || name !== checked.from.name) continue;
1013
+ occurrence += 1;
1014
+ if (occurrence === checked.from.count) {
1015
+ targetOrdinal = record.ordinal;
1016
+ break;
1017
+ }
1018
+ }
1019
+ if (targetOrdinal === undefined) {
1020
+ throw __wfError(
1021
+ `restart() could not find ${checked.from.type} step ` +
1022
+ `${JSON.stringify(checked.from.name)} occurrence ${checked.from.count}`,
1023
+ );
1024
+ }
1025
+ }
1026
+ const generation = crypto.randomUUID();
1027
+ const newPrefix = __wfLedgerPrefix(generation);
1028
+ if (targetOrdinal !== undefined) {
1029
+ for (const [key, record] of transaction.kv.list({ prefix: oldPrefix + "step." })) {
1030
+ if (Number.isInteger(record.ordinal) && record.ordinal < targetOrdinal) {
1031
+ transaction.kv.put(newPrefix + key.slice(oldPrefix.length), record);
1032
+ }
1033
+ }
1034
+ }
1035
+ for (const [key] of transaction.kv.list({ prefix: oldPrefix })) {
1036
+ transaction.kv.delete(key);
1037
+ }
1038
+ meta.generation = generation;
1039
+ meta.status = "queued";
1040
+ delete meta.pausedMs;
1041
+ delete meta.output;
1042
+ delete meta.error;
1043
+ transaction.kv.put("__wf.meta", meta);
1044
+ transaction.setAlarm(Date.now());
1045
+ });
1046
+ }
1047
+ async __wfTerminate() {
1048
+ const storage = this._state.storage;
1049
+ transactionSync(storage, (transaction) => {
1050
+ const meta = transaction.kv.get("__wf.meta");
1051
+ if (meta === undefined) throw __wfError("instance does not exist");
1052
+ if (__wfTerminal(meta.status)) {
1053
+ throw __wfError(
1054
+ `cannot terminate an instance with status ${JSON.stringify(meta.status)}`,
1055
+ );
1056
+ }
1057
+ meta.status = "terminated";
1058
+ __wfFinishRetention(meta, transaction);
1059
+ transaction.kv.put("__wf.meta", meta);
1060
+ /*__CELLD_TEST_WORKFLOW_TERMINAL_ALARM_SET__*/
1061
+ });
1062
+ }
1063
+ async __wfSendEvent(options) {
1064
+ const storage = this._state.storage;
1065
+ // The binding validates first to avoid an unnecessary cell call. Repeat
1066
+ // the check here because this RPC owns the durable event invariant: no
1067
+ // current or future internal caller can bypass validation before storage.
1068
+ const { type, payload } = __wfEventOptions(options);
1069
+ transactionSync(storage, (transaction) => {
1070
+ const meta = transaction.kv.get("__wf.meta");
1071
+ if (meta === undefined) throw __wfError("instance does not exist");
1072
+ if (__wfTerminal(meta.status)) {
1073
+ throw __wfError(
1074
+ `cannot send an event to an instance with status ${JSON.stringify(meta.status)}`,
1075
+ );
1076
+ }
1077
+ const sequenceKey = __wfLedgerPrefix(meta.generation) + "eventSeq";
1078
+ const sequence = transaction.kv.get(sequenceKey) ?? 0;
1079
+ transaction.kv.put(sequenceKey, sequence + 1);
1080
+ transaction.kv.put(__wfEventKey(meta.generation, sequence), {
1081
+ type,
1082
+ payload,
1083
+ timestampMs: Date.now(),
1084
+ });
1085
+ // Allocation, the event body, and the nudge are one commit. Parallel
1086
+ // writers cannot reuse a sequence and a crash cannot expose a partial
1087
+ // acknowledged send.
1088
+ if (meta.status !== "paused" && meta.status !== "waitingForPause") {
1089
+ transaction.setAlarm(Date.now());
1090
+ }
1091
+ });
1092
+ }
1093
+ async alarm() {
1094
+ const storage = this._state.storage;
1095
+ // One drive at a time. A sendEvent nudge (setAlarm(now)) landing
1096
+ // mid-fire replaces the firing alarm in the core, and the replacement
1097
+ // can dispatch a second alarm() into this isolate while the first drive
1098
+ // still awaits user code. Two concurrent replays would both see the
1099
+ // same incomplete steps and double-execute their callbacks. The guard
1100
+ // is isolate-local and that is sufficient: every drive for a cell runs
1101
+ // in its owner's isolate, and a takeover starts a fresh isolate with no
1102
+ // drive in flight. The nudge is not lost -- the in-flight drive folds
1103
+ // `_wfNudge` into the minimum it re-arms, and if it suspended before
1104
+ // seeing the flag, the re-arm below starts a fresh drive immediately.
1105
+ // A failed drive is not re-raised here: the engine already retries the
1106
+ // alarm that fired it.
1107
+ if (this._wfDriving !== undefined) {
1108
+ this._wfNudge = Date.now();
1109
+ await this._wfDriving.catch(() => {});
1110
+ if (this._wfNudge !== undefined) {
1111
+ this._wfNudge = undefined;
1112
+ const meta = await storage.get("__wf.meta");
1113
+ if (
1114
+ meta !== undefined && !__wfTerminal(meta.status) &&
1115
+ meta.status !== "paused" && meta.status !== "waitingForPause"
1116
+ ) {
1117
+ await storage.setAlarm(Date.now());
1118
+ }
1119
+ }
1120
+ return;
1121
+ }
1122
+ const meta = await storage.get("__wf.meta");
1123
+ // A terminal instance's pending alarm can still fire. It owns retention:
1124
+ // remove expired state, or restore the expiry wake after a stale nudge.
1125
+ if (meta === undefined) {
1126
+ await storage.deleteAlarm();
1127
+ return;
1128
+ }
1129
+ if (__wfTerminal(meta.status)) {
1130
+ transactionSync(storage, (transaction) => {
1131
+ const current = transaction.kv.get("__wf.meta");
1132
+ if (current === undefined) {
1133
+ transaction.deleteAlarm();
1134
+ return;
1135
+ }
1136
+ if (!expireTerminal(transaction, current, Date.now())) {
1137
+ transaction.setAlarm(current.expiresMs);
1138
+ }
1139
+ });
1140
+ return;
1141
+ }
1142
+ if (meta.status === "paused") {
1143
+ await storage.deleteAlarm();
1144
+ return;
1145
+ }
1146
+ if (meta.status === "waitingForPause") {
1147
+ transactionSync(storage, (transaction) => {
1148
+ const current = transaction.kv.get("__wf.meta");
1149
+ if (current === undefined || current.generation !== meta.generation ||
1150
+ current.status !== "waitingForPause") return;
1151
+ current.status = "paused";
1152
+ current.pausedMs = Date.now();
1153
+ transaction.kv.put("__wf.meta", current);
1154
+ transaction.deleteAlarm();
1155
+ });
1156
+ return;
1157
+ }
1158
+ // The fired alarm stays armed while the drive runs. The engine consumes
1159
+ // it only when this handler returns cleanly, and retries it when the
1160
+ // handler fails -- that retry is the crash safety net. Deleting it here
1161
+ // would commit the deletion durably mid-drive (durability is per SQLite
1162
+ // commit, and replication ships frames per commit), so a SIGKILL before
1163
+ // the suspend-time re-arm would strand a "running" instance with no
1164
+ // alarm and no wake entry. The suspend path re-arms explicitly, and a
1165
+ // terminal drive deletes.
1166
+ const driving = this._drive(meta);
1167
+ this._wfDriving = driving;
1168
+ try {
1169
+ await driving;
1170
+ } finally {
1171
+ this._wfDriving = undefined;
1172
+ }
1173
+ }
1174
+ async _drive(meta) {
1175
+ const storage = this._state.storage;
1176
+ // The drive owns `status`, `output`, and `error`; every other piece of
1177
+ // instance state belongs to the RPCs (sendEvent's sequence lives in its
1178
+ // own key for the same reason). The gate is open at every await, so a
1179
+ // terminate can land mid-drive: re-reading before each write, and
1180
+ // writing nothing once the status is terminal, is what makes "terminate
1181
+ // wins the race" true rather than asserted. Writing back the meta this
1182
+ // drive entered with would resurrect a terminated instance as "waiting"
1183
+ // and re-arm the alarm its terminate deleted.
1184
+ const settle = (mutate) => transactionSync(storage, (transaction) => {
1185
+ const current = transaction.kv.get("__wf.meta");
1186
+ if (current === undefined || __wfTerminal(current.status) ||
1187
+ current.generation !== meta.generation) return false;
1188
+ mutate(current, transaction);
1189
+ transaction.kv.put("__wf.meta", current);
1190
+ return true;
1191
+ });
1192
+ const className = __cell.workflows[meta.workflowName];
1193
+ const cls = className === undefined ? undefined : __cf.exports[className];
1194
+ if (typeof cls !== "function" ||
1195
+ typeof (cls.prototype && cls.prototype.run) !== "function") {
1196
+ // Deploy cannot see inside the bundle, so a missing export fails
1197
+ // here, loudly, with the class and the config key named.
1198
+ settle((current, transaction) => {
1199
+ current.status = "errored";
1200
+ current.error = {
1201
+ name: "Error",
1202
+ message: className === undefined
1203
+ ? `workflow ${JSON.stringify(meta.workflowName)} is not declared ` +
1204
+ "in this deployment's `workflows`"
1205
+ : `workflow class ${JSON.stringify(className)} is not a module ` +
1206
+ "export with a run() method; export it and extend " +
1207
+ "WorkflowEntrypoint",
1208
+ };
1209
+ __wfFinishRetention(current, transaction);
1210
+ /*__CELLD_TEST_WORKFLOW_TERMINAL_ALARM_SET__*/
1211
+ });
1212
+ return;
1213
+ }
1214
+ const driver = {
1215
+ storage: undefined,
1216
+ storageFailure: undefined,
1217
+ generation: meta.generation,
1218
+ ledgerPrefix: __wfLedgerPrefix(meta.generation),
1219
+ counts: new Map(),
1220
+ ordinal: 0,
1221
+ running: 0,
1222
+ blocked: [],
1223
+ pauseRequested: false,
1224
+ finished: false,
1225
+ checkQueued: false,
1226
+ stallTimer: undefined,
1227
+ clearStall() {
1228
+ if (this.stallTimer !== undefined) {
1229
+ clearTimeout(this.stallTimer);
1230
+ this.stallTimer = undefined;
1231
+ }
1232
+ },
1233
+ };
1234
+ driver.storage = __wfTrackStorageFailures(storage, driver);
1235
+ const outcome = new Promise((resolve) => {
1236
+ driver.finish = (result) => {
1237
+ if (driver.finished) return;
1238
+ driver.finished = true;
1239
+ driver.clearStall();
1240
+ resolve(result);
1241
+ };
1242
+ });
1243
+ // Quiescence: when a step is blocked, no step callback is executing,
1244
+ // and a macrotask has passed, nothing can progress, so the invocation
1245
+ // suspends. The macrotask lets every microtask chain a completed step
1246
+ // unblocked drain first; checking on the spot would suspend an instance
1247
+ // whose next step is one `await` away from starting.
1248
+ driver.check = () => {
1249
+ if (driver.checkQueued || driver.finished) return;
1250
+ driver.checkQueued = true;
1251
+ setTimeout(() => {
1252
+ driver.checkQueued = false;
1253
+ if (driver.finished || driver.running !== 0) return;
1254
+ if (driver.pauseRequested) {
1255
+ driver.finish({ kind: "pause" });
1256
+ return;
1257
+ }
1258
+ if (driver.blocked.length > 0) {
1259
+ driver.finish({ kind: "suspend" });
1260
+ return;
1261
+ }
1262
+ // No step runs, none is blocked, and run() has not settled: it
1263
+ // awaits something that is not a step. Upstream permits that -- an
1264
+ // un-stepped `await fetch()` between steps is legal, merely
1265
+ // non-durable -- so this is not yet an error; but a promise that
1266
+ // never settles would wedge the alarm handler in silence, with the
1267
+ // instance "running" forever. A grace timer splits the two: if the
1268
+ // same nothing-can-progress state still holds when it fires, the
1269
+ // instance fails loudly. The timer is a plain in-isolate timeout,
1270
+ // not a durable alarm, on purpose: an eviction or a restart replays
1271
+ // run() from the top anyway, so losing the timer loses nothing --
1272
+ // the replay re-enters the same stall and re-arms its own grace.
1273
+ if (driver.stallTimer !== undefined) return;
1274
+ driver.stallTimer = setTimeout(() => {
1275
+ driver.stallTimer = undefined;
1276
+ if (driver.finished || driver.running !== 0 || driver.blocked.length > 0) {
1277
+ return;
1278
+ }
1279
+ driver.finish({
1280
+ kind: "error",
1281
+ error: __wfError(
1282
+ "run() has been awaiting something that is not a step for " +
1283
+ (__WF_STALL_GRACE_MS / 1000) + " seconds while no step is " +
1284
+ "running or blocked; a replay cannot resume this wait, so " +
1285
+ "wrap long async work in step.do",
1286
+ ),
1287
+ });
1288
+ }, __WF_STALL_GRACE_MS);
1289
+ }, 0);
1290
+ };
1291
+ if (
1292
+ meta.status !== "running" &&
1293
+ !(await settle((current) => {
1294
+ current.status = "running";
1295
+ }))
1296
+ ) {
1297
+ return;
1298
+ }
1299
+ const step = __wfMakeStep(driver);
1300
+ const event = {
1301
+ payload: meta.params,
1302
+ timestamp: new Date(meta.createdMs),
1303
+ instanceId: meta.instanceId,
1304
+ workflowName: meta.workflowName,
1305
+ };
1306
+ // ExecutionContext-shaped, as the WorkflowEntrypoint constructor
1307
+ // documents upstream.
1308
+ const ctx = {
1309
+ waitUntil: __registerWaitUntil,
1310
+ passThroughOnException() {},
1311
+ };
1312
+ (async () => new cls(ctx, __cell.env).run(event, step))().then(
1313
+ (value) => driver.finish({ kind: "complete", value }),
1314
+ (error) => driver.finish({ kind: "error", error }),
1315
+ );
1316
+ /*__CELLD_TEST_WORKFLOW_AFTER_RUN_STARTED__*/
1317
+ let result = await outcome;
1318
+ // The workflow can catch a rejected step promise, but it cannot turn an
1319
+ // engine storage failure into a successful run. Preserve the alarm so
1320
+ // the runtime retries over the last committed ledger prefix.
1321
+ if (driver.storageFailure !== undefined) {
1322
+ throw driver.storageFailure;
1323
+ }
1324
+ const currentMeta = await storage.get("__wf.meta");
1325
+ if (
1326
+ result.kind === "pause" ||
1327
+ (currentMeta !== undefined &&
1328
+ currentMeta.generation === meta.generation &&
1329
+ currentMeta.status === "waitingForPause")
1330
+ ) {
1331
+ /*__CELLD_TEST_WORKFLOW_BEFORE_PAUSE_SETTLE__*/
1332
+ settle((current, transaction) => {
1333
+ // A resume can cancel waitingForPause after this drive selected its
1334
+ // pause result. Preserve that newer running state and its alarm, or
1335
+ // this stale settlement strands an acknowledged resume as paused.
1336
+ if (current.status !== "waitingForPause") return;
1337
+ current.status = "paused";
1338
+ current.pausedMs = Date.now();
1339
+ transaction.deleteAlarm();
1340
+ });
1341
+ return;
1342
+ }
1343
+ if (result.kind === "suspend") {
1344
+ let at = Math.min(...driver.blocked.map((entry) => entry.at));
1345
+ // A nudge that landed mid-drive must not wait out the pending
1346
+ // deadline. It is visible in one of two places: getAlarm(), when the
1347
+ // nudge re-armed and no second alarm dispatched yet, or `_wfNudge`,
1348
+ // when a second alarm() already entered and parked on the drive
1349
+ // guard -- its dispatch consumed the armed alarm, so getAlarm()
1350
+ // alone would miss it.
1351
+ const nudge = await storage.getAlarm();
1352
+ if (nudge !== null) at = Math.min(at, nudge);
1353
+ if (this._wfNudge !== undefined) {
1354
+ at = Math.min(at, this._wfNudge);
1355
+ this._wfNudge = undefined;
1356
+ }
1357
+ settle((current, transaction) => {
1358
+ current.status = "waiting";
1359
+ transaction.setAlarm(at);
1360
+ });
1361
+ // The explicit re-arm replaces the alarm that fired this drive.
1362
+ // Durability is per SQLite commit, not per turn: a crash can persist
1363
+ // any prefix of this invocation's ledger writes, and the unconsumed
1364
+ // fired alarm is what re-drives the replay over whatever prefix
1365
+ // survived. A terminated instance re-arms nothing; its leftover
1366
+ // alarm fires once, sees the terminal status, and retires itself.
1367
+ return;
1368
+ }
1369
+ if (result.kind === "complete") {
1370
+ try {
1371
+ if (result.value !== undefined) {
1372
+ __wfCheckValue(result.value, "the run() return value");
1373
+ }
1374
+ } catch (error) {
1375
+ result = { kind: "error", error };
1376
+ }
1377
+ if (result.kind === "complete") {
1378
+ // Keep settlement outside the value-validation catch. A storage
1379
+ // failure must escape the alarm handler so the engine retries the
1380
+ // still-armed drive; converting it to a user error would consume the
1381
+ // alarm and make a transient commit failure terminal.
1382
+ settle((current, transaction) => {
1383
+ current.status = "complete";
1384
+ if (result.value !== undefined) current.output = result.value;
1385
+ // The terminal commit installs the expiry wake with the retained
1386
+ // state, so a crash can expose neither without the other.
1387
+ __wfFinishRetention(current, transaction);
1388
+ /*__CELLD_TEST_WORKFLOW_TERMINAL_ALARM_SET__*/
1389
+ });
1390
+ return;
1391
+ }
1392
+ }
1393
+ settle((current, transaction) => {
1394
+ current.status = "errored";
1395
+ current.error = __wfErrorRecord(result.error);
1396
+ __wfFinishRetention(current, transaction);
1397
+ /*__CELLD_TEST_WORKFLOW_TERMINAL_ALARM_SET__*/
1398
+ });
1399
+ }
1400
+ };
1401
+ })();
1402
+ __runtimeClass("__Workflow", __WorkflowCell);
1403
+
1404
+ class NonRetryableError extends Error {
1405
+ constructor(message, name = "NonRetryableError") {
1406
+ super(message);
1407
+ this.name = name;
1408
+ }
1409
+ }
1410
+ // Backing object for the `cloudflare:workflows` builtin module.
1411
+ __celld.__cfWorkflows = { NonRetryableError };
1412
+ // `celld:python`, imported only by the Python Worker bundle.
1413
+ __celld.__pythonHost = {
1414
+ condemnIsolate: (reason) => __isolate_condemn(String(reason)),
1415
+ };
1416
+
1417
+ const __wfValidInstanceId = (id) =>
1418
+ typeof id === "string" && id.length >= 1 && id.length <= 100 &&
1419
+ __WF_NAME_RE.test(id);
1420
+ const __wfCreateOptions = (options, what, validateParams = true) => {
1421
+ if (!__wfOwnObject(options)) {
1422
+ throw __wfError(`${what} needs an object`);
1423
+ }
1424
+ const retention = __wfRetentionOptions(options.retention, what);
1425
+ const locationHint = __wfLocationHint(options.locationHint, what);
1426
+ const id = options.id === undefined ? crypto.randomUUID() : options.id;
1427
+ if (!__wfValidInstanceId(id)) {
1428
+ throw __wfError(
1429
+ `invalid instance id ${JSON.stringify(id)}: use letters, digits, ` +
1430
+ "\"-\" and \"_\" (not starting with \"-\"), at most 100 characters",
1431
+ );
1432
+ }
1433
+ if (validateParams && options.params !== undefined) {
1434
+ // The cell repeats this serialization check because it owns the durable
1435
+ // create invariant. A single create rejects before it addresses the cell.
1436
+ __wfCheckValue(options.params, "the workflow params");
1437
+ }
1438
+ return { id, params: options.params, retention, locationHint };
1439
+ };
1440
+
1441
+ // Named so SDKs that sniff a binding by `constructor.name` recognise it.
1442
+ class WorkflowInstance {
1443
+ constructor(workflowName, id) {
1444
+ Object.defineProperty(this, "_workflowName", { value: workflowName });
1445
+ this.id = id;
1446
+ }
1447
+ // Resolved per call rather than cached: a cell can move between calls.
1448
+ get _stub() {
1449
+ // The reserved class is script-scoped (`deploy::workflow_class`), because
1450
+ // a workflow instance is: two co-hosted scripts must not share one entry
1451
+ // in the deployment's flat class registry. `__cell.script` is injected
1452
+ // before any binding runs, so this resolves at call time.
1453
+ return __cell.makeNamespace("__Workflow." + __cell.script)
1454
+ .getByName(this._workflowName + "/" + this.id);
1455
+ }
1456
+ async status() {
1457
+ return await this._stub.__wfStatus();
1458
+ }
1459
+ async terminate(options) {
1460
+ if (options && options.rollback) {
1461
+ throw __wfError(
1462
+ "terminate({rollback: true}) is not implemented in celld; step " +
1463
+ "rollbackOptions are not implemented, so no handler could run",
1464
+ );
1465
+ }
1466
+ await this._stub.__wfTerminate();
1467
+ }
1468
+ async sendEvent(options) {
1469
+ await this._stub.__wfSendEvent(__wfEventOptions(options));
1470
+ }
1471
+ async pause() {
1472
+ await this._stub.__wfPause();
1473
+ }
1474
+ async resume() {
1475
+ await this._stub.__wfResume();
1476
+ }
1477
+ async restart(options) {
1478
+ await this._stub.__wfRestart(__wfRestartOptions(options));
1479
+ }
1480
+ async delete() {
1481
+ try {
1482
+ await this._stub.__wfDelete();
1483
+ } catch (error) {
1484
+ // The deletion commit resets an active Workflow cell so an in-flight
1485
+ // callback cannot put old ledger state back. That reset rejects this
1486
+ // RPC with its private completion marker; it is the successful path.
1487
+ if (!String(error && error.message || error).includes("WORKFLOW_INSTANCE_DELETED")) {
1488
+ throw error;
1489
+ }
1490
+ }
1491
+ }
1492
+ }
1493
+
1494
+ class Workflow {
1495
+ constructor(workflowName) {
1496
+ Object.defineProperty(this, "_workflowName", { value: workflowName });
1497
+ }
1498
+ async _create(options, skipExisting) {
1499
+ const { id, params, retention, locationHint } = options;
1500
+ const instance = new WorkflowInstance(this._workflowName, id);
1501
+ const result = await instance._stub.__wfCreate({
1502
+ workflowName: this._workflowName,
1503
+ instanceId: id,
1504
+ params,
1505
+ retention,
1506
+ locationHint,
1507
+ skipExisting,
1508
+ });
1509
+ return result.created ? instance : undefined;
1510
+ }
1511
+ async create(options = {}) {
1512
+ return await this._create(__wfCreateOptions(options, "create()"), false);
1513
+ }
1514
+ async createBatch(batch) {
1515
+ if (!Array.isArray(batch)) {
1516
+ throw __wfError("createBatch() needs an array of create options");
1517
+ }
1518
+ if (batch.length > 100) {
1519
+ throw __wfError(
1520
+ `createBatch() accepts at most 100 instances, got ${batch.length}`,
1521
+ );
1522
+ }
1523
+ if (batch.length === 0) {
1524
+ throw __wfError("createBatch() needs at least one instance");
1525
+ }
1526
+ // Cloudflare rejects a structural error in any item before it creates the
1527
+ // first instance, but it filters a params clone failure per item. Keep
1528
+ // these two gates separate so a bad ID stays atomic while a clone failure
1529
+ // does not suppress the valid items around it.
1530
+ const optionsList = batch.map((options, index) =>
1531
+ __wfCreateOptions(options, `createBatch() item ${index}`, false));
1532
+ const creatable = [];
1533
+ for (const options of optionsList) {
1534
+ let encoded;
1535
+ try {
1536
+ if (options.params !== undefined) {
1537
+ encoded = __wfEncodeValue(options.params, "the workflow params");
1538
+ }
1539
+ } catch {
1540
+ continue;
1541
+ }
1542
+ if (encoded !== undefined) {
1543
+ __wfCheckEncodedValue(encoded, "the workflow params");
1544
+ }
1545
+ creatable.push(options);
1546
+ }
1547
+ const created = [];
1548
+ for (const options of creatable) {
1549
+ const instance = await this._create(options, true);
1550
+ if (instance !== undefined) created.push(instance);
1551
+ }
1552
+ return created;
1553
+ }
1554
+ async get(id) {
1555
+ if (!__wfValidInstanceId(id)) {
1556
+ throw __wfError(`invalid instance id ${JSON.stringify(id)}`);
1557
+ }
1558
+ const instance = new WorkflowInstance(this._workflowName, id);
1559
+ // The cell exists on first address like any cell, but get() must not
1560
+ // mint an instance for a typo: an id with no ledger rejects here.
1561
+ await instance._stub.__wfStatus();
1562
+ return instance;
1563
+ }
1564
+ async deleteBatch(instanceIds) {
1565
+ if (!Array.isArray(instanceIds)) {
1566
+ throw __wfError("deleteBatch() needs an array of instance ids");
1567
+ }
1568
+ if (instanceIds.length < 1 || instanceIds.length > 100) {
1569
+ throw __wfError(
1570
+ `deleteBatch() accepts 1 to 100 instance ids, got ${instanceIds.length}`,
1571
+ );
1572
+ }
1573
+ for (const id of instanceIds) {
1574
+ if (!__wfValidInstanceId(id)) {
1575
+ throw __wfError(`invalid instance id ${JSON.stringify(id)}`);
1576
+ }
1577
+ }
1578
+ const uniqueIds = [...new Set(instanceIds)];
1579
+ const settled = await Promise.allSettled(uniqueIds.map(async (id) => {
1580
+ const instance = new WorkflowInstance(this._workflowName, id);
1581
+ await instance.delete();
1582
+ }));
1583
+ const results = new Map(uniqueIds.map((id, index) => [id, settled[index]]));
1584
+ const output = { deleted: [], errors: [] };
1585
+ for (const id of instanceIds) {
1586
+ const result = results.get(id);
1587
+ if (result.status === "fulfilled") {
1588
+ output.deleted.push({ id });
1589
+ continue;
1590
+ }
1591
+ const notFound = String(result.reason && result.reason.message || result.reason)
1592
+ .includes("instance does not exist");
1593
+ output.errors.push({
1594
+ id,
1595
+ code: notFound ? 10400 : 10001,
1596
+ message: notFound
1597
+ ? "workflows.api.error.instance.not_found"
1598
+ : "workflows.api.error.internal_server",
1599
+ });
1600
+ }
1601
+ return output;
1602
+ }
1603
+ }
1604
+
1605
+ const __makeWorkflow = __celld.__makeWorkflow =
1606
+ (workflowName) => new Workflow(workflowName);
1607
+
1608
+ return { WorkflowCell: classes.__Workflow, makeWorkflow: __celld.__makeWorkflow,
1609
+ NonRetryableError: __celld.__cfWorkflows.NonRetryableError };
1610
+ }