@demlik/tea 0.11.0 → 0.13.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 (163) hide show
  1. package/dist/agent/index.d.ts +6 -5
  2. package/dist/agent/index.js +11 -11
  3. package/dist/authed-call/index.d.ts +3 -2
  4. package/dist/authed-call/index.js +7 -7
  5. package/dist/await-terminal/index.d.ts +3 -2
  6. package/dist/await-terminal/index.js +2 -2
  7. package/dist/batch-window/index.d.ts +4 -3
  8. package/dist/batch-window/index.js +3 -3
  9. package/dist/cache/index.d.ts +3 -2
  10. package/dist/cache/index.js +4 -4
  11. package/dist/chart/index.d.ts +4 -3
  12. package/dist/chart/index.js +2 -2
  13. package/dist/chart/inspect/index.d.ts +5 -4
  14. package/dist/chart/inspect/index.js +3 -3
  15. package/dist/chart/inspect/react.d.ts +6 -5
  16. package/dist/chart/inspect/react.js +5 -5
  17. package/dist/chart/lane/index.d.ts +7 -6
  18. package/dist/chart/lane/index.js +6 -6
  19. package/dist/chart/lane/react.d.ts +6 -5
  20. package/dist/chart/lane/react.js +7 -7
  21. package/dist/chart/lane/viewer/assets/{chunk-5RXB4S5H-IPzIfiJ6.js → chunk-5RXB4S5H-CcNtpUd7.js} +1 -1
  22. package/dist/chart/lane/viewer/assets/{classDiagram-v2-OCEON4UE-DnUXz1Pv.js → classDiagram-v2-OCEON4UE-D4uAiFr8.js} +1 -1
  23. package/dist/chart/lane/viewer/assets/{dagre-VZM6K2ZE-Og2UIoWg.js → dagre-VZM6K2ZE-831qZcA8.js} +1 -1
  24. package/dist/chart/lane/viewer/assets/{index-sP-6AD57.css → index-9SkH5NA3.css} +1 -1
  25. package/dist/chart/lane/viewer/assets/{index-Obj05Jex.js → index-Bgt4sXpW.js} +4 -4
  26. package/dist/chart/lane/viewer/assets/{kanban-definition-27J2QSJJ-CxkCxJUN.js → kanban-definition-27J2QSJJ-G7PaPzy-.js} +1 -1
  27. package/dist/chart/lane/viewer/assets/{mindmap-definition-FAOFIHXS-DvgvXZhp.js → mindmap-definition-FAOFIHXS-CGj9nrVk.js} +1 -1
  28. package/dist/chart/lane/viewer/assets/{stateDiagram-OKZ733FA-CIZQWK5X.js → stateDiagram-OKZ733FA-B_88AzBS.js} +1 -1
  29. package/dist/chart/lane/viewer/assets/{stateDiagram-v2-UEYNNEHI-Sm6p0NoR.js → stateDiagram-v2-UEYNNEHI-CR5gcgq6.js} +1 -1
  30. package/dist/chart/lane/viewer/assets/{timeline-definition-Z64GVDOM-CV3KwSb-.js → timeline-definition-Z64GVDOM-CCv52MfY.js} +1 -1
  31. package/dist/chart/lane/viewer/index.html +2 -2
  32. package/dist/chart/report/index.d.ts +5 -4
  33. package/dist/chart/report/index.js +4 -4
  34. package/dist/{chunk-ZF6WDK2D.js → chunk-3I52QZZ5.js} +7 -7
  35. package/dist/{chunk-ZF6WDK2D.js.map → chunk-3I52QZZ5.js.map} +1 -1
  36. package/dist/{chunk-ZD6PNZBK.js → chunk-3ZAFFRRB.js} +3 -3
  37. package/dist/{chunk-ZD6PNZBK.js.map → chunk-3ZAFFRRB.js.map} +1 -1
  38. package/dist/{chunk-4KTWLPL5.js → chunk-4EQZ76JC.js} +3 -3
  39. package/dist/{chunk-4KTWLPL5.js.map → chunk-4EQZ76JC.js.map} +1 -1
  40. package/dist/{chunk-EUF7XOEE.js → chunk-4KLG43GY.js} +6 -6
  41. package/dist/{chunk-EUF7XOEE.js.map → chunk-4KLG43GY.js.map} +1 -1
  42. package/dist/{chunk-JER5O7PQ.js → chunk-7FRZ2CT2.js} +15 -7
  43. package/dist/chunk-7FRZ2CT2.js.map +1 -0
  44. package/dist/{chunk-ZCL7SCBK.js → chunk-CIQG5L42.js} +5 -5
  45. package/dist/{chunk-ZCL7SCBK.js.map → chunk-CIQG5L42.js.map} +1 -1
  46. package/dist/{chunk-TP5UPGLY.js → chunk-CLM6ERNO.js} +3 -3
  47. package/dist/{chunk-TP5UPGLY.js.map → chunk-CLM6ERNO.js.map} +1 -1
  48. package/dist/{chunk-ND3DH6KM.js → chunk-COS5MSTO.js} +3 -3
  49. package/dist/{chunk-ND3DH6KM.js.map → chunk-COS5MSTO.js.map} +1 -1
  50. package/dist/{chunk-JP5D6GSL.js → chunk-CRPZJVD2.js} +4 -4
  51. package/dist/{chunk-JP5D6GSL.js.map → chunk-CRPZJVD2.js.map} +1 -1
  52. package/dist/{chunk-SUST46KO.js → chunk-D62FDQKE.js} +3 -3
  53. package/dist/{chunk-SUST46KO.js.map → chunk-D62FDQKE.js.map} +1 -1
  54. package/dist/{chunk-JGVL3WPL.js → chunk-DGE4PV4A.js} +3 -3
  55. package/dist/{chunk-JGVL3WPL.js.map → chunk-DGE4PV4A.js.map} +1 -1
  56. package/dist/{chunk-WLGO64WC.js → chunk-DMYGQB4O.js} +4 -4
  57. package/dist/{chunk-WLGO64WC.js.map → chunk-DMYGQB4O.js.map} +1 -1
  58. package/dist/{chunk-A56OR5RF.js → chunk-L57ZOT5K.js} +3 -3
  59. package/dist/{chunk-A56OR5RF.js.map → chunk-L57ZOT5K.js.map} +1 -1
  60. package/dist/{chunk-5KJKJOFC.js → chunk-NR7BU22N.js} +3 -3
  61. package/dist/{chunk-5KJKJOFC.js.map → chunk-NR7BU22N.js.map} +1 -1
  62. package/dist/{chunk-7V4EVLZA.js → chunk-O4MVQTEJ.js} +4 -4
  63. package/dist/{chunk-7V4EVLZA.js.map → chunk-O4MVQTEJ.js.map} +1 -1
  64. package/dist/{chunk-B5VXOFMZ.js → chunk-OOYD44JI.js} +54 -3
  65. package/dist/chunk-OOYD44JI.js.map +1 -0
  66. package/dist/{chunk-7SVIRMC6.js → chunk-RH5EFRVJ.js} +3 -3
  67. package/dist/{chunk-7SVIRMC6.js.map → chunk-RH5EFRVJ.js.map} +1 -1
  68. package/dist/{chunk-3JU6657S.js → chunk-VYFAZN3F.js} +3 -3
  69. package/dist/{chunk-3JU6657S.js.map → chunk-VYFAZN3F.js.map} +1 -1
  70. package/dist/chunk-XGELA764.js +163 -0
  71. package/dist/chunk-XGELA764.js.map +1 -0
  72. package/dist/{chunk-I5OV65MS.js → chunk-YUXP55T2.js} +3 -3
  73. package/dist/{chunk-I5OV65MS.js.map → chunk-YUXP55T2.js.map} +1 -1
  74. package/dist/{chunk-E6GEDSF4.js → chunk-YWIKDKHY.js} +3 -3
  75. package/dist/{chunk-E6GEDSF4.js.map → chunk-YWIKDKHY.js.map} +1 -1
  76. package/dist/{chunk-5EHLEUTL.js → chunk-ZSUUOEPL.js} +51 -6
  77. package/dist/chunk-ZSUUOEPL.js.map +1 -0
  78. package/dist/{core-Bjc0bS8j.d.ts → core-Co0LWkIA.d.ts} +190 -3
  79. package/dist/deadline/index.d.ts +3 -2
  80. package/dist/deadline/index.js +3 -3
  81. package/dist/do/index.d.ts +12 -8
  82. package/dist/do/index.js +11 -11
  83. package/dist/do/index.js.map +1 -1
  84. package/dist/{durable-effects-D8SqU_if.d.ts → durable-effects-C6uwzjb5.d.ts} +1 -1
  85. package/dist/extension/index.d.ts +3 -2
  86. package/dist/extension/index.js +3 -3
  87. package/dist/extension/react.js +3 -3
  88. package/dist/extension/subs/index.d.ts +3 -2
  89. package/dist/fan-out/index.d.ts +3 -2
  90. package/dist/{fold-Blbhru48.d.ts → fold-BJPdjc9w.d.ts} +1 -1
  91. package/dist/{graph-CcjWZVrd.d.ts → graph-B3L5e0N1.d.ts} +1 -1
  92. package/dist/idempotent-intake/index.d.ts +3 -2
  93. package/dist/index.d.ts +14 -6
  94. package/dist/index.js +2 -2
  95. package/dist/journal/index.d.ts +373 -0
  96. package/dist/journal/index.js +3 -0
  97. package/dist/journal/index.js.map +1 -0
  98. package/dist/llm-call/index.d.ts +3 -2
  99. package/dist/llm-call/index.js +8 -8
  100. package/dist/{machine-0OmPvsy_.d.ts → machine-BtFWQmmq.d.ts} +1 -1
  101. package/dist/machine-viz/index.d.ts +2 -1
  102. package/dist/machine-viz/index.js +2 -2
  103. package/dist/mem/index.d.ts +3 -2
  104. package/dist/monitored-run/index.d.ts +4 -3
  105. package/dist/monitored-run/index.js +5 -5
  106. package/dist/node/index.d.ts +31 -3
  107. package/dist/node/index.js +92 -3
  108. package/dist/node/index.js.map +1 -1
  109. package/dist/paginated-walk/index.d.ts +3 -2
  110. package/dist/paginated-walk/index.js +7 -7
  111. package/dist/parity/index.d.ts +3 -2
  112. package/dist/parity/index.js +3 -3
  113. package/dist/pbt/arbitraries/index.d.ts +2 -1
  114. package/dist/pbt/index.d.ts +2 -1
  115. package/dist/pbt/index.js +3 -3
  116. package/dist/pbt/runners/index.d.ts +2 -1
  117. package/dist/pbt/runners/index.js +3 -3
  118. package/dist/poller/index.d.ts +3 -2
  119. package/dist/poller/index.js +3 -3
  120. package/dist/prediction/index.d.ts +2 -1
  121. package/dist/prediction/index.js +2 -2
  122. package/dist/pure/index.d.ts +2 -1
  123. package/dist/pure/index.js +2 -2
  124. package/dist/react/index.d.ts +4 -3
  125. package/dist/react/index.js +2 -2
  126. package/dist/react/index.js.map +1 -1
  127. package/dist/reconciler/index.d.ts +3 -2
  128. package/dist/reconciler/index.js +7 -7
  129. package/dist/recorder/index.d.ts +3 -2
  130. package/dist/{reducer-BqyoVyBG.d.ts → reducer-4GNR6b8B.d.ts} +2 -2
  131. package/dist/resilient-call/index.d.ts +3 -2
  132. package/dist/resilient-call/index.js +6 -6
  133. package/dist/{run-BVeqJDBF.d.ts → run-pAcmBpah.d.ts} +3 -3
  134. package/dist/{runtime-types-Bxc637bp.d.ts → runtime-types-CNLmhWif.d.ts} +17 -2
  135. package/dist/saga/index.d.ts +3 -2
  136. package/dist/{samples-DCt6nF8H.d.ts → samples-B3My5oh2.d.ts} +1 -1
  137. package/dist/snapshot/index.d.ts +2 -1
  138. package/dist/snapshot/index.js +3 -3
  139. package/dist/subs/index.d.ts +4 -3
  140. package/dist/subs/index.js +3 -3
  141. package/dist/testing/index.d.ts +3 -2
  142. package/dist/testing/index.js +2 -2
  143. package/dist/throttled-input/index.d.ts +3 -2
  144. package/dist/throttled-input/index.js +6 -6
  145. package/dist/token-refresh/index.d.ts +2 -1
  146. package/dist/token-refresh/index.js +3 -3
  147. package/dist/trace-replay/index.d.ts +3 -2
  148. package/dist/trace-replay/index.js +3 -3
  149. package/dist/{types-D7u9DkH7.d.ts → types-XS_miRnB.d.ts} +1 -1
  150. package/dist/with-deadline/index.d.ts +2 -1
  151. package/dist/with-deadline/index.js +2 -2
  152. package/dist/with-resilience/index.d.ts +3 -2
  153. package/dist/with-resilience/index.js +6 -6
  154. package/dist/with-telemetry/index.d.ts +2 -1
  155. package/dist/with-telemetry/index.js +2 -2
  156. package/dist/work-queue/adapter.d.ts +3 -2
  157. package/dist/work-queue/index.d.ts +3 -2
  158. package/dist/work-queue/ops.d.ts +3 -2
  159. package/dist/workflow/index.d.ts +4 -3
  160. package/package.json +11 -4
  161. package/dist/chunk-5EHLEUTL.js.map +0 -1
  162. package/dist/chunk-B5VXOFMZ.js.map +0 -1
  163. package/dist/chunk-JER5O7PQ.js.map +0 -1
