@demlik/tea 0.1.0 → 0.1.1

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 (238) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent/index.d.ts +907 -0
  3. package/dist/agent/index.js +20 -0
  4. package/dist/agent/index.js.map +1 -0
  5. package/dist/authed-call/index.d.ts +119 -0
  6. package/dist/authed-call/index.js +148 -0
  7. package/dist/authed-call/index.js.map +1 -0
  8. package/dist/await-terminal/index.d.ts +99 -0
  9. package/dist/await-terminal/index.js +75 -0
  10. package/dist/await-terminal/index.js.map +1 -0
  11. package/dist/batch-window/index.d.ts +259 -0
  12. package/dist/batch-window/index.js +43 -0
  13. package/dist/batch-window/index.js.map +1 -0
  14. package/dist/cache/index.d.ts +2 -2
  15. package/dist/cache/index.js +2 -50
  16. package/dist/cache/index.js.map +1 -1
  17. package/dist/{chunk-CMG2KMWX.js → chunk-2WZHS4P2.js} +220 -177
  18. package/dist/chunk-2WZHS4P2.js.map +1 -0
  19. package/dist/chunk-2YOE5PIS.js +45 -0
  20. package/dist/chunk-2YOE5PIS.js.map +1 -0
  21. package/dist/chunk-3545BOYL.js +65 -0
  22. package/dist/chunk-3545BOYL.js.map +1 -0
  23. package/dist/chunk-5VNWZADP.js +49 -0
  24. package/dist/chunk-5VNWZADP.js.map +1 -0
  25. package/dist/{chunk-ZRUST6BK.js → chunk-7FYQGDMF.js} +2 -2
  26. package/dist/chunk-7FYQGDMF.js.map +1 -0
  27. package/dist/chunk-7VEG4KHB.js +202 -0
  28. package/dist/chunk-7VEG4KHB.js.map +1 -0
  29. package/dist/{chunk-PYBQREFU.js → chunk-A56GNPI7.js} +20 -6
  30. package/dist/chunk-A56GNPI7.js.map +1 -0
  31. package/dist/chunk-BNJYE4AU.js +157 -0
  32. package/dist/chunk-BNJYE4AU.js.map +1 -0
  33. package/dist/chunk-BYQUGDD3.js +16 -0
  34. package/dist/chunk-BYQUGDD3.js.map +1 -0
  35. package/dist/chunk-D5EQDLDV.js +186 -0
  36. package/dist/chunk-D5EQDLDV.js.map +1 -0
  37. package/dist/chunk-EDGDR5XX.js +60 -0
  38. package/dist/chunk-EDGDR5XX.js.map +1 -0
  39. package/dist/chunk-EIZJTYKR.js +93 -0
  40. package/dist/chunk-EIZJTYKR.js.map +1 -0
  41. package/dist/chunk-EQRUAP2A.js +56 -0
  42. package/dist/chunk-EQRUAP2A.js.map +1 -0
  43. package/dist/chunk-FH5RUIIL.js +61 -0
  44. package/dist/chunk-FH5RUIIL.js.map +1 -0
  45. package/dist/chunk-HJO6VNQI.js +15 -0
  46. package/dist/chunk-HJO6VNQI.js.map +1 -0
  47. package/dist/chunk-IFBQJYGI.js +132 -0
  48. package/dist/chunk-IFBQJYGI.js.map +1 -0
  49. package/dist/chunk-IN33Q4VJ.js +52 -0
  50. package/dist/chunk-IN33Q4VJ.js.map +1 -0
  51. package/dist/chunk-JELSZN25.js +47 -0
  52. package/dist/chunk-JELSZN25.js.map +1 -0
  53. package/dist/chunk-K575PT4S.js +58 -0
  54. package/dist/chunk-K575PT4S.js.map +1 -0
  55. package/dist/chunk-LEC4U6CX.js +91 -0
  56. package/dist/chunk-LEC4U6CX.js.map +1 -0
  57. package/dist/{chunk-Y5JM3UUH.js → chunk-NSDFA6M6.js} +75 -3
  58. package/dist/chunk-NSDFA6M6.js.map +1 -0
  59. package/dist/chunk-QGZW4W3E.js +20 -0
  60. package/dist/chunk-QGZW4W3E.js.map +1 -0
  61. package/dist/{chunk-PYOCUBQJ.js → chunk-RGTPPF4U.js} +22 -15
  62. package/dist/chunk-RGTPPF4U.js.map +1 -0
  63. package/dist/chunk-RVJFGRGK.js +222 -0
  64. package/dist/chunk-RVJFGRGK.js.map +1 -0
  65. package/dist/chunk-RVU3RBE6.js +31 -0
  66. package/dist/chunk-RVU3RBE6.js.map +1 -0
  67. package/dist/chunk-SC3FAFZN.js +53 -0
  68. package/dist/chunk-SC3FAFZN.js.map +1 -0
  69. package/dist/chunk-SINXE47Y.js +55 -0
  70. package/dist/chunk-SINXE47Y.js.map +1 -0
  71. package/dist/chunk-TAO4YOLO.js +87 -0
  72. package/dist/chunk-TAO4YOLO.js.map +1 -0
  73. package/dist/chunk-TC4HWABA.js +506 -0
  74. package/dist/chunk-TC4HWABA.js.map +1 -0
  75. package/dist/chunk-TGG26XMK.js +52 -0
  76. package/dist/chunk-TGG26XMK.js.map +1 -0
  77. package/dist/chunk-UV6UWGEO.js +147 -0
  78. package/dist/chunk-UV6UWGEO.js.map +1 -0
  79. package/dist/chunk-UYWUVEP6.js +158 -0
  80. package/dist/chunk-UYWUVEP6.js.map +1 -0
  81. package/dist/chunk-WUV3PSCZ.js +54 -0
  82. package/dist/chunk-WUV3PSCZ.js.map +1 -0
  83. package/dist/chunk-XRXKH7KA.js +268 -0
  84. package/dist/chunk-XRXKH7KA.js.map +1 -0
  85. package/dist/circuit-breaker/index.js +1 -51
  86. package/dist/circuit-breaker/index.js.map +1 -1
  87. package/dist/core-CymXZCeY.d.ts +178 -0
  88. package/dist/deadline/index.d.ts +24 -5
  89. package/dist/deadline/index.js +4 -18
  90. package/dist/deadline/index.js.map +1 -1
  91. package/dist/debounce/index.js +1 -54
  92. package/dist/debounce/index.js.map +1 -1
  93. package/dist/devtools/index.d.ts +1 -1
  94. package/dist/devtools/index.js +18 -6
  95. package/dist/devtools/index.js.map +1 -1
  96. package/dist/do/index.d.ts +1732 -92
  97. package/dist/do/index.js +677 -31
  98. package/dist/do/index.js.map +1 -1
  99. package/dist/durable-effects-2MDxDs45.d.ts +180 -0
  100. package/dist/extension/index.d.ts +31 -13
  101. package/dist/extension/index.js +5 -3
  102. package/dist/extension/index.js.map +1 -1
  103. package/dist/extension/react.js +8 -3
  104. package/dist/extension/react.js.map +1 -1
  105. package/dist/extension/subs/index.d.ts +2 -2
  106. package/dist/extension/subs/index.js.map +1 -1
  107. package/dist/extension/test-utils.js +11 -3
  108. package/dist/extension/test-utils.js.map +1 -1
  109. package/dist/fan-out/index.d.ts +239 -0
  110. package/dist/fan-out/index.js +5 -0
  111. package/dist/fan-out/index.js.map +1 -0
  112. package/dist/idempotency/adapter.d.ts +62 -0
  113. package/dist/idempotency/adapter.js +4 -0
  114. package/dist/idempotency/adapter.js.map +1 -0
  115. package/dist/idempotency/index.js +1 -56
  116. package/dist/idempotency/index.js.map +1 -1
  117. package/dist/idempotent-intake/index.d.ts +186 -0
  118. package/dist/idempotent-intake/index.js +7 -0
  119. package/dist/idempotent-intake/index.js.map +1 -0
  120. package/dist/index.d.ts +341 -191
  121. package/dist/index.js +2 -1
  122. package/dist/llm-call/index.d.ts +309 -0
  123. package/dist/llm-call/index.js +14 -0
  124. package/dist/llm-call/index.js.map +1 -0
  125. package/dist/machine-viz/index.d.ts +1 -1
  126. package/dist/machine-viz/index.js +1 -1
  127. package/dist/machine-viz/index.js.map +1 -1
  128. package/dist/mem/index.d.ts +1 -0
  129. package/dist/monitored-run/index.d.ts +253 -0
  130. package/dist/monitored-run/index.js +10 -0
  131. package/dist/monitored-run/index.js.map +1 -0
  132. package/dist/node/index.d.ts +5 -5
  133. package/dist/node/index.js.map +1 -1
  134. package/dist/paginated-walk/index.d.ts +234 -0
  135. package/dist/paginated-walk/index.js +15 -0
  136. package/dist/paginated-walk/index.js.map +1 -0
  137. package/dist/paginator/index.d.ts +271 -0
  138. package/dist/paginator/index.js +3 -0
  139. package/dist/paginator/index.js.map +1 -0
  140. package/dist/parity/index.d.ts +86 -0
  141. package/dist/parity/index.js +77 -0
  142. package/dist/parity/index.js.map +1 -0
  143. package/dist/pbt/arbitraries/index.d.ts +1 -1
  144. package/dist/pbt/arbitraries/index.js +1 -1
  145. package/dist/pbt/index.d.ts +1 -1
  146. package/dist/pbt/index.js +5 -4
  147. package/dist/pbt/index.js.map +1 -1
  148. package/dist/pbt/runners/index.d.ts +1 -1
  149. package/dist/pbt/runners/index.js +3 -2
  150. package/dist/poller/index.d.ts +288 -0
  151. package/dist/poller/index.js +79 -0
  152. package/dist/poller/index.js.map +1 -0
  153. package/dist/prediction/index.d.ts +109 -0
  154. package/dist/prediction/index.js +4 -0
  155. package/dist/prediction/index.js.map +1 -0
  156. package/dist/protocol-B5YmJr0d.d.ts +45 -0
  157. package/dist/pure/index.d.ts +2 -0
  158. package/dist/pure/index.js +4 -0
  159. package/dist/pure/index.js.map +1 -0
  160. package/dist/raft/demo.d.ts +105 -0
  161. package/dist/raft/demo.js +172 -0
  162. package/dist/raft/demo.js.map +1 -0
  163. package/dist/raft/index.d.ts +548 -0
  164. package/dist/raft/index.js +7 -0
  165. package/dist/raft/index.js.map +1 -0
  166. package/dist/raft/sim.d.ts +222 -0
  167. package/dist/raft/sim.js +8 -0
  168. package/dist/raft/sim.js.map +1 -0
  169. package/dist/raft/timeline.d.ts +46 -0
  170. package/dist/raft/timeline.js +62 -0
  171. package/dist/raft/timeline.js.map +1 -0
  172. package/dist/rate-limit/index.js +1 -43
  173. package/dist/rate-limit/index.js.map +1 -1
  174. package/dist/react/index.d.ts +2 -1
  175. package/dist/react/index.js +39 -20
  176. package/dist/react/index.js.map +1 -1
  177. package/dist/reconciler/index.d.ts +192 -0
  178. package/dist/reconciler/index.js +166 -0
  179. package/dist/reconciler/index.js.map +1 -0
  180. package/dist/recorder/index.d.ts +7 -3
  181. package/dist/recorder/index.js +1 -155
  182. package/dist/recorder/index.js.map +1 -1
  183. package/dist/resilient-call/index.d.ts +251 -0
  184. package/dist/resilient-call/index.js +13 -0
  185. package/dist/resilient-call/index.js.map +1 -0
  186. package/dist/retry-backoff/index.d.ts +42 -5
  187. package/dist/retry-backoff/index.js +1 -35
  188. package/dist/retry-backoff/index.js.map +1 -1
  189. package/dist/saga/index.d.ts +225 -0
  190. package/dist/saga/index.js +150 -0
  191. package/dist/saga/index.js.map +1 -0
  192. package/dist/snapshot/index.d.ts +334 -0
  193. package/dist/snapshot/index.js +5 -0
  194. package/dist/snapshot/index.js.map +1 -0
  195. package/dist/subs/index.d.ts +48 -10
  196. package/dist/subs/index.js +1 -1
  197. package/dist/testing/index.d.ts +107 -2
  198. package/dist/testing/index.js +204 -6
  199. package/dist/testing/index.js.map +1 -1
  200. package/dist/throttle/index.js +1 -52
  201. package/dist/throttle/index.js.map +1 -1
  202. package/dist/throttled-input/index.d.ts +372 -0
  203. package/dist/throttled-input/index.js +88 -0
  204. package/dist/throttled-input/index.js.map +1 -0
  205. package/dist/token-refresh/index.d.ts +215 -0
  206. package/dist/token-refresh/index.js +6 -0
  207. package/dist/token-refresh/index.js.map +1 -0
  208. package/dist/trace-replay/index.d.ts +11 -2
  209. package/dist/trace-replay/index.js +3 -55
  210. package/dist/trace-replay/index.js.map +1 -1
  211. package/dist/{types-CwBC5wMn.d.ts → types-N9LZbdnr.d.ts} +1 -1
  212. package/dist/with-deadline/index.d.ts +196 -0
  213. package/dist/with-deadline/index.js +130 -0
  214. package/dist/with-deadline/index.js.map +1 -0
  215. package/dist/with-resilience/index.d.ts +205 -0
  216. package/dist/with-resilience/index.js +274 -0
  217. package/dist/with-resilience/index.js.map +1 -0
  218. package/dist/with-telemetry/index.d.ts +149 -0
  219. package/dist/with-telemetry/index.js +95 -0
  220. package/dist/with-telemetry/index.js.map +1 -0
  221. package/dist/work-queue/adapter.d.ts +82 -0
  222. package/dist/work-queue/adapter.js +4 -0
  223. package/dist/work-queue/adapter.js.map +1 -0
  224. package/dist/work-queue/index.d.ts +25 -11
  225. package/dist/work-queue/index.js +1 -46
  226. package/dist/work-queue/index.js.map +1 -1
  227. package/dist/work-queue/ops.d.ts +69 -0
  228. package/dist/work-queue/ops.js +3 -0
  229. package/dist/work-queue/ops.js.map +1 -0
  230. package/dist/workflow/index.d.ts +444 -0
  231. package/dist/workflow/index.js +225 -0
  232. package/dist/workflow/index.js.map +1 -0
  233. package/package.json +162 -25
  234. package/dist/chunk-CMG2KMWX.js.map +0 -1
  235. package/dist/chunk-PYBQREFU.js.map +0 -1
  236. package/dist/chunk-PYOCUBQJ.js.map +0 -1
  237. package/dist/chunk-Y5JM3UUH.js.map +0 -1
  238. package/dist/chunk-ZRUST6BK.js.map +0 -1
