@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 @@
1
+ {"version":3,"file":"time-scale.cjs","names":[],"sources":["../../src/engine/time-scale.ts"],"sourcesContent":["/**\n * Debug time scale: divide every duration in a workflow's IR by a factor so a\n * journey written in days plays out in seconds — with a factor of 3600, a\n * `delay('2 days')` sleeps 48 s, a `waitUntil(..., { timeout: '3 days' })`\n * gives up after 72 s, and a goal `within: '30 days'` closes after 12 min.\n *\n * Everything that is a Duration scales together (delays, random delays, wait\n * timeouts, goal and `performed(... within)` windows inside the workflow), so\n * the journey keeps its shape, just faster. What does not scale: wall-clock\n * `timeWindow`s (they are times of day, not durations). Segment definitions\n * live in the store rather than the workflow body; `scaleSegmentWindows`\n * scales them as they are read, so a segment's `within` keeps pace too.\n *\n * Durations are recognised structurally — an object whose only keys are\n * `value` (the source text) and `ms` — which is the DurationIR shape and\n * nothing else in the IR. The text is kept so the studio still shows what was\n * written.\n */\nexport function scaleDurations<T>(ir: T, factor: number): T {\n if (!(factor > 0) || factor === 1) return ir;\n return walk(ir, factor) as T;\n}\n\nfunction walk(value: unknown, factor: number): unknown {\n if (Array.isArray(value)) return value.map((item) => walk(item, factor));\n if (value === null || typeof value !== 'object') return value;\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record);\n if (keys.length === 2 && typeof record.value === 'string' && typeof record.ms === 'number') {\n return { value: record.value, ms: Math.max(0, Math.round(record.ms / factor)) };\n }\n return Object.fromEntries(keys.map((key) => [key, walk(record[key], factor)]));\n}\n\n/**\n * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads\n * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)\n * shrink with the journeys that reference it, and with the segment triggers it routes. Every\n * other method is the store's own.\n */\nexport function scaleSegmentWindows<\n S extends { getSegmentCondition(name: string): Promise<unknown> },\n>(store: S, factor: number): S {\n if (!(factor > 0) || factor === 1) return store;\n return new Proxy(store, {\n get(target, property) {\n if (property === 'getSegmentCondition') {\n return async (name: string) =>\n scaleDurations(await target.getSegmentCondition(name), factor);\n }\n const value: unknown = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkBA,SAAgB,eAAkB,IAAO,QAAmB;CAC1D,IAAI,EAAE,SAAS,MAAM,WAAW,GAAG,OAAO;CAC1C,OAAO,KAAK,IAAI,MAAM;AACxB;AAEA,SAAS,KAAK,OAAgB,QAAyB;CACrD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,SAAS,KAAK,MAAM,MAAM,CAAC;CACvE,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,MAAM,SAAS;CACf,MAAM,OAAO,OAAO,KAAK,MAAM;CAC/B,IAAI,KAAK,WAAW,KAAK,OAAO,OAAO,UAAU,YAAY,OAAO,OAAO,OAAO,UAChF,OAAO;EAAE,OAAO,OAAO;EAAO,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,KAAK,MAAM,CAAC;CAAE;CAEhF,OAAO,OAAO,YAAY,KAAK,KAAK,QAAQ,CAAC,KAAK,KAAK,OAAO,MAAM,MAAM,CAAC,CAAC,CAAC;AAC/E;;;;;;;AAQA,SAAgB,oBAEd,OAAU,QAAmB;CAC7B,IAAI,EAAE,SAAS,MAAM,WAAW,GAAG,OAAO;CAC1C,OAAO,IAAI,MAAM,OAAO,EACtB,IAAI,QAAQ,UAAU;EACpB,IAAI,aAAa,uBACf,OAAO,OAAO,SACZ,eAAe,MAAM,OAAO,oBAAoB,IAAI,GAAG,MAAM;EAEjE,MAAM,QAAiB,QAAQ,IAAI,QAAQ,UAAU,MAAM;EAC3D,OAAO,OAAO,UAAU,aAAa,MAAM,KAAK,MAAM,IAAI;CAC5D,EACF,CAAC;AACH"}
@@ -0,0 +1,31 @@
1
+ //#region src/engine/time-scale.d.ts
2
+ /**
3
+ * Debug time scale: divide every duration in a workflow's IR by a factor so a
4
+ * journey written in days plays out in seconds — with a factor of 3600, a
5
+ * `delay('2 days')` sleeps 48 s, a `waitUntil(..., { timeout: '3 days' })`
6
+ * gives up after 72 s, and a goal `within: '30 days'` closes after 12 min.
7
+ *
8
+ * Everything that is a Duration scales together (delays, random delays, wait
9
+ * timeouts, goal and `performed(... within)` windows inside the workflow), so
10
+ * the journey keeps its shape, just faster. What does not scale: wall-clock
11
+ * `timeWindow`s (they are times of day, not durations). Segment definitions
12
+ * live in the store rather than the workflow body; `scaleSegmentWindows`
13
+ * scales them as they are read, so a segment's `within` keeps pace too.
14
+ *
15
+ * Durations are recognised structurally — an object whose only keys are
16
+ * `value` (the source text) and `ms` — which is the DurationIR shape and
17
+ * nothing else in the IR. The text is kept so the studio still shows what was
18
+ * written.
19
+ */
20
+ export declare function scaleDurations<T>(ir: T, factor: number): T;
21
+ /**
22
+ * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
23
+ * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)
24
+ * shrink with the journeys that reference it, and with the segment triggers it routes. Every
25
+ * other method is the store's own.
26
+ */
27
+ export declare function scaleSegmentWindows<S extends {
28
+ getSegmentCondition(name: string): Promise<unknown>;
29
+ }>(store: S, factor: number): S;
30
+ //#endregion
31
+ //# sourceMappingURL=time-scale.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-scale.d.cts","names":[],"sources":["../../src/engine/time-scale.ts"],"mappings":";;;;;;;;;;;;;;;;;;;wBAkBgB,eAAe,GAAG,IAAI,GAAG,iBAAiB;;;;;;;wBAsB1C,oBACd;EAAY,oBAAoB,eAAe;GAC/C,OAAO,GAAG,iBAAiB"}
@@ -0,0 +1,31 @@
1
+ //#region src/engine/time-scale.d.ts
2
+ /**
3
+ * Debug time scale: divide every duration in a workflow's IR by a factor so a
4
+ * journey written in days plays out in seconds — with a factor of 3600, a
5
+ * `delay('2 days')` sleeps 48 s, a `waitUntil(..., { timeout: '3 days' })`
6
+ * gives up after 72 s, and a goal `within: '30 days'` closes after 12 min.
7
+ *
8
+ * Everything that is a Duration scales together (delays, random delays, wait
9
+ * timeouts, goal and `performed(... within)` windows inside the workflow), so
10
+ * the journey keeps its shape, just faster. What does not scale: wall-clock
11
+ * `timeWindow`s (they are times of day, not durations). Segment definitions
12
+ * live in the store rather than the workflow body; `scaleSegmentWindows`
13
+ * scales them as they are read, so a segment's `within` keeps pace too.
14
+ *
15
+ * Durations are recognised structurally — an object whose only keys are
16
+ * `value` (the source text) and `ms` — which is the DurationIR shape and
17
+ * nothing else in the IR. The text is kept so the studio still shows what was
18
+ * written.
19
+ */
20
+ export declare function scaleDurations<T>(ir: T, factor: number): T;
21
+ /**
22
+ * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
23
+ * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)
24
+ * shrink with the journeys that reference it, and with the segment triggers it routes. Every
25
+ * other method is the store's own.
26
+ */
27
+ export declare function scaleSegmentWindows<S extends {
28
+ getSegmentCondition(name: string): Promise<unknown>;
29
+ }>(store: S, factor: number): S;
30
+ //#endregion
31
+ //# sourceMappingURL=time-scale.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-scale.d.mts","names":[],"sources":["../../src/engine/time-scale.ts"],"mappings":";;;;;;;;;;;;;;;;;;;wBAkBgB,eAAe,GAAG,IAAI,GAAG,iBAAiB;;;;;;;wBAsB1C,oBACd;EAAY,oBAAoB,eAAe;GAC/C,OAAO,GAAG,iBAAiB"}
@@ -0,0 +1,52 @@
1
+ //#region src/engine/time-scale.ts
2
+ /**
3
+ * Debug time scale: divide every duration in a workflow's IR by a factor so a
4
+ * journey written in days plays out in seconds — with a factor of 3600, a
5
+ * `delay('2 days')` sleeps 48 s, a `waitUntil(..., { timeout: '3 days' })`
6
+ * gives up after 72 s, and a goal `within: '30 days'` closes after 12 min.
7
+ *
8
+ * Everything that is a Duration scales together (delays, random delays, wait
9
+ * timeouts, goal and `performed(... within)` windows inside the workflow), so
10
+ * the journey keeps its shape, just faster. What does not scale: wall-clock
11
+ * `timeWindow`s (they are times of day, not durations). Segment definitions
12
+ * live in the store rather than the workflow body; `scaleSegmentWindows`
13
+ * scales them as they are read, so a segment's `within` keeps pace too.
14
+ *
15
+ * Durations are recognised structurally — an object whose only keys are
16
+ * `value` (the source text) and `ms` — which is the DurationIR shape and
17
+ * nothing else in the IR. The text is kept so the studio still shows what was
18
+ * written.
19
+ */
20
+ function scaleDurations(ir, factor) {
21
+ if (!(factor > 0) || factor === 1) return ir;
22
+ return walk(ir, factor);
23
+ }
24
+ function walk(value, factor) {
25
+ if (Array.isArray(value)) return value.map((item) => walk(item, factor));
26
+ if (value === null || typeof value !== "object") return value;
27
+ const record = value;
28
+ const keys = Object.keys(record);
29
+ if (keys.length === 2 && typeof record.value === "string" && typeof record.ms === "number") return {
30
+ value: record.value,
31
+ ms: Math.max(0, Math.round(record.ms / factor))
32
+ };
33
+ return Object.fromEntries(keys.map((key) => [key, walk(record[key], factor)]));
34
+ }
35
+ /**
36
+ * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
37
+ * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)
38
+ * shrink with the journeys that reference it, and with the segment triggers it routes. Every
39
+ * other method is the store's own.
40
+ */
41
+ function scaleSegmentWindows(store, factor) {
42
+ if (!(factor > 0) || factor === 1) return store;
43
+ return new Proxy(store, { get(target, property) {
44
+ if (property === "getSegmentCondition") return async (name) => scaleDurations(await target.getSegmentCondition(name), factor);
45
+ const value = Reflect.get(target, property, target);
46
+ return typeof value === "function" ? value.bind(target) : value;
47
+ } });
48
+ }
49
+ //#endregion
50
+ export { scaleDurations, scaleSegmentWindows };
51
+
52
+ //# sourceMappingURL=time-scale.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-scale.mjs","names":[],"sources":["../../src/engine/time-scale.ts"],"sourcesContent":["/**\n * Debug time scale: divide every duration in a workflow's IR by a factor so a\n * journey written in days plays out in seconds — with a factor of 3600, a\n * `delay('2 days')` sleeps 48 s, a `waitUntil(..., { timeout: '3 days' })`\n * gives up after 72 s, and a goal `within: '30 days'` closes after 12 min.\n *\n * Everything that is a Duration scales together (delays, random delays, wait\n * timeouts, goal and `performed(... within)` windows inside the workflow), so\n * the journey keeps its shape, just faster. What does not scale: wall-clock\n * `timeWindow`s (they are times of day, not durations). Segment definitions\n * live in the store rather than the workflow body; `scaleSegmentWindows`\n * scales them as they are read, so a segment's `within` keeps pace too.\n *\n * Durations are recognised structurally — an object whose only keys are\n * `value` (the source text) and `ms` — which is the DurationIR shape and\n * nothing else in the IR. The text is kept so the studio still shows what was\n * written.\n */\nexport function scaleDurations<T>(ir: T, factor: number): T {\n if (!(factor > 0) || factor === 1) return ir;\n return walk(ir, factor) as T;\n}\n\nfunction walk(value: unknown, factor: number): unknown {\n if (Array.isArray(value)) return value.map((item) => walk(item, factor));\n if (value === null || typeof value !== 'object') return value;\n const record = value as Record<string, unknown>;\n const keys = Object.keys(record);\n if (keys.length === 2 && typeof record.value === 'string' && typeof record.ms === 'number') {\n return { value: record.value, ms: Math.max(0, Math.round(record.ms / factor)) };\n }\n return Object.fromEntries(keys.map((key) => [key, walk(record[key], factor)]));\n}\n\n/**\n * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads\n * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)\n * shrink with the journeys that reference it, and with the segment triggers it routes. Every\n * other method is the store's own.\n */\nexport function scaleSegmentWindows<\n S extends { getSegmentCondition(name: string): Promise<unknown> },\n>(store: S, factor: number): S {\n if (!(factor > 0) || factor === 1) return store;\n return new Proxy(store, {\n get(target, property) {\n if (property === 'getSegmentCondition') {\n return async (name: string) =>\n scaleDurations(await target.getSegmentCondition(name), factor);\n }\n const value: unknown = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkBA,SAAgB,eAAkB,IAAO,QAAmB;CAC1D,IAAI,EAAE,SAAS,MAAM,WAAW,GAAG,OAAO;CAC1C,OAAO,KAAK,IAAI,MAAM;AACxB;AAEA,SAAS,KAAK,OAAgB,QAAyB;CACrD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,SAAS,KAAK,MAAM,MAAM,CAAC;CACvE,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,MAAM,SAAS;CACf,MAAM,OAAO,OAAO,KAAK,MAAM;CAC/B,IAAI,KAAK,WAAW,KAAK,OAAO,OAAO,UAAU,YAAY,OAAO,OAAO,OAAO,UAChF,OAAO;EAAE,OAAO,OAAO;EAAO,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,KAAK,MAAM,CAAC;CAAE;CAEhF,OAAO,OAAO,YAAY,KAAK,KAAK,QAAQ,CAAC,KAAK,KAAK,OAAO,MAAM,MAAM,CAAC,CAAC,CAAC;AAC/E;;;;;;;AAQA,SAAgB,oBAEd,OAAU,QAAmB;CAC7B,IAAI,EAAE,SAAS,MAAM,WAAW,GAAG,OAAO;CAC1C,OAAO,IAAI,MAAM,OAAO,EACtB,IAAI,QAAQ,UAAU;EACpB,IAAI,aAAa,uBACf,OAAO,OAAO,SACZ,eAAe,MAAM,OAAO,oBAAoB,IAAI,GAAG,MAAM;EAEjE,MAAM,QAAiB,QAAQ,IAAI,QAAQ,UAAU,MAAM;EAC3D,OAAO,OAAO,UAAU,aAAa,MAAM,KAAK,MAAM,IAAI;CAC5D,EACF,CAAC;AACH"}
@@ -0,0 +1,149 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/engine/time-window.ts
3
+ /**
4
+ * Time-window scheduling — when does the next send window open?
5
+ *
6
+ * Pure functions of (nowMs, days, between, tz): the interpreter calls them
7
+ * inside a durable step, so the answer is checkpointed and replay-safe.
8
+ * Timezone math goes through Intl.DateTimeFormat (available in Node, workerd
9
+ * and browsers alike) — no tz database dependency. An unknown timezone falls
10
+ * back to UTC rather than failing the journey.
11
+ */
12
+ const DAY_MS = 864e5;
13
+ /** Indexed by Date#getUTCDay, matching WeekdayIR's lowercase three-letter form. */
14
+ const WEEKDAYS = [
15
+ "sun",
16
+ "mon",
17
+ "tue",
18
+ "wed",
19
+ "thu",
20
+ "fri",
21
+ "sat"
22
+ ];
23
+ const formatters = /* @__PURE__ */ new Map();
24
+ /** Cached per-timezone formatter; throws on an invalid timezone id. */
25
+ function formatterFor(tz) {
26
+ let dtf = formatters.get(tz);
27
+ if (dtf === void 0) {
28
+ dtf = new Intl.DateTimeFormat("en-US", {
29
+ timeZone: tz,
30
+ hourCycle: "h23",
31
+ year: "numeric",
32
+ month: "numeric",
33
+ day: "numeric",
34
+ hour: "numeric",
35
+ minute: "numeric"
36
+ });
37
+ formatters.set(tz, dtf);
38
+ }
39
+ return dtf;
40
+ }
41
+ /** Validate a timezone id, falling back to UTC — a bad profile value must not wedge the journey. */
42
+ function resolveTimeZone(tz) {
43
+ try {
44
+ formatterFor(tz);
45
+ return tz;
46
+ } catch {
47
+ return "UTC";
48
+ }
49
+ }
50
+ function localParts(ms, tz) {
51
+ const parts = {};
52
+ for (const part of formatterFor(tz).formatToParts(new Date(ms))) parts[part.type] = part.value;
53
+ return {
54
+ year: Number(parts.year),
55
+ month: Number(parts.month),
56
+ day: Number(parts.day),
57
+ hour: Number(parts.hour),
58
+ minute: Number(parts.minute)
59
+ };
60
+ }
61
+ /** The zone's UTC offset at an instant, as (wall clock read as if it were UTC) − instant. */
62
+ function offsetMsAt(ts, tz) {
63
+ const p = localParts(ts, tz);
64
+ return Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute) - ts;
65
+ }
66
+ /**
67
+ * The first instant at or after `before` whose offset differs from the one
68
+ * there — i.e. the DST transition separating the two bounds. Bisects on the
69
+ * minute grid, the finest granularity any tz rule uses, so the loop lands on
70
+ * the transition itself rather than merely near it.
71
+ */
72
+ function transitionBetween(before, after, tz) {
73
+ const startOffset = offsetMsAt(before, tz);
74
+ let lo = before;
75
+ let hi = after;
76
+ while (hi - lo > 6e4) {
77
+ const mid = lo + Math.floor((hi - lo) / 12e4) * 6e4;
78
+ if (offsetMsAt(mid, tz) === startOffset) lo = mid;
79
+ else hi = mid;
80
+ }
81
+ return hi;
82
+ }
83
+ /**
84
+ * The UTC instant at which the wall clock in `tz` reads y-m-d hh:mm.
85
+ *
86
+ * Guess the UTC value from the offset at the guess, read it back through the
87
+ * timezone and correct by the difference; a second pass settles every wall
88
+ * time that exists, and picks the earlier of the two when a fall-back repeats
89
+ * an hour.
90
+ *
91
+ * A wall time inside a spring-forward gap exists on no clock, so the two
92
+ * corrections straddle the transition and neither reads back as asked. The
93
+ * convention here is the usual one: move forward to the first instant that
94
+ * does exist, i.e. the transition itself — 02:30 on a US spring-forward day
95
+ * resolves to 03:00. Without that branch the correction bottoms out on the low
96
+ * side and the window opens an hour *before* its configured start.
97
+ */
98
+ function zonedInstant(year, month, day, hour, minute, tz) {
99
+ const target = Date.UTC(year, month - 1, day, hour, minute);
100
+ const first = target - offsetMsAt(target, tz);
101
+ if (offsetMsAt(first, tz) === target - first) return first;
102
+ const second = target - offsetMsAt(first, tz);
103
+ if (offsetMsAt(second, tz) === target - second) return second;
104
+ return transitionBetween(Math.min(first, second), Math.max(first, second), tz);
105
+ }
106
+ const TIME = /^(\d{1,2}):(\d{2})$/;
107
+ function parseTime(value) {
108
+ const match = TIME.exec(value);
109
+ if (!match) return null;
110
+ const hh = Number(match[1]);
111
+ const mm = Number(match[2]);
112
+ if (hh > 23 || mm > 59) return null;
113
+ return {
114
+ hh,
115
+ mm
116
+ };
117
+ }
118
+ /**
119
+ * The instant the next [start, end) window opens in `tz`, or null when nowMs
120
+ * is already inside a window. Scans nine calendar days so every weekday is
121
+ * reachable. Malformed times (the DSL validates its own input, but IR may come
122
+ * from elsewhere) disable the window — better to send now than to hold a user
123
+ * forever on a config typo.
124
+ */
125
+ function nextWindowStart(nowMs, days, between, tz) {
126
+ const start = parseTime(between[0]);
127
+ const end = parseTime(between[1]);
128
+ if (start === null || end === null) return null;
129
+ const zone = resolveTimeZone(tz);
130
+ const today = localParts(nowMs, zone);
131
+ const firstDay = Date.UTC(today.year, today.month - 1, today.day);
132
+ for (let offset = 0; offset < 9; offset++) {
133
+ const date = new Date(firstDay + offset * DAY_MS);
134
+ if (!days.includes(WEEKDAYS[date.getUTCDay()])) continue;
135
+ const year = date.getUTCFullYear();
136
+ const month = date.getUTCMonth() + 1;
137
+ const day = date.getUTCDate();
138
+ const opensAt = zonedInstant(year, month, day, start.hh, start.mm, zone);
139
+ const closesAt = zonedInstant(year, month, day, end.hh, end.mm, zone);
140
+ if (nowMs < opensAt) return opensAt;
141
+ if (nowMs < closesAt) return null;
142
+ }
143
+ return null;
144
+ }
145
+ //#endregion
146
+ exports.nextWindowStart = nextWindowStart;
147
+ exports.resolveTimeZone = resolveTimeZone;
148
+
149
+ //# sourceMappingURL=time-window.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-window.cjs","names":[],"sources":["../../src/engine/time-window.ts"],"sourcesContent":["/**\n * Time-window scheduling — when does the next send window open?\n *\n * Pure functions of (nowMs, days, between, tz): the interpreter calls them\n * inside a durable step, so the answer is checkpointed and replay-safe.\n * Timezone math goes through Intl.DateTimeFormat (available in Node, workerd\n * and browsers alike) — no tz database dependency. An unknown timezone falls\n * back to UTC rather than failing the journey.\n */\n\nconst DAY_MS = 86_400_000;\n\n/** Indexed by Date#getUTCDay, matching WeekdayIR's lowercase three-letter form. */\nconst WEEKDAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];\n\ninterface LocalParts {\n year: number;\n month: number;\n day: number;\n hour: number;\n minute: number;\n}\n\nconst formatters = new Map<string, Intl.DateTimeFormat>();\n\n/** Cached per-timezone formatter; throws on an invalid timezone id. */\nfunction formatterFor(tz: string): Intl.DateTimeFormat {\n let dtf = formatters.get(tz);\n if (dtf === undefined) {\n dtf = new Intl.DateTimeFormat('en-US', {\n timeZone: tz,\n hourCycle: 'h23',\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n });\n formatters.set(tz, dtf);\n }\n return dtf;\n}\n\n/** Validate a timezone id, falling back to UTC — a bad profile value must not wedge the journey. */\nexport function resolveTimeZone(tz: string): string {\n try {\n formatterFor(tz);\n return tz;\n } catch {\n return 'UTC';\n }\n}\n\nfunction localParts(ms: number, tz: string): LocalParts {\n const parts: Partial<Record<string, string>> = {};\n for (const part of formatterFor(tz).formatToParts(new Date(ms))) {\n parts[part.type] = part.value;\n }\n return {\n year: Number(parts.year),\n month: Number(parts.month),\n day: Number(parts.day),\n hour: Number(parts.hour),\n minute: Number(parts.minute),\n };\n}\n\n/** The zone's UTC offset at an instant, as (wall clock read as if it were UTC) − instant. */\nfunction offsetMsAt(ts: number, tz: string): number {\n const p = localParts(ts, tz);\n return Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute) - ts;\n}\n\n/**\n * The first instant at or after `before` whose offset differs from the one\n * there — i.e. the DST transition separating the two bounds. Bisects on the\n * minute grid, the finest granularity any tz rule uses, so the loop lands on\n * the transition itself rather than merely near it.\n */\nfunction transitionBetween(before: number, after: number, tz: string): number {\n const startOffset = offsetMsAt(before, tz);\n let lo = before;\n let hi = after;\n while (hi - lo > 60_000) {\n // Halve on whole minutes: both bounds stay minute-aligned, so mid is\n // strictly inside (lo, hi) and the loop cannot stall.\n const mid = lo + Math.floor((hi - lo) / 120_000) * 60_000;\n if (offsetMsAt(mid, tz) === startOffset) lo = mid;\n else hi = mid;\n }\n return hi;\n}\n\n/**\n * The UTC instant at which the wall clock in `tz` reads y-m-d hh:mm.\n *\n * Guess the UTC value from the offset at the guess, read it back through the\n * timezone and correct by the difference; a second pass settles every wall\n * time that exists, and picks the earlier of the two when a fall-back repeats\n * an hour.\n *\n * A wall time inside a spring-forward gap exists on no clock, so the two\n * corrections straddle the transition and neither reads back as asked. The\n * convention here is the usual one: move forward to the first instant that\n * does exist, i.e. the transition itself — 02:30 on a US spring-forward day\n * resolves to 03:00. Without that branch the correction bottoms out on the low\n * side and the window opens an hour *before* its configured start.\n */\nfunction zonedInstant(\n year: number,\n month: number,\n day: number,\n hour: number,\n minute: number,\n tz: string\n): number {\n const target = Date.UTC(year, month - 1, day, hour, minute);\n const first = target - offsetMsAt(target, tz);\n if (offsetMsAt(first, tz) === target - first) return first;\n const second = target - offsetMsAt(first, tz);\n if (offsetMsAt(second, tz) === target - second) return second;\n return transitionBetween(Math.min(first, second), Math.max(first, second), tz);\n}\n\nconst TIME = /^(\\d{1,2}):(\\d{2})$/;\n\nfunction parseTime(value: string): { hh: number; mm: number } | null {\n const match = TIME.exec(value);\n if (!match) return null;\n const hh = Number(match[1]);\n const mm = Number(match[2]);\n if (hh > 23 || mm > 59) return null;\n return { hh, mm };\n}\n\n/**\n * The instant the next [start, end) window opens in `tz`, or null when nowMs\n * is already inside a window. Scans nine calendar days so every weekday is\n * reachable. Malformed times (the DSL validates its own input, but IR may come\n * from elsewhere) disable the window — better to send now than to hold a user\n * forever on a config typo.\n */\nexport function nextWindowStart(\n nowMs: number,\n days: readonly string[],\n between: readonly [string, string],\n tz: string\n): number | null {\n const start = parseTime(between[0]);\n const end = parseTime(between[1]);\n if (start === null || end === null) return null;\n const zone = resolveTimeZone(tz);\n\n // Step the local *calendar*, not the absolute clock: a spring-forward day is\n // 23 hours long, so nowMs + n·24h crosses two local midnights and skips that\n // date entirely — the send would be held until the weekday came round again.\n // Date.UTC arithmetic over the civil date has no DST to trip on, and\n // getUTCDay names the same weekday the zone does.\n const today = localParts(nowMs, zone);\n const firstDay = Date.UTC(today.year, today.month - 1, today.day);\n\n for (let offset = 0; offset < 9; offset++) {\n const date = new Date(firstDay + offset * DAY_MS);\n if (!days.includes(WEEKDAYS[date.getUTCDay()])) continue;\n const year = date.getUTCFullYear();\n const month = date.getUTCMonth() + 1;\n const day = date.getUTCDate();\n const opensAt = zonedInstant(year, month, day, start.hh, start.mm, zone);\n const closesAt = zonedInstant(year, month, day, end.hh, end.mm, zone);\n if (nowMs < opensAt) return opensAt;\n if (nowMs < closesAt) return null;\n // Past this day's window (e.g. 23:00 against 09:00–17:00) → keep scanning forward\n }\n return null; // no allowed day in range (an empty days list) → do not wait\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ //#region src/engine/time-window.d.ts
2
+ /**
3
+ * Time-window scheduling — when does the next send window open?
4
+ *
5
+ * Pure functions of (nowMs, days, between, tz): the interpreter calls them
6
+ * inside a durable step, so the answer is checkpointed and replay-safe.
7
+ * Timezone math goes through Intl.DateTimeFormat (available in Node, workerd
8
+ * and browsers alike) — no tz database dependency. An unknown timezone falls
9
+ * back to UTC rather than failing the journey.
10
+ */
11
+ /** Validate a timezone id, falling back to UTC — a bad profile value must not wedge the journey. */
12
+ export declare function resolveTimeZone(tz: string): string;
13
+ /**
14
+ * The instant the next [start, end) window opens in `tz`, or null when nowMs
15
+ * is already inside a window. Scans nine calendar days so every weekday is
16
+ * reachable. Malformed times (the DSL validates its own input, but IR may come
17
+ * from elsewhere) disable the window — better to send now than to hold a user
18
+ * forever on a config typo.
19
+ */
20
+ export declare function nextWindowStart(nowMs: number, days: readonly string[], between: readonly [string, string], tz: string): number | null;
21
+ //#endregion
22
+ //# sourceMappingURL=time-window.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-window.d.cts","names":[],"sources":["../../src/engine/time-window.ts"],"mappings":";;;;;;;;;;;wBA4CgB,gBAAgB;;;;;;;;wBAkGhB,gBACd,eACA,yBACA,oCACA"}
@@ -0,0 +1,22 @@
1
+ //#region src/engine/time-window.d.ts
2
+ /**
3
+ * Time-window scheduling — when does the next send window open?
4
+ *
5
+ * Pure functions of (nowMs, days, between, tz): the interpreter calls them
6
+ * inside a durable step, so the answer is checkpointed and replay-safe.
7
+ * Timezone math goes through Intl.DateTimeFormat (available in Node, workerd
8
+ * and browsers alike) — no tz database dependency. An unknown timezone falls
9
+ * back to UTC rather than failing the journey.
10
+ */
11
+ /** Validate a timezone id, falling back to UTC — a bad profile value must not wedge the journey. */
12
+ export declare function resolveTimeZone(tz: string): string;
13
+ /**
14
+ * The instant the next [start, end) window opens in `tz`, or null when nowMs
15
+ * is already inside a window. Scans nine calendar days so every weekday is
16
+ * reachable. Malformed times (the DSL validates its own input, but IR may come
17
+ * from elsewhere) disable the window — better to send now than to hold a user
18
+ * forever on a config typo.
19
+ */
20
+ export declare function nextWindowStart(nowMs: number, days: readonly string[], between: readonly [string, string], tz: string): number | null;
21
+ //#endregion
22
+ //# sourceMappingURL=time-window.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-window.d.mts","names":[],"sources":["../../src/engine/time-window.ts"],"mappings":";;;;;;;;;;;wBA4CgB,gBAAgB;;;;;;;;wBAkGhB,gBACd,eACA,yBACA,oCACA"}
@@ -0,0 +1,147 @@
1
+ //#region src/engine/time-window.ts
2
+ /**
3
+ * Time-window scheduling — when does the next send window open?
4
+ *
5
+ * Pure functions of (nowMs, days, between, tz): the interpreter calls them
6
+ * inside a durable step, so the answer is checkpointed and replay-safe.
7
+ * Timezone math goes through Intl.DateTimeFormat (available in Node, workerd
8
+ * and browsers alike) — no tz database dependency. An unknown timezone falls
9
+ * back to UTC rather than failing the journey.
10
+ */
11
+ const DAY_MS = 864e5;
12
+ /** Indexed by Date#getUTCDay, matching WeekdayIR's lowercase three-letter form. */
13
+ const WEEKDAYS = [
14
+ "sun",
15
+ "mon",
16
+ "tue",
17
+ "wed",
18
+ "thu",
19
+ "fri",
20
+ "sat"
21
+ ];
22
+ const formatters = /* @__PURE__ */ new Map();
23
+ /** Cached per-timezone formatter; throws on an invalid timezone id. */
24
+ function formatterFor(tz) {
25
+ let dtf = formatters.get(tz);
26
+ if (dtf === void 0) {
27
+ dtf = new Intl.DateTimeFormat("en-US", {
28
+ timeZone: tz,
29
+ hourCycle: "h23",
30
+ year: "numeric",
31
+ month: "numeric",
32
+ day: "numeric",
33
+ hour: "numeric",
34
+ minute: "numeric"
35
+ });
36
+ formatters.set(tz, dtf);
37
+ }
38
+ return dtf;
39
+ }
40
+ /** Validate a timezone id, falling back to UTC — a bad profile value must not wedge the journey. */
41
+ function resolveTimeZone(tz) {
42
+ try {
43
+ formatterFor(tz);
44
+ return tz;
45
+ } catch {
46
+ return "UTC";
47
+ }
48
+ }
49
+ function localParts(ms, tz) {
50
+ const parts = {};
51
+ for (const part of formatterFor(tz).formatToParts(new Date(ms))) parts[part.type] = part.value;
52
+ return {
53
+ year: Number(parts.year),
54
+ month: Number(parts.month),
55
+ day: Number(parts.day),
56
+ hour: Number(parts.hour),
57
+ minute: Number(parts.minute)
58
+ };
59
+ }
60
+ /** The zone's UTC offset at an instant, as (wall clock read as if it were UTC) − instant. */
61
+ function offsetMsAt(ts, tz) {
62
+ const p = localParts(ts, tz);
63
+ return Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute) - ts;
64
+ }
65
+ /**
66
+ * The first instant at or after `before` whose offset differs from the one
67
+ * there — i.e. the DST transition separating the two bounds. Bisects on the
68
+ * minute grid, the finest granularity any tz rule uses, so the loop lands on
69
+ * the transition itself rather than merely near it.
70
+ */
71
+ function transitionBetween(before, after, tz) {
72
+ const startOffset = offsetMsAt(before, tz);
73
+ let lo = before;
74
+ let hi = after;
75
+ while (hi - lo > 6e4) {
76
+ const mid = lo + Math.floor((hi - lo) / 12e4) * 6e4;
77
+ if (offsetMsAt(mid, tz) === startOffset) lo = mid;
78
+ else hi = mid;
79
+ }
80
+ return hi;
81
+ }
82
+ /**
83
+ * The UTC instant at which the wall clock in `tz` reads y-m-d hh:mm.
84
+ *
85
+ * Guess the UTC value from the offset at the guess, read it back through the
86
+ * timezone and correct by the difference; a second pass settles every wall
87
+ * time that exists, and picks the earlier of the two when a fall-back repeats
88
+ * an hour.
89
+ *
90
+ * A wall time inside a spring-forward gap exists on no clock, so the two
91
+ * corrections straddle the transition and neither reads back as asked. The
92
+ * convention here is the usual one: move forward to the first instant that
93
+ * does exist, i.e. the transition itself — 02:30 on a US spring-forward day
94
+ * resolves to 03:00. Without that branch the correction bottoms out on the low
95
+ * side and the window opens an hour *before* its configured start.
96
+ */
97
+ function zonedInstant(year, month, day, hour, minute, tz) {
98
+ const target = Date.UTC(year, month - 1, day, hour, minute);
99
+ const first = target - offsetMsAt(target, tz);
100
+ if (offsetMsAt(first, tz) === target - first) return first;
101
+ const second = target - offsetMsAt(first, tz);
102
+ if (offsetMsAt(second, tz) === target - second) return second;
103
+ return transitionBetween(Math.min(first, second), Math.max(first, second), tz);
104
+ }
105
+ const TIME = /^(\d{1,2}):(\d{2})$/;
106
+ function parseTime(value) {
107
+ const match = TIME.exec(value);
108
+ if (!match) return null;
109
+ const hh = Number(match[1]);
110
+ const mm = Number(match[2]);
111
+ if (hh > 23 || mm > 59) return null;
112
+ return {
113
+ hh,
114
+ mm
115
+ };
116
+ }
117
+ /**
118
+ * The instant the next [start, end) window opens in `tz`, or null when nowMs
119
+ * is already inside a window. Scans nine calendar days so every weekday is
120
+ * reachable. Malformed times (the DSL validates its own input, but IR may come
121
+ * from elsewhere) disable the window — better to send now than to hold a user
122
+ * forever on a config typo.
123
+ */
124
+ function nextWindowStart(nowMs, days, between, tz) {
125
+ const start = parseTime(between[0]);
126
+ const end = parseTime(between[1]);
127
+ if (start === null || end === null) return null;
128
+ const zone = resolveTimeZone(tz);
129
+ const today = localParts(nowMs, zone);
130
+ const firstDay = Date.UTC(today.year, today.month - 1, today.day);
131
+ for (let offset = 0; offset < 9; offset++) {
132
+ const date = new Date(firstDay + offset * DAY_MS);
133
+ if (!days.includes(WEEKDAYS[date.getUTCDay()])) continue;
134
+ const year = date.getUTCFullYear();
135
+ const month = date.getUTCMonth() + 1;
136
+ const day = date.getUTCDate();
137
+ const opensAt = zonedInstant(year, month, day, start.hh, start.mm, zone);
138
+ const closesAt = zonedInstant(year, month, day, end.hh, end.mm, zone);
139
+ if (nowMs < opensAt) return opensAt;
140
+ if (nowMs < closesAt) return null;
141
+ }
142
+ return null;
143
+ }
144
+ //#endregion
145
+ export { nextWindowStart, resolveTimeZone };
146
+
147
+ //# sourceMappingURL=time-window.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-window.mjs","names":[],"sources":["../../src/engine/time-window.ts"],"sourcesContent":["/**\n * Time-window scheduling — when does the next send window open?\n *\n * Pure functions of (nowMs, days, between, tz): the interpreter calls them\n * inside a durable step, so the answer is checkpointed and replay-safe.\n * Timezone math goes through Intl.DateTimeFormat (available in Node, workerd\n * and browsers alike) — no tz database dependency. An unknown timezone falls\n * back to UTC rather than failing the journey.\n */\n\nconst DAY_MS = 86_400_000;\n\n/** Indexed by Date#getUTCDay, matching WeekdayIR's lowercase three-letter form. */\nconst WEEKDAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];\n\ninterface LocalParts {\n year: number;\n month: number;\n day: number;\n hour: number;\n minute: number;\n}\n\nconst formatters = new Map<string, Intl.DateTimeFormat>();\n\n/** Cached per-timezone formatter; throws on an invalid timezone id. */\nfunction formatterFor(tz: string): Intl.DateTimeFormat {\n let dtf = formatters.get(tz);\n if (dtf === undefined) {\n dtf = new Intl.DateTimeFormat('en-US', {\n timeZone: tz,\n hourCycle: 'h23',\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n });\n formatters.set(tz, dtf);\n }\n return dtf;\n}\n\n/** Validate a timezone id, falling back to UTC — a bad profile value must not wedge the journey. */\nexport function resolveTimeZone(tz: string): string {\n try {\n formatterFor(tz);\n return tz;\n } catch {\n return 'UTC';\n }\n}\n\nfunction localParts(ms: number, tz: string): LocalParts {\n const parts: Partial<Record<string, string>> = {};\n for (const part of formatterFor(tz).formatToParts(new Date(ms))) {\n parts[part.type] = part.value;\n }\n return {\n year: Number(parts.year),\n month: Number(parts.month),\n day: Number(parts.day),\n hour: Number(parts.hour),\n minute: Number(parts.minute),\n };\n}\n\n/** The zone's UTC offset at an instant, as (wall clock read as if it were UTC) − instant. */\nfunction offsetMsAt(ts: number, tz: string): number {\n const p = localParts(ts, tz);\n return Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute) - ts;\n}\n\n/**\n * The first instant at or after `before` whose offset differs from the one\n * there — i.e. the DST transition separating the two bounds. Bisects on the\n * minute grid, the finest granularity any tz rule uses, so the loop lands on\n * the transition itself rather than merely near it.\n */\nfunction transitionBetween(before: number, after: number, tz: string): number {\n const startOffset = offsetMsAt(before, tz);\n let lo = before;\n let hi = after;\n while (hi - lo > 60_000) {\n // Halve on whole minutes: both bounds stay minute-aligned, so mid is\n // strictly inside (lo, hi) and the loop cannot stall.\n const mid = lo + Math.floor((hi - lo) / 120_000) * 60_000;\n if (offsetMsAt(mid, tz) === startOffset) lo = mid;\n else hi = mid;\n }\n return hi;\n}\n\n/**\n * The UTC instant at which the wall clock in `tz` reads y-m-d hh:mm.\n *\n * Guess the UTC value from the offset at the guess, read it back through the\n * timezone and correct by the difference; a second pass settles every wall\n * time that exists, and picks the earlier of the two when a fall-back repeats\n * an hour.\n *\n * A wall time inside a spring-forward gap exists on no clock, so the two\n * corrections straddle the transition and neither reads back as asked. The\n * convention here is the usual one: move forward to the first instant that\n * does exist, i.e. the transition itself — 02:30 on a US spring-forward day\n * resolves to 03:00. Without that branch the correction bottoms out on the low\n * side and the window opens an hour *before* its configured start.\n */\nfunction zonedInstant(\n year: number,\n month: number,\n day: number,\n hour: number,\n minute: number,\n tz: string\n): number {\n const target = Date.UTC(year, month - 1, day, hour, minute);\n const first = target - offsetMsAt(target, tz);\n if (offsetMsAt(first, tz) === target - first) return first;\n const second = target - offsetMsAt(first, tz);\n if (offsetMsAt(second, tz) === target - second) return second;\n return transitionBetween(Math.min(first, second), Math.max(first, second), tz);\n}\n\nconst TIME = /^(\\d{1,2}):(\\d{2})$/;\n\nfunction parseTime(value: string): { hh: number; mm: number } | null {\n const match = TIME.exec(value);\n if (!match) return null;\n const hh = Number(match[1]);\n const mm = Number(match[2]);\n if (hh > 23 || mm > 59) return null;\n return { hh, mm };\n}\n\n/**\n * The instant the next [start, end) window opens in `tz`, or null when nowMs\n * is already inside a window. Scans nine calendar days so every weekday is\n * reachable. Malformed times (the DSL validates its own input, but IR may come\n * from elsewhere) disable the window — better to send now than to hold a user\n * forever on a config typo.\n */\nexport function nextWindowStart(\n nowMs: number,\n days: readonly string[],\n between: readonly [string, string],\n tz: string\n): number | null {\n const start = parseTime(between[0]);\n const end = parseTime(between[1]);\n if (start === null || end === null) return null;\n const zone = resolveTimeZone(tz);\n\n // Step the local *calendar*, not the absolute clock: a spring-forward day is\n // 23 hours long, so nowMs + n·24h crosses two local midnights and skips that\n // date entirely — the send would be held until the weekday came round again.\n // Date.UTC arithmetic over the civil date has no DST to trip on, and\n // getUTCDay names the same weekday the zone does.\n const today = localParts(nowMs, zone);\n const firstDay = Date.UTC(today.year, today.month - 1, today.day);\n\n for (let offset = 0; offset < 9; offset++) {\n const date = new Date(firstDay + offset * DAY_MS);\n if (!days.includes(WEEKDAYS[date.getUTCDay()])) continue;\n const year = date.getUTCFullYear();\n const month = date.getUTCMonth() + 1;\n const day = date.getUTCDate();\n const opensAt = zonedInstant(year, month, day, start.hh, start.mm, zone);\n const closesAt = zonedInstant(year, month, day, end.hh, end.mm, zone);\n if (nowMs < opensAt) return opensAt;\n if (nowMs < closesAt) return null;\n // Past this day's window (e.g. 23:00 against 09:00–17:00) → keep scanning forward\n }\n return null; // no allowed day in range (an empty days list) → do not wait\n}\n"],"mappings":"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"}