@shware/workflow 0.0.0-stage → 0.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 (442) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -2
  3. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  4. package/dist/apns-relay.cjs +105 -0
  5. package/dist/apns-relay.cjs.map +1 -0
  6. package/dist/apns-relay.d.cts +28 -0
  7. package/dist/apns-relay.d.cts.map +1 -0
  8. package/dist/apns-relay.d.mts +28 -0
  9. package/dist/apns-relay.d.mts.map +1 -0
  10. package/dist/apns-relay.mjs +104 -0
  11. package/dist/apns-relay.mjs.map +1 -0
  12. package/dist/apns.cjs +121 -0
  13. package/dist/apns.cjs.map +1 -0
  14. package/dist/apns.d.cts +63 -0
  15. package/dist/apns.d.cts.map +1 -0
  16. package/dist/apns.d.mts +63 -0
  17. package/dist/apns.d.mts.map +1 -0
  18. package/dist/apns.mjs +116 -0
  19. package/dist/apns.mjs.map +1 -0
  20. package/dist/client.cjs +125 -0
  21. package/dist/client.cjs.map +1 -0
  22. package/dist/client.d.cts +108 -0
  23. package/dist/client.d.cts.map +1 -0
  24. package/dist/client.d.mts +108 -0
  25. package/dist/client.d.mts.map +1 -0
  26. package/dist/client.mjs +122 -0
  27. package/dist/client.mjs.map +1 -0
  28. package/dist/cloudflare/bindings.cjs +28 -0
  29. package/dist/cloudflare/bindings.cjs.map +1 -0
  30. package/dist/cloudflare/bindings.d.cts +144 -0
  31. package/dist/cloudflare/bindings.d.cts.map +1 -0
  32. package/dist/cloudflare/bindings.d.mts +144 -0
  33. package/dist/cloudflare/bindings.d.mts.map +1 -0
  34. package/dist/cloudflare/bindings.mjs +25 -0
  35. package/dist/cloudflare/bindings.mjs.map +1 -0
  36. package/dist/cloudflare/channels.cjs +164 -0
  37. package/dist/cloudflare/channels.cjs.map +1 -0
  38. package/dist/cloudflare/channels.d.cts +29 -0
  39. package/dist/cloudflare/channels.d.cts.map +1 -0
  40. package/dist/cloudflare/channels.d.mts +29 -0
  41. package/dist/cloudflare/channels.d.mts.map +1 -0
  42. package/dist/cloudflare/channels.mjs +161 -0
  43. package/dist/cloudflare/channels.mjs.map +1 -0
  44. package/dist/cloudflare/d1-env.cjs +17 -0
  45. package/dist/cloudflare/d1-env.cjs.map +1 -0
  46. package/dist/cloudflare/d1-env.d.cts +12 -0
  47. package/dist/cloudflare/d1-env.d.cts.map +1 -0
  48. package/dist/cloudflare/d1-env.d.mts +12 -0
  49. package/dist/cloudflare/d1-env.d.mts.map +1 -0
  50. package/dist/cloudflare/d1-env.mjs +16 -0
  51. package/dist/cloudflare/d1-env.mjs.map +1 -0
  52. package/dist/cloudflare/index.cjs +54 -0
  53. package/dist/cloudflare/index.d.cts +19 -0
  54. package/dist/cloudflare/index.d.mts +19 -0
  55. package/dist/cloudflare/index.mjs +16 -0
  56. package/dist/cloudflare/router.cjs +516 -0
  57. package/dist/cloudflare/router.cjs.map +1 -0
  58. package/dist/cloudflare/router.d.cts +157 -0
  59. package/dist/cloudflare/router.d.cts.map +1 -0
  60. package/dist/cloudflare/router.d.mts +157 -0
  61. package/dist/cloudflare/router.d.mts.map +1 -0
  62. package/dist/cloudflare/router.mjs +503 -0
  63. package/dist/cloudflare/router.mjs.map +1 -0
  64. package/dist/cloudflare/runner.cjs +225 -0
  65. package/dist/cloudflare/runner.cjs.map +1 -0
  66. package/dist/cloudflare/runner.d.cts +73 -0
  67. package/dist/cloudflare/runner.d.cts.map +1 -0
  68. package/dist/cloudflare/runner.d.mts +73 -0
  69. package/dist/cloudflare/runner.d.mts.map +1 -0
  70. package/dist/cloudflare/runner.mjs +224 -0
  71. package/dist/cloudflare/runner.mjs.map +1 -0
  72. package/dist/cloudflare/schema.sql +127 -0
  73. package/dist/cloudflare/senders.cjs +114 -0
  74. package/dist/cloudflare/senders.cjs.map +1 -0
  75. package/dist/cloudflare/senders.d.cts +68 -0
  76. package/dist/cloudflare/senders.d.cts.map +1 -0
  77. package/dist/cloudflare/senders.d.mts +68 -0
  78. package/dist/cloudflare/senders.d.mts.map +1 -0
  79. package/dist/cloudflare/senders.mjs +108 -0
  80. package/dist/cloudflare/senders.mjs.map +1 -0
  81. package/dist/cloudflare/worker.cjs +228 -0
  82. package/dist/cloudflare/worker.cjs.map +1 -0
  83. package/dist/cloudflare/worker.d.cts +92 -0
  84. package/dist/cloudflare/worker.d.cts.map +1 -0
  85. package/dist/cloudflare/worker.d.mts +92 -0
  86. package/dist/cloudflare/worker.d.mts.map +1 -0
  87. package/dist/cloudflare/worker.mjs +224 -0
  88. package/dist/cloudflare/worker.mjs.map +1 -0
  89. package/dist/cloudflare/workers.d.cjs +0 -0
  90. package/dist/cloudflare/workers.d.cts +44 -0
  91. package/dist/cloudflare/workers.d.cts.map +1 -0
  92. package/dist/cloudflare/workers.d.mts +44 -0
  93. package/dist/cloudflare/workers.d.mts.map +1 -0
  94. package/dist/config.cjs +151 -0
  95. package/dist/config.cjs.map +1 -0
  96. package/dist/config.d.cts +149 -0
  97. package/dist/config.d.cts.map +1 -0
  98. package/dist/config.d.mts +149 -0
  99. package/dist/config.d.mts.map +1 -0
  100. package/dist/config.mjs +143 -0
  101. package/dist/config.mjs.map +1 -0
  102. package/dist/contract.cjs +212 -0
  103. package/dist/contract.cjs.map +1 -0
  104. package/dist/contract.d.cts +90 -0
  105. package/dist/contract.d.cts.map +1 -0
  106. package/dist/contract.d.mts +90 -0
  107. package/dist/contract.d.mts.map +1 -0
  108. package/dist/contract.mjs +206 -0
  109. package/dist/contract.mjs.map +1 -0
  110. package/dist/dsl/action.cjs +29 -0
  111. package/dist/dsl/action.cjs.map +1 -0
  112. package/dist/dsl/action.d.cts +64 -0
  113. package/dist/dsl/action.d.cts.map +1 -0
  114. package/dist/dsl/action.d.mts +64 -0
  115. package/dist/dsl/action.d.mts.map +1 -0
  116. package/dist/dsl/action.mjs +28 -0
  117. package/dist/dsl/action.mjs.map +1 -0
  118. package/dist/dsl/base.cjs +34 -0
  119. package/dist/dsl/base.cjs.map +1 -0
  120. package/dist/dsl/base.d.cts +45 -0
  121. package/dist/dsl/base.d.cts.map +1 -0
  122. package/dist/dsl/base.d.mts +45 -0
  123. package/dist/dsl/base.d.mts.map +1 -0
  124. package/dist/dsl/base.mjs +30 -0
  125. package/dist/dsl/base.mjs.map +1 -0
  126. package/dist/dsl/bundle.cjs +207 -0
  127. package/dist/dsl/bundle.cjs.map +1 -0
  128. package/dist/dsl/bundle.d.cts +19 -0
  129. package/dist/dsl/bundle.d.cts.map +1 -0
  130. package/dist/dsl/bundle.d.mts +19 -0
  131. package/dist/dsl/bundle.d.mts.map +1 -0
  132. package/dist/dsl/bundle.mjs +206 -0
  133. package/dist/dsl/bundle.mjs.map +1 -0
  134. package/dist/dsl/condition.cjs +177 -0
  135. package/dist/dsl/condition.cjs.map +1 -0
  136. package/dist/dsl/condition.d.cts +140 -0
  137. package/dist/dsl/condition.d.cts.map +1 -0
  138. package/dist/dsl/condition.d.mts +140 -0
  139. package/dist/dsl/condition.d.mts.map +1 -0
  140. package/dist/dsl/condition.mjs +156 -0
  141. package/dist/dsl/condition.mjs.map +1 -0
  142. package/dist/dsl/flow.cjs +228 -0
  143. package/dist/dsl/flow.cjs.map +1 -0
  144. package/dist/dsl/flow.d.cts +223 -0
  145. package/dist/dsl/flow.d.cts.map +1 -0
  146. package/dist/dsl/flow.d.mts +223 -0
  147. package/dist/dsl/flow.d.mts.map +1 -0
  148. package/dist/dsl/flow.mjs +226 -0
  149. package/dist/dsl/flow.mjs.map +1 -0
  150. package/dist/dsl/index.cjs +38 -0
  151. package/dist/dsl/index.d.cts +10 -0
  152. package/dist/dsl/index.d.mts +10 -0
  153. package/dist/dsl/index.mjs +9 -0
  154. package/dist/dsl/refs.cjs +59 -0
  155. package/dist/dsl/refs.cjs.map +1 -0
  156. package/dist/dsl/refs.d.cts +88 -0
  157. package/dist/dsl/refs.d.cts.map +1 -0
  158. package/dist/dsl/refs.d.mts +88 -0
  159. package/dist/dsl/refs.d.mts.map +1 -0
  160. package/dist/dsl/refs.mjs +55 -0
  161. package/dist/dsl/refs.mjs.map +1 -0
  162. package/dist/dsl/template.cjs +65 -0
  163. package/dist/dsl/template.cjs.map +1 -0
  164. package/dist/dsl/template.d.cts +75 -0
  165. package/dist/dsl/template.d.cts.map +1 -0
  166. package/dist/dsl/template.d.mts +75 -0
  167. package/dist/dsl/template.d.mts.map +1 -0
  168. package/dist/dsl/template.mjs +63 -0
  169. package/dist/dsl/template.mjs.map +1 -0
  170. package/dist/dsl/trigger.cjs +40 -0
  171. package/dist/dsl/trigger.cjs.map +1 -0
  172. package/dist/dsl/trigger.d.cts +65 -0
  173. package/dist/dsl/trigger.d.cts.map +1 -0
  174. package/dist/dsl/trigger.d.mts +65 -0
  175. package/dist/dsl/trigger.d.mts.map +1 -0
  176. package/dist/dsl/trigger.mjs +39 -0
  177. package/dist/dsl/trigger.mjs.map +1 -0
  178. package/dist/dsl/workflow.cjs +121 -0
  179. package/dist/dsl/workflow.cjs.map +1 -0
  180. package/dist/dsl/workflow.d.cts +59 -0
  181. package/dist/dsl/workflow.d.cts.map +1 -0
  182. package/dist/dsl/workflow.d.mts +59 -0
  183. package/dist/dsl/workflow.d.mts.map +1 -0
  184. package/dist/dsl/workflow.mjs +120 -0
  185. package/dist/dsl/workflow.mjs.map +1 -0
  186. package/dist/engine/actions.cjs +27 -0
  187. package/dist/engine/actions.cjs.map +1 -0
  188. package/dist/engine/actions.d.cts +40 -0
  189. package/dist/engine/actions.d.cts.map +1 -0
  190. package/dist/engine/actions.d.mts +40 -0
  191. package/dist/engine/actions.d.mts.map +1 -0
  192. package/dist/engine/actions.mjs +26 -0
  193. package/dist/engine/actions.mjs.map +1 -0
  194. package/dist/engine/bucket.cjs +72 -0
  195. package/dist/engine/bucket.cjs.map +1 -0
  196. package/dist/engine/bucket.d.cts +32 -0
  197. package/dist/engine/bucket.d.cts.map +1 -0
  198. package/dist/engine/bucket.d.mts +32 -0
  199. package/dist/engine/bucket.d.mts.map +1 -0
  200. package/dist/engine/bucket.mjs +69 -0
  201. package/dist/engine/bucket.mjs.map +1 -0
  202. package/dist/engine/condition.cjs +166 -0
  203. package/dist/engine/condition.cjs.map +1 -0
  204. package/dist/engine/condition.d.cts +52 -0
  205. package/dist/engine/condition.d.cts.map +1 -0
  206. package/dist/engine/condition.d.mts +52 -0
  207. package/dist/engine/condition.d.mts.map +1 -0
  208. package/dist/engine/condition.mjs +161 -0
  209. package/dist/engine/condition.mjs.map +1 -0
  210. package/dist/engine/index.cjs +20 -0
  211. package/dist/engine/index.d.cts +8 -0
  212. package/dist/engine/index.d.mts +8 -0
  213. package/dist/engine/index.mjs +7 -0
  214. package/dist/engine/interpreter.cjs +428 -0
  215. package/dist/engine/interpreter.cjs.map +1 -0
  216. package/dist/engine/interpreter.d.cts +6 -0
  217. package/dist/engine/interpreter.d.cts.map +1 -0
  218. package/dist/engine/interpreter.d.mts +6 -0
  219. package/dist/engine/interpreter.d.mts.map +1 -0
  220. package/dist/engine/interpreter.mjs +427 -0
  221. package/dist/engine/interpreter.mjs.map +1 -0
  222. package/dist/engine/ports.cjs +0 -0
  223. package/dist/engine/ports.d.cts +252 -0
  224. package/dist/engine/ports.d.cts.map +1 -0
  225. package/dist/engine/ports.d.mts +252 -0
  226. package/dist/engine/ports.d.mts.map +1 -0
  227. package/dist/engine/ports.mjs +1 -0
  228. package/dist/engine/recipient.cjs +23 -0
  229. package/dist/engine/recipient.cjs.map +1 -0
  230. package/dist/engine/recipient.d.cts +13 -0
  231. package/dist/engine/recipient.d.cts.map +1 -0
  232. package/dist/engine/recipient.d.mts +13 -0
  233. package/dist/engine/recipient.d.mts.map +1 -0
  234. package/dist/engine/recipient.mjs +22 -0
  235. package/dist/engine/recipient.mjs.map +1 -0
  236. package/dist/engine/time-scale.cjs +54 -0
  237. package/dist/engine/time-scale.cjs.map +1 -0
  238. package/dist/engine/time-scale.d.cts +31 -0
  239. package/dist/engine/time-scale.d.cts.map +1 -0
  240. package/dist/engine/time-scale.d.mts +31 -0
  241. package/dist/engine/time-scale.d.mts.map +1 -0
  242. package/dist/engine/time-scale.mjs +52 -0
  243. package/dist/engine/time-scale.mjs.map +1 -0
  244. package/dist/engine/time-window.cjs +149 -0
  245. package/dist/engine/time-window.cjs.map +1 -0
  246. package/dist/engine/time-window.d.cts +22 -0
  247. package/dist/engine/time-window.d.cts.map +1 -0
  248. package/dist/engine/time-window.d.mts +22 -0
  249. package/dist/engine/time-window.d.mts.map +1 -0
  250. package/dist/engine/time-window.mjs +147 -0
  251. package/dist/engine/time-window.mjs.map +1 -0
  252. package/dist/fcm.cjs +125 -0
  253. package/dist/fcm.cjs.map +1 -0
  254. package/dist/fcm.d.cts +48 -0
  255. package/dist/fcm.d.cts.map +1 -0
  256. package/dist/fcm.d.mts +48 -0
  257. package/dist/fcm.d.mts.map +1 -0
  258. package/dist/fcm.mjs +121 -0
  259. package/dist/fcm.mjs.map +1 -0
  260. package/dist/hash.cjs +245 -0
  261. package/dist/hash.cjs.map +1 -0
  262. package/dist/hash.d.cts +30 -0
  263. package/dist/hash.d.cts.map +1 -0
  264. package/dist/hash.d.mts +30 -0
  265. package/dist/hash.d.mts.map +1 -0
  266. package/dist/hash.mjs +239 -0
  267. package/dist/hash.mjs.map +1 -0
  268. package/dist/index.cjs +100 -0
  269. package/dist/index.d.cts +18 -0
  270. package/dist/index.d.mts +18 -0
  271. package/dist/index.mjs +17 -0
  272. package/dist/ir.cjs +366 -0
  273. package/dist/ir.cjs.map +1 -0
  274. package/dist/ir.d.cts +486 -0
  275. package/dist/ir.d.cts.map +1 -0
  276. package/dist/ir.d.mts +486 -0
  277. package/dist/ir.d.mts.map +1 -0
  278. package/dist/ir.mjs +344 -0
  279. package/dist/ir.mjs.map +1 -0
  280. package/dist/jwt.cjs +22 -0
  281. package/dist/jwt.cjs.map +1 -0
  282. package/dist/jwt.d.cts +9 -0
  283. package/dist/jwt.d.cts.map +1 -0
  284. package/dist/jwt.d.mts +9 -0
  285. package/dist/jwt.d.mts.map +1 -0
  286. package/dist/jwt.mjs +20 -0
  287. package/dist/jwt.mjs.map +1 -0
  288. package/dist/plan.cjs +223 -0
  289. package/dist/plan.cjs.map +1 -0
  290. package/dist/plan.d.cts +68 -0
  291. package/dist/plan.d.cts.map +1 -0
  292. package/dist/plan.d.mts +68 -0
  293. package/dist/plan.d.mts.map +1 -0
  294. package/dist/plan.mjs +221 -0
  295. package/dist/plan.mjs.map +1 -0
  296. package/dist/postgres-js.cjs +42 -0
  297. package/dist/postgres-js.cjs.map +1 -0
  298. package/dist/postgres-js.d.cts +28 -0
  299. package/dist/postgres-js.d.cts.map +1 -0
  300. package/dist/postgres-js.d.mts +28 -0
  301. package/dist/postgres-js.d.mts.map +1 -0
  302. package/dist/postgres-js.mjs +38 -0
  303. package/dist/postgres-js.mjs.map +1 -0
  304. package/dist/provenance.cjs +67 -0
  305. package/dist/provenance.cjs.map +1 -0
  306. package/dist/provenance.d.cts +41 -0
  307. package/dist/provenance.d.cts.map +1 -0
  308. package/dist/provenance.d.mts +41 -0
  309. package/dist/provenance.d.mts.map +1 -0
  310. package/dist/provenance.mjs +65 -0
  311. package/dist/provenance.mjs.map +1 -0
  312. package/dist/push.cjs +14 -0
  313. package/dist/push.cjs.map +1 -0
  314. package/dist/push.d.cts +16 -0
  315. package/dist/push.d.cts.map +1 -0
  316. package/dist/push.d.mts +16 -0
  317. package/dist/push.d.mts.map +1 -0
  318. package/dist/push.mjs +13 -0
  319. package/dist/push.mjs.map +1 -0
  320. package/dist/react-email.cjs +48 -0
  321. package/dist/react-email.cjs.map +1 -0
  322. package/dist/react-email.d.cts +7 -0
  323. package/dist/react-email.d.cts.map +1 -0
  324. package/dist/react-email.d.mts +7 -0
  325. package/dist/react-email.d.mts.map +1 -0
  326. package/dist/react-email.mjs +47 -0
  327. package/dist/react-email.mjs.map +1 -0
  328. package/dist/reporting/classify.cjs +42 -0
  329. package/dist/reporting/classify.cjs.map +1 -0
  330. package/dist/reporting/classify.d.cts +60 -0
  331. package/dist/reporting/classify.d.cts.map +1 -0
  332. package/dist/reporting/classify.d.mts +60 -0
  333. package/dist/reporting/classify.d.mts.map +1 -0
  334. package/dist/reporting/classify.mjs +38 -0
  335. package/dist/reporting/classify.mjs.map +1 -0
  336. package/dist/reporting/observer.cjs +63 -0
  337. package/dist/reporting/observer.cjs.map +1 -0
  338. package/dist/reporting/observer.d.cts +18 -0
  339. package/dist/reporting/observer.d.cts.map +1 -0
  340. package/dist/reporting/observer.d.mts +18 -0
  341. package/dist/reporting/observer.d.mts.map +1 -0
  342. package/dist/reporting/observer.mjs +62 -0
  343. package/dist/reporting/observer.mjs.map +1 -0
  344. package/dist/reporting/tracking.cjs +153 -0
  345. package/dist/reporting/tracking.cjs.map +1 -0
  346. package/dist/reporting/tracking.d.cts +43 -0
  347. package/dist/reporting/tracking.d.cts.map +1 -0
  348. package/dist/reporting/tracking.d.mts +43 -0
  349. package/dist/reporting/tracking.d.mts.map +1 -0
  350. package/dist/reporting/tracking.mjs +151 -0
  351. package/dist/reporting/tracking.mjs.map +1 -0
  352. package/dist/runtime.cjs +19 -0
  353. package/dist/runtime.cjs.map +1 -0
  354. package/dist/runtime.d.cts +30 -0
  355. package/dist/runtime.d.cts.map +1 -0
  356. package/dist/runtime.d.mts +30 -0
  357. package/dist/runtime.d.mts.map +1 -0
  358. package/dist/runtime.mjs +18 -0
  359. package/dist/runtime.mjs.map +1 -0
  360. package/dist/store/d1.cjs +185 -0
  361. package/dist/store/d1.cjs.map +1 -0
  362. package/dist/store/d1.d.cts +55 -0
  363. package/dist/store/d1.d.cts.map +1 -0
  364. package/dist/store/d1.d.mts +55 -0
  365. package/dist/store/d1.d.mts.map +1 -0
  366. package/dist/store/d1.mjs +184 -0
  367. package/dist/store/d1.mjs.map +1 -0
  368. package/dist/store/facts.cjs +60 -0
  369. package/dist/store/facts.cjs.map +1 -0
  370. package/dist/store/facts.d.cts +38 -0
  371. package/dist/store/facts.d.cts.map +1 -0
  372. package/dist/store/facts.d.mts +38 -0
  373. package/dist/store/facts.d.mts.map +1 -0
  374. package/dist/store/facts.mjs +58 -0
  375. package/dist/store/facts.mjs.map +1 -0
  376. package/dist/store/index.cjs +31 -0
  377. package/dist/store/index.cjs.map +1 -0
  378. package/dist/store/index.d.cts +291 -0
  379. package/dist/store/index.d.cts.map +1 -0
  380. package/dist/store/index.d.mts +291 -0
  381. package/dist/store/index.d.mts.map +1 -0
  382. package/dist/store/index.mjs +22 -0
  383. package/dist/store/index.mjs.map +1 -0
  384. package/dist/store/postgres-reporting.cjs +213 -0
  385. package/dist/store/postgres-reporting.cjs.map +1 -0
  386. package/dist/store/postgres-reporting.d.cts +63 -0
  387. package/dist/store/postgres-reporting.d.cts.map +1 -0
  388. package/dist/store/postgres-reporting.d.mts +63 -0
  389. package/dist/store/postgres-reporting.d.mts.map +1 -0
  390. package/dist/store/postgres-reporting.mjs +212 -0
  391. package/dist/store/postgres-reporting.mjs.map +1 -0
  392. package/dist/store/postgres.cjs +309 -0
  393. package/dist/store/postgres.cjs.map +1 -0
  394. package/dist/store/postgres.d.cts +77 -0
  395. package/dist/store/postgres.d.cts.map +1 -0
  396. package/dist/store/postgres.d.mts +77 -0
  397. package/dist/store/postgres.d.mts.map +1 -0
  398. package/dist/store/postgres.mjs +306 -0
  399. package/dist/store/postgres.mjs.map +1 -0
  400. package/dist/store/sql.cjs +24 -0
  401. package/dist/store/sql.cjs.map +1 -0
  402. package/dist/store/sql.d.cts +25 -0
  403. package/dist/store/sql.d.cts.map +1 -0
  404. package/dist/store/sql.d.mts +25 -0
  405. package/dist/store/sql.d.mts.map +1 -0
  406. package/dist/store/sql.mjs +23 -0
  407. package/dist/store/sql.mjs.map +1 -0
  408. package/dist/store/tables.cjs +404 -0
  409. package/dist/store/tables.cjs.map +1 -0
  410. package/dist/store/tables.d.cts +81 -0
  411. package/dist/store/tables.d.cts.map +1 -0
  412. package/dist/store/tables.d.mts +81 -0
  413. package/dist/store/tables.d.mts.map +1 -0
  414. package/dist/store/tables.mjs +399 -0
  415. package/dist/store/tables.mjs.map +1 -0
  416. package/dist/templates.cjs +247 -0
  417. package/dist/templates.cjs.map +1 -0
  418. package/dist/templates.d.cts +187 -0
  419. package/dist/templates.d.cts.map +1 -0
  420. package/dist/templates.d.mts +187 -0
  421. package/dist/templates.d.mts.map +1 -0
  422. package/dist/templates.mjs +230 -0
  423. package/dist/templates.mjs.map +1 -0
  424. package/dist/webhooks.cjs +96 -0
  425. package/dist/webhooks.cjs.map +1 -0
  426. package/dist/webhooks.d.cts +25 -0
  427. package/dist/webhooks.d.cts.map +1 -0
  428. package/dist/webhooks.d.mts +25 -0
  429. package/dist/webhooks.d.mts.map +1 -0
  430. package/dist/webhooks.mjs +94 -0
  431. package/dist/webhooks.mjs.map +1 -0
  432. package/dist/webpush.cjs +180 -0
  433. package/dist/webpush.cjs.map +1 -0
  434. package/dist/webpush.d.cts +53 -0
  435. package/dist/webpush.d.cts.map +1 -0
  436. package/dist/webpush.d.mts +53 -0
  437. package/dist/webpush.d.mts.map +1 -0
  438. package/dist/webpush.mjs +175 -0
  439. package/dist/webpush.mjs.map +1 -0
  440. package/package.json +189 -3
  441. package/src/cloudflare/schema.sql +127 -0
  442. package/src/cloudflare/workers.d.ts +44 -0
