@demlik/tea 0.20.0 → 0.22.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 (130) hide show
  1. package/README.md +11 -2
  2. package/dist/agent/index.d.ts +36 -23
  3. package/dist/agent/index.js +12 -10
  4. package/dist/{chunk-UHAQ4PI6.js → chunk-2A5XTKQQ.js} +205 -59
  5. package/dist/chunk-2A5XTKQQ.js.map +1 -0
  6. package/dist/{chunk-JAKM53JF.js → chunk-7K7SEYCH.js} +16 -15
  7. package/dist/chunk-7K7SEYCH.js.map +1 -0
  8. package/dist/{chunk-RKVUVALV.js → chunk-A3QPMX5M.js} +31 -9
  9. package/dist/chunk-A3QPMX5M.js.map +1 -0
  10. package/dist/{chunk-DVNXEB7V.js → chunk-BVFS3ICC.js} +3 -3
  11. package/dist/chunk-BVFS3ICC.js.map +1 -0
  12. package/dist/{chunk-FTCYY2DO.js → chunk-CPE5ZQXQ.js} +3 -3
  13. package/dist/{chunk-FTCYY2DO.js.map → chunk-CPE5ZQXQ.js.map} +1 -1
  14. package/dist/chunk-DEAPNMKO.js +11 -0
  15. package/dist/chunk-DEAPNMKO.js.map +1 -0
  16. package/dist/{chunk-XWIE5V7R.js → chunk-ETRNSJCR.js} +4 -4
  17. package/dist/{chunk-XWIE5V7R.js.map → chunk-ETRNSJCR.js.map} +1 -1
  18. package/dist/chunk-FXWDDNGD.js +98 -0
  19. package/dist/chunk-FXWDDNGD.js.map +1 -0
  20. package/dist/chunk-GFGOQIEC.js +10 -0
  21. package/dist/chunk-GFGOQIEC.js.map +1 -0
  22. package/dist/{chunk-2R62Q5UK.js → chunk-IAHEYPYJ.js} +3 -3
  23. package/dist/{chunk-2R62Q5UK.js.map → chunk-IAHEYPYJ.js.map} +1 -1
  24. package/dist/{chunk-NGYFBD5G.js → chunk-J5JNLXO7.js} +3 -3
  25. package/dist/{chunk-NGYFBD5G.js.map → chunk-J5JNLXO7.js.map} +1 -1
  26. package/dist/{chunk-72CN3VPY.js → chunk-NFOGYDRS.js} +5 -5
  27. package/dist/{chunk-72CN3VPY.js.map → chunk-NFOGYDRS.js.map} +1 -1
  28. package/dist/{chunk-A73IJ27X.js → chunk-R2YNDRGL.js} +3 -3
  29. package/dist/{chunk-A73IJ27X.js.map → chunk-R2YNDRGL.js.map} +1 -1
  30. package/dist/{chunk-5HVKDHY6.js → chunk-R5NXOPL2.js} +3 -3
  31. package/dist/{chunk-5HVKDHY6.js.map → chunk-R5NXOPL2.js.map} +1 -1
  32. package/dist/chunk-RMBZ7YH7.js +52 -0
  33. package/dist/chunk-RMBZ7YH7.js.map +1 -0
  34. package/dist/{chunk-AIXC33SE.js → chunk-SHXUSTHM.js} +62 -15
  35. package/dist/chunk-SHXUSTHM.js.map +1 -0
  36. package/dist/{chunk-TAKPC6SD.js → chunk-UGTC33VH.js} +4 -4
  37. package/dist/{chunk-TAKPC6SD.js.map → chunk-UGTC33VH.js.map} +1 -1
  38. package/dist/{chunk-EA47MI5Q.js → chunk-VH5MYJ2K.js} +4 -4
  39. package/dist/{chunk-EA47MI5Q.js.map → chunk-VH5MYJ2K.js.map} +1 -1
  40. package/dist/{chunk-2774I242.js → chunk-WZEDIUWR.js} +21 -20
  41. package/dist/chunk-WZEDIUWR.js.map +1 -0
  42. package/dist/{chunk-TGJLXZAU.js → chunk-ZRRET3RP.js} +11 -11
  43. package/dist/chunk-ZRRET3RP.js.map +1 -0
  44. package/dist/{core-CtvAyPpH.d.ts → core-DNDAEhO2.d.ts} +9 -12
  45. package/dist/devtools/index.d.ts +4 -4
  46. package/dist/do/index.d.ts +103 -38
  47. package/dist/do/index.js +40 -21
  48. package/dist/do/index.js.map +1 -1
  49. package/dist/{drive-loop-Dw1sGSsW.d.ts → drive-loop-DlOEv49c.d.ts} +2 -2
  50. package/dist/{durable-effects-Ump5lSdO.d.ts → durable-effects-wLWEdy9H.d.ts} +1 -1
  51. package/dist/effect/index.d.ts +121 -28
  52. package/dist/effect/index.js +45 -31
  53. package/dist/effect/index.js.map +1 -1
  54. package/dist/extension/index.d.ts +3 -3
  55. package/dist/extension/index.js +2 -2
  56. package/dist/extension/index.js.map +1 -1
  57. package/dist/flow/index.d.ts +17 -13
  58. package/dist/flow/index.js +15 -12
  59. package/dist/flow/index.js.map +1 -1
  60. package/dist/idempotency/index.d.ts +3 -3
  61. package/dist/idempotency/index.js +3 -3
  62. package/dist/{index-CrcoVfd_.d.ts → index-9P1PaXIV.d.ts} +2 -2
  63. package/dist/{index-D3NY3VCN.d.ts → index-BBLI2WWN.d.ts} +1 -1
  64. package/dist/{index-Dl71V7kt.d.ts → index-C1WhBZ6a.d.ts} +4 -5
  65. package/dist/{index-CKtEKGyp.d.ts → index-C92SgNWz.d.ts} +3 -3
  66. package/dist/{index-CZZ3HI-Z.d.ts → index-CAsUE_d1.d.ts} +4 -4
  67. package/dist/{index-EIgfl83J.d.ts → index-CO34esWT.d.ts} +2 -2
  68. package/dist/{index-Bk2hBUdK.d.ts → index-COeX9Bmz.d.ts} +11 -9
  69. package/dist/{index-CVU7SK3a.d.ts → index-Cg0QXnzf.d.ts} +2 -2
  70. package/dist/index.d.ts +9 -9
  71. package/dist/index.js +2 -2
  72. package/dist/jev/index.d.ts +15 -15
  73. package/dist/jev/index.js +5 -5
  74. package/dist/jev/index.js.map +1 -1
  75. package/dist/laws-DTtCDDlU.d.ts +135 -0
  76. package/dist/loop-DReKGV1X.d.ts +70 -0
  77. package/dist/{machine-Dtre2TmN.d.ts → machine-C0WhOAyB.d.ts} +7 -7
  78. package/dist/machine-viz/index.d.ts +1 -1
  79. package/dist/machine-viz/index.js +2 -2
  80. package/dist/mem/index.d.ts +4 -3
  81. package/dist/mem/index.js +2 -2
  82. package/dist/mem/index.js.map +1 -1
  83. package/dist/node/index.d.ts +57 -9
  84. package/dist/node/index.js +175 -68
  85. package/dist/node/index.js.map +1 -1
  86. package/dist/otel/index.d.ts +28 -12
  87. package/dist/otel/index.js +25 -21
  88. package/dist/otel/index.js.map +1 -1
  89. package/dist/paginate/index.d.ts +4 -4
  90. package/dist/paginate/index.js +5 -5
  91. package/dist/parity/index.d.ts +11 -15
  92. package/dist/parity/index.js +4 -9
  93. package/dist/parity/index.js.map +1 -1
  94. package/dist/pbt/index.d.ts +279 -67
  95. package/dist/pbt/index.js +299 -58
  96. package/dist/pbt/index.js.map +1 -1
  97. package/dist/persistence/index.d.ts +10 -12
  98. package/dist/persistence/index.js +4 -4
  99. package/dist/promise/index.d.ts +118 -21
  100. package/dist/promise/index.js +6 -4
  101. package/dist/react/index.d.ts +34 -21
  102. package/dist/react/index.js +271 -42
  103. package/dist/react/index.js.map +1 -1
  104. package/dist/resilience/index.d.ts +6 -6
  105. package/dist/resilience/index.js +5 -5
  106. package/dist/{run-fiBe7pOG.d.ts → run-Cnw4qXj2.d.ts} +105 -23
  107. package/dist/{runtime-types-DEluQgGO.d.ts → runtime-types-CP-ykwgf.d.ts} +157 -29
  108. package/dist/testing/effect/index.d.ts +162 -9
  109. package/dist/testing/effect/index.js +110 -10
  110. package/dist/testing/effect/index.js.map +1 -1
  111. package/dist/testing/index.d.ts +6 -8
  112. package/dist/testing/index.js +4 -24
  113. package/dist/testing/index.js.map +1 -1
  114. package/dist/testing/promise/index.d.ts +4 -4
  115. package/dist/testing/promise/index.js +5 -5
  116. package/dist/timing/index.d.ts +4 -4
  117. package/dist/timing/index.js +4 -4
  118. package/dist/{types-CXNzlbAg.d.ts → types-S53WDy_h.d.ts} +1 -1
  119. package/dist/work-queue/index.d.ts +4 -4
  120. package/dist/work-queue/index.js +2 -2
  121. package/package.json +19 -17
  122. package/dist/chunk-2774I242.js.map +0 -1
  123. package/dist/chunk-AIXC33SE.js.map +0 -1
  124. package/dist/chunk-DVNXEB7V.js.map +0 -1
  125. package/dist/chunk-HX3C6LDK.js +0 -34
  126. package/dist/chunk-HX3C6LDK.js.map +0 -1
  127. package/dist/chunk-JAKM53JF.js.map +0 -1
  128. package/dist/chunk-RKVUVALV.js.map +0 -1
  129. package/dist/chunk-TGJLXZAU.js.map +0 -1
  130. package/dist/chunk-UHAQ4PI6.js.map +0 -1
