@shware/workflow 3.3.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/README.md +230 -40
  2. package/dist/apns.cjs +113 -26
  3. package/dist/apns.cjs.map +1 -1
  4. package/dist/apns.d.cts +52 -6
  5. package/dist/apns.d.cts.map +1 -1
  6. package/dist/apns.d.mts +52 -6
  7. package/dist/apns.d.mts.map +1 -1
  8. package/dist/apns.mjs +111 -27
  9. package/dist/apns.mjs.map +1 -1
  10. package/dist/aws/engine-step.cjs +5 -5
  11. package/dist/aws/engine-step.cjs.map +1 -1
  12. package/dist/aws/engine-step.d.cts +8 -2
  13. package/dist/aws/engine-step.d.cts.map +1 -1
  14. package/dist/aws/engine-step.d.mts +8 -2
  15. package/dist/aws/engine-step.d.mts.map +1 -1
  16. package/dist/aws/engine-step.mjs +5 -5
  17. package/dist/aws/engine-step.mjs.map +1 -1
  18. package/dist/aws/index.cjs +50 -6
  19. package/dist/aws/index.cjs.map +1 -1
  20. package/dist/aws/index.d.cts +4 -3
  21. package/dist/aws/index.d.cts.map +1 -1
  22. package/dist/aws/index.d.mts +4 -3
  23. package/dist/aws/index.d.mts.map +1 -1
  24. package/dist/aws/index.mjs +49 -7
  25. package/dist/aws/index.mjs.map +1 -1
  26. package/dist/aws/instances.cjs +29 -6
  27. package/dist/aws/instances.cjs.map +1 -1
  28. package/dist/aws/instances.d.cts +11 -1
  29. package/dist/aws/instances.d.cts.map +1 -1
  30. package/dist/aws/instances.d.mts +11 -1
  31. package/dist/aws/instances.d.mts.map +1 -1
  32. package/dist/aws/instances.mjs +27 -4
  33. package/dist/aws/instances.mjs.map +1 -1
  34. package/dist/client.cjs +4 -1
  35. package/dist/client.cjs.map +1 -1
  36. package/dist/client.d.cts +2 -2
  37. package/dist/client.d.cts.map +1 -1
  38. package/dist/client.d.mts +2 -2
  39. package/dist/client.d.mts.map +1 -1
  40. package/dist/client.mjs +4 -1
  41. package/dist/client.mjs.map +1 -1
  42. package/dist/cloudflare/bindings.cjs +28 -25
  43. package/dist/cloudflare/bindings.cjs.map +1 -1
  44. package/dist/cloudflare/bindings.d.cts +15 -129
  45. package/dist/cloudflare/bindings.d.cts.map +1 -1
  46. package/dist/cloudflare/bindings.d.mts +15 -129
  47. package/dist/cloudflare/bindings.d.mts.map +1 -1
  48. package/dist/cloudflare/bindings.mjs +28 -23
  49. package/dist/cloudflare/bindings.mjs.map +1 -1
  50. package/dist/cloudflare/d1-env.cjs +6 -6
  51. package/dist/cloudflare/d1-env.cjs.map +1 -1
  52. package/dist/cloudflare/d1-env.d.cts +5 -5
  53. package/dist/cloudflare/d1-env.d.mts +5 -5
  54. package/dist/cloudflare/d1-env.mjs +6 -6
  55. package/dist/cloudflare/d1-env.mjs.map +1 -1
  56. package/dist/cloudflare/index.cjs +29 -18
  57. package/dist/cloudflare/index.d.cts +8 -4
  58. package/dist/cloudflare/index.d.mts +8 -4
  59. package/dist/cloudflare/index.mjs +9 -5
  60. package/dist/cloudflare/runner.cjs +29 -14
  61. package/dist/cloudflare/runner.cjs.map +1 -1
  62. package/dist/cloudflare/runner.d.cts +9 -2
  63. package/dist/cloudflare/runner.d.cts.map +1 -1
  64. package/dist/cloudflare/runner.d.mts +9 -2
  65. package/dist/cloudflare/runner.d.mts.map +1 -1
  66. package/dist/cloudflare/runner.mjs +28 -13
  67. package/dist/cloudflare/runner.mjs.map +1 -1
  68. package/dist/cloudflare/schema.sql +27 -6
  69. package/dist/cloudflare/worker.cjs +19 -9
  70. package/dist/cloudflare/worker.cjs.map +1 -1
  71. package/dist/cloudflare/worker.d.cts +2 -2
  72. package/dist/cloudflare/worker.d.cts.map +1 -1
  73. package/dist/cloudflare/worker.d.mts +2 -2
  74. package/dist/cloudflare/worker.d.mts.map +1 -1
  75. package/dist/cloudflare/worker.mjs +19 -9
  76. package/dist/cloudflare/worker.mjs.map +1 -1
  77. package/dist/config.cjs +21 -5
  78. package/dist/config.cjs.map +1 -1
  79. package/dist/config.mjs +21 -5
  80. package/dist/config.mjs.map +1 -1
  81. package/dist/dsl/action.cjs +3 -6
  82. package/dist/dsl/action.cjs.map +1 -1
  83. package/dist/dsl/action.d.cts +12 -11
  84. package/dist/dsl/action.d.cts.map +1 -1
  85. package/dist/dsl/action.d.mts +12 -11
  86. package/dist/dsl/action.d.mts.map +1 -1
  87. package/dist/dsl/action.mjs +3 -6
  88. package/dist/dsl/action.mjs.map +1 -1
  89. package/dist/dsl/bundle.cjs +35 -31
  90. package/dist/dsl/bundle.cjs.map +1 -1
  91. package/dist/dsl/bundle.d.cts.map +1 -1
  92. package/dist/dsl/bundle.d.mts.map +1 -1
  93. package/dist/dsl/bundle.mjs +35 -31
  94. package/dist/dsl/bundle.mjs.map +1 -1
  95. package/dist/dsl/condition.cjs +13 -1
  96. package/dist/dsl/condition.cjs.map +1 -1
  97. package/dist/dsl/condition.d.cts +9 -1
  98. package/dist/dsl/condition.d.cts.map +1 -1
  99. package/dist/dsl/condition.d.mts +9 -1
  100. package/dist/dsl/condition.d.mts.map +1 -1
  101. package/dist/dsl/condition.mjs +13 -2
  102. package/dist/dsl/condition.mjs.map +1 -1
  103. package/dist/dsl/flow.cjs +33 -11
  104. package/dist/dsl/flow.cjs.map +1 -1
  105. package/dist/dsl/flow.d.cts +9 -1
  106. package/dist/dsl/flow.d.cts.map +1 -1
  107. package/dist/dsl/flow.d.mts +9 -1
  108. package/dist/dsl/flow.d.mts.map +1 -1
  109. package/dist/dsl/flow.mjs +34 -13
  110. package/dist/dsl/flow.mjs.map +1 -1
  111. package/dist/dsl/workflow.cjs +2 -3
  112. package/dist/dsl/workflow.cjs.map +1 -1
  113. package/dist/dsl/workflow.d.cts.map +1 -1
  114. package/dist/dsl/workflow.d.mts.map +1 -1
  115. package/dist/dsl/workflow.mjs +3 -4
  116. package/dist/dsl/workflow.mjs.map +1 -1
  117. package/dist/engine/actions.cjs +1 -8
  118. package/dist/engine/actions.cjs.map +1 -1
  119. package/dist/engine/actions.d.cts +1 -12
  120. package/dist/engine/actions.d.cts.map +1 -1
  121. package/dist/engine/actions.d.mts +1 -12
  122. package/dist/engine/actions.d.mts.map +1 -1
  123. package/dist/engine/actions.mjs +1 -8
  124. package/dist/engine/actions.mjs.map +1 -1
  125. package/dist/engine/condition.cjs +25 -5
  126. package/dist/engine/condition.cjs.map +1 -1
  127. package/dist/engine/condition.d.cts +12 -3
  128. package/dist/engine/condition.d.cts.map +1 -1
  129. package/dist/engine/condition.d.mts +12 -3
  130. package/dist/engine/condition.d.mts.map +1 -1
  131. package/dist/engine/condition.mjs +25 -6
  132. package/dist/engine/condition.mjs.map +1 -1
  133. package/dist/engine/index.cjs +3 -0
  134. package/dist/engine/index.d.cts +5 -5
  135. package/dist/engine/index.d.mts +5 -5
  136. package/dist/engine/index.mjs +4 -4
  137. package/dist/engine/interpreter.cjs +27 -20
  138. package/dist/engine/interpreter.cjs.map +1 -1
  139. package/dist/engine/interpreter.d.cts.map +1 -1
  140. package/dist/engine/interpreter.d.mts.map +1 -1
  141. package/dist/engine/interpreter.mjs +27 -20
  142. package/dist/engine/interpreter.mjs.map +1 -1
  143. package/dist/engine/ports.d.cts +15 -6
  144. package/dist/engine/ports.d.cts.map +1 -1
  145. package/dist/engine/ports.d.mts +15 -6
  146. package/dist/engine/ports.d.mts.map +1 -1
  147. package/dist/engine/retry.cjs +13 -0
  148. package/dist/engine/retry.cjs.map +1 -1
  149. package/dist/engine/retry.d.cts +12 -0
  150. package/dist/engine/retry.d.cts.map +1 -1
  151. package/dist/engine/retry.d.mts +12 -0
  152. package/dist/engine/retry.d.mts.map +1 -1
  153. package/dist/engine/retry.mjs +13 -1
  154. package/dist/engine/retry.mjs.map +1 -1
  155. package/dist/engine/time-scale.cjs +90 -15
  156. package/dist/engine/time-scale.cjs.map +1 -1
  157. package/dist/engine/time-scale.d.cts +9 -6
  158. package/dist/engine/time-scale.d.cts.map +1 -1
  159. package/dist/engine/time-scale.d.mts +9 -6
  160. package/dist/engine/time-scale.d.mts.map +1 -1
  161. package/dist/engine/time-scale.mjs +90 -16
  162. package/dist/engine/time-scale.mjs.map +1 -1
  163. package/dist/fcm.cjs +111 -38
  164. package/dist/fcm.cjs.map +1 -1
  165. package/dist/fcm.d.cts +45 -5
  166. package/dist/fcm.d.cts.map +1 -1
  167. package/dist/fcm.d.mts +45 -5
  168. package/dist/fcm.d.mts.map +1 -1
  169. package/dist/fcm.mjs +110 -39
  170. package/dist/fcm.mjs.map +1 -1
  171. package/dist/hash.cjs +6 -6
  172. package/dist/hash.cjs.map +1 -1
  173. package/dist/hash.mjs +6 -6
  174. package/dist/hash.mjs.map +1 -1
  175. package/dist/host/auth.cjs +49 -0
  176. package/dist/host/auth.cjs.map +1 -0
  177. package/dist/host/auth.d.cts +35 -0
  178. package/dist/host/auth.d.cts.map +1 -0
  179. package/dist/host/auth.d.mts +35 -0
  180. package/dist/host/auth.d.mts.map +1 -0
  181. package/dist/host/auth.mjs +46 -0
  182. package/dist/host/auth.mjs.map +1 -0
  183. package/dist/host/channels.cjs +118 -40
  184. package/dist/host/channels.cjs.map +1 -1
  185. package/dist/host/channels.d.cts +27 -1
  186. package/dist/host/channels.d.cts.map +1 -1
  187. package/dist/host/channels.d.mts +27 -1
  188. package/dist/host/channels.d.mts.map +1 -1
  189. package/dist/host/channels.mjs +121 -44
  190. package/dist/host/channels.mjs.map +1 -1
  191. package/dist/host/execute.cjs +14 -2
  192. package/dist/host/execute.cjs.map +1 -1
  193. package/dist/host/execute.d.cts +4 -4
  194. package/dist/host/execute.d.cts.map +1 -1
  195. package/dist/host/execute.d.mts +4 -4
  196. package/dist/host/execute.d.mts.map +1 -1
  197. package/dist/host/execute.mjs +13 -1
  198. package/dist/host/execute.mjs.map +1 -1
  199. package/dist/host/fresh-read.cjs +30 -0
  200. package/dist/host/fresh-read.cjs.map +1 -0
  201. package/dist/host/fresh-read.d.cts +18 -0
  202. package/dist/host/fresh-read.d.cts.map +1 -0
  203. package/dist/host/fresh-read.d.mts +18 -0
  204. package/dist/host/fresh-read.d.mts.map +1 -0
  205. package/dist/host/fresh-read.mjs +29 -0
  206. package/dist/host/fresh-read.mjs.map +1 -0
  207. package/dist/host/http.cjs +340 -0
  208. package/dist/host/http.cjs.map +1 -0
  209. package/dist/host/http.d.cts +58 -0
  210. package/dist/host/http.d.cts.map +1 -0
  211. package/dist/host/http.d.mts +58 -0
  212. package/dist/host/http.d.mts.map +1 -0
  213. package/dist/host/http.mjs +336 -0
  214. package/dist/host/http.mjs.map +1 -0
  215. package/dist/host/{router.cjs → ingest.cjs} +147 -175
  216. package/dist/host/ingest.cjs.map +1 -0
  217. package/dist/host/{router.d.cts → ingest.d.cts} +48 -30
  218. package/dist/host/ingest.d.cts.map +1 -0
  219. package/dist/host/{router.d.mts → ingest.d.mts} +48 -30
  220. package/dist/host/ingest.d.mts.map +1 -0
  221. package/dist/host/{router.mjs → ingest.mjs} +144 -173
  222. package/dist/host/ingest.mjs.map +1 -0
  223. package/dist/host/journey-host.cjs +66 -93
  224. package/dist/host/journey-host.cjs.map +1 -1
  225. package/dist/host/journey-host.d.cts +31 -26
  226. package/dist/host/journey-host.d.cts.map +1 -1
  227. package/dist/host/journey-host.d.mts +31 -26
  228. package/dist/host/journey-host.d.mts.map +1 -1
  229. package/dist/host/journey-host.mjs +66 -93
  230. package/dist/host/journey-host.mjs.map +1 -1
  231. package/dist/host/platform.cjs +21 -0
  232. package/dist/host/platform.cjs.map +1 -0
  233. package/dist/host/platform.d.cts +126 -0
  234. package/dist/host/platform.d.cts.map +1 -0
  235. package/dist/host/platform.d.mts +126 -0
  236. package/dist/host/platform.d.mts.map +1 -0
  237. package/dist/host/platform.mjs +19 -0
  238. package/dist/host/platform.mjs.map +1 -0
  239. package/dist/host/senders.cjs +18 -9
  240. package/dist/host/senders.cjs.map +1 -1
  241. package/dist/host/senders.d.cts +9 -4
  242. package/dist/host/senders.d.cts.map +1 -1
  243. package/dist/host/senders.d.mts +9 -4
  244. package/dist/host/senders.d.mts.map +1 -1
  245. package/dist/host/senders.mjs +18 -9
  246. package/dist/host/senders.mjs.map +1 -1
  247. package/dist/index.cjs +1 -1
  248. package/dist/index.mjs +1 -1
  249. package/dist/ir.cjs +5 -7
  250. package/dist/ir.cjs.map +1 -1
  251. package/dist/ir.d.cts +3 -12
  252. package/dist/ir.d.cts.map +1 -1
  253. package/dist/ir.d.mts +3 -12
  254. package/dist/ir.d.mts.map +1 -1
  255. package/dist/ir.mjs +5 -7
  256. package/dist/ir.mjs.map +1 -1
  257. package/dist/plan.cjs +27 -33
  258. package/dist/plan.cjs.map +1 -1
  259. package/dist/plan.d.cts +1 -1
  260. package/dist/plan.d.cts.map +1 -1
  261. package/dist/plan.d.mts +1 -1
  262. package/dist/plan.d.mts.map +1 -1
  263. package/dist/plan.mjs +27 -33
  264. package/dist/plan.mjs.map +1 -1
  265. package/dist/reporting/tracking.cjs +14 -4
  266. package/dist/reporting/tracking.cjs.map +1 -1
  267. package/dist/reporting/tracking.d.cts +10 -10
  268. package/dist/reporting/tracking.d.cts.map +1 -1
  269. package/dist/reporting/tracking.d.mts +10 -10
  270. package/dist/reporting/tracking.d.mts.map +1 -1
  271. package/dist/reporting/tracking.mjs +14 -4
  272. package/dist/reporting/tracking.mjs.map +1 -1
  273. package/dist/store/d1.cjs +88 -29
  274. package/dist/store/d1.cjs.map +1 -1
  275. package/dist/store/d1.d.cts +29 -11
  276. package/dist/store/d1.d.cts.map +1 -1
  277. package/dist/store/d1.d.mts +29 -11
  278. package/dist/store/d1.d.mts.map +1 -1
  279. package/dist/store/d1.mjs +88 -29
  280. package/dist/store/d1.mjs.map +1 -1
  281. package/dist/store/erasure.cjs +22 -0
  282. package/dist/store/erasure.cjs.map +1 -0
  283. package/dist/store/erasure.d.cts +18 -0
  284. package/dist/store/erasure.d.cts.map +1 -0
  285. package/dist/store/erasure.d.mts +18 -0
  286. package/dist/store/erasure.d.mts.map +1 -0
  287. package/dist/store/erasure.mjs +21 -0
  288. package/dist/store/erasure.mjs.map +1 -0
  289. package/dist/store/index.cjs +2 -0
  290. package/dist/store/index.cjs.map +1 -1
  291. package/dist/store/index.d.cts +67 -13
  292. package/dist/store/index.d.cts.map +1 -1
  293. package/dist/store/index.d.mts +67 -13
  294. package/dist/store/index.d.mts.map +1 -1
  295. package/dist/store/index.mjs +2 -1
  296. package/dist/store/index.mjs.map +1 -1
  297. package/dist/store/postgres-reporting.cjs +125 -62
  298. package/dist/store/postgres-reporting.cjs.map +1 -1
  299. package/dist/store/postgres-reporting.d.cts +30 -2
  300. package/dist/store/postgres-reporting.d.cts.map +1 -1
  301. package/dist/store/postgres-reporting.d.mts +30 -2
  302. package/dist/store/postgres-reporting.d.mts.map +1 -1
  303. package/dist/store/postgres-reporting.mjs +125 -62
  304. package/dist/store/postgres-reporting.mjs.map +1 -1
  305. package/dist/store/postgres.cjs +179 -60
  306. package/dist/store/postgres.cjs.map +1 -1
  307. package/dist/store/postgres.d.cts +44 -7
  308. package/dist/store/postgres.d.cts.map +1 -1
  309. package/dist/store/postgres.d.mts +44 -7
  310. package/dist/store/postgres.d.mts.map +1 -1
  311. package/dist/store/postgres.mjs +179 -60
  312. package/dist/store/postgres.mjs.map +1 -1
  313. package/dist/store/tables.cjs +7 -5
  314. package/dist/store/tables.cjs.map +1 -1
  315. package/dist/store/tables.d.cts.map +1 -1
  316. package/dist/store/tables.d.mts.map +1 -1
  317. package/dist/store/tables.mjs +7 -5
  318. package/dist/store/tables.mjs.map +1 -1
  319. package/dist/store/user-lock.cjs +43 -0
  320. package/dist/store/user-lock.cjs.map +1 -0
  321. package/dist/store/user-lock.d.cts +32 -0
  322. package/dist/store/user-lock.d.cts.map +1 -0
  323. package/dist/store/user-lock.d.mts +32 -0
  324. package/dist/store/user-lock.d.mts.map +1 -0
  325. package/dist/store/user-lock.mjs +40 -0
  326. package/dist/store/user-lock.mjs.map +1 -0
  327. package/dist/transports/cloudflare-email.cjs +1 -1
  328. package/dist/transports/cloudflare-email.mjs +1 -1
  329. package/dist/transports/ses.cjs +47 -6
  330. package/dist/transports/ses.cjs.map +1 -1
  331. package/dist/transports/ses.d.cts +9 -1
  332. package/dist/transports/ses.d.cts.map +1 -1
  333. package/dist/transports/ses.d.mts +9 -1
  334. package/dist/transports/ses.d.mts.map +1 -1
  335. package/dist/transports/ses.mjs +47 -7
  336. package/dist/transports/ses.mjs.map +1 -1
  337. package/dist/webpush.cjs +133 -47
  338. package/dist/webpush.cjs.map +1 -1
  339. package/dist/webpush.d.cts +49 -8
  340. package/dist/webpush.d.cts.map +1 -1
  341. package/dist/webpush.d.mts +49 -8
  342. package/dist/webpush.d.mts.map +1 -1
  343. package/dist/webpush.mjs +130 -47
  344. package/dist/webpush.mjs.map +1 -1
  345. package/package.json +1 -1
  346. package/src/cloudflare/schema.sql +27 -6
  347. package/dist/host/router.cjs.map +0 -1
  348. package/dist/host/router.d.cts.map +0 -1
  349. package/dist/host/router.d.mts.map +0 -1
  350. package/dist/host/router.mjs.map +0 -1
