@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,6 +1,34 @@
1
- import { detectUpdateForm, foldUpdates, __DEV__, deepFreeze, applyCell, assertPureResult } from './chunk-DYRKDCJI.js';
1
+ import { detectUpdateForm, foldUpdates, structuralHash, applyCellChecked } from './chunk-PBDYW2TU.js';
2
2
  import { Result } from 'better-result';
3
3
 
4
+ // src/observability.ts
5
+ function historyTracker(runtime, size) {
6
+ const buffer = [];
7
+ let stopped = false;
8
+ const push = (entry) => {
9
+ buffer.push(entry);
10
+ if (buffer.length > size) buffer.shift();
11
+ };
12
+ const unobserve = size <= 0 ? () => {
13
+ } : runtime.observe((msg, state) => {
14
+ push({ msg, state });
15
+ });
16
+ const unboot = size <= 0 ? () => {
17
+ } : runtime.onBoot((state) => {
18
+ push({ msg: null, state });
19
+ });
20
+ return {
21
+ snapshot() {
22
+ return buffer.slice();
23
+ },
24
+ stop() {
25
+ if (stopped) return;
26
+ stopped = true;
27
+ unobserve();
28
+ unboot();
29
+ }
30
+ };
31
+ }
4
32
  function absurd(x) {
5
33
  throw new Error(`unreachable: ${JSON.stringify(x)}`);
6
34
  }
@@ -8,25 +36,25 @@ var noop = (state, _msg) => [state, []];
8
36
  var definedPortNames = /* @__PURE__ */ new Set();
9
37
  var PortNameCollisionError = class extends Error {
10
38
  name = "PortNameCollisionError";
39
+ _tag = "PortNameCollisionError";
11
40
  constructor(portName) {
12
41
  super(
13
42
  `definePort: a port named "${portName}" was already defined. Each definePort call must use a unique name. If two modules need the same port, export it from one module and import it.`
14
43
  );
15
44
  }
16
45
  };