@@ -1,7 +1,9 @@
1
1
  import WebSocket from 'ws';
2
- import { S as Store } from '../runtime-types-Bxc637bp.js';
3
- import { S as Sub, b as SubId } from '../core-Bjc0bS8j.js';
2
+ import { S as Store } from '../runtime-types-CNLmhWif.js';
3
+ import { S as Sub, b as SubId } from '../core-Co0LWkIA.js';
4
+ import { Journal } from '../journal/index.js';
4
5
  import 'better-result';
6
+ import 'zod';
5
7
 
6
8
  /**
7
9
  * @packageDocumentation
@@ -53,6 +55,32 @@ import 'better-result';
53
55
  * `Store<S>.migrate` contract.
54
56
  */
55
57
  declare function fileStore<S>(path: string, parse: (raw: unknown) => S | null): Store<S>;
58
+ /**
59
+ * The Node file substrate for `@demlik/tea/journal`, beside `fileStore`. A
60
+ * stream is one JSONL file under `dir` (`<encoded-stream>.jsonl`), one
61
+ * `{"seq":n,"record":…}` object per line — readable and greppable on disk, no
62
+ * SQLite. `parse` is REQUIRED for the same reason `fileStore` requires it: the
63
+ * bytes read back through `JSON.parse` are structurally `unknown`, and the
64
+ * caller who owns `R` is the only party that can validate a record's shape.
65
+ *
66
+ * `append` rewrites the whole stream file via a pid-scoped temp + `rename`,
67
+ * exactly as `fileStore.save` does — a reader sees the old file or the new one,
68
+ * never a torn line, and the write survives a crash mid-append. The append is
69
+ * O(n) in the stream's length; a durable record log a machine replays is read
70
+ * whole anyway, so the atomic-rewrite guarantee is worth the cost.
71
+ *
72
+ * The ordering key is taken under a `wx` lock file (`<file>.lock`) holding the
73
+ * writer's PID: a second writer racing the same stream blocks on the lock, so
74
+ * `seq` is a single total order across processes. A crashed writer's lock is
75
+ * stolen once its PID proves dead (`process.kill(pid, 0)` → `ESRCH`), so a
76
+ * crash never wedges the stream. This is the lock phoenix spike #6673 proved:
77
+ * its only job is an honest order key, not the win.
78
+ *
79
+ * `changes` fires for appends made through THIS instance; a cross-process
80
+ * append lands on disk but is not broadcast here (that is a polling concern the
81
+ * log leaves to its consumer, and the remote-journal work downstream).
82
+ */
83
+ declare function fileJournal<R>(dir: string, parse: (raw: unknown) => R): Journal<R>;
56
84
  /**
57
85
  * A node WebSocket sub. The substrate diffs subs by `id`; the same `id` across
58
86
  * transitions = the same socket (kept open). Emit a DIFFERENT `id` to force a
@@ -133,4 +161,4 @@ declare function nodeSubscribe<M, Ctx extends NodeSubscribeCtx>(): {
133
161
  node_signal: (sub: NodeSignalSub<M>, ctx: Ctx, dispatch: (msg: M) => void) => () => void;
134
162
  };
135
163
 
136
- export { type AssertNodeSubIsSub, type NodeSignalSub, type NodeSub, type NodeSubscribeCtx, type NodeTimerSub, type NodeWsRegistry, type NodeWsSub, fileStore, nodeSubscribe, sendToWebSocket };
164
+ export { type AssertNodeSubIsSub, type NodeSignalSub, type NodeSub, type NodeSubscribeCtx, type NodeTimerSub, type NodeWsRegistry, type NodeWsSub, fileJournal, fileStore, nodeSubscribe, sendToWebSocket };
@@ -1,6 +1,7 @@
1
1
  import { dispatchIfPresent } from '../chunk-ZJ5AOYBH.js';
2
- import { mkdir, writeFile, rename, readFile } from 'fs/promises';
3
- import { dirname } from 'path';
2
+ import { makeJournal } from '../chunk-XGELA764.js';
3
+ import { mkdir, writeFile, rename, readFile, unlink, open } from 'fs/promises';
4
+ import { dirname, join } from 'path';
4
5
  import WebSocket from 'ws';
5
6
 
6
7
  function fileStore(path, parse) {
@@ -26,6 +27,94 @@ function fileStore(path, parse) {
26
27
  }
27
28
  };
28
29
  }
30
+ var LOCK_SPIN_MS = 10;
31
+ function fileJournal(dir, parse) {
32
+ const streamPath = (stream) => join(dir, `${encodeURIComponent(stream)}.jsonl`);
33
+ const readEntries = async (stream) => {
34
+ const path = streamPath(stream);
35
+ let raw;
36
+ try {
37
+ raw = await readFile(path, "utf8");
38
+ } catch (err) {
39
+ if (err.code === "ENOENT") return [];
40
+ throw err;
41
+ }
42
+ return raw.split("\n").filter((line) => line.length > 0).map((line) => {
43
+ const row = JSON.parse(line);
44
+ return { stream, seq: row.seq, record: parse(row.record) };
45
+ });
46
+ };
47
+ return makeJournal({
48
+ read: readEntries,
49
+ async append(stream, record) {
50
+ const entries = await readEntries(stream);
51
+ const entry = {
52
+ stream,
53
+ seq: entries.length + 1,
54
+ record
55
+ };
56
+ const path = streamPath(stream);
57
+ const lines = [...entries, entry].map((e) => JSON.stringify({ seq: e.seq, record: e.record })).join("\n");
58
+ await mkdir(dir, { recursive: true });
59
+ const tmp = `${path}.${process.pid}.tmp`;
60
+ await writeFile(tmp, `${lines}
61
+ `, "utf8");
62
+ await rename(tmp, path);
63
+ return entry;
64
+ },
65
+ async lock(stream, fx) {
66
+ const lockPath = `${streamPath(stream)}.lock`;
67
+ await acquireFileLock(dir, lockPath);
68
+ try {
69
+ return await fx();
70
+ } finally {
71
+ await unlink(lockPath).catch(() => {
72
+ });
73
+ }
74
+ }
75
+ });
76
+ }
77
+ async function acquireFileLock(dir, lockPath) {
78
+ await mkdir(dir, { recursive: true });
79
+ for (; ; ) {
80
+ try {
81
+ const handle = await open(lockPath, "wx");
82
+ try {
83
+ await handle.writeFile(String(process.pid));
84
+ } finally {
85
+ await handle.close();
86
+ }
87
+ return;
88
+ } catch (err) {
89
+ if (err.code !== "EEXIST") throw err;
90
+ if (await stealIfStale(lockPath)) continue;
91
+ await new Promise((resolve) => setTimeout(resolve, LOCK_SPIN_MS));
92
+ }
93
+ }
94
+ }
95
+ async function stealIfStale(lockPath) {
96
+ let holder;
97
+ try {
98
+ holder = await readFile(lockPath, "utf8");
99
+ } catch (err) {
100
+ if (err.code === "ENOENT") return true;
101
+ throw err;
102
+ }
103
+ const pid = Number(holder.trim());
104
+ if (!Number.isInteger(pid) || pid <= 0) return false;
105
+ if (isProcessAlive(pid)) return false;
106
+ await unlink(lockPath).catch(() => {
107
+ });
108
+ return true;
109
+ }
110
+ function isProcessAlive(pid) {
111
+ try {
112
+ process.kill(pid, 0);
113
+ return true;
114
+ } catch (err) {
115
+ return err.code === "EPERM";
116
+ }
117
+ }
29
118
  function sendToWebSocket(ctx, id, data) {
30
119
  const socket = ctx.wsRegistry.get(id);
31
120
  if (!socket || socket.readyState !== WebSocket.OPEN) return false;
@@ -83,6 +172,6 @@ function nodeSubscribe() {
83
172
  };
84
173
  }
85
174
 
86
- export { fileStore, nodeSubscribe, sendToWebSocket };
175
+ export { fileJournal, fileStore, nodeSubscribe, sendToWebSocket };
87
176
  //# sourceMappingURL=index.js.map
88
177
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/node/index.ts"],"names":["handle"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @packageDocumentation\n * @demlik/tea/node — Node host adapter for `@demlik/tea`.\n *\n * Every host adapter ships two halves (mirrors `@demlik/tea/do`):\n *\n * 1. `fileStore<S>(path, parse)` — a `Store<S>` over a JSON file on disk.\n * Atomic save (temp + rename). Absent file → `load()` resolves `null`\n * (fresh boot). Structurally malformed JSON throws at `load()` (an infra\n * error), matching `doStore`; a shape mismatch is NOT a throw — the\n * substrate calls `migrate`, which returns `null` and boots fresh.\n * Use for resumable scripts / local repro of prod machines. Ephemeral\n * CLIs can skip persistence with `memoryStore` from `@demlik/tea/mem`.\n *\n * 2. `nodeSubscribe<M, Ctx>()` — handler registry for the three node-native\n * Sub types this package owns:\n * - `node_ws` — opens a `ws` WebSocket; routes open/message/close/\n * error into Msgs. Registers the live socket in\n * `ctx.wsRegistry` so a Cmd handler can write to it\n * (see `sendToWebSocket`). Cleanup closes the socket\n * for ANY non-CLOSED state.\n * - `node_timer` — setTimeout / setInterval; cleanup clears it.\n * - `node_signal` — a process signal (SIGINT/SIGTERM/…) → Msg; cleanup\n * detaches the listener.\n *\n * Unlike tea-do — whose `alarm()` / `webSocketMessage()` are DO lifecycle\n * methods the handler can't observe from the outside, hence its registries —\n * a node process is continuously alive: each handler holds its resource in\n * closure and the returned cleanup closes it. The substrate's subscription\n * reconciler runs that cleanup the instant `subscriptions(state)` stops listing\n * the Sub — end-then-start choreography for free (Rule 9, [invariant 4]).\n * A lifecycle-bound resource (a socket, a timer) becomes impossible to leak\n * from a reducer cell, because no reducer cell owns its teardown.\n *\n * `ctx.wsRegistry` is the one shared surface: `node_ws` populates it; a Cmd\n * interpreter reads it to write to the open socket. Same shape as tea-do's\n * `wsRegistry`.\n */\n\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport WebSocket, { type RawData } from \"ws\";\nimport type { Store, Sub, SubId } from \"../index\";\nimport { dispatchIfPresent } from \"../subs/types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// fileStore — `Store<S>` over a JSON file.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * `Store<S>` backed by a JSON file at `path`.\n *\n * `parse` is REQUIRED because the file is a real serialization boundary — the\n * bytes that come back through `JSON.parse` are structurally `unknown`, and the\n * caller (who owns `S`) is the only party that can validate the shape.\n * Returning `null` from `parse` means \"no usable persisted state\" — the\n * substrate boots `init` with `loaded = null`. `parse` must NOT throw per the\n * `Store<S>.migrate` contract.\n */\nexport function fileStore<S>(\n path: string,\n parse: (raw: unknown) => S | null,\n): Store<S> {\n return {\n async load(): Promise<unknown> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n // JSON.parse throws on structural malformation — propagate (infra error),\n // matching `doStore`. The decoded value is intentionally returned as\n // `unknown`; the substrate's `migrate` callback is the boundary parse.\n return JSON.parse(raw);\n },\n async save(state: S): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n // Write to a pid-scoped temp then atomically rename. The rename gives\n // torn-write safety (a reader sees the old file or the new one, never a\n // partial) and survives process crash / Ctrl-C — the only failure a local\n // resumable run resumes from. fsync (file + dir) would add power-loss\n // durability the use case doesn't need; the substrate serializes saves\n // through run()'s queue, so there's never a second concurrent writer.\n const tmp = `${path}.${process.pid}.tmp`;\n await writeFile(tmp, JSON.stringify(state), \"utf8\");\n await rename(tmp, path);\n },\n migrate(raw: unknown): S | null {\n return parse(raw);\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// NodeSub — the three node-native subscription variants.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A node WebSocket sub. The substrate diffs subs by `id`; the same `id` across\n * transitions = the same socket (kept open). Emit a DIFFERENT `id` to force a\n * reconnect.\n *\n * Only `onMessage` returns `M | null` (null drops the frame) because inbound\n * frames are the boundary that needs a parse-and-maybe-discard seam. `onOpen`/\n * `onClose`/`onError` always dispatch — a spurious one is dropped in the reducer\n * (the cell ignores it), not here.\n */\nexport type NodeWsSub<M> = {\n id: SubId;\n type: \"node_ws\";\n url: string;\n onMessage: (data: string) => M | null;\n onOpen?: () => M;\n onClose?: (code: number, reason: string) => M;\n onError?: (message: string) => M;\n};\n\n/**\n * A node timer sub. One-shot `setTimeout` by default; `repeat: true` →\n * `setInterval`. Cleanup clears the timer — a timer keyed to a phase is\n * cancelled the moment the machine leaves that phase.\n */\nexport type NodeTimerSub<M> = {\n id: SubId;\n type: \"node_timer\";\n delayMs: number;\n msg: M;\n repeat?: boolean;\n};\n\n/**\n * A process-signal sub: `signal` (SIGINT/SIGTERM/…) → `msg`. Cleanup detaches\n * the listener. Lets graceful shutdown be a real machine transition rather than\n * an out-of-band `process.on` that the reducer can't see.\n */\nexport type NodeSignalSub<M> = {\n id: SubId;\n type: \"node_signal\";\n signal: NodeJS.Signals;\n msg: M;\n};\n\nexport type NodeSub<M> = NodeWsSub<M> | NodeTimerSub<M> | NodeSignalSub<M>;\n\n// Compile-time guarantee: `NodeSub<M>` is structurally a `Sub` (has `id`+`type`).\n// Erased at runtime — the `extends Sub` constraint on the alias's type\n// parameter fails to compile if `NodeSub` ever drifts off the `Sub` shape.\nexport type AssertNodeSubIsSub<T extends Sub = NodeSub<unknown>> = T;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Ctx — what the node Subs read / populate.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type NodeWsRegistry = Map<SubId, WebSocket>;\n\n/**\n * The Ctx fields the node Subs depend on. A consumer's Ctx must extend this so\n * `node_ws` can register the live socket and Cmd handlers can reach it.\n *\n * Deliberately NOT parameterized by Msg: the registry holds raw `WebSocket`s,\n * and each Sub variant carries its own Msg-typed callbacks. Trip-wire: if a\n * future node Sub holds a Msg-typed callback on ctx, this becomes\n * `NodeSubscribeCtx<M>`.\n */\nexport interface NodeSubscribeCtx {\n wsRegistry: NodeWsRegistry;\n}\n\n/**\n * Write a frame to a registered `node_ws` socket. No-op (returns `false`) when\n * the socket is absent, not OPEN, or `send()` throws. The Cmd-side companion to\n * the `node_ws` Sub: the Sub owns the socket's lifetime; this writes to it.\n *\n * The contract is a boolean no-op, never a throw: the socket can transition out\n * of OPEN between the `readyState` check and `send()` (or `ws` can throw on an\n * errored underlying connection), and a Cmd interpreter must not crash on that.\n */\nexport function sendToWebSocket(\n ctx: NodeSubscribeCtx,\n id: SubId,\n data: string,\n): boolean {\n const socket = ctx.wsRegistry.get(id);\n if (!socket || socket.readyState !== WebSocket.OPEN) return false;\n try {\n socket.send(data);\n return true;\n } catch {\n return false;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// nodeSubscribe — the handler registry.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build the `subscribe` handlers for `node_ws`, `node_timer`, and\n * `node_signal`. Matches `Machine#subscribe`'s\n * `[K in U[\"type\"]]: (sub, ctx, dispatch) => () => void` contract for the\n * `NodeSub<M>` variants. Generic `Ctx` must extend `NodeSubscribeCtx` so the\n * `node_ws` handler can register its socket.\n */\nexport function nodeSubscribe<M, Ctx extends NodeSubscribeCtx>(): {\n node_ws: (\n sub: NodeWsSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n node_timer: (\n sub: NodeTimerSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n node_signal: (\n sub: NodeSignalSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n} {\n return {\n node_ws: (sub, ctx, dispatch) => {\n const socket = new WebSocket(sub.url);\n ctx.wsRegistry.set(sub.id, socket);\n\n socket.on(\"open\", () => {\n if (sub.onOpen) dispatch(sub.onOpen());\n });\n socket.on(\"message\", (data: RawData) => {\n dispatchIfPresent(dispatch, sub.onMessage(data.toString()));\n });\n socket.on(\"close\", (code: number, reason: Buffer) => {\n if (sub.onClose) dispatch(sub.onClose(code, reason.toString()));\n });\n socket.on(\"error\", (err: Error) => {\n if (sub.onError) dispatch(sub.onError(err.message));\n });\n\n return () => {\n ctx.wsRegistry.delete(sub.id);\n socket.removeAllListeners();\n // Swallow teardown-induced errors: terminate()-ing a CONNECTING socket\n // emits an 'error' (\"closed before the connection was established\"),\n // and with our listeners gone that would be an unhandled 'error' that\n // crashes the process. A no-op listener absorbs it.\n socket.on(\"error\", () => {});\n // Close for ANY non-closed state: a graceful 1000 when OPEN, a hard\n // terminate() otherwise (CONNECTING/CLOSING) so a socket caught mid-\n // handshake can't dangle. This is the teardown the hand-driven Cmd-pair\n // approach (`cleanup_browser` closing the ws only `if OPEN`) could not\n // guarantee.\n if (socket.readyState === WebSocket.OPEN) {\n socket.close(1000, \"tea-node sub cleanup\");\n } else if (socket.readyState !== WebSocket.CLOSED) {\n socket.terminate();\n }\n };\n },\n\n node_timer: (sub, _ctx, dispatch) => {\n // Two reconcile lifecycles under one variant: a one-shot (`setTimeout`)\n // that `subscriptions(state)` should drop on the render after it fires,\n // vs a repeating (`setInterval`) one the state keeps listing across\n // renders. Same Sub.type; the `repeat` flag picks the install + cleanup.\n if (sub.repeat) {\n const handle = setInterval(() => dispatch(sub.msg), sub.delayMs);\n return () => clearInterval(handle);\n }\n const handle = setTimeout(() => dispatch(sub.msg), sub.delayMs);\n return () => clearTimeout(handle);\n },\n\n node_signal: (sub, _ctx, dispatch) => {\n const handler = () => dispatch(sub.msg);\n process.on(sub.signal, handler);\n return () => {\n process.off(sub.signal, handler);\n };\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/node/index.ts"],"names":["handle"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @packageDocumentation\n * @demlik/tea/node — Node host adapter for `@demlik/tea`.\n *\n * Every host adapter ships two halves (mirrors `@demlik/tea/do`):\n *\n * 1. `fileStore<S>(path, parse)` — a `Store<S>` over a JSON file on disk.\n * Atomic save (temp + rename). Absent file → `load()` resolves `null`\n * (fresh boot). Structurally malformed JSON throws at `load()` (an infra\n * error), matching `doStore`; a shape mismatch is NOT a throw — the\n * substrate calls `migrate`, which returns `null` and boots fresh.\n * Use for resumable scripts / local repro of prod machines. Ephemeral\n * CLIs can skip persistence with `memoryStore` from `@demlik/tea/mem`.\n *\n * 2. `nodeSubscribe<M, Ctx>()` — handler registry for the three node-native\n * Sub types this package owns:\n * - `node_ws` — opens a `ws` WebSocket; routes open/message/close/\n * error into Msgs. Registers the live socket in\n * `ctx.wsRegistry` so a Cmd handler can write to it\n * (see `sendToWebSocket`). Cleanup closes the socket\n * for ANY non-CLOSED state.\n * - `node_timer` — setTimeout / setInterval; cleanup clears it.\n * - `node_signal` — a process signal (SIGINT/SIGTERM/…) → Msg; cleanup\n * detaches the listener.\n *\n * Unlike tea-do — whose `alarm()` / `webSocketMessage()` are DO lifecycle\n * methods the handler can't observe from the outside, hence its registries —\n * a node process is continuously alive: each handler holds its resource in\n * closure and the returned cleanup closes it. The substrate's subscription\n * reconciler runs that cleanup the instant `subscriptions(state)` stops listing\n * the Sub — end-then-start choreography for free (Rule 9, [invariant 4]).\n * A lifecycle-bound resource (a socket, a timer) becomes impossible to leak\n * from a reducer cell, because no reducer cell owns its teardown.\n *\n * `ctx.wsRegistry` is the one shared surface: `node_ws` populates it; a Cmd\n * interpreter reads it to write to the open socket. Same shape as tea-do's\n * `wsRegistry`.\n */\n\nimport {\n mkdir,\n open,\n readFile,\n rename,\n unlink,\n writeFile,\n} from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport WebSocket, { type RawData } from \"ws\";\nimport type { Store, Sub, SubId } from \"../index\";\nimport {\n type Journal,\n type JournalEntry,\n makeJournal,\n type Seq,\n} from \"../journal\";\nimport { dispatchIfPresent } from \"../subs/types\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// fileStore — `Store<S>` over a JSON file.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * `Store<S>` backed by a JSON file at `path`.\n *\n * `parse` is REQUIRED because the file is a real serialization boundary — the\n * bytes that come back through `JSON.parse` are structurally `unknown`, and the\n * caller (who owns `S`) is the only party that can validate the shape.\n * Returning `null` from `parse` means \"no usable persisted state\" — the\n * substrate boots `init` with `loaded = null`. `parse` must NOT throw per the\n * `Store<S>.migrate` contract.\n */\nexport function fileStore<S>(\n path: string,\n parse: (raw: unknown) => S | null,\n): Store<S> {\n return {\n async load(): Promise<unknown> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n // JSON.parse throws on structural malformation — propagate (infra error),\n // matching `doStore`. The decoded value is intentionally returned as\n // `unknown`; the substrate's `migrate` callback is the boundary parse.\n return JSON.parse(raw);\n },\n async save(state: S): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n // Write to a pid-scoped temp then atomically rename. The rename gives\n // torn-write safety (a reader sees the old file or the new one, never a\n // partial) and survives process crash / Ctrl-C — the only failure a local\n // resumable run resumes from. fsync (file + dir) would add power-loss\n // durability the use case doesn't need; the substrate serializes saves\n // through run()'s queue, so there's never a second concurrent writer.\n const tmp = `${path}.${process.pid}.tmp`;\n await writeFile(tmp, JSON.stringify(state), \"utf8\");\n await rename(tmp, path);\n },\n migrate(raw: unknown): S | null {\n return parse(raw);\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// fileJournal — a `Journal<R>` over one on-disk JSONL file per stream.\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst LOCK_SPIN_MS = 10;\n\n/**\n * The Node file substrate for `@demlik/tea/journal`, beside `fileStore`. A\n * stream is one JSONL file under `dir` (`<encoded-stream>.jsonl`), one\n * `{\"seq\":n,\"record\":…}` object per line — readable and greppable on disk, no\n * SQLite. `parse` is REQUIRED for the same reason `fileStore` requires it: the\n * bytes read back through `JSON.parse` are structurally `unknown`, and the\n * caller who owns `R` is the only party that can validate a record's shape.\n *\n * `append` rewrites the whole stream file via a pid-scoped temp + `rename`,\n * exactly as `fileStore.save` does — a reader sees the old file or the new one,\n * never a torn line, and the write survives a crash mid-append. The append is\n * O(n) in the stream's length; a durable record log a machine replays is read\n * whole anyway, so the atomic-rewrite guarantee is worth the cost.\n *\n * The ordering key is taken under a `wx` lock file (`<file>.lock`) holding the\n * writer's PID: a second writer racing the same stream blocks on the lock, so\n * `seq` is a single total order across processes. A crashed writer's lock is\n * stolen once its PID proves dead (`process.kill(pid, 0)` → `ESRCH`), so a\n * crash never wedges the stream. This is the lock phoenix spike #6673 proved:\n * its only job is an honest order key, not the win.\n *\n * `changes` fires for appends made through THIS instance; a cross-process\n * append lands on disk but is not broadcast here (that is a polling concern the\n * log leaves to its consumer, and the remote-journal work downstream).\n */\nexport function fileJournal<R>(\n dir: string,\n parse: (raw: unknown) => R,\n): Journal<R> {\n const streamPath = (stream: string): string =>\n join(dir, `${encodeURIComponent(stream)}.jsonl`);\n\n const readEntries = async (stream: string): Promise<JournalEntry<R>[]> => {\n const path = streamPath(stream);\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw err;\n }\n return raw\n .split(\"\\n\")\n .filter((line) => line.length > 0)\n .map((line) => {\n const row = JSON.parse(line) as { seq: Seq; record: unknown };\n return { stream, seq: row.seq, record: parse(row.record) };\n });\n };\n\n return makeJournal<R>({\n read: readEntries,\n async append(stream, record) {\n const entries = await readEntries(stream);\n const entry: JournalEntry<R> = {\n stream,\n seq: entries.length + 1,\n record,\n };\n const path = streamPath(stream);\n const lines = [...entries, entry]\n .map((e) => JSON.stringify({ seq: e.seq, record: e.record }))\n .join(\"\\n\");\n await mkdir(dir, { recursive: true });\n const tmp = `${path}.${process.pid}.tmp`;\n await writeFile(tmp, `${lines}\\n`, \"utf8\");\n await rename(tmp, path);\n return entry;\n },\n async lock(stream, fx) {\n const lockPath = `${streamPath(stream)}.lock`;\n await acquireFileLock(dir, lockPath);\n try {\n return await fx();\n } finally {\n await unlink(lockPath).catch(() => {});\n }\n },\n });\n}\n\n/**\n * Acquire an exclusive lock by creating `lockPath` with `wx` (O_CREAT|O_EXCL) —\n * the one primitive that is atomic across processes. On contention, steal the\n * lock only once its holder's PID proves dead; otherwise spin. `process.kill(pid,\n * 0)` sends no signal — it tests existence: it throws `ESRCH` for a dead PID,\n * and `EPERM` for a live one this process may not signal (still alive, so hold\n * off). A lock that vanishes between the failed create and the read is already\n * free, so retry.\n */\nasync function acquireFileLock(dir: string, lockPath: string): Promise<void> {\n await mkdir(dir, { recursive: true });\n for (;;) {\n try {\n const handle = await open(lockPath, \"wx\");\n try {\n await handle.writeFile(String(process.pid));\n } finally {\n await handle.close();\n }\n return;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"EEXIST\") throw err;\n if (await stealIfStale(lockPath)) continue;\n await new Promise((resolve) => setTimeout(resolve, LOCK_SPIN_MS));\n }\n }\n}\n\nasync function stealIfStale(lockPath: string): Promise<boolean> {\n let holder: string;\n try {\n holder = await readFile(lockPath, \"utf8\");\n } catch (err) {\n // Vanished between the failed create and this read — already free.\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return true;\n throw err;\n }\n const pid = Number(holder.trim());\n if (!Number.isInteger(pid) || pid <= 0) return false;\n if (isProcessAlive(pid)) return false;\n await unlink(lockPath).catch(() => {});\n return true;\n}\n\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// NodeSub — the three node-native subscription variants.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * A node WebSocket sub. The substrate diffs subs by `id`; the same `id` across\n * transitions = the same socket (kept open). Emit a DIFFERENT `id` to force a\n * reconnect.\n *\n * Only `onMessage` returns `M | null` (null drops the frame) because inbound\n * frames are the boundary that needs a parse-and-maybe-discard seam. `onOpen`/\n * `onClose`/`onError` always dispatch — a spurious one is dropped in the reducer\n * (the cell ignores it), not here.\n */\nexport type NodeWsSub<M> = {\n id: SubId;\n type: \"node_ws\";\n url: string;\n onMessage: (data: string) => M | null;\n onOpen?: () => M;\n onClose?: (code: number, reason: string) => M;\n onError?: (message: string) => M;\n};\n\n/**\n * A node timer sub. One-shot `setTimeout` by default; `repeat: true` →\n * `setInterval`. Cleanup clears the timer — a timer keyed to a phase is\n * cancelled the moment the machine leaves that phase.\n */\nexport type NodeTimerSub<M> = {\n id: SubId;\n type: \"node_timer\";\n delayMs: number;\n msg: M;\n repeat?: boolean;\n};\n\n/**\n * A process-signal sub: `signal` (SIGINT/SIGTERM/…) → `msg`. Cleanup detaches\n * the listener. Lets graceful shutdown be a real machine transition rather than\n * an out-of-band `process.on` that the reducer can't see.\n */\nexport type NodeSignalSub<M> = {\n id: SubId;\n type: \"node_signal\";\n signal: NodeJS.Signals;\n msg: M;\n};\n\nexport type NodeSub<M> = NodeWsSub<M> | NodeTimerSub<M> | NodeSignalSub<M>;\n\n// Compile-time guarantee: `NodeSub<M>` is structurally a `Sub` (has `id`+`type`).\n// Erased at runtime — the `extends Sub` constraint on the alias's type\n// parameter fails to compile if `NodeSub` ever drifts off the `Sub` shape.\nexport type AssertNodeSubIsSub<T extends Sub = NodeSub<unknown>> = T;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Ctx — what the node Subs read / populate.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type NodeWsRegistry = Map<SubId, WebSocket>;\n\n/**\n * The Ctx fields the node Subs depend on. A consumer's Ctx must extend this so\n * `node_ws` can register the live socket and Cmd handlers can reach it.\n *\n * Deliberately NOT parameterized by Msg: the registry holds raw `WebSocket`s,\n * and each Sub variant carries its own Msg-typed callbacks. Trip-wire: if a\n * future node Sub holds a Msg-typed callback on ctx, this becomes\n * `NodeSubscribeCtx<M>`.\n */\nexport interface NodeSubscribeCtx {\n wsRegistry: NodeWsRegistry;\n}\n\n/**\n * Write a frame to a registered `node_ws` socket. No-op (returns `false`) when\n * the socket is absent, not OPEN, or `send()` throws. The Cmd-side companion to\n * the `node_ws` Sub: the Sub owns the socket's lifetime; this writes to it.\n *\n * The contract is a boolean no-op, never a throw: the socket can transition out\n * of OPEN between the `readyState` check and `send()` (or `ws` can throw on an\n * errored underlying connection), and a Cmd interpreter must not crash on that.\n */\nexport function sendToWebSocket(\n ctx: NodeSubscribeCtx,\n id: SubId,\n data: string,\n): boolean {\n const socket = ctx.wsRegistry.get(id);\n if (!socket || socket.readyState !== WebSocket.OPEN) return false;\n try {\n socket.send(data);\n return true;\n } catch {\n return false;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// nodeSubscribe — the handler registry.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build the `subscribe` handlers for `node_ws`, `node_timer`, and\n * `node_signal`. Matches `Machine#subscribe`'s\n * `[K in U[\"type\"]]: (sub, ctx, dispatch) => () => void` contract for the\n * `NodeSub<M>` variants. Generic `Ctx` must extend `NodeSubscribeCtx` so the\n * `node_ws` handler can register its socket.\n */\nexport function nodeSubscribe<M, Ctx extends NodeSubscribeCtx>(): {\n node_ws: (\n sub: NodeWsSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n node_timer: (\n sub: NodeTimerSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n node_signal: (\n sub: NodeSignalSub<M>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n} {\n return {\n node_ws: (sub, ctx, dispatch) => {\n const socket = new WebSocket(sub.url);\n ctx.wsRegistry.set(sub.id, socket);\n\n socket.on(\"open\", () => {\n if (sub.onOpen) dispatch(sub.onOpen());\n });\n socket.on(\"message\", (data: RawData) => {\n dispatchIfPresent(dispatch, sub.onMessage(data.toString()));\n });\n socket.on(\"close\", (code: number, reason: Buffer) => {\n if (sub.onClose) dispatch(sub.onClose(code, reason.toString()));\n });\n socket.on(\"error\", (err: Error) => {\n if (sub.onError) dispatch(sub.onError(err.message));\n });\n\n return () => {\n ctx.wsRegistry.delete(sub.id);\n socket.removeAllListeners();\n // Swallow teardown-induced errors: terminate()-ing a CONNECTING socket\n // emits an 'error' (\"closed before the connection was established\"),\n // and with our listeners gone that would be an unhandled 'error' that\n // crashes the process. A no-op listener absorbs it.\n socket.on(\"error\", () => {});\n // Close for ANY non-closed state: a graceful 1000 when OPEN, a hard\n // terminate() otherwise (CONNECTING/CLOSING) so a socket caught mid-\n // handshake can't dangle. This is the teardown the hand-driven Cmd-pair\n // approach (`cleanup_browser` closing the ws only `if OPEN`) could not\n // guarantee.\n if (socket.readyState === WebSocket.OPEN) {\n socket.close(1000, \"tea-node sub cleanup\");\n } else if (socket.readyState !== WebSocket.CLOSED) {\n socket.terminate();\n }\n };\n },\n\n node_timer: (sub, _ctx, dispatch) => {\n // Two reconcile lifecycles under one variant: a one-shot (`setTimeout`)\n // that `subscriptions(state)` should drop on the render after it fires,\n // vs a repeating (`setInterval`) one the state keeps listing across\n // renders. Same Sub.type; the `repeat` flag picks the install + cleanup.\n if (sub.repeat) {\n const handle = setInterval(() => dispatch(sub.msg), sub.delayMs);\n return () => clearInterval(handle);\n }\n const handle = setTimeout(() => dispatch(sub.msg), sub.delayMs);\n return () => clearTimeout(handle);\n },\n\n node_signal: (sub, _ctx, dispatch) => {\n const handler = () => dispatch(sub.msg);\n process.on(sub.signal, handler);\n return () => {\n process.off(sub.signal, handler);\n };\n },\n };\n}\n"]}
@@ -1,11 +1,12 @@
1
- import { C as Cmd } from '../core-Bjc0bS8j.js';
1
+ import { C as Cmd } from '../core-Co0LWkIA.js';
2
2
  import { PaginatorState } from '../paginator/index.js';