@@ -1,126 +1,10 @@
1
+ import { detectUpdateForm, formOf, foldUpdates, __DEV__, deepFreeze, assertPureResult } from './chunk-BNJYE4AU.js';
1
2
  import { Result } from 'better-result';
2
3
 
3
- // src/index.ts
4
- var __DEV__ = (() => {
5
- try {
6
- const meta = import.meta;
7
- if (meta.env?.DEV !== void 0) {
8
- return meta.env.DEV;
9
- }
10
- } catch {
11
- }
12
- try {
13
- return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
14
- } catch {
15
- return false;
16
- }
17
- })();
18
- function deepFreeze(obj, seen) {
19
- if (obj === null || typeof obj !== "object") return obj;
20
- const proto = Object.getPrototypeOf(obj);
21
- if (proto !== Object.prototype && proto !== Array.prototype && proto !== null) return obj;
22
- const visited = seen ?? /* @__PURE__ */ new WeakSet();
23
- if (visited.has(obj)) return obj;
24
- visited.add(obj);
25
- Object.freeze(obj);
26
- for (const v of Object.values(obj)) deepFreeze(v, visited);
27
- return obj;
28
- }
29
- function hasFunctionValues(obj) {
30
- if (obj === null || typeof obj !== "object") return null;
31
- for (const [key, value] of Object.entries(obj)) {
32
- if (typeof value === "function") return key;
33
- }
34
- return null;
35
- }
36
- function assertPureResult(result, msgType) {
37
- if (result && typeof result.then === "function") {
38
- throw new Error(
39
- `@demlik/tea: update cell "${msgType}" returned a Promise. update must be synchronous. Move async work to interpret.`
40
- );
41
- }
42
- if (!Array.isArray(result) || result.length < 2 || !Array.isArray(result[1])) {
43
- throw new Error(
44
- `@demlik/tea: update cell "${msgType}" returned a non-tuple. Expected [State, Cmd[]], got ${typeof result}.`
45
- );
46
- }
47
- const cmds = result[1];
48
- for (const cmd of cmds) {
49
- const fnField = hasFunctionValues(cmd);
50
- if (fnField !== null) {
51
- throw new Error(
52
- `@demlik/tea: Cmd "${cmd.type}" has function field "${fnField}". Cmds must be plain data, not closures.`
53
- );
54
- }
55
- }
56
- }
57
4
  function absurd(x) {
58
5
  throw new Error(`unreachable: ${JSON.stringify(x)}`);
59
6
  }
