@shware/workflow 3.2.0 → 4.0.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 (364) hide show
  1. package/README.md +100 -29
  2. package/dist/apns.cjs +113 -26
  3. package/dist/apns.cjs.map +1 -1
  4. package/dist/apns.d.cts +52 -6
  5. package/dist/apns.d.cts.map +1 -1
  6. package/dist/apns.d.mts +52 -6
  7. package/dist/apns.d.mts.map +1 -1
  8. package/dist/apns.mjs +111 -27
  9. package/dist/apns.mjs.map +1 -1
  10. package/dist/aws/engine-step.cjs +5 -5
  11. package/dist/aws/engine-step.cjs.map +1 -1
  12. package/dist/aws/engine-step.d.cts +8 -2
  13. package/dist/aws/engine-step.d.cts.map +1 -1
  14. package/dist/aws/engine-step.d.mts +8 -2
  15. package/dist/aws/engine-step.d.mts.map +1 -1
  16. package/dist/aws/engine-step.mjs +5 -5
  17. package/dist/aws/engine-step.mjs.map +1 -1
  18. package/dist/aws/index.cjs +47 -6
  19. package/dist/aws/index.cjs.map +1 -1
  20. package/dist/aws/index.d.cts +2 -2
  21. package/dist/aws/index.d.cts.map +1 -1
  22. package/dist/aws/index.d.mts +2 -2
  23. package/dist/aws/index.d.mts.map +1 -1
  24. package/dist/aws/index.mjs +47 -6
  25. package/dist/aws/index.mjs.map +1 -1
  26. package/dist/aws/instances.cjs +29 -6
  27. package/dist/aws/instances.cjs.map +1 -1
  28. package/dist/aws/instances.d.cts +11 -1
  29. package/dist/aws/instances.d.cts.map +1 -1
  30. package/dist/aws/instances.d.mts +11 -1
  31. package/dist/aws/instances.d.mts.map +1 -1
  32. package/dist/aws/instances.mjs +27 -4
  33. package/dist/aws/instances.mjs.map +1 -1
  34. package/dist/client.cjs +4 -1
  35. package/dist/client.cjs.map +1 -1
  36. package/dist/client.d.cts +2 -2
  37. package/dist/client.d.cts.map +1 -1
  38. package/dist/client.d.mts +2 -2
  39. package/dist/client.d.mts.map +1 -1
  40. package/dist/client.mjs +4 -1
  41. package/dist/client.mjs.map +1 -1
  42. package/dist/cloudflare/bindings.cjs +28 -25
  43. package/dist/cloudflare/bindings.cjs.map +1 -1
  44. package/dist/cloudflare/bindings.d.cts +17 -129
  45. package/dist/cloudflare/bindings.d.cts.map +1 -1
  46. package/dist/cloudflare/bindings.d.mts +17 -129
  47. package/dist/cloudflare/bindings.d.mts.map +1 -1
  48. package/dist/cloudflare/bindings.mjs +28 -23
  49. package/dist/cloudflare/bindings.mjs.map +1 -1
  50. package/dist/cloudflare/d1-env.cjs +6 -6
  51. package/dist/cloudflare/d1-env.cjs.map +1 -1
  52. package/dist/cloudflare/d1-env.d.cts +5 -5
  53. package/dist/cloudflare/d1-env.d.mts +5 -5
  54. package/dist/cloudflare/d1-env.mjs +6 -6
  55. package/dist/cloudflare/d1-env.mjs.map +1 -1
  56. package/dist/cloudflare/index.cjs +28 -18
  57. package/dist/cloudflare/index.d.cts +8 -4
  58. package/dist/cloudflare/index.d.mts +8 -4
  59. package/dist/cloudflare/index.mjs +9 -5
  60. package/dist/cloudflare/runner.cjs +29 -14
  61. package/dist/cloudflare/runner.cjs.map +1 -1
  62. package/dist/cloudflare/runner.d.cts +9 -2
  63. package/dist/cloudflare/runner.d.cts.map +1 -1
  64. package/dist/cloudflare/runner.d.mts +9 -2
  65. package/dist/cloudflare/runner.d.mts.map +1 -1
  66. package/dist/cloudflare/runner.mjs +28 -13
  67. package/dist/cloudflare/runner.mjs.map +1 -1
  68. package/dist/cloudflare/schema.sql +27 -6
  69. package/dist/cloudflare/worker.cjs +19 -9
  70. package/dist/cloudflare/worker.cjs.map +1 -1
  71. package/dist/cloudflare/worker.d.cts +2 -2
  72. package/dist/cloudflare/worker.d.cts.map +1 -1
  73. package/dist/cloudflare/worker.d.mts +2 -2
  74. package/dist/cloudflare/worker.d.mts.map +1 -1
  75. package/dist/cloudflare/worker.mjs +19 -9
  76. package/dist/cloudflare/worker.mjs.map +1 -1
  77. package/dist/config.cjs +21 -5
  78. package/dist/config.cjs.map +1 -1
  79. package/dist/config.mjs +21 -5
  80. package/dist/config.mjs.map +1 -1
  81. package/dist/dsl/action.cjs +3 -6
  82. package/dist/dsl/action.cjs.map +1 -1
  83. package/dist/dsl/action.d.cts +12 -11
  84. package/dist/dsl/action.d.cts.map +1 -1
  85. package/dist/dsl/action.d.mts +12 -11
  86. package/dist/dsl/action.d.mts.map +1 -1
  87. package/dist/dsl/action.mjs +3 -6
  88. package/dist/dsl/action.mjs.map +1 -1
  89. package/dist/dsl/bundle.cjs +35 -31
  90. package/dist/dsl/bundle.cjs.map +1 -1
  91. package/dist/dsl/bundle.d.cts.map +1 -1
  92. package/dist/dsl/bundle.d.mts.map +1 -1
  93. package/dist/dsl/bundle.mjs +35 -31
  94. package/dist/dsl/bundle.mjs.map +1 -1
  95. package/dist/dsl/condition.cjs +13 -1
  96. package/dist/dsl/condition.cjs.map +1 -1
  97. package/dist/dsl/condition.d.cts +9 -1
  98. package/dist/dsl/condition.d.cts.map +1 -1
  99. package/dist/dsl/condition.d.mts +9 -1
  100. package/dist/dsl/condition.d.mts.map +1 -1
  101. package/dist/dsl/condition.mjs +13 -2
  102. package/dist/dsl/condition.mjs.map +1 -1
  103. package/dist/dsl/flow.cjs +33 -11
  104. package/dist/dsl/flow.cjs.map +1 -1
  105. package/dist/dsl/flow.d.cts +9 -1
  106. package/dist/dsl/flow.d.cts.map +1 -1
  107. package/dist/dsl/flow.d.mts +9 -1
  108. package/dist/dsl/flow.d.mts.map +1 -1
  109. package/dist/dsl/flow.mjs +34 -13
  110. package/dist/dsl/flow.mjs.map +1 -1
  111. package/dist/dsl/workflow.cjs +2 -3
  112. package/dist/dsl/workflow.cjs.map +1 -1
  113. package/dist/dsl/workflow.d.cts.map +1 -1
  114. package/dist/dsl/workflow.d.mts.map +1 -1
  115. package/dist/dsl/workflow.mjs +3 -4
  116. package/dist/dsl/workflow.mjs.map +1 -1
  117. package/dist/email-sender.cjs +24 -0
  118. package/dist/email-sender.cjs.map +1 -1
  119. package/dist/email-sender.d.cts +18 -0
  120. package/dist/email-sender.d.cts.map +1 -1
  121. package/dist/email-sender.d.mts +18 -0
  122. package/dist/email-sender.d.mts.map +1 -1
  123. package/dist/email-sender.mjs +24 -1
  124. package/dist/email-sender.mjs.map +1 -1
  125. package/dist/engine/actions.cjs +1 -8
  126. package/dist/engine/actions.cjs.map +1 -1
  127. package/dist/engine/actions.d.cts +1 -12
  128. package/dist/engine/actions.d.cts.map +1 -1
  129. package/dist/engine/actions.d.mts +1 -12
  130. package/dist/engine/actions.d.mts.map +1 -1
  131. package/dist/engine/actions.mjs +1 -8
  132. package/dist/engine/actions.mjs.map +1 -1
  133. package/dist/engine/condition.cjs +25 -5
  134. package/dist/engine/condition.cjs.map +1 -1
  135. package/dist/engine/condition.d.cts +12 -3
  136. package/dist/engine/condition.d.cts.map +1 -1
  137. package/dist/engine/condition.d.mts +12 -3
  138. package/dist/engine/condition.d.mts.map +1 -1
  139. package/dist/engine/condition.mjs +25 -6
  140. package/dist/engine/condition.mjs.map +1 -1
  141. package/dist/engine/index.cjs +3 -0
  142. package/dist/engine/index.d.cts +5 -5
  143. package/dist/engine/index.d.mts +5 -5
  144. package/dist/engine/index.mjs +4 -4
  145. package/dist/engine/interpreter.cjs +27 -20
  146. package/dist/engine/interpreter.cjs.map +1 -1
  147. package/dist/engine/interpreter.d.cts.map +1 -1
  148. package/dist/engine/interpreter.d.mts.map +1 -1
  149. package/dist/engine/interpreter.mjs +27 -20
  150. package/dist/engine/interpreter.mjs.map +1 -1
  151. package/dist/engine/ports.d.cts +21 -6
  152. package/dist/engine/ports.d.cts.map +1 -1
  153. package/dist/engine/ports.d.mts +21 -6
  154. package/dist/engine/ports.d.mts.map +1 -1
  155. package/dist/engine/retry.cjs +13 -0
  156. package/dist/engine/retry.cjs.map +1 -1
  157. package/dist/engine/retry.d.cts +12 -0
  158. package/dist/engine/retry.d.cts.map +1 -1
  159. package/dist/engine/retry.d.mts +12 -0
  160. package/dist/engine/retry.d.mts.map +1 -1
  161. package/dist/engine/retry.mjs +13 -1
  162. package/dist/engine/retry.mjs.map +1 -1
  163. package/dist/engine/time-scale.cjs +90 -15
  164. package/dist/engine/time-scale.cjs.map +1 -1
  165. package/dist/engine/time-scale.d.cts +9 -6
  166. package/dist/engine/time-scale.d.cts.map +1 -1
  167. package/dist/engine/time-scale.d.mts +9 -6
  168. package/dist/engine/time-scale.d.mts.map +1 -1
  169. package/dist/engine/time-scale.mjs +90 -16
  170. package/dist/engine/time-scale.mjs.map +1 -1
  171. package/dist/fcm.cjs +111 -38
  172. package/dist/fcm.cjs.map +1 -1
  173. package/dist/fcm.d.cts +45 -5
  174. package/dist/fcm.d.cts.map +1 -1
  175. package/dist/fcm.d.mts +45 -5
  176. package/dist/fcm.d.mts.map +1 -1
  177. package/dist/fcm.mjs +110 -39
  178. package/dist/fcm.mjs.map +1 -1
  179. package/dist/hash.cjs +6 -6
  180. package/dist/hash.cjs.map +1 -1
  181. package/dist/hash.mjs +6 -6
  182. package/dist/hash.mjs.map +1 -1
  183. package/dist/host/auth.cjs +49 -0
  184. package/dist/host/auth.cjs.map +1 -0
  185. package/dist/host/auth.d.cts +21 -0
  186. package/dist/host/auth.d.cts.map +1 -0
  187. package/dist/host/auth.d.mts +21 -0
  188. package/dist/host/auth.d.mts.map +1 -0
  189. package/dist/host/auth.mjs +47 -0
  190. package/dist/host/auth.mjs.map +1 -0
  191. package/dist/host/channels.cjs +118 -40
  192. package/dist/host/channels.cjs.map +1 -1
  193. package/dist/host/channels.d.cts +27 -1
  194. package/dist/host/channels.d.cts.map +1 -1
  195. package/dist/host/channels.d.mts +27 -1
  196. package/dist/host/channels.d.mts.map +1 -1
  197. package/dist/host/channels.mjs +121 -44
  198. package/dist/host/channels.mjs.map +1 -1
  199. package/dist/host/execute.cjs +14 -2
  200. package/dist/host/execute.cjs.map +1 -1
  201. package/dist/host/execute.d.cts +4 -4
  202. package/dist/host/execute.d.cts.map +1 -1
  203. package/dist/host/execute.d.mts +4 -4
  204. package/dist/host/execute.d.mts.map +1 -1
  205. package/dist/host/execute.mjs +13 -1
  206. package/dist/host/execute.mjs.map +1 -1
  207. package/dist/host/fresh-read.cjs +30 -0
  208. package/dist/host/fresh-read.cjs.map +1 -0
  209. package/dist/host/fresh-read.d.cts +18 -0
  210. package/dist/host/fresh-read.d.cts.map +1 -0
  211. package/dist/host/fresh-read.d.mts +18 -0
  212. package/dist/host/fresh-read.d.mts.map +1 -0
  213. package/dist/host/fresh-read.mjs +29 -0
  214. package/dist/host/fresh-read.mjs.map +1 -0
  215. package/dist/host/http.cjs +321 -0
  216. package/dist/host/http.cjs.map +1 -0
  217. package/dist/host/http.d.cts +54 -0
  218. package/dist/host/http.d.cts.map +1 -0
  219. package/dist/host/http.d.mts +54 -0
  220. package/dist/host/http.d.mts.map +1 -0
  221. package/dist/host/http.mjs +317 -0
  222. package/dist/host/http.mjs.map +1 -0
  223. package/dist/host/{router.cjs → ingest.cjs} +147 -175
  224. package/dist/host/ingest.cjs.map +1 -0
  225. package/dist/host/{router.d.cts → ingest.d.cts} +48 -30
  226. package/dist/host/ingest.d.cts.map +1 -0
  227. package/dist/host/{router.d.mts → ingest.d.mts} +48 -30
  228. package/dist/host/ingest.d.mts.map +1 -0
  229. package/dist/host/{router.mjs → ingest.mjs} +144 -173
  230. package/dist/host/ingest.mjs.map +1 -0
  231. package/dist/host/journey-host.cjs +65 -93
  232. package/dist/host/journey-host.cjs.map +1 -1
  233. package/dist/host/journey-host.d.cts +19 -26
  234. package/dist/host/journey-host.d.cts.map +1 -1
  235. package/dist/host/journey-host.d.mts +19 -26
  236. package/dist/host/journey-host.d.mts.map +1 -1
  237. package/dist/host/journey-host.mjs +65 -93
  238. package/dist/host/journey-host.mjs.map +1 -1
  239. package/dist/host/platform.cjs +21 -0
  240. package/dist/host/platform.cjs.map +1 -0
  241. package/dist/host/platform.d.cts +126 -0
  242. package/dist/host/platform.d.cts.map +1 -0
  243. package/dist/host/platform.d.mts +126 -0
  244. package/dist/host/platform.d.mts.map +1 -0
  245. package/dist/host/platform.mjs +19 -0
  246. package/dist/host/platform.mjs.map +1 -0
  247. package/dist/host/senders.cjs +18 -9
  248. package/dist/host/senders.cjs.map +1 -1
  249. package/dist/host/senders.d.cts +9 -4
  250. package/dist/host/senders.d.cts.map +1 -1
  251. package/dist/host/senders.d.mts +9 -4
  252. package/dist/host/senders.d.mts.map +1 -1
  253. package/dist/host/senders.mjs +18 -9
  254. package/dist/host/senders.mjs.map +1 -1
  255. package/dist/index.cjs +1 -1
  256. package/dist/index.mjs +1 -1
  257. package/dist/ir.cjs +5 -7
  258. package/dist/ir.cjs.map +1 -1
  259. package/dist/ir.d.cts +3 -12
  260. package/dist/ir.d.cts.map +1 -1
  261. package/dist/ir.d.mts +3 -12
  262. package/dist/ir.d.mts.map +1 -1
  263. package/dist/ir.mjs +5 -7
  264. package/dist/ir.mjs.map +1 -1
  265. package/dist/plan.cjs +27 -33
  266. package/dist/plan.cjs.map +1 -1
  267. package/dist/plan.d.cts +1 -1
  268. package/dist/plan.d.cts.map +1 -1
  269. package/dist/plan.d.mts +1 -1
  270. package/dist/plan.d.mts.map +1 -1
  271. package/dist/plan.mjs +27 -33
  272. package/dist/plan.mjs.map +1 -1
  273. package/dist/reporting/observer.cjs +1 -0
  274. package/dist/reporting/observer.cjs.map +1 -1
  275. package/dist/reporting/observer.mjs +1 -0
  276. package/dist/reporting/observer.mjs.map +1 -1
  277. package/dist/reporting/tracking.cjs +19 -4
  278. package/dist/reporting/tracking.cjs.map +1 -1
  279. package/dist/reporting/tracking.d.cts +13 -11
  280. package/dist/reporting/tracking.d.cts.map +1 -1
  281. package/dist/reporting/tracking.d.mts +13 -11
  282. package/dist/reporting/tracking.d.mts.map +1 -1
  283. package/dist/reporting/tracking.mjs +19 -4
  284. package/dist/reporting/tracking.mjs.map +1 -1
  285. package/dist/store/d1.cjs +88 -29
  286. package/dist/store/d1.cjs.map +1 -1
  287. package/dist/store/d1.d.cts +29 -11
  288. package/dist/store/d1.d.cts.map +1 -1
  289. package/dist/store/d1.d.mts +29 -11
  290. package/dist/store/d1.d.mts.map +1 -1
  291. package/dist/store/d1.mjs +88 -29
  292. package/dist/store/d1.mjs.map +1 -1
  293. package/dist/store/erasure.cjs +22 -0
  294. package/dist/store/erasure.cjs.map +1 -0
  295. package/dist/store/erasure.d.cts +18 -0
  296. package/dist/store/erasure.d.cts.map +1 -0
  297. package/dist/store/erasure.d.mts +18 -0
  298. package/dist/store/erasure.d.mts.map +1 -0
  299. package/dist/store/erasure.mjs +21 -0
  300. package/dist/store/erasure.mjs.map +1 -0
  301. package/dist/store/index.cjs +2 -0
  302. package/dist/store/index.cjs.map +1 -1
  303. package/dist/store/index.d.cts +67 -13
  304. package/dist/store/index.d.cts.map +1 -1
  305. package/dist/store/index.d.mts +67 -13
  306. package/dist/store/index.d.mts.map +1 -1
  307. package/dist/store/index.mjs +2 -1
  308. package/dist/store/index.mjs.map +1 -1
  309. package/dist/store/postgres-reporting.cjs +125 -62
  310. package/dist/store/postgres-reporting.cjs.map +1 -1
  311. package/dist/store/postgres-reporting.d.cts +30 -2
  312. package/dist/store/postgres-reporting.d.cts.map +1 -1
  313. package/dist/store/postgres-reporting.d.mts +30 -2
  314. package/dist/store/postgres-reporting.d.mts.map +1 -1
  315. package/dist/store/postgres-reporting.mjs +125 -62
  316. package/dist/store/postgres-reporting.mjs.map +1 -1
  317. package/dist/store/postgres.cjs +179 -60
  318. package/dist/store/postgres.cjs.map +1 -1
  319. package/dist/store/postgres.d.cts +44 -7
  320. package/dist/store/postgres.d.cts.map +1 -1
  321. package/dist/store/postgres.d.mts +44 -7
  322. package/dist/store/postgres.d.mts.map +1 -1
  323. package/dist/store/postgres.mjs +179 -60
  324. package/dist/store/postgres.mjs.map +1 -1
  325. package/dist/store/tables.cjs +7 -5
  326. package/dist/store/tables.cjs.map +1 -1
  327. package/dist/store/tables.d.cts.map +1 -1
  328. package/dist/store/tables.d.mts.map +1 -1
  329. package/dist/store/tables.mjs +7 -5
  330. package/dist/store/tables.mjs.map +1 -1
  331. package/dist/store/user-lock.cjs +43 -0
  332. package/dist/store/user-lock.cjs.map +1 -0
  333. package/dist/store/user-lock.d.cts +32 -0
  334. package/dist/store/user-lock.d.cts.map +1 -0
  335. package/dist/store/user-lock.d.mts +32 -0
  336. package/dist/store/user-lock.d.mts.map +1 -0
  337. package/dist/store/user-lock.mjs +40 -0
  338. package/dist/store/user-lock.mjs.map +1 -0
  339. package/dist/transports/cloudflare-email.cjs +3 -2
  340. package/dist/transports/cloudflare-email.cjs.map +1 -1
  341. package/dist/transports/cloudflare-email.mjs +3 -2
  342. package/dist/transports/cloudflare-email.mjs.map +1 -1
  343. package/dist/transports/ses.cjs +52 -7
  344. package/dist/transports/ses.cjs.map +1 -1
  345. package/dist/transports/ses.d.cts +9 -1
  346. package/dist/transports/ses.d.cts.map +1 -1
  347. package/dist/transports/ses.d.mts +9 -1
  348. package/dist/transports/ses.d.mts.map +1 -1
  349. package/dist/transports/ses.mjs +52 -8
  350. package/dist/transports/ses.mjs.map +1 -1
  351. package/dist/webpush.cjs +133 -47
  352. package/dist/webpush.cjs.map +1 -1
  353. package/dist/webpush.d.cts +49 -8
  354. package/dist/webpush.d.cts.map +1 -1
  355. package/dist/webpush.d.mts +49 -8
  356. package/dist/webpush.d.mts.map +1 -1
  357. package/dist/webpush.mjs +130 -47
  358. package/dist/webpush.mjs.map +1 -1
  359. package/package.json +1 -1
  360. package/src/cloudflare/schema.sql +27 -6
  361. package/dist/host/router.cjs.map +0 -1
  362. package/dist/host/router.d.cts.map +0 -1
  363. package/dist/host/router.d.mts.map +0 -1
  364. package/dist/host/router.mjs.map +0 -1