3
3
  import { RunCmd, FailMsg, SucceedMsg, CircuitConfig, RateLimitConfig, DeadlineConfig, ResilientPorts, ResilientState, ResilientTimerMsg } from '../resilient-call/index.js';
4
4
  import { RetryPolicy } from '../retry-backoff/index.js';
5
5
  import { DeadlineSub } from '../deadline/index.js';
6
6
  export { deadlineSub, subscribeDeadline } from '../deadline/index.js';
7
+ import 'zod';
7
8
  import '../cache/index.js';
8
- import '../types-D7u9DkH7.js';
9
+ import '../types-XS_miRnB.js';
9
10
  import '../circuit-breaker/index.js';
10
11
  import '../protocol-B5YmJr0d.js';
11
12
  import '../rate-limit/index.js';
@@ -1,16 +1,16 @@
1
- export { PAGE_KEY, createPaginatedWalk, liftWalk } from '../chunk-I5OV65MS.js';
1
+ export { PAGE_KEY, createPaginatedWalk, liftWalk } from '../chunk-YUXP55T2.js';
2
2
  import '../chunk-SINXE47Y.js';
3
- import '../chunk-EUF7XOEE.js';
3
+ import '../chunk-4KLG43GY.js';
4
4
  import '../chunk-JELSZN25.js';