@@ -1,60 +1,289 @@
1
- import { useRef, useMemo, useReducer, useSyncExternalStore, useEffect } from 'react';
1
+ import { builtinRunners } from '../chunk-GFGOQIEC.js';
2
+ import { readInstalledState } from '../chunk-DEAPNMKO.js';
3
+ import * as React from 'react';
4
+ import { useRef, useMemo, useSyncExternalStore, useInsertionEffect, useEffect } from 'react';
2
5
 
6
+ // src/react/sub-gate.ts
7
+ var GatedSub = class {
8
+ constructor(begin) {
9
+ this.begin = begin;
10
+ }
11
+ begin;
12
+ dispose = null;
13
+ lastDisposal = void 0;
14
+ start() {
15
+ this.dispose ??= this.begin();
16
+ }
17
+ /** Returns the cleanup still settling, so `stop()` can wait for it. */
18
+ stop() {
19
+ const dispose = this.dispose;
20
+ this.dispose = null;
21
+ if (dispose !== null) this.lastDisposal = dispose();
22
+ return this.lastDisposal;
23
+ }
24
+ /**
25
+ * A hide. The engine is not waiting on this cleanup, so a rejection would go
26
+ * unhandled: it goes to `raise`, and `stop()` later waits on a settled promise.
27
+ */
28
+ hide(raise) {
29
+ this.lastDisposal = this.stop()?.then(void 0, raise);
30
+ }
31
+ };
32
+ var raiseGlobally = (error) => {
33
+ queueMicrotask(() => {
34
+ throw error;
35
+ });
36
+ };
37
+ var SubGate = class {
38
+ /** @param raise where an async cleanup's rejection at a hide goes. */
39
+ constructor(raise = raiseGlobally) {
40
+ this.raise = raise;
41
+ }
42
+ raise;
43
+ isOpen = false;
44
+ subs = /* @__PURE__ */ new Set();
45
+ /**
46
+ * The engine started a Sub. `begin` runs now if the gate is open, so a runner
47
+ * that throws here throws to the engine, as it would without the gate.
48
+ */
49
+ admit(begin) {
50
+ const sub = new GatedSub(begin);
51
+ if (this.isOpen) sub.start();
52
+ this.subs.add(sub);
53
+ return () => {
54
+ this.subs.delete(sub);
55
+ return sub.stop();
56
+ };
57
+ }
58
+ open() {
59
+ this.isOpen = true;
60
+ this.forEachSub((sub) => sub.start());
61
+ }
62
+ close() {
63
+ this.isOpen = false;
64
+ this.forEachSub((sub) => sub.hide(this.raise));
65
+ }
66
+ /**
67
+ * One bad Sub never strands the others. The engine is not on the stack here,
68
+ * so its sink cannot hear a throw: the first one is thrown after the pass, to
69
+ * the error boundary from an effect and to the global handler from a microtask.
70
+ */
71
+ forEachSub(fn) {
72
+ const errors = [];
73
+ for (const sub of this.subs) {
74
+ try {
75
+ fn(sub);
76
+ } catch (error) {
77
+ errors.push(error);
78
+ }
79
+ }
80
+ if (errors.length > 0) throw errors[0];
81
+ }
82
+ };
83
+
84
+ // src/react/mounted-run.ts
85
+ var stoppedError = () => new Error("@demlik/tea: runtime stopped");
86
+ var MountedRun = class {
87
+ /**
88
+ * @param startRun boots the run, its Sub runners going through `subs`.
89
+ * @param initial the value React reads until the run has told it a State.
90
+ * @param cleanupMayBeAHide whether this React can run a passive cleanup on a
91
+ * hide, and so runs the insertion cleanup on every delete.
92
+ */
93
+ constructor(startRun, initial, cleanupMayBeAHide2) {
94
+ this.startRun = startRun;
95
+ this.cleanupMayBeAHide = cleanupMayBeAHide2;
96
+ this.snapshot = initial;
97
+ }
98
+ startRun;
99
+ cleanupMayBeAHide;
100
+ snapshot;
101
+ phase = { type: "not-started" };
102
+ // The run's booted handle: the only thing with a `getState`. `null` until its
103
+ // `ready` resolves, and for good if its boot fails.
104
+ booted = null;
105
+ // An effect is standing, so the component is on screen.
106
+ connected = false;
107
+ held = [];
108
+ subs = new SubGate();
109
+ listeners = /* @__PURE__ */ new Set();
110
+ getSnapshot = () => this.snapshot;
111
+ subscribe = (listener) => {
112
+ this.listeners.add(listener);
113
+ return () => {
114
+ this.listeners.delete(listener);
115
+ };
116
+ };
117
+ /**
118
+ * Stable for the life of this object. A hide changes nothing about it: the run
119
+ * is still there. A Msg dispatched before the first effect (a child's effects
120
+ * run before its parent's, and a tree first mounted hidden runs none) waits
121
+ * for the run, in order.
122
+ */
123
+ dispatch = (msg) => {
124
+ switch (this.phase.type) {
125
+ case "live":
126
+ case "leaving":
127
+ return this.phase.handle.dispatch(msg);
128
+ case "stopped":
129
+ return Promise.reject(stoppedError());
130
+ case "not-started":
131
+ return new Promise((resolve, reject) => {
132
+ this.held.push({ msg, resolve, reject });
133
+ });
134
+ }
135
+ };
136
+ /** The effect ran: the component is on screen. */
137
+ connect() {
138
+ this.connected = true;
139
+ this.subs.open();
140
+ switch (this.phase.type) {
141
+ case "live":
142
+ return;
143
+ case "leaving":
144
+ this.phase = { ...this.phase, type: "live" };
145
+ return;
146
+ case "not-started":
147
+ case "stopped":
148
+ this.start();
149
+ }
150
+ }
151
+ /** The effect's cleanup ran. Act on it unless the effect runs again first. */
152
+ disconnect() {
153
+ this.connected = false;
154
+ queueMicrotask(() => {
155
+ if (this.connected) return;
156
+ if (this.phase.type === "leaving" || !this.cleanupMayBeAHide) this.stop();
157
+ else this.subs.close();
158
+ });
159
+ }
160
+ /**
161
+ * The component is gone for good: this runs from `useInsertionEffect`'s
162
+ * cleanup, which neither Strict Mode's double run nor `<Activity>` calls. Before
163
+ * React 19.2 a deleted hidden tree never gets here, and `disconnect` stops it.
164
+ */
165
+ retire() {
166
+ switch (this.phase.type) {
167
+ case "not-started": {
168
+ this.phase = { type: "stopped" };
169
+ const held = this.held;
170
+ this.held = [];
171
+ for (const { reject } of held) reject(stoppedError());
172
+ return;
173
+ }
174
+ case "live":
175
+ if (this.connected) this.phase = { ...this.phase, type: "leaving" };
176
+ else this.stop();
177
+ return;
178
+ default:
179
+ return;
180
+ }
181
+ }
182
+ start() {
183
+ const handle = this.startRun(this.subs);
184
+ this.booted = null;
185
+ const unlisten = handle.subscribe(() => {
186
+ if (this.booted !== null) {
187
+ this.publish(this.booted.getState());
188
+ return;
189
+ }
190
+ const installed = readInstalledState(handle);
191
+ if (installed !== void 0) this.publish(installed);
192
+ });
193
+ this.phase = { type: "live", handle, unlisten };
194
+ handle.ready.then(
195
+ (booted) => {
196
+ if (!("handle" in this.phase) || this.phase.handle !== handle) return;
197
+ this.booted = booted;
198
+ this.publish(booted.getState());
199
+ },
200
+ () => {
201
+ }
202
+ );
203
+ const held = this.held;
204
+ this.held = [];
205
+ for (const { msg, resolve, reject } of held) {
206
+ handle.dispatch(msg).then(resolve, reject);
207
+ }
208
+ }
209
+ stop() {
210
+ if (!("handle" in this.phase)) return;
211
+ const { handle, unlisten } = this.phase;
212
+ this.phase = { type: "stopped" };
213
+ unlisten();
214
+ void handle.stop();
215
+ }
216
+ publish(state) {
217
+ this.snapshot = state;
218
+ for (const listener of this.listeners) listener();
219
+ }
220
+ };
221
+
222
+ // src/react/index.ts
223
+ var cleanupMayBeAHide = "Activity" in React;
3
224
  function useMachine(machine, opts) {
4
225
  const run = opts.run;
5
226
  const ctx = opts.ctx;
6
227
  const store = opts.store;
7
228
  const handlersRef = useRef(opts);
8
229
  handlersRef.current = opts;
9
- const handlers = useMemo(
10
- () => ({
11
- interpret: latestTable(
12
- () => handlersRef.current.interpret
13
- ),
14
- subscribe: latestTable(() => handlersRef.current.subscribe)
15
- }),
230
+ const interpret = useMemo(
231
+ () => latestTable(
232
+ () => handlersRef.current.interpret
233
+ ),
16
234
  []
17
235
  );
18
- const booting = useMemo(
19
- () => run(machine, {
20
- ctx,
21
- store,
22
- ...handlers
23
- }),
24
- [machine, run, ctx, store, handlers]
236
+ const onErrorRef = useRef(opts.onError);
237
+ onErrorRef.current = opts.onError;
238
+ const onError = useMemo(
239
+ () => (error, context) => {
240
+ if (onErrorRef.current === void 0) throw error;
241
+ onErrorRef.current(error, context);
242
+ },
243
+ []
25
244
  );
26
- const readyRef = useRef(null);
27
- const [, markBooted] = useReducer((tick) => tick + 1, 0);
28
- const preliminaryState = useMemo(
29
- () => machine.init(null, ctx)[0],
30
- [machine, ctx]
245
+ const mounted = useMemo(
246
+ () => new MountedRun(
247
+ (subs) => run(machine, {
248
+ ctx,
249
+ store,
250
+ onError,
251
+ interpret,
252
+ subscribe: gatedRunners(
253
+ subs,
254
+ () => handlersRef.current.subscribe
255
+ )
256
+ }),
257
+ machine.init(null, ctx)[0],
258
+ cleanupMayBeAHide
259
+ ),
260
+ [machine, run, ctx, store, onError, interpret]
31
261
  );
32
- const getSnapshot = () => readyRef.current !== null ? readyRef.current.getState() : preliminaryState;
33
262
  const state = useSyncExternalStore(
34
- booting.subscribe,
35
- getSnapshot,
36
- getSnapshot
263
+ mounted.subscribe,
264
+ mounted.getSnapshot,
265
+ mounted.getSnapshot
37
266
  );
267
+ useInsertionEffect(() => () => mounted.retire(), [mounted]);
38
268
  useEffect(() => {
39
- let live = true;
40
- booting.ready.then(
41
- (r) => {
42
- if (live) {
43
- readyRef.current = r;
44
- markBooted();
45
- }
46
- },
47
- () => {
269
+ mounted.connect();
270
+ return () => mounted.disconnect();
271
+ }, [mounted]);
272
+ return [state, mounted.dispatch];
273
+ }
274
+ function gatedRunners(subs, current) {
275
+ const runnerFor = (type) => current()?.[type] ?? builtinRunners[type];
276
+ return new Proxy(
277
+ {},
278
+ {
279
+ get: (_target, type) => {
280
+ const first = runnerFor(type);
281
+ if (first === void 0) return void 0;
282
+ const gated = (sub, ctx, dispatch) => subs.admit(() => (runnerFor(type) ?? first)(sub, ctx, dispatch));
283
+ return gated;
48
284
  }
49
- );
50
- return () => {
51
- live = false;
52
- readyRef.current = null;
53
- booting.stop().catch(() => {
54
- });
55
- };
56
- }, [booting]);
57
- return [state, booting.dispatch];
285
+ }
286
+ );
58
287
  }
59
288
  function latestTable(current) {
60
289
  return new Proxy(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.ts"],"names":[],"mappings":";;AAqGO,SAAS,UAAA,CAOd,SACA,IAAA,EACgC;AAMhC,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA;AACjB,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA;AACjB,EAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AAInB,EAAA,MAAM,WAAA,GAAc,OAAkC,IAAI,CAAA;AAC1D,EAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,EAAA,MAAM,QAAA,GAAW,OAAA;AAAA,IACf,OAAO;AAAA,MACL,SAAA,EAAW,WAAA;AAAA,QACT,MAAO,YAAY,OAAA,CAAmC;AAAA,OACxD;AAAA,MACA,SAAA,EAAW,WAAA,CAAY,MAAM,WAAA,CAAY,QAAQ,SAAS;AAAA,KAG5D,CAAA;AAAA,IACA;AAAC,GACH;AAMA,EAAA,MAAM,OAAA,GAAU,OAAA;AAAA,IACd,MACE,IAAI,OAAA,EAAS;AAAA,MACX,GAAA;AAAA,MACA,KAAA;AAAA,MACA,GAAG;AAAA,KAC2B,CAAA;AAAA,IAClC,CAAC,OAAA,EAAS,GAAA,EAAK,GAAA,EAAK,OAAO,QAAQ;AAAA,GACrC;AAeA,EAAA,MAAM,QAAA,GAAW,OAAqC,IAAI,CAAA;AAC1D,EAAA,MAAM,GAAG,UAAU,CAAA,GAAI,WAAW,CAAC,IAAA,KAAiB,IAAA,GAAO,CAAA,EAAG,CAAC,CAAA;AAa/D,EAAA,MAAM,gBAAA,GAAmB,OAAA;AAAA,IACvB,MAAM,OAAA,CAAQ,IAAA,CAAK,IAAA,EAAM,GAAG,EAAE,CAAC,CAAA;AAAA,IAC/B,CAAC,SAAS,GAAG;AAAA,GACf;AAMA,EAAA,MAAM,WAAA,GAAc,MAClB,QAAA,CAAS,OAAA,KAAY,OAAO,QAAA,CAAS,OAAA,CAAQ,UAAS,GAAI,gBAAA;AAE5D,EAAA,MAAM,KAAA,GAAQ,oBAAA;AAAA,IACZ,OAAA,CAAQ,SAAA;AAAA,IACR,WAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,IAAA,GAAO,IAAA;AAMX,IAAA,OAAA,CAAQ,KAAA,CAAM,IAAA;AAAA,MACZ,CAAC,CAAA,KAAM;AACL,QAAA,IAAI,IAAA,EAAM;AACR,UAAA,QAAA,CAAS,OAAA,GAAU,CAAA;AACnB,UAAA,UAAA,EAAW;AAAA,QACb;AAAA,MACF,CAAA;AAAA,MACA,MAAM;AAAA,MAAC;AAAA,KACT;AACA,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,GAAO,KAAA;AACP,MAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AAInB,MAAA,OAAA,CAAQ,IAAA,EAAK,CAAE,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAAA,IAC/B,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,OAAO,CAAC,KAAA,EAAO,OAAA,CAAQ,QAAQ,CAAA;AACjC;AAOA,SAAS,YAAY,OAAA,EAA2C;AAC9D,EAAA,OAAO,IAAI,KAAA;AAAA,IACT,EAAC;AAAA,IACD;AAAA,MACE,KAAK,CAAC,OAAA,EAAS,IAAA,KACZ,OAAA,KAAyD,IAAI;AAAA;AAClE,GACF;AACF;AAgBO,SAAS,WAId,OAAA,EAAmE;AACnE,EAAA,MAAM,KAAA,GAAQ,oBAAA;AAAA,IACZ,OAAA,CAAQ,SAAA;AAAA,IACR,OAAA,CAAQ,QAAA;AAAA,IACR,OAAA,CAAQ;AAAA,GACV;AACA,EAAA,OAAO,CAAC,KAAA,EAAO,OAAA,CAAQ,QAAQ,CAAA;AACjC","file":"index.js","sourcesContent":["/**\n * @packageDocumentation\n * @demlik/tea/react — React host adapter for `@demlik/tea`.\n *\n * The thinnest possible adapter that makes a tea machine consumable from a\n * React component. Three rules (PRD §\"React adapter\"):\n *\n * 1. The runtime is the source of truth, NOT React state. We subscribe via\n * `useSyncExternalStore(runtime.subscribe, runtime.getState,\n * runtime.getState)`. React renders are driven by runtime state changes;\n * React never owns the state.\n * 2. The runtime is memoized per component-mount, not per render. The deps\n * array is `[machine, opts.ctx, opts.store]` — IDENTITY, not value. See\n * `README.md` for the ctx-identity footgun + fix.\n * 3. Concurrent React is the design target. `useSyncExternalStore` is the\n * React-blessed primitive for tearing-free external state.\n *\n * Client-only in v1 — components that call `useMachine` MUST be `\"use client\"`.\n * Server Components consume data via Relay/RSC as today; tea machines own\n * client-side stateful flows. No RSC streaming, no server-side state\n * hydration. Crossing this boundary is a v2 concern.\n */\n\n\"use client\";\n\nimport {\n useEffect,\n useMemo,\n useReducer,\n useRef,\n useSyncExternalStore,\n} from \"react\";\nimport type {\n BootedRunHandle,\n Cmd,\n EngineRun,\n Interpret,\n Machine,\n RunHandle,\n RunHandlers,\n RunOptions,\n Store,\n Sub,\n Subscribe,\n} from \"../index\";\n\n/**\n * Options passed to `useMachine`. `run` is the engine's `run` — `run` from\n * `@demlik/tea/promise`, or any engine's; the hook imports no engine. `ctx`\n * is required (every machine has one), `store` is optional (omit it for\n * volatile-state machines), and the handlers are the ones `run` takes beside\n * the machine: `interpret` (required once the machine emits a Cmd) and the\n * `subscribe` runners (required once the machine declares a Sub type other\n * than the built-in `timer`) — see {@link RunHandlers}. Same requiredness as\n * `run`, because it is the same type.\n *\n * **The handlers are read fresh, never memoized on.** A handler table written\n * inline in the component is a new object every render; keying the runtime on\n * it would reboot the machine on every render. So `useMachine` hands `run` a\n * table that looks each cell up on the latest render's `interpret` /\n * `subscribe` at the moment a Cmd runs or a Sub starts — a handler may close\n * over props and state freely.\n *\n * **Identity matters.** The runtime is memoized on `[machine, opts.run,\n * opts.ctx, opts.store]`. A new `ctx` reference rebuilds the runtime, and so\n * does a new `run` — pass the engine's own function, not an inline wrapper. Always `useMemo`\n * (or otherwise stabilize) the ctx at the call site. See README.\n *\n * Forgetting to is no longer silent: the replaced runtime's `stop()` reports\n * `RuntimeDiscardedError` under `phase: \"discard\"` when Cmds were still in\n * flight, which the substrate's default sink warns about (issue #365).\n */\nexport type UseMachineOpts<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n> = {\n run: EngineRun<S, M, C, U, Ctx>;\n ctx: Ctx;\n store?: Store<S>;\n} & RunHandlers<M, C, U, Ctx>;\n\n/**\n * Build and own a run of `machine` for the lifetime of the component mount, on\n * the engine whose `run` the caller hands in.\n *\n * - Builds via `useMemo(() => opts.run(machine, opts), [machine, opts.run,\n * opts.ctx, opts.store])` — the run is recreated when ANY of those\n * identities change.\n * - Subscribes via `useSyncExternalStore(runtime.subscribe, runtime.getState,\n * runtime.getState)` — tearing-free under React 18 concurrent rendering.\n * - On unmount (or any dep change), `useEffect` cleanup calls\n * `runtime.stop()` — drains the queue, runs every active sub cleanup,\n * flushes state, and — if Cmds were still in flight — reports\n * `RuntimeDiscardedError` (`phase: \"discard\"`) rather than dropping them\n * silently.\n *\n * Returns `[state, dispatch]` shaped like `useReducer`.\n */\nexport function useMachine<\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 opts: NoInfer<UseMachineOpts<S, M, C, U, Ctx>>,\n): [S, (msg: M) => Promise<void>] {\n // Deps are literal: machine, engine, ctx and store identity. NOT `[opts]`\n // (would rebuild every render — callers pass fresh objects). We pass\n // explicit fields to `run()` so the closure captures only the deps in the\n // array — keeps biome's `useExhaustiveDependencies` happy without lying\n // about what the memo actually depends on.\n const run = opts.run;\n const ctx = opts.ctx;\n const store = opts.store;\n // The latest render's handlers, read per call (see `UseMachineOpts`). Written\n // during render on purpose: a Cmd a transition emits runs after the render\n // that produced it, so the table it reads is never older than that render.\n const handlersRef = useRef<RunHandlers<M, C, U, Ctx>>(opts);\n handlersRef.current = opts;\n const handlers = useMemo(\n () => ({\n interpret: latestTable(\n () => (handlersRef.current as { interpret?: object }).interpret,\n ) as Interpret<M, C, Ctx>,\n subscribe: latestTable(() => handlersRef.current.subscribe) as Partial<\n Subscribe<M, U, Ctx>\n >,\n }),\n [],\n );\n // `run()` returns a `RunHandle<S, M>` SYNCHRONOUSLY (issue #45) — exactly\n // what `useMemo` needs. We never `await` here: awaiting would force an async\n // memo, a null first-commit state, and a resolved-flag dance. Instead we hold\n // the booting handle and capture the booted one (the only thing with a total\n // `getState`) once `ready` resolves.\n const booting = useMemo<RunHandle<S, M>>(\n () =>\n run(machine, {\n ctx,\n store,\n ...handlers,\n } as RunOptions<S, M, C, U, Ctx>),\n [machine, run, ctx, store, handlers],\n );\n\n // Captures the booted handle once `ready` resolves. Until then it is\n // `null` and `getSnapshot` serves `preliminaryState`. Reset whenever the\n // booting handle identity changes (a new machine/ctx/store mount).\n //\n // The boot race the paired `bootedTick` bump exists for: the substrate's\n // boot fanout fires BEFORE `ready` resolves (fireListeners runs inside the\n // boot step; `ready` chains off the boot promise), so at notification time\n // this ref is still null and `getSnapshot` returns the same\n // `preliminaryState` reference — React sees an unchanged snapshot and\n // skips the re-render. Nothing re-notifies after the ref is set, so a\n // store-backed mount would stay stuck on the preliminary state until the\n // next unrelated transition. Bumping a reducer when we capture the booted\n // runtime forces the render that swaps preliminary → real state.\n const readyRef = useRef<BootedRunHandle<S, M> | null>(null);\n const [, markBooted] = useReducer((tick: number) => tick + 1, 0);\n\n // Preliminary state computed sync from `machine.init(null, ctx)`. This is\n // the snapshot React sees BEFORE the runtime finishes booting (matters\n // only when `opts.store` is present — without a store, the substrate\n // already sets state synchronously inside `run()`, so the boot fanout that\n // captures `readyRef` lands on the same microtask as the first commit).\n //\n // We deliberately recompute on each render but cache by `[machine,\n // opts.ctx]` — `init` is pure by convention, so cost is negligible. The\n // cached snapshot is also identity-stable across rerenders when the deps\n // don't change, which `useSyncExternalStore` rewards (no spurious\n // re-renders).\n const preliminaryState = useMemo<S>(\n () => machine.init(null, ctx)[0],\n [machine, ctx],\n );\n\n // `getSnapshot` reads the booted runtime if `ready` has resolved (the ref is\n // populated), else falls back to the preliminary state. No try/catch dance\n // anymore — pre-boot there is simply no booted handle to read from, so\n // the fallback is structural, not exception-driven.\n const getSnapshot = (): S =>\n readyRef.current !== null ? readyRef.current.getState() : preliminaryState;\n\n const state = useSyncExternalStore(\n booting.subscribe,\n getSnapshot,\n getSnapshot,\n );\n\n useEffect(() => {\n let live = true;\n // Capture the booted runtime when `ready` resolves, then force the\n // render that swaps preliminary → real state (see the boot-race note on\n // `readyRef` — the boot fanout fires too early to do it for us). Boot\n // rejections are swallowed: a failed boot surfaces on every `dispatch`,\n // which is where the consumer handles it.\n booting.ready.then(\n (r) => {\n if (live) {\n readyRef.current = r;\n markBooted();\n }\n },\n () => {},\n );\n return () => {\n live = false;\n readyRef.current = null;\n // Fire-and-forget — `stop()` is async but `useEffect` cleanup is sync.\n // The runtime swallows in-flight rejections internally, so this never\n // rejects in practice; we still attach `.catch` for safety.\n booting.stop().catch(() => {});\n };\n }, [booting]);\n\n return [state, booting.dispatch];\n}\n\n/**\n * A handler table whose every cell is looked up on `current()` at the moment it\n * is read, so `run` — which reads a cell per Cmd / per Sub start — always sees\n * the latest render's handler without the runtime being rebuilt.\n */\nfunction latestTable(current: () => object | undefined): object {\n return new Proxy(\n {},\n {\n get: (_target, type) =>\n (current() as Record<PropertyKey, unknown> | undefined)?.[type],\n },\n );\n}\n\n/**\n * Lower-level escape hatch: consume an externally-built, booted run — any\n * engine's {@link BootedRunHandle} (the Promise engine's `Runtime` is one).\n *\n * Use this when the runtime is owned by something OTHER than the component —\n * a parent component, a test harness, a singleton. **The component does NOT\n * call `runtime.stop()` on unmount** — the caller owns the lifecycle.\n *\n * Primary use case in this repo: component tests. Build a runtime with\n * `@demlik/tea/mem` and pre-seed it via `replay`, then mount the component with\n * `useRuntime(testRuntime)` to render in any phase.\n *\n * Returns `[state, dispatch]` — same shape as `useMachine`.\n */\nexport function useRuntime<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n>(runtime: BootedRunHandle<S, M, E>): [S, (msg: M) => Promise<void>] {\n const state = useSyncExternalStore(\n runtime.subscribe,\n runtime.getState,\n runtime.getState,\n );\n return [state, runtime.dispatch];\n}\n"]}
1
+ {"version":3,"sources":["../../src/react/sub-gate.ts","../../src/react/mounted-run.ts","../../src/react/index.ts"],"names":["cleanupMayBeAHide"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Stops and starts a live run's Subs without the run knowing.\n *\n * A hidden component's Subs must not run, but stopping its run would lose the\n * Cmds in flight. The engine has no way to pause a Sub, and it needs none: the\n * hook hands the engine runners that go through this gate. To the engine each\n * Sub stays started for as long as its State wants it. Underneath, the real\n * runner is cleaned up when the gate closes and started again when it opens.\n *\n * A Sub the State asks for while the gate is closed starts when it opens. A\n * `timer` starts its countdown over, as any restarted Sub does.\n */\n\nimport type { Dispose } from \"../index\";\n\n/** One Sub the engine believes is running. */\nclass GatedSub {\n private dispose: Dispose | null = null;\n private lastDisposal: ReturnType<Dispose> = undefined;\n\n constructor(private readonly begin: () => Dispose) {}\n\n start(): void {\n this.dispose ??= this.begin();\n }\n\n /** Returns the cleanup still settling, so `stop()` can wait for it. */\n stop(): ReturnType<Dispose> {\n const dispose = this.dispose;\n this.dispose = null;\n if (dispose !== null) this.lastDisposal = dispose();\n return this.lastDisposal;\n }\n\n /**\n * A hide. The engine is not waiting on this cleanup, so a rejection would go\n * unhandled: it goes to `raise`, and `stop()` later waits on a settled promise.\n */\n hide(raise: (error: unknown) => void): void {\n this.lastDisposal = this.stop()?.then(undefined, raise);\n }\n}\n\n/** The global error handler, as a throw from a hide's own microtask reaches it. */\nconst raiseGlobally = (error: unknown): void => {\n queueMicrotask(() => {\n throw error;\n });\n};\n\nexport class SubGate {\n private isOpen = false;\n private readonly subs = new Set<GatedSub>();\n\n /** @param raise where an async cleanup's rejection at a hide goes. */\n constructor(private readonly raise = raiseGlobally) {}\n\n /**\n * The engine started a Sub. `begin` runs now if the gate is open, so a runner\n * that throws here throws to the engine, as it would without the gate.\n */\n admit(begin: () => Dispose): Dispose {\n const sub = new GatedSub(begin);\n if (this.isOpen) sub.start();\n this.subs.add(sub);\n return () => {\n this.subs.delete(sub);\n return sub.stop();\n };\n }\n\n open(): void {\n this.isOpen = true;\n this.forEachSub((sub) => sub.start());\n }\n\n close(): void {\n this.isOpen = false;\n this.forEachSub((sub) => sub.hide(this.raise));\n }\n\n /**\n * One bad Sub never strands the others. The engine is not on the stack here,\n * so its sink cannot hear a throw: the first one is thrown after the pass, to\n * the error boundary from an effect and to the global handler from a microtask.\n */\n private forEachSub(fn: (sub: GatedSub) => void): void {\n const errors: unknown[] = [];\n for (const sub of this.subs) {\n try {\n fn(sub);\n } catch (error) {\n errors.push(error);\n }\n }\n if (errors.length > 0) throw errors[0];\n }\n}\n","/**\n * The run behind one `useMachine` mount, as React's effects see it.\n *\n * A passive effect's cleanup does not mean the component went away: Strict Mode\n * runs it and the effect again on mount, and `<Activity>` runs it on hide and\n * the effect again on show. So:\n *\n * - The first effect starts the one run. Render starts nothing, so a render\n * may build this object and throw it away.\n * - A cleanup and an effect in one synchronous pass (Strict Mode) are no event\n * at all: what the cleanup does is put off to a microtask, and the effect\n * cancels it.\n * - A cleanup with nothing after it (a hide) closes the {@link SubGate}, and\n * that is all. The run stays live, so a Cmd in flight lands, its answer\n * folds, and `init` never runs twice. An effect after it (a show) opens the\n * gate again.\n * - The insertion effect's cleanup, which neither of those cases runs, means\n * the component is gone. It stops the run.\n *\n * That last signal is only good from React 19.2. Before it, React skips the\n * insertion cleanup when it deletes a tree a Suspense fallback is hiding, so\n * nothing would stop the run. Those Reacts have no stable `<Activity>` either, so\n * a cleanup with nothing after it can only be an unmount, and it stops the run\n * itself. `peer-range.test.tsx` runs all of this on each React in the peer range.\n */\n\nimport type { BootedRunHandle, RunHandle } from \"../index\";\nimport { readInstalledState } from \"../internal/engine/installed-state\";\nimport { SubGate } from \"./sub-gate\";\n\n/** A Msg dispatched before the run has started, and the promise its caller holds. */\ninterface HeldMsg<M> {\n readonly msg: M;\n readonly resolve: () => void;\n readonly reject: (error: unknown) => void;\n}\n\n/** A run and the listener this object holds on it. */\ninterface Started<S, M extends { type: string }> {\n readonly handle: RunHandle<S, M>;\n readonly unlisten: () => void;\n}\n\n/**\n * Where the run is. `leaving` is a run whose component is gone but whose passive\n * cleanups have not run yet: a child's cleanup may still send it a last Msg.\n */\ntype Phase<S, M extends { type: string }> =\n | { readonly type: \"not-started\" }\n | ({ readonly type: \"live\" | \"leaving\" } & Started<S, M>)\n | { readonly type: \"stopped\" };\n\nconst stoppedError = () => new Error(\"@demlik/tea: runtime stopped\");\n\nexport class MountedRun<S, M extends { type: string }> {\n private snapshot: S;\n private phase: Phase<S, M> = { type: \"not-started\" };\n // The run's booted handle: the only thing with a `getState`. `null` until its\n // `ready` resolves, and for good if its boot fails.\n private booted: BootedRunHandle<S, M> | null = null;\n // An effect is standing, so the component is on screen.\n private connected = false;\n private held: HeldMsg<M>[] = [];\n private readonly subs = new SubGate();\n private readonly listeners = new Set<() => void>();\n\n /**\n * @param startRun boots the run, its Sub runners going through `subs`.\n * @param initial the value React reads until the run has told it a State.\n * @param cleanupMayBeAHide whether this React can run a passive cleanup on a\n * hide, and so runs the insertion cleanup on every delete.\n */\n constructor(\n private readonly startRun: (subs: SubGate) => RunHandle<S, M>,\n initial: S,\n private readonly cleanupMayBeAHide: boolean,\n ) {\n this.snapshot = initial;\n }\n\n readonly getSnapshot = (): S => this.snapshot;\n\n readonly subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n /**\n * Stable for the life of this object. A hide changes nothing about it: the run\n * is still there. A Msg dispatched before the first effect (a child's effects\n * run before its parent's, and a tree first mounted hidden runs none) waits\n * for the run, in order.\n */\n readonly dispatch = (msg: M): Promise<void> => {\n switch (this.phase.type) {\n case \"live\":\n case \"leaving\":\n return this.phase.handle.dispatch(msg);\n case \"stopped\":\n return Promise.reject(stoppedError());\n case \"not-started\":\n return new Promise((resolve, reject) => {\n this.held.push({ msg, resolve, reject });\n });\n }\n };\n\n /** The effect ran: the component is on screen. */\n connect(): void {\n this.connected = true;\n this.subs.open();\n switch (this.phase.type) {\n case \"live\":\n return;\n case \"leaving\":\n // Only if React ever calls an insertion cleanup on a hide. The show that\n // follows must not find a dead run, so it takes the run back.\n this.phase = { ...this.phase, type: \"live\" };\n return;\n case \"not-started\":\n case \"stopped\":\n this.start();\n }\n }\n\n /** The effect's cleanup ran. Act on it unless the effect runs again first. */\n disconnect(): void {\n this.connected = false;\n queueMicrotask(() => {\n if (this.connected) return;\n if (this.phase.type === \"leaving\" || !this.cleanupMayBeAHide) this.stop();\n else this.subs.close();\n });\n }\n\n /**\n * The component is gone for good: this runs from `useInsertionEffect`'s\n * cleanup, which neither Strict Mode's double run nor `<Activity>` calls. Before\n * React 19.2 a deleted hidden tree never gets here, and `disconnect` stops it.\n */\n retire(): void {\n switch (this.phase.type) {\n case \"not-started\": {\n this.phase = { type: \"stopped\" };\n const held = this.held;\n this.held = [];\n for (const { reject } of held) reject(stoppedError());\n return;\n }\n case \"live\":\n // A standing effect's cleanup comes later and stops the run then, so a\n // child's cleanup can still reach it. A hidden one has no cleanup coming.\n if (this.connected) this.phase = { ...this.phase, type: \"leaving\" };\n else this.stop();\n return;\n default:\n return;\n }\n }\n\n private start(): void {\n const handle = this.startRun(this.subs);\n this.booted = null;\n // `ready` waits for boot's Cmds, so before it the State comes off the read\n // the listener was told about: a page reloaded mid-work shows it while\n // `init`'s Cmds run (#695). A handle with no such read waits for `ready`.\n const unlisten = handle.subscribe(() => {\n if (this.booted !== null) {\n this.publish(this.booted.getState());\n return;\n }\n const installed = readInstalledState<S>(handle);\n if (installed !== undefined) this.publish(installed);\n });\n this.phase = { type: \"live\", handle, unlisten };\n // A failed boot is not swallowed: it rejects every dispatch into this run.\n handle.ready.then(\n (booted) => {\n // A run stopped mid-boot still boots, and nobody is reading it.\n if (!(\"handle\" in this.phase) || this.phase.handle !== handle) return;\n this.booted = booted;\n this.publish(booted.getState());\n },\n () => {},\n );\n const held = this.held;\n this.held = [];\n for (const { msg, resolve, reject } of held) {\n handle.dispatch(msg).then(resolve, reject);\n }\n }\n\n private stop(): void {\n if (!(\"handle\" in this.phase)) return;\n const { handle, unlisten } = this.phase;\n this.phase = { type: \"stopped\" };\n unlisten();\n void handle.stop();\n }\n\n private publish(state: S): void {\n this.snapshot = state;\n for (const listener of this.listeners) listener();\n }\n}\n","/**\n * @packageDocumentation\n * @demlik/tea/react — React host adapter for `@demlik/tea`.\n *\n * The thinnest possible adapter that makes a tea machine consumable from a\n * React component. Three rules (PRD §\"React adapter\"):\n *\n * 1. The runtime is the source of truth, NOT React state. We subscribe via\n * `useSyncExternalStore`, reading a snapshot of the run's state. React\n * renders are driven by runtime state changes; React never owns the state.\n * 2. The runtime is kept per component-mount, not per render. It is keyed on\n * `[machine, opts.run, opts.ctx, opts.store]` — IDENTITY, not value. See\n * `docs/how-to/drive-from-react.md` for the ctx-identity footgun + fix.\n * 3. Concurrent React is the design target. `useSyncExternalStore` is the\n * React-blessed primitive for tearing-free external state.\n *\n * Client-only in v1 — components that call `useMachine` MUST be `\"use client\"`.\n * Server Components consume data via Relay/RSC as today; tea machines own\n * client-side stateful flows. No RSC streaming, no server-side state\n * hydration. Crossing this boundary is a v2 concern.\n */\n\n\"use client\";\n\nimport * as React from \"react\";\nimport {\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from \"react\";\nimport type {\n BootedRunHandle,\n Cmd,\n Dispose,\n EngineRun,\n Interpret,\n Machine,\n OnError,\n RunHandlers,\n RunOptions,\n StoreAndOnError,\n Sub,\n Subscribe,\n} from \"../index\";\nimport { builtinRunners } from \"../internal/engine/builtin-runners\";\nimport { MountedRun } from \"./mounted-run\";\nimport type { SubGate } from \"./sub-gate\";\n\n// A stable `<Activity>` arrived in React 19.2. That is the first React where a\n// passive cleanup can be a hide, and the first to run an insertion cleanup in a\n// deleted hidden tree. `MountedRun` says why it needs to know.\nconst cleanupMayBeAHide = \"Activity\" in React;\n\n/**\n * Options passed to `useMachine`. `run` is the engine's `run` — `run` from\n * `@demlik/tea/promise`, or any engine's; the hook imports no engine. `ctx`\n * is required (every machine has one), `store` is optional (omit it for\n * volatile-state machines), and the handlers are the ones `run` takes beside\n * the machine: `interpret` (required once the machine emits a Cmd) and the\n * `subscribe` runners (required once the machine declares a Sub type other\n * than the built-in `timer`) — see {@link RunHandlers}. Same requiredness as\n * `run`, because it is the same type.\n *\n * **The handlers are read fresh, never memoized on.** A handler table written\n * inline in the component is a new object every render; keying the runtime on\n * it would reboot the machine on every render. So `useMachine` hands `run` a\n * table that looks each cell up on the latest render's `interpret` /\n * `subscribe` at the moment a Cmd runs or a Sub starts — a handler may close\n * over props and state freely.\n *\n * **Identity matters.** The runtime is keyed on `[machine, opts.run,\n * opts.ctx, opts.store]`. A new `ctx` reference rebuilds the runtime, and so\n * does a new `run` — pass the engine's own function, not an inline wrapper. Always `useMemo`\n * (or otherwise stabilize) the ctx at the call site. See README.\n *\n * Forgetting to is no longer silent: the replaced runtime's `stop()` reports\n * `RuntimeDiscardedError` under `phase: \"discard\"` when Cmds were still in\n * flight, which the substrate's default sink warns about (issue #365).\n */\nexport type UseMachineOpts<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n> = {\n run: EngineRun<S, M, C, U, Ctx>;\n ctx: Ctx;\n} & StoreAndOnError<S> &\n RunHandlers<M, C, U, Ctx>;\n\n/**\n * Build and own a run of `machine` for the lifetime of the component mount, on\n * the engine whose `run` the caller hands in.\n *\n * - Starts the run in an effect, never during render, and keys it on `[machine,\n * opts.run, opts.ctx, opts.store]`: the run is replaced when ANY of those\n * identities change. Until the run has its first State saved, `state` is\n * `init(null, ctx)`'s.\n * - Subscribes via `useSyncExternalStore` — tearing-free under React 18\n * concurrent rendering.\n * - On unmount (or any dep change), calls `stop()` — drains the queue, up to\n * `disposeTimeoutMs`, runs every active sub cleanup, flushes state, and — if\n * Cmds were still in flight — reports `RuntimeDiscardedError` (`phase:\n * \"discard\"`) rather than dropping them silently. A handler that has not\n * returned by the limit is given up on and reported as a `DrainTimeoutNotice`.\n *\n * **Strict Mode:** its extra effect run on mount does nothing. One run starts,\n * `run` is called once, and each Sub starts once.\n *\n * **`<Activity>`** (React 19.2 and later): a hide stops the machine's Subs, and nothing else: their\n * cleanups run, and they start again on show. It is the same run across the hide,\n * so `state` keeps its value, `init` is not called again, and a Cmd in flight\n * lands and its answer folds, whether that comes during the hide or after the\n * show. A Msg dispatched while hidden folds at once, and any Cmd it emits runs.\n *\n * `dispatch` stays the same function across a hide and show. A Msg dispatched\n * before the run has started, which on a tree first mounted hidden means before\n * its first show, waits for the run and its promise settles once it is folded.\n * After an unmount, `dispatch` rejects with `@demlik/tea: runtime stopped`.\n *\n * Returns `[state, dispatch]` shaped like `useReducer`.\n */\nexport function useMachine<\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 opts: NoInfer<UseMachineOpts<S, M, C, U, Ctx>>,\n): [S, (msg: M) => Promise<void>] {\n // Deps are literal: machine, engine, ctx and store identity. NOT `[opts]`\n // (would rebuild every render — callers pass fresh objects). We pass\n // explicit fields to `run()` so the closure captures only the deps in the\n // array — keeps biome's `useExhaustiveDependencies` happy without lying\n // about what the memo actually depends on.\n const run = opts.run;\n const ctx = opts.ctx;\n const store = opts.store;\n // The latest render's handlers, read per call (see `UseMachineOpts`). Written\n // during render on purpose: a Cmd a transition emits runs after the render\n // that produced it, so the table it reads is never older than that render.\n const handlersRef = useRef<RunHandlers<M, C, U, Ctx>>(opts);\n handlersRef.current = opts;\n const interpret = useMemo(\n () =>\n latestTable(\n () => (handlersRef.current as { interpret?: object }).interpret,\n ) as Interpret<M, C, Ctx>,\n [],\n );\n // The latest render's `onError`, on the same terms, so a sink passed inline\n // does not replace the run. A render that passes none rethrows: the engine\n // sends what its sink throws to the default sink (see `OnError`).\n const onErrorRef = useRef<OnError | undefined>(opts.onError);\n onErrorRef.current = opts.onError;\n const onError = useMemo<OnError>(\n () => (error, context) => {\n if (onErrorRef.current === undefined) throw error;\n onErrorRef.current(error, context);\n },\n [],\n );\n // Render only builds the holder and reads `init` for the first paint, which\n // is pure. `useMemo` may call this twice under Strict Mode and an abandoned\n // render may drop the result, so no run starts here: that would leak one.\n const mounted = useMemo(\n () =>\n new MountedRun<S, M>(\n (subs) =>\n run(machine, {\n ctx,\n store,\n onError,\n interpret,\n subscribe: gatedRunners(\n subs,\n () => handlersRef.current.subscribe,\n ) as Partial<Subscribe<M, U, Ctx>>,\n } as RunOptions<S, M, C, U, Ctx>),\n machine.init(null, ctx)[0],\n cleanupMayBeAHide,\n ),\n [machine, run, ctx, store, onError, interpret],\n );\n\n const state = useSyncExternalStore(\n mounted.subscribe,\n mounted.getSnapshot,\n mounted.getSnapshot,\n );\n\n // The one cleanup React runs only when the component is really gone: neither\n // Strict Mode's double run nor `<Activity>` touches an insertion effect. It\n // is what tells a hide from an unmount, which the effect below cannot. Reacts\n // before 19.2 can skip it, and `MountedRun` covers them.\n useInsertionEffect(() => () => mounted.retire(), [mounted]);\n\n useEffect(() => {\n mounted.connect();\n return () => mounted.disconnect();\n }, [mounted]);\n\n return [state, mounted.dispatch];\n}\n\n/** A Sub runner as `run` calls it. */\ntype GatedRunner = (sub: Sub, ctx: unknown, dispatch: unknown) => Dispose;\n\n/**\n * The Sub runner table `run` is handed: each runner the latest render names goes\n * through `subs`, read again each time the Sub starts. The built-ins are named\n * here so they go through it too. A type with no runner at all stays `undefined`,\n * which is what the engine reports as a missing runner.\n */\nfunction gatedRunners(\n subs: SubGate,\n current: () => object | undefined,\n): object {\n const runnerFor = (type: PropertyKey) =>\n ((current() as Record<PropertyKey, unknown> | undefined)?.[type] ??\n (builtinRunners as Record<PropertyKey, unknown>)[type]) as\n | GatedRunner\n | undefined;\n return new Proxy(\n {},\n {\n get: (_target, type) => {\n const first = runnerFor(type);\n if (first === undefined) return undefined;\n const gated: GatedRunner = (sub, ctx, dispatch) =>\n subs.admit(() => (runnerFor(type) ?? first)(sub, ctx, dispatch));\n return gated;\n },\n },\n );\n}\n\n/**\n * A handler table whose every cell is looked up on `current()` at the moment it\n * is read, so `run` — which reads a cell per Cmd / per Sub start — always sees\n * the latest render's handler without the runtime being rebuilt.\n */\nfunction latestTable(current: () => object | undefined): object {\n return new Proxy(\n {},\n {\n get: (_target, type) =>\n (current() as Record<PropertyKey, unknown> | undefined)?.[type],\n },\n );\n}\n\n/**\n * Lower-level escape hatch: consume an externally-built, booted run — any\n * engine's {@link BootedRunHandle} (the Promise engine's `Runtime` is one).\n *\n * Use this when the runtime is owned by something OTHER than the component —\n * a parent component, a test harness, a singleton. **The component does NOT\n * call `runtime.stop()` on unmount** — the caller owns the lifecycle.\n *\n * Primary use case in this repo: component tests. Build a runtime with\n * `@demlik/tea/mem` and pre-seed it via `replay`, then mount the component with\n * `useRuntime(testRuntime)` to render in any phase.\n *\n * Returns `[state, dispatch]` — same shape as `useMachine`.\n */\nexport function useRuntime<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n>(runtime: BootedRunHandle<S, M, E>): [S, (msg: M) => Promise<void>] {\n const state = useSyncExternalStore(\n runtime.subscribe,\n runtime.getState,\n runtime.getState,\n );\n return [state, runtime.dispatch];\n}\n"]}
@@ -1,12 +1,12 @@
1
- import { a as CmdOf, b as CmdDef, e as SettledErr, f as TaggedError, d as SettledOk, T as TimerDeps, C as Cmd } from '../core-CtvAyPpH.js';
1
+ import { a as CmdOf, b as CmdDef, e as SettledErr, f as TaggedError, d as SettledOk, T as TimerDeps, C as Cmd } from '../core-DNDAEhO2.js';
2
2
  import { M as MsgType } from '../protocol-ClRZBhHQ.js';
3
- import { a as DeadlineSub } from '../index-CVU7SK3a.js';
4
- export { A as ArmTimer, e as CacheEntry, c as CacheEvictMsg, f as CacheEvictionDeps, C as CacheEvictionSub, D as DeadlineExceeded, g as DeadlineMsgType, h as DeadlineOpts, b as DeadlinesSub, T as TtlCache, i as cacheEvictMsg, j as cacheEvictionSub, k as cacheEvictionSubscribe, d as deadlineExceeded, l as deadlineMsgType, m as deadlineSub, n as deadlines, o as deadlinesSub, p as evictExpired, q as get, r as has, t as initCache, u as nextTimer, v as remove, w as set, x as setTimeoutArmTimer, s as subscribeDeadline, y as subscribeWith } from '../index-CVU7SK3a.js';
5
- import { d as ResilientConfig, b as ResilientState, R as RunCmd, S as SucceedMsg, F as FailMsg, a as ResilientTimerMsg } from '../index-CKtEKGyp.js';
6
- export { C as CacheConfig, e as CallBudget, f as CallPhase, g as CircuitConfig, h as CircuitPolicy, i as CircuitState, j as DEFAULT_RESILIENT_NAME, k as DeadlineExceededError, c as DeadlineNameOf, D as DefaultResilientName, l as RateLimitConfig, m as ResilientCallDeadlineConfig, n as ResilientDeadlineType, o as ResilientErrType, p as ResilientOkType, q as ResilientRunType, r as RunCmdDef, s as RunErr, t as SettleMsg, u as SettleOutcome, v as SettleResult, w as SlidingWindow, T as TokenBucket, x as canPass, y as createResilientCall, z as defaultCircuitPolicy, A as initBucket, B as initCircuit, E as initWindow, G as liftResilience, H as onFailure, I as onSuccess, J as record, K as refill, L as remaining, M as runCmdDef, N as tryConsume } from '../index-CKtEKGyp.js';
3
+ import { a as DeadlineSub } from '../index-Cg0QXnzf.js';
4
+ export { A as ArmTimer, e as CacheEntry, c as CacheEvictMsg, f as CacheEvictionDeps, C as CacheEvictionSub, D as DeadlineExceeded, g as DeadlineMsgType, h as DeadlineOpts, b as DeadlinesSub, T as TtlCache, i as cacheEvictMsg, j as cacheEvictionSub, k as cacheEvictionSubscribe, d as deadlineExceeded, l as deadlineMsgType, m as deadlineSub, n as deadlines, o as deadlinesSub, p as evictExpired, q as get, r as has, t as initCache, u as nextTimer, v as remove, w as set, x as setTimeoutArmTimer, s as subscribeDeadline, y as subscribeWith } from '../index-Cg0QXnzf.js';
5
+ import { d as ResilientConfig, b as ResilientState, R as RunCmd, S as SucceedMsg, F as FailMsg, a as ResilientTimerMsg } from '../index-C92SgNWz.js';
6
+ export { C as CacheConfig, e as CallBudget, f as CallPhase, g as CircuitConfig, h as CircuitPolicy, i as CircuitState, j as DEFAULT_RESILIENT_NAME, k as DeadlineExceededError, c as DeadlineNameOf, D as DefaultResilientName, l as RateLimitConfig, m as ResilientCallDeadlineConfig, n as ResilientDeadlineType, o as ResilientErrType, p as ResilientOkType, q as ResilientRunType, r as RunCmdDef, s as RunErr, t as SettleMsg, u as SettleOutcome, v as SettleResult, w as SlidingWindow, T as TokenBucket, x as canPass, y as createResilientCall, z as defaultCircuitPolicy, A as initBucket, B as initCircuit, E as initWindow, G as liftResilience, H as onFailure, I as onSuccess, J as record, K as refill, L as remaining, M as runCmdDef, N as tryConsume } from '../index-C92SgNWz.js';
7
7
  import { Rng, RetryPolicy } from '../retry-backoff/index.js';
8
8
  import '@standard-schema/spec';
9
- import '../types-CXNzlbAg.js';
9
+ import '../types-S53WDy_h.js';
10
10
 
11
11
  /**
12
12
  * A minted credential: the opaque `value` to send on the wire, and the absolute
@@ -1,10 +1,10 @@
1
- import { createResilientCall } from '../chunk-72CN3VPY.js';
2
- export { DEFAULT_RESILIENT_NAME, canPass, createResilientCall, defaultCircuitPolicy, initBucket, initCircuit, initWindow, liftResilience, onFailure, onSuccess, record, refill, remaining, runCmdDef, tryConsume } from '../chunk-72CN3VPY.js';
3
- export { cacheEvictMsg, cacheEvictionSub, cacheEvictionSubscribe, deadlineExceeded, deadlineMsgType, deadlineSub, deadlines, deadlinesSub, evictExpired, get, has, initCache, nextTimer, remove, set, setTimeoutArmTimer, subscribeDeadline, subscribeWith } from '../chunk-TAKPC6SD.js';
1
+ import { createResilientCall } from '../chunk-NFOGYDRS.js';
2
+ export { DEFAULT_RESILIENT_NAME, canPass, createResilientCall, defaultCircuitPolicy, initBucket, initCircuit, initWindow, liftResilience, onFailure, onSuccess, record, refill, remaining, runCmdDef, tryConsume } from '../chunk-NFOGYDRS.js';
3
+ export { cacheEvictMsg, cacheEvictionSub, cacheEvictionSubscribe, deadlineExceeded, deadlineMsgType, deadlineSub, deadlines, deadlinesSub, evictExpired, get, has, initCache, nextTimer, remove, set, setTimeoutArmTimer, subscribeDeadline, subscribeWith } from '../chunk-UGTC33VH.js';
4
4
  import { defaultRng, initRetry, recordFailure, shouldRetry, nextDelayMs } from '../chunk-VBDU4JL6.js';
5
5
  import { unchecked } from '../chunk-4LPY67E4.js';
6
- import '../chunk-DVNXEB7V.js';
7
- import { Cmd, without } from '../chunk-RKVUVALV.js';
6
+ import '../chunk-BVFS3ICC.js';
7
+ import { Cmd, without } from '../chunk-A3QPMX5M.js';
8
8
 
9
9
  // src/internal/resilience/token-refresh/index.ts
10
10
  function initTokenRefresh() {