@@ -1,11 +1,12 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_runtime = require("../runtime.cjs");
3
2
  const require_engine_retry = require("../engine/retry.cjs");
4
- const require_cloudflare_bindings = require("./bindings.cjs");
3
+ const require_runtime = require("../runtime.cjs");
5
4
  const require_cloudflare_d1_env = require("./d1-env.cjs");
5
+ const require_host_platform = require("../host/platform.cjs");
6
6
  const require_host_execute = require("../host/execute.cjs");
7
7
  const require_engine_actions = require("../engine/actions.cjs");
8
8
  const require_host_senders = require("../host/senders.cjs");
9
+ const require_cloudflare_bindings = require("./bindings.cjs");
9
10
  let cloudflare_workers = require("cloudflare:workers");
10
11
  let cloudflare_workflows = require("cloudflare:workflows");
11
12
  //#region src/cloudflare/runner.ts
@@ -14,7 +15,8 @@ let cloudflare_workflows = require("cloudflare:workflows");
14
15
  * subscribe/unsubscribe write the subscription table inside durable steps (the
15
16
  * router wakes instances from that table); waitForWake parks on CF's
16
17
  * waitForEvent, which signals a timeout by throwing — and so does every other
17
- * failure in there, so only the clock can tell them apart (see wakeExpired).
18
+ * failure in there, so only the clock, against the checkpointed deadline, can
19
+ * tell them apart (see wakeExpired).
18
20
  */