60
- var noop = (state, _msg) => [
61
- state,
62
- []
63
- ];
64
- var Cmd = {
65
- /**
66
- * The empty Cmd array. Typed `readonly never[]` so it's assignable to any
67
- * `readonly C[]` for any `C extends Cmd`. Use in `init` returns and
68
- * Transitions cells that emit zero effects:
69
- *
70
- * init: (loaded) => [loaded ?? initial, Cmd.none],
71
- * tick: (state) => [state, Cmd.none],
72
- *
73
- * Elm's `Cmd.none` analogue. Cultural signal alongside the runtime help:
74
- * `[state, Cmd.none]` reads as intent ("this transition emits nothing"),
75
- * `[state, []]` reads as "empty array of what".
76
- *
77
- * Frozen at runtime so a downstream consumer can't `.push()` into the
78
- * shared reference.
79
- */
80
- none: Object.freeze([]),
81
- /**
82
- * Flat concat of cmd arrays. Use when a cell composes effects from
83
- * multiple conditional sources:
84
- *
85
- * return [next, Cmd.batch(
86
- * Cmd.whenDefined(state.queueItemId, (id) => ({ type: "complete", id })),
87
- * Cmd.when(state.windowId !== undefined, { type: "close_window", windowId }),
88
- * [{ type: "detach_debugger", tabId: state.tabId }],
89
- * )];
90
- *
91
- * `<const C>` keeps inline cmd literals' discriminants narrow (same
92
- * rationale as `when` / `whenDefined` above).
93
- */
94
- batch: (...arrs) => {
95
- const out = [];
96
- for (const arr of arrs) out.push(...arr);
97
- return out;
98
- },
99
- /**
100
- * Emit `cmd` wrapped in a single-element array iff `cond` is true.
101
- * Otherwise return the empty array. Spreads cleanly into the cmds array
102
- * returned by a Transitions cell.
103
- *
104
- * `const C` (TypeScript 5.0+) keeps the inferred `type:` discriminator
105
- * literal narrow when called with an object literal — so `Cmd.when(b,
106
- * { type: "x", ... })` infers as `{ type: "x", ... }`, not `{ type:
107
- * string, ... }`, and stays assignable to a discriminated-union arm
108
- * without `as const` at the call site.
109
- */
110
- when: (cond, cmd) => cond ? [cmd] : [],
111
- /**
112
- * If `value` is defined, call `build(value)` and emit the resulting cmd
113
- * wrapped in a single-element array. If `value` is `undefined`, return
114
- * the empty array. `value` is narrowed to `T` inside `build`.
115
- *
116
- * `const C` on the return type keeps the callback's object-literal
117
- * `type:` field narrow (see `when` above for the full rationale).
118
- */
119
- whenDefined: (value, build) => value !== void 0 ? [build(value)] : []
120
- };
121
- function subId(s) {
122
- return s;
123
- }
7
+ var noop = (state, _msg) => [state, []];
124
8
  var definedPortNames = /* @__PURE__ */ new Set();
