@demlik/tea 0.2.0 → 0.4.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 (196) hide show
  1. package/README.md +49 -0
  2. package/dist/agent/index.d.ts +16 -770
  3. package/dist/agent/index.js +18 -16
  4. package/dist/authed-call/index.d.ts +3 -3
  5. package/dist/authed-call/index.js +22 -36
  6. package/dist/authed-call/index.js.map +1 -1
  7. package/dist/await-terminal/index.d.ts +2 -2
  8. package/dist/await-terminal/index.js +2 -2
  9. package/dist/batch-window/index.d.ts +3 -3
  10. package/dist/batch-window/index.js +3 -3
  11. package/dist/cache/index.d.ts +2 -2
  12. package/dist/cache/index.js +5 -2
  13. package/dist/{chunk-3545BOYL.js → chunk-25LBDCBC.js} +6 -5
  14. package/dist/chunk-25LBDCBC.js.map +1 -0
  15. package/dist/{chunk-IK4S5DJN.js → chunk-2AHAR7CX.js} +7 -14
  16. package/dist/chunk-2AHAR7CX.js.map +1 -0
  17. package/dist/{chunk-IN33Q4VJ.js → chunk-2KNLKQVN.js} +3 -3
  18. package/dist/{chunk-IN33Q4VJ.js.map → chunk-2KNLKQVN.js.map} +1 -1
  19. package/dist/{chunk-VFGKTGGC.js → chunk-5W7CNAHB.js} +191 -178
  20. package/dist/chunk-5W7CNAHB.js.map +1 -0
  21. package/dist/{chunk-6GIGQT6C.js → chunk-B5RAZVXW.js} +12 -11
  22. package/dist/chunk-B5RAZVXW.js.map +1 -0
  23. package/dist/{chunk-6VDCTR7N.js → chunk-D4NJBQAH.js} +258 -151
  24. package/dist/chunk-D4NJBQAH.js.map +1 -0
  25. package/dist/{chunk-UV6UWGEO.js → chunk-D736QUGF.js} +6 -5
  26. package/dist/chunk-D736QUGF.js.map +1 -0
  27. package/dist/{chunk-EGWTT5XJ.js → chunk-ET6QQNSQ.js} +76 -95
  28. package/dist/chunk-ET6QQNSQ.js.map +1 -0
  29. package/dist/chunk-EWJP3YYP.js +21 -0
  30. package/dist/chunk-EWJP3YYP.js.map +1 -0
  31. package/dist/{chunk-M6UZT7HS.js → chunk-FCKP6N7Y.js} +6 -5
  32. package/dist/chunk-FCKP6N7Y.js.map +1 -0
  33. package/dist/{chunk-TGG26XMK.js → chunk-FV3Z5MBE.js} +10 -5
  34. package/dist/chunk-FV3Z5MBE.js.map +1 -0
  35. package/dist/chunk-G5E74IJB.js +8 -0
  36. package/dist/chunk-G5E74IJB.js.map +1 -0
  37. package/dist/{chunk-G2U7DJHL.js → chunk-JFFVBEDI.js} +3 -3
  38. package/dist/{chunk-G2U7DJHL.js.map → chunk-JFFVBEDI.js.map} +1 -1
  39. package/dist/{chunk-FOGL3TC7.js → chunk-JXYYRKD6.js} +8 -10
  40. package/dist/chunk-JXYYRKD6.js.map +1 -0
  41. package/dist/{chunk-ZDRN3YCE.js → chunk-LJJV6ZD5.js} +17 -24
  42. package/dist/chunk-LJJV6ZD5.js.map +1 -0
  43. package/dist/chunk-LYLDCIPH.js +365 -0
  44. package/dist/chunk-LYLDCIPH.js.map +1 -0
  45. package/dist/{chunk-HJO6VNQI.js → chunk-MU6MCIAC.js} +3 -3
  46. package/dist/{chunk-HJO6VNQI.js.map → chunk-MU6MCIAC.js.map} +1 -1
  47. package/dist/{chunk-UYWUVEP6.js → chunk-MUUROGIH.js} +2 -2
  48. package/dist/chunk-MUUROGIH.js.map +1 -0
  49. package/dist/{chunk-DYRKDCJI.js → chunk-PBDYW2TU.js} +41 -7
  50. package/dist/chunk-PBDYW2TU.js.map +1 -0
  51. package/dist/{chunk-5VNWZADP.js → chunk-Q2UM7BXU.js} +18 -7
  52. package/dist/chunk-Q2UM7BXU.js.map +1 -0
  53. package/dist/chunk-RMX4EOQK.js +26 -0
  54. package/dist/chunk-RMX4EOQK.js.map +1 -0
  55. package/dist/{chunk-2YOE5PIS.js → chunk-S2EGRPCM.js} +8 -6
  56. package/dist/chunk-S2EGRPCM.js.map +1 -0
  57. package/dist/{chunk-5KVHID6A.js → chunk-SPWVPIFQ.js} +4 -4
  58. package/dist/{chunk-5KVHID6A.js.map → chunk-SPWVPIFQ.js.map} +1 -1
  59. package/dist/chunk-SSHDBOXF.js +71 -0
  60. package/dist/chunk-SSHDBOXF.js.map +1 -0
  61. package/dist/{chunk-6OUDPSSG.js → chunk-TWDHNIY6.js} +3 -3
  62. package/dist/{chunk-6OUDPSSG.js.map → chunk-TWDHNIY6.js.map} +1 -1
  63. package/dist/{chunk-SC3FAFZN.js → chunk-UF4MZSLT.js} +2 -2
  64. package/dist/chunk-UF4MZSLT.js.map +1 -0
  65. package/dist/chunk-ZJ5AOYBH.js +8 -0
  66. package/dist/chunk-ZJ5AOYBH.js.map +1 -0
  67. package/dist/{chunk-LEC4U6CX.js → chunk-ZVJZ72HY.js} +3 -3
  68. package/dist/{chunk-LEC4U6CX.js.map → chunk-ZVJZ72HY.js.map} +1 -1
  69. package/dist/circuit-breaker/index.d.ts +1 -0
  70. package/dist/circuit-breaker/index.js +1 -1
  71. package/dist/{core-BmlJOvxM.d.ts → core-42kapW0n.d.ts} +96 -6
  72. package/dist/deadline/index.d.ts +77 -11
  73. package/dist/deadline/index.js +3 -3
  74. package/dist/devtools/index.js +8 -28
  75. package/dist/devtools/index.js.map +1 -1
  76. package/dist/do/index.d.ts +52 -24
  77. package/dist/do/index.js +94 -92
  78. package/dist/do/index.js.map +1 -1
  79. package/dist/{durable-effects-9LxFpmh3.d.ts → durable-effects-CFRngTNA.d.ts} +3 -3
  80. package/dist/extension/index.d.ts +25 -7
  81. package/dist/extension/index.js +4 -3
  82. package/dist/extension/index.js.map +1 -1
  83. package/dist/extension/react.js +4 -3
  84. package/dist/extension/react.js.map +1 -1
  85. package/dist/extension/subs/index.d.ts +2 -2
  86. package/dist/extension/subs/index.js +7 -10
  87. package/dist/extension/subs/index.js.map +1 -1
  88. package/dist/fan-out/index.d.ts +10 -10
  89. package/dist/fan-out/index.js +3 -3
  90. package/dist/idempotent-intake/index.d.ts +2 -2
  91. package/dist/idempotent-intake/index.js +3 -3
  92. package/dist/index.d.ts +50 -489
  93. package/dist/index.js +2 -2
  94. package/dist/llm-call/index.d.ts +3 -2
  95. package/dist/llm-call/index.js +12 -10
  96. package/dist/machine-B_BF4jij.d.ts +804 -0
  97. package/dist/machine-viz/index.d.ts +1 -1
  98. package/dist/machine-viz/index.js +2 -2
  99. package/dist/mem/index.d.ts +3 -2
  100. package/dist/mem/index.js.map +1 -1
  101. package/dist/monitored-run/index.d.ts +54 -30
  102. package/dist/monitored-run/index.js +7 -7
  103. package/dist/node/index.d.ts +3 -2
  104. package/dist/node/index.js +2 -3
  105. package/dist/node/index.js.map +1 -1
  106. package/dist/paginated-walk/index.d.ts +2 -2
  107. package/dist/paginated-walk/index.js +11 -10
  108. package/dist/parity/index.d.ts +6 -5
  109. package/dist/parity/index.js +5 -4
  110. package/dist/parity/index.js.map +1 -1
  111. package/dist/pbt/arbitraries/index.d.ts +1 -1
  112. package/dist/pbt/index.d.ts +1 -1
  113. package/dist/pbt/index.js +3 -3
  114. package/dist/pbt/index.js.map +1 -1
  115. package/dist/pbt/runners/index.d.ts +1 -1
  116. package/dist/pbt/runners/index.js +3 -3
  117. package/dist/poller/index.d.ts +59 -17
  118. package/dist/poller/index.js +56 -20
  119. package/dist/poller/index.js.map +1 -1
  120. package/dist/prediction/index.d.ts +1 -1
  121. package/dist/prediction/index.js +2 -2
  122. package/dist/pure/index.d.ts +1 -1
  123. package/dist/pure/index.js +2 -2
  124. package/dist/rate-limit/index.d.ts +2 -2
  125. package/dist/rate-limit/index.js +1 -1
  126. package/dist/react/index.d.ts +10 -3
  127. package/dist/react/index.js +2 -2
  128. package/dist/react/index.js.map +1 -1
  129. package/dist/reconciler/index.d.ts +2 -2
  130. package/dist/reconciler/index.js +13 -15
  131. package/dist/reconciler/index.js.map +1 -1
  132. package/dist/recorder/index.d.ts +20 -20
  133. package/dist/recorder/index.js +1 -1
  134. package/dist/resilient-call/index.d.ts +31 -6
  135. package/dist/resilient-call/index.js +10 -9
  136. package/dist/retry-backoff/index.d.ts +191 -14
  137. package/dist/retry-backoff/index.js +1 -1
  138. package/dist/retry-to-success/index.js +1 -1
  139. package/dist/runtime-types-CukV8kMX.d.ts +406 -0
  140. package/dist/saga/index.d.ts +3 -2
  141. package/dist/saga/index.js +2 -2
  142. package/dist/saga/index.js.map +1 -1
  143. package/dist/snapshot/index.d.ts +1 -1
  144. package/dist/snapshot/index.js +3 -3
  145. package/dist/subs/index.d.ts +372 -4
  146. package/dist/subs/index.js +4 -1
  147. package/dist/testing/index.d.ts +2 -2
  148. package/dist/testing/index.js +2 -2
  149. package/dist/throttled-input/index.d.ts +2 -2
  150. package/dist/throttled-input/index.js +14 -13
  151. package/dist/throttled-input/index.js.map +1 -1
  152. package/dist/token-refresh/index.d.ts +1 -1
  153. package/dist/token-refresh/index.js +3 -3
  154. package/dist/trace-replay/index.d.ts +12 -7
  155. package/dist/trace-replay/index.js +4 -3
  156. package/dist/{types-BoUWgwSm.d.ts → types-KbL3W1mj.d.ts} +1 -1
  157. package/dist/with-deadline/index.d.ts +1 -1
  158. package/dist/with-deadline/index.js +2 -2
  159. package/dist/with-resilience/index.d.ts +21 -2
  160. package/dist/with-resilience/index.js +12 -23
  161. package/dist/with-resilience/index.js.map +1 -1
  162. package/dist/with-telemetry/index.d.ts +1 -1
  163. package/dist/with-telemetry/index.js +2 -2
  164. package/dist/work-queue/adapter.d.ts +2 -2
  165. package/dist/work-queue/adapter.js +2 -2
  166. package/dist/work-queue/index.d.ts +3 -2
  167. package/dist/work-queue/index.js +1 -1
  168. package/dist/work-queue/index.js.map +1 -1
  169. package/dist/work-queue/ops.d.ts +2 -2
  170. package/dist/work-queue/ops.js +1 -1
  171. package/dist/workflow/index.d.ts +51 -19
  172. package/dist/workflow/index.js +71 -52
  173. package/dist/workflow/index.js.map +1 -1
  174. package/package.json +4 -1
  175. package/dist/chunk-2YOE5PIS.js.map +0 -1
  176. package/dist/chunk-3545BOYL.js.map +0 -1
  177. package/dist/chunk-5VNWZADP.js.map +0 -1
  178. package/dist/chunk-6GIGQT6C.js.map +0 -1
  179. package/dist/chunk-6VDCTR7N.js.map +0 -1
  180. package/dist/chunk-DYRKDCJI.js.map +0 -1
  181. package/dist/chunk-EGWTT5XJ.js.map +0 -1
  182. package/dist/chunk-EMJTPHI7.js +0 -91
  183. package/dist/chunk-EMJTPHI7.js.map +0 -1
  184. package/dist/chunk-FOGL3TC7.js.map +0 -1
  185. package/dist/chunk-IK4S5DJN.js.map +0 -1
  186. package/dist/chunk-M6UZT7HS.js.map +0 -1
  187. package/dist/chunk-NSDFA6M6.js +0 -198
  188. package/dist/chunk-NSDFA6M6.js.map +0 -1
  189. package/dist/chunk-SC3FAFZN.js.map +0 -1
  190. package/dist/chunk-SKAMZKTE.js +0 -20
  191. package/dist/chunk-SKAMZKTE.js.map +0 -1
  192. package/dist/chunk-TGG26XMK.js.map +0 -1
  193. package/dist/chunk-UV6UWGEO.js.map +0 -1
  194. package/dist/chunk-UYWUVEP6.js.map +0 -1
  195. package/dist/chunk-VFGKTGGC.js.map +0 -1
  196. package/dist/chunk-ZDRN3YCE.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { C as Cmd, S as Sub, M as Machine } from '../core-BmlJOvxM.js';