19
21
  var CfEngineStep = class {
20
22
  step;
@@ -30,14 +32,19 @@ var CfEngineStep = class {
30
32
  /**
31
33
  * A failure a retry would only repeat (`isRetryable`: a template refusing its props, the
32
34
  * contract refusing an emitted event, a provider rejecting the request) ends the step at
33
- * once instead of burning the retry budget.
35
+ * once instead of burning the retry budget. The engine recognizes a NonRetryableError by its
36
+ * `name`, so the original's name is not written over it — the original travels as `cause`.
34
37
  */
35
38
  do(name, fn) {
36
39
  return this.step.do(name, async () => {
37
40
  try {
38
41
  return await fn();
39
42
  } catch (error) {
40
- if (error instanceof Error && !require_engine_retry.isRetryable(error)) throw new cloudflare_workflows.NonRetryableError(error.message, error.name);
43
+ if (error instanceof Error && !require_engine_retry.isRetryable(error)) {
44
+ const permanent = new cloudflare_workflows.NonRetryableError(error.message);
45
+ permanent.cause = error;
46
+ throw permanent;
47
+ }
41
48
  throw error;
42
49
  }
43
50
  });
@@ -45,25 +52,21 @@ var CfEngineStep = class {
45
52
  sleep(name, ms) {
46
53
  return this.step.sleep(name, ms);
47
54
  }
48
- sleepUntil(name, timestampMs) {
49
- return this.step.sleepUntil(name, timestampMs);
50
- }
51
55
  subscribe(name, events) {
52
56
  return this.step.do(name, () => this.store.subscribe(this.instanceId, this.userId, events, Date.now()));
53
57
  }
54
58
  unsubscribe(name) {
55
59
  return this.step.do(name, () => this.store.unsubscribe(this.instanceId));
56
60
  }
57
- async waitForWake(name, timeoutMs) {
58
- const startedAt = Date.now();
61
+ async waitForWake(name, { timeoutMs, deadlineMs }) {
59
62
  try {
60
63
  await this.step.waitForEvent(name, {
61
- type: require_cloudflare_bindings.WAKE_EVENT_TYPE,
64
+ type: require_host_platform.WAKE_EVENT_TYPE,
62
65
  timeout: timeoutMs
63
66
  });
64
67
  return "event";
65
68
  } catch (error) {
66
- if (await this.step.do(`${name}:timed-out`, async () => require_cloudflare_bindings.wakeExpired(startedAt, timeoutMs, Date.now()))) return "timeout";
69
+ if (await this.step.do(`${name}:timed-out`, async () => require_cloudflare_bindings.wakeExpired(deadlineMs, Date.now()))) return "timeout";
67
70
  throw error;
68
71
  }
69
72
  }
@@ -76,7 +79,7 @@ var CfEngineStep = class {
76
79
  */
77
80
  var JourneyRunner = class extends cloudflare_workers.WorkflowEntrypoint {
78
81
  /**
79
- * The data plane. D1 + KV from the env by default; an app on Postgres
82
+ * The data plane. D1 from the env by default; an app on Postgres
80
83
  * overrides this (and `closeStore` if its client needs closing):
81
84
  *
82
85
  * protected override createStore() { return new PostgresJourneyStore(client(this.env)); }
@@ -86,6 +89,18 @@ var JourneyRunner = class extends cloudflare_workers.WorkflowEntrypoint {
86
89
  }
87
90
  /** Called once `run` is over, success or not — release whatever `createStore` opened. */
88
91
  async closeStore(_store) {}
92
+ adapted;
93
+ /**
94
+ * The env a run ingests its emitted events with: the Workflow binding behind the host's port,
95
+ * so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the
96
+ * host drop its wakes). Built once per instance.
97
+ */
98
+ hostEnv() {
99
+ return this.adapted ??= {
100
+ ...this.env,
101
+ JOURNEY: require_cloudflare_bindings.cloudflareJourneyBinding(this.env.JOURNEY)
102
+ };
103
+ }
89
104
  /**
90
105
  * Runtime options for this instance — typed configuration, resolved by the app (typically
91
106
  * `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from
@@ -145,7 +160,7 @@ var JourneyRunner = class extends cloudflare_workers.WorkflowEntrypoint {
145
160
  createRunParts(store, params) {
146
161
  return {
147
162
  store,
148
- env: this.env,
163
+ env: this.hostEnv(),
149
164
  runtime: this.runtime(params),
150
165
  messages: this.createMessageSender(),
151
166
  actions: this.createActionInvoker(),
@@ -1 +1 @@
1
- {"version":3,"file":"runner.cjs","names":["isRetryable","NonRetryableError","WAKE_EVENT_TYPE","wakeExpired","WorkflowEntrypoint","d1Store","resolveRuntime","WebhookMessageSender","LogMessageSender","RegistryActionInvoker","executeJourney"],"sources":["../../src/cloudflare/runner.ts"],"sourcesContent":["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';\nimport { NonRetryableError } from 'cloudflare:workflows';\nimport type { Contract } from '../contract';\nimport { type RegisteredAction, RegistryActionInvoker } from '../engine/actions';\nimport type {\n ActionInvoker,\n EngineStep,\n JourneyObserver,\n JourneyOutcome,\n MessageSender,\n MessageValidator,\n} from '../engine/ports';\nimport { isRetryable } from '../engine/retry';\nimport { type JourneyRunParts, executeJourney } from '../host/execute';\nimport { LogMessageSender, WebhookMessageSender } from '../host/senders';\nimport { type JourneyRuntimeOptions, resolveRuntime } from '../runtime';\nimport type { JourneyStore } from '../store/index';\nimport { type JourneyEnv, type JourneyParams, WAKE_EVENT_TYPE, wakeExpired } from './bindings';\nimport { d1Store } from './d1-env';\n\n/**\n * Adapts CF's WorkflowStep to EngineStep.\n * subscribe/unsubscribe write the subscription table inside durable steps (the\n * router wakes instances from that table); waitForWake parks on CF's\n * waitForEvent, which signals a timeout by throwing — and so does every other\n * failure in there, so only the clock can tell them apart (see wakeExpired).\n */\nclass CfEngineStep implements EngineStep {\n constructor(\n private readonly step: WorkflowStep,\n private readonly store: JourneyStore,\n private readonly userId: string,\n private readonly instanceId: string\n ) {}\n\n /**\n * A failure a retry would only repeat (`isRetryable`: a template refusing its props, the\n * contract refusing an emitted event, a provider rejecting the request) ends the step at\n * once instead of burning the retry budget.\n */\n do<T>(name: string, fn: () => Promise<T>): Promise<T> {\n return this.step.do(name, async () => {\n try {\n return await fn();\n } catch (error) {\n if (error instanceof Error && !isRetryable(error)) {\n throw new NonRetryableError(error.message, error.name);\n }\n throw error;\n }\n });\n }\n\n sleep(name: string, ms: number): Promise<void> {\n return this.step.sleep(name, ms);\n }\n\n sleepUntil(name: string, timestampMs: number): Promise<void> {\n return this.step.sleepUntil(name, timestampMs);\n }\n\n subscribe(name: string, events: readonly string[]): Promise<void> {\n // Idempotent in the store: the interpreter re-subscribes on every wait\n // attempt (one-shot-callback contract) and registrations persist here.\n return this.step.do(name, () =>\n this.store.subscribe(this.instanceId, this.userId, events, Date.now())\n );\n }\n\n unsubscribe(name: string): Promise<void> {\n return this.step.do(name, () => this.store.unsubscribe(this.instanceId));\n }\n\n async waitForWake(name: string, timeoutMs: number): Promise<'event' | 'timeout'> {\n const startedAt = Date.now();\n try {\n await this.step.waitForEvent(name, { type: WAKE_EVENT_TYPE, timeout: timeoutMs });\n return 'event';\n } catch (error) {\n /*\n * Only the deadline can tell a real timeout from a failure (see\n * wakeExpired). `startedAt` is not replay-stable, but it is read only\n * inside the step body below, and a step body runs on the live attempt\n * alone — a replay returns the persisted verdict without touching the\n * clock.\n */\n const timedOut = await this.step.do(`${name}:timed-out`, async () =>\n wakeExpired(startedAt, timeoutMs, Date.now())\n );\n if (timedOut) return 'timeout';\n throw error;\n }\n }\n}\n\n/**\n * The generic journey executor: one class runs every workflow, because IR is\n * data. It is loaded from the store by the contentHash pinned at entry —\n * content addressing makes it immutable, so reading it outside a step is\n * replay-safe.\n */\nexport class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, JourneyParams> {\n /**\n * The data plane. D1 + KV from the env by default; an app on Postgres\n * overrides this (and `closeStore` if its client needs closing):\n *\n * protected override createStore() { return new PostgresJourneyStore(client(this.env)); }\n */\n protected createStore(): JourneyStore {\n return d1Store(this.env);\n }\n\n /** Called once `run` is over, success or not — release whatever `createStore` opened. */\n protected async closeStore(_store: JourneyStore): Promise<void> {}\n\n /**\n * Runtime options for this instance — typed configuration, resolved by the app (typically\n * `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from\n * an ENVIRONMENT var). The instance params are passed so the decision can also be per user:\n * a tester account in production can run in fast-forward while everyone else runs in real\n * time. Defaults: real time, quiet.\n *\n * protected override runtime({ userId }) {\n * return resolveRuntime(workflowConfig.runtime, isTester(userId) ? 'test' : this.env.ENVIRONMENT);\n * }\n */\n protected runtime(_params: JourneyParams): JourneyRuntimeOptions {\n return resolveRuntime(undefined, 'production');\n }\n\n /**\n * Message outlet; an app overrides this to plug in real channels (say\n * an EmailSender over a transport with a react-email renderer). By default it picks the webhook\n * or logging sender based on the environment.\n */\n protected createMessageSender(): MessageSender {\n return this.env.MESSAGE_WEBHOOK_URL\n ? new WebhookMessageSender(this.env.MESSAGE_WEBHOOK_URL)\n : new LogMessageSender();\n }\n\n /**\n * Custom-action registry; an app overrides this returning the same\n * `action(...)` objects its workflows reference (an ActionRef is a\n * RegisteredAction structurally) — the code plane of the dual-plane model:\n *\n * protected override actions() { return [syncCrm, issueCoupon]; }\n */\n protected actions(): readonly RegisteredAction[] {\n return [];\n }\n\n /**\n * The contract events this journey emits (`.sendEvent()`) are checked\n * against, exactly like ingest; journeyWorker passes its config's.\n */\n protected contract(): Contract | undefined {\n return undefined;\n }\n\n /**\n * Template props check before every send; an app overrides this with\n * `registryValidator({ emails, pushes })` (journeyWorker does it from its\n * registries). By default props go out as resolved.\n */\n protected createMessageValidator(): MessageValidator | undefined {\n return undefined;\n }\n\n /**\n * Reporting hooks: node arrivals, every copy sent, how each instance ended. The host stores\n * what it needs for its dashboards; by default nothing is reported.\n */\n protected createObserver(_store: JourneyStore): JourneyObserver | undefined {\n return undefined;\n }\n\n /** Override for a different version policy ('warn' by default) or a fully custom invoker (e.g. webhook degradation for hosted tenants). */\n protected createActionInvoker(): ActionInvoker {\n return new RegistryActionInvoker(this.actions());\n }\n\n /**\n * Everything one run is wired to. By default it is composed from the methods above; a host\n * that assembles the whole set itself (journeyWorker) overrides this one instead.\n */\n protected createRunParts(store: JourneyStore, params: JourneyParams): JourneyRunParts {\n return {\n store,\n env: this.env,\n runtime: this.runtime(params),\n messages: this.createMessageSender(),\n actions: this.createActionInvoker(),\n contract: this.contract(),\n validator: this.createMessageValidator(),\n observer: this.createObserver(store),\n };\n }\n\n async run(event: WorkflowEvent<JourneyParams>, step: WorkflowStep): Promise<JourneyOutcome> {\n const store = this.createStore();\n try {\n const { userId } = event.payload;\n return await executeJourney(\n {\n instanceId: event.instanceId,\n params: event.payload,\n // Trigger-event creation time: replay-stable, unlike Date.now() here\n enteredAtMs: event.timestamp.getTime(),\n step: new CfEngineStep(step, store, userId, event.instanceId),\n },\n this.createRunParts(store, event.payload)\n );\n } finally {\n await this.closeStore(store);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runner.cjs","names":["isRetryable","NonRetryableError","WAKE_EVENT_TYPE","wakeExpired","WorkflowEntrypoint","d1Store","cloudflareJourneyBinding","resolveRuntime","WebhookMessageSender","LogMessageSender","RegistryActionInvoker","executeJourney"],"sources":["../../src/cloudflare/runner.ts"],"sourcesContent":["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';\nimport { NonRetryableError } from 'cloudflare:workflows';\nimport type { Contract } from '../contract';\nimport { type RegisteredAction, RegistryActionInvoker } from '../engine/actions';\nimport type {\n ActionInvoker,\n EngineStep,\n JourneyObserver,\n JourneyOutcome,\n MessageSender,\n MessageValidator,\n} from '../engine/ports';\nimport { isRetryable } from '../engine/retry';\nimport { type JourneyRunParts, executeJourney } from '../host/execute';\nimport { type JourneyEnv, type JourneyParams, WAKE_EVENT_TYPE } from '../host/platform';\nimport { LogMessageSender, WebhookMessageSender } from '../host/senders';\nimport { type JourneyRuntimeOptions, resolveRuntime } from '../runtime';\nimport type { JourneyStore } from '../store/index';\nimport { cloudflareJourneyBinding, wakeExpired } from './bindings';\nimport { d1Store } from './d1-env';\n\n/**\n * Adapts CF's WorkflowStep to EngineStep.\n * subscribe/unsubscribe write the subscription table inside durable steps (the\n * router wakes instances from that table); waitForWake parks on CF's\n * waitForEvent, which signals a timeout by throwing — and so does every other\n * failure in there, so only the clock, against the checkpointed deadline, can\n * tell them apart (see wakeExpired).\n */\nclass CfEngineStep implements EngineStep {\n constructor(\n private readonly step: WorkflowStep,\n private readonly store: JourneyStore,\n private readonly userId: string,\n private readonly instanceId: string\n ) {}\n\n /**\n * A failure a retry would only repeat (`isRetryable`: a template refusing its props, the\n * contract refusing an emitted event, a provider rejecting the request) ends the step at\n * once instead of burning the retry budget. The engine recognizes a NonRetryableError by its\n * `name`, so the original's name is not written over it — the original travels as `cause`.\n */\n do<T>(name: string, fn: () => Promise<T>): Promise<T> {\n return this.step.do(name, async () => {\n try {\n return await fn();\n } catch (error) {\n if (error instanceof Error && !isRetryable(error)) {\n const permanent = new NonRetryableError(error.message);\n permanent.cause = error;\n throw permanent;\n }\n throw error;\n }\n });\n }\n\n sleep(name: string, ms: number): Promise<void> {\n return this.step.sleep(name, ms);\n }\n\n subscribe(name: string, events: readonly string[]): Promise<void> {\n // Idempotent in the store: the interpreter re-subscribes on every wait\n // attempt (one-shot-callback contract) and registrations persist here.\n return this.step.do(name, () =>\n this.store.subscribe(this.instanceId, this.userId, events, Date.now())\n );\n }\n\n unsubscribe(name: string): Promise<void> {\n return this.step.do(name, () => this.store.unsubscribe(this.instanceId));\n }\n\n async waitForWake(\n name: string,\n { timeoutMs, deadlineMs }: { timeoutMs: number; deadlineMs: number }\n ): Promise<'event' | 'timeout'> {\n try {\n await this.step.waitForEvent(name, { type: WAKE_EVENT_TYPE, timeout: timeoutMs });\n return 'event';\n } catch (error) {\n /*\n * Only the deadline can tell a real timeout from a failure (see wakeExpired). The\n * deadline is the interpreter's checkpoint, so the verdict is the same on the attempt\n * that parked and on a replay after an eviction — which is where the wait usually\n * resurfaces: the instance is rebuilt, the wait is already over, and the first thing to\n * run is this step. The verdict is checkpointed too, so later replays reuse it.\n */\n const timedOut = await this.step.do(`${name}:timed-out`, async () =>\n wakeExpired(deadlineMs, Date.now())\n );\n if (timedOut) return 'timeout';\n throw error;\n }\n }\n}\n\n/**\n * The generic journey executor: one class runs every workflow, because IR is\n * data. It is loaded from the store by the contentHash pinned at entry —\n * content addressing makes it immutable, so reading it outside a step is\n * replay-safe.\n */\nexport class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, JourneyParams> {\n /**\n * The data plane. D1 from the env by default; an app on Postgres\n * overrides this (and `closeStore` if its client needs closing):\n *\n * protected override createStore() { return new PostgresJourneyStore(client(this.env)); }\n */\n protected createStore(): JourneyStore {\n return d1Store(this.env);\n }\n\n /** Called once `run` is over, success or not — release whatever `createStore` opened. */\n protected async closeStore(_store: JourneyStore): Promise<void> {}\n\n private adapted: JourneyEnv | undefined;\n\n /**\n * The env a run ingests its emitted events with: the Workflow binding behind the host's port,\n * so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the\n * host drop its wakes). Built once per instance.\n */\n protected hostEnv(): JourneyEnv {\n return (this.adapted ??= { ...this.env, JOURNEY: cloudflareJourneyBinding(this.env.JOURNEY) });\n }\n\n /**\n * Runtime options for this instance — typed configuration, resolved by the app (typically\n * `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from\n * an ENVIRONMENT var). The instance params are passed so the decision can also be per user:\n * a tester account in production can run in fast-forward while everyone else runs in real\n * time. Defaults: real time, quiet.\n *\n * protected override runtime({ userId }) {\n * return resolveRuntime(workflowConfig.runtime, isTester(userId) ? 'test' : this.env.ENVIRONMENT);\n * }\n */\n protected runtime(_params: JourneyParams): JourneyRuntimeOptions {\n return resolveRuntime(undefined, 'production');\n }\n\n /**\n * Message outlet; an app overrides this to plug in real channels (say\n * an EmailSender over a transport with a react-email renderer). By default it picks the webhook\n * or logging sender based on the environment.\n */\n protected createMessageSender(): MessageSender {\n return this.env.MESSAGE_WEBHOOK_URL\n ? new WebhookMessageSender(this.env.MESSAGE_WEBHOOK_URL)\n : new LogMessageSender();\n }\n\n /**\n * Custom-action registry; an app overrides this returning the same\n * `action(...)` objects its workflows reference (an ActionRef is a\n * RegisteredAction structurally) — the code plane of the dual-plane model:\n *\n * protected override actions() { return [syncCrm, issueCoupon]; }\n */\n protected actions(): readonly RegisteredAction[] {\n return [];\n }\n\n /**\n * The contract events this journey emits (`.sendEvent()`) are checked\n * against, exactly like ingest; journeyWorker passes its config's.\n */\n protected contract(): Contract | undefined {\n return undefined;\n }\n\n /**\n * Template props check before every send; an app overrides this with\n * `registryValidator({ emails, pushes })` (journeyWorker does it from its\n * registries). By default props go out as resolved.\n */\n protected createMessageValidator(): MessageValidator | undefined {\n return undefined;\n }\n\n /**\n * Reporting hooks: node arrivals, every copy sent, how each instance ended. The host stores\n * what it needs for its dashboards; by default nothing is reported.\n */\n protected createObserver(_store: JourneyStore): JourneyObserver | undefined {\n return undefined;\n }\n\n /** Override for a different version policy ('warn' by default) or a fully custom invoker (e.g. webhook degradation for hosted tenants). */\n protected createActionInvoker(): ActionInvoker {\n return new RegistryActionInvoker(this.actions());\n }\n\n /**\n * Everything one run is wired to. By default it is composed from the methods above; a host\n * that assembles the whole set itself (journeyWorker) overrides this one instead.\n */\n protected createRunParts(store: JourneyStore, params: JourneyParams): JourneyRunParts {\n return {\n store,\n env: this.hostEnv(),\n runtime: this.runtime(params),\n messages: this.createMessageSender(),\n actions: this.createActionInvoker(),\n contract: this.contract(),\n validator: this.createMessageValidator(),\n observer: this.createObserver(store),\n };\n }\n\n async run(event: WorkflowEvent<JourneyParams>, step: WorkflowStep): Promise<JourneyOutcome> {\n const store = this.createStore();\n try {\n const { userId } = event.payload;\n return await executeJourney(\n {\n instanceId: event.instanceId,\n params: event.payload,\n // Trigger-event creation time: replay-stable, unlike Date.now() here\n enteredAtMs: event.timestamp.getTime(),\n step: new CfEngineStep(step, store, userId, event.instanceId),\n },\n this.createRunParts(store, event.payload)\n );\n } finally {\n await this.closeStore(store);\n }\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { ActionInvoker, JourneyObserver, JourneyOutcome, MessageSender, MessageValidator } from "../engine/ports.cjs";
2
2
  import { RegisteredAction } from "../engine/actions.cjs";
3
- import { JourneyEnv, JourneyParams } from "./bindings.cjs";
3
+ import { JourneyEnv, JourneyParams } from "../host/platform.cjs";
4
4
  import { JourneyStore } from "../store/index.cjs";
5
5
  import { Contract } from "../contract.cjs";
6
6
  import { JourneyRuntimeOptions } from "../runtime.cjs";
@@ -15,7 +15,7 @@ import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:work
15
15
  */
16
16
  export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, JourneyParams> {
17
17
  /**
18
- * The data plane. D1 + KV from the env by default; an app on Postgres
18
+ * The data plane. D1 from the env by default; an app on Postgres
19
19
  * overrides this (and `closeStore` if its client needs closing):
20
20
  *
21
21
  * protected override createStore() { return new PostgresJourneyStore(client(this.env)); }
@@ -23,6 +23,13 @@ export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, Journe
23
23
  protected createStore(): JourneyStore;
24
24
  /** Called once `run` is over, success or not — release whatever `createStore` opened. */
25
25
  protected closeStore(_store: JourneyStore): Promise<void>;
26
+ private adapted;
27
+ /**
28
+ * The env a run ingests its emitted events with: the Workflow binding behind the host's port,
29
+ * so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the
30
+ * host drop its wakes). Built once per instance.
31
+ */
32
+ protected hostEnv(): JourneyEnv;
26
33
  /**
27
34
  * Runtime options for this instance — typed configuration, resolved by the app (typically
28
35
  * `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.cts","names":[],"sources":["../../src/cloudflare/runner.ts"],"mappings":";;;;;;;;;;;;;;;qBAqGa,sBAAsB,mBAAmB,YAAY;;;;;;;YAOtD,eAAe;;YAKT,WAAW,QAAQ,eAAe;;;;;;;;;;;;YAaxC,QAAQ,SAAS,gBAAgB;;;;;;YASjC,uBAAuB;;;;;;;;YAavB,oBAAoB;;;;;YAQpB,YAAY;;;;;;YASZ,0BAA0B;;;;;YAQ1B,eAAe,QAAQ,eAAe;;YAKtC,uBAAuB;;;;;YAQvB,eAAe,OAAO,cAAc,QAAQ,gBAAgB;EAahE,IAAI,OAAO,cAAc,gBAAgB,MAAM,eAAe,QAAQ"}
1
+ {"version":3,"file":"runner.d.cts","names":[],"sources":["../../src/cloudflare/runner.ts"],"mappings":";;;;;;;;;;;;;;;qBAwGa,sBAAsB,mBAAmB,YAAY;;;;;;;YAOtD,eAAe;;YAKT,WAAW,QAAQ,eAAe;UAE1C;;;;;;YAOE,WAAW;;;;;;;;;;;;YAeX,QAAQ,SAAS,gBAAgB;;;;;;YASjC,uBAAuB;;;;;;;;YAavB,oBAAoB;;;;;YAQpB,YAAY;;;;;;YASZ,0BAA0B;;;;;YAQ1B,eAAe,QAAQ,eAAe;;YAKtC,uBAAuB;;;;;YAQvB,eAAe,OAAO,cAAc,QAAQ,gBAAgB;EAahE,IAAI,OAAO,cAAc,gBAAgB,MAAM,eAAe,QAAQ"}
@@ -1,6 +1,6 @@
1
1
  import { ActionInvoker, JourneyObserver, JourneyOutcome, MessageSender, MessageValidator } from "../engine/ports.mjs";
2
2
  import { RegisteredAction } from "../engine/actions.mjs";
3
- import { JourneyEnv, JourneyParams } from "./bindings.mjs";
3
+ import { JourneyEnv, JourneyParams } from "../host/platform.mjs";
4
4
  import { JourneyStore } from "../store/index.mjs";
5
5
  import { Contract } from "../contract.mjs";
6
6
  import { JourneyRuntimeOptions } from "../runtime.mjs";
@@ -15,7 +15,7 @@ import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:work
15
15
  */
16
16
  export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, JourneyParams> {
17
17
  /**
18
- * The data plane. D1 + KV from the env by default; an app on Postgres
18
+ * The data plane. D1 from the env by default; an app on Postgres
19
19
  * overrides this (and `closeStore` if its client needs closing):
20
20
  *
21
21
  * protected override createStore() { return new PostgresJourneyStore(client(this.env)); }
@@ -23,6 +23,13 @@ export declare class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, Journe
23
23
  protected createStore(): JourneyStore;
24
24
  /** Called once `run` is over, success or not — release whatever `createStore` opened. */
25
25
  protected closeStore(_store: JourneyStore): Promise<void>;
26
+ private adapted;
27
+ /**
28
+ * The env a run ingests its emitted events with: the Workflow binding behind the host's port,
29
+ * so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the
30
+ * host drop its wakes). Built once per instance.
31
+ */
32
+ protected hostEnv(): JourneyEnv;
26
33
  /**
27
34
  * Runtime options for this instance — typed configuration, resolved by the app (typically
28
35
  * `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.mts","names":[],"sources":["../../src/cloudflare/runner.ts"],"mappings":";;;;;;;;;;;;;;;qBAqGa,sBAAsB,mBAAmB,YAAY;;;;;;;YAOtD,eAAe;;YAKT,WAAW,QAAQ,eAAe;;;;;;;;;;;;YAaxC,QAAQ,SAAS,gBAAgB;;;;;;YASjC,uBAAuB;;;;;;;;YAavB,oBAAoB;;;;;YAQpB,YAAY;;;;;;YASZ,0BAA0B;;;;;YAQ1B,eAAe,QAAQ,eAAe;;YAKtC,uBAAuB;;;;;YAQvB,eAAe,OAAO,cAAc,QAAQ,gBAAgB;EAahE,IAAI,OAAO,cAAc,gBAAgB,MAAM,eAAe,QAAQ"}
1
+ {"version":3,"file":"runner.d.mts","names":[],"sources":["../../src/cloudflare/runner.ts"],"mappings":";;;;;;;;;;;;;;;qBAwGa,sBAAsB,mBAAmB,YAAY;;;;;;;YAOtD,eAAe;;YAKT,WAAW,QAAQ,eAAe;UAE1C;;;;;;YAOE,WAAW;;;;;;;;;;;;YAeX,QAAQ,SAAS,gBAAgB;;;;;;YASjC,uBAAuB;;;;;;;;YAavB,oBAAoB;;;;;YAQpB,YAAY;;;;;;YASZ,0BAA0B;;;;;YAQ1B,eAAe,QAAQ,eAAe;;YAKtC,uBAAuB;;;;;YAQvB,eAAe,OAAO,cAAc,QAAQ,gBAAgB;EAahE,IAAI,OAAO,cAAc,gBAAgB,MAAM,eAAe,QAAQ"}
@@ -1,10 +1,11 @@
1
- import { resolveRuntime } from "../runtime.mjs";
2
1
  import { isRetryable } from "../engine/retry.mjs";
3
- import { WAKE_EVENT_TYPE, wakeExpired } from "./bindings.mjs";
2
+ import { resolveRuntime } from "../runtime.mjs";
4
3
  import { d1Store } from "./d1-env.mjs";
4
+ import { WAKE_EVENT_TYPE } from "../host/platform.mjs";
5
5
  import { executeJourney } from "../host/execute.mjs";
6
6
  import { RegistryActionInvoker } from "../engine/actions.mjs";
7
7
  import { LogMessageSender, WebhookMessageSender } from "../host/senders.mjs";
8
+ import { cloudflareJourneyBinding, wakeExpired } from "./bindings.mjs";
8
9
  import { WorkflowEntrypoint } from "cloudflare:workers";
9
10
  import { NonRetryableError } from "cloudflare:workflows";
10
11
  //#region src/cloudflare/runner.ts
@@ -13,7 +14,8 @@ import { NonRetryableError } from "cloudflare:workflows";
13
14
  * subscribe/unsubscribe write the subscription table inside durable steps (the
14
15
  * router wakes instances from that table); waitForWake parks on CF's
15
16
  * waitForEvent, which signals a timeout by throwing — and so does every other
16
- * failure in there, so only the clock can tell them apart (see wakeExpired).
17
+ * failure in there, so only the clock, against the checkpointed deadline, can
18
+ * tell them apart (see wakeExpired).
17
19
  */
18
20
  var CfEngineStep = class {
19
21
  step;
@@ -29,14 +31,19 @@ var CfEngineStep = class {
29
31
  /**
30
32
  * A failure a retry would only repeat (`isRetryable`: a template refusing its props, the
31
33
  * contract refusing an emitted event, a provider rejecting the request) ends the step at
32
- * once instead of burning the retry budget.
34
+ * once instead of burning the retry budget. The engine recognizes a NonRetryableError by its
35
+ * `name`, so the original's name is not written over it — the original travels as `cause`.
33
36
  */
34
37
  do(name, fn) {
35
38
  return this.step.do(name, async () => {
36
39
  try {
37
40
  return await fn();
38
41
  } catch (error) {
39
- if (error instanceof Error && !isRetryable(error)) throw new NonRetryableError(error.message, error.name);
42
+ if (error instanceof Error && !isRetryable(error)) {
43
+ const permanent = new NonRetryableError(error.message);
44
+ permanent.cause = error;
45
+ throw permanent;
46
+ }
40
47
  throw error;
41
48
  }
42
49
  });
@@ -44,17 +51,13 @@ var CfEngineStep = class {
44
51
  sleep(name, ms) {
45
52
  return this.step.sleep(name, ms);
46
53
  }
47
- sleepUntil(name, timestampMs) {
48
- return this.step.sleepUntil(name, timestampMs);
49
- }
50
54
  subscribe(name, events) {
51
55
  return this.step.do(name, () => this.store.subscribe(this.instanceId, this.userId, events, Date.now()));
52
56
  }
53
57
  unsubscribe(name) {
54
58
  return this.step.do(name, () => this.store.unsubscribe(this.instanceId));
55
59
  }
56
- async waitForWake(name, timeoutMs) {
57
- const startedAt = Date.now();
60
+ async waitForWake(name, { timeoutMs, deadlineMs }) {
58
61
  try {
59
62
  await this.step.waitForEvent(name, {
60
63
  type: WAKE_EVENT_TYPE,
@@ -62,7 +65,7 @@ var CfEngineStep = class {
62
65
  });
63
66
  return "event";
64
67
  } catch (error) {
65
- if (await this.step.do(`${name}:timed-out`, async () => wakeExpired(startedAt, timeoutMs, Date.now()))) return "timeout";
68
+ if (await this.step.do(`${name}:timed-out`, async () => wakeExpired(deadlineMs, Date.now()))) return "timeout";
66
69
  throw error;
67
70
  }
68
71
  }
@@ -75,7 +78,7 @@ var CfEngineStep = class {
75
78
  */
76
79
  var JourneyRunner = class extends WorkflowEntrypoint {
77
80
  /**
78
- * The data plane. D1 + KV from the env by default; an app on Postgres
81
+ * The data plane. D1 from the env by default; an app on Postgres
79
82
  * overrides this (and `closeStore` if its client needs closing):
80
83
  *
81
84
  * protected override createStore() { return new PostgresJourneyStore(client(this.env)); }
@@ -85,6 +88,18 @@ var JourneyRunner = class extends WorkflowEntrypoint {
85
88
  }
86
89
  /** Called once `run` is over, success or not — release whatever `createStore` opened. */
87
90
  async closeStore(_store) {}
91
+ adapted;
92
+ /**
93
+ * The env a run ingests its emitted events with: the Workflow binding behind the host's port,
94
+ * so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the
95
+ * host drop its wakes). Built once per instance.
96
+ */
97
+ hostEnv() {
98
+ return this.adapted ??= {
99
+ ...this.env,
100
+ JOURNEY: cloudflareJourneyBinding(this.env.JOURNEY)
101
+ };
102
+ }
88
103
  /**
89
104
  * Runtime options for this instance — typed configuration, resolved by the app (typically
90
105
  * `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from
@@ -144,7 +159,7 @@ var JourneyRunner = class extends WorkflowEntrypoint {
144
159
  createRunParts(store, params) {
145
160
  return {
146
161
  store,
147
- env: this.env,
162
+ env: this.hostEnv(),
148
163
  runtime: this.runtime(params),
149
164
  messages: this.createMessageSender(),
150
165
  actions: this.createActionInvoker(),
@@ -1 +1 @@
1
- {"version":3,"file":"runner.mjs","names":[],"sources":["../../src/cloudflare/runner.ts"],"sourcesContent":["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';\nimport { NonRetryableError } from 'cloudflare:workflows';\nimport type { Contract } from '../contract';\nimport { type RegisteredAction, RegistryActionInvoker } from '../engine/actions';\nimport type {\n ActionInvoker,\n EngineStep,\n JourneyObserver,\n JourneyOutcome,\n MessageSender,\n MessageValidator,\n} from '../engine/ports';\nimport { isRetryable } from '../engine/retry';\nimport { type JourneyRunParts, executeJourney } from '../host/execute';\nimport { LogMessageSender, WebhookMessageSender } from '../host/senders';\nimport { type JourneyRuntimeOptions, resolveRuntime } from '../runtime';\nimport type { JourneyStore } from '../store/index';\nimport { type JourneyEnv, type JourneyParams, WAKE_EVENT_TYPE, wakeExpired } from './bindings';\nimport { d1Store } from './d1-env';\n\n/**\n * Adapts CF's WorkflowStep to EngineStep.\n * subscribe/unsubscribe write the subscription table inside durable steps (the\n * router wakes instances from that table); waitForWake parks on CF's\n * waitForEvent, which signals a timeout by throwing — and so does every other\n * failure in there, so only the clock can tell them apart (see wakeExpired).\n */\nclass CfEngineStep implements EngineStep {\n constructor(\n private readonly step: WorkflowStep,\n private readonly store: JourneyStore,\n private readonly userId: string,\n private readonly instanceId: string\n ) {}\n\n /**\n * A failure a retry would only repeat (`isRetryable`: a template refusing its props, the\n * contract refusing an emitted event, a provider rejecting the request) ends the step at\n * once instead of burning the retry budget.\n */\n do<T>(name: string, fn: () => Promise<T>): Promise<T> {\n return this.step.do(name, async () => {\n try {\n return await fn();\n } catch (error) {\n if (error instanceof Error && !isRetryable(error)) {\n throw new NonRetryableError(error.message, error.name);\n }\n throw error;\n }\n });\n }\n\n sleep(name: string, ms: number): Promise<void> {\n return this.step.sleep(name, ms);\n }\n\n sleepUntil(name: string, timestampMs: number): Promise<void> {\n return this.step.sleepUntil(name, timestampMs);\n }\n\n subscribe(name: string, events: readonly string[]): Promise<void> {\n // Idempotent in the store: the interpreter re-subscribes on every wait\n // attempt (one-shot-callback contract) and registrations persist here.\n return this.step.do(name, () =>\n this.store.subscribe(this.instanceId, this.userId, events, Date.now())\n );\n }\n\n unsubscribe(name: string): Promise<void> {\n return this.step.do(name, () => this.store.unsubscribe(this.instanceId));\n }\n\n async waitForWake(name: string, timeoutMs: number): Promise<'event' | 'timeout'> {\n const startedAt = Date.now();\n try {\n await this.step.waitForEvent(name, { type: WAKE_EVENT_TYPE, timeout: timeoutMs });\n return 'event';\n } catch (error) {\n /*\n * Only the deadline can tell a real timeout from a failure (see\n * wakeExpired). `startedAt` is not replay-stable, but it is read only\n * inside the step body below, and a step body runs on the live attempt\n * alone — a replay returns the persisted verdict without touching the\n * clock.\n */\n const timedOut = await this.step.do(`${name}:timed-out`, async () =>\n wakeExpired(startedAt, timeoutMs, Date.now())\n );\n if (timedOut) return 'timeout';\n throw error;\n }\n }\n}\n\n/**\n * The generic journey executor: one class runs every workflow, because IR is\n * data. It is loaded from the store by the contentHash pinned at entry —\n * content addressing makes it immutable, so reading it outside a step is\n * replay-safe.\n */\nexport class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, JourneyParams> {\n /**\n * The data plane. D1 + KV from the env by default; an app on Postgres\n * overrides this (and `closeStore` if its client needs closing):\n *\n * protected override createStore() { return new PostgresJourneyStore(client(this.env)); }\n */\n protected createStore(): JourneyStore {\n return d1Store(this.env);\n }\n\n /** Called once `run` is over, success or not — release whatever `createStore` opened. */\n protected async closeStore(_store: JourneyStore): Promise<void> {}\n\n /**\n * Runtime options for this instance — typed configuration, resolved by the app (typically\n * `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from\n * an ENVIRONMENT var). The instance params are passed so the decision can also be per user:\n * a tester account in production can run in fast-forward while everyone else runs in real\n * time. Defaults: real time, quiet.\n *\n * protected override runtime({ userId }) {\n * return resolveRuntime(workflowConfig.runtime, isTester(userId) ? 'test' : this.env.ENVIRONMENT);\n * }\n */\n protected runtime(_params: JourneyParams): JourneyRuntimeOptions {\n return resolveRuntime(undefined, 'production');\n }\n\n /**\n * Message outlet; an app overrides this to plug in real channels (say\n * an EmailSender over a transport with a react-email renderer). By default it picks the webhook\n * or logging sender based on the environment.\n */\n protected createMessageSender(): MessageSender {\n return this.env.MESSAGE_WEBHOOK_URL\n ? new WebhookMessageSender(this.env.MESSAGE_WEBHOOK_URL)\n : new LogMessageSender();\n }\n\n /**\n * Custom-action registry; an app overrides this returning the same\n * `action(...)` objects its workflows reference (an ActionRef is a\n * RegisteredAction structurally) — the code plane of the dual-plane model:\n *\n * protected override actions() { return [syncCrm, issueCoupon]; }\n */\n protected actions(): readonly RegisteredAction[] {\n return [];\n }\n\n /**\n * The contract events this journey emits (`.sendEvent()`) are checked\n * against, exactly like ingest; journeyWorker passes its config's.\n */\n protected contract(): Contract | undefined {\n return undefined;\n }\n\n /**\n * Template props check before every send; an app overrides this with\n * `registryValidator({ emails, pushes })` (journeyWorker does it from its\n * registries). By default props go out as resolved.\n */\n protected createMessageValidator(): MessageValidator | undefined {\n return undefined;\n }\n\n /**\n * Reporting hooks: node arrivals, every copy sent, how each instance ended. The host stores\n * what it needs for its dashboards; by default nothing is reported.\n */\n protected createObserver(_store: JourneyStore): JourneyObserver | undefined {\n return undefined;\n }\n\n /** Override for a different version policy ('warn' by default) or a fully custom invoker (e.g. webhook degradation for hosted tenants). */\n protected createActionInvoker(): ActionInvoker {\n return new RegistryActionInvoker(this.actions());\n }\n\n /**\n * Everything one run is wired to. By default it is composed from the methods above; a host\n * that assembles the whole set itself (journeyWorker) overrides this one instead.\n */\n protected createRunParts(store: JourneyStore, params: JourneyParams): JourneyRunParts {\n return {\n store,\n env: this.env,\n runtime: this.runtime(params),\n messages: this.createMessageSender(),\n actions: this.createActionInvoker(),\n contract: this.contract(),\n validator: this.createMessageValidator(),\n observer: this.createObserver(store),\n };\n }\n\n async run(event: WorkflowEvent<JourneyParams>, step: WorkflowStep): Promise<JourneyOutcome> {\n const store = this.createStore();\n try {\n const { userId } = event.payload;\n return await executeJourney(\n {\n instanceId: event.instanceId,\n params: event.payload,\n // Trigger-event creation time: replay-stable, unlike Date.now() here\n enteredAtMs: event.timestamp.getTime(),\n step: new CfEngineStep(step, store, userId, event.instanceId),\n },\n this.createRunParts(store, event.payload)\n );\n } finally {\n await this.closeStore(store);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA2BA,IAAM,eAAN,MAAyC;CAEpB;CACA;CACA;CACA;CAJnB,YACE,MACA,OACA,QACA,YACA;EAJiB,KAAA,OAAA;EACA,KAAA,QAAA;EACA,KAAA,SAAA;EACA,KAAA,aAAA;CAChB;;;;;;CAOH,GAAM,MAAc,IAAkC;EACpD,OAAO,KAAK,KAAK,GAAG,MAAM,YAAY;GACpC,IAAI;IACF,OAAO,MAAM,GAAG;GAClB,SAAS,OAAO;IACd,IAAI,iBAAiB,SAAS,CAAC,YAAY,KAAK,GAC9C,MAAM,IAAI,kBAAkB,MAAM,SAAS,MAAM,IAAI;IAEvD,MAAM;GACR;EACF,CAAC;CACH;CAEA,MAAM,MAAc,IAA2B;EAC7C,OAAO,KAAK,KAAK,MAAM,MAAM,EAAE;CACjC;CAEA,WAAW,MAAc,aAAoC;EAC3D,OAAO,KAAK,KAAK,WAAW,MAAM,WAAW;CAC/C;CAEA,UAAU,MAAc,QAA0C;EAGhE,OAAO,KAAK,KAAK,GAAG,YAClB,KAAK,MAAM,UAAU,KAAK,YAAY,KAAK,QAAQ,QAAQ,KAAK,IAAI,CAAC,CACvE;CACF;CAEA,YAAY,MAA6B;EACvC,OAAO,KAAK,KAAK,GAAG,YAAY,KAAK,MAAM,YAAY,KAAK,UAAU,CAAC;CACzE;CAEA,MAAM,YAAY,MAAc,WAAiD;EAC/E,MAAM,YAAY,KAAK,IAAI;EAC3B,IAAI;GACF,MAAM,KAAK,KAAK,aAAa,MAAM;IAAE,MAAM;IAAiB,SAAS;GAAU,CAAC;GAChF,OAAO;EACT,SAAS,OAAO;GAWd,IAAI,MAHmB,KAAK,KAAK,GAAG,GAAG,KAAK,aAAa,YACvD,YAAY,WAAW,WAAW,KAAK,IAAI,CAAC,CAC9C,GACc,OAAO;GACrB,MAAM;EACR;CACF;AACF;;;;;;;AAQA,IAAa,gBAAb,cAAmC,mBAA8C;;;;;;;CAO/E,cAAsC;EACpC,OAAO,QAAQ,KAAK,GAAG;CACzB;;CAGA,MAAgB,WAAW,QAAqC,CAAC;;;;;;;;;;;;CAajE,QAAkB,SAA+C;EAC/D,OAAO,eAAe,KAAA,GAAW,YAAY;CAC/C;;;;;;CAOA,sBAA+C;EAC7C,OAAO,KAAK,IAAI,sBACZ,IAAI,qBAAqB,KAAK,IAAI,mBAAmB,IACrD,IAAI,iBAAiB;CAC3B;;;;;;;;CASA,UAAiD;EAC/C,OAAO,CAAC;CACV;;;;;CAMA,WAA2C,CAE3C;;;;;;CAOA,yBAAiE,CAEjE;;;;;CAMA,eAAyB,QAAmD,CAE5E;;CAGA,sBAA+C;EAC7C,OAAO,IAAI,sBAAsB,KAAK,QAAQ,CAAC;CACjD;;;;;CAMA,eAAyB,OAAqB,QAAwC;EACpF,OAAO;GACL;GACA,KAAK,KAAK;GACV,SAAS,KAAK,QAAQ,MAAM;GAC5B,UAAU,KAAK,oBAAoB;GACnC,SAAS,KAAK,oBAAoB;GAClC,UAAU,KAAK,SAAS;GACxB,WAAW,KAAK,uBAAuB;GACvC,UAAU,KAAK,eAAe,KAAK;EACrC;CACF;CAEA,MAAM,IAAI,OAAqC,MAA6C;EAC1F,MAAM,QAAQ,KAAK,YAAY;EAC/B,IAAI;GACF,MAAM,EAAE,WAAW,MAAM;GACzB,OAAO,MAAM,eACX;IACE,YAAY,MAAM;IAClB,QAAQ,MAAM;IAEd,aAAa,MAAM,UAAU,QAAQ;IACrC,MAAM,IAAI,aAAa,MAAM,OAAO,QAAQ,MAAM,UAAU;GAC9D,GACA,KAAK,eAAe,OAAO,MAAM,OAAO,CAC1C;EACF,UAAU;GACR,MAAM,KAAK,WAAW,KAAK;EAC7B;CACF;AACF"}
1
+ {"version":3,"file":"runner.mjs","names":[],"sources":["../../src/cloudflare/runner.ts"],"sourcesContent":["import { WorkflowEntrypoint, type WorkflowEvent, type WorkflowStep } from 'cloudflare:workers';\nimport { NonRetryableError } from 'cloudflare:workflows';\nimport type { Contract } from '../contract';\nimport { type RegisteredAction, RegistryActionInvoker } from '../engine/actions';\nimport type {\n ActionInvoker,\n EngineStep,\n JourneyObserver,\n JourneyOutcome,\n MessageSender,\n MessageValidator,\n} from '../engine/ports';\nimport { isRetryable } from '../engine/retry';\nimport { type JourneyRunParts, executeJourney } from '../host/execute';\nimport { type JourneyEnv, type JourneyParams, WAKE_EVENT_TYPE } from '../host/platform';\nimport { LogMessageSender, WebhookMessageSender } from '../host/senders';\nimport { type JourneyRuntimeOptions, resolveRuntime } from '../runtime';\nimport type { JourneyStore } from '../store/index';\nimport { cloudflareJourneyBinding, wakeExpired } from './bindings';\nimport { d1Store } from './d1-env';\n\n/**\n * Adapts CF's WorkflowStep to EngineStep.\n * subscribe/unsubscribe write the subscription table inside durable steps (the\n * router wakes instances from that table); waitForWake parks on CF's\n * waitForEvent, which signals a timeout by throwing — and so does every other\n * failure in there, so only the clock, against the checkpointed deadline, can\n * tell them apart (see wakeExpired).\n */\nclass CfEngineStep implements EngineStep {\n constructor(\n private readonly step: WorkflowStep,\n private readonly store: JourneyStore,\n private readonly userId: string,\n private readonly instanceId: string\n ) {}\n\n /**\n * A failure a retry would only repeat (`isRetryable`: a template refusing its props, the\n * contract refusing an emitted event, a provider rejecting the request) ends the step at\n * once instead of burning the retry budget. The engine recognizes a NonRetryableError by its\n * `name`, so the original's name is not written over it — the original travels as `cause`.\n */\n do<T>(name: string, fn: () => Promise<T>): Promise<T> {\n return this.step.do(name, async () => {\n try {\n return await fn();\n } catch (error) {\n if (error instanceof Error && !isRetryable(error)) {\n const permanent = new NonRetryableError(error.message);\n permanent.cause = error;\n throw permanent;\n }\n throw error;\n }\n });\n }\n\n sleep(name: string, ms: number): Promise<void> {\n return this.step.sleep(name, ms);\n }\n\n subscribe(name: string, events: readonly string[]): Promise<void> {\n // Idempotent in the store: the interpreter re-subscribes on every wait\n // attempt (one-shot-callback contract) and registrations persist here.\n return this.step.do(name, () =>\n this.store.subscribe(this.instanceId, this.userId, events, Date.now())\n );\n }\n\n unsubscribe(name: string): Promise<void> {\n return this.step.do(name, () => this.store.unsubscribe(this.instanceId));\n }\n\n async waitForWake(\n name: string,\n { timeoutMs, deadlineMs }: { timeoutMs: number; deadlineMs: number }\n ): Promise<'event' | 'timeout'> {\n try {\n await this.step.waitForEvent(name, { type: WAKE_EVENT_TYPE, timeout: timeoutMs });\n return 'event';\n } catch (error) {\n /*\n * Only the deadline can tell a real timeout from a failure (see wakeExpired). The\n * deadline is the interpreter's checkpoint, so the verdict is the same on the attempt\n * that parked and on a replay after an eviction — which is where the wait usually\n * resurfaces: the instance is rebuilt, the wait is already over, and the first thing to\n * run is this step. The verdict is checkpointed too, so later replays reuse it.\n */\n const timedOut = await this.step.do(`${name}:timed-out`, async () =>\n wakeExpired(deadlineMs, Date.now())\n );\n if (timedOut) return 'timeout';\n throw error;\n }\n }\n}\n\n/**\n * The generic journey executor: one class runs every workflow, because IR is\n * data. It is loaded from the store by the contentHash pinned at entry —\n * content addressing makes it immutable, so reading it outside a step is\n * replay-safe.\n */\nexport class JourneyRunner extends WorkflowEntrypoint<JourneyEnv, JourneyParams> {\n /**\n * The data plane. D1 from the env by default; an app on Postgres\n * overrides this (and `closeStore` if its client needs closing):\n *\n * protected override createStore() { return new PostgresJourneyStore(client(this.env)); }\n */\n protected createStore(): JourneyStore {\n return d1Store(this.env);\n }\n\n /** Called once `run` is over, success or not — release whatever `createStore` opened. */\n protected async closeStore(_store: JourneyStore): Promise<void> {}\n\n private adapted: JourneyEnv | undefined;\n\n /**\n * The env a run ingests its emitted events with: the Workflow binding behind the host's port,\n * so an instance the platform no longer knows answers `InstanceNotFoundError` (what lets the\n * host drop its wakes). Built once per instance.\n */\n protected hostEnv(): JourneyEnv {\n return (this.adapted ??= { ...this.env, JOURNEY: cloudflareJourneyBinding(this.env.JOURNEY) });\n }\n\n /**\n * Runtime options for this instance — typed configuration, resolved by the app (typically\n * `resolveRuntime(config.runtime, mode)` over its workflow.config.ts, with the mode coming from\n * an ENVIRONMENT var). The instance params are passed so the decision can also be per user:\n * a tester account in production can run in fast-forward while everyone else runs in real\n * time. Defaults: real time, quiet.\n *\n * protected override runtime({ userId }) {\n * return resolveRuntime(workflowConfig.runtime, isTester(userId) ? 'test' : this.env.ENVIRONMENT);\n * }\n */\n protected runtime(_params: JourneyParams): JourneyRuntimeOptions {\n return resolveRuntime(undefined, 'production');\n }\n\n /**\n * Message outlet; an app overrides this to plug in real channels (say\n * an EmailSender over a transport with a react-email renderer). By default it picks the webhook\n * or logging sender based on the environment.\n */\n protected createMessageSender(): MessageSender {\n return this.env.MESSAGE_WEBHOOK_URL\n ? new WebhookMessageSender(this.env.MESSAGE_WEBHOOK_URL)\n : new LogMessageSender();\n }\n\n /**\n * Custom-action registry; an app overrides this returning the same\n * `action(...)` objects its workflows reference (an ActionRef is a\n * RegisteredAction structurally) — the code plane of the dual-plane model:\n *\n * protected override actions() { return [syncCrm, issueCoupon]; }\n */\n protected actions(): readonly RegisteredAction[] {\n return [];\n }\n\n /**\n * The contract events this journey emits (`.sendEvent()`) are checked\n * against, exactly like ingest; journeyWorker passes its config's.\n */\n protected contract(): Contract | undefined {\n return undefined;\n }\n\n /**\n * Template props check before every send; an app overrides this with\n * `registryValidator({ emails, pushes })` (journeyWorker does it from its\n * registries). By default props go out as resolved.\n */\n protected createMessageValidator(): MessageValidator | undefined {\n return undefined;\n }\n\n /**\n * Reporting hooks: node arrivals, every copy sent, how each instance ended. The host stores\n * what it needs for its dashboards; by default nothing is reported.\n */\n protected createObserver(_store: JourneyStore): JourneyObserver | undefined {\n return undefined;\n }\n\n /** Override for a different version policy ('warn' by default) or a fully custom invoker (e.g. webhook degradation for hosted tenants). */\n protected createActionInvoker(): ActionInvoker {\n return new RegistryActionInvoker(this.actions());\n }\n\n /**\n * Everything one run is wired to. By default it is composed from the methods above; a host\n * that assembles the whole set itself (journeyWorker) overrides this one instead.\n */\n protected createRunParts(store: JourneyStore, params: JourneyParams): JourneyRunParts {\n return {\n store,\n env: this.hostEnv(),\n runtime: this.runtime(params),\n messages: this.createMessageSender(),\n actions: this.createActionInvoker(),\n contract: this.contract(),\n validator: this.createMessageValidator(),\n observer: this.createObserver(store),\n };\n }\n\n async run(event: WorkflowEvent<JourneyParams>, step: WorkflowStep): Promise<JourneyOutcome> {\n const store = this.createStore();\n try {\n const { userId } = event.payload;\n return await executeJourney(\n {\n instanceId: event.instanceId,\n params: event.payload,\n // Trigger-event creation time: replay-stable, unlike Date.now() here\n enteredAtMs: event.timestamp.getTime(),\n step: new CfEngineStep(step, store, userId, event.instanceId),\n },\n this.createRunParts(store, event.payload)\n );\n } finally {\n await this.closeStore(store);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6BA,IAAM,eAAN,MAAyC;CAEpB;CACA;CACA;CACA;CAJnB,YACE,MACA,OACA,QACA,YACA;EAJiB,KAAA,OAAA;EACA,KAAA,QAAA;EACA,KAAA,SAAA;EACA,KAAA,aAAA;CAChB;;;;;;;CAQH,GAAM,MAAc,IAAkC;EACpD,OAAO,KAAK,KAAK,GAAG,MAAM,YAAY;GACpC,IAAI;IACF,OAAO,MAAM,GAAG;GAClB,SAAS,OAAO;IACd,IAAI,iBAAiB,SAAS,CAAC,YAAY,KAAK,GAAG;KACjD,MAAM,YAAY,IAAI,kBAAkB,MAAM,OAAO;KACrD,UAAU,QAAQ;KAClB,MAAM;IACR;IACA,MAAM;GACR;EACF,CAAC;CACH;CAEA,MAAM,MAAc,IAA2B;EAC7C,OAAO,KAAK,KAAK,MAAM,MAAM,EAAE;CACjC;CAEA,UAAU,MAAc,QAA0C;EAGhE,OAAO,KAAK,KAAK,GAAG,YAClB,KAAK,MAAM,UAAU,KAAK,YAAY,KAAK,QAAQ,QAAQ,KAAK,IAAI,CAAC,CACvE;CACF;CAEA,YAAY,MAA6B;EACvC,OAAO,KAAK,KAAK,GAAG,YAAY,KAAK,MAAM,YAAY,KAAK,UAAU,CAAC;CACzE;CAEA,MAAM,YACJ,MACA,EAAE,WAAW,cACiB;EAC9B,IAAI;GACF,MAAM,KAAK,KAAK,aAAa,MAAM;IAAE,MAAM;IAAiB,SAAS;GAAU,CAAC;GAChF,OAAO;EACT,SAAS,OAAO;GAWd,IAAI,MAHmB,KAAK,KAAK,GAAG,GAAG,KAAK,aAAa,YACvD,YAAY,YAAY,KAAK,IAAI,CAAC,CACpC,GACc,OAAO;GACrB,MAAM;EACR;CACF;AACF;;;;;;;AAQA,IAAa,gBAAb,cAAmC,mBAA8C;;;;;;;CAO/E,cAAsC;EACpC,OAAO,QAAQ,KAAK,GAAG;CACzB;;CAGA,MAAgB,WAAW,QAAqC,CAAC;CAEjE;;;;;;CAOA,UAAgC;EAC9B,OAAQ,KAAK,YAAY;GAAE,GAAG,KAAK;GAAK,SAAS,yBAAyB,KAAK,IAAI,OAAO;EAAE;CAC9F;;;;;;;;;;;;CAaA,QAAkB,SAA+C;EAC/D,OAAO,eAAe,KAAA,GAAW,YAAY;CAC/C;;;;;;CAOA,sBAA+C;EAC7C,OAAO,KAAK,IAAI,sBACZ,IAAI,qBAAqB,KAAK,IAAI,mBAAmB,IACrD,IAAI,iBAAiB;CAC3B;;;;;;;;CASA,UAAiD;EAC/C,OAAO,CAAC;CACV;;;;;CAMA,WAA2C,CAE3C;;;;;;CAOA,yBAAiE,CAEjE;;;;;CAMA,eAAyB,QAAmD,CAE5E;;CAGA,sBAA+C;EAC7C,OAAO,IAAI,sBAAsB,KAAK,QAAQ,CAAC;CACjD;;;;;CAMA,eAAyB,OAAqB,QAAwC;EACpF,OAAO;GACL;GACA,KAAK,KAAK,QAAQ;GAClB,SAAS,KAAK,QAAQ,MAAM;GAC5B,UAAU,KAAK,oBAAoB;GACnC,SAAS,KAAK,oBAAoB;GAClC,UAAU,KAAK,SAAS;GACxB,WAAW,KAAK,uBAAuB;GACvC,UAAU,KAAK,eAAe,KAAK;EACrC;CACF;CAEA,MAAM,IAAI,OAAqC,MAA6C;EAC1F,MAAM,QAAQ,KAAK,YAAY;EAC/B,IAAI;GACF,MAAM,EAAE,WAAW,MAAM;GACzB,OAAO,MAAM,eACX;IACE,YAAY,MAAM;IAClB,QAAQ,MAAM;IAEd,aAAa,MAAM,UAAU,QAAQ;IACrC,MAAM,IAAI,aAAa,MAAM,OAAO,QAAQ,MAAM,UAAU;GAC9D,GACA,KAAK,eAAe,OAAO,MAAM,OAAO,CAC1C;EACF,UAAU;GACR,MAAM,KAAK,WAAW,KAAK;EAC7B;CACF;AACF"}
@@ -9,16 +9,21 @@
9
9
  -- interpreter's send_event step being retried after it already committed its
10
10
  -- row), so both pass an id they can reproduce.
11
11
  --
12
+ -- The id is per user: the key is (user_id, id), so one sender's id can never
13
+ -- block another user's event, and a producer only has to keep its ids unique
14
+ -- within a user.
15
+ --
12
16
  -- Only the sender knows whether two deliveries describe one occurrence or two,
13
17
  -- so the engine never invents that answer: an ingest that arrives without an id
14
18
  -- is given a fresh one, which is a truthful "this is its own occurrence" rather
15
19
  -- than a guess.
16
20
  CREATE TABLE IF NOT EXISTS events (
17
- id TEXT PRIMARY KEY NOT NULL,
21
+ id TEXT NOT NULL,
18
22
  user_id TEXT NOT NULL,
19
23
  name TEXT NOT NULL,
20
24
  ts INTEGER NOT NULL,
21
- payload TEXT NOT NULL DEFAULT '{}'
25
+ payload TEXT NOT NULL DEFAULT '{}',
26
+ PRIMARY KEY (user_id, id)
22
27
  );
23
28
  CREATE INDEX IF NOT EXISTS idx_events_user_name_ts ON events (user_id, name, ts);
24
29
 
@@ -27,6 +32,15 @@ CREATE TABLE IF NOT EXISTS profiles (
27
32
  props TEXT NOT NULL DEFAULT '{}'
28
33
  );
29
34
 
35
+ -- Workflow bodies (WorkflowIR JSON), content-addressed. They live beside the
36
+ -- routes that point at them so a deploy is read-your-writes: the trigger row
37
+ -- and the body it names are in one strongly consistent database, and a route
38
+ -- never names a body another store has yet to catch up on.
39
+ CREATE TABLE IF NOT EXISTS workflow_ir (
40
+ content_hash TEXT PRIMARY KEY,
41
+ ir TEXT NOT NULL
42
+ );
43
+
30
44
  -- Deploy artefacts: segment definitions (resolved by id during evaluation)
31
45
  -- and the trigger routing table.
32
46
  -- Push devices: one row per provider token, owned by the user who registered it last.
@@ -87,11 +101,14 @@ CREATE TABLE IF NOT EXISTS segment_starts (
87
101
  PRIMARY KEY (segment, workflow, user_id)
88
102
  );
89
103
 
90
- -- Erasures in progress (forgetUser): the user's data stays until every running
91
- -- instance is stopped, and the scheduled sweep retries what is left.
104
+ -- Erasure tombstones (forgetUser): every write about a user is conditional on
105
+ -- no row here. While erased_at is null the user's data stays until every
106
+ -- running instance is stopped, and the scheduled sweep retries what is left;
107
+ -- once set, the data is gone and the row stays so the id is never written again.
92
108
  CREATE TABLE IF NOT EXISTS erasures (
93
- user_id TEXT PRIMARY KEY,
94
- ts INTEGER NOT NULL
109
+ user_id TEXT PRIMARY KEY,
110
+ ts INTEGER NOT NULL,
111
+ erased_at INTEGER
95
112
  );
96
113
 
97
114
  -- Entry ledger: the once-per-user policy, instance addressing, and the audit
@@ -110,6 +127,10 @@ CREATE TABLE IF NOT EXISTS entries (
110
127
  hash TEXT NOT NULL,
111
128
  status TEXT NOT NULL,
112
129
  ts INTEGER NOT NULL,
130
+ -- When the runner admitted itself (admitInstance), before its first
131
+ -- observable effect; null until then, so an erasure knows nothing of the
132
+ -- instance has run yet.
133
+ admitted_at INTEGER,
113
134
  PRIMARY KEY (workflow, user_id)
114
135
  );
115
136
 
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_host_journey_host = require("../host/journey-host.cjs");
3
+ const require_cloudflare_bindings = require("./bindings.cjs");
3
4
  const require_cloudflare_runner = require("./runner.cjs");
4
5
  //#region src/cloudflare/worker.ts
5
6
  /**
@@ -27,9 +28,20 @@ function journeyWorker(options) {
27
28
  await this.leases.get(store)?.();
28
29
  }
29
30
  createRunParts(store, params) {
30
- return host.runParts(host.checkEnv(this.env), store, params);
31
+ return host.runParts(host.checkEnv(this.hostEnv()), store, params);
31
32
  }
32
33
  }
34
+ /**
35
+ * The validated environment, its Workflow binding behind the host's port (an unknown instance
36
+ * answers `InstanceNotFoundError`). Everything below reads this one, never the raw env.
37
+ */
38
+ const hostEnv = (rawEnv) => {
39
+ const env = host.checkEnv(rawEnv);
40
+ return {
41
+ ...env,
42
+ JOURNEY: require_cloudflare_bindings.cloudflareJourneyBinding(env.JOURNEY)
43
+ };
44
+ };
33
45
  /** CF's version metadata: the bundle compiled into each new version goes live by itself. */
34
46
  const ownVersion = (env) => {
35
47
  const metadata = env.CF_VERSION_METADATA;
@@ -42,7 +54,7 @@ function journeyWorker(options) {
42
54
  const fetch = async (request, rawEnv, ctx) => {
43
55
  let env;
44
56
  try {
45
- env = host.checkEnv(rawEnv);
57
+ env = hostEnv(rawEnv);
46
58
  } catch (error) {
47
59
  if (!(error instanceof require_host_journey_host.EnvValidationError)) throw error;
48
60
  console.error(`[workflow] ${error.message}`);
@@ -64,13 +76,11 @@ function journeyWorker(options) {
64
76
  else ctx.waitUntil(close());
65
77
  }
66
78
  };
67
- const sweep = (rawEnv) => host.sweep(host.checkEnv(rawEnv));
68
- const scheduled = async (_controller, env, ctx) => {
69
- ctx.waitUntil((async () => {
70
- const checked = host.checkEnv(env);
71
- await host.applyOwnBundle(checked, ownVersion(checked));
72
- await sweep(env);
73
- })());
79
+ const sweep = (rawEnv) => host.sweep(hostEnv(rawEnv));
80
+ const scheduled = async (_controller, rawEnv, _ctx) => {
81
+ const env = hostEnv(rawEnv);
82
+ await host.applyOwnBundle(env, ownVersion(env));
83
+ await host.sweep(env);
74
84
  };
75
85
  return {
76
86
  Runner,
@@ -1 +1 @@
1
- {"version":3,"file":"worker.cjs","names":["createJourneyHost","JourneyRunner","EnvValidationError"],"sources":["../../src/cloudflare/worker.ts"],"sourcesContent":["import type { JourneyRunParts } from '../host/execute';\nimport {\n EnvValidationError,\n type JourneyHostOptions,\n createJourneyHost,\n} from '../host/journey-host';\nimport type { SweepResult } from '../host/router';\nimport type { BundleVersion, JourneyStore } from '../store/index';\nimport type { JourneyEnv, JourneyParams } from './bindings';\nimport { JourneyRunner } from './runner';\n\nexport { EnvValidationError } from '../host/journey-host';\n\n/** The fetch handler's execution context (the subset used). */\nexport interface ExecutionContextLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\n/**\n * `journeyWorker`'s options: the host's (config, store, email transport, observer, ...). The\n * email transport is the host's choice — `cloudflareEmailTransport({ binding: env.EMAIL })` for\n * Cloudflare Email, `sesEmailTransport(...)` for SES.\n */\nexport type JourneyWorkerOptions<Env extends JourneyEnv> = JourneyHostOptions<Env>;\n\nexport interface JourneyWorker<Env extends JourneyEnv> {\n /** Export this under the `class_name` of the wrangler `workflows` binding. */\n Runner: new (...args: never[]) => JourneyRunner;\n /** The Worker's fetch handler: `/health`, `/events`, `/identify`, `/devices`, `/messages/opened`, `/segments/sweep`, `/t/*` (email tracking), `/deploy`, `/bundle`, `/bundle/deploy` and `/preview/*`. */\n fetch: (request: Request, env: Env, ctx?: ExecutionContextLike) => Promise<Response>;\n /**\n * The segment sweep (`sweepSegments`): membership of every deployed segment for every known\n * user, the journeys of segments entered by the clock alone (`not(performed(e.login, { within:\n * '7 days' }))` has no event to ride on), and each segment's size. Also served as\n * `POST /segments/sweep`.\n */\n sweep: (env: Env) => Promise<SweepResult>;\n /** The cron trigger's handler: runs the sweep. Export it beside `fetch` with a `triggers.crons` entry. */\n scheduled: (controller: unknown, env: Env, ctx: ExecutionContextLike) => Promise<void>;\n}\n\n/**\n * A complete journey host in one call: the runner class with the app's store,\n * outlets and runtime plugged in, and the fetch handler that serves the ingest\n * API around them.\n *\n * const journeys = journeyWorker<Env>({\n * config, // workflow.config.ts (defineConfig)\n * store: (env, database) => postgresJsStore(env.HYPERDRIVE.connectionString, database),\n * });\n * export const MyRunner = journeys.Runner;\n * export default { fetch: journeys.fetch };\n */\nexport function journeyWorker<Env extends JourneyEnv>(\n options: JourneyWorkerOptions<Env>\n): JourneyWorker<Env> {\n const host = createJourneyHost(options);\n\n class Runner extends JourneyRunner {\n private readonly leases = new WeakMap<JourneyStore, () => Promise<void>>();\n\n protected override createStore(): JourneyStore {\n // A run fails at its start on an invalid environment, not at the send that needs it\n const { store, close } = host.lease(host.checkEnv(this.env as Env));\n this.leases.set(store, close);\n return store;\n }\n\n protected override async closeStore(store: JourneyStore): Promise<void> {\n await this.leases.get(store)?.();\n }\n\n protected override createRunParts(store: JourneyStore, params: JourneyParams): JourneyRunParts {\n return host.runParts(host.checkEnv(this.env as Env), store, params);\n }\n }\n\n /** CF's version metadata: the bundle compiled into each new version goes live by itself. */\n const ownVersion = (env: Env): BundleVersion | undefined => {\n const metadata = env.CF_VERSION_METADATA;\n if (metadata === undefined) return undefined;\n return { id: metadata.id, createdAt: Date.parse(metadata.timestamp) };\n };\n\n const fetch = async (request: Request, rawEnv: Env, ctx?: ExecutionContextLike) => {\n // Everything below reads the validated environment (the schema's defaults applied)\n let env: Env;\n try {\n env = host.checkEnv(rawEnv);\n } catch (error) {\n if (!(error instanceof EnvValidationError)) throw error;\n console.error(`[workflow] ${error.message}`);\n return new Response(JSON.stringify({ error: error.message }), {\n status: 500,\n headers: { 'content-type': 'application/json' },\n });\n }\n const pending = host.applyOwnBundle(env, ownVersion(env));\n // In the background where the runtime has waitUntil; inline otherwise (tests)\n if (pending !== undefined) {\n if (ctx === undefined) await pending;\n else ctx.waitUntil(pending);\n }\n const { store, close } = host.lease(env);\n try {\n return await host.handle(request, env, store);\n } finally {\n // After the response when the runtime offers waitUntil, inline otherwise (tests).\n if (ctx === undefined) await close();\n else ctx.waitUntil(close());\n }\n };\n\n const sweep = (rawEnv: Env) => host.sweep(host.checkEnv(rawEnv));\n\n const scheduled = async (_controller: unknown, env: Env, ctx: ExecutionContextLike) => {\n // A version with no traffic yet goes live here; the sweep then reads its segments\n ctx.waitUntil(\n (async () => {\n const checked = host.checkEnv(env);\n await host.applyOwnBundle(checked, ownVersion(checked));\n await sweep(env);\n })()\n );\n };\n\n return { Runner, fetch, sweep, scheduled };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqDA,SAAgB,cACd,SACoB;CACpB,MAAM,OAAOA,0BAAAA,kBAAkB,OAAO;CAEtC,MAAM,eAAeC,0BAAAA,cAAc;EACjC,yBAA0B,IAAI,QAA2C;EAEzE,cAA+C;GAE7C,MAAM,EAAE,OAAO,UAAU,KAAK,MAAM,KAAK,SAAS,KAAK,GAAU,CAAC;GAClE,KAAK,OAAO,IAAI,OAAO,KAAK;GAC5B,OAAO;EACT;EAEA,MAAyB,WAAW,OAAoC;GACtE,MAAM,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG;EACjC;EAEA,eAAkC,OAAqB,QAAwC;GAC7F,OAAO,KAAK,SAAS,KAAK,SAAS,KAAK,GAAU,GAAG,OAAO,MAAM;EACpE;CACF;;CAGA,MAAM,cAAc,QAAwC;EAC1D,MAAM,WAAW,IAAI;EACrB,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;EACnC,OAAO;GAAE,IAAI,SAAS;GAAI,WAAW,KAAK,MAAM,SAAS,SAAS;EAAE;CACtE;CAEA,MAAM,QAAQ,OAAO,SAAkB,QAAa,QAA+B;EAEjF,IAAI;EACJ,IAAI;GACF,MAAM,KAAK,SAAS,MAAM;EAC5B,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiBC,0BAAAA,qBAAqB,MAAM;GAClD,QAAQ,MAAM,cAAc,MAAM,SAAS;GAC3C,OAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,MAAM,QAAQ,CAAC,GAAG;IAC5D,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;GAChD,CAAC;EACH;EACA,MAAM,UAAU,KAAK,eAAe,KAAK,WAAW,GAAG,CAAC;EAExD,IAAI,YAAY,KAAA,GAAW;GACzB,IAAI,QAAQ,KAAA,GAAW,MAAM;QACxB,IAAI,UAAU,OAAO;EAC5B;EACA,MAAM,EAAE,OAAO,UAAU,KAAK,MAAM,GAAG;EACvC,IAAI;GACF,OAAO,MAAM,KAAK,OAAO,SAAS,KAAK,KAAK;EAC9C,UAAU;GAER,IAAI,QAAQ,KAAA,GAAW,MAAM,MAAM;QAC9B,IAAI,UAAU,MAAM,CAAC;EAC5B;CACF;CAEA,MAAM,SAAS,WAAgB,KAAK,MAAM,KAAK,SAAS,MAAM,CAAC;CAE/D,MAAM,YAAY,OAAO,aAAsB,KAAU,QAA8B;EAErF,IAAI,WACD,YAAY;GACX,MAAM,UAAU,KAAK,SAAS,GAAG;GACjC,MAAM,KAAK,eAAe,SAAS,WAAW,OAAO,CAAC;GACtD,MAAM,MAAM,GAAG;EACjB,EAAA,CAAG,CACL;CACF;CAEA,OAAO;EAAE;EAAQ;EAAO;EAAO;CAAU;AAC3C"}
1
+ {"version":3,"file":"worker.cjs","names":["createJourneyHost","JourneyRunner","cloudflareJourneyBinding","EnvValidationError"],"sources":["../../src/cloudflare/worker.ts"],"sourcesContent":["import type { JourneyRunParts } from '../host/execute';\nimport type { SweepResult } from '../host/ingest';\nimport {\n EnvValidationError,\n type JourneyHostOptions,\n createJourneyHost,\n} from '../host/journey-host';\nimport type { JourneyEnv, JourneyParams } from '../host/platform';\nimport type { BundleVersion, JourneyStore } from '../store/index';\nimport { cloudflareJourneyBinding } from './bindings';\nimport { JourneyRunner } from './runner';\n\nexport { EnvValidationError } from '../host/journey-host';\n\n/** The fetch handler's execution context (the subset used). */\nexport interface ExecutionContextLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\n/**\n * `journeyWorker`'s options: the host's (config, store, email transport, observer, ...). The\n * email transport is the host's choice — `cloudflareEmailTransport({ binding: env.EMAIL })` for\n * Cloudflare Email, `sesEmailTransport(...)` for SES.\n */\nexport type JourneyWorkerOptions<Env extends JourneyEnv> = JourneyHostOptions<Env>;\n\nexport interface JourneyWorker<Env extends JourneyEnv> {\n /** Export this under the `class_name` of the wrangler `workflows` binding. */\n Runner: new (...args: never[]) => JourneyRunner;\n /** The Worker's fetch handler: `/health`, `/events`, `/identify`, `/devices`, `/messages/opened`, `/segments/sweep`, `/t/*` (email tracking), `/deploy`, `/bundle`, `/bundle/deploy` and `/preview/*`. */\n fetch: (request: Request, env: Env, ctx?: ExecutionContextLike) => Promise<Response>;\n /**\n * The segment sweep (`sweepSegments`): membership of every deployed segment for every known\n * user, the journeys of segments entered by the clock alone (`not(performed(e.login, { within:\n * '7 days' }))` has no event to ride on), and each segment's size. Also served as\n * `POST /segments/sweep`.\n */\n sweep: (env: Env) => Promise<SweepResult>;\n /** The cron trigger's handler: runs the sweep. Export it beside `fetch` with a `triggers.crons` entry. */\n scheduled: (controller: unknown, env: Env, ctx: ExecutionContextLike) => Promise<void>;\n}\n\n/**\n * A complete journey host in one call: the runner class with the app's store,\n * outlets and runtime plugged in, and the fetch handler that serves the ingest\n * API around them.\n *\n * const journeys = journeyWorker<Env>({\n * config, // workflow.config.ts (defineConfig)\n * store: (env, database) => postgresJsStore(env.HYPERDRIVE.connectionString, database),\n * });\n * export const MyRunner = journeys.Runner;\n * export default { fetch: journeys.fetch };\n */\nexport function journeyWorker<Env extends JourneyEnv>(\n options: JourneyWorkerOptions<Env>\n): JourneyWorker<Env> {\n const host = createJourneyHost(options);\n\n class Runner extends JourneyRunner {\n private readonly leases = new WeakMap<JourneyStore, () => Promise<void>>();\n\n protected override createStore(): JourneyStore {\n // A run fails at its start on an invalid environment, not at the send that needs it\n const { store, close } = host.lease(host.checkEnv(this.env as Env));\n this.leases.set(store, close);\n return store;\n }\n\n protected override async closeStore(store: JourneyStore): Promise<void> {\n await this.leases.get(store)?.();\n }\n\n protected override createRunParts(store: JourneyStore, params: JourneyParams): JourneyRunParts {\n return host.runParts(host.checkEnv(this.hostEnv() as Env), store, params);\n }\n }\n\n /**\n * The validated environment, its Workflow binding behind the host's port (an unknown instance\n * answers `InstanceNotFoundError`). Everything below reads this one, never the raw env.\n */\n const hostEnv = (rawEnv: Env): Env => {\n const env = host.checkEnv(rawEnv);\n return { ...env, JOURNEY: cloudflareJourneyBinding(env.JOURNEY) };\n };\n\n /** CF's version metadata: the bundle compiled into each new version goes live by itself. */\n const ownVersion = (env: Env): BundleVersion | undefined => {\n const metadata = env.CF_VERSION_METADATA;\n if (metadata === undefined) return undefined;\n return { id: metadata.id, createdAt: Date.parse(metadata.timestamp) };\n };\n\n const fetch = async (request: Request, rawEnv: Env, ctx?: ExecutionContextLike) => {\n let env: Env;\n try {\n env = hostEnv(rawEnv);\n } catch (error) {\n if (!(error instanceof EnvValidationError)) throw error;\n console.error(`[workflow] ${error.message}`);\n return new Response(JSON.stringify({ error: error.message }), {\n status: 500,\n headers: { 'content-type': 'application/json' },\n });\n }\n const pending = host.applyOwnBundle(env, ownVersion(env));\n // In the background where the runtime has waitUntil; inline otherwise (tests)\n if (pending !== undefined) {\n if (ctx === undefined) await pending;\n else ctx.waitUntil(pending);\n }\n const { store, close } = host.lease(env);\n try {\n return await host.handle(request, env, store);\n } finally {\n // After the response when the runtime offers waitUntil, inline otherwise (tests).\n if (ctx === undefined) await close();\n else ctx.waitUntil(close());\n }\n };\n\n const sweep = (rawEnv: Env) => host.sweep(hostEnv(rawEnv));\n\n const scheduled = async (_controller: unknown, rawEnv: Env, _ctx: ExecutionContextLike) => {\n // Awaited, not handed to waitUntil: the runtime keeps a scheduled handler alive for as long\n // as its promise runs, while a sweep only parked in waitUntil can be cut off by the\n // handler returning, and its failure would surface nowhere. A version with no traffic yet\n // goes live here; the sweep then reads its segments.\n const env = hostEnv(rawEnv);\n await host.applyOwnBundle(env, ownVersion(env));\n await host.sweep(env);\n };\n\n return { Runner, fetch, sweep, scheduled };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAsDA,SAAgB,cACd,SACoB;CACpB,MAAM,OAAOA,0BAAAA,kBAAkB,OAAO;CAEtC,MAAM,eAAeC,0BAAAA,cAAc;EACjC,yBAA0B,IAAI,QAA2C;EAEzE,cAA+C;GAE7C,MAAM,EAAE,OAAO,UAAU,KAAK,MAAM,KAAK,SAAS,KAAK,GAAU,CAAC;GAClE,KAAK,OAAO,IAAI,OAAO,KAAK;GAC5B,OAAO;EACT;EAEA,MAAyB,WAAW,OAAoC;GACtE,MAAM,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG;EACjC;EAEA,eAAkC,OAAqB,QAAwC;GAC7F,OAAO,KAAK,SAAS,KAAK,SAAS,KAAK,QAAQ,CAAQ,GAAG,OAAO,MAAM;EAC1E;CACF;;;;;CAMA,MAAM,WAAW,WAAqB;EACpC,MAAM,MAAM,KAAK,SAAS,MAAM;EAChC,OAAO;GAAE,GAAG;GAAK,SAASC,4BAAAA,yBAAyB,IAAI,OAAO;EAAE;CAClE;;CAGA,MAAM,cAAc,QAAwC;EAC1D,MAAM,WAAW,IAAI;EACrB,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;EACnC,OAAO;GAAE,IAAI,SAAS;GAAI,WAAW,KAAK,MAAM,SAAS,SAAS;EAAE;CACtE;CAEA,MAAM,QAAQ,OAAO,SAAkB,QAAa,QAA+B;EACjF,IAAI;EACJ,IAAI;GACF,MAAM,QAAQ,MAAM;EACtB,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiBC,0BAAAA,qBAAqB,MAAM;GAClD,QAAQ,MAAM,cAAc,MAAM,SAAS;GAC3C,OAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,MAAM,QAAQ,CAAC,GAAG;IAC5D,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;GAChD,CAAC;EACH;EACA,MAAM,UAAU,KAAK,eAAe,KAAK,WAAW,GAAG,CAAC;EAExD,IAAI,YAAY,KAAA,GAAW;GACzB,IAAI,QAAQ,KAAA,GAAW,MAAM;QACxB,IAAI,UAAU,OAAO;EAC5B;EACA,MAAM,EAAE,OAAO,UAAU,KAAK,MAAM,GAAG;EACvC,IAAI;GACF,OAAO,MAAM,KAAK,OAAO,SAAS,KAAK,KAAK;EAC9C,UAAU;GAER,IAAI,QAAQ,KAAA,GAAW,MAAM,MAAM;QAC9B,IAAI,UAAU,MAAM,CAAC;EAC5B;CACF;CAEA,MAAM,SAAS,WAAgB,KAAK,MAAM,QAAQ,MAAM,CAAC;CAEzD,MAAM,YAAY,OAAO,aAAsB,QAAa,SAA+B;EAKzF,MAAM,MAAM,QAAQ,MAAM;EAC1B,MAAM,KAAK,eAAe,KAAK,WAAW,GAAG,CAAC;EAC9C,MAAM,KAAK,MAAM,GAAG;CACtB;CAEA,OAAO;EAAE;EAAQ;EAAO;EAAO;CAAU;AAC3C"}
@@ -1,5 +1,5 @@
1
- import { JourneyEnv } from "./bindings.cjs";
2
- import { SweepResult } from "../host/router.cjs";
1
+ import { JourneyEnv } from "../host/platform.cjs";
2
+ import { SweepResult } from "../host/ingest.cjs";
3
3
  import { EnvValidationError, JourneyHostOptions } from "../host/journey-host.cjs";
4
4
  import { JourneyRunner } from "./runner.cjs";
5
5
  //#region src/cloudflare/worker.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.cts","names":[],"sources":["../../src/cloudflare/worker.ts"],"mappings":";;;;;;iBAciB;EACf,UAAU,SAAS;;;;;;;YAQT,qBAAqB,YAAY,cAAc,mBAAmB;iBAE7D,cAAc,YAAY;;EAEzC,gBAAgB,kBAAkB;;EAElC,QAAQ,SAAS,SAAS,KAAK,KAAK,MAAM,yBAAyB,QAAQ;;;;;;;EAO3E,QAAQ,KAAK,QAAQ,QAAQ;;EAE7B,YAAY,qBAAqB,KAAK,KAAK,KAAK,yBAAyB;;;;;;;;;;;;;;wBAe3D,cAAc,YAAY,YACxC,SAAS,qBAAqB,OAC7B,cAAc"}
1
+ {"version":3,"file":"worker.d.cts","names":[],"sources":["../../src/cloudflare/worker.ts"],"mappings":";;;;;;iBAeiB;EACf,UAAU,SAAS;;;;;;;YAQT,qBAAqB,YAAY,cAAc,mBAAmB;iBAE7D,cAAc,YAAY;;EAEzC,gBAAgB,kBAAkB;;EAElC,QAAQ,SAAS,SAAS,KAAK,KAAK,MAAM,yBAAyB,QAAQ;;;;;;;EAO3E,QAAQ,KAAK,QAAQ,QAAQ;;EAE7B,YAAY,qBAAqB,KAAK,KAAK,KAAK,yBAAyB;;;;;;;;;;;;;;wBAe3D,cAAc,YAAY,YACxC,SAAS,qBAAqB,OAC7B,cAAc"}
@@ -1,5 +1,5 @@
1
- import { JourneyEnv } from "./bindings.mjs";
2
- import { SweepResult } from "../host/router.mjs";
1
+ import { JourneyEnv } from "../host/platform.mjs";
2
+ import { SweepResult } from "../host/ingest.mjs";
3
3
  import { EnvValidationError, JourneyHostOptions } from "../host/journey-host.mjs";
4
4
  import { JourneyRunner } from "./runner.mjs";
5
5
  //#region src/cloudflare/worker.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"worker.d.mts","names":[],"sources":["../../src/cloudflare/worker.ts"],"mappings":";;;;;;iBAciB;EACf,UAAU,SAAS;;;;;;;YAQT,qBAAqB,YAAY,cAAc,mBAAmB;iBAE7D,cAAc,YAAY;;EAEzC,gBAAgB,kBAAkB;;EAElC,QAAQ,SAAS,SAAS,KAAK,KAAK,MAAM,yBAAyB,QAAQ;;;;;;;EAO3E,QAAQ,KAAK,QAAQ,QAAQ;;EAE7B,YAAY,qBAAqB,KAAK,KAAK,KAAK,yBAAyB;;;;;;;;;;;;;;wBAe3D,cAAc,YAAY,YACxC,SAAS,qBAAqB,OAC7B,cAAc"}
1
+ {"version":3,"file":"worker.d.mts","names":[],"sources":["../../src/cloudflare/worker.ts"],"mappings":";;;;;;iBAeiB;EACf,UAAU,SAAS;;;;;;;YAQT,qBAAqB,YAAY,cAAc,mBAAmB;iBAE7D,cAAc,YAAY;;EAEzC,gBAAgB,kBAAkB;;EAElC,QAAQ,SAAS,SAAS,KAAK,KAAK,MAAM,yBAAyB,QAAQ;;;;;;;EAO3E,QAAQ,KAAK,QAAQ,QAAQ;;EAE7B,YAAY,qBAAqB,KAAK,KAAK,KAAK,yBAAyB;;;;;;;;;;;;;;wBAe3D,cAAc,YAAY,YACxC,SAAS,qBAAqB,OAC7B,cAAc"}