125
9
  var PortNameCollisionError = class extends Error {
126
10
  name = "PortNameCollisionError";
@@ -133,6 +17,25 @@ var PortNameCollisionError = class extends Error {
133
17
  function __resetPortRegistry() {
134
18
  definedPortNames.clear();
135
19
  }
20
+ function defaultOnError(error, _context) {
21
+ setTimeout(() => {
22
+ throw error;
23
+ }, 0);
24
+ }
25
+ function normalizeSupervision(supervision) {
26
+ if (supervision === void 0) return { strategy: "stop" };
27
+ if (typeof supervision === "string") return { strategy: supervision };
28
+ return supervision;
29
+ }
30
+ var QuiescenceTimeoutError = class extends Error {
31
+ constructor(iterations) {
32
+ super(
33
+ `@demlik/tea: idle() did not reach quiescence after ${iterations} iterations \u2014 the dispatch tail kept advancing. The machine is likely livelocking (an interpret handler enqueues a follow-up Msg that enqueues another, without end). Bound the follow-up chain in the reducer, or stop the runtime.`
34
+ );
35
+ this.iterations = iterations;
36
+ }
37
+ name = "QuiescenceTimeoutError";
38
+ };
136
39
  function definePort(name) {
137
40
  let isHmr = false;
138
41
  try {
@@ -148,16 +51,44 @@ function definePort(name) {
148
51
  return { __brand: "port", name };
149
52
  }
150
53
  function defineMachine(m) {
54
+ if (m.__form === void 0) {
55
+ Object.defineProperty(m, "__form", {
56
+ value: detectUpdateForm(m.update),
57
+ enumerable: false,
58
+ writable: false,
59
+ configurable: true
60
+ });
61
+ }
151
62
  return m;
152
63
  }
64
+ function asReducer(reducer) {
65
+ return reducer;
66
+ }
153
67
  function run(machine, opts) {
154
- const { ctx, store } = opts;
68
+ const { store } = opts;
69
+ const ctx = opts.ctx ?? {};
70
+ const idleCap = opts.__idleCap ?? 1e5;
71
+ const projectEvents = opts.events ?? (() => []);
72
+ const isTerminal = opts.terminal ?? (() => false);
73
+ const onError = opts.onError ?? defaultOnError;
74
+ const reportError = (error, context) => {
75
+ try {
76
+ onError(error, context);
77
+ } catch (sinkError) {
78
+ defaultOnError(sinkError);
79
+ }
80
+ };
81
+ const supervision = normalizeSupervision(opts.supervision);
155
82
  let state;
156
83
  let bootError = null;
157
84
  let stopped = false;
158
85
  const subRegistry = /* @__PURE__ */ new Map();
159
86
  const listeners = /* @__PURE__ */ new Set();
160
87
  const observers = /* @__PURE__ */ new Set();
88
+ const bootHandlers = /* @__PURE__ */ new Set();
89
+ let booted = false;
90
+ const eventHandlers = /* @__PURE__ */ new Map();
91
+ const doneWaiters = /* @__PURE__ */ new Set();
161
92
  const portRegistry = /* @__PURE__ */ new Map();
162
93
  function portEmit(port, value) {
163
94
  const subscribers = portRegistry.get(port);
@@ -172,14 +103,7 @@ function run(machine, opts) {
172
103
  }
173
104
  const augmentedCtx = { ...ctx, emit: portEmit };
174
105
  const updateForm = machine.update;
175
- const updateMode = (() => {
176
- const firstKey = Object.keys(updateForm)[0];
177
- if (firstKey === void 0) {
178
- return "reducer";
179
- }
180
- const firstValue = updateForm[firstKey];
181
- return typeof firstValue === "function" ? "reducer" : "transitions";
182
- })();
106
+ const updateMode = formOf(machine);
183
107
  function applyUpdate(state2, msg) {
184
108
  if (__DEV__) deepFreeze(state2);
185
109
  let result;
@@ -230,7 +154,11 @@ function run(machine, opts) {
230
154
  continue;
231
155
  }
232
156
  try {
233
- const cleanup = handler(sub, ctx, enqueueDispatch);
157
+ const cleanup = handler(
158
+ sub,
159
+ ctx,
160
+ enqueueDispatch
161
+ );
234
162
  subRegistry.set(sub.id, cleanup);
235
163
  } catch (err) {
236
164
  if (firstStartError === null) firstStartError = err;
@@ -243,9 +171,13 @@ function run(machine, opts) {
243
171
  for (const cmd of cmds) {
244
172
  const handler = interpretMap[cmd.type];
245
173
  if (!handler) continue;
246
- const follow = await handler(cmd, augmentedCtx);
174
+ const follow = await handler(
175
+ cmd,
176
+ augmentedCtx
177
+ );
247
178
  if (follow !== void 0 && follow !== null) {
248
- enqueueDispatch(follow).catch(() => {
179
+ enqueueDispatch(follow).catch((error) => {
180
+ reportError(error, { phase: "follow-up" });
249
181
  });
250
182
  }
251
183
  }
@@ -270,17 +202,83 @@ function run(machine, opts) {
270
202
  }
271
203
  }
272
204
  }
205
+ function fireEvents(msg) {
206
+ if (eventHandlers.size === 0 || state === void 0) return;
207
+ let events;
208
+ try {
209
+ events = projectEvents(msg, state);
210
+ } catch (err) {
211
+ console.error(err);
212
+ return;
213
+ }
214
+ for (const event of events) {
215
+ const bucket = eventHandlers.get(event.type);
216
+ if (bucket === void 0) continue;
217
+ for (const handler of bucket) {
218
+ try {
219
+ handler(event);
220
+ } catch (err) {
221
+ console.error(err);
222
+ }
223
+ }
224
+ }
225
+ }
226
+ function fireBoot() {
227
+ booted = true;
228
+ if (bootHandlers.size === 0 || state === void 0) return;
229
+ const snapshot = state;
230
+ for (const handler of bootHandlers) {
231
+ try {
232
+ handler(snapshot);
233
+ } catch (err) {
234
+ console.error(err);
235
+ }
236
+ }
237
+ }
238
+ function settleDoneWaiters() {
239
+ if (doneWaiters.size === 0 || state === void 0) return;
240
+ if (!isTerminal(state)) return;
241
+ const snapshot = state;
242
+ const parked = [...doneWaiters];
243
+ doneWaiters.clear();
244
+ for (const resolve of parked) resolve(snapshot);
245
+ }
273
246
  async function stepDispatch(msg) {
274
247
  if (state === void 0) {
275
248
  throw new Error("@demlik/tea: runtime not booted");
276
249
  }
277
- const [next, cmds] = applyUpdate(state, msg);
250
+ let next;
251
+ let cmds;
252
+ try {
253
+ [next, cmds] = applyUpdate(state, msg);
254
+ } catch (reduceError) {
255
+ reportError(reduceError, { phase: "reduce" });
256
+ switch (supervision.strategy) {
257
+ case "restart": {
258
+ state = supervision.rehydrate(state, msg, reduceError);
259
+ if (store) await store.save(state);
260
+ reconcileSubs();
261
+ fireListeners();
262
+ fireObservers(msg);
263
+ fireEvents(msg);
264
+ settleDoneWaiters();
265
+ return;
266
+ }
267
+ case "escalate":
268
+ throw reduceError;
269
+ default:
270
+ stopped = true;
271
+ throw reduceError;
272
+ }
273
+ }
278
274
  state = next;
279
275
  if (store) await store.save(state);
280
276
  reconcileSubs();
281
277
  await runInterpret(cmds);
282
278
  fireListeners();
283
279
  fireObservers(msg);
280
+ fireEvents(msg);
281
+ settleDoneWaiters();
284
282
  }
285
283
  let pendingInitCmds = [];
286
284
  if (!store) {
@@ -300,10 +298,12 @@ function run(machine, opts) {
300
298
  reconcileSubs();
301
299
  await runInterpret(pendingInitCmds);
302
300
  fireListeners();
303
- fireObservers(null);
301
+ fireBoot();
302
+ settleDoneWaiters();
304
303
  }
305
304
  function enqueueDispatch(msg) {
306
- if (stopped) return Promise.reject(new Error("@demlik/tea: runtime stopped"));
305
+ if (stopped)
306
+ return Promise.reject(new Error("@demlik/tea: runtime stopped"));
307
307
  const next = tail.then(() => {
308
308
  if (bootError !== null) throw bootError;
309
309
  return stepDispatch(msg);
@@ -312,17 +312,30 @@ function run(machine, opts) {
312
312
  });
313
313
  return next;
314
314
  }
315
+ async function drainToQuiescence() {
316
+ for (let i = 0; i < idleCap; i++) {
317
+ const observed = tail;
318
+ await observed;
319
+ if (tail === observed) return;
320
+ }
321
+ throw new QuiescenceTimeoutError(idleCap);
322
+ }
323
+ async function dispatchToQuiescence(msg, opts2) {
324
+ await enqueueDispatch(msg);
325
+ if (opts2?.settle === "once") return;
326
+ await drainToQuiescence();
327
+ }
315
328
  const bootPromise = stepBootEffects();
316
329
  tail = bootPromise.catch((err) => {
317
330
  bootError = err;
318
331
  });
332
+ const readyPromise = bootPromise.then(
333
+ () => runtime
334
+ );
319
335
  const runtime = {
320
- dispatch: enqueueDispatch,
336
+ dispatch: dispatchToQuiescence,
337
+ dispatchOnce: enqueueDispatch,
321
338
  getState() {
322
- if (bootError !== null) throw bootError;
323
- if (state === void 0) {
324
- throw new Error("@demlik/tea: getState() called before runtime booted");
325
- }
326
339
  return state;
327
340
  },
328
341
  subscribe(listener) {
@@ -337,6 +350,36 @@ function run(machine, opts) {
337
350
  observers.delete(observer);
338
351
  };
339
352
  },
353
+ onBoot(handler) {
354
+ if (booted && state !== void 0) {
355
+ try {
356
+ handler(state);
357
+ } catch (err) {
358
+ console.error(err);
359
+ }
360
+ return () => {
361
+ };
362
+ }
363
+ bootHandlers.add(handler);
364
+ return () => {
365
+ bootHandlers.delete(handler);
366
+ };
367
+ },
368
+ on(type, handler) {
369
+ let bucket = eventHandlers.get(type);
370
+ if (bucket === void 0) {
371
+ bucket = /* @__PURE__ */ new Set();
372
+ eventHandlers.set(type, bucket);
373
+ }
374
+ const erased = handler;
375
+ bucket.add(erased);
376
+ return () => {
377
+ const set = eventHandlers.get(type);
378
+ if (set === void 0) return;
379
+ set.delete(erased);
380
+ if (set.size === 0) eventHandlers.delete(type);
381
+ };
382
+ },
340
383
  subscribePort(port, listener) {
341
384
  const key = port;
342
385
  let bucket = portRegistry.get(key);
@@ -356,11 +399,24 @@ function run(machine, opts) {
356
399
  emitPort(port, value) {
357
400
  portEmit(port, value);
358
401
  },
359
- // `ready` is the original (un-swallowed) boot promise. Idempotency
360
- // comes for free — promises settle exactly once; every read returns the
361
- // same settled value. See the boot-promise note above for why this is
362
- // distinct from `tail`.
363
- ready: bootPromise,
402
+ // `ready` is assigned just below (it must resolve to `runtime` itself,
403
+ // which can't be referenced inside its own object literal). See the
404
+ // assignment + rationale after this block.
405
+ ready: readyPromise,
406
+ idle() {
407
+ return drainToQuiescence();
408
+ },
409
+ result() {
410
+ const current = state;
411
+ return isTerminal(current) ? current : void 0;
412
+ },
413
+ done() {
414
+ const current = state;
415
+ if (isTerminal(current)) return Promise.resolve(current);
416
+ return new Promise((resolve) => {
417
+ doneWaiters.add(resolve);
418
+ });
419
+ },
364
420
  async stop() {
365
421
  stopped = true;
366
422
  await tail;
@@ -375,8 +431,8 @@ function run(machine, opts) {
375
431
  if (store && state !== void 0 && bootError === null) {
376
432
  try {
377
433
  await store.save(state);
378
- } catch (err) {
379
- console.error(err);
434
+ } catch (error) {
435
+ reportError(error, { phase: "stop-save" });
380
436
  }
381
437
  }
382
438
  }
@@ -386,10 +442,17 @@ function run(machine, opts) {
386
442
  function historyTracker(runtime, size) {
387
443
  const buffer = [];
388
444
  let stopped = false;
445
+ const push = (entry) => {
446
+ buffer.push(entry);
447
+ if (buffer.length > size) buffer.shift();
448
+ };
389
449
  const unobserve = size <= 0 ? () => {
390
450
  } : runtime.observe((msg, state) => {
391
- buffer.push({ msg, state });
392
- if (buffer.length > size) buffer.shift();
451
+ push({ msg, state });
452
+ });
453
+ const unboot = size <= 0 ? () => {
454
+ } : runtime.onBoot((state) => {
455
+ push({ msg: null, state });
393
456
  });
394
457
  return {
395
458
  snapshot() {
@@ -399,6 +462,7 @@ function historyTracker(runtime, size) {
399
462
  if (stopped) return;
400
463
  stopped = true;
401
464
  unobserve();
465
+ unboot();
402
466
  }
403
467
  };
404
468
  }
@@ -410,33 +474,12 @@ function replay(machine, opts) {
410
474
  "TEA contract violation: machine.init must return [state, []] (no Cmds) when `loaded` is non-null. Init's rehydrate branch is a pure state passthrough \u2014 the migration/parse boundary, not the boot-effect hook.\n\nRoutes for boot effects:\n - Stateless infrastructure (e.g. chrome.alarms ensure) \u2192 host module top level, outside TEA.\n - State-conditional resume (e.g. re-attach a session) \u2192 a `boot` Msg dispatched once from the host after `run(...)` returns, handled in update.\n\nSee .patterns/tea/tea-invariants.md (Invariant 2)."
411
475
  );
412
476
  }
413
- let state = initialState;
414
- const cmds = [...initCmds];
415
- const updateForm = machine.update;
416
- const updateMode = (() => {
417
- const firstKey = Object.keys(updateForm)[0];
418
- if (firstKey === void 0) return "reducer";
419
- const firstValue = updateForm[firstKey];
420
- return typeof firstValue === "function" ? "reducer" : "transitions";
421
- })();
422
- for (const msg of opts.msgs) {
423
- if (__DEV__) deepFreeze(state);
424
- let result;
425
- if (updateMode === "reducer") {
426
- result = updateForm[msg.type](
427
- state,
428
- msg
429
- );
430
- } else {
431
- const table = updateForm;
432
- const stateKey = state.type;
433
- result = table[stateKey][msg.type](state, msg);
434
- }
435
- if (__DEV__) assertPureResult(result, msg.type);
436
- const [next, emitted] = result;
437
- state = next;
438
- cmds.push(...emitted);
439
- }
477
+ const { state, cmds: foldedCmds } = foldUpdates(
478
+ machine,
479
+ initialState,
480
+ opts.msgs
481
+ );
482
+ const cmds = [...initCmds, ...foldedCmds];
440
483
  const subs = machine.subscriptions ? [...machine.subscriptions(state)] : [];
441
484
  return { state, cmds, subs };
442
485
  }
@@ -453,6 +496,6 @@ function tryInterpret(work, onOk, onErr) {
453
496
  };
454
497
  }
455
498
 
456
- export { Cmd, PortNameCollisionError, __resetPortRegistry, absurd, defineMachine, definePort, historyTracker, noop, replay, run, subId, tryInterpret };
457
- //# sourceMappingURL=chunk-CMG2KMWX.js.map
458
- //# sourceMappingURL=chunk-CMG2KMWX.js.map
499
+ export { PortNameCollisionError, QuiescenceTimeoutError, __resetPortRegistry, absurd, asReducer, defineMachine, definePort, historyTracker, noop, replay, run, tryInterpret };
500
+ //# sourceMappingURL=chunk-2WZHS4P2.js.map
501
+ //# sourceMappingURL=chunk-2WZHS4P2.js.map