5
- export { deadlineSub, subscribeDeadline } from '../chunk-JGVL3WPL.js';
5
+ import '../chunk-RH5EFRVJ.js';
6
+ export { deadlineSub, subscribeDeadline } from '../chunk-DGE4PV4A.js';
6
7
  import '../chunk-Q2UM7BXU.js';
7
8
  import '../chunk-S2EGRPCM.js';
8
9
  import '../chunk-UF4MZSLT.js';
9
- import '../chunk-7SVIRMC6.js';
10
- import '../chunk-3JU6657S.js';
10
+ import '../chunk-VYFAZN3F.js';
11
11
  import '../chunk-2YFLOCWX.js';
12
12
  import '../chunk-ZJ5AOYBH.js';
13
- import '../chunk-5EHLEUTL.js';
14
- import '../chunk-B5VXOFMZ.js';
13
+ import '../chunk-ZSUUOEPL.js';
14
+ import '../chunk-OOYD44JI.js';
15
15
  //# sourceMappingURL=index.js.map
16
16
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,8 @@
1
- import { B as BootingRuntime } from '../runtime-types-Bxc637bp.js';
2
- import { C as Cmd, S as Sub, M as Machine } from '../core-Bjc0bS8j.js';
1
+ import { B as BootingRuntime } from '../runtime-types-CNLmhWif.js';
2
+ import { C as Cmd, S as Sub, M as Machine } from '../core-Co0LWkIA.js';
3
3
  import { Trace, RecorderOptions } from '../recorder/index.js';