46
+ var SubIdCollisionError = class extends Error {
47
+ name = "SubIdCollisionError";
48
+ _tag = "SubIdCollisionError";
49
+ constructor(id, declaredType, conflictingType) {
50
+ super(
51
+ `@demlik/tea: Sub.id collision: id="${id}" declared as type="${declaredType}" and type="${conflictingType}"`
52
+ );
53
+ }
54
+ };
17
55
  function __resetPortRegistry() {
18
56
  definedPortNames.clear();
19
57
  }
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
58
  var QuiescenceTimeoutError = class extends Error {
31
59
  constructor(iterations) {
32
60
  super(
@@ -35,6 +63,29 @@ var QuiescenceTimeoutError = class extends Error {
35
63
  this.iterations = iterations;
36
64
  }
37
65
  name = "QuiescenceTimeoutError";
66
+ _tag = "QuiescenceTimeoutError";
67
+ };
68
+ var RuntimeDiscardNotice = class extends Error {
69
+ };
70
+ var RuntimeDiscardedError = class extends RuntimeDiscardNotice {
71
+ constructor(pendingCmds) {
72
+ super(
73
+ `@demlik/tea: runtime stopped with ${pendingCmds} Cmd(s) in flight \u2014 their results will reach no listener. In React this is usually a ctx-identity change rebuilding useMachine's runtime mid-flight: stabilize the ctx (useMemo / useState) so the runtime outlives the Cmds it started. An intentional teardown (unmount, navigation) can silence this with a run({ onError }) sink that ignores phase: "discard".`
74
+ );
75
+ this.pendingCmds = pendingCmds;
76
+ }
77
+ name = "RuntimeDiscardedError";
78
+ _tag = "RuntimeDiscardedError";
79
+ };
80
+ var DispatchDiscardedError = class extends RuntimeDiscardNotice {
81
+ constructor(msgType) {
82
+ super(
83
+ `@demlik/tea: Msg "${msgType}" arrived while the runtime was stopping and was discarded \u2014 stop() refuses new work so the drain terminates. It is the result of a Cmd that outlived its runtime; keep the runtime alive until the Cmd settles (in React: stabilize useMachine's ctx) if the transition matters.`
84
+ );
85
+ this.msgType = msgType;
86
+ }
87
+ name = "DispatchDiscardedError";
88
+ _tag = "DispatchDiscardedError";
38
89
  };
39
90
  function definePort(name) {
40
91
  let isHmr = false;
@@ -50,6 +101,18 @@ function definePort(name) {
50
101
  definedPortNames.add(name);
51
102
  return { __brand: "port", name };
52
103
  }
104
+ function schemaMigrate(schema, upcast = (raw) => raw) {
105
+ return (raw) => {
106
+ let migrated;
107
+ try {
108
+ migrated = upcast(raw);
109
+ } catch {
110
+ return null;
111
+ }
112
+ const parsed = schema.safeParse(migrated);
113
+ return parsed.success ? parsed.data : null;
114
+ };
115
+ }
53
116
  function defineMachine(m) {
54
117
  if (m.__form === void 0) {
55
118
  Object.defineProperty(m, "__form", {
@@ -64,6 +127,68 @@ function defineMachine(m) {
64
127
  function asReducer(reducer) {
65
128
  return reducer;
66
129
  }
130
+ function replay(machine, opts) {
131
+ const loaded = opts.loaded ?? null;
132
+ const [initialState, initCmds] = machine.init(loaded, opts.ctx);
133
+ if (loaded !== null && initCmds.length > 0) {
134
+ throw new Error(
135
+ "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)."
136
+ );
137
+ }
138
+ const { state, cmds: foldedCmds } = foldUpdates(
139
+ machine,
140
+ initialState,
141
+ opts.msgs
142
+ );
143
+ const cmds = [...initCmds, ...foldedCmds];
144
+ const subs = machine.subscriptions ? [...machine.subscriptions(state)] : [];
145
+ const depSubs = [];
146
+ if (machine.subs) {
147
+ for (const [index, entry] of machine.subs.entries()) {
148
+ const deps = entry.deps(state);
149
+ if (deps !== null) depSubs.push({ index, id: structuralHash(deps) });
150
+ }
151
+ }
152
+ return { state, cmds, subs, depSubs };
153
+ }
154
+ function wrapDetached(handler) {
155
+ return (cmd, ctx, dispatch) => {
156
+ if (dispatch === void 0) {
157
+ throw new Error(
158
+ "@demlik/tea: a wrapDetached handler was invoked without the injected dispatch. The kernel always supplies it; call the handler through the runtime, not directly."
159
+ );
160
+ }
161
+ return handler(cmd, ctx, dispatch);
162
+ };
163
+ }
164
+ function tryInterpret(work, onOk, onErr) {
165
+ return async (cmd, ctx) => {
166
+ const result = await Result.tryPromise({
167
+ try: () => work(cmd, ctx),
168
+ catch: (error) => error
169
+ });
170
+ return result.match({
171
+ ok: (value) => onOk(value, cmd),
172
+ err: (error) => onErr(error, cmd)
173
+ });
174
+ };
175
+ }
176
+
177
+ // src/run.ts
178
+ function defaultOnError(error, _context) {
179
+ if (error instanceof RuntimeDiscardNotice) {
180
+ console.warn(error);
181
+ return;
182
+ }
183
+ setTimeout(() => {
184
+ throw error;
185
+ }, 0);
186
+ }
187
+ function normalizeSupervision(supervision) {
188
+ if (supervision === void 0) return { strategy: "stop" };
189
+ if (typeof supervision === "string") return { strategy: supervision };
190
+ return supervision;
191
+ }
67
192
  function run(machine, opts) {
68
193
  const { store } = opts;
69
194
  const ctx = opts.ctx ?? {};
@@ -78,11 +203,22 @@ function run(machine, opts) {
78
203
  defaultOnError(sinkError);
79
204
  }
80
205
  };
206
+ function fanout(items, phase, fn) {
207
+ for (const item of items) {
208
+ try {
209
+ fn(item);
210
+ } catch (err) {
211
+ reportError(err, { phase });
212
+ }
213
+ }
214
+ }
81
215
  const supervision = normalizeSupervision(opts.supervision);
82
216
  let state;
83
217
  let bootError = null;
84
- let stopped = false;
218
+ let gate = "open";
219
+ let inFlightCmds = 0;
85
220
  const subRegistry = /* @__PURE__ */ new Map();
221
+ const depSubRegistry = /* @__PURE__ */ new Map();
86
222
  const listeners = /* @__PURE__ */ new Set();
87
223
  const observers = /* @__PURE__ */ new Set();
88
224
  const bootHandlers = /* @__PURE__ */ new Set();
@@ -93,48 +229,78 @@ function run(machine, opts) {
93
229
  function portEmit(port, value) {
94
230
  const subscribers = portRegistry.get(port);
95
231
  if (!subscribers || subscribers.size === 0) return;
96
- for (const listener of subscribers) {
232
+ fanout(subscribers, "port-emit", (listener) => listener(value));
233
+ }
234
+ const augmentedCtx = { ...ctx, emit: portEmit };
235
+ let tail = Promise.resolve();
236
+ function stopSubs(ids) {
237
+ for (const id of ids) {
238
+ const cleanup = subRegistry.get(id);
239
+ if (cleanup === void 0) continue;
97
240
  try {
98
- listener(value);
241
+ cleanup();
99
242
  } catch (err) {
100
- reportError(err, { phase: "port-emit" });
243
+ reportError(err, { phase: "sub-cleanup" });
101
244
  }
245
+ subRegistry.delete(id);
102
246
  }
103
247
  }
104
- const augmentedCtx = { ...ctx, emit: portEmit };
105
- function applyUpdate(state2, msg) {
106
- if (__DEV__) deepFreeze(state2);
107
- const result = applyCell(machine, state2, msg);
108
- if (__DEV__) assertPureResult(result, msg.type);
109
- return result;
248
+ function disposeDepSubs(indices) {
249
+ for (const index of indices) {
250
+ const running = depSubRegistry.get(index);
251
+ if (running === void 0) continue;
252
+ try {
253
+ running.dispose();
254
+ } catch (err) {
255
+ reportError(err, { phase: "sub-cleanup" });
256
+ }
257
+ depSubRegistry.delete(index);
258
+ }
259
+ }
260
+ function reconcileDepSubs() {
261
+ let firstError = null;
262
+ const subs = machine.subs;
263
+ if (!subs) return firstError;
264
+ for (const [i, entry] of subs.entries()) {
265
+ const deps = entry.deps(state);
266
+ if (deps === null) {
267
+ disposeDepSubs([i]);
268
+ continue;
269
+ }
270
+ const id = structuralHash(deps);
271
+ const running = depSubRegistry.get(i);
272
+ if (running !== void 0 && running.runningId === id) {
273
+ continue;
274
+ }
275
+ disposeDepSubs([i]);
276
+ try {
277
+ const dispose = entry.source(state, enqueueDispatch, ctx);
278
+ depSubRegistry.set(i, { runningId: id, dispose });
279
+ } catch (err) {
280
+ if (firstError === null) firstError = err;
281
+ }
282
+ }
283
+ return firstError;
110
284
  }
111
- let tail = Promise.resolve();
112
285
  function reconcileSubs() {
113
- if (!machine.subscriptions) return;
286
+ const depError = reconcileDepSubs();
287
+ if (!machine.subscriptions) {
288
+ if (depError !== null) throw depError;
289
+ return;
290
+ }
114
291
  const desired = machine.subscriptions(state);
115
292
  const desiredIds = /* @__PURE__ */ new Set();
116
293
  const desiredTypeById = /* @__PURE__ */ new Map();
117
294
  for (const sub of desired) {
118
295
  const existing = desiredTypeById.get(sub.id);
119
296
  if (existing !== void 0 && existing !== sub.type) {
120
- throw new Error(
121
- `@demlik/tea: Sub.id collision: id="${sub.id}" declared as type="${existing}" and type="${sub.type}"`
122
- );
297
+ throw new SubIdCollisionError(sub.id, existing, sub.type);
123
298
  }
124
299
  desiredTypeById.set(sub.id, sub.type);
125
300
  }
126
301
  for (const sub of desired) desiredIds.add(sub.id);
127
- for (const [id, cleanup] of subRegistry) {
128
- if (!desiredIds.has(id)) {
129
- try {
130
- cleanup();
131
- } catch (err) {
132
- reportError(err, { phase: "sub-cleanup" });
133
- }
134
- subRegistry.delete(id);
135
- }
136
- }
137
- let firstStartError = null;
302
+ stopSubs([...subRegistry.keys()].filter((id) => !desiredIds.has(id)));
303
+ let firstStartError = depError;
138
304
  for (const sub of desired) {
139
305
  if (subRegistry.has(sub.id)) continue;
140
306
  const handler = machine.subscribe?.[sub.type];
@@ -155,40 +321,43 @@ function run(machine, opts) {
155
321
  if (firstStartError !== null) throw firstStartError;
156
322
  }
157
323
  const interpretMap = machine.interpret ?? {};
324
+ const interpretDispatch = (msg) => {
325
+ enqueueDispatch(msg).catch(reportUndelivered);
326
+ };
327
+ function reportUndelivered(error) {
328
+ reportError(error, {
329
+ phase: error instanceof DispatchDiscardedError ? "discard" : "follow-up"
330
+ });
331
+ }
332
+ function trackInFlight(work) {
333
+ inFlightCmds++;
334
+ return Promise.resolve(work).finally(() => {
335
+ inFlightCmds--;
336
+ });
337
+ }
158
338
  async function runInterpret(cmds) {
159
339
  for (const cmd of cmds) {
160
340
  const handler = interpretMap[cmd.type];
161
341
  if (!handler) continue;
162
- const follow = await handler(
163
- cmd,
164
- augmentedCtx
342
+ const follow = await trackInFlight(
343
+ handler(
344
+ cmd,
345
+ augmentedCtx,
346
+ interpretDispatch
347
+ )
165
348
  );
166
349
  if (follow !== void 0 && follow !== null) {
167
- enqueueDispatch(follow).catch((error) => {
168
- reportError(error, { phase: "follow-up" });
169
- });
350
+ enqueueDispatch(follow).catch(reportUndelivered);
170
351
  }
171
352
  }
172
353
  }
173
354
  function fireListeners() {
174
- for (const listener of listeners) {
175
- try {
176
- listener();
177
- } catch (err) {
178
- reportError(err, { phase: "listener" });
179
- }
180
- }
355
+ fanout(listeners, "listener", (listener) => listener());
181
356
  }
182
357
  function fireObservers(msg) {
183
358
  if (observers.size === 0 || state === void 0) return;
184
359
  const snapshot = state;
185
- for (const observer of observers) {
186
- try {
187
- observer(msg, snapshot);
188
- } catch (err) {
189
- reportError(err, { phase: "observer" });
190
- }
191
- }
360
+ fanout(observers, "observer", (observer) => observer(msg, snapshot));
192
361
  }
193
362
  function fireEvents(msg) {
194
363
  if (eventHandlers.size === 0 || state === void 0) return;
@@ -202,26 +371,14 @@ function run(machine, opts) {
202
371
  for (const event of events) {
203
372
  const bucket = eventHandlers.get(event.type);
204
373
  if (bucket === void 0) continue;
205
- for (const handler of bucket) {
206
- try {
207
- handler(event);
208
- } catch (err) {
209
- reportError(err, { phase: "event" });
210
- }
211
- }
374
+ fanout(bucket, "event", (handler) => handler(event));
212
375
  }
213
376
  }
214
377
  function fireBoot() {
215
378
  booted = true;
216
379
  if (bootHandlers.size === 0 || state === void 0) return;
217
380
  const snapshot = state;
218
- for (const handler of bootHandlers) {
219
- try {
220
- handler(snapshot);
221
- } catch (err) {
222
- reportError(err, { phase: "boot" });
223
- }
224
- }
381
+ fanout(bootHandlers, "boot", (handler) => handler(snapshot));
225
382
  }
226
383
  function settleDoneWaiters() {
227
384
  if (doneWaiters.size === 0 || state === void 0) return;
@@ -245,10 +402,19 @@ function run(machine, opts) {
245
402
  if (state === void 0) {
246
403
  throw new Error("@demlik/tea: runtime not booted");
247
404
  }
405
+ if (machine.identity !== void 0) {
406
+ const addressed = machine.identity.ofMsg(msg);
407
+ if (addressed !== void 0) {
408
+ const own = machine.identity.ofState(state);
409
+ if (own !== void 0 && structuralHash(addressed) !== structuralHash(own)) {
410
+ return;
411
+ }
412
+ }
413
+ }
248
414
  let next;
249
415
  let cmds;
250
416
  try {
251
- [next, cmds] = applyUpdate(state, msg);
417
+ [next, cmds] = applyCellChecked(machine, state, msg);
252
418
  } catch (reduceError) {
253
419
  reportError(reduceError, { phase: "reduce" });
254
420
  switch (supervision.strategy) {
@@ -259,7 +425,7 @@ function run(machine, opts) {
259
425
  case "escalate":
260
426
  throw reduceError;
261
427
  default:
262
- stopped = true;
428
+ gate = "closed";
263
429
  throw reduceError;
264
430
  }
265
431
  }
@@ -287,8 +453,11 @@ function run(machine, opts) {
287
453
  settleDoneWaiters();
288
454
  }
289
455
  function enqueueDispatch(msg) {
290
- if (stopped)
291
- return Promise.reject(new Error("@demlik/tea: runtime stopped"));
456
+ if (gate !== "open") {
457
+ return Promise.reject(
458
+ gate === "draining" ? new DispatchDiscardedError(msg.type) : new Error("@demlik/tea: runtime stopped")
459
+ );
460
+ }
292
461
  const next = tail.then(() => {
293
462
  if (bootError !== null) throw bootError;
294
463
  return stepDispatch(msg);
@@ -337,11 +506,8 @@ function run(machine, opts) {
337
506
  },
338
507
  onBoot(handler) {
339
508
  if (booted && state !== void 0) {
340
- try {
341
- handler(state);
342
- } catch (err) {
343
- reportError(err, { phase: "boot" });
344
- }
509
+ const snapshot = state;
510
+ fanout([handler], "boot", (h) => h(snapshot));
345
511
  return () => {
346
512
  };
347
513
  }
@@ -384,9 +550,6 @@ function run(machine, opts) {
384
550
  emitPort(port, value) {
385
551
  portEmit(port, value);
386
552
  },
387
- // `ready` is assigned just below (it must resolve to `runtime` itself,
388
- // which can't be referenced inside its own object literal). See the
389
- // assignment + rationale after this block.
390
553
  ready: readyPromise,
391
554
  idle() {
392
555
  return drainToQuiescence();
@@ -403,16 +566,16 @@ function run(machine, opts) {
403
566
  });
404
567
  },
405
568
  async stop() {
406
- stopped = true;
407
- await tail;
408
- for (const [id, cleanup] of subRegistry) {
409
- try {
410
- cleanup();
411
- } catch (err) {
412
- reportError(err, { phase: "sub-cleanup" });
413
- }
414
- subRegistry.delete(id);
569
+ if (gate === "open") gate = "draining";
570
+ if (inFlightCmds > 0) {
571
+ reportError(new RuntimeDiscardedError(inFlightCmds), {
572
+ phase: "discard"
573
+ });
415
574
  }
575
+ await tail;
576
+ gate = "closed";
577
+ stopSubs([...subRegistry.keys()]);
578
+ disposeDepSubs([...depSubRegistry.keys()]);
416
579
  if (store && state !== void 0 && bootError === null) {
417
580
  try {
418
581
  await store.save(state);
@@ -424,63 +587,7 @@ function run(machine, opts) {
424
587
  };
425
588
  return runtime;
426
589
  }
427
- function historyTracker(runtime, size) {
428
- const buffer = [];
429
- let stopped = false;
430
- const push = (entry) => {
431
- buffer.push(entry);
432
- if (buffer.length > size) buffer.shift();
433
- };
434
- const unobserve = size <= 0 ? () => {
435
- } : runtime.observe((msg, state) => {
436
- push({ msg, state });
437
- });
438
- const unboot = size <= 0 ? () => {
439
- } : runtime.onBoot((state) => {
440
- push({ msg: null, state });
441
- });
442
- return {
443
- snapshot() {
444
- return buffer.slice();
445
- },
446
- stop() {
447
- if (stopped) return;
448
- stopped = true;
449
- unobserve();
450
- unboot();
451
- }
452
- };
453
- }
454
- function replay(machine, opts) {
455
- const loaded = opts.loaded ?? null;
456
- const [initialState, initCmds] = machine.init(loaded, opts.ctx);
457
- if (loaded !== null && initCmds.length > 0) {
458
- throw new Error(
459
- "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)."
460
- );
461
- }
462
- const { state, cmds: foldedCmds } = foldUpdates(
463
- machine,
464
- initialState,
465
- opts.msgs
466
- );
467
- const cmds = [...initCmds, ...foldedCmds];
468
- const subs = machine.subscriptions ? [...machine.subscriptions(state)] : [];
469
- return { state, cmds, subs };
470
- }
471
- function tryInterpret(work, onOk, onErr) {
472
- return async (cmd, ctx) => {
473
- const result = await Result.tryPromise({
474
- try: () => work(cmd, ctx),
475
- catch: (error) => error
476
- });
477
- return result.match({
478
- ok: (value) => onOk(value, cmd),
479
- err: (error) => onErr(error, cmd)
480
- });
481
- };
482
- }
483
590
 
484
- export { PortNameCollisionError, QuiescenceTimeoutError, __resetPortRegistry, absurd, asReducer, defineMachine, definePort, historyTracker, noop, replay, run, tryInterpret };
485
- //# sourceMappingURL=chunk-6VDCTR7N.js.map
486
- //# sourceMappingURL=chunk-6VDCTR7N.js.map
591
+ export { DispatchDiscardedError, PortNameCollisionError, QuiescenceTimeoutError, RuntimeDiscardNotice, RuntimeDiscardedError, SubIdCollisionError, __resetPortRegistry, absurd, asReducer, defineMachine, definePort, historyTracker, noop, replay, run, schemaMigrate, tryInterpret, wrapDetached };
592
+ //# sourceMappingURL=chunk-D4NJBQAH.js.map
593
+ //# sourceMappingURL=chunk-D4NJBQAH.js.map