@@ -0,0 +1,224 @@
1
+ import { TemplateValidationError, registryValidator } from "../templates.mjs";
2
+ import { compileConfig } from "../config.mjs";
3
+ import { ContractValidationError, checkEvent } from "../contract.mjs";
4
+ import "../store/tables.mjs";
5
+ import { registryRenderer } from "../react-email.mjs";
6
+ import { handleTracking } from "../reporting/tracking.mjs";
7
+ import { SchemaMismatchError } from "../store/index.mjs";
8
+ import { d1Store } from "./d1-env.mjs";
9
+ import { deployBundle, handleRequest, ingestEvent, sweepSegments } from "./router.mjs";
10
+ import { channelsFromConfig, runtimeFromConfig } from "./channels.mjs";
11
+ import { scaleSegmentWindows } from "../engine/time-scale.mjs";
12
+ import { JourneyRunner } from "./runner.mjs";
13
+ import { reportingObserver } from "../reporting/observer.mjs";
14
+ import * as z from "zod/mini";
15
+ //#region src/cloudflare/worker.ts
16
+ /**
17
+ * A complete journey host in one call: the runner class with the app's store,
18
+ * outlets and runtime plugged in, and the fetch handler that serves the ingest
19
+ * API around them.
20
+ *
21
+ * const journeys = journeyWorker<Env>({
22
+ * config, // workflow.config.ts (defineConfig)
23
+ * store: (env, database) => postgresJsStore(env.HYPERDRIVE.connectionString, database),
24
+ * });
25
+ * export const MyRunner = journeys.Runner;
26
+ * export default { fetch: journeys.fetch };
27
+ */
28
+ /** The Worker's environment failed its schema (`journeyWorker({ env })`). */
29
+ var EnvValidationError = class extends Error {
30
+ issues;
31
+ name = "EnvValidationError";
32
+ constructor(issues) {
33
+ super(`the Worker's environment is invalid:\n ${issues.join("\n ")}`);
34
+ this.issues = issues;
35
+ }
36
+ };
37
+ function journeyWorker(options) {
38
+ const { config } = options;
39
+ const checked = /* @__PURE__ */ new WeakMap();
40
+ const checkEnv = (env) => {
41
+ if (options.env === void 0) return env;
42
+ const cached = checked.get(env);
43
+ if (cached !== void 0) return cached;
44
+ const result = z.safeParse(options.env, env);
45
+ if (!result.success) throw new EnvValidationError(result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`));
46
+ const merged = {
47
+ ...env,
48
+ ...result.data
49
+ };
50
+ checked.set(env, merged);
51
+ return merged;
52
+ };
53
+ const bundle = () => compileConfig(config);
54
+ const emails = config.templates?.email;
55
+ const database = { schema: config.database?.schema ?? "workflow" };
56
+ const lease = (env) => {
57
+ const opened = options.store === void 0 ? d1Store(env) : options.store(env, database);
58
+ const noop = async () => {};
59
+ const factor = runtimeFromConfig(config, env).timeScale;
60
+ return "store" in opened ? {
61
+ store: scaleSegmentWindows(opened.store, factor),
62
+ close: opened.close ?? noop
63
+ } : {
64
+ store: scaleSegmentWindows(opened, factor),
65
+ close: noop
66
+ };
67
+ };
68
+ class Runner extends JourneyRunner {
69
+ leases = /* @__PURE__ */ new WeakMap();
70
+ current;
71
+ createStore() {
72
+ const { store, close } = lease(checkEnv(this.env));
73
+ this.leases.set(store, close);
74
+ this.current = store;
75
+ return store;
76
+ }
77
+ async closeStore(store) {
78
+ await this.leases.get(store)?.();
79
+ }
80
+ contract() {
81
+ return config.contract;
82
+ }
83
+ createMessageValidator() {
84
+ return registryValidator(config.templates ?? {});
85
+ }
86
+ createMessageSender() {
87
+ if (this.current === void 0) return super.createMessageSender();
88
+ if (options.messages !== void 0) return options.messages(checkEnv(this.env), this.current);
89
+ return channelsFromConfig(config, checkEnv(this.env), { store: this.current });
90
+ }
91
+ createObserver(store) {
92
+ if (options.observer !== void 0) return options.observer(checkEnv(this.env), store);
93
+ return store.reporting === void 0 ? void 0 : reportingObserver(store.reporting, checkEnv(this.env).WORKFLOW_ORIGIN);
94
+ }
95
+ runtime(params) {
96
+ const env = checkEnv(this.env);
97
+ if (options.runtime !== void 0) return options.runtime(env, params);
98
+ return runtimeFromConfig(config, env);
99
+ }
100
+ actions() {
101
+ return config.actions ?? [];
102
+ }
103
+ }
104
+ const json = (body, status = 200) => new Response(JSON.stringify(body), {
105
+ status,
106
+ headers: { "content-type": "application/json" }
107
+ });
108
+ const authorized = (request, env) => env.API_TOKEN === void 0 || request.headers.get("authorization") === `Bearer ${env.API_TOKEN}`;
109
+ const preview = emails === void 0 ? void 0 : registryRenderer(emails);
110
+ const fetch = async (request, rawEnv, ctx) => {
111
+ let env;
112
+ try {
113
+ env = checkEnv(rawEnv);
114
+ } catch (error) {
115
+ if (!(error instanceof EnvValidationError)) throw error;
116
+ console.error(`[workflow] ${error.message}`);
117
+ return json({ error: error.message }, 500);
118
+ }
119
+ const url = new URL(request.url);
120
+ const { store, close } = lease(env);
121
+ try {
122
+ if (url.pathname.startsWith("/t/") && store.reporting !== void 0) {
123
+ const openedEvent = config.webPush?.openedEvent;
124
+ const tracked = await handleTracking(request, store.reporting, {
125
+ unsubscribe: config.emails?.unsubscribe,
126
+ product: config.title,
127
+ quickMs: config.emails?.tracking?.quickClickMs,
128
+ pushOpened: openedEvent === void 0 ? void 0 : async ({ messageId, userId, at }) => {
129
+ const payload = { message_id: messageId };
130
+ const contract = config.contract;
131
+ const verdict = contract === void 0 ? void 0 : checkEvent(contract, openedEvent, payload);
132
+ if (verdict !== void 0 && !verdict.ok) throw new ContractValidationError(openedEvent, verdict.issues);
133
+ await ingestEvent(env, {
134
+ id: `${openedEvent}:${messageId}`,
135
+ userId,
136
+ event: openedEvent,
137
+ payload: verdict?.data ?? payload,
138
+ ts: at
139
+ }, store);
140
+ }
141
+ });
142
+ if (tracked !== void 0) return tracked;
143
+ }
144
+ if (url.pathname === "/segments/sweep") {
145
+ if (!authorized(request, env)) return json({ error: "unauthorized" }, 401);
146
+ if (request.method !== "POST") return json({ error: "method not allowed" }, 405);
147
+ return json(await sweepSegments(env, store));
148
+ }
149
+ if (url.pathname === "/bundle") {
150
+ if (!authorized(request, env)) return json({ error: "unauthorized" }, 401);
151
+ if (request.method !== "GET") return json({ error: "method not allowed" }, 405);
152
+ return json(bundle());
153
+ }
154
+ if (url.pathname === "/bundle/deploy") {
155
+ if (!authorized(request, env)) return json({ error: "unauthorized" }, 401);
156
+ if (request.method !== "POST") return json({ error: "method not allowed" }, 405);
157
+ try {
158
+ return json(await deployBundle(env, bundle(), store));
159
+ } catch (error) {
160
+ if (!(error instanceof SchemaMismatchError)) throw error;
161
+ return json({
162
+ error: error.message,
163
+ problems: error.problems
164
+ }, 503);
165
+ }
166
+ }
167
+ if (emails !== void 0 && preview !== void 0 && url.pathname.startsWith("/preview/")) {
168
+ if (!authorized(request, env)) return json({ error: "unauthorized" }, 401);
169
+ if (request.method !== "GET") return json({ error: "method not allowed" }, 405);
170
+ const key = url.pathname.slice(9);
171
+ if (!Object.hasOwn(emails, key)) return json({ error: `unknown email template: ${key}` }, 404);
172
+ let props = {};
173
+ try {
174
+ props = JSON.parse(url.searchParams.get("props") ?? "{}");
175
+ } catch {
176
+ return json({ error: "props must be a JSON object" }, 400);
177
+ }
178
+ if (typeof props !== "object" || props === null || Array.isArray(props)) return json({ error: "props must be a JSON object" }, 400);
179
+ try {
180
+ const { subject, html } = await preview(key, props);
181
+ return new Response(html, { headers: {
182
+ "content-type": "text/html; charset=utf-8",
183
+ "x-subject": subject
184
+ } });
185
+ } catch (error) {
186
+ if (error instanceof TemplateValidationError) return json({
187
+ error: error.message,
188
+ issues: error.issues
189
+ }, 400);
190
+ throw error;
191
+ }
192
+ }
193
+ return await handleRequest(request, env, store, {
194
+ contract: config.contract,
195
+ mode: options.contractMode
196
+ });
197
+ } finally {
198
+ if (ctx === void 0) await close();
199
+ else ctx.waitUntil(close());
200
+ }
201
+ };
202
+ const sweep = async (rawEnv) => {
203
+ const env = checkEnv(rawEnv);
204
+ const { store, close } = lease(env);
205
+ try {
206
+ return await sweepSegments(env, store);
207
+ } finally {
208
+ await close();
209
+ }
210
+ };
211
+ const scheduled = async (_controller, env, ctx) => {
212
+ ctx.waitUntil(sweep(env));
213
+ };
214
+ return {
215
+ Runner,
216
+ fetch,
217
+ sweep,
218
+ scheduled
219
+ };
220
+ }
221
+ //#endregion
222
+ export { EnvValidationError, journeyWorker };
223
+
224
+ //# sourceMappingURL=worker.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.mjs","names":[],"sources":["../../src/cloudflare/worker.ts"],"sourcesContent":["import * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport { type JourneyConfig, compileConfig } from '../config';\nimport { type Contract, ContractValidationError, checkEvent } from '../contract';\nimport type { RegisteredAction } from '../engine/actions';\nimport type { JourneyObserver, MessageSender, MessageValidator } from '../engine/ports';\nimport { scaleSegmentWindows } from '../engine/time-scale';\nimport type { ScalarIR } from '../ir';\nimport { registryRenderer } from '../react-email';\nimport { reportingObserver } from '../reporting/observer';\nimport { handleTracking } from '../reporting/tracking';\nimport type { JourneyRuntimeOptions } from '../runtime';\nimport { type JourneyStore, SchemaMismatchError, type StoreLease } from '../store/index';\nimport { DEFAULT_SCHEMA } from '../store/tables';\nimport { TemplateValidationError, registryValidator } from '../templates';\nimport type { JourneyEnv, JourneyParams } from './bindings';\nimport { channelsFromConfig, runtimeFromConfig } from './channels';\nimport { d1Store } from './d1-env';\nimport {\n type SweepResult,\n deployBundle,\n handleRequest,\n ingestEvent,\n sweepSegments,\n} from './router';\nimport { JourneyRunner } from './runner';\n\n/** The fetch handler's execution context (the subset used). */\nexport interface ExecutionContextLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\nexport interface JourneyWorkerOptions<Env extends JourneyEnv> {\n /**\n * The project's `workflow.config.ts` (`defineConfig`): what the Worker\n * deploys (`GET /bundle`, `POST /bundle/deploy` serve `compileConfig`), the\n * template registries the senders, the validator and `/preview` render from,\n * the contract every `/events` and `/identify` is checked against, the email\n * envelope, actions, and the runtime / APNs / FCM settings. Secrets come\n * from the env under the names `JourneyEnv` documents.\n */\n config: JourneyConfig;\n /**\n * The data plane, opened per request and per workflow run. Return a\n * `StoreLease` when the client must be released afterwards (a Postgres\n * connection); defaults to D1 + KV from the env's bindings. `database` is\n * the config's (`schema` defaulting to `workflow`), for a Postgres store:\n *\n * store: (env, database) => postgresJsStore(env.HYPERDRIVE.connectionString, database)\n */\n store?: (env: Env, database: { schema: string }) => JourneyStore | StoreLease;\n /**\n * Reporting hooks: node arrivals, every copy sent (with the tracking it should carry — click\n * links and an open pixel for email, payload data for a push), and how each instance ended.\n * By default a store that keeps reporting (Postgres) records them itself, and the Worker\n * serves the email tracking routes (`/t/...`, on `WORKFLOW_ORIGIN`); override to report\n * elsewhere.\n */\n observer?: (env: Env, store: JourneyStore) => JourneyObserver;\n /** Message outlet override (tests, a custom gateway); by default the channels the config describes. */\n messages?: (env: Env, store: JourneyStore) => MessageSender;\n /** Per-instance runtime options override (a tester in fast-forward); by default `config.runtime` for `ENVIRONMENT`. */\n runtime?: (env: Env, params: JourneyParams) => JourneyRuntimeOptions;\n /**\n * The host's environment, as a zod schema over its vars and secrets (bindings pass through\n * untouched): checked the first time the Worker runs, so a missing or malformed variable fails\n * there — every problem listed — not at the send that needs it. Webhook templates read their\n * URL from the parsed values: `webhook: (env: Env) => env.WEBHOOK_DISCORD_ALERTS`.\n */\n env?: core.$ZodType<object>;\n /** 'enforce' (default) answers an event or patch the contract refuses with a 400; 'report' only logs it. */\n contractMode?: 'enforce' | 'report';\n}\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 */\n/** The Worker's environment failed its schema (`journeyWorker({ env })`). */\nexport class EnvValidationError extends Error {\n override readonly name = 'EnvValidationError';\n constructor(readonly issues: readonly string[]) {\n super(`the Worker's environment is invalid:\\n ${issues.join('\\n ')}`);\n }\n}\n\nexport function journeyWorker<Env extends JourneyEnv>(\n options: JourneyWorkerOptions<Env>\n): JourneyWorker<Env> {\n const { config } = options;\n // Parsed once per env object: the runtime hands the same one to every request of an isolate\n const checked = new WeakMap<object, Env>();\n const checkEnv = (env: Env): Env => {\n if (options.env === undefined) return env;\n const cached = checked.get(env);\n if (cached !== undefined) return cached;\n const result = z.safeParse(options.env, env);\n if (!result.success) {\n // Names and reasons only: the values are secrets\n throw new EnvValidationError(\n result.error.issues.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`)\n );\n }\n const merged: Env = { ...env, ...result.data };\n checked.set(env, merged);\n return merged;\n };\n const bundle = () => compileConfig(config);\n const emails = config.templates?.email;\n const database = { schema: config.database?.schema ?? DEFAULT_SCHEMA };\n // In fast-forward, segment windows scale with the journeys reading them (time-scale.ts)\n const lease = (env: Env): Required<StoreLease> => {\n const opened = options.store === undefined ? d1Store(env) : options.store(env, database);\n const noop = async () => {};\n const factor = runtimeFromConfig(config, env).timeScale;\n return 'store' in opened\n ? { store: scaleSegmentWindows(opened.store, factor), close: opened.close ?? noop }\n : { store: scaleSegmentWindows(opened, factor), close: noop };\n };\n\n class Runner extends JourneyRunner {\n private readonly leases = new WeakMap<JourneyStore, () => Promise<void>>();\n private current: JourneyStore | undefined;\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 } = lease(checkEnv(this.env as Env));\n this.leases.set(store, close);\n this.current = store;\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 contract(): Contract | undefined {\n return config.contract;\n }\n\n protected override createMessageValidator(): MessageValidator {\n return registryValidator(config.templates ?? {});\n }\n\n protected override createMessageSender(): MessageSender {\n if (this.current === undefined) return super.createMessageSender();\n if (options.messages !== undefined) {\n return options.messages(checkEnv(this.env as Env), this.current);\n }\n return channelsFromConfig(config, checkEnv(this.env as Env), { store: this.current });\n }\n\n protected override createObserver(store: JourneyStore): JourneyObserver | undefined {\n if (options.observer !== undefined) return options.observer(checkEnv(this.env as Env), store);\n return store.reporting === undefined\n ? undefined\n : reportingObserver(store.reporting, checkEnv(this.env as Env).WORKFLOW_ORIGIN);\n }\n\n protected override runtime(params: JourneyParams): JourneyRuntimeOptions {\n const env = checkEnv(this.env as Env);\n if (options.runtime !== undefined) return options.runtime(env, params);\n return runtimeFromConfig(config, env);\n }\n\n protected override actions(): readonly RegisteredAction[] {\n // An action ref is a registered action structurally: the same object carries name, code and hash\n return config.actions ?? [];\n }\n }\n\n const json = (body: unknown, status = 200): Response =>\n new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json' },\n });\n\n const authorized = (request: Request, env: Env): boolean =>\n env.API_TOKEN === undefined ||\n request.headers.get('authorization') === `Bearer ${env.API_TOKEN}`;\n\n const preview = emails === undefined ? undefined : registryRenderer(emails);\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 = checkEnv(rawEnv);\n } catch (error) {\n if (!(error instanceof EnvValidationError)) throw error;\n console.error(`[workflow] ${error.message}`);\n return json({ error: error.message }, 500);\n }\n const url = new URL(request.url);\n const { store, close } = lease(env);\n try {\n // Opened by recipients, so no bearer: the routes only record, and redirect to recorded URLs\n if (url.pathname.startsWith('/t/') && store.reporting !== undefined) {\n const openedEvent = config.webPush?.openedEvent;\n const tracked = await handleTracking(request, store.reporting, {\n unsubscribe: config.emails?.unsubscribe,\n product: config.title,\n quickMs: config.emails?.tracking?.quickClickMs,\n pushOpened:\n openedEvent === undefined\n ? undefined\n : async ({ messageId, userId, at }) => {\n const payload: Record<string, unknown> = { message_id: messageId };\n const contract = config.contract;\n const verdict =\n contract === undefined ? undefined : checkEvent(contract, openedEvent, payload);\n if (verdict !== undefined && !verdict.ok) {\n throw new ContractValidationError(openedEvent, verdict.issues);\n }\n // Keyed by the copy: a second tap of the same notification is the same event\n await ingestEvent(\n env,\n {\n id: `${openedEvent}:${messageId}`,\n userId,\n event: openedEvent,\n payload: verdict?.data ?? payload,\n ts: at,\n },\n store\n );\n },\n });\n if (tracked !== undefined) return tracked;\n }\n if (url.pathname === '/segments/sweep') {\n if (!authorized(request, env)) return json({ error: 'unauthorized' }, 401);\n if (request.method !== 'POST') return json({ error: 'method not allowed' }, 405);\n return json(await sweepSegments(env, store));\n }\n if (url.pathname === '/bundle') {\n if (!authorized(request, env)) return json({ error: 'unauthorized' }, 401);\n if (request.method !== 'GET') return json({ error: 'method not allowed' }, 405);\n return json(bundle());\n }\n if (url.pathname === '/bundle/deploy') {\n if (!authorized(request, env)) return json({ error: 'unauthorized' }, 401);\n if (request.method !== 'POST') return json({ error: 'method not allowed' }, 405);\n try {\n return json(await deployBundle(env, bundle(), store));\n } catch (error) {\n if (!(error instanceof SchemaMismatchError)) throw error;\n return json({ error: error.message, problems: error.problems }, 503);\n }\n }\n if (emails !== undefined && preview !== undefined && url.pathname.startsWith('/preview/')) {\n if (!authorized(request, env)) return json({ error: 'unauthorized' }, 401);\n if (request.method !== 'GET') return json({ error: 'method not allowed' }, 405);\n const key = url.pathname.slice('/preview/'.length);\n if (!Object.hasOwn(emails, key))\n return json({ error: `unknown email template: ${key}` }, 404);\n // `?props=<JSON object>`: JSON keeps numbers and booleans typed, which query strings cannot\n let props: unknown = {};\n try {\n props = JSON.parse(url.searchParams.get('props') ?? '{}');\n } catch {\n return json({ error: 'props must be a JSON object' }, 400);\n }\n if (typeof props !== 'object' || props === null || Array.isArray(props)) {\n return json({ error: 'props must be a JSON object' }, 400);\n }\n try {\n const { subject, html } = await preview(key, props as Record<string, ScalarIR>);\n return new Response(html, {\n headers: { 'content-type': 'text/html; charset=utf-8', 'x-subject': subject },\n });\n } catch (error) {\n // The query string is the props: a refusal is the caller's to fix, with the field paths\n if (error instanceof TemplateValidationError) {\n return json({ error: error.message, issues: error.issues }, 400);\n }\n throw error;\n }\n }\n return await handleRequest(request, env, store, {\n contract: config.contract,\n mode: options.contractMode,\n });\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 = async (rawEnv: Env) => {\n const env = checkEnv(rawEnv);\n const { store, close } = lease(env);\n try {\n return await sweepSegments(env, store);\n } finally {\n await close();\n }\n };\n\n const scheduled = async (_controller: unknown, env: Env, ctx: ExecutionContextLike) => {\n ctx.waitUntil(sweep(env));\n };\n\n return { Runner, fetch, sweep, scheduled };\n}\n"],"mappings":"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"}
File without changes
@@ -0,0 +1,44 @@
1
+ //#region src/cloudflare/workers.d.ts
2
+ /**
3
+ * Minimal ambient declaration for cloudflare:workers — only the surface
4
+ * JourneyRunner actually uses. @cloudflare/workers-types is avoided because its
5
+ * global types clash with react/DOM. Shapes follow the official API:
6
+ * https://developers.cloudflare.com/workflows/build/workers-api/
7
+ */
8
+ declare module 'cloudflare:workers' {
9
+ export interface WorkflowEvent<Params = unknown> {
10
+ payload: Readonly<Params>;
11
+ timestamp: Date;
12
+ instanceId: string;
13
+ }
14
+ export interface WorkflowStepEvent<Payload = unknown> {
15
+ payload: Payload;
16
+ timestamp: Date;
17
+ type: string;
18
+ }
19
+ export interface WorkflowStep {
20
+ do<T>(name: string, callback: () => Promise<T>): Promise<T>;
21
+ sleep(name: string, durationMs: number | string): Promise<void>;
22
+ sleepUntil(name: string, timestamp: Date | number): Promise<void>;
23
+ waitForEvent<Payload = unknown>(name: string, options: {
24
+ type: string;
25
+ timeout?: number | string;
26
+ }): Promise<WorkflowStepEvent<Payload>>;
27
+ }
28
+ export abstract class WorkflowEntrypoint<Env = unknown, Params = unknown> {
29
+ /** The runtime constructs it with the execution context and the bindings. */
30
+ constructor(ctx: unknown, env: Env);
31
+ protected readonly ctx: unknown;
32
+ protected readonly env: Env;
33
+ abstract run(event: WorkflowEvent<Params>, step: WorkflowStep): Promise<unknown>;
34
+ }
35
+ }
36
+ /** The one export of cloudflare:workflows the runner uses: an error that ends the step without retries. */
37
+ declare module 'cloudflare:workflows' {
38
+ export class NonRetryableError extends Error {
39
+ constructor(message: string, name?: string);
40
+ }
41
+ }
42
+ //#endregion
43
+ export {};
44
+ //# sourceMappingURL=workers.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers.d.cts","names":[],"sources":["../../src/cloudflare/workers.d.ts"],"mappings":";;;;;;;;mBAOmB,cAAc;IAC7B,SAAS,SAAS;IAClB,WAAW;IACX;;mBAGe,kBAAkB;IACjC,SAAS;IACT,WAAW;IACX;;mBAGe;IACf,GAAG,GAAG,cAAc,gBAAgB,QAAQ,KAAK,QAAQ;IACzD,MAAM,cAAc,8BAA8B;IAClD,WAAW,cAAc,WAAW,gBAAgB;IACpD,aAAa,mBACX,cACA;MAAW;MAAc;QACxB,QAAQ,kBAAkB;;wBAGT,mBAAmB,eAAe;;IAEtD,YAAY,cAAc,KAAK;uBACZ;uBACA,KAAK;aACf,IAAI,OAAO,cAAc,SAAS,MAAM,eAAe;;;;;eAMrD,0BAA0B;IACrC,YAAY,iBAAiB"}
@@ -0,0 +1,44 @@
1
+ //#region src/cloudflare/workers.d.ts
2
+ /**
3
+ * Minimal ambient declaration for cloudflare:workers — only the surface
4
+ * JourneyRunner actually uses. @cloudflare/workers-types is avoided because its
5
+ * global types clash with react/DOM. Shapes follow the official API:
6
+ * https://developers.cloudflare.com/workflows/build/workers-api/
7
+ */
8
+ declare module 'cloudflare:workers' {
9
+ export interface WorkflowEvent<Params = unknown> {
10
+ payload: Readonly<Params>;
11
+ timestamp: Date;
12
+ instanceId: string;
13
+ }
14
+ export interface WorkflowStepEvent<Payload = unknown> {
15
+ payload: Payload;
16
+ timestamp: Date;
17
+ type: string;
18
+ }
19
+ export interface WorkflowStep {
20
+ do<T>(name: string, callback: () => Promise<T>): Promise<T>;
21
+ sleep(name: string, durationMs: number | string): Promise<void>;
22
+ sleepUntil(name: string, timestamp: Date | number): Promise<void>;
23
+ waitForEvent<Payload = unknown>(name: string, options: {
24
+ type: string;
25
+ timeout?: number | string;
26
+ }): Promise<WorkflowStepEvent<Payload>>;
27
+ }
28
+ export abstract class WorkflowEntrypoint<Env = unknown, Params = unknown> {
29
+ /** The runtime constructs it with the execution context and the bindings. */
30
+ constructor(ctx: unknown, env: Env);
31
+ protected readonly ctx: unknown;
32
+ protected readonly env: Env;
33
+ abstract run(event: WorkflowEvent<Params>, step: WorkflowStep): Promise<unknown>;
34
+ }
35
+ }
36
+ /** The one export of cloudflare:workflows the runner uses: an error that ends the step without retries. */
37
+ declare module 'cloudflare:workflows' {
38
+ export class NonRetryableError extends Error {
39
+ constructor(message: string, name?: string);
40
+ }
41
+ }
42
+ //#endregion
43
+ export {};
44
+ //# sourceMappingURL=workers.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers.d.mts","names":[],"sources":["../../src/cloudflare/workers.d.ts"],"mappings":";;;;;;;;mBAOmB,cAAc;IAC7B,SAAS,SAAS;IAClB,WAAW;IACX;;mBAGe,kBAAkB;IACjC,SAAS;IACT,WAAW;IACX;;mBAGe;IACf,GAAG,GAAG,cAAc,gBAAgB,QAAQ,KAAK,QAAQ;IACzD,MAAM,cAAc,8BAA8B;IAClD,WAAW,cAAc,WAAW,gBAAgB;IACpD,aAAa,mBACX,cACA;MAAW;MAAc;QACxB,QAAQ,kBAAkB;;wBAGT,mBAAmB,eAAe;;IAEtD,YAAY,cAAc,KAAK;uBACZ;uBACA,KAAK;aACf,IAAI,OAAO,cAAc,SAAS,MAAM,eAAe;;;;;eAMrD,0BAA0B;IACrC,YAAY,iBAAiB"}
@@ -0,0 +1,151 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
3
+ const require_dsl_template = require("./dsl/template.cjs");
4
+ const require_dsl_bundle = require("./dsl/bundle.cjs");
5
+ require("./dsl/index.cjs");
6
+ let zod_mini = require("zod/mini");
7
+ zod_mini = require_runtime.__toESM(zod_mini, 1);
8
+ //#region src/config.ts
9
+ /** `Name <email>`, or the bare address. */
10
+ function formatEmailAddress(address) {
11
+ if (typeof address === "string") return address;
12
+ return address.name === void 0 ? address.email : `${address.name} <${address.email}>`;
13
+ }
14
+ function defineConfig(definition) {
15
+ return {
16
+ kind: "journey-config",
17
+ ...definition
18
+ };
19
+ }
20
+ function isConfig(value) {
21
+ return typeof value === "object" && value !== null && value.kind === "journey-config";
22
+ }
23
+ /** The APNs environment for `mode`: the configured one, else production only for production. */
24
+ function apnsEnvironment(config, mode) {
25
+ return config.environment?.[mode] ?? (mode === "production" ? "production" : "sandbox");
26
+ }
27
+ /** A message node the config's registries cannot serve, or whose literal props its template refuses. */
28
+ var ConfigCompileError = class extends Error {
29
+ name = "ConfigCompileError";
30
+ };
31
+ /**
32
+ * Compile the config: the bundle `compileBundle` would produce, with the
33
+ * template manifest generated from the registries (every registered template,
34
+ * not a hand-kept list), after checking every message node against them —
35
+ * an unregistered or cross-channel template reference fails with the workflow,
36
+ * node and channel named, and so does a literal prop the template's schema
37
+ * refuses (a missing required prop, an undeclared one, a wrong literal).
38
+ * User-property references cannot be parsed here (their values exist only at
39
+ * send time) — they are checked by name only, and parsed before sending.
40
+ */
41
+ function compileConfig(config) {
42
+ const registered = (channel) => Object.keys(registryOf(config, channel) ?? {});
43
+ const bundle = require_dsl_bundle.compileBundle({
44
+ workflows: config.workflows,
45
+ ...config.segments === void 0 ? {} : { segments: config.segments },
46
+ ...config.actions === void 0 ? {} : { actions: config.actions },
47
+ templates: [
48
+ ...registered("email").map((key) => require_dsl_template.template.email(key)),
49
+ ...registered("push").map((key) => require_dsl_template.template.push(key)),
50
+ ...registered("discord").map((key) => require_dsl_template.template.discord(key)),
51
+ ...registered("slack").map((key) => require_dsl_template.template.slack(key))
52
+ ]
53
+ });
54
+ for (const workflow of bundle.workflows) checkMessages(workflow, workflow.flow, config);
55
+ return bundle;
56
+ }
57
+ const TEMPLATE_CHANNELS = [
58
+ "email",
59
+ "push",
60
+ "discord",
61
+ "slack"
62
+ ];
63
+ /** The config's registry for a channel; undefined for a channel templates cannot be declared for. */
64
+ function registryOf(config, channel) {
65
+ switch (channel) {
66
+ case "email": return config.templates?.email;
67
+ case "push": return config.templates?.push;
68
+ case "discord": return config.templates?.discord;
69
+ case "slack": return config.templates?.slack;
70
+ default: return;
71
+ }
72
+ }
73
+ /** A field that may be left out: optional, or with a default the schema fills in. */
74
+ function canBeOmitted(schema) {
75
+ const type = schema._zod.def.type;
76
+ return type === "optional" || type === "default" || type === "prefault";
77
+ }
78
+ function checkMessages(workflow, nodes, config) {
79
+ for (const node of nodes) switch (node.type) {
80
+ case "message":
81
+ checkMessage(workflow, node, config);
82
+ break;
83
+ case "send_event":
84
+ checkEmit(workflow, node, config);
85
+ break;
86
+ case "branch":
87
+ for (const c of node.cases) checkMessages(workflow, c.flow, config);
88
+ if (node.otherwise) checkMessages(workflow, node.otherwise, config);
89
+ break;
90
+ case "cohort":
91
+ for (const arm of node.arms) checkMessages(workflow, arm.flow, config);
92
+ break;
93
+ case "wait_until": if (Array.isArray(node.onTimeout)) checkMessages(workflow, node.onTimeout, config);
94
+ }
95
+ }
96
+ function checkMessage(workflow, node, config) {
97
+ const where = `workflow '${workflow.id}' node ${node.id}`;
98
+ const registry = registryOf(config, node.channel);
99
+ if (registry === void 0 && !TEMPLATE_CHANNELS.includes(node.channel)) return;
100
+ if (registry === void 0 || !Object.hasOwn(registry, node.template)) {
101
+ const other = TEMPLATE_CHANNELS.find((channel) => channel !== node.channel && Object.hasOwn(registryOf(config, channel) ?? {}, node.template));
102
+ const hint = other === void 0 ? "" : ` (it is registered as a ${other} template)`;
103
+ throw new ConfigCompileError(`${where}: ${node.channel} template '${node.template}' is not registered${hint}`);
104
+ }
105
+ const shape = registry[node.template].props._zod.def.shape;
106
+ const problems = [];
107
+ for (const [field, value] of Object.entries(node.props)) {
108
+ if (!Object.hasOwn(shape, field)) {
109
+ problems.push(`'${field}' is not a prop of the template`);
110
+ continue;
111
+ }
112
+ if (typeof value === "object" && value !== null && "type" in value) {
113
+ const profile = config.contract?.schemas.profile._zod.def;
114
+ if (profile !== void 0 && value.path !== "userId" && !Object.hasOwn(profile.shape, value.path)) problems.push(`'${field}' reads u.${value.path}, which the contract's profile does not declare`);
115
+ continue;
116
+ }
117
+ if (!zod_mini.safeParse(shape[field], value).success) problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the schema`);
118
+ }
119
+ for (const [field, schema] of Object.entries(shape)) if (!canBeOmitted(schema) && !Object.hasOwn(node.props, field)) problems.push(`required prop '${field}' is missing`);
120
+ if (problems.length > 0) throw new ConfigCompileError(`${where}: ${node.channel} '${node.template}': ${problems.join("; ")}`);
121
+ }
122
+ /**
123
+ * An emitted event against the contract, as far as compile time can see: the
124
+ * event is declared, literal payload values parse against their field, and no
125
+ * required field is missing. Values read from the profile are checked when the
126
+ * event is emitted, by the same contract.
127
+ */
128
+ function checkEmit(workflow, node, config) {
129
+ const contract = config.contract;
130
+ if (contract === void 0) return;
131
+ const where = `workflow '${workflow.id}' node ${node.id}`;
132
+ if (!contract.isEvent(node.event)) throw new ConfigCompileError(`${where}: emits '${String(node.event)}', which is not in the contract`);
133
+ const shape = contract.schemas.events[node.event]._zod.def.shape;
134
+ const problems = [];
135
+ for (const [field, value] of Object.entries(node.payload)) {
136
+ if (typeof value === "object" && value !== null && "type" in value) continue;
137
+ if (!Object.hasOwn(shape, field)) continue;
138
+ if (!zod_mini.safeParse(shape[field], value).success) problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the contract`);
139
+ }
140
+ for (const [field, fieldSchema] of Object.entries(shape)) if (!canBeOmitted(fieldSchema) && !Object.hasOwn(node.payload, field)) problems.push(`required field '${field}' is missing`);
141
+ if (problems.length > 0) throw new ConfigCompileError(`${where}: event '${node.event}': ${problems.join("; ")}`);
142
+ }
143
+ //#endregion
144
+ exports.ConfigCompileError = ConfigCompileError;
145
+ exports.apnsEnvironment = apnsEnvironment;
146
+ exports.compileConfig = compileConfig;
147
+ exports.defineConfig = defineConfig;
148
+ exports.formatEmailAddress = formatEmailAddress;
149
+ exports.isConfig = isConfig;
150
+
151
+ //# sourceMappingURL=config.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.cjs","names":["compileBundle","template","z"],"sources":["../src/config.ts"],"sourcesContent":["import * as z from 'zod/mini';\nimport type * as core from 'zod/v4/core';\nimport type { Contract } from './contract';\nimport {\n type ActionRef,\n type SegmentRef,\n type TemplateRegistry,\n type WorkflowBuilder,\n compileBundle,\n template,\n} from './dsl';\nimport type { BundleIR, ChannelIR, NodeIR, WorkflowIR } from './ir';\nimport type { JourneyMode, JourneyRuntimeConfig } from './runtime';\nimport type { EmailTemplate, PropsSchema, PushTemplate } from './templates';\n\n/**\n * `workflow.config.ts` — the project's journey configuration, typed, flat and\n * committed with the code: what it deploys (workflows, segments, actions),\n * the template registries behind them, the contract every inbound event is\n * checked against, the email envelope, and how the engine runs per\n * environment (time scale, APNs, FCM). The compiler (`compileConfig`), the\n * Worker (`journeyWorker({ config })`) and the Workflow Studio all read this\n * one object, so everything is registered exactly once:\n *\n * export default defineConfig({\n * title: 'Workflow Studio · Acme',\n * contract,\n * workflows: [onboarding, notificationTest],\n * templates: registry, // { email, push }; `t = templates(registry)` for the workflows\n * emails: { from: { email: 'noreply@acme.test', name: 'Acme' } },\n * runtime: { development: { timeScale: 600, logMessages: true } },\n * apns: { topic: 'io.acme.app', keyId: 'ABC123DEFG', teamId: 'TEAM123456' },\n * });\n *\n * Secrets are the one thing that stay out: a `.p8`, a service account, the\n * ingest bearer. The host reads them from its environment under fixed names\n * (see `JourneyEnv`), so the config says *which* key without carrying it. The\n * config is a source object (schemas, components, functions); only its\n * compiled bundle and preview-safe template data may cross to a browser.\n */\n\n/** An address as the transports take it: bare, or with a display name. */\nexport type EmailAddress = string | { email: string; name?: string };\n\n/** `Name <email>`, or the bare address. */\nexport function formatEmailAddress(address: EmailAddress): string {\n if (typeof address === 'string') return address;\n return address.name === undefined ? address.email : `${address.name} <${address.email}>`;\n}\n\n/** The envelope every journey email goes out with (and the studio shows). */\nexport interface EmailConfig {\n from: EmailAddress;\n replyTo?: EmailAddress;\n /**\n * The unsubscribe link the templates' footer carries. Tracked like every link, but following\n * it shows a confirmation page, and only the confirmation (a POST no link scanner sends) is\n * recorded as the message's unsubscribe.\n */\n unsubscribe?: string;\n /** How the Worker tells a person's opens and clicks from a machine's (see the reporting classifier). */\n tracking?: {\n /**\n * An open or click this many milliseconds after delivery is a weak machine signal — machine\n * only together with another (a second link within seconds); 1000 by default. A person\n * tapping a mail notification can click within a few seconds, so keep it short.\n */\n quickClickMs?: number;\n };\n}\n\n/** Token-based APNs, the `.p8` auth key; the key itself is `APNS_PRIVATE_KEY_BASE64` in the environment. */\nexport interface ApnsConfig {\n /** The app's bundle id (`apns-topic`). */\n topic: string;\n /** The key's 10-character id. */\n keyId: string;\n /** The Apple Developer team id. */\n teamId: string;\n /**\n * Which APNs host each mode's device tokens belong to: `sandbox` for builds\n * signed with `aps-environment: development` (Xcode, the simulator),\n * `production` for App Store and TestFlight builds. Unlisted modes default\n * to `production` for the production mode and `sandbox` for every other.\n */\n environment?: Partial<Record<JourneyMode, 'sandbox' | 'production'>>;\n}\n\n/**\n * Standard Web Push for browsers (VAPID); the key pair is `VAPID_PUBLIC_KEY` /\n * `VAPID_PRIVATE_KEY` in the environment, and the site subscribes with the same public key.\n */\nexport interface WebPushConfig {\n /** Who push services may contact about this sender: `mailto:` or an `https:` URL. */\n subject: string;\n /**\n * The contract event a notification tap the browser reports (`opened_url`) is also recorded\n * as, with `{ message_id }`: the app reports its own taps as an event, and a service worker has\n * no session to do so — without this, browser opens reach the reports but not the conditions\n * (a `performed(e.push_opened)` segment). One event per copy, however often it is tapped.\n */\n openedEvent?: string;\n}\n\n/** FCM HTTP v1 for Android; the service account is `GOOGLE_APPLICATION_CREDENTIALS_BASE64` in the environment. */\nexport interface FcmConfig {\n /** Android notification channel the pushes land in (`android.notification.channel_id`). */\n androidChannelId?: string;\n}\n\n/**\n * The engine's tables live in a Postgres schema of their own, so their short names (`event`,\n * `profile`, `message`, …) never meet the app's. `workflow-ui generate` writes them as one drizzle\n * file on this schema; the host migrates it with its own tooling.\n */\nexport interface DatabaseConfig {\n /** The Postgres schema; `workflow` by default. */\n schema?: string;\n}\n\nexport interface JourneyConfigDefinition<\n E extends Record<string, EmailTemplate> = Record<string, EmailTemplate>,\n P extends Record<string, PushTemplate> = Record<string, PushTemplate>,\n> {\n /** Studio title (the browser tab names the project). */\n title?: string;\n /** The shared `defineContract(...)` the Worker validates ingest against. */\n contract?: Contract;\n workflows: readonly WorkflowBuilder[];\n segments?: readonly SegmentRef[];\n actions?: readonly ActionRef<object>[];\n templates?: TemplateRegistry<E, P>;\n emails?: EmailConfig;\n /** How the engine behaves per mode (`development`, `production`, …): time scale, message logging. */\n runtime?: JourneyRuntimeConfig;\n apns?: ApnsConfig;\n fcm?: FcmConfig;\n webPush?: WebPushConfig;\n /** Where a Postgres store keeps the engine's tables (the D1 store ignores it). */\n database?: DatabaseConfig;\n}\n\nexport interface JourneyConfig<\n E extends Record<string, EmailTemplate> = Record<string, EmailTemplate>,\n P extends Record<string, PushTemplate> = Record<string, PushTemplate>,\n> extends JourneyConfigDefinition<E, P> {\n readonly kind: 'journey-config';\n}\n\nexport function defineConfig<\n E extends Record<string, EmailTemplate> = Record<never, never>,\n P extends Record<string, PushTemplate> = Record<never, never>,\n>(definition: JourneyConfigDefinition<E, P>): JourneyConfig<E, P> {\n return { kind: 'journey-config', ...definition };\n}\n\nexport function isConfig(value: unknown): value is JourneyConfig {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { kind?: unknown }).kind === 'journey-config'\n );\n}\n\n/** The APNs environment for `mode`: the configured one, else production only for production. */\nexport function apnsEnvironment(config: ApnsConfig, mode: JourneyMode): 'sandbox' | 'production' {\n return config.environment?.[mode] ?? (mode === 'production' ? 'production' : 'sandbox');\n}\n\n/** A message node the config's registries cannot serve, or whose literal props its template refuses. */\nexport class ConfigCompileError extends Error {\n override readonly name = 'ConfigCompileError';\n}\n\n/**\n * Compile the config: the bundle `compileBundle` would produce, with the\n * template manifest generated from the registries (every registered template,\n * not a hand-kept list), after checking every message node against them —\n * an unregistered or cross-channel template reference fails with the workflow,\n * node and channel named, and so does a literal prop the template's schema\n * refuses (a missing required prop, an undeclared one, a wrong literal).\n * User-property references cannot be parsed here (their values exist only at\n * send time) — they are checked by name only, and parsed before sending.\n */\nexport function compileConfig(config: JourneyConfig): BundleIR {\n const registered = (channel: ChannelIR) => Object.keys(registryOf(config, channel) ?? {});\n const bundle = compileBundle({\n workflows: config.workflows,\n ...(config.segments === undefined ? {} : { segments: config.segments }),\n ...(config.actions === undefined ? {} : { actions: config.actions }),\n templates: [\n ...registered('email').map((key) => template.email(key)),\n ...registered('push').map((key) => template.push(key)),\n ...registered('discord').map((key) => template.discord(key)),\n ...registered('slack').map((key) => template.slack(key)),\n ],\n });\n for (const workflow of bundle.workflows) checkMessages(workflow, workflow.flow, config);\n return bundle;\n}\n\nconst TEMPLATE_CHANNELS: readonly ChannelIR[] = ['email', 'push', 'discord', 'slack'];\n\n/** The config's registry for a channel; undefined for a channel templates cannot be declared for. */\nfunction registryOf(\n config: JourneyConfig,\n channel: ChannelIR\n): Readonly<Record<string, { props: PropsSchema }>> | undefined {\n switch (channel) {\n case 'email':\n return config.templates?.email;\n case 'push':\n return config.templates?.push;\n case 'discord':\n return config.templates?.discord;\n case 'slack':\n return config.templates?.slack;\n default:\n return undefined;\n }\n}\n\n/** A field that may be left out: optional, or with a default the schema fills in. */\nfunction canBeOmitted(schema: core.$ZodType): boolean {\n const type = schema._zod.def.type;\n return type === 'optional' || type === 'default' || type === 'prefault';\n}\n\nfunction checkMessages(\n workflow: WorkflowIR,\n nodes: readonly NodeIR[],\n config: JourneyConfig\n): void {\n for (const node of nodes) {\n switch (node.type) {\n case 'message':\n checkMessage(workflow, node, config);\n break;\n case 'send_event':\n checkEmit(workflow, node, config);\n break;\n case 'branch':\n for (const c of node.cases) checkMessages(workflow, c.flow, config);\n if (node.otherwise) checkMessages(workflow, node.otherwise, config);\n break;\n case 'cohort':\n for (const arm of node.arms) checkMessages(workflow, arm.flow, config);\n break;\n case 'wait_until':\n if (Array.isArray(node.onTimeout)) checkMessages(workflow, node.onTimeout, config);\n break;\n default:\n break;\n }\n }\n}\n\nfunction checkMessage(\n workflow: WorkflowIR,\n node: Extract<NodeIR, { type: 'message' }>,\n config: JourneyConfig\n): void {\n const where = `workflow '${workflow.id}' node ${node.id}`;\n const registry = registryOf(config, node.channel);\n // Channels without a registry (sms, …) are rendered by app-supplied senders\n if (registry === undefined && !TEMPLATE_CHANNELS.includes(node.channel)) return;\n if (registry === undefined || !Object.hasOwn(registry, node.template)) {\n const other = TEMPLATE_CHANNELS.find(\n (channel) =>\n channel !== node.channel && Object.hasOwn(registryOf(config, channel) ?? {}, node.template)\n );\n const hint = other === undefined ? '' : ` (it is registered as a ${other} template)`;\n throw new ConfigCompileError(\n `${where}: ${node.channel} template '${node.template}' is not registered${hint}`\n );\n }\n const entry = registry[node.template];\n const shape = (entry.props._zod.def as core.$ZodObjectDef).shape;\n const problems: string[] = [];\n for (const [field, value] of Object.entries(node.props)) {\n if (!Object.hasOwn(shape, field)) {\n problems.push(`'${field}' is not a prop of the template`);\n continue;\n }\n if (typeof value === 'object' && value !== null && 'type' in value) {\n // A user ref's value exists only at send time (parsed then); here its name can be checked\n const profile = config.contract?.schemas.profile._zod.def;\n if (\n profile !== undefined &&\n value.path !== 'userId' &&\n !Object.hasOwn(profile.shape, value.path)\n ) {\n problems.push(\n `'${field}' reads u.${value.path}, which the contract's profile does not declare`\n );\n }\n continue;\n }\n const result = z.safeParse(shape[field], value);\n if (!result.success)\n problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the schema`);\n }\n for (const [field, schema] of Object.entries(shape)) {\n const optional = canBeOmitted(schema);\n if (!optional && !Object.hasOwn(node.props, field))\n problems.push(`required prop '${field}' is missing`);\n }\n if (problems.length > 0) {\n throw new ConfigCompileError(\n `${where}: ${node.channel} '${node.template}': ${problems.join('; ')}`\n );\n }\n}\n\n/**\n * An emitted event against the contract, as far as compile time can see: the\n * event is declared, literal payload values parse against their field, and no\n * required field is missing. Values read from the profile are checked when the\n * event is emitted, by the same contract.\n */\nfunction checkEmit(\n workflow: WorkflowIR,\n node: Extract<NodeIR, { type: 'send_event' }>,\n config: JourneyConfig\n): void {\n const contract = config.contract;\n if (contract === undefined) return;\n const where = `workflow '${workflow.id}' node ${node.id}`;\n if (!contract.isEvent(node.event)) {\n throw new ConfigCompileError(\n `${where}: emits '${String(node.event)}', which is not in the contract`\n );\n }\n const schema = contract.schemas.events[node.event];\n const shape = (schema._zod.def as core.$ZodObjectDef).shape;\n const problems: string[] = [];\n for (const [field, value] of Object.entries(node.payload)) {\n if (typeof value === 'object' && value !== null && 'type' in value) continue; // read at emit time\n if (!Object.hasOwn(shape, field)) continue; // a loose payload may carry extra fields\n const result = z.safeParse(shape[field], value);\n if (!result.success)\n problems.push(`'${field}' = ${JSON.stringify(value)} is refused by the contract`);\n }\n for (const [field, fieldSchema] of Object.entries(shape)) {\n const optional = canBeOmitted(fieldSchema);\n if (!optional && !Object.hasOwn(node.payload, field))\n problems.push(`required field '${field}' is missing`);\n }\n if (problems.length > 0) {\n throw new ConfigCompileError(`${where}: event '${node.event}': ${problems.join('; ')}`);\n }\n}\n"],"mappings":"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"}