1
+ import { C as Cmd, S as Sub, M as Machine } from '../core-42kapW0n.js';
2
2
 
3
3
  /**
4
4
  * @demlik/tea/machine-viz — turn a `Machine` into a Mermaid diagram string.
@@ -1,5 +1,5 @@
1
- import '../chunk-6VDCTR7N.js';
2
- import { formOf } from '../chunk-DYRKDCJI.js';
1
+ import '../chunk-D4NJBQAH.js';
2
+ import { formOf } from '../chunk-PBDYW2TU.js';
3
3
 
4
4
  // src/machine-viz/index.ts
5
5
  function safeId(raw) {
@@ -1,7 +1,8 @@
1
- import { Store } from '../index.js';
2
- import '../core-BmlJOvxM.js';
1
+ import { S as Store } from '../runtime-types-CukV8kMX.js';
2
+ import '../core-42kapW0n.js';
3
3
 
4
4
  /**
5
+ * @packageDocumentation
5
6
  * @demlik/tea/mem — in-memory `Store<S>` adapter for `@demlik/tea`.
6
7
  *
7
8
  * The standard test adapter — used everywhere a Worker or DO would be
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/mem/index.ts"],"names":[],"mappings":";AA0CO,SAAS,WAAA,CACd,OAAA,EACA,KAAA,GAAoC,CAAC,QAAQ,GAAA,EACnC;AAGV,EAAA,IAAI,OAAiB,OAAA,IAAW,IAAA;AAEhC,EAAA,OAAO;AAAA,IACL,MAAM,IAAA,GAAyB;AAC7B,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,KAAK,KAAA,EAAyB;AAClC,MAAA,IAAA,GAAO,KAAA;AAAA,IACT,CAAA;AAAA,IACA,QAAQ,GAAA,EAAwB;AAC9B,MAAA,OAAO,MAAM,GAAG,CAAA;AAAA,IAClB;AAAA,GACF;AACF","file":"index.js","sourcesContent":["/**\n * @demlik/tea/mem — in-memory `Store<S>` adapter for `@demlik/tea`.\n *\n * The standard test adapter — used everywhere a Worker or DO would be\n * overkill. Mirrors the source-only convention from `packages/widget-engine`\n * and `packages/tea` (no build step).\n *\n * Reference semantics, NOT deep-clone semantics:\n * - `save(state)` stores the reference; subsequent mutations to that\n * reference by the caller are observable through later `load()` calls.\n * - `load()` returns the stored reference; mutations made through it are\n * observable on the next `load()`.\n *\n * This matches what a real KV/DO `get()` would NOT do (those serialize +\n * deserialize), but it also matches what an in-memory cache obviously does.\n * Adding a deep clone here would lie about the boundary and impose a JSON\n * round-trip on every test. Tests that need fresh copies should clone at the\n * call site.\n *\n * Boundary parse (invariant 8): `Store<S>` requires `migrate(raw)`. For\n * `memoryStore` we ship a permissive identity-default — `(raw) => raw as\n * S | null` — because this adapter is a degenerate same-process boundary:\n * there is no serialization, the reference we read is structurally the\n * reference we wrote. The `as` cast here is the documented exception to the\n * invariant-8 no-`as` rule, scoped to the parameter default. Callers that\n * want strict parsing (e.g. a test that hand-seeds the cell from outside)\n * may pass an explicit `parse` to override.\n */\n\nimport type { Store } from \"../index\";\n\n/**\n * Build an in-memory `Store<S>`.\n *\n * @param initial - optional seed value. `undefined` and explicit `null` both\n * mean \"no seed\"; `load()` resolves with `null` until the first `save()`.\n * A concrete `S` seed is returned by `load()` until overwritten.\n * @param parse - optional boundary parse for the `Store<S>.migrate` contract.\n * Defaults to identity (`(raw) => raw as S | null`) — honest for this\n * adapter's degenerate same-process boundary. Pass an explicit parser when\n * the test wants to reject seeded shapes that don't match `S`.\n */\nexport function memoryStore<S>(\n initial?: S | null,\n parse: (raw: unknown) => S | null = (raw) => raw as S | null,\n): Store<S> {\n // Single internal cell — `undefined` and `null` collapse to one\n // representation so the load site is branchless.\n let cell: S | null = initial ?? null;\n\n return {\n async load(): Promise<unknown> {\n return cell;\n },\n async save(state: S): Promise<void> {\n cell = state;\n },\n migrate(raw: unknown): S | null {\n return parse(raw);\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/mem/index.ts"],"names":[],"mappings":";AA2CO,SAAS,WAAA,CACd,OAAA,EACA,KAAA,GAAoC,CAAC,QAAQ,GAAA,EACnC;AAGV,EAAA,IAAI,OAAiB,OAAA,IAAW,IAAA;AAEhC,EAAA,OAAO;AAAA,IACL,MAAM,IAAA,GAAyB;AAC7B,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,KAAK,KAAA,EAAyB;AAClC,MAAA,IAAA,GAAO,KAAA;AAAA,IACT,CAAA;AAAA,IACA,QAAQ,GAAA,EAAwB;AAC9B,MAAA,OAAO,MAAM,GAAG,CAAA;AAAA,IAClB;AAAA,GACF;AACF","file":"index.js","sourcesContent":["/**\n * @packageDocumentation\n * @demlik/tea/mem — in-memory `Store<S>` adapter for `@demlik/tea`.\n *\n * The standard test adapter — used everywhere a Worker or DO would be\n * overkill. Mirrors the source-only convention from `packages/widget-engine`\n * and `packages/tea` (no build step).\n *\n * Reference semantics, NOT deep-clone semantics:\n * - `save(state)` stores the reference; subsequent mutations to that\n * reference by the caller are observable through later `load()` calls.\n * - `load()` returns the stored reference; mutations made through it are\n * observable on the next `load()`.\n *\n * This matches what a real KV/DO `get()` would NOT do (those serialize +\n * deserialize), but it also matches what an in-memory cache obviously does.\n * Adding a deep clone here would lie about the boundary and impose a JSON\n * round-trip on every test. Tests that need fresh copies should clone at the\n * call site.\n *\n * Boundary parse (invariant 8): `Store<S>` requires `migrate(raw)`. For\n * `memoryStore` we ship a permissive identity-default — `(raw) => raw as\n * S | null` — because this adapter is a degenerate same-process boundary:\n * there is no serialization, the reference we read is structurally the\n * reference we wrote. The `as` cast here is the documented exception to the\n * invariant-8 no-`as` rule, scoped to the parameter default. Callers that\n * want strict parsing (e.g. a test that hand-seeds the cell from outside)\n * may pass an explicit `parse` to override.\n */\n\nimport type { Store } from \"../index\";\n\n/**\n * Build an in-memory `Store<S>`.\n *\n * @param initial - optional seed value. `undefined` and explicit `null` both\n * mean \"no seed\"; `load()` resolves with `null` until the first `save()`.\n * A concrete `S` seed is returned by `load()` until overwritten.\n * @param parse - optional boundary parse for the `Store<S>.migrate` contract.\n * Defaults to identity (`(raw) => raw as S | null`) — honest for this\n * adapter's degenerate same-process boundary. Pass an explicit parser when\n * the test wants to reject seeded shapes that don't match `S`.\n */\nexport function memoryStore<S>(\n initial?: S | null,\n parse: (raw: unknown) => S | null = (raw) => raw as S | null,\n): Store<S> {\n // Single internal cell — `undefined` and `null` collapse to one\n // representation so the load site is branchless.\n let cell: S | null = initial ?? null;\n\n return {\n async load(): Promise<unknown> {\n return cell;\n },\n async save(state: S): Promise<void> {\n cell = state;\n },\n migrate(raw: unknown): S | null {\n return parse(raw);\n },\n };\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  import { DeadlineExceeded, DeadlineSub } from '../deadline/index.js';
2
2
  export { deadlineSub, subscribeDeadline } from '../deadline/index.js';
3
- import { I as Interpret, C as Cmd } from '../core-BmlJOvxM.js';
3
+ import { c as Interpret, C as Cmd } from '../core-42kapW0n.js';
4
4
  import { SnapshotState, SnapshotWriteCmd, SnapshotStore, SnapshotSavedMsg, SnapshotFailedMsg, SnapshotLoadedMsg, SnapshotLoadFailedMsg, SnapshotLoadCmd } from '../snapshot/index.js';
5
5
  import { QueueItem } from '../work-queue/index.js';
6
- import '../types-BoUWgwSm.js';
7
- import '../index.js';
6
+ import '../types-KbL3W1mj.js';
7
+ import '../runtime-types-CukV8kMX.js';
8
8
 
9
9
  /**
10
10
  * @demlik/tea/monitored-run — a long-running operation that is BOTH staged and
@@ -44,9 +44,10 @@ import '../index.js';
44
44
  *
45
45
  * ## Why the run lifecycle is INLINE (run-tracker not extracted)
46
46
  *
47
- * The phase union (`running` / `done` / `failed` / `stale`) and its
48
- * bookkeeping (`runId`, `startedAt`, `lastProgressAt`, `progressSeq`) live
49
- * directly on this slice rather than behind a `run-tracker` sub-knob.
47
+ * The phase union (`idle` / `running` / `stale` / `done` / `failed`, a
48
+ * discriminated union carrying only each phase's own data) and its bookkeeping
49
+ * (`runId`, `startedAt`, `lastProgressAt`, `progressSeq`) live directly on this
50
+ * slice rather than behind a `run-tracker` sub-knob.
50
51
  * Extraction earns its keep when two independent axes vary; here the lifecycle
51
52
  * has exactly one anchoring axis (this run), so a separate module would be a
52
53
  * premature seam. The two genuinely independent axes — the safety DEADLINE and
@@ -151,25 +152,12 @@ type RunFailure<Stage> = {
151
152
  readonly error: unknown;
152
153
  };
153
154
  /**
154
- * Run lifecycle phase. Discriminated on `phase`:
155
+ * The run bookkeeping every phase carries in common, split out so each arm of
156
+ * the `MonitoredRunState` discriminated union names only the data it ADDS
157
+ * (`runId` on started phases, `failure` on `failed`) — the same discipline the
158
+ * sibling `PaginatorState` uses for its shared `seen` / `pages` counters. Plain
159
+ * data end to end so the whole slice survives DO eviction / reload:
155
160
  *
156
- * - `running` — the run is live. Carries the current pipeline position
157
- * (`stepStates` is the stage queue; the single `running` item in it is the
158
- * current stage, `undefined` for a single-shot run) and the watchdog
159
- * bookkeeping (`lastProgressAt` / `progressSeq` keying the safety alarm).
160
- * - `stale` — a soft "no progress observed" mark the consumer can set and
161
- * clear; the run is still resumable (a `progress` clears it back to
162
- * `running`). Distinct from a fired deadline, which is terminal.
163
- * - `done` — every stage retired (or the single-shot `advance` landed).
164
- * - `failed` — terminal failure; `failure` carries why.
165
- */
166
- type RunPhase = "running" | "stale" | "done" | "failed";
167
- /**
168
- * The slice. Plain data end to end:
169
- * - `phase` — the lifecycle discriminant.
170
- * - `runId` — host-minted run identity, stamped at `start` (the
171
- * machine never mints — see the audit seed's
172
- * "run identity is host-minted").
173
161
  * - `stepStates` — the stage pipeline as a `work-queue` of stage items.
174
162
  * Empty for a single-shot run. The ONE `running` item is
175
163
  * the current stage; `done` items are retired stages
@@ -183,21 +171,57 @@ type RunPhase = "running" | "stale" | "done" | "failed";
183
171
  * - `progressSeq` — monotonic progress counter; keys the safety alarm Sub
184
172
  * id so every bump retires the old alarm and arms a new
185
173
  * one (the audit subs' `safety:{progressSeq}` discipline).
186
- * - `failure` — present iff `phase === "failed"`; the typed reason.
187
174
  * - `snapshot` — the `../snapshot` slice (cadence counter + durable
188
175
  * watermark). Always present (uniform shape); the gate
189
176
  * is simply never driven when `snapshotEvery` is omitted.
190
177
  */
191
- interface MonitoredRunState<Stage> {
192
- readonly phase: RunPhase;
193
- readonly runId: string;
178
+ interface RunCore<Stage> {
194
179
  readonly stepStates: readonly QueueItem<Stage>[];
195
180
  readonly startedAt: number;
196
181
  readonly lastProgressAt: number;
197
182
  readonly progressSeq: number;
198
- readonly failure: RunFailure<Stage> | null;
199
183
  readonly snapshot: SnapshotState;
200
184
  }
185
+ /**
186
+ * The slice — a discriminated union on `phase` so each phase carries ONLY its
187
+ * own data and impossible combinations are unrepresentable (pattern 11), the
188
+ * same idiom the sibling `CircuitState` / `PaginatorState` / `RunFailure` follow:
189
+ *
190
+ * - `idle` — created but never started. Carries no `runId` (identity is
191
+ * host-minted at `start`) and no `failure`: "not started" is its
192
+ * OWN phase, not a `running` run wearing an empty-string `runId`
193
+ * sentinel. `start` is the only transition out, so no verb has to
194
+ * guard "has this actually started?" — `phase === "idle"` says it.
195
+ * - `running` — the run is live. Carries the host-minted `runId` and the
196
+ * `RunCore` pipeline position + watchdog bookkeeping.
197
+ * - `stale` — a soft "no progress observed" mark the consumer can set and
198
+ * clear; still resumable (a `progress` clears it back to
199
+ * `running`). Distinct from a fired deadline, which is terminal.
200
+ * - `done` — every stage retired (or the single-shot `advance` landed).
201
+ * - `failed` — terminal failure; the typed `failure` reason rides ONLY this
202
+ * arm, so it is present iff `phase === "failed"` BY CONSTRUCTION —
203
+ * there is no nullable side field to keep in sync.
204
+ *
205
+ * `runId` is the host-minted run identity, stamped at `start` (the machine never
206
+ * mints — see the audit seed's "run identity is host-minted"). It rides every
207
+ * started phase, but not `idle`.
208
+ */
209
+ type MonitoredRunState<Stage> = (RunCore<Stage> & {
210
+ readonly phase: "idle";
211
+ }) | (RunCore<Stage> & {
212
+ readonly phase: "running";
213
+ readonly runId: string;
214
+ }) | (RunCore<Stage> & {
215
+ readonly phase: "stale";
216
+ readonly runId: string;
217
+ }) | (RunCore<Stage> & {
218
+ readonly phase: "done";
219
+ readonly runId: string;
220
+ }) | (RunCore<Stage> & {
221
+ readonly phase: "failed";
222
+ readonly runId: string;
223
+ readonly failure: RunFailure<Stage>;
224
+ });
201
225
  /**
202
226
  * The outcome a consumer reports to `advance`: the current stage either
203
227
  * succeeded (retire it, claim the next) or failed (terminate the run). Plain
@@ -250,4 +274,4 @@ declare function liftRun<S extends {
250
274
  run: MonitoredRunState<Stage>;
251
275
  }, Stage, C extends Cmd>(state: S, [slice, cmds]: readonly [MonitoredRunState<Stage>, readonly C[]]): readonly [S, readonly C[]];
252
276
 
253
- export { DeadlineExceeded, DeadlineSub, type MonitoredRunCmd, type MonitoredRunConfig, type MonitoredRunPorts, type MonitoredRunState, type MonitoredRunTimerMsg, type RunFailure, type RunPhase, SnapshotSavedMsg, SnapshotWriteCmd, type StageResult, createMonitoredRun, liftRun };
277
+ export { DeadlineExceeded, DeadlineSub, type MonitoredRunCmd, type MonitoredRunConfig, type MonitoredRunPorts, type MonitoredRunState, type MonitoredRunTimerMsg, type RunFailure, SnapshotSavedMsg, SnapshotWriteCmd, type StageResult, createMonitoredRun, liftRun };
@@ -1,10 +1,10 @@
1
- export { createMonitoredRun, liftRun } from '../chunk-ZDRN3YCE.js';
2
- import '../chunk-5KVHID6A.js';
3
- export { deadlineSub, subscribeDeadline } from '../chunk-SKAMZKTE.js';
4
- import '../chunk-HJO6VNQI.js';
5
- import '../chunk-TGG26XMK.js';
1
+ export { createMonitoredRun, liftRun } from '../chunk-LJJV6ZD5.js';
2
+ import '../chunk-SPWVPIFQ.js';
3
+ export { deadlineSub, subscribeDeadline } from '../chunk-RMX4EOQK.js';
4
+ import '../chunk-MU6MCIAC.js';
5
+ import '../chunk-FV3Z5MBE.js';
6
6
  import '../chunk-2YFLOCWX.js';
7
- import '../chunk-6VDCTR7N.js';
8
- import '../chunk-DYRKDCJI.js';
7
+ import '../chunk-D4NJBQAH.js';
8
+ import '../chunk-PBDYW2TU.js';
9
9
  //# sourceMappingURL=index.js.map
10
10
  //# sourceMappingURL=index.js.map
@@ -1,8 +1,9 @@
1
1
  import WebSocket from 'ws';
2
- import { Store } from '../index.js';
3
- import { a as SubId, S as Sub } from '../core-BmlJOvxM.js';
2
+ import { S as Store } from '../runtime-types-CukV8kMX.js';
3
+ import { a as SubId, S as Sub } from '../core-42kapW0n.js';
4
4
 
5
5
  /**
6
+ * @packageDocumentation
6
7
  * @demlik/tea/node — Node host adapter for `@demlik/tea`.
7
8
  *
8
9
  * Every host adapter ships two halves (mirrors `@demlik/tea/do`):
@@ -1,8 +1,8 @@
1
+ import { dispatchIfPresent } from '../chunk-ZJ5AOYBH.js';
1
2
  import { mkdir, writeFile, rename, readFile } from 'fs/promises';
2
3
  import { dirname } from 'path';
3
4
  import WebSocket from 'ws';
4
5
 
5
- // src/node/index.ts
6
6
  function fileStore(path, parse) {
7
7
  return {
8
8
  async load() {
@@ -45,8 +45,7 @@ function nodeSubscribe() {
45
45
  if (sub.onOpen) dispatch(sub.onOpen());
46
46
  });
47
47
  socket.on("message", (data) => {
48
- const msg = sub.onMessage(data.toString());
49
- if (msg !== null) dispatch(msg);
48
+ dispatchIfPresent(dispatch, sub.onMessage(data.toString()));
50
49
  });
51
50
  socket.on("close", (code, reason) => {
52
51
  if (sub.onClose) dispatch(sub.onClose(code, reason.toString()));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/node/index.ts"],"names":["handle"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @demlik/tea/node — Node host adapter for `@demlik/tea`.\n *\n * Every host adapter ships two halves (mirrors `@demlik/tea/do`):\n *\n * 1. `fileStore<S>(path, parse)` — a `Store<S>` over a JSON file on disk.\n * Atomic save (temp + rename). Absent file → `load()` resolves `null`\n * (fresh boot). Structurally malformed JSON throws at `load()` (an infra\n * error), matching `doStore`; a shape mismatch is NOT a throw — the\n * substrate calls `migrate`, which returns `null` and boots fresh.\n * Use for resumable scripts / local repro of prod machines. Ephemeral\n * CLIs can skip persistence with `memoryStore` from `@demlik/tea/mem`.\n *\n * 2. `nodeSubscribe<M, Ctx>()` — handler registry for the three node-native\n * Sub types this package owns:\n * - `node_ws` — opens a `ws` WebSocket; routes open/message/close/\n * error into Msgs. Registers the live socket in\n * `ctx.wsRegistry` so a Cmd handler can write to it\n * (see `sendToWebSocket`). Cleanup closes the socket\n * for ANY non-CLOSED state.\n * - `node_timer` — setTimeout / setInterval; cleanup clears it.\n * - `node_signal` — a process signal (SIGINT/SIGTERM/…) → Msg; cleanup\n * detaches the listener.\n *\n * Unlike tea-do — whose `alarm()` / `webSocketMessage()` are DO lifecycle\n * methods the handler can't observe from the outside, hence its registries —\n * a node process is continuously alive: each handler holds its resource in\n * closure and the returned cleanup closes it. The substrate's subscription\n * reconciler runs that cleanup the instant `subscriptions(state)` stops listing\n * the Sub — end-then-start choreography for free (Rule 9, [invariant 4]).\n * A lifecycle-bound resource (a socket, a timer) becomes impossible to leak\n * from a reducer cell, because no reducer cell owns its teardown.\n *\n * `ctx.wsRegistry` is the one shared surface: `node_ws` populates it; a Cmd\n * interpreter reads it to write to the open socket. Same shape as tea-do's\n * `wsRegistry`.\n */\n\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport WebSocket, { type RawData } from \"ws\";\nimport type { Store, Sub, SubId } from \"../index\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// fileStore — `Store<S>` over a JSON file.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * `Store<S>` backed by a JSON file at `path`.\n *\n * `parse` is REQUIRED because the file is a real serialization boundary — the\n * bytes that come back through `JSON.parse` are structurally `unknown`, and the\n * caller (who owns `S`) is the only party that can validate the shape.\n * Returning `null` from `parse` means \"no usable persisted state\" — the\n * substrate boots `init` with `loaded = null`. `parse` must NOT throw per the\n * `Store<S>.migrate` contract.\n */\nexport function fileStore<S>(\n path: string,\n parse: (raw: unknown) => S | null,\n): Store<S> {\n return {\n async load(): Promise<unknown> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n // JSON.parse throws on structural malformation — propagate (infra error),\n // matching `doStore`. The decoded value is intentionally returned as\n // `unknown`; the substrate's `migrate` callback is the boundary parse.\n return JSON.parse(raw);\n },\n async save(state: S): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n // Write to a pid-scoped temp then atomically rename. The rename gives\n // torn-write safety (a reader sees the old file or the new one, never a\n // partial) and survives process crash / Ctrl-C — the only failure a local\n // resumable run resumes from. fsync (file + dir) would add power-loss\n // durability the use case doesn't need; the substrate serializes saves\n // through run()'s queue, so there's never a second concurrent writer.\n const tmp = `${path}.${process.pid}.tmp`;\n await writeFile(tmp, JSON.stringify(state), \"utf8\");\n await rename(tmp, path);\n },\n migrate(raw: unknown): S | null {\n return parse(raw);\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// NodeSub — the three node-native subscription variants.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A node WebSocket sub. The substrate diffs subs by `id`; the same `id` across\n * transitions = the same socket (kept open). Emit a DIFFERENT `id` to force a\n * reconnect.\n *\n * Only `onMessage` returns `M | null` (null drops the frame) because inbound\n * frames are the boundary that needs a parse-and-maybe-discard seam. `onOpen`/\n * `onClose`/`onError` always dispatch — a spurious one is dropped in the reducer\n * (the cell ignores it), not here.\n */\nexport type NodeWsSub<M> = {\n id: SubId;\n type: \"node_ws\";\n url: string;\n onMessage: (data: string) => M | null;\n onOpen?: () => M;\n onClose?: (code: number, reason: string) => M;\n onError?: (message: string) => M;\n};\n\n/**\n * A node timer sub. One-shot `setTimeout` by default; `repeat: true` →\n * `setInterval`. Cleanup clears the timer — a timer keyed to a phase is\n * cancelled the moment the machine leaves that phase.\n */\nexport type NodeTimerSub<M> = {\n id: SubId;\n type: \"node_timer\";\n delayMs: number;\n msg: M;\n repeat?: boolean;\n};\n\n/**\n * A process-signal sub: `signal` (SIGINT/SIGTERM/…) → `msg`. Cleanup detaches\n * the listener. Lets graceful shutdown be a real machine transition rather than\n * an out-of-band `process.on` that the reducer can't see.\n */\nexport type NodeSignalSub<M> = {\n id: SubId;\n type: \"node_signal\";\n signal: NodeJS.Signals;\n msg: M;\n};\n\nexport type NodeSub<M> = NodeWsSub<M> | NodeTimerSub<M> | NodeSignalSub<M>;\n\n// Compile-time guarantee: `NodeSub<M>` is structurally a `Sub` (has `id`+`type`).\n// Erased at runtime — the `extends Sub` constraint on the alias's type\n// parameter fails to compile if `NodeSub` ever drifts off the `Sub` shape.\nexport type AssertNodeSubIsSub<T extends Sub = NodeSub<unknown>> = T;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Ctx — what the node Subs read / populate.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type NodeWsRegistry = Map<SubId, WebSocket>;\n\n/**\n * The Ctx fields the node Subs depend on. A consumer's Ctx must extend this so\n * `node_ws` can register the live socket and Cmd handlers can reach it.\n *\n * Deliberately NOT parameterized by Msg: the registry holds raw `WebSocket`s,\n * and each Sub variant carries its own Msg-typed callbacks. Trip-wire: if a\n * future node Sub holds a Msg-typed callback on ctx, this becomes\n * `NodeSubscribeCtx<M>`.\n */\nexport interface NodeSubscribeCtx {\n wsRegistry: NodeWsRegistry;\n}\n\n/**\n * Write a frame to a registered `node_ws` socket. No-op (returns `false`) when\n * the socket is absent, not OPEN, or `send()` throws. The Cmd-side companion to\n * the `node_ws` Sub: the Sub owns the socket's lifetime; this writes to it.\n *\n * The contract is a boolean no-op, never a throw: the socket can transition out\n * of OPEN between the `readyState` check and `send()` (or `ws` can throw on an\n * errored underlying connection), and a Cmd interpreter must not crash on that.\n */\nexport function sendToWebSocket(\n ctx: NodeSubscribeCtx,\n id: SubId,\n data: string,\n): boolean {\n const socket = ctx.wsRegistry.get(id);\n if (!socket || socket.readyState !== WebSocket.OPEN) return false;\n try {\n socket.send(data);\n return true;\n } catch {\n return false;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// nodeSubscribe — the handler registry.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build the `subscribe` handlers for `node_ws`, `node_timer`, and\n * `node_signal`. Matches `Machine#subscribe`'s\n * `[K in U[\"type\"]]: (sub, ctx, dispatch) => () => void` contract for the\n * `NodeSub<M>` variants. Generic `Ctx` must extend `NodeSubscribeCtx` so the\n * `node_ws` handler can register its socket.\n */\nexport function nodeSubscribe<M, Ctx extends NodeSubscribeCtx>(): {\n node_ws: (\n sub: NodeWsSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n node_timer: (\n sub: NodeTimerSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n node_signal: (\n sub: NodeSignalSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n} {\n return {\n node_ws: (sub, ctx, dispatch) => {\n const socket = new WebSocket(sub.url);\n ctx.wsRegistry.set(sub.id, socket);\n\n socket.on(\"open\", () => {\n if (sub.onOpen) dispatch(sub.onOpen());\n });\n socket.on(\"message\", (data: RawData) => {\n const msg = sub.onMessage(data.toString());\n if (msg !== null) dispatch(msg);\n });\n socket.on(\"close\", (code: number, reason: Buffer) => {\n if (sub.onClose) dispatch(sub.onClose(code, reason.toString()));\n });\n socket.on(\"error\", (err: Error) => {\n if (sub.onError) dispatch(sub.onError(err.message));\n });\n\n return () => {\n ctx.wsRegistry.delete(sub.id);\n socket.removeAllListeners();\n // Swallow teardown-induced errors: terminate()-ing a CONNECTING socket\n // emits an 'error' (\"closed before the connection was established\"),\n // and with our listeners gone that would be an unhandled 'error' that\n // crashes the process. A no-op listener absorbs it.\n socket.on(\"error\", () => {});\n // Close for ANY non-closed state: a graceful 1000 when OPEN, a hard\n // terminate() otherwise (CONNECTING/CLOSING) so a socket caught mid-\n // handshake can't dangle. This is the teardown the hand-driven Cmd-pair\n // approach (`cleanup_browser` closing the ws only `if OPEN`) could not\n // guarantee.\n if (socket.readyState === WebSocket.OPEN) {\n socket.close(1000, \"tea-node sub cleanup\");\n } else if (socket.readyState !== WebSocket.CLOSED) {\n socket.terminate();\n }\n };\n },\n\n node_timer: (sub, _ctx, dispatch) => {\n // Two reconcile lifecycles under one variant: a one-shot (`setTimeout`)\n // that `subscriptions(state)` should drop on the render after it fires,\n // vs a repeating (`setInterval`) one the state keeps listing across\n // renders. Same Sub.type; the `repeat` flag picks the install + cleanup.\n if (sub.repeat) {\n const handle = setInterval(() => dispatch(sub.msg), sub.delayMs);\n return () => clearInterval(handle);\n }\n const handle = setTimeout(() => dispatch(sub.msg), sub.delayMs);\n return () => clearTimeout(handle);\n },\n\n node_signal: (sub, _ctx, dispatch) => {\n const handler = () => dispatch(sub.msg);\n process.on(sub.signal, handler);\n return () => {\n process.off(sub.signal, handler);\n };\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/node/index.ts"],"names":["handle"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @packageDocumentation\n * @demlik/tea/node — Node host adapter for `@demlik/tea`.\n *\n * Every host adapter ships two halves (mirrors `@demlik/tea/do`):\n *\n * 1. `fileStore<S>(path, parse)` — a `Store<S>` over a JSON file on disk.\n * Atomic save (temp + rename). Absent file → `load()` resolves `null`\n * (fresh boot). Structurally malformed JSON throws at `load()` (an infra\n * error), matching `doStore`; a shape mismatch is NOT a throw — the\n * substrate calls `migrate`, which returns `null` and boots fresh.\n * Use for resumable scripts / local repro of prod machines. Ephemeral\n * CLIs can skip persistence with `memoryStore` from `@demlik/tea/mem`.\n *\n * 2. `nodeSubscribe<M, Ctx>()` — handler registry for the three node-native\n * Sub types this package owns:\n * - `node_ws` — opens a `ws` WebSocket; routes open/message/close/\n * error into Msgs. Registers the live socket in\n * `ctx.wsRegistry` so a Cmd handler can write to it\n * (see `sendToWebSocket`). Cleanup closes the socket\n * for ANY non-CLOSED state.\n * - `node_timer` — setTimeout / setInterval; cleanup clears it.\n * - `node_signal` — a process signal (SIGINT/SIGTERM/…) → Msg; cleanup\n * detaches the listener.\n *\n * Unlike tea-do — whose `alarm()` / `webSocketMessage()` are DO lifecycle\n * methods the handler can't observe from the outside, hence its registries —\n * a node process is continuously alive: each handler holds its resource in\n * closure and the returned cleanup closes it. The substrate's subscription\n * reconciler runs that cleanup the instant `subscriptions(state)` stops listing\n * the Sub — end-then-start choreography for free (Rule 9, [invariant 4]).\n * A lifecycle-bound resource (a socket, a timer) becomes impossible to leak\n * from a reducer cell, because no reducer cell owns its teardown.\n *\n * `ctx.wsRegistry` is the one shared surface: `node_ws` populates it; a Cmd\n * interpreter reads it to write to the open socket. Same shape as tea-do's\n * `wsRegistry`.\n */\n\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport WebSocket, { type RawData } from \"ws\";\nimport type { Store, Sub, SubId } from \"../index\";\nimport { dispatchIfPresent } from \"../subs/types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// fileStore — `Store<S>` over a JSON file.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * `Store<S>` backed by a JSON file at `path`.\n *\n * `parse` is REQUIRED because the file is a real serialization boundary — the\n * bytes that come back through `JSON.parse` are structurally `unknown`, and the\n * caller (who owns `S`) is the only party that can validate the shape.\n * Returning `null` from `parse` means \"no usable persisted state\" — the\n * substrate boots `init` with `loaded = null`. `parse` must NOT throw per the\n * `Store<S>.migrate` contract.\n */\nexport function fileStore<S>(\n path: string,\n parse: (raw: unknown) => S | null,\n): Store<S> {\n return {\n async load(): Promise<unknown> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n // JSON.parse throws on structural malformation — propagate (infra error),\n // matching `doStore`. The decoded value is intentionally returned as\n // `unknown`; the substrate's `migrate` callback is the boundary parse.\n return JSON.parse(raw);\n },\n async save(state: S): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n // Write to a pid-scoped temp then atomically rename. The rename gives\n // torn-write safety (a reader sees the old file or the new one, never a\n // partial) and survives process crash / Ctrl-C — the only failure a local\n // resumable run resumes from. fsync (file + dir) would add power-loss\n // durability the use case doesn't need; the substrate serializes saves\n // through run()'s queue, so there's never a second concurrent writer.\n const tmp = `${path}.${process.pid}.tmp`;\n await writeFile(tmp, JSON.stringify(state), \"utf8\");\n await rename(tmp, path);\n },\n migrate(raw: unknown): S | null {\n return parse(raw);\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// NodeSub — the three node-native subscription variants.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A node WebSocket sub. The substrate diffs subs by `id`; the same `id` across\n * transitions = the same socket (kept open). Emit a DIFFERENT `id` to force a\n * reconnect.\n *\n * Only `onMessage` returns `M | null` (null drops the frame) because inbound\n * frames are the boundary that needs a parse-and-maybe-discard seam. `onOpen`/\n * `onClose`/`onError` always dispatch — a spurious one is dropped in the reducer\n * (the cell ignores it), not here.\n */\nexport type NodeWsSub<M> = {\n id: SubId;\n type: \"node_ws\";\n url: string;\n onMessage: (data: string) => M | null;\n onOpen?: () => M;\n onClose?: (code: number, reason: string) => M;\n onError?: (message: string) => M;\n};\n\n/**\n * A node timer sub. One-shot `setTimeout` by default; `repeat: true` →\n * `setInterval`. Cleanup clears the timer — a timer keyed to a phase is\n * cancelled the moment the machine leaves that phase.\n */\nexport type NodeTimerSub<M> = {\n id: SubId;\n type: \"node_timer\";\n delayMs: number;\n msg: M;\n repeat?: boolean;\n};\n\n/**\n * A process-signal sub: `signal` (SIGINT/SIGTERM/…) → `msg`. Cleanup detaches\n * the listener. Lets graceful shutdown be a real machine transition rather than\n * an out-of-band `process.on` that the reducer can't see.\n */\nexport type NodeSignalSub<M> = {\n id: SubId;\n type: \"node_signal\";\n signal: NodeJS.Signals;\n msg: M;\n};\n\nexport type NodeSub<M> = NodeWsSub<M> | NodeTimerSub<M> | NodeSignalSub<M>;\n\n// Compile-time guarantee: `NodeSub<M>` is structurally a `Sub` (has `id`+`type`).\n// Erased at runtime — the `extends Sub` constraint on the alias's type\n// parameter fails to compile if `NodeSub` ever drifts off the `Sub` shape.\nexport type AssertNodeSubIsSub<T extends Sub = NodeSub<unknown>> = T;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Ctx — what the node Subs read / populate.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type NodeWsRegistry = Map<SubId, WebSocket>;\n\n/**\n * The Ctx fields the node Subs depend on. A consumer's Ctx must extend this so\n * `node_ws` can register the live socket and Cmd handlers can reach it.\n *\n * Deliberately NOT parameterized by Msg: the registry holds raw `WebSocket`s,\n * and each Sub variant carries its own Msg-typed callbacks. Trip-wire: if a\n * future node Sub holds a Msg-typed callback on ctx, this becomes\n * `NodeSubscribeCtx<M>`.\n */\nexport interface NodeSubscribeCtx {\n wsRegistry: NodeWsRegistry;\n}\n\n/**\n * Write a frame to a registered `node_ws` socket. No-op (returns `false`) when\n * the socket is absent, not OPEN, or `send()` throws. The Cmd-side companion to\n * the `node_ws` Sub: the Sub owns the socket's lifetime; this writes to it.\n *\n * The contract is a boolean no-op, never a throw: the socket can transition out\n * of OPEN between the `readyState` check and `send()` (or `ws` can throw on an\n * errored underlying connection), and a Cmd interpreter must not crash on that.\n */\nexport function sendToWebSocket(\n ctx: NodeSubscribeCtx,\n id: SubId,\n data: string,\n): boolean {\n const socket = ctx.wsRegistry.get(id);\n if (!socket || socket.readyState !== WebSocket.OPEN) return false;\n try {\n socket.send(data);\n return true;\n } catch {\n return false;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// nodeSubscribe — the handler registry.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build the `subscribe` handlers for `node_ws`, `node_timer`, and\n * `node_signal`. Matches `Machine#subscribe`'s\n * `[K in U[\"type\"]]: (sub, ctx, dispatch) => () => void` contract for the\n * `NodeSub<M>` variants. Generic `Ctx` must extend `NodeSubscribeCtx` so the\n * `node_ws` handler can register its socket.\n */\nexport function nodeSubscribe<M, Ctx extends NodeSubscribeCtx>(): {\n node_ws: (\n sub: NodeWsSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n node_timer: (\n sub: NodeTimerSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n node_signal: (\n sub: NodeSignalSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n} {\n return {\n node_ws: (sub, ctx, dispatch) => {\n const socket = new WebSocket(sub.url);\n ctx.wsRegistry.set(sub.id, socket);\n\n socket.on(\"open\", () => {\n if (sub.onOpen) dispatch(sub.onOpen());\n });\n socket.on(\"message\", (data: RawData) => {\n dispatchIfPresent(dispatch, sub.onMessage(data.toString()));\n });\n socket.on(\"close\", (code: number, reason: Buffer) => {\n if (sub.onClose) dispatch(sub.onClose(code, reason.toString()));\n });\n socket.on(\"error\", (err: Error) => {\n if (sub.onError) dispatch(sub.onError(err.message));\n });\n\n return () => {\n ctx.wsRegistry.delete(sub.id);\n socket.removeAllListeners();\n // Swallow teardown-induced errors: terminate()-ing a CONNECTING socket\n // emits an 'error' (\"closed before the connection was established\"),\n // and with our listeners gone that would be an unhandled 'error' that\n // crashes the process. A no-op listener absorbs it.\n socket.on(\"error\", () => {});\n // Close for ANY non-closed state: a graceful 1000 when OPEN, a hard\n // terminate() otherwise (CONNECTING/CLOSING) so a socket caught mid-\n // handshake can't dangle. This is the teardown the hand-driven Cmd-pair\n // approach (`cleanup_browser` closing the ws only `if OPEN`) could not\n // guarantee.\n if (socket.readyState === WebSocket.OPEN) {\n socket.close(1000, \"tea-node sub cleanup\");\n } else if (socket.readyState !== WebSocket.CLOSED) {\n socket.terminate();\n }\n };\n },\n\n node_timer: (sub, _ctx, dispatch) => {\n // Two reconcile lifecycles under one variant: a one-shot (`setTimeout`)\n // that `subscriptions(state)` should drop on the render after it fires,\n // vs a repeating (`setInterval`) one the state keeps listing across\n // renders. Same Sub.type; the `repeat` flag picks the install + cleanup.\n if (sub.repeat) {\n const handle = setInterval(() => dispatch(sub.msg), sub.delayMs);\n return () => clearInterval(handle);\n }\n const handle = setTimeout(() => dispatch(sub.msg), sub.delayMs);\n return () => clearTimeout(handle);\n },\n\n node_signal: (sub, _ctx, dispatch) => {\n const handler = () => dispatch(sub.msg);\n process.on(sub.signal, handler);\n return () => {\n process.off(sub.signal, handler);\n };\n },\n };\n}\n"]}
@@ -1,11 +1,11 @@
1
- import { C as Cmd } from '../core-BmlJOvxM.js';
1
+ import { C as Cmd } from '../core-42kapW0n.js';
2
2
  import { PaginatorState } from '../paginator/index.js';
3
3
  import { CircuitConfig, RateLimitConfig, DeadlineConfig, ResilientState, RunCmd, SucceedMsg, FailMsg, ResilientTimerMsg, ResilientPorts } from '../resilient-call/index.js';
4
4
  import { RetryPolicy } from '../retry-backoff/index.js';
5
5
  import { DeadlineSub } from '../deadline/index.js';
6
6
  export { deadlineSub, subscribeDeadline } from '../deadline/index.js';
7
7
  import '../cache/index.js';
8
- import '../types-BoUWgwSm.js';
8
+ import '../types-KbL3W1mj.js';
9
9
  import '../circuit-breaker/index.js';
10
10
  import '../protocol-B5YmJr0d.js';
11
11
  import '../rate-limit/index.js';
@@ -1,15 +1,16 @@
1
- export { PAGE_KEY, createPaginatedWalk, liftWalk } from '../chunk-FOGL3TC7.js';
1
+ export { PAGE_KEY, createPaginatedWalk, liftWalk } from '../chunk-JXYYRKD6.js';
2
2
  import '../chunk-SINXE47Y.js';
3
- import '../chunk-6GIGQT6C.js';
3
+ import '../chunk-B5RAZVXW.js';
4
4
  import '../chunk-JELSZN25.js';
5
- export { deadlineSub, subscribeDeadline } from '../chunk-SKAMZKTE.js';
6
- import '../chunk-5VNWZADP.js';
7
- import '../chunk-2YOE5PIS.js';
8
- import '../chunk-SC3FAFZN.js';
9
- import '../chunk-IN33Q4VJ.js';
10
- import '../chunk-NSDFA6M6.js';
5
+ export { deadlineSub, subscribeDeadline } from '../chunk-RMX4EOQK.js';
6
+ import '../chunk-Q2UM7BXU.js';
7
+ import '../chunk-S2EGRPCM.js';
8
+ import '../chunk-UF4MZSLT.js';
9
+ import '../chunk-2KNLKQVN.js';
10
+ import '../chunk-LYLDCIPH.js';
11
11
  import '../chunk-2YFLOCWX.js';
12
- import '../chunk-6VDCTR7N.js';
13
- import '../chunk-DYRKDCJI.js';
12
+ import '../chunk-ZJ5AOYBH.js';
13
+ import '../chunk-D4NJBQAH.js';
14
+ import '../chunk-PBDYW2TU.js';
14
15
  //# sourceMappingURL=index.js.map
15
16
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,6 @@
1
- import { BootingRuntime } from '../index.js';
1
+ import { B as BootingRuntime } from '../runtime-types-CukV8kMX.js';
2
+ import { C as Cmd, S as Sub, M as Machine } from '../core-42kapW0n.js';
2
3
  import { Trace, RecorderOptions } from '../recorder/index.js';
3
- import { C as Cmd, S as Sub, M as Machine } from '../core-BmlJOvxM.js';
4
4
 
5
5
  /**
6
6
  * A live parity recording attached to a Runtime — the go/no-go gate's golden
@@ -77,9 +77,10 @@ interface ParitySchema {
77
77
  declare function normalizeForParity(schema?: ParitySchema): (value: unknown) => unknown;
78
78
  /**
79
79
  * The parity verdict: `true` iff `a` and `b` are structurally equal. Reuses
80
- * `trace-replay`'s order-insensitive deep-equal (the single comparator this
81
- * package owns) rather than adding a second one — feed it the pair produced by
82
- * {@link normalizeForParity} for the normalized-JSON equality the gate rests on.
80
+ * `trace-replay`'s order-insensitive deep-equal (the comparator the
81
+ * record/replay lane owns) rather than adding another boolean deep-compare —
82
+ * feed it the pair produced by {@link normalizeForParity} for the
83
+ * normalized-JSON equality the gate rests on.
83
84
  */
84
85
  declare function parityEqual(a: unknown, b: unknown): boolean;
85
86
  /**
@@ -1,7 +1,8 @@
1
- import { recorder } from '../chunk-UYWUVEP6.js';
2
- import { deepEqual } from '../chunk-IK4S5DJN.js';
3
- import { replay } from '../chunk-6VDCTR7N.js';
4
- import '../chunk-DYRKDCJI.js';
1
+ import { recorder } from '../chunk-MUUROGIH.js';
2
+ import { deepEqual } from '../chunk-2AHAR7CX.js';
3
+ import '../chunk-EWJP3YYP.js';
4
+ import { replay } from '../chunk-D4NJBQAH.js';
5
+ import '../chunk-PBDYW2TU.js';
5
6
 
6
7
  // src/parity/index.ts
7
8
  function recordRun(runtime, opts) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/parity/index.ts"],"names":["key"],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// parity — the record → replay → normalized-diff go/no-go gate.\n//\n// The parity spike proved the mechanism already composes from in-tree\n// primitives: `recorder` captures a run's Msg sequence (the recording), `replay`\n// re-folds it purely, and `trace-replay`'s order-insensitive deep-equal diffs\n// the result. Non-determinism is a RECORD-TIME property — ids/timestamps freeze\n// into the recording, `replay` never re-mints them — so the same recording\n// replays byte-identical every time.\n//\n// This module is the thin packaging over that substrate plus the one genuinely\n// new helper, `normalizeForParity`: the finding-normalizer that strips\n// record-time non-determinism (generated ids/timestamps) and stable-key-sorts\n// collections, so an OLD-engine golden run and a NEW-engine run diff as equal\n// exactly when they are behaviourally equivalent. Every domain (audit, auth,\n// ingest, …) gets its old-vs-new parity check without reinventing the capture,\n// the normalization, and the diff.\n//\n// recordRun(runtime) → Recording // wraps the recorder\n// goldenReplay(machine, rec) → State // wraps replay\n// normalizeForParity(schema) → (finding) => norm // strip + stable-sort\n// parityEqual(a, b) → boolean // normalized deep-equal\n// ---------------------------------------------------------------------------\n\nimport type { BootingRuntime, Cmd, Machine, Sub } from \"../index\";\nimport { replay } from \"../index\";\nimport { type RecorderOptions, recorder, type Trace } from \"../recorder\";\nimport { deepEqual } from \"../trace-replay\";\n\n/**\n * A live parity recording attached to a Runtime — the go/no-go gate's golden\n * artifact. Wraps a {@link recorder}: drive the runtime, then snapshot with\n * {@link Recording.trace} (or hand the live `Recording` straight to\n * {@link goldenReplay}, which snapshots for you).\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport interface Recording<S, M> {\n /**\n * Snapshot the recording so far as a replayable {@link Trace} — `loaded`\n * (boot state), the ordered non-null `msgs`, and `finalState`. Sufficient to\n * reproduce the run via {@link goldenReplay}. Fresh object each call.\n */\n trace(): Trace<S, M>;\n /** Serialize the recording as JSONL (see {@link recorder}'s `toJSONL`). */\n toJSONL(): string;\n /** Detach the underlying observer. Idempotent. */\n stop(): void;\n}\n\n/**\n * Attach a parity recording to a Runtime, reusing the `recorder`/`historyTracker`\n * capture (the boot observe for `loaded` + every non-null msg in order) rather\n * than a fresh observer path.\n *\n * Attach on the synchronous `run()` handle so the boot transition is captured\n * as `loaded`; drive the runtime; then `trace()` (or pass the handle to\n * {@link goldenReplay}). See {@link RecorderOptions} for `captureSteps` (needed\n * only for the per-step / resume-cache-prefix inspection, not for replay).\n */\nexport function recordRun<S, M extends { type: string }>(\n runtime: BootingRuntime<S, M>,\n opts?: RecorderOptions,\n): Recording<S, M> {\n const rec = recorder(runtime, opts);\n return {\n trace: () => rec.dump(),\n toJSONL: () => rec.toJSONL(),\n stop: () => rec.stop(),\n };\n}\n\n/**\n * Reproduce a recording's final state by folding its msgs through the pure\n * `replay` — `init(loaded, ctx)` then `update` per msg, never `interpret`,\n * never a `Store`, never a live subscription. Accepts a live {@link Recording}\n * (snapshotted here) or a bare {@link Trace}.\n *\n * `ctx` is the ENVIRONMENT supplied fresh at replay time (the trace is the\n * INPUT: `loaded` + `msgs`), mirroring `replayTrace`. It is optional so a\n * void-ctx machine can call `goldenReplay(machine, rec)`; a machine that reads\n * ctx passes it explicitly.\n */\nexport function goldenReplay<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n recording: Recording<S, M> | Trace<S, M>,\n ctx?: Ctx,\n): S {\n const trace = asTrace(recording);\n const { state } = replay(machine, {\n msgs: trace.msgs,\n ctx: ctx as Ctx,\n loaded: trace.loaded,\n });\n return state;\n}\n\n/** Narrow a `Recording | Trace` to a `Trace`, snapshotting a live recording. */\nfunction asTrace<S, M>(recording: Recording<S, M> | Trace<S, M>): Trace<S, M> {\n return typeof (recording as Recording<S, M>).trace === \"function\"\n ? (recording as Recording<S, M>).trace()\n : (recording as Trace<S, M>);\n}\n\n/** Configuration for {@link normalizeForParity}. */\nexport interface ParitySchema {\n /**\n * Object keys stripped wherever they appear — the record-time\n * non-determinism `replay` never re-mints. Default: `id`, `runId`,\n * `timestamp`.\n */\n readonly stripKeys?: readonly string[];\n /**\n * Candidate keys used to stable-sort arrays of objects. For each array the\n * first candidate present (as a primitive) on the items decides the order;\n * arrays with no candidate fall back to canonical-JSON order. Default:\n * `ruleId`, `selector`, `url`.\n */\n readonly sortKeys?: readonly string[];\n}\n\n// Defaults proven by the spike: strip the generated id/runId/timestamp,\n// stable-key-sort by ruleId | selector | url. Fan-out ordering is already\n// deterministic on this substrate (array order); the sort only defends against\n// intra-effect concurrency (Promise.all / Set).\nconst DEFAULT_STRIP_KEYS: readonly string[] = [\"id\", \"runId\", \"timestamp\"];\nconst DEFAULT_SORT_KEYS: readonly string[] = [\"ruleId\", \"selector\", \"url\"];\n\n/**\n * Build a finding-normalizer: a pure `(value) => normalized` that strips the\n * schema's `stripKeys`, recursively sorts object keys, and stable-key-sorts\n * arrays by the schema's `sortKeys`. Two runs that differ only in generated\n * ids/timestamps or intra-effect ordering normalize to a byte-identical value,\n * so {@link parityEqual} on the normalized pair reflects behavioural (not incidental)\n * difference.\n */\nexport function normalizeForParity(\n schema?: ParitySchema,\n): (value: unknown) => unknown {\n const stripKeys = new Set(schema?.stripKeys ?? DEFAULT_STRIP_KEYS);\n const sortKeys = schema?.sortKeys ?? DEFAULT_SORT_KEYS;\n\n const normalize = (value: unknown): unknown => {\n if (Array.isArray(value)) {\n const items = value.map(normalize);\n // Decorate-sort-undecorate: primary key = first present sortKey value,\n // secondary = canonical JSON (items are already normalized so keys are\n // sorted), original index last for a stable total order.\n return items\n .map((item, index) => ({\n item,\n index,\n primary: primarySortKey(item, sortKeys),\n json: JSON.stringify(item) ?? \"\",\n }))\n .sort(\n (a, b) =>\n compareStrings(a.primary, b.primary) ||\n compareStrings(a.json, b.json) ||\n a.index - b.index,\n )\n .map((decorated) => decorated.item);\n }\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)\n .filter((key) => !stripKeys.has(key))\n .sort()) {\n out[key] = normalize(value[key]);\n }\n return out;\n }\n return value;\n };\n\n return normalize;\n}\n\n/** The first present `sortKeys` candidate on `item` as a string, or `\"\"`. */\nfunction primarySortKey(item: unknown, sortKeys: readonly string[]): string {\n if (!isPlainObject(item)) return \"\";\n for (const key of sortKeys) {\n const candidate = item[key];\n if (\n typeof candidate === \"string\" ||\n typeof candidate === \"number\" ||\n typeof candidate === \"boolean\"\n ) {\n return String(candidate);\n }\n }\n return \"\";\n}\n\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * The parity verdict: `true` iff `a` and `b` are structurally equal. Reuses\n * `trace-replay`'s order-insensitive deep-equal (the single comparator this\n * package owns) rather than adding a second one — feed it the pair produced by\n * {@link normalizeForParity} for the normalized-JSON equality the gate rests on.\n */\nexport function parityEqual(a: unknown, b: unknown): boolean {\n return deepEqual(a, b);\n}\n\n/**\n * @deprecated Inverted name — it returns `true` when the values are EQUAL, so\n * `if (diff(old, new))` reads backwards (#278). Use {@link parityEqual}.\n * Alias kept for one minor, then removed.\n */\nexport const diff = parityEqual;\n"]}