@@ -17,10 +17,11 @@ import { ChannelIR, ConditionIR, ScalarIR } from "../ir.mjs";
17
17
  export interface EngineStep {
18
18
  /** Durable step: fn's return value is checkpointed; on replay the result is reused without re-running. */
19
19
  do<T>(name: string, fn: () => Promise<T>): Promise<T>;
20
- /** Durable sleep: suspended time is not billed. */
20
+ /**
21
+ * Durable sleep: suspended time is not billed. The only sleep primitive — a wait until an
22
+ * absolute instant is the interpreter checkpointing the remaining time, then sleeping it.
23
+ */
21
24
  sleep(name: string, ms: number): Promise<void>;
22
- /** Sleep until an absolute instant (used by time_window). */
23
- sleepUntil(name: string, timestampMs: number): Promise<void>;
24
25
  /**
25
26
  * Durable: register interest in the named events (Cloudflare: write the
26
27
  * subscription table; AWS: create a callback and store its id). Must
@@ -43,8 +44,18 @@ export interface EngineStep {
43
44
  /**
44
45
  * Wait for a wake-up or a timeout. A registration from `subscribe` must
45
46
  * already exist — this call only parks the instance.
47
+ *
48
+ * Both numbers are checkpointed by the interpreter, so they are replay-stable: `timeoutMs`
49
+ * is how long this attempt may park (the remaining time when it was computed), `deadlineMs`
50
+ * the absolute instant the whole wait gives up. An adapter that learns of a timeout only by
51
+ * a thrown error (Cloudflare) decides against `deadlineMs` — the clock at the replay that
52
+ * first runs that decision is what matters, not when the park began; one whose wake handle
53
+ * carries its own timeout (AWS) set it from `deadlineMs` at subscribe and ignores both here.
46
54
  */
47
- waitForWake(name: string, timeoutMs: number): Promise<'event' | 'timeout'>;
55
+ waitForWake(name: string, wait: {
56
+ timeoutMs: number;
57
+ deadlineMs: number;
58
+ }): Promise<'event' | 'timeout'>;
48
59
  }