4
4
  import 'better-result';
5
+ import 'zod';
5
6
 
6
7
  /**
7
8
  * the record → replay → normalized-diff go/no-go gate.
@@ -1,8 +1,8 @@
1
+ import { deepEqual } from '../chunk-D62FDQKE.js';
1
2
  import { recorder } from '../chunk-MUUROGIH.js';
2
- import { deepEqual } from '../chunk-SUST46KO.js';
3
+ import { replay } from '../chunk-ZSUUOEPL.js';
4
+ import '../chunk-OOYD44JI.js';
3
5
  import '../chunk-EWJP3YYP.js';
4
- import { replay } from '../chunk-5EHLEUTL.js';
5
- import '../chunk-B5VXOFMZ.js';
6
6
 
7
7
  // src/parity/index.ts
8
8
  function recordRun(runtime, opts) {
@@ -1,5 +1,6 @@
1
1
  import * as fc from 'fast-check';
2
- import { C as Cmd, S as Sub, M as Machine } from '../../core-Bjc0bS8j.js';
2
+ import { C as Cmd, S as Sub, M as Machine } from '../../core-Co0LWkIA.js';
3
+ import 'zod';
3
4
 
4
5
  /**
5
6
  * Build a Ctx value whose every property access throws. Use as the `ctx`
@@ -1,7 +1,8 @@
1
1
  export { MsgArbitraryTable, arbConstantMsg, arbGuidedSequence, arbMsg, arbMsgSequence, arbRecordMsg, stubCtxThrowingProxy } from './arbitraries/index.js';
2
2
  export { Step, foldEvents, propertyInvariant, propertyTerminates, propertyTrace } from './runners/index.js';
3
- import { C as Cmd, R as Reducer, T as Transitions, U as UpdateForm } from '../core-Bjc0bS8j.js';
3
+ import { C as Cmd, c as Reducer, T as Transitions, U as UpdateForm } from '../core-Co0LWkIA.js';
4
4
  import 'fast-check';
5
+ import 'zod';
5
6
 
6
7
  /**
7
8
  * Extract the Msg discriminant set at runtime from a machine. Useful for
package/dist/pbt/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { arbConstantMsg, arbGuidedSequence, arbMsg, arbMsgSequence, arbRecordMsg, stubCtxThrowingProxy } from '../chunk-AZZLE7XR.js';
2
- export { foldEvents, propertyInvariant, propertyTerminates, propertyTrace } from '../chunk-JP5D6GSL.js';
3
- import '../chunk-5EHLEUTL.js';
4
- import { msgKeysOf } from '../chunk-B5VXOFMZ.js';
2
+ export { foldEvents, propertyInvariant, propertyTerminates, propertyTrace } from '../chunk-CRPZJVD2.js';
3
+ import '../chunk-ZSUUOEPL.js';
4
+ import { msgKeysOf } from '../chunk-OOYD44JI.js';
5
5
 
6
6
  // src/pbt/util/msg-keys.ts
7
7
  function msgTypeKeys(machine) {
@@ -1,5 +1,6 @@
1
1
  import * as fc from 'fast-check';
2
- import { C as Cmd, S as Sub, M as Machine } from '../../core-Bjc0bS8j.js';
2
+ import { C as Cmd, S as Sub, M as Machine } from '../../core-Co0LWkIA.js';
3
+ import 'zod';
3
4
 
4
5
  /**
5
6
  * One unit in a fold trace — the four pieces of data a property
@@ -1,5 +1,5 @@
1
- export { foldEvents, propertyInvariant, propertyTerminates, propertyTrace } from '../../chunk-JP5D6GSL.js';
2
- import '../../chunk-5EHLEUTL.js';
3
- import '../../chunk-B5VXOFMZ.js';
1
+ export { foldEvents, propertyInvariant, propertyTerminates, propertyTrace } from '../../chunk-CRPZJVD2.js';
2
+ import '../../chunk-ZSUUOEPL.js';
3
+ import '../../chunk-OOYD44JI.js';
4
4
  //# sourceMappingURL=index.js.map
5
5
  //# sourceMappingURL=index.js.map
@@ -1,8 +1,9 @@
1
1
  import { DeadlineSub } from '../deadline/index.js';
2
2
  import { IdempotencyStore } from '../idempotency/index.js';
3
- import { C as Cmd, S as Sub } from '../core-Bjc0bS8j.js';
3
+ import { C as Cmd, S as Sub } from '../core-Co0LWkIA.js';
4
4
  import { RetryState, AnyRetryPolicy } from '../retry-backoff/index.js';
5
- import '../types-D7u9DkH7.js';
5
+ import '../types-XS_miRnB.js';
6
+ import 'zod';
6
7
 
7
8
  /**
8
9
  * @demlik/tea/poller — "poll a source every N ms until a predicate holds, with
@@ -1,10 +1,10 @@
1
- import { deadlineSub } from '../chunk-JGVL3WPL.js';
1
+ import { deadlineSub } from '../chunk-DGE4PV4A.js';
2
2
  import { asRng, defaultRetryPolicy, initRetry, recordFailure, shouldRetry, nextDelayMs } from '../chunk-Q2UM7BXU.js';
3
3
  import { idempotencyMemory } from '../chunk-BYQUGDD3.js';
4
4
  import '../chunk-K575PT4S.js';
5
5
  import '../chunk-2YFLOCWX.js';
6
- import '../chunk-5EHLEUTL.js';
7
- import '../chunk-B5VXOFMZ.js';
6
+ import '../chunk-ZSUUOEPL.js';
7
+ import '../chunk-OOYD44JI.js';
8
8
 
9
9
  // src/poller/index.ts
10
10
  function pollerSubId(atMs) {
@@ -1,4 +1,5 @@
1
- import { C as Cmd, S as Sub, M as Machine } from '../core-Bjc0bS8j.js';
1
+ import { C as Cmd, S as Sub, M as Machine } from '../core-Co0LWkIA.js';
2
+ import 'zod';
2
3
 
3
4
  /**
4
5
  * @demlik/tea/prediction — the client-prediction ack primitive (epic #186, facet 2).
@@ -1,4 +1,4 @@
1
- export { NO_ACK, ack, initAck, nextSeq, partitionByAck, reconcile, tagSeq } from '../chunk-4KTWLPL5.js';
2
- import '../chunk-B5VXOFMZ.js';
1
+ export { NO_ACK, ack, initAck, nextSeq, partitionByAck, reconcile, tagSeq } from '../chunk-4EQZ76JC.js';
2
+ import '../chunk-OOYD44JI.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -1,2 +1,3 @@
1
1
  export { Ack, AckPartition, NO_ACK, Seq, SeqTagged, ack, initAck, nextSeq, partitionByAck, reconcile, tagSeq } from '../prediction/index.js';
2
- export { C as Cmd, d as ContextFree, D as DepKeyedSub, e as Dispose, f as Identity, I as Interpret, c as InterpretDetached, M as Machine, g as MachineShape, N as NoCellError, h as NoCtx, a as Port, P as PortEmitter, R as Reducer, S as Sub, b as SubId, i as Subscribe, j as SyncReturn, T as Transitions, U as UpdateForm, k as acceptsOf, n as describeMachine, o as detectUpdateForm, p as foldMsgs, r as formOf, t as structuralHash, u as subId } from '../core-Bjc0bS8j.js';
2
+ export { A as AnyCmdDef, C as Cmd, h as CmdDef, i as CmdInput, e as CmdOf, j as CmdValue, k as ContextFree, D as DepKeyedSub, l as Dispose, E as ErrOf, m as ErrorsOf, n as Identity, I as Interpret, g as InterpretDetached, M as Machine, o as MachineShape, p as MalformedResult, q as Needs, f as NeedsOf, N as NoCellError, r as NoCtx, O as OkOf, a as Port, P as PortEmitter, c as Reducer, R as RequiredCtx, d as Settled, s as SettledErr, t as SettledOk, S as Sub, b as SubId, u as Subscribe, v as SyncReturn, w as Tagged, x as TaggedError, T as Transitions, U as UpdateForm, y as acceptsOf, F as describeMachine, G as detectUpdateForm, H as foldMsgs, K as formOf, Q as structuralHash, V as subId } from '../core-Co0LWkIA.js';
3
+ import 'zod';
@@ -1,4 +1,4 @@
1
- export { NO_ACK, ack, initAck, nextSeq, partitionByAck, reconcile, tagSeq } from '../chunk-4KTWLPL5.js';
2
- export { Cmd, NoCellError, acceptsOf, describeMachine, detectUpdateForm, foldMsgs, formOf, structuralHash, subId } from '../chunk-B5VXOFMZ.js';
1
+ export { NO_ACK, ack, initAck, nextSeq, partitionByAck, reconcile, tagSeq } from '../chunk-4EQZ76JC.js';
2
+ export { Cmd, NoCellError, acceptsOf, describeMachine, detectUpdateForm, foldMsgs, formOf, structuralHash, subId } from '../chunk-OOYD44JI.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,7 @@
1
- import { S as Store, R as Runtime } from '../runtime-types-Bxc637bp.js';
2
- import { C as Cmd, S as Sub, M as Machine } from '../core-Bjc0bS8j.js';
1
+ import { S as Store, R as Runtime } from '../runtime-types-CNLmhWif.js';
2
+ import { C as Cmd, S as Sub, M as Machine, R as RequiredCtx } from '../core-Co0LWkIA.js';
3
3
  import 'better-result';
4
+ import 'zod';
4
5
 
5
6
  /**
6
7
  * @packageDocumentation
@@ -60,7 +61,7 @@ interface UseMachineOpts<S, Ctx> {
60
61
  */