1
+ {"version":3,"sources":["../../src/parity/index.ts"],"names":["key"],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// parity — the record → replay → normalized-diff go/no-go gate.\n//\n// The parity spike proved the mechanism already composes from in-tree\n// primitives: `recorder` captures a run's Msg sequence (the recording), `replay`\n// re-folds it purely, and `trace-replay`'s order-insensitive deep-equal diffs\n// the result. Non-determinism is a RECORD-TIME property — ids/timestamps freeze\n// into the recording, `replay` never re-mints them — so the same recording\n// replays byte-identical every time.\n//\n// This module is the thin packaging over that substrate plus the one genuinely\n// new helper, `normalizeForParity`: the finding-normalizer that strips\n// record-time non-determinism (generated ids/timestamps) and stable-key-sorts\n// collections, so an OLD-engine golden run and a NEW-engine run diff as equal\n// exactly when they are behaviourally equivalent. Every domain (audit, auth,\n// ingest, …) gets its old-vs-new parity check without reinventing the capture,\n// the normalization, and the diff.\n//\n// recordRun(runtime) → Recording // wraps the recorder\n// goldenReplay(machine, rec) → State // wraps replay\n// normalizeForParity(schema) → (finding) => norm // strip + stable-sort\n// parityEqual(a, b) → boolean // normalized deep-equal\n// ---------------------------------------------------------------------------\n\nimport type { BootingRuntime, Cmd, Machine, Sub } from \"../index\";\nimport { replay } from \"../index\";\nimport { type RecorderOptions, recorder, type Trace } from \"../recorder\";\nimport { deepEqual } from \"../trace-replay\";\n\n/**\n * A live parity recording attached to a Runtime — the go/no-go gate's golden\n * artifact. Wraps a {@link recorder}: drive the runtime, then snapshot with\n * {@link Recording.trace} (or hand the live `Recording` straight to\n * {@link goldenReplay}, which snapshots for you).\n *\n * @typeParam S - the machine's State type.\n * @typeParam M - the machine's Msg union.\n */\nexport interface Recording<S, M> {\n /**\n * Snapshot the recording so far as a replayable {@link Trace} — `loaded`\n * (boot state), the ordered non-null `msgs`, and `finalState`. Sufficient to\n * reproduce the run via {@link goldenReplay}. Fresh object each call.\n */\n trace(): Trace<S, M>;\n /** Serialize the recording as JSONL (see {@link recorder}'s `toJSONL`). */\n toJSONL(): string;\n /** Detach the underlying observer. Idempotent. */\n stop(): void;\n}\n\n/**\n * Attach a parity recording to a Runtime, reusing the `recorder`/`historyTracker`\n * capture (the boot observe for `loaded` + every non-null msg in order) rather\n * than a fresh observer path.\n *\n * Attach on the synchronous `run()` handle so the boot transition is captured\n * as `loaded`; drive the runtime; then `trace()` (or pass the handle to\n * {@link goldenReplay}). See {@link RecorderOptions} for `captureSteps` (needed\n * only for the per-step / resume-cache-prefix inspection, not for replay).\n */\nexport function recordRun<S, M extends { type: string }>(\n runtime: BootingRuntime<S, M>,\n opts?: RecorderOptions,\n): Recording<S, M> {\n const rec = recorder(runtime, opts);\n return {\n trace: () => rec.dump(),\n toJSONL: () => rec.toJSONL(),\n stop: () => rec.stop(),\n };\n}\n\n/**\n * Reproduce a recording's final state by folding its msgs through the pure\n * `replay` — `init(loaded, ctx)` then `update` per msg, never `interpret`,\n * never a `Store`, never a live subscription. Accepts a live {@link Recording}\n * (snapshotted here) or a bare {@link Trace}.\n *\n * `ctx` is the ENVIRONMENT supplied fresh at replay time (the trace is the\n * INPUT: `loaded` + `msgs`), mirroring `replayTrace`. It is optional so a\n * void-ctx machine can call `goldenReplay(machine, rec)`; a machine that reads\n * ctx passes it explicitly.\n */\nexport function goldenReplay<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n recording: Recording<S, M> | Trace<S, M>,\n ctx?: Ctx,\n): S {\n const trace = asTrace(recording);\n const { state } = replay(machine, {\n msgs: trace.msgs,\n ctx: ctx as Ctx,\n loaded: trace.loaded,\n });\n return state;\n}\n\n/** Narrow a `Recording | Trace` to a `Trace`, snapshotting a live recording. */\nfunction asTrace<S, M>(recording: Recording<S, M> | Trace<S, M>): Trace<S, M> {\n return typeof (recording as Recording<S, M>).trace === \"function\"\n ? (recording as Recording<S, M>).trace()\n : (recording as Trace<S, M>);\n}\n\n/** Configuration for {@link normalizeForParity}. */\nexport interface ParitySchema {\n /**\n * Object keys stripped wherever they appear — the record-time\n * non-determinism `replay` never re-mints. Default: `id`, `runId`,\n * `timestamp`.\n */\n readonly stripKeys?: readonly string[];\n /**\n * Candidate keys used to stable-sort arrays of objects. For each array the\n * first candidate present (as a primitive) on the items decides the order;\n * arrays with no candidate fall back to canonical-JSON order. Default:\n * `ruleId`, `selector`, `url`.\n */\n readonly sortKeys?: readonly string[];\n}\n\n// Defaults proven by the spike: strip the generated id/runId/timestamp,\n// stable-key-sort by ruleId | selector | url. Fan-out ordering is already\n// deterministic on this substrate (array order); the sort only defends against\n// intra-effect concurrency (Promise.all / Set).\nconst DEFAULT_STRIP_KEYS: readonly string[] = [\"id\", \"runId\", \"timestamp\"];\nconst DEFAULT_SORT_KEYS: readonly string[] = [\"ruleId\", \"selector\", \"url\"];\n\n/**\n * Build a finding-normalizer: a pure `(value) => normalized` that strips the\n * schema's `stripKeys`, recursively sorts object keys, and stable-key-sorts\n * arrays by the schema's `sortKeys`. Two runs that differ only in generated\n * ids/timestamps or intra-effect ordering normalize to a byte-identical value,\n * so {@link parityEqual} on the normalized pair reflects behavioural (not incidental)\n * difference.\n */\nexport function normalizeForParity(\n schema?: ParitySchema,\n): (value: unknown) => unknown {\n const stripKeys = new Set(schema?.stripKeys ?? DEFAULT_STRIP_KEYS);\n const sortKeys = schema?.sortKeys ?? DEFAULT_SORT_KEYS;\n\n const normalize = (value: unknown): unknown => {\n if (Array.isArray(value)) {\n const items = value.map(normalize);\n // Decorate-sort-undecorate: primary key = first present sortKey value,\n // secondary = canonical JSON (items are already normalized so keys are\n // sorted), original index last for a stable total order.\n return items\n .map((item, index) => ({\n item,\n index,\n primary: primarySortKey(item, sortKeys),\n json: JSON.stringify(item) ?? \"\",\n }))\n .sort(\n (a, b) =>\n compareStrings(a.primary, b.primary) ||\n compareStrings(a.json, b.json) ||\n a.index - b.index,\n )\n .map((decorated) => decorated.item);\n }\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)\n .filter((key) => !stripKeys.has(key))\n .sort()) {\n out[key] = normalize(value[key]);\n }\n return out;\n }\n return value;\n };\n\n return normalize;\n}\n\n/** The first present `sortKeys` candidate on `item` as a string, or `\"\"`. */\nfunction primarySortKey(item: unknown, sortKeys: readonly string[]): string {\n if (!isPlainObject(item)) return \"\";\n for (const key of sortKeys) {\n const candidate = item[key];\n if (\n typeof candidate === \"string\" ||\n typeof candidate === \"number\" ||\n typeof candidate === \"boolean\"\n ) {\n return String(candidate);\n }\n }\n return \"\";\n}\n\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * The parity verdict: `true` iff `a` and `b` are structurally equal. Reuses\n * `trace-replay`'s order-insensitive deep-equal (the comparator the\n * record/replay lane owns) rather than adding another boolean deep-compare —\n * feed it the pair produced by {@link normalizeForParity} for the\n * normalized-JSON equality the gate rests on.\n */\nexport function parityEqual(a: unknown, b: unknown): boolean {\n return deepEqual(a, b);\n}\n\n/**\n * @deprecated Inverted name — it returns `true` when the values are EQUAL, so\n * `if (diff(old, new))` reads backwards (#278). Use {@link parityEqual}.\n * Alias kept for one minor, then removed.\n */\nexport const diff = parityEqual;\n"]}
@@ -1,5 +1,5 @@
1
1
  import * as fc from 'fast-check';
2
- import { C as Cmd, S as Sub, M as Machine } from '../../core-BmlJOvxM.js';
2
+ import { C as Cmd, S as Sub, M as Machine } from '../../core-42kapW0n.js';
3
3
 
4
4
  /**
5
5
  * Build a Ctx value whose every property access throws. Use as the `ctx`
@@ -1,6 +1,6 @@
1
1
  export { MsgArbitraryTable, arbConstantMsg, arbGuidedSequence, arbMsg, arbMsgSequence, arbRecordMsg, stubCtxThrowingProxy } from './arbitraries/index.js';
2
2
  export { Step, foldEvents, propertyInvariant, propertyTerminates, propertyTrace } from './runners/index.js';
3
- import { C as Cmd, R as Reducer, T as Transitions, U as UpdateForm } from '../core-BmlJOvxM.js';
3
+ import { C as Cmd, R as Reducer, T as Transitions, U as UpdateForm } from '../core-42kapW0n.js';
4
4
  import 'fast-check';
5
5
 
6
6
  /**
package/dist/pbt/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { arbConstantMsg, arbGuidedSequence, arbMsg, arbMsgSequence, arbRecordMsg, stubCtxThrowingProxy } from '../chunk-7FYQGDMF.js';
2
- export { foldEvents, propertyInvariant, propertyTerminates, propertyTrace } from '../chunk-EMJTPHI7.js';
3
- import '../chunk-6VDCTR7N.js';
4
- import { msgKeysOf } from '../chunk-DYRKDCJI.js';
2
+ export { foldEvents, propertyInvariant, propertyTerminates, propertyTrace } from '../chunk-SSHDBOXF.js';
3
+ import '../chunk-D4NJBQAH.js';
4
+ import { msgKeysOf } from '../chunk-PBDYW2TU.js';
5
5
 
6
6
  // src/pbt/util/msg-keys.ts
7
7
  function msgTypeKeys(machine) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pbt/util/msg-keys.ts"],"names":[],"mappings":";;;;;;AA2CO,SAAS,YAId,OAAA,EAKoB;AACpB,EAAA,OAAO,UAAU,OAAO,CAAA;AAC1B","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// msgTypeKeys — read the Msg discriminant set at runtime from a machine's\n// Reducer record or Transitions table.\n//\n// TS Msg unions only exist at the type level; the discriminant strings are\n// erased at runtime. The reducer record / transitions table is the load-\n// bearing artifact that DOES carry the discriminants at runtime — its keys\n// (for Reducer) or any one cell's inner keys (for Transitions) are exactly\n// the set we need to build an `arbMsg` table without hand-listing them.\n//\n// Used by `arbMsg` to assert (in dev) that the user-supplied table covers\n// every Msg variant the reducer claims to handle. Strict drift detection\n// without runtime introspection of the type system.\n// ---------------------------------------------------------------------------\n\nimport {\n type Cmd,\n msgKeysOf,\n type Reducer,\n type Transitions,\n type UpdateForm,\n} from \"../../index\";\n\n/**\n * Extract the Msg discriminant set at runtime from a machine. Useful for\n * asserting that a user-supplied `MsgArbitraryTable<M>` covers every variant\n * the reducer can handle.\n *\n * Takes the MACHINE (not the bare `update` record) so the reducer-vs-\n * transitions branch honors the authoritative `__form` tag via `msgKeysOf` —\n * the same classification production `run` uses (#275).\n *\n * - **Reducer form** — keys of the top-level record ARE the Msg variants.\n * - **Transitions form** — keys of any single state's inner record. Every\n * state's inner record must cover every Msg variant (mapped-type\n * discipline), so reading the first state's keys is equivalent to reading\n * the union.\n *\n * @example\n * import { auditBackgroundMachine } from \"./reducer\";\n * msgTypeKeys(auditBackgroundMachine);\n * // → [\"start_audit\", \"stop_audit\", \"window_created\", ...]\n */\nexport function msgTypeKeys<\n S,\n M extends { type: string },\n C extends Cmd,\n>(machine: {\n update:\n | Reducer<S, M, C>\n | ([S] extends [{ type: string }] ? Transitions<S, M, C> : never);\n __form?: UpdateForm;\n}): readonly string[] {\n return msgKeysOf(machine);\n}\n"]}
1
+ {"version":3,"sources":["../../src/pbt/util/msg-keys.ts"],"names":[],"mappings":";;;;;;AA8CO,SAAS,YAId,OAAA,EAKoB;AACpB,EAAA,OAAO,UAAU,OAAO,CAAA;AAC1B","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// msgTypeKeys — read the Msg discriminant set at runtime from a machine's\n// Reducer record or Transitions table.\n//\n// TS Msg unions only exist at the type level; the discriminant strings are\n// erased at runtime. The reducer record / transitions table is the load-\n// bearing artifact that DOES carry the discriminants at runtime — its keys\n// (for Reducer) or any one cell's inner keys (for Transitions) are exactly\n// the set we need to build an `arbMsg` table without hand-listing them.\n//\n// Standalone helper: hand it a machine and it returns the Msg variants the\n// reducer/transitions record carries at runtime. `arbMsg` does NOT call it —\n// `MsgArbitraryTable<M>` enforces table coverage at compile time (adding a\n// variant without an arbitrary fails to compile, not at runtime). This helper\n// is for callers that want the discriminant set explicitly at runtime — e.g.\n// a test asserting a table covers every variant.\n// ---------------------------------------------------------------------------\n\nimport {\n type Cmd,\n msgKeysOf,\n type Reducer,\n type Transitions,\n type UpdateForm,\n} from \"../../index\";\n\n/**\n * Extract the Msg discriminant set at runtime from a machine. Useful for\n * asserting that a user-supplied `MsgArbitraryTable<M>` covers every variant\n * the reducer can handle.\n *\n * Takes the MACHINE (not the bare `update` record) so the reducer-vs-\n * transitions branch honors the authoritative `__form` tag via `msgKeysOf` —\n * the same classification production `run` uses (#275).\n *\n * - **Reducer form** — keys of the top-level record ARE the Msg variants.\n * - **Transitions form** — keys of any single state's inner record. Every\n * state's inner record must cover every Msg variant (mapped-type\n * discipline), so reading the first state's keys is equivalent to reading\n * the union.\n *\n * @example\n * import { auditBackgroundMachine } from \"./reducer\";\n * msgTypeKeys(auditBackgroundMachine);\n * // → [\"start_audit\", \"stop_audit\", \"window_created\", ...]\n */\nexport function msgTypeKeys<\n S,\n M extends { type: string },\n C extends Cmd,\n>(machine: {\n update:\n | Reducer<S, M, C>\n | ([S] extends [{ type: string }] ? Transitions<S, M, C> : never);\n __form?: UpdateForm;\n}): readonly string[] {\n return msgKeysOf(machine);\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import * as fc from 'fast-check';
2
- import { C as Cmd, S as Sub, M as Machine } from '../../core-BmlJOvxM.js';
2
+ import { C as Cmd, S as Sub, M as Machine } from '../../core-42kapW0n.js';
3
3
 
4
4
  /**
5
5
  * One unit in a fold trace — the four pieces of data a property
@@ -1,5 +1,5 @@
1
- export { foldEvents, propertyInvariant, propertyTerminates, propertyTrace } from '../../chunk-EMJTPHI7.js';
2
- import '../../chunk-6VDCTR7N.js';
3
- import '../../chunk-DYRKDCJI.js';
1
+ export { foldEvents, propertyInvariant, propertyTerminates, propertyTrace } from '../../chunk-SSHDBOXF.js';
2
+ import '../../chunk-D4NJBQAH.js';
3
+ import '../../chunk-PBDYW2TU.js';
4
4
  //# sourceMappingURL=index.js.map
5
5
  //# sourceMappingURL=index.js.map