49
60
  /** One device a push can go to: the provider token (APNs device token, FCM registration token) and the platform that picks the provider. */
50
61
  export interface PushDevice {
@@ -209,8 +220,6 @@ export interface EventSink {
209
220
  export interface ActionInvocation {
210
221
  /** Registry name; the code itself lives in the deployed bundle (dual-plane model, see dsl/action.ts). */
211
222
  action: string;
212
- /** Code-identity pin from the IR the journey is running; adapters compare it against the registered implementation. */
213
- codeHash?: string | undefined;
214
223
  /** Already resolved: user_property references have been replaced by actual values (same contract as message props). */
215
224
  args: Record<string, ScalarIR | undefined>;
216
225
  userId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ports.d.mts","names":[],"sources":["../../src/engine/ports.ts"],"mappings":";;;;;;;;;;;;;;;;iBAiBiB;;EAEf,GAAG,GAAG,cAAc,UAAU,QAAQ,KAAK,QAAQ;;EAGnD,MAAM,cAAc,aAAa;;EAGjC,WAAW,cAAc,sBAAsB;;;;;;;;;;;;;;;EAgB/C,UACE,cACA,2BACA;IAAY;MACX;;EAGH,YAAY,eAAe;;;;;EAM3B,YAAY,cAAc,oBAAoB;;;iBAI/B;EACf;EACA;;;iBAIe;;;;;;EAMf,YACE,eACA;IAAS;IAAkB;IAAkB,QAAQ;MACpD;EACH,YAAY,eAAe,QAAQ;;EAEnC,oBAAoB,eAAe,QAAQ;;EAE3C,eAAe,QAAQ;;EAEvB,cAAc,SAAS,YAAY;;iBAGpB;;EAEf;EACA;EACA;EACA,SAAS;EACT;;EAEA,OAAO,eAAe;EACtB;;;;;;EAMA;;EAEA,SAAS;;;;;;EAMT;;EAEA,WAAW;;;YAID;;;;;;iBAOK;;;;;EAKf,QAAQ,aAAa,SAAS;;EAE9B;;;;;;EAMA;;EAEA,OAAO;;;;;;EAMP,eAAe;;;;;;;iBAQA;EACf;EACA;;iBAGe;EACf,KAAK,SAAS,kBAAkB,eAAe;;;;;;;;;iBAUhC;;EAEf,SAAS;IACP;IACA;IACA;IACA;MACE;;EAEJ,aAAa;IACX;IACA;IACA;IACA;IACA;MACE;;EAEJ,iBAAiB,SAAS,kBAAkB,QAAQ;;EAEpD,aAAa,SAAS,iBAAiB;IAAU;MAAoB,aAAa;;EAElF,UAAU;IACR;IACA;IACA;IACA,QAAQ;IACR;;IAEA;MACE;;;;;;;;;iBAUW;EACf,SAAS;IACP,SAAS;IACT;IACA,OAAO,eAAe;MACpB,eAAe;;;iBAIJ;;;;;;;;EAQf,KAAK,eAAe,SAAS,eAAe,uBAAuB,aAAa;;;iBAIjE;;EAEf;;EAEA;;EAEA,MAAM,eAAe;EACrB;;EAEA;;;;;;;iBAQe;EACf,OAAO,YAAY,mBAAmB;;iBAGvB;EACf;;EAEA;;;;;;;EAOA;EACA,MAAM;EACN,OAAO;EACP,UAAU;EACV,QAAQ;;EAER,UAAU;;EAEV,YAAY;;EAEZ,WAAW;;;;;;;YAQD;EACN;;EACA;EAAkB;;EAClB;;EACA;;EACA"}
1
+ {"version":3,"file":"ports.d.mts","names":[],"sources":["../../src/engine/ports.ts"],"mappings":";;;;;;;;;;;;;;;;iBAiBiB;;EAEf,GAAG,GAAG,cAAc,UAAU,QAAQ,KAAK,QAAQ;;;;;EAMnD,MAAM,cAAc,aAAa;;;;;;;;;;;;;;;EAgBjC,UACE,cACA,2BACA;IAAY;MACX;;EAGH,YAAY,eAAe;;;;;;;;;;;;EAa3B,YACE,cACA;IAAQ;IAAmB;MAC1B;;;iBAIY;EACf;EACA;;;iBAIe;;;;;;EAMf,YACE,eACA;IAAS;IAAkB;IAAkB,QAAQ;MACpD;EACH,YAAY,eAAe,QAAQ;;EAEnC,oBAAoB,eAAe,QAAQ;;EAE3C,eAAe,QAAQ;;EAEvB,cAAc,SAAS,YAAY;;iBAGpB;;EAEf;EACA;EACA;EACA,SAAS;EACT;;EAEA,OAAO,eAAe;EACtB;;;;;;EAMA;;EAEA,SAAS;;;;;;EAMT;;EAEA,WAAW;;;YAID;;;;;;iBAOK;;;;;EAKf,QAAQ,aAAa,SAAS;;EAE9B;;;;;;EAMA;;EAEA,OAAO;;;;;;EAMP,eAAe;;;;;;;iBAQA;EACf;EACA;;iBAGe;EACf,KAAK,SAAS,kBAAkB,eAAe;;;;;;;;;iBAUhC;;EAEf,SAAS;IACP;IACA;IACA;IACA;MACE;;EAEJ,aAAa;IACX;IACA;IACA;IACA;IACA;MACE;;EAEJ,iBAAiB,SAAS,kBAAkB,QAAQ;;EAEpD,aAAa,SAAS,iBAAiB;IAAU;MAAoB,aAAa;;EAElF,UAAU;IACR;IACA;IACA;IACA,QAAQ;IACR;;IAEA;MACE;;;;;;;;;iBAUW;EACf,SAAS;IACP,SAAS;IACT;IACA,OAAO,eAAe;MACpB,eAAe;;;iBAIJ;;;;;;;;EAQf,KAAK,eAAe,SAAS,eAAe,uBAAuB,aAAa;;;iBAIjE;;EAEf;;EAEA,MAAM,eAAe;EACrB;;EAEA;;;;;;;iBAQe;EACf,OAAO,YAAY,mBAAmB;;iBAGvB;EACf;;EAEA;;;;;;;EAOA;EACA,MAAM;EACN,OAAO;EACP,UAAU;EACV,QAAQ;;EAER,UAAU;;EAEV,YAAY;;EAEZ,WAAW;;;;;;;YAQD;EACN;;EACA;EAAkB;;EAClB;;EACA;;EACA"}
@@ -7,6 +7,18 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  * instead of burning the retry budget and delaying the journey. Every runtime adapter asks this
8
8
  * one rule.
9
9
  */
10
+ /**
11
+ * A failure a retry would only repeat: a missing or malformed configuration, a provider refusing
12
+ * the request itself, an input the runtime cannot act on. Thrown inside a durable step, it ends
13
+ * the step at once (`isRetryable` is false) instead of burning the retry budget.
14
+ */
15
+ var PermanentError = class extends Error {
16
+ name = "PermanentError";
17
+ retryable = false;
18
+ constructor(message, options) {
19
+ super(message, options);
20
+ }
21
+ };
10
22
  /** Errors the engine raises that are deterministic by construction. */
11
23
  const DETERMINISTIC = /* @__PURE__ */ new Set(["TemplateValidationError", "ContractValidationError"]);
12
24
  /**
@@ -23,6 +35,7 @@ function isRetryableStatus(status) {
23
35
  return status === 408 || status === 429 || status >= 500;
24
36
  }
25
37
  //#endregion
38
+ exports.PermanentError = PermanentError;
26
39
  exports.isRetryable = isRetryable;
27
40
  exports.isRetryableStatus = isRetryableStatus;
28
41
 
@@ -1 +1 @@
1
- {"version":3,"file":"retry.cjs","names":[],"sources":["../../src/engine/retry.ts"],"sourcesContent":["/**\n * Which step failures a retry would only repeat. A durable step retries what it throws; an\n * error that is the same on every attempt — a template refusing its props, the contract\n * refusing an emitted event, a provider rejecting the request itself — ends the step at once\n * instead of burning the retry budget and delaying the journey. Every runtime adapter asks this\n * one rule.\n */\n\n/** Errors the engine raises that are deterministic by construction. */\nconst DETERMINISTIC = new Set(['TemplateValidationError', 'ContractValidationError']);\n\n/**\n * False for an error a retry cannot fix: one of the engine's deterministic refusals, or any\n * error that says so with `retryable: false` (the transports' 4xx answers).\n */\nexport function isRetryable(error: unknown): boolean {\n if (!(error instanceof Error)) return true;\n if (DETERMINISTIC.has(error.name)) return false;\n return (error as { retryable?: unknown }).retryable !== false;\n}\n\n/** An HTTP answer a retry can change: a timeout, throttling, or the provider's own fault. */\nexport function isRetryableStatus(status: number): boolean {\n return status === 408 || status === 429 || status >= 500;\n}\n"],"mappings":";;;;;;;;;;AASA,MAAM,gCAAgB,IAAI,IAAI,CAAC,2BAA2B,yBAAyB,CAAC;;;;;AAMpF,SAAgB,YAAY,OAAyB;CACnD,IAAI,EAAE,iBAAiB,QAAQ,OAAO;CACtC,IAAI,cAAc,IAAI,MAAM,IAAI,GAAG,OAAO;CAC1C,OAAQ,MAAkC,cAAc;AAC1D;;AAGA,SAAgB,kBAAkB,QAAyB;CACzD,OAAO,WAAW,OAAO,WAAW,OAAO,UAAU;AACvD"}
1
+ {"version":3,"file":"retry.cjs","names":[],"sources":["../../src/engine/retry.ts"],"sourcesContent":["/**\n * Which step failures a retry would only repeat. A durable step retries what it throws; an\n * error that is the same on every attempt — a template refusing its props, the contract\n * refusing an emitted event, a provider rejecting the request itself — ends the step at once\n * instead of burning the retry budget and delaying the journey. Every runtime adapter asks this\n * one rule.\n */\n\n/**\n * A failure a retry would only repeat: a missing or malformed configuration, a provider refusing\n * the request itself, an input the runtime cannot act on. Thrown inside a durable step, it ends\n * the step at once (`isRetryable` is false) instead of burning the retry budget.\n */\nexport class PermanentError extends Error {\n override readonly name: string = 'PermanentError';\n readonly retryable = false;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n }\n}\n\n/** Errors the engine raises that are deterministic by construction. */\nconst DETERMINISTIC = new Set(['TemplateValidationError', 'ContractValidationError']);\n\n/**\n * False for an error a retry cannot fix: one of the engine's deterministic refusals, or any\n * error that says so with `retryable: false` (the transports' 4xx answers).\n */\nexport function isRetryable(error: unknown): boolean {\n if (!(error instanceof Error)) return true;\n if (DETERMINISTIC.has(error.name)) return false;\n return (error as { retryable?: unknown }).retryable !== false;\n}\n\n/** An HTTP answer a retry can change: a timeout, throttling, or the provider's own fault. */\nexport function isRetryableStatus(status: number): boolean {\n return status === 408 || status === 429 || status >= 500;\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,IAAa,iBAAb,cAAoC,MAAM;CACxC,OAAiC;CACjC,YAAqB;CACrB,YAAY,SAAiB,SAA+B;EAC1D,MAAM,SAAS,OAAO;CACxB;AACF;;AAGA,MAAM,gCAAgB,IAAI,IAAI,CAAC,2BAA2B,yBAAyB,CAAC;;;;;AAMpF,SAAgB,YAAY,OAAyB;CACnD,IAAI,EAAE,iBAAiB,QAAQ,OAAO;CACtC,IAAI,cAAc,IAAI,MAAM,IAAI,GAAG,OAAO;CAC1C,OAAQ,MAAkC,cAAc;AAC1D;;AAGA,SAAgB,kBAAkB,QAAyB;CACzD,OAAO,WAAW,OAAO,WAAW,OAAO,UAAU;AACvD"}
@@ -6,6 +6,18 @@
6
6
  * instead of burning the retry budget and delaying the journey. Every runtime adapter asks this
7
7
  * one rule.
8
8
  */
9
+ /**
10
+ * A failure a retry would only repeat: a missing or malformed configuration, a provider refusing
11
+ * the request itself, an input the runtime cannot act on. Thrown inside a durable step, it ends
12
+ * the step at once (`isRetryable` is false) instead of burning the retry budget.
13
+ */
14
+ export declare class PermanentError extends Error {
15
+ readonly name: string;
16
+ readonly retryable = false;
17
+ constructor(message: string, options?: {
18
+ cause?: unknown;
19
+ });
20
+ }
9
21
  /**
10
22
  * False for an error a retry cannot fix: one of the engine's deterministic refusals, or any
11
23
  * error that says so with `retryable: false` (the transports' 4xx answers).
@@ -1 +1 @@
1
- {"version":3,"file":"retry.d.cts","names":[],"sources":["../../src/engine/retry.ts"],"mappings":";;;;;;;;;;;;wBAegB,YAAY;;wBAOZ,kBAAkB"}
1
+ {"version":3,"file":"retry.d.cts","names":[],"sources":["../../src/engine/retry.ts"],"mappings":";;;;;;;;;;;;;qBAaa,uBAAuB;WAChB;WACT;EACT,YAAY,iBAAiB;IAAY;;;;;;;wBAY3B,YAAY;;wBAOZ,kBAAkB"}
@@ -6,6 +6,18 @@
6
6
  * instead of burning the retry budget and delaying the journey. Every runtime adapter asks this
7
7
  * one rule.
8
8
  */
9
+ /**
10
+ * A failure a retry would only repeat: a missing or malformed configuration, a provider refusing
11
+ * the request itself, an input the runtime cannot act on. Thrown inside a durable step, it ends
12
+ * the step at once (`isRetryable` is false) instead of burning the retry budget.
13
+ */
14
+ export declare class PermanentError extends Error {
15
+ readonly name: string;
16
+ readonly retryable = false;
17
+ constructor(message: string, options?: {
18
+ cause?: unknown;
19
+ });
20
+ }
9
21
  /**
10
22
  * False for an error a retry cannot fix: one of the engine's deterministic refusals, or any
11
23
  * error that says so with `retryable: false` (the transports' 4xx answers).
@@ -1 +1 @@
1
- {"version":3,"file":"retry.d.mts","names":[],"sources":["../../src/engine/retry.ts"],"mappings":";;;;;;;;;;;;wBAegB,YAAY;;wBAOZ,kBAAkB"}
1
+ {"version":3,"file":"retry.d.mts","names":[],"sources":["../../src/engine/retry.ts"],"mappings":";;;;;;;;;;;;;qBAaa,uBAAuB;WAChB;WACT;EACT,YAAY,iBAAiB;IAAY;;;;;;;wBAY3B,YAAY;;wBAOZ,kBAAkB"}
@@ -6,6 +6,18 @@
6
6
  * instead of burning the retry budget and delaying the journey. Every runtime adapter asks this
7
7
  * one rule.
8
8
  */
9
+ /**
10
+ * A failure a retry would only repeat: a missing or malformed configuration, a provider refusing
11
+ * the request itself, an input the runtime cannot act on. Thrown inside a durable step, it ends
12
+ * the step at once (`isRetryable` is false) instead of burning the retry budget.
13
+ */
14
+ var PermanentError = class extends Error {
15
+ name = "PermanentError";
16
+ retryable = false;
17
+ constructor(message, options) {
18
+ super(message, options);
19
+ }
20
+ };
9
21
  /** Errors the engine raises that are deterministic by construction. */
10
22
  const DETERMINISTIC = /* @__PURE__ */ new Set(["TemplateValidationError", "ContractValidationError"]);
11
23
  /**
@@ -22,6 +34,6 @@ function isRetryableStatus(status) {
22
34
  return status === 408 || status === 429 || status >= 500;
23
35
  }
24
36
  //#endregion
25
- export { isRetryable, isRetryableStatus };
37
+ export { PermanentError, isRetryable, isRetryableStatus };
26
38
 
27
39
  //# sourceMappingURL=retry.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"retry.mjs","names":[],"sources":["../../src/engine/retry.ts"],"sourcesContent":["/**\n * Which step failures a retry would only repeat. A durable step retries what it throws; an\n * error that is the same on every attempt — a template refusing its props, the contract\n * refusing an emitted event, a provider rejecting the request itself — ends the step at once\n * instead of burning the retry budget and delaying the journey. Every runtime adapter asks this\n * one rule.\n */\n\n/** Errors the engine raises that are deterministic by construction. */\nconst DETERMINISTIC = new Set(['TemplateValidationError', 'ContractValidationError']);\n\n/**\n * False for an error a retry cannot fix: one of the engine's deterministic refusals, or any\n * error that says so with `retryable: false` (the transports' 4xx answers).\n */\nexport function isRetryable(error: unknown): boolean {\n if (!(error instanceof Error)) return true;\n if (DETERMINISTIC.has(error.name)) return false;\n return (error as { retryable?: unknown }).retryable !== false;\n}\n\n/** An HTTP answer a retry can change: a timeout, throttling, or the provider's own fault. */\nexport function isRetryableStatus(status: number): boolean {\n return status === 408 || status === 429 || status >= 500;\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,gCAAgB,IAAI,IAAI,CAAC,2BAA2B,yBAAyB,CAAC;;;;;AAMpF,SAAgB,YAAY,OAAyB;CACnD,IAAI,EAAE,iBAAiB,QAAQ,OAAO;CACtC,IAAI,cAAc,IAAI,MAAM,IAAI,GAAG,OAAO;CAC1C,OAAQ,MAAkC,cAAc;AAC1D;;AAGA,SAAgB,kBAAkB,QAAyB;CACzD,OAAO,WAAW,OAAO,WAAW,OAAO,UAAU;AACvD"}
1
+ {"version":3,"file":"retry.mjs","names":[],"sources":["../../src/engine/retry.ts"],"sourcesContent":["/**\n * Which step failures a retry would only repeat. A durable step retries what it throws; an\n * error that is the same on every attempt — a template refusing its props, the contract\n * refusing an emitted event, a provider rejecting the request itself — ends the step at once\n * instead of burning the retry budget and delaying the journey. Every runtime adapter asks this\n * one rule.\n */\n\n/**\n * A failure a retry would only repeat: a missing or malformed configuration, a provider refusing\n * the request itself, an input the runtime cannot act on. Thrown inside a durable step, it ends\n * the step at once (`isRetryable` is false) instead of burning the retry budget.\n */\nexport class PermanentError extends Error {\n override readonly name: string = 'PermanentError';\n readonly retryable = false;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n }\n}\n\n/** Errors the engine raises that are deterministic by construction. */\nconst DETERMINISTIC = new Set(['TemplateValidationError', 'ContractValidationError']);\n\n/**\n * False for an error a retry cannot fix: one of the engine's deterministic refusals, or any\n * error that says so with `retryable: false` (the transports' 4xx answers).\n */\nexport function isRetryable(error: unknown): boolean {\n if (!(error instanceof Error)) return true;\n if (DETERMINISTIC.has(error.name)) return false;\n return (error as { retryable?: unknown }).retryable !== false;\n}\n\n/** An HTTP answer a retry can change: a timeout, throttling, or the provider's own fault. */\nexport function isRetryableStatus(status: number): boolean {\n return status === 408 || status === 429 || status >= 500;\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAa,iBAAb,cAAoC,MAAM;CACxC,OAAiC;CACjC,YAAqB;CACrB,YAAY,SAAiB,SAA+B;EAC1D,MAAM,SAAS,OAAO;CACxB;AACF;;AAGA,MAAM,gCAAgB,IAAI,IAAI,CAAC,2BAA2B,yBAAyB,CAAC;;;;;AAMpF,SAAgB,YAAY,OAAyB;CACnD,IAAI,EAAE,iBAAiB,QAAQ,OAAO;CACtC,IAAI,cAAc,IAAI,MAAM,IAAI,GAAG,OAAO;CAC1C,OAAQ,MAAkC,cAAc;AAC1D;;AAGA,SAAgB,kBAAkB,QAAyB;CACzD,OAAO,WAAW,OAAO,WAAW,OAAO,UAAU;AACvD"}
@@ -13,25 +13,96 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
13
13
  * live in the store rather than the workflow body; `scaleSegmentWindows`
14
14
  * scales them as they are read, so a segment's `within` keeps pace too.
15
15
  *
16
- * Durations are recognised structurally — an object whose only keys are
17
- * `value` (the source text) and `ms` — which is the DurationIR shape and
18
- * nothing else in the IR. The text is kept so the studio still shows what was
19
- * written.
16
+ * The walk follows the IR's own structure — the fields that hold a Duration
17
+ * are known — so a message prop or an event payload that happens to look like
18
+ * one (`{ value, ms }`) is left alone. The source text is kept so the studio
19
+ * still shows what was written.
20
20
  */
21
21
  function scaleDurations(ir, factor) {
22
22
  if (!(factor > 0) || factor === 1) return ir;
23
- return walk(ir, factor);
23
+ return {
24
+ ...ir,
25
+ trigger: ir.trigger.type === "event" ? {
26
+ ...ir.trigger,
27
+ ...ir.trigger.filter === void 0 ? {} : { filter: scaleCondition(ir.trigger.filter, factor) }
28
+ } : ir.trigger,
29
+ ...ir.goal === void 0 ? {} : { goal: {
30
+ ...ir.goal,
31
+ condition: scaleCondition(ir.goal.condition, factor),
32
+ ...ir.goal.within === void 0 ? {} : { within: scale(ir.goal.within, factor) }
33
+ } },
34
+ ...ir.exitWhen === void 0 ? {} : { exitWhen: scaleCondition(ir.exitWhen, factor) },
35
+ flow: scaleFlow(ir.flow, factor)
36
+ };
37
+ }
38
+ /** A condition with its `performed(... within)` windows divided by `factor`. */
39
+ function scaleCondition(condition, factor) {
40
+ if (!(factor > 0) || factor === 1) return condition;
41
+ switch (condition.type) {
42
+ case "and":
43
+ case "or": return {
44
+ ...condition,
45
+ conditions: condition.conditions.map((child) => scaleCondition(child, factor))
46
+ };
47
+ case "not": return {
48
+ ...condition,
49
+ condition: scaleCondition(condition.condition, factor)
50
+ };
51
+ case "performed": return condition.within === void 0 ? condition : {
52
+ ...condition,
53
+ within: scale(condition.within, factor)
54
+ };
55
+ default: return condition;
56
+ }
57
+ }
58
+ function scaleFlow(nodes, factor) {
59
+ return nodes.map((node) => scaleNode(node, factor));
60
+ }
61
+ function scaleNode(node, factor) {
62
+ switch (node.type) {
63
+ case "delay": return {
64
+ ...node,
65
+ duration: scale(node.duration, factor)
66
+ };
67
+ case "random_delay": return {
68
+ ...node,
69
+ min: scale(node.min, factor),
70
+ max: scale(node.max, factor)
71
+ };
72
+ case "wait_until": return {
73
+ ...node,
74
+ condition: scaleCondition(node.condition, factor),
75
+ timeout: scale(node.timeout, factor),
76
+ onTimeout: Array.isArray(node.onTimeout) ? scaleFlow(node.onTimeout, factor) : node.onTimeout
77
+ };
78
+ case "branch": return {
79
+ ...node,
80
+ cases: node.cases.map((branch) => ({
81
+ ...branch,
82
+ condition: scaleCondition(branch.condition, factor),
83
+ flow: scaleFlow(branch.flow, factor)
84
+ })),
85
+ ...node.otherwise === void 0 ? {} : { otherwise: scaleFlow(node.otherwise, factor) }
86
+ };
87
+ case "filter": return {
88
+ ...node,
89
+ condition: scaleCondition(node.condition, factor)
90
+ };
91
+ case "cohort": return {
92
+ ...node,
93
+ arms: node.arms.map((arm) => ({
94
+ ...arm,
95
+ flow: scaleFlow(arm.flow, factor)
96
+ }))
97
+ };
98
+ default: return node;
99
+ }
24
100
  }
25
- function walk(value, factor) {
26
- if (Array.isArray(value)) return value.map((item) => walk(item, factor));
27
- if (value === null || typeof value !== "object") return value;
28
- const record = value;
29
- const keys = Object.keys(record);
30
- if (keys.length === 2 && typeof record.value === "string" && typeof record.ms === "number") return {
31
- value: record.value,
32
- ms: Math.max(0, Math.round(record.ms / factor))
101
+ function scale(duration, factor) {
102
+ return {
103
+ value: duration.value,
104
+ ms: Math.max(0, Math.round(duration.ms / factor))
33
105
  };
34
- return Object.fromEntries(keys.map((key) => [key, walk(record[key], factor)]));
35
106
  }
36
107
  /**
37
108
  * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
@@ -42,12 +113,16 @@ function walk(value, factor) {
42
113
  function scaleSegmentWindows(store, factor) {
43
114
  if (!(factor > 0) || factor === 1) return store;
44
115
  return new Proxy(store, { get(target, property) {
45
- if (property === "getSegmentCondition") return async (name) => scaleDurations(await target.getSegmentCondition(name), factor);
116
+ if (property === "getSegmentCondition") return async (name) => {
117
+ const condition = await target.getSegmentCondition(name);
118
+ return condition === void 0 ? void 0 : scaleCondition(condition, factor);
119
+ };
46
120
  const value = Reflect.get(target, property, target);
47
121
  return typeof value === "function" ? value.bind(target) : value;
48
122
  } });
49
123
  }
50
124
  //#endregion
125
+ exports.scaleCondition = scaleCondition;
51
126
  exports.scaleDurations = scaleDurations;
52
127
  exports.scaleSegmentWindows = scaleSegmentWindows;
53
128
 
@@ -1 +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"}
1
+ {"version":3,"file":"time-scale.cjs","names":[],"sources":["../../src/engine/time-scale.ts"],"sourcesContent":["import type { ConditionIR, DurationIR, NodeIR, WorkflowIR } from '../ir';\n\n/**\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 * The walk follows the IR's own structure — the fields that hold a Duration\n * are known — so a message prop or an event payload that happens to look like\n * one (`{ value, ms }`) is left alone. The source text is kept so the studio\n * still shows what was written.\n */\nexport function scaleDurations(ir: WorkflowIR, factor: number): WorkflowIR {\n if (!(factor > 0) || factor === 1) return ir;\n return {\n ...ir,\n trigger:\n ir.trigger.type === 'event'\n ? {\n ...ir.trigger,\n ...(ir.trigger.filter === undefined\n ? {}\n : { filter: scaleCondition(ir.trigger.filter, factor) }),\n }\n : ir.trigger,\n ...(ir.goal === undefined\n ? {}\n : {\n goal: {\n ...ir.goal,\n condition: scaleCondition(ir.goal.condition, factor),\n ...(ir.goal.within === undefined ? {} : { within: scale(ir.goal.within, factor) }),\n },\n }),\n ...(ir.exitWhen === undefined ? {} : { exitWhen: scaleCondition(ir.exitWhen, factor) }),\n flow: scaleFlow(ir.flow, factor),\n };\n}\n\n/** A condition with its `performed(... within)` windows divided by `factor`. */\nexport function scaleCondition(condition: ConditionIR, factor: number): ConditionIR {\n if (!(factor > 0) || factor === 1) return condition;\n switch (condition.type) {\n case 'and':\n case 'or':\n return {\n ...condition,\n conditions: condition.conditions.map((child) => scaleCondition(child, factor)),\n };\n case 'not':\n return { ...condition, condition: scaleCondition(condition.condition, factor) };\n case 'performed':\n return condition.within === undefined\n ? condition\n : { ...condition, within: scale(condition.within, factor) };\n default:\n return condition;\n }\n}\n\nfunction scaleFlow(nodes: readonly NodeIR[], factor: number): NodeIR[] {\n return nodes.map((node) => scaleNode(node, factor));\n}\n\nfunction scaleNode(node: NodeIR, factor: number): NodeIR {\n switch (node.type) {\n case 'delay':\n return { ...node, duration: scale(node.duration, factor) };\n case 'random_delay':\n return { ...node, min: scale(node.min, factor), max: scale(node.max, factor) };\n case 'wait_until':\n return {\n ...node,\n condition: scaleCondition(node.condition, factor),\n timeout: scale(node.timeout, factor),\n onTimeout: Array.isArray(node.onTimeout)\n ? scaleFlow(node.onTimeout, factor)\n : node.onTimeout,\n };\n case 'branch':\n return {\n ...node,\n cases: node.cases.map((branch) => ({\n ...branch,\n condition: scaleCondition(branch.condition, factor),\n flow: scaleFlow(branch.flow, factor),\n })),\n ...(node.otherwise === undefined ? {} : { otherwise: scaleFlow(node.otherwise, factor) }),\n };\n case 'filter':\n return { ...node, condition: scaleCondition(node.condition, factor) };\n case 'cohort':\n return {\n ...node,\n arms: node.arms.map((arm) => ({ ...arm, flow: scaleFlow(arm.flow, factor) })),\n };\n default:\n return node;\n }\n}\n\nfunction scale(duration: DurationIR, factor: number): DurationIR {\n return { value: duration.value, ms: Math.max(0, Math.round(duration.ms / 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<ConditionIR | undefined> },\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 const condition = await target.getSegmentCondition(name);\n return condition === undefined ? undefined : scaleCondition(condition, factor);\n };\n }\n const value: unknown = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { ConditionIR, WorkflowIR } from "../ir.cjs";
1
2
  //#region src/engine/time-scale.d.ts
2
3
  /**
3
4
  * Debug time scale: divide every duration in a workflow's IR by a factor so a
@@ -12,12 +13,14 @@
12
13
  * live in the store rather than the workflow body; `scaleSegmentWindows`
13
14
  * scales them as they are read, so a segment's `within` keeps pace too.
14
15
  *
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.
16
+ * The walk follows the IR's own structure — the fields that hold a Duration
17
+ * are known — so a message prop or an event payload that happens to look like
18
+ * one (`{ value, ms }`) is left alone. The source text is kept so the studio
19
+ * still shows what was written.
19
20
  */
20
- export declare function scaleDurations<T>(ir: T, factor: number): T;
21
+ export declare function scaleDurations(ir: WorkflowIR, factor: number): WorkflowIR;
22
+ /** A condition with its `performed(... within)` windows divided by `factor`. */
23
+ export declare function scaleCondition(condition: ConditionIR, factor: number): ConditionIR;
21
24
  /**
22
25
  * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
23
26
  * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)
@@ -25,7 +28,7 @@ export declare function scaleDurations<T>(ir: T, factor: number): T;
25
28
  * other method is the store's own.
26
29
  */
27
30
  export declare function scaleSegmentWindows<S extends {
28
- getSegmentCondition(name: string): Promise<unknown>;
31
+ getSegmentCondition(name: string): Promise<ConditionIR | undefined>;
29
32
  }>(store: S, factor: number): S;
30
33
  //#endregion
31
34
  //# sourceMappingURL=time-scale.d.cts.map
@@ -1 +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"}
1
+ {"version":3,"file":"time-scale.d.cts","names":[],"sources":["../../src/engine/time-scale.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;wBAoBgB,eAAe,IAAI,YAAY,iBAAiB;;wBA4BhD,eAAe,WAAW,aAAa,iBAAiB;;;;;;;wBAuExD,oBACd;EAAY,oBAAoB,eAAe,QAAQ;GACvD,OAAO,GAAG,iBAAiB"}
@@ -1,3 +1,4 @@
1
+ import { ConditionIR, WorkflowIR } from "../ir.mjs";
1
2
  //#region src/engine/time-scale.d.ts
2
3
  /**
3
4
  * Debug time scale: divide every duration in a workflow's IR by a factor so a
@@ -12,12 +13,14 @@
12
13
  * live in the store rather than the workflow body; `scaleSegmentWindows`
13
14
  * scales them as they are read, so a segment's `within` keeps pace too.
14
15
  *
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.
16
+ * The walk follows the IR's own structure — the fields that hold a Duration
17
+ * are known — so a message prop or an event payload that happens to look like
18
+ * one (`{ value, ms }`) is left alone. The source text is kept so the studio
19
+ * still shows what was written.
19
20
  */
20
- export declare function scaleDurations<T>(ir: T, factor: number): T;
21
+ export declare function scaleDurations(ir: WorkflowIR, factor: number): WorkflowIR;
22
+ /** A condition with its `performed(... within)` windows divided by `factor`. */
23
+ export declare function scaleCondition(condition: ConditionIR, factor: number): ConditionIR;
21
24
  /**
22
25
  * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
23
26
  * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)
@@ -25,7 +28,7 @@ export declare function scaleDurations<T>(ir: T, factor: number): T;
25
28
  * other method is the store's own.
26
29
  */
27
30
  export declare function scaleSegmentWindows<S extends {
28
- getSegmentCondition(name: string): Promise<unknown>;
31
+ getSegmentCondition(name: string): Promise<ConditionIR | undefined>;
29
32
  }>(store: S, factor: number): S;
30
33
  //#endregion
31
34
  //# sourceMappingURL=time-scale.d.mts.map
@@ -1 +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"}
1
+ {"version":3,"file":"time-scale.d.mts","names":[],"sources":["../../src/engine/time-scale.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;wBAoBgB,eAAe,IAAI,YAAY,iBAAiB;;wBA4BhD,eAAe,WAAW,aAAa,iBAAiB;;;;;;;wBAuExD,oBACd;EAAY,oBAAoB,eAAe,QAAQ;GACvD,OAAO,GAAG,iBAAiB"}
@@ -12,25 +12,96 @@
12
12
  * live in the store rather than the workflow body; `scaleSegmentWindows`
13
13
  * scales them as they are read, so a segment's `within` keeps pace too.
14
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.
15
+ * The walk follows the IR's own structure — the fields that hold a Duration
16
+ * are known — so a message prop or an event payload that happens to look like
17
+ * one (`{ value, ms }`) is left alone. The source text is kept so the studio
18
+ * still shows what was written.
19
19
  */
20
20
  function scaleDurations(ir, factor) {
21
21
  if (!(factor > 0) || factor === 1) return ir;
22
- return walk(ir, factor);
22
+ return {
23
+ ...ir,
24
+ trigger: ir.trigger.type === "event" ? {
25
+ ...ir.trigger,
26
+ ...ir.trigger.filter === void 0 ? {} : { filter: scaleCondition(ir.trigger.filter, factor) }
27
+ } : ir.trigger,
28
+ ...ir.goal === void 0 ? {} : { goal: {
29
+ ...ir.goal,
30
+ condition: scaleCondition(ir.goal.condition, factor),
31
+ ...ir.goal.within === void 0 ? {} : { within: scale(ir.goal.within, factor) }
32
+ } },
33
+ ...ir.exitWhen === void 0 ? {} : { exitWhen: scaleCondition(ir.exitWhen, factor) },
34
+ flow: scaleFlow(ir.flow, factor)
35
+ };
36
+ }
37
+ /** A condition with its `performed(... within)` windows divided by `factor`. */
38
+ function scaleCondition(condition, factor) {
39
+ if (!(factor > 0) || factor === 1) return condition;
40
+ switch (condition.type) {
41
+ case "and":
42
+ case "or": return {
43
+ ...condition,
44
+ conditions: condition.conditions.map((child) => scaleCondition(child, factor))
45
+ };
46
+ case "not": return {
47
+ ...condition,
48
+ condition: scaleCondition(condition.condition, factor)
49
+ };
50
+ case "performed": return condition.within === void 0 ? condition : {
51
+ ...condition,
52
+ within: scale(condition.within, factor)
53
+ };
54
+ default: return condition;
55
+ }
56
+ }
57
+ function scaleFlow(nodes, factor) {
58
+ return nodes.map((node) => scaleNode(node, factor));
59
+ }
60
+ function scaleNode(node, factor) {
61
+ switch (node.type) {
62
+ case "delay": return {
63
+ ...node,
64
+ duration: scale(node.duration, factor)
65
+ };
66
+ case "random_delay": return {
67
+ ...node,
68
+ min: scale(node.min, factor),
69
+ max: scale(node.max, factor)
70
+ };
71
+ case "wait_until": return {
72
+ ...node,
73
+ condition: scaleCondition(node.condition, factor),
74
+ timeout: scale(node.timeout, factor),
75
+ onTimeout: Array.isArray(node.onTimeout) ? scaleFlow(node.onTimeout, factor) : node.onTimeout
76
+ };
77
+ case "branch": return {
78
+ ...node,
79
+ cases: node.cases.map((branch) => ({
80
+ ...branch,
81
+ condition: scaleCondition(branch.condition, factor),
82
+ flow: scaleFlow(branch.flow, factor)
83
+ })),
84
+ ...node.otherwise === void 0 ? {} : { otherwise: scaleFlow(node.otherwise, factor) }
85
+ };
86
+ case "filter": return {
87
+ ...node,
88
+ condition: scaleCondition(node.condition, factor)
89
+ };
90
+ case "cohort": return {
91
+ ...node,
92
+ arms: node.arms.map((arm) => ({
93
+ ...arm,
94
+ flow: scaleFlow(arm.flow, factor)
95
+ }))
96
+ };
97
+ default: return node;
98
+ }
23
99
  }
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))
100
+ function scale(duration, factor) {
101
+ return {
102
+ value: duration.value,
103
+ ms: Math.max(0, Math.round(duration.ms / factor))
32
104
  };
33
- return Object.fromEntries(keys.map((key) => [key, walk(record[key], factor)]));
34
105
  }
35
106
  /**
36
107
  * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
@@ -41,12 +112,15 @@ function walk(value, factor) {
41
112
  function scaleSegmentWindows(store, factor) {
42
113
  if (!(factor > 0) || factor === 1) return store;
43
114
  return new Proxy(store, { get(target, property) {
44
- if (property === "getSegmentCondition") return async (name) => scaleDurations(await target.getSegmentCondition(name), factor);
115
+ if (property === "getSegmentCondition") return async (name) => {
116
+ const condition = await target.getSegmentCondition(name);
117
+ return condition === void 0 ? void 0 : scaleCondition(condition, factor);
118
+ };
45
119
  const value = Reflect.get(target, property, target);
46
120
  return typeof value === "function" ? value.bind(target) : value;
47
121
  } });
48
122
  }
49
123
  //#endregion
50
- export { scaleDurations, scaleSegmentWindows };
124
+ export { scaleCondition, scaleDurations, scaleSegmentWindows };
51
125
 
52
126
  //# sourceMappingURL=time-scale.mjs.map
@@ -1 +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"}
1
+ {"version":3,"file":"time-scale.mjs","names":[],"sources":["../../src/engine/time-scale.ts"],"sourcesContent":["import type { ConditionIR, DurationIR, NodeIR, WorkflowIR } from '../ir';\n\n/**\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 * The walk follows the IR's own structure — the fields that hold a Duration\n * are known — so a message prop or an event payload that happens to look like\n * one (`{ value, ms }`) is left alone. The source text is kept so the studio\n * still shows what was written.\n */\nexport function scaleDurations(ir: WorkflowIR, factor: number): WorkflowIR {\n if (!(factor > 0) || factor === 1) return ir;\n return {\n ...ir,\n trigger:\n ir.trigger.type === 'event'\n ? {\n ...ir.trigger,\n ...(ir.trigger.filter === undefined\n ? {}\n : { filter: scaleCondition(ir.trigger.filter, factor) }),\n }\n : ir.trigger,\n ...(ir.goal === undefined\n ? {}\n : {\n goal: {\n ...ir.goal,\n condition: scaleCondition(ir.goal.condition, factor),\n ...(ir.goal.within === undefined ? {} : { within: scale(ir.goal.within, factor) }),\n },\n }),\n ...(ir.exitWhen === undefined ? {} : { exitWhen: scaleCondition(ir.exitWhen, factor) }),\n flow: scaleFlow(ir.flow, factor),\n };\n}\n\n/** A condition with its `performed(... within)` windows divided by `factor`. */\nexport function scaleCondition(condition: ConditionIR, factor: number): ConditionIR {\n if (!(factor > 0) || factor === 1) return condition;\n switch (condition.type) {\n case 'and':\n case 'or':\n return {\n ...condition,\n conditions: condition.conditions.map((child) => scaleCondition(child, factor)),\n };\n case 'not':\n return { ...condition, condition: scaleCondition(condition.condition, factor) };\n case 'performed':\n return condition.within === undefined\n ? condition\n : { ...condition, within: scale(condition.within, factor) };\n default:\n return condition;\n }\n}\n\nfunction scaleFlow(nodes: readonly NodeIR[], factor: number): NodeIR[] {\n return nodes.map((node) => scaleNode(node, factor));\n}\n\nfunction scaleNode(node: NodeIR, factor: number): NodeIR {\n switch (node.type) {\n case 'delay':\n return { ...node, duration: scale(node.duration, factor) };\n case 'random_delay':\n return { ...node, min: scale(node.min, factor), max: scale(node.max, factor) };\n case 'wait_until':\n return {\n ...node,\n condition: scaleCondition(node.condition, factor),\n timeout: scale(node.timeout, factor),\n onTimeout: Array.isArray(node.onTimeout)\n ? scaleFlow(node.onTimeout, factor)\n : node.onTimeout,\n };\n case 'branch':\n return {\n ...node,\n cases: node.cases.map((branch) => ({\n ...branch,\n condition: scaleCondition(branch.condition, factor),\n flow: scaleFlow(branch.flow, factor),\n })),\n ...(node.otherwise === undefined ? {} : { otherwise: scaleFlow(node.otherwise, factor) }),\n };\n case 'filter':\n return { ...node, condition: scaleCondition(node.condition, factor) };\n case 'cohort':\n return {\n ...node,\n arms: node.arms.map((arm) => ({ ...arm, flow: scaleFlow(arm.flow, factor) })),\n };\n default:\n return node;\n }\n}\n\nfunction scale(duration: DurationIR, factor: number): DurationIR {\n return { value: duration.value, ms: Math.max(0, Math.round(duration.ms / 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<ConditionIR | undefined> },\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 const condition = await target.getSegmentCondition(name);\n return condition === undefined ? undefined : scaleCondition(condition, factor);\n };\n }\n const value: unknown = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n"],"mappings":"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"}