61
62
  declare function useMachine<S, M extends {
62
63
  type: string;
63
- }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, opts: UseMachineOpts<S, Ctx>): [S, (msg: M) => Promise<void>];
64
+ }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, opts: UseMachineOpts<S, Ctx & RequiredCtx<C>>): [S, (msg: M) => Promise<void>];
64
65
  /**
65
66
  * Lower-level escape hatch: consume an externally-built `Runtime<S, M>`.
66
67
  *
@@ -1,5 +1,5 @@
1
- import { run } from '../chunk-5EHLEUTL.js';
2
- import '../chunk-B5VXOFMZ.js';
1
+ import { run } from '../chunk-ZSUUOEPL.js';
2
+ import '../chunk-OOYD44JI.js';
3
3
  import { useMemo, useRef, useReducer, useSyncExternalStore, useEffect } from 'react';
4
4
 
5
5
  function useMachine(machine, opts) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.ts"],"names":[],"mappings":";;;;AA4EO,SAAS,UAAA,CAOd,SACA,IAAA,EACgC;AAMhC,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA;AACjB,EAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AAMnB,EAAA,MAAM,OAAA,GAAU,OAAA;AAAA,IACd,MAAM,GAAA,CAAI,OAAA,EAAS,EAAE,GAAA,EAAK,OAAO,CAAA;AAAA,IACjC,CAAC,OAAA,EAAS,GAAA,EAAK,KAAK;AAAA,GACtB;AAeA,EAAA,MAAM,QAAA,GAAW,OAA6B,IAAI,CAAA;AAClD,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;AAeO,SAAS,WACd,OAAA,EACgC;AAChC,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 {\n type BootingRuntime,\n type Cmd,\n type Machine,\n type Runtime,\n run,\n type Store,\n type Sub,\n} from \"../index\";\n\n/**\n * Options passed to `useMachine`. The shape is intentionally minimal — `ctx`\n * is required (every machine has one), `store` is optional (omit it for\n * volatile-state machines).\n *\n * **Identity matters.** The runtime is memoized on `[machine, opts.ctx,\n * opts.store]`. A new `ctx` reference rebuilds the runtime. 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 interface UseMachineOpts<S, Ctx> {\n ctx: Ctx;\n store?: Store<S>;\n}\n\n/**\n * Build and own a `Runtime<S, M>` for the lifetime of the component mount.\n *\n * - Builds via `useMemo(() => run(machine, opts), [machine, opts.ctx,\n * opts.store])` — runtime is recreated when ANY of the three identities\n * 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: UseMachineOpts<S, Ctx>,\n): [S, (msg: M) => Promise<void>] {\n // Deps are literal: machine identity, ctx identity, store identity. NOT\n // `[opts]` (would rebuild every render — callers pass fresh objects).\n // We pass explicit fields to `run()` so the closure captures only the\n // deps in the array — keeps biome's `useExhaustiveDependencies` happy\n // without lying about what the memo actually depends on.\n const ctx = opts.ctx;\n const store = opts.store;\n // `run()` returns a `BootingRuntime<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 `Runtime` (the only thing with a\n // total `getState`) once `ready` resolves.\n const booting = useMemo<BootingRuntime<S, M>>(\n () => run(machine, { ctx, store }),\n [machine, ctx, store],\n );\n\n // Captures the booted `Runtime` 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<Runtime<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 `Runtime` 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 * Lower-level escape hatch: consume an externally-built `Runtime<S, M>`.\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<S, M extends { type: string }>(\n runtime: Runtime<S, M>,\n): [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/index.ts"],"names":[],"mappings":";;;;AA6EO,SAAS,UAAA,CAOd,SAEA,IAAA,EACgC;AAMhC,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA;AACjB,EAAA,MAAM,QAAQ,IAAA,CAAK,KAAA;AAMnB,EAAA,MAAM,OAAA,GAAU,OAAA;AAAA,IACd,MAAM,GAAA,CAAI,OAAA,EAAS,EAAE,GAAA,EAAK,OAAO,CAAA;AAAA,IACjC,CAAC,OAAA,EAAS,GAAA,EAAK,KAAK;AAAA,GACtB;AAeA,EAAA,MAAM,QAAA,GAAW,OAA6B,IAAI,CAAA;AAClD,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;AAeO,SAAS,WACd,OAAA,EACgC;AAChC,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 {\n type BootingRuntime,\n type Cmd,\n type Machine,\n type RequiredCtx,\n type Runtime,\n run,\n type Store,\n type Sub,\n} from \"../index\";\n\n/**\n * Options passed to `useMachine`. The shape is intentionally minimal — `ctx`\n * is required (every machine has one), `store` is optional (omit it for\n * volatile-state machines).\n *\n * **Identity matters.** The runtime is memoized on `[machine, opts.ctx,\n * opts.store]`. A new `ctx` reference rebuilds the runtime. 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 interface UseMachineOpts<S, Ctx> {\n ctx: Ctx;\n store?: Store<S>;\n}\n\n/**\n * Build and own a `Runtime<S, M>` for the lifetime of the component mount.\n *\n * - Builds via `useMemo(() => run(machine, opts), [machine, opts.ctx,\n * opts.store])` — runtime is recreated when ANY of the three identities\n * 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 // The ctx `run` demands: the machine's own plus every typed Cmd's `R`.\n opts: UseMachineOpts<S, Ctx & RequiredCtx<C>>,\n): [S, (msg: M) => Promise<void>] {\n // Deps are literal: machine identity, ctx identity, store identity. NOT\n // `[opts]` (would rebuild every render — callers pass fresh objects).\n // We pass explicit fields to `run()` so the closure captures only the\n // deps in the array — keeps biome's `useExhaustiveDependencies` happy\n // without lying about what the memo actually depends on.\n const ctx = opts.ctx;\n const store = opts.store;\n // `run()` returns a `BootingRuntime<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 `Runtime` (the only thing with a\n // total `getState`) once `ready` resolves.\n const booting = useMemo<BootingRuntime<S, M>>(\n () => run(machine, { ctx, store }),\n [machine, ctx, store],\n );\n\n // Captures the booted `Runtime` 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<Runtime<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 `Runtime` 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 * Lower-level escape hatch: consume an externally-built `Runtime<S, M>`.\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<S, M extends { type: string }>(\n runtime: Runtime<S, M>,\n): [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,6 +1,6 @@
1
1
  import { CircuitConfig, RateLimitConfig, DeadlineConfig, ResilientPorts, RunCmd, FailMsg, SucceedMsg } from '../resilient-call/index.js';
2
2
  import { TtlCache } from '../cache/index.js';
3
- import { C as Cmd } from '../core-Bjc0bS8j.js';
3
+ import { C as Cmd } from '../core-Co0LWkIA.js';
4
4
  import { PaginatedWalkState, PaginatedWalkTimerMsg, FetchPageCmd, PageErrMsg, PageOkMsg } from '../paginated-walk/index.js';
5
5
  import { RetryPolicy } from '../retry-backoff/index.js';
6
6
  import { DeadlineSub } from '../deadline/index.js';
@@ -8,7 +8,8 @@ export { deadlineSub, subscribeDeadline } from '../deadline/index.js';
8
8
  import '../circuit-breaker/index.js';
9
9
  import '../protocol-B5YmJr0d.js';
10
10
  import '../rate-limit/index.js';
11
- import '../types-D7u9DkH7.js';
11
+ import '../types-XS_miRnB.js';
12
+ import 'zod';
12
13
  import '../paginator/index.js';
13
14
 
14
15
  /**
@@ -1,17 +1,17 @@
1
- import { createPaginatedWalk, PAGE_KEY } from '../chunk-I5OV65MS.js';
1
+ import { createPaginatedWalk, PAGE_KEY } from '../chunk-YUXP55T2.js';
2
2
  import '../chunk-SINXE47Y.js';
3
- import '../chunk-EUF7XOEE.js';
3
+ import '../chunk-4KLG43GY.js';
4
4
  import '../chunk-JELSZN25.js';
5
- export { deadlineSub, subscribeDeadline } from '../chunk-JGVL3WPL.js';
5
+ import { initCache, get, set } from '../chunk-RH5EFRVJ.js';
6
+ export { deadlineSub, subscribeDeadline } from '../chunk-DGE4PV4A.js';
6
7
  import '../chunk-Q2UM7BXU.js';
7
8
  import '../chunk-S2EGRPCM.js';
8
9
  import '../chunk-UF4MZSLT.js';
9
- import { initCache, get, set } from '../chunk-7SVIRMC6.js';
10
- import '../chunk-3JU6657S.js';
10
+ import '../chunk-VYFAZN3F.js';
11
11
  import '../chunk-2YFLOCWX.js';
12
12
  import '../chunk-ZJ5AOYBH.js';
13
- import '../chunk-5EHLEUTL.js';
14
- import '../chunk-B5VXOFMZ.js';
13
+ import '../chunk-ZSUUOEPL.js';
14
+ import '../chunk-OOYD44JI.js';
15
15
 
16
16
  // src/reconciler/index.ts
17
17
  function createReconciler(config, rng = Math.random) {
@@ -1,6 +1,7 @@
1
- import { B as BootingRuntime } from '../runtime-types-Bxc637bp.js';
1
+ import { B as BootingRuntime } from '../runtime-types-CNLmhWif.js';
2
2
  import 'better-result';
3
- import '../core-Bjc0bS8j.js';
3
+ import '../core-Co0LWkIA.js';
4
+ import 'zod';
4
5
 
5
6
  /**
6
7
  * A recorded run, sufficient to reproduce it via `../trace-replay`.
@@ -1,5 +1,5 @@
1
- import { k as EventOrigin, C as Chart, R as ReducerChart } from './graph-CcjWZVrd.js';
2
- import { R as RtSamples } from './samples-DCt6nF8H.js';
1
+ import { k as EventOrigin, C as Chart, R as ReducerChart } from './graph-B3L5e0N1.js';
2
+ import { R as RtSamples } from './samples-B3My5oh2.js';
3
3
 
4
4
  /**
5
5
  * WHY a (state, event) pair is refused. The chart's three refusal mechanisms,
@@ -2,11 +2,12 @@ import { TtlCache } from '../cache/index.js';
2
2
  import { CircuitState } from '../circuit-breaker/index.js';
3
3
  import { DeadlineExceeded, DeadlineSub } from '../deadline/index.js';
4
4
  export { deadlineSub, subscribeDeadline } from '../deadline/index.js';
5
- import { C as Cmd } from '../core-Bjc0bS8j.js';
5
+ import { C as Cmd } from '../core-Co0LWkIA.js';
6
6
  import { M as MsgType } from '../protocol-B5YmJr0d.js';
7
7
  import { TokenBucket } from '../rate-limit/index.js';
8
8
  import { RetryState, AnyRetryPolicy } from '../retry-backoff/index.js';
9
- import '../types-D7u9DkH7.js';
9
+ import '../types-XS_miRnB.js';
10
+ import 'zod';
10
11
 
11
12
  /**
12
13
  * @packageDocumentation