@demlik/tea 0.1.2 → 0.3.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.
- package/README.md +49 -0
- package/dist/agent/index.d.ts +16 -770
- package/dist/agent/index.js +18 -16
- package/dist/authed-call/index.d.ts +3 -3
- package/dist/authed-call/index.js +22 -36
- package/dist/authed-call/index.js.map +1 -1
- package/dist/await-terminal/index.d.ts +2 -2
- package/dist/await-terminal/index.js +2 -2
- package/dist/batch-window/index.d.ts +3 -3
- package/dist/batch-window/index.js +3 -3
- package/dist/cache/index.d.ts +2 -2
- package/dist/cache/index.js +5 -2
- package/dist/{chunk-3545BOYL.js → chunk-25LBDCBC.js} +6 -5
- package/dist/chunk-25LBDCBC.js.map +1 -0
- package/dist/{chunk-IN33Q4VJ.js → chunk-2KNLKQVN.js} +3 -3
- package/dist/{chunk-IN33Q4VJ.js.map → chunk-2KNLKQVN.js.map} +1 -1
- package/dist/{chunk-MMH7CW5K.js → chunk-BP2R7TPP.js} +216 -157
- package/dist/chunk-BP2R7TPP.js.map +1 -0
- package/dist/chunk-C6QC273X.js +71 -0
- package/dist/chunk-C6QC273X.js.map +1 -0
- package/dist/{chunk-7W4LMPUW.js → chunk-CAXMQJJ3.js} +17 -24
- package/dist/chunk-CAXMQJJ3.js.map +1 -0
- package/dist/{chunk-UV6UWGEO.js → chunk-D736QUGF.js} +6 -5
- package/dist/chunk-D736QUGF.js.map +1 -0
- package/dist/chunk-EWJP3YYP.js +21 -0
- package/dist/chunk-EWJP3YYP.js.map +1 -0
- package/dist/{chunk-TGG26XMK.js → chunk-FV3Z5MBE.js} +10 -5
- package/dist/chunk-FV3Z5MBE.js.map +1 -0
- package/dist/chunk-G5E74IJB.js +8 -0
- package/dist/chunk-G5E74IJB.js.map +1 -0
- package/dist/{chunk-27N2SPTN.js → chunk-IXBKFQH5.js} +12 -11
- package/dist/chunk-IXBKFQH5.js.map +1 -0
- package/dist/{chunk-SQTCS44W.js → chunk-JFFVBEDI.js} +3 -3
- package/dist/{chunk-SQTCS44W.js.map → chunk-JFFVBEDI.js.map} +1 -1
- package/dist/chunk-LYLDCIPH.js +365 -0
- package/dist/chunk-LYLDCIPH.js.map +1 -0
- package/dist/{chunk-HJO6VNQI.js → chunk-MU6MCIAC.js} +3 -3
- package/dist/{chunk-HJO6VNQI.js.map → chunk-MU6MCIAC.js.map} +1 -1
- package/dist/{chunk-UYWUVEP6.js → chunk-MUUROGIH.js} +2 -2
- package/dist/chunk-MUUROGIH.js.map +1 -0
- package/dist/{chunk-EHIX7NYZ.js → chunk-MVY5DAMF.js} +4 -4
- package/dist/{chunk-EHIX7NYZ.js.map → chunk-MVY5DAMF.js.map} +1 -1
- package/dist/{chunk-LP7VQBRG.js → chunk-NPERUUUE.js} +76 -95
- package/dist/chunk-NPERUUUE.js.map +1 -0
- package/dist/{chunk-QLA76GIT.js → chunk-PBDYW2TU.js} +68 -9
- package/dist/chunk-PBDYW2TU.js.map +1 -0
- package/dist/{chunk-5VNWZADP.js → chunk-Q2UM7BXU.js} +18 -7
- package/dist/chunk-Q2UM7BXU.js.map +1 -0
- package/dist/chunk-RMX4EOQK.js +26 -0
- package/dist/chunk-RMX4EOQK.js.map +1 -0
- package/dist/{chunk-2YOE5PIS.js → chunk-S2EGRPCM.js} +8 -6
- package/dist/chunk-S2EGRPCM.js.map +1 -0
- package/dist/{chunk-ON24RKSE.js → chunk-S4O26D7B.js} +7 -14
- package/dist/chunk-S4O26D7B.js.map +1 -0
- package/dist/{chunk-3XJWC7VW.js → chunk-SK5JGUY7.js} +3 -3
- package/dist/{chunk-3XJWC7VW.js.map → chunk-SK5JGUY7.js.map} +1 -1
- package/dist/{chunk-5DEQ5I3U.js → chunk-TPGASTFF.js} +8 -10
- package/dist/chunk-TPGASTFF.js.map +1 -0
- package/dist/{chunk-SC3FAFZN.js → chunk-UF4MZSLT.js} +2 -2
- package/dist/chunk-UF4MZSLT.js.map +1 -0
- package/dist/{chunk-PS23H6KQ.js → chunk-VBUCHVCV.js} +6 -5
- package/dist/chunk-VBUCHVCV.js.map +1 -0
- package/dist/{chunk-S3FQN4SL.js → chunk-WGGAMW7Y.js} +191 -178
- package/dist/chunk-WGGAMW7Y.js.map +1 -0
- package/dist/chunk-ZJ5AOYBH.js +8 -0
- package/dist/chunk-ZJ5AOYBH.js.map +1 -0
- package/dist/{chunk-LEC4U6CX.js → chunk-ZVJZ72HY.js} +3 -3
- package/dist/{chunk-LEC4U6CX.js.map → chunk-ZVJZ72HY.js.map} +1 -1
- package/dist/circuit-breaker/index.d.ts +1 -0
- package/dist/circuit-breaker/index.js +1 -1
- package/dist/{core-BoB6t7dB.d.ts → core-42kapW0n.d.ts} +105 -6
- package/dist/deadline/index.d.ts +77 -11
- package/dist/deadline/index.js +3 -3
- package/dist/devtools/index.js +8 -28
- package/dist/devtools/index.js.map +1 -1
- package/dist/do/index.d.ts +483 -339
- package/dist/do/index.js +244 -214
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-CO9Uh971.d.ts → durable-effects-CFRngTNA.d.ts} +3 -3
- package/dist/extension/index.d.ts +25 -7
- package/dist/extension/index.js +4 -3
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/react.js +4 -3
- package/dist/extension/react.js.map +1 -1
- package/dist/extension/subs/index.d.ts +2 -2
- package/dist/extension/subs/index.js +7 -10
- package/dist/extension/subs/index.js.map +1 -1
- package/dist/fan-out/index.d.ts +10 -10
- package/dist/fan-out/index.js +3 -3
- package/dist/idempotent-intake/index.d.ts +2 -2
- package/dist/idempotent-intake/index.js +3 -3
- package/dist/index.d.ts +50 -472
- package/dist/index.js +2 -2
- package/dist/llm-call/index.d.ts +3 -2
- package/dist/llm-call/index.js +12 -10
- package/dist/machine-B_BF4jij.d.ts +804 -0
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +2 -2
- package/dist/mem/index.d.ts +3 -2
- package/dist/mem/index.js.map +1 -1
- package/dist/monitored-run/index.d.ts +54 -30
- package/dist/monitored-run/index.js +7 -7
- package/dist/node/index.d.ts +3 -2
- package/dist/node/index.js +2 -3
- package/dist/node/index.js.map +1 -1
- package/dist/paginated-walk/index.d.ts +2 -2
- package/dist/paginated-walk/index.js +11 -10
- package/dist/parity/index.d.ts +6 -5
- package/dist/parity/index.js +5 -4
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/arbitraries/index.d.ts +1 -1
- package/dist/pbt/index.d.ts +1 -1
- package/dist/pbt/index.js +3 -3
- package/dist/pbt/index.js.map +1 -1
- package/dist/pbt/runners/index.d.ts +1 -1
- package/dist/pbt/runners/index.js +3 -3
- package/dist/poller/index.d.ts +59 -17
- package/dist/poller/index.js +56 -20
- package/dist/poller/index.js.map +1 -1
- package/dist/prediction/index.d.ts +1 -1
- package/dist/prediction/index.js +2 -2
- package/dist/pure/index.d.ts +1 -1
- package/dist/pure/index.js +2 -2
- package/dist/rate-limit/index.d.ts +2 -2
- package/dist/rate-limit/index.js +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/reconciler/index.d.ts +2 -2
- package/dist/reconciler/index.js +13 -15
- package/dist/reconciler/index.js.map +1 -1
- package/dist/recorder/index.d.ts +20 -20
- package/dist/recorder/index.js +1 -1
- package/dist/resilient-call/index.d.ts +31 -6
- package/dist/resilient-call/index.js +10 -9
- package/dist/retry-backoff/index.d.ts +191 -14
- package/dist/retry-backoff/index.js +1 -1
- package/dist/retry-to-success/index.js +1 -1
- package/dist/runtime-types-gsog27b6.d.ts +337 -0
- package/dist/saga/index.d.ts +3 -2
- package/dist/saga/index.js +2 -2
- package/dist/saga/index.js.map +1 -1
- package/dist/snapshot/index.d.ts +1 -1
- package/dist/snapshot/index.js +3 -3
- package/dist/subs/index.d.ts +372 -4
- package/dist/subs/index.js +4 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/throttled-input/index.d.ts +2 -2
- package/dist/throttled-input/index.js +14 -13
- package/dist/throttled-input/index.js.map +1 -1
- package/dist/token-refresh/index.d.ts +1 -1
- package/dist/token-refresh/index.js +3 -3
- package/dist/trace-replay/index.d.ts +12 -7
- package/dist/trace-replay/index.js +4 -3
- package/dist/{types-B-8bcAp8.d.ts → types-KbL3W1mj.d.ts} +1 -1
- package/dist/with-deadline/index.d.ts +1 -1
- package/dist/with-deadline/index.js +2 -2
- package/dist/with-resilience/index.d.ts +21 -2
- package/dist/with-resilience/index.js +12 -23
- package/dist/with-resilience/index.js.map +1 -1
- package/dist/with-telemetry/index.d.ts +1 -1
- package/dist/with-telemetry/index.js +2 -2
- package/dist/work-queue/adapter.d.ts +2 -2
- package/dist/work-queue/adapter.js +2 -2
- package/dist/work-queue/index.d.ts +3 -2
- package/dist/work-queue/index.js +1 -1
- package/dist/work-queue/index.js.map +1 -1
- package/dist/work-queue/ops.d.ts +2 -2
- package/dist/work-queue/ops.js +1 -1
- package/dist/workflow/index.d.ts +51 -19
- package/dist/workflow/index.js +71 -52
- package/dist/workflow/index.js.map +1 -1
- package/package.json +4 -19
- package/dist/chunk-27N2SPTN.js.map +0 -1
- package/dist/chunk-2YOE5PIS.js.map +0 -1
- package/dist/chunk-3545BOYL.js.map +0 -1
- package/dist/chunk-5DEQ5I3U.js.map +0 -1
- package/dist/chunk-5VNWZADP.js.map +0 -1
- package/dist/chunk-6RGBKODV.js +0 -268
- package/dist/chunk-6RGBKODV.js.map +0 -1
- package/dist/chunk-7W4LMPUW.js.map +0 -1
- package/dist/chunk-DILMKOZ7.js +0 -91
- package/dist/chunk-DILMKOZ7.js.map +0 -1
- package/dist/chunk-K7Q23EQ4.js +0 -20
- package/dist/chunk-K7Q23EQ4.js.map +0 -1
- package/dist/chunk-LP7VQBRG.js.map +0 -1
- package/dist/chunk-MMH7CW5K.js.map +0 -1
- package/dist/chunk-NSDFA6M6.js +0 -198
- package/dist/chunk-NSDFA6M6.js.map +0 -1
- package/dist/chunk-ON24RKSE.js.map +0 -1
- package/dist/chunk-PS23H6KQ.js.map +0 -1
- package/dist/chunk-QLA76GIT.js.map +0 -1
- package/dist/chunk-S3FQN4SL.js.map +0 -1
- package/dist/chunk-SC3FAFZN.js.map +0 -1
- package/dist/chunk-TGG26XMK.js.map +0 -1
- package/dist/chunk-UV6UWGEO.js.map +0 -1
- package/dist/chunk-UYWUVEP6.js.map +0 -1
- package/dist/chunk-VLGARN6H.js +0 -202
- package/dist/chunk-VLGARN6H.js.map +0 -1
- package/dist/raft/demo.d.ts +0 -105
- package/dist/raft/demo.js +0 -172
- package/dist/raft/demo.js.map +0 -1
- package/dist/raft/index.d.ts +0 -548
- package/dist/raft/index.js +0 -7
- package/dist/raft/index.js.map +0 -1
- package/dist/raft/sim.d.ts +0 -222
- package/dist/raft/sim.js +0 -8
- package/dist/raft/sim.js.map +0 -1
- package/dist/raft/timeline.d.ts +0 -46
- package/dist/raft/timeline.js +0 -62
- package/dist/raft/timeline.js.map +0 -1
package/dist/chunk-NSDFA6M6.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
// src/subs/from-broadcast-channel.ts
|
|
2
|
-
function fromBroadcastChannel(msgFn) {
|
|
3
|
-
return (sub, _ctx, dispatch) => {
|
|
4
|
-
const channel = new BroadcastChannel(sub.channelName);
|
|
5
|
-
const listener = (event) => {
|
|
6
|
-
const msg = msgFn(event, sub);
|
|
7
|
-
if (msg !== null) dispatch(msg);
|
|
8
|
-
};
|
|
9
|
-
channel.addEventListener("message", listener);
|
|
10
|
-
return () => {
|
|
11
|
-
channel.removeEventListener("message", listener);
|
|
12
|
-
channel.close();
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// src/subs/from-event-source.ts
|
|
18
|
-
function fromEventSource(opts) {
|
|
19
|
-
return (sub, _ctx, dispatch) => {
|
|
20
|
-
const source = new EventSource(sub.url);
|
|
21
|
-
const messageListener = (event) => {
|
|
22
|
-
const msg = opts.onMessage(event.data, sub);
|
|
23
|
-
if (msg !== null) dispatch(msg);
|
|
24
|
-
};
|
|
25
|
-
source.addEventListener("message", messageListener);
|
|
26
|
-
const onError = opts.onError;
|
|
27
|
-
const errorListener = onError ? (event) => {
|
|
28
|
-
const msg = onError(event, sub);
|
|
29
|
-
if (msg !== null) dispatch(msg);
|
|
30
|
-
} : void 0;
|
|
31
|
-
if (errorListener) source.addEventListener("error", errorListener);
|
|
32
|
-
const onOpen = opts.onOpen;
|
|
33
|
-
const openListener = onOpen ? () => {
|
|
34
|
-
const msg = onOpen(sub);
|
|
35
|
-
if (msg !== null) dispatch(msg);
|
|
36
|
-
} : void 0;
|
|
37
|
-
if (openListener) source.addEventListener("open", openListener);
|
|
38
|
-
return () => {
|
|
39
|
-
source.removeEventListener("message", messageListener);
|
|
40
|
-
if (errorListener) source.removeEventListener("error", errorListener);
|
|
41
|
-
if (openListener) source.removeEventListener("open", openListener);
|
|
42
|
-
source.close();
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// src/subs/from-event-target.ts
|
|
48
|
-
function fromEventTarget(getTarget, eventName, msgFn) {
|
|
49
|
-
return (sub, _ctx, dispatch) => {
|
|
50
|
-
const target = getTarget();
|
|
51
|
-
const listener = (event) => {
|
|
52
|
-
const msg = msgFn(event, sub);
|
|
53
|
-
if (msg !== null) dispatch(msg);
|
|
54
|
-
};
|
|
55
|
-
target.addEventListener(eventName, listener);
|
|
56
|
-
return () => {
|
|
57
|
-
target.removeEventListener(eventName, listener);
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// src/subs/from-interval.ts
|
|
63
|
-
function fromInterval(msgFn) {
|
|
64
|
-
return (sub, _ctx, dispatch) => {
|
|
65
|
-
const handle = setInterval(() => {
|
|
66
|
-
dispatch(msgFn(sub));
|
|
67
|
-
}, sub.intervalMs);
|
|
68
|
-
return () => {
|
|
69
|
-
clearInterval(handle);
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// src/subs/from-port.ts
|
|
75
|
-
function fromPort(selectRuntime, port, msgFn) {
|
|
76
|
-
return (sub, ctx, dispatch) => {
|
|
77
|
-
const runtime = selectRuntime(ctx);
|
|
78
|
-
return runtime.subscribePort(port, (value) => {
|
|
79
|
-
const msg = msgFn(value, sub);
|
|
80
|
-
if (msg !== null) dispatch(msg);
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// src/subs/from-reconnecting-web-socket.ts
|
|
86
|
-
var defaultConnect = (url) => new WebSocket(url);
|
|
87
|
-
var defaultSchedule = (fn, ms) => {
|
|
88
|
-
const handle = setTimeout(fn, ms);
|
|
89
|
-
return () => clearTimeout(handle);
|
|
90
|
-
};
|
|
91
|
-
function fromReconnectingWebSocket(opts) {
|
|
92
|
-
const backoffBaseMs = opts.backoffBaseMs ?? 250;
|
|
93
|
-
const backoffMaxMs = opts.backoffMaxMs ?? 5e3;
|
|
94
|
-
const connect = opts.connect ?? defaultConnect;
|
|
95
|
-
const schedule = opts.schedule ?? defaultSchedule;
|
|
96
|
-
const { onMessage, onOpen, onError, onClose, onReconnect } = opts;
|
|
97
|
-
return (sub, _ctx, dispatch) => {
|
|
98
|
-
let ws = null;
|
|
99
|
-
let attempt = 0;
|
|
100
|
-
let opened = false;
|
|
101
|
-
let closedByCaller = false;
|
|
102
|
-
let cancelReconnect = null;
|
|
103
|
-
const emit = (msg) => {
|
|
104
|
-
if (msg !== null) dispatch(msg);
|
|
105
|
-
};
|
|
106
|
-
const handleClose = (code, reason) => {
|
|
107
|
-
ws = null;
|
|
108
|
-
if (onClose) emit(onClose(code, reason, sub));
|
|
109
|
-
if (closedByCaller) return;
|
|
110
|
-
const delay = Math.min(backoffBaseMs * 2 ** attempt, backoffMaxMs);
|
|
111
|
-
attempt += 1;
|
|
112
|
-
cancelReconnect = schedule(() => {
|
|
113
|
-
cancelReconnect = null;
|
|
114
|
-
if (!closedByCaller) open();
|
|
115
|
-
}, delay);
|
|
116
|
-
};
|
|
117
|
-
function open() {
|
|
118
|
-
const socket = connect(sub.wsUrl);
|
|
119
|
-
ws = socket;
|
|
120
|
-
socket.onopen = () => {
|
|
121
|
-
const reconnected = opened;
|
|
122
|
-
const reconnectCount = attempt;
|
|
123
|
-
attempt = 0;
|
|
124
|
-
opened = true;
|
|
125
|
-
if (onOpen) emit(onOpen(sub));
|
|
126
|
-
if (reconnected && onReconnect) emit(onReconnect(reconnectCount, sub));
|
|
127
|
-
};
|
|
128
|
-
socket.onmessage = (event) => {
|
|
129
|
-
emit(onMessage(event.data, sub));
|
|
130
|
-
};
|
|
131
|
-
socket.onerror = onError ? (event) => emit(onError(event, sub)) : () => {
|
|
132
|
-
};
|
|
133
|
-
socket.onclose = (event) => {
|
|
134
|
-
handleClose(event.code, event.reason);
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
open();
|
|
138
|
-
return () => {
|
|
139
|
-
closedByCaller = true;
|
|
140
|
-
if (cancelReconnect !== null) {
|
|
141
|
-
cancelReconnect();
|
|
142
|
-
cancelReconnect = null;
|
|
143
|
-
}
|
|
144
|
-
const socket = ws;
|
|
145
|
-
ws = null;
|
|
146
|
-
if (socket !== null) {
|
|
147
|
-
socket.onopen = null;
|
|
148
|
-
socket.onmessage = null;
|
|
149
|
-
socket.onerror = null;
|
|
150
|
-
socket.onclose = null;
|
|
151
|
-
socket.close(1e3);
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// src/subs/from-web-socket.ts
|
|
158
|
-
function fromWebSocket(opts) {
|
|
159
|
-
return (sub, _ctx, dispatch) => {
|
|
160
|
-
const ws = new WebSocket(sub.wsUrl);
|
|
161
|
-
ws.onmessage = (event) => {
|
|
162
|
-
const msg = opts.onMessage(event.data, sub);
|
|
163
|
-
if (msg !== null) dispatch(msg);
|
|
164
|
-
};
|
|
165
|
-
const onOpen = opts.onOpen;
|
|
166
|
-
if (onOpen) {
|
|
167
|
-
ws.onopen = () => {
|
|
168
|
-
const msg = onOpen(sub);
|
|
169
|
-
if (msg !== null) dispatch(msg);
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
const onError = opts.onError;
|
|
173
|
-
if (onError) {
|
|
174
|
-
ws.onerror = (event) => {
|
|
175
|
-
const msg = onError(event, sub);
|
|
176
|
-
if (msg !== null) dispatch(msg);
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
const onClose = opts.onClose;
|
|
180
|
-
if (onClose) {
|
|
181
|
-
ws.onclose = (event) => {
|
|
182
|
-
const msg = onClose(event.code, event.reason, sub);
|
|
183
|
-
if (msg !== null) dispatch(msg);
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
return () => {
|
|
187
|
-
ws.onmessage = null;
|
|
188
|
-
ws.onopen = null;
|
|
189
|
-
ws.onerror = null;
|
|
190
|
-
ws.onclose = null;
|
|
191
|
-
ws.close(1e3);
|
|
192
|
-
};
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export { fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromReconnectingWebSocket, fromWebSocket };
|
|
197
|
-
//# sourceMappingURL=chunk-NSDFA6M6.js.map
|
|
198
|
-
//# sourceMappingURL=chunk-NSDFA6M6.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/subs/from-broadcast-channel.ts","../src/subs/from-event-source.ts","../src/subs/from-event-target.ts","../src/subs/from-interval.ts","../src/subs/from-port.ts","../src/subs/from-reconnecting-web-socket.ts","../src/subs/from-web-socket.ts"],"names":[],"mappings":";AAsCO,SAAS,qBACd,KAAA,EACiC;AACjC,EAAA,OAAO,CAAC,GAAA,EAAK,IAAA,EAAM,QAAA,KAAa;AAC9B,IAAA,MAAM,OAAA,GAAU,IAAI,gBAAA,CAAiB,GAAA,CAAI,WAAW,CAAA;AACpD,IAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAqC;AACrD,MAAA,MAAM,GAAA,GAAM,KAAA,CAAM,KAAA,EAAO,GAAG,CAAA;AAC5B,MAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,IAChC,CAAA;AACA,IAAA,OAAA,CAAQ,gBAAA,CAAiB,WAAW,QAAQ,CAAA;AAC5C,IAAA,OAAO,MAAM;AACX,MAAA,OAAA,CAAQ,mBAAA,CAAoB,WAAW,QAAQ,CAAA;AAC/C,MAAA,OAAA,CAAQ,KAAA,EAAM;AAAA,IAChB,CAAA;AAAA,EACF,CAAA;AACF;;;ACMO,SAAS,gBACd,IAAA,EACiC;AACjC,EAAA,OAAO,CAAC,GAAA,EAAK,IAAA,EAAM,QAAA,KAAa;AAC9B,IAAA,MAAM,MAAA,GAAS,IAAI,WAAA,CAAY,GAAA,CAAI,GAAG,CAAA;AAEtC,IAAA,MAAM,eAAA,GAAkB,CAAC,KAAA,KAAqC;AAI5D,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,MAAgB,GAAG,CAAA;AACpD,MAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,IAChC,CAAA;AACA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,eAAe,CAAA;AAElD,IAAA,MAAM,UAAU,IAAA,CAAK,OAAA;AACrB,IAAA,MAAM,aAAA,GAAgB,OAAA,GAClB,CAAC,KAAA,KAA8B;AAC7B,MAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA;AAC9B,MAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,IAChC,CAAA,GACA,MAAA;AACJ,IAAA,IAAI,aAAA,EAAe,MAAA,CAAO,gBAAA,CAAiB,OAAA,EAAS,aAAa,CAAA;AAEjE,IAAA,MAAM,SAAS,IAAA,CAAK,MAAA;AACpB,IAAA,MAAM,YAAA,GAAe,SACjB,MAAY;AACV,MAAA,MAAM,GAAA,GAAM,OAAO,GAAG,CAAA;AACtB,MAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,IAChC,CAAA,GACA,MAAA;AACJ,IAAA,IAAI,YAAA,EAAc,MAAA,CAAO,gBAAA,CAAiB,MAAA,EAAQ,YAAY,CAAA;AAE9D,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,mBAAA,CAAoB,WAAW,eAAe,CAAA;AACrD,MAAA,IAAI,aAAA,EAAe,MAAA,CAAO,mBAAA,CAAoB,OAAA,EAAS,aAAa,CAAA;AACpE,MAAA,IAAI,YAAA,EAAc,MAAA,CAAO,mBAAA,CAAoB,MAAA,EAAQ,YAAY,CAAA;AACjE,MAAA,MAAA,CAAO,KAAA,EAAM;AAAA,IACf,CAAA;AAAA,EACF,CAAA;AACF;;;ACjEO,SAAS,eAAA,CACd,SAAA,EACA,SAAA,EACA,KAAA,EACiC;AACjC,EAAA,OAAO,CAAC,GAAA,EAAK,IAAA,EAAM,QAAA,KAAa;AAC9B,IAAA,MAAM,SAAS,SAAA,EAAU;AACzB,IAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAA8B;AAC9C,MAAA,MAAM,GAAA,GAAM,KAAA,CAAM,KAAA,EAAO,GAAG,CAAA;AAC5B,MAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,IAChC,CAAA;AACA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,QAAQ,CAAA;AAC3C,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,CAAO,mBAAA,CAAoB,WAAW,QAAQ,CAAA;AAAA,IAChD,CAAA;AAAA,EACF,CAAA;AACF;;;ACtBO,SAAS,aACd,KAAA,EACiC;AACjC,EAAA,OAAO,CAAC,GAAA,EAAK,IAAA,EAAM,QAAA,KAAa;AAC9B,IAAA,MAAM,MAAA,GAAS,YAAY,MAAM;AAC/B,MAAA,QAAA,CAAS,KAAA,CAAM,GAAG,CAAC,CAAA;AAAA,IACrB,CAAA,EAAG,IAAI,UAAU,CAAA;AACjB,IAAA,OAAO,MAAM;AACX,MAAA,aAAA,CAAc,MAAM,CAAA;AAAA,IACtB,CAAA;AAAA,EACF,CAAA;AACF;;;ACSO,SAAS,QAAA,CAQd,aAAA,EACA,IAAA,EACA,KAAA,EAC6B;AAC7B,EAAA,OAAO,CAAC,GAAA,EAAK,GAAA,EAAK,QAAA,KAAa;AAC7B,IAAA,MAAM,OAAA,GAAU,cAAc,GAAG,CAAA;AACjC,IAAA,OAAO,OAAA,CAAQ,aAAA,CAAc,IAAA,EAAM,CAAC,KAAA,KAAU;AAC5C,MAAA,MAAM,GAAA,GAAM,KAAA,CAAM,KAAA,EAAO,GAAG,CAAA;AAC5B,MAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,IAChC,CAAC,CAAA;AAAA,EACH,CAAA;AACF;;;ACmBA,IAAM,cAAA,GAAiB,CAAC,GAAA,KAAkC,IAAI,UAAU,GAAG,CAAA;AAE3E,IAAM,eAAA,GAAkB,CAAC,EAAA,EAAgB,EAAA,KAA4B;AACnE,EAAA,MAAM,MAAA,GAAS,UAAA,CAAW,EAAA,EAAI,EAAE,CAAA;AAChC,EAAA,OAAO,MAAM,aAAa,MAAM,CAAA;AAClC,CAAA;AAEO,SAAS,0BACd,IAAA,EACiC;AACjC,EAAA,MAAM,aAAA,GAAgB,KAAK,aAAA,IAAiB,GAAA;AAC5C,EAAA,MAAM,YAAA,GAAe,KAAK,YAAA,IAAgB,GAAA;AAC1C,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,IAAW,cAAA;AAChC,EAAA,MAAM,QAAA,GAAW,KAAK,QAAA,IAAY,eAAA;AAElC,EAAA,MAAM,EAAE,SAAA,EAAW,MAAA,EAAQ,OAAA,EAAS,OAAA,EAAS,aAAY,GAAI,IAAA;AAE7D,EAAA,OAAO,CAAC,GAAA,EAAK,IAAA,EAAM,QAAA,KAAa;AAC9B,IAAA,IAAI,EAAA,GAA8B,IAAA;AAClC,IAAA,IAAI,OAAA,GAAU,CAAA;AACd,IAAA,IAAI,MAAA,GAAS,KAAA;AACb,IAAA,IAAI,cAAA,GAAiB,KAAA;AACrB,IAAA,IAAI,eAAA,GAAsC,IAAA;AAE1C,IAAA,MAAM,IAAA,GAAO,CAAC,GAAA,KAAwB;AACpC,MAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,IAChC,CAAA;AAEA,IAAA,MAAM,WAAA,GAAc,CAAC,IAAA,EAAc,MAAA,KAAyB;AAC1D,MAAA,EAAA,GAAK,IAAA;AACL,MAAA,IAAI,SAAS,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,MAAA,EAAQ,GAAG,CAAC,CAAA;AAG5C,MAAA,IAAI,cAAA,EAAgB;AACpB,MAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,aAAA,GAAgB,CAAA,IAAK,SAAS,YAAY,CAAA;AACjE,MAAA,OAAA,IAAW,CAAA;AACX,MAAA,eAAA,GAAkB,SAAS,MAAM;AAC/B,QAAA,eAAA,GAAkB,IAAA;AAClB,QAAA,IAAI,CAAC,gBAAgB,IAAA,EAAK;AAAA,MAC5B,GAAG,KAAK,CAAA;AAAA,IACV,CAAA;AAEA,IAAA,SAAS,IAAA,GAAa;AACpB,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,KAAK,CAAA;AAChC,MAAA,EAAA,GAAK,MAAA;AAEL,MAAA,MAAA,CAAO,SAAS,MAAY;AAG1B,QAAA,MAAM,WAAA,GAAc,MAAA;AACpB,QAAA,MAAM,cAAA,GAAiB,OAAA;AACvB,QAAA,OAAA,GAAU,CAAA;AACV,QAAA,MAAA,GAAS,IAAA;AACT,QAAA,IAAI,MAAA,EAAQ,IAAA,CAAK,MAAA,CAAO,GAAG,CAAC,CAAA;AAC5B,QAAA,IAAI,eAAe,WAAA,EAAa,IAAA,CAAK,WAAA,CAAY,cAAA,EAAgB,GAAG,CAAC,CAAA;AAAA,MACvE,CAAA;AAEA,MAAA,MAAA,CAAO,SAAA,GAAY,CAAC,KAAA,KAAgB;AAClC,QAAA,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,IAAA,EAAM,GAAG,CAAC,CAAA;AAAA,MACjC,CAAA;AAKA,MAAA,MAAA,CAAO,OAAA,GAAU,OAAA,GACb,CAAC,KAAA,KAAgB,IAAA,CAAK,QAAQ,KAAA,EAAO,GAAG,CAAC,CAAA,GACzC,MAAY;AAAA,MAAC,CAAA;AAEjB,MAAA,MAAA,CAAO,OAAA,GAAU,CAAC,KAAA,KAAgB;AAChC,QAAA,WAAA,CAAY,KAAA,CAAM,IAAA,EAAM,KAAA,CAAM,MAAM,CAAA;AAAA,MACtC,CAAA;AAAA,IACF;AAEA,IAAA,IAAA,EAAK;AAEL,IAAA,OAAO,MAAM;AAIX,MAAA,cAAA,GAAiB,IAAA;AACjB,MAAA,IAAI,oBAAoB,IAAA,EAAM;AAC5B,QAAA,eAAA,EAAgB;AAChB,QAAA,eAAA,GAAkB,IAAA;AAAA,MACpB;AACA,MAAA,MAAM,MAAA,GAAS,EAAA;AACf,MAAA,EAAA,GAAK,IAAA;AACL,MAAA,IAAI,WAAW,IAAA,EAAM;AACnB,QAAA,MAAA,CAAO,MAAA,GAAS,IAAA;AAChB,QAAA,MAAA,CAAO,SAAA,GAAY,IAAA;AACnB,QAAA,MAAA,CAAO,OAAA,GAAU,IAAA;AACjB,QAAA,MAAA,CAAO,OAAA,GAAU,IAAA;AAEjB,QAAA,MAAA,CAAO,MAAM,GAAI,CAAA;AAAA,MACnB;AAAA,IACF,CAAA;AAAA,EACF,CAAA;AACF;;;ACrHO,SAAS,cACd,IAAA,EACiC;AACjC,EAAA,OAAO,CAAC,GAAA,EAAK,IAAA,EAAM,QAAA,KAAa;AAC9B,IAAA,MAAM,EAAA,GAAK,IAAI,SAAA,CAAU,GAAA,CAAI,KAAK,CAAA;AAElC,IAAA,EAAA,CAAG,SAAA,GAAY,CAAC,KAAA,KAAqC;AAGnD,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,MAAM,GAAG,CAAA;AAC1C,MAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,IAChC,CAAA;AAEA,IAAA,MAAM,SAAS,IAAA,CAAK,MAAA;AACpB,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,EAAA,CAAG,SAAS,MAAY;AACtB,QAAA,MAAM,GAAA,GAAM,OAAO,GAAG,CAAA;AACtB,QAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,MAChC,CAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAU,IAAA,CAAK,OAAA;AACrB,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,EAAA,CAAG,OAAA,GAAU,CAAC,KAAA,KAA8B;AAC1C,QAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA;AAC9B,QAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,MAChC,CAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAU,IAAA,CAAK,OAAA;AACrB,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,EAAA,CAAG,OAAA,GAAU,CAAC,KAAA,KAAmC;AAC/C,QAAA,MAAM,MAAM,OAAA,CAAQ,KAAA,CAAM,IAAA,EAAM,KAAA,CAAM,QAAQ,GAAG,CAAA;AACjD,QAAA,IAAI,GAAA,KAAQ,IAAA,EAAM,QAAA,CAAS,GAAG,CAAA;AAAA,MAChC,CAAA;AAAA,IACF;AAEA,IAAA,OAAO,MAAM;AAIX,MAAA,EAAA,CAAG,SAAA,GAAY,IAAA;AACf,MAAA,EAAA,CAAG,MAAA,GAAS,IAAA;AACZ,MAAA,EAAA,CAAG,OAAA,GAAU,IAAA;AACb,MAAA,EAAA,CAAG,OAAA,GAAU,IAAA;AAEb,MAAA,EAAA,CAAG,MAAM,GAAI,CAAA;AAAA,IACf,CAAA;AAAA,EACF,CAAA;AACF","file":"chunk-NSDFA6M6.js","sourcesContent":["// ---------------------------------------------------------------------------\n// fromBroadcastChannel — universal `BroadcastChannel`-shaped Sub factory.\n//\n// `BroadcastChannel` is a same-origin pub/sub primitive available in\n// every modern browser context (window, worker, service worker) AND in\n// Cloudflare Workers (with origin scoped to the worker isolate). The\n// constructor opens the channel; the `message` event fires whenever\n// another participant on the same channel name posts a message; the\n// `close()` method releases the OS resource.\n//\n// The factory carries `channelName` on the Sub itself (not in the\n// closure) so the same factory instance handles every BroadcastChannel\n// Sub in a machine — one Sub per channel, named by id, sharing the\n// factory's lifecycle code. Renaming a channel requires a new Sub.id\n// (the reconcile pass otherwise leaves the existing channel open).\n//\n// `msgFn` returns `M | null` — same drop-on-null pattern as\n// `fromEventTarget`. The MessageEvent's `data` is `unknown` from the\n// platform's perspective; the caller decides whether the payload is a\n// recognizable shape, and returns `null` to drop unknown messages\n// without polluting the reducer with parse-discard cells.\n//\n// Cleanup calls `channel.close()`, which removes the listener AND\n// releases the channel reference — `removeEventListener` alone would\n// leak the BroadcastChannel handle. The pair is intentional.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalBroadcastChannelCtor,\n MinimalMessageEvent,\n} from \"./platform\";\nimport type { SubscribeHandler } from \"./types\";\n\ntype BroadcastSubData = { channelName: string };\n\ndeclare const BroadcastChannel: MinimalBroadcastChannelCtor;\n\nexport function fromBroadcastChannel<S extends Sub & BroadcastSubData, M>(\n msgFn: (event: MinimalMessageEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const channel = new BroadcastChannel(sub.channelName);\n const listener = (event: MinimalMessageEvent): void => {\n const msg = msgFn(event, sub);\n if (msg !== null) dispatch(msg);\n };\n channel.addEventListener(\"message\", listener);\n return () => {\n channel.removeEventListener(\"message\", listener);\n channel.close();\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromEventSource — universal `EventSource` (server-sent events) Sub\n// factory.\n//\n// SSE is the asymmetric streaming protocol used by the agent log /\n// audit log pipelines: HTTP-shaped open, server pushes events forever,\n// client receives until close. `EventSource` is the standard browser\n// API; the constructor opens the connection, `message` fires per\n// server-pushed event, `error` fires on disconnect or non-2xx, `open`\n// fires once on initial handshake, `close()` tears the connection\n// down.\n//\n// Three callbacks because three observable events ARE three concerns\n// the caller routinely cares about:\n//\n// - `onMessage(data, sub)` — required. The SSE payload's `data` field\n// is `string` from the platform; the caller decides parse + Msg\n// shape. Returns `M | null`; null drops the dispatch.\n//\n// - `onError(event, sub)` — optional. SSE's `error` event fires both\n// on transient network drops (the browser auto-reconnects) AND on\n// permanent failures. Most consumers want to surface ONE of these\n// as a Msg (e.g. \"stream-disconnected\") without folding both into\n// `onMessage`'s discriminant. Omitting the callback drops error\n// events silently.\n//\n// - `onOpen(sub)` — optional. The connection's initial handshake\n// succeeded. Useful for \"show 'connected' indicator\" Msgs. Omitting\n// drops open events silently. NOT fired on auto-reconnect by every\n// browser implementation — treat the event as best-effort.\n//\n// `url` lives on the Sub itself (`EventSourceSubData`). Same id +\n// different url = the substrate's reconcile pass leaves the existing\n// connection alone (the URL change is silent). Changing the URL\n// requires a new Sub.id.\n//\n// Cleanup is `close()` + `removeEventListener` for all three —\n// belt-and-suspenders against EventSource implementations that\n// dispatch events into already-closed sources.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalEvent,\n MinimalEventSourceCtor,\n MinimalMessageEvent,\n} from \"./platform\";\nimport type { SubscribeHandler } from \"./types\";\n\ntype EventSourceSubData = { url: string };\n\ndeclare const EventSource: MinimalEventSourceCtor;\n\nexport interface EventSourceFactoryOpts<S, M> {\n onMessage: (data: string, sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n}\n\nexport function fromEventSource<S extends Sub & EventSourceSubData, M>(\n opts: EventSourceFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const source = new EventSource(sub.url);\n\n const messageListener = (event: MinimalMessageEvent): void => {\n // The SSE `data` payload is a string per the platform contract.\n // Cast at the boundary: callers express `(data: string) => M | null`\n // and never see `unknown` flowing in from the platform global.\n const msg = opts.onMessage(event.data as string, sub);\n if (msg !== null) dispatch(msg);\n };\n source.addEventListener(\"message\", messageListener);\n\n const onError = opts.onError;\n const errorListener = onError\n ? (event: MinimalEvent): void => {\n const msg = onError(event, sub);\n if (msg !== null) dispatch(msg);\n }\n : undefined;\n if (errorListener) source.addEventListener(\"error\", errorListener);\n\n const onOpen = opts.onOpen;\n const openListener = onOpen\n ? (): void => {\n const msg = onOpen(sub);\n if (msg !== null) dispatch(msg);\n }\n : undefined;\n if (openListener) source.addEventListener(\"open\", openListener);\n\n return () => {\n source.removeEventListener(\"message\", messageListener);\n if (errorListener) source.removeEventListener(\"error\", errorListener);\n if (openListener) source.removeEventListener(\"open\", openListener);\n source.close();\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromEventTarget — universal addEventListener/removeEventListener Sub\n// factory over any `EventTarget` (DOM nodes, `window`, `document`,\n// `BroadcastChannel`, `MessagePort`, `Worker`, `ServiceWorker`, etc.).\n//\n// Two non-obvious shape choices:\n//\n// - `getTarget: () => EventTarget` is a thunk, not a direct value. The\n// factory MUST work in environments where the target is not available\n// at module-load time — service workers run before any `document`\n// exists; some targets are constructed lazily from `ctx`. Deferring\n// the lookup to subscribe time also means a state-driven swap of the\n// target requires only a new Sub.id; the factory pulls the current\n// target on subscribe.\n//\n// - `msgFn` may return `null` — that emission is *dropped*. The event\n// fired, the factory called `msgFn`, but the caller decided \"this\n// event is not interesting given current state.\" No dispatch is made;\n// the listener stays armed for the next event. This is the explicit\n// filter point at the Sub boundary — the alternative (filtering inside\n// the reducer via a noop cell) costs a transition + reconcile pass per\n// filtered event.\n//\n// Cleanup uses the SAME `getTarget()` instance captured at subscribe time\n// so a target swap mid-Sub doesn't leak the listener on the old target.\n// We close over the resolved target rather than calling `getTarget()`\n// again at cleanup — the alternative (re-resolving) is the cause of\n// silent listener leaks in long-lived runtimes.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { MinimalEvent, MinimalEventTarget } from \"./platform\";\nimport type { SubscribeHandler } from \"./types\";\n\nexport function fromEventTarget<S extends Sub, M>(\n getTarget: () => MinimalEventTarget,\n eventName: string,\n msgFn: (event: MinimalEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const target = getTarget();\n const listener = (event: MinimalEvent): void => {\n const msg = msgFn(event, sub);\n if (msg !== null) dispatch(msg);\n };\n target.addEventListener(eventName, listener);\n return () => {\n target.removeEventListener(eventName, listener);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromInterval — universal `setInterval`-shaped Sub factory.\n//\n// The recurring shape at every `subscribe[type]` cell that wraps a periodic\n// timer is the same five lines: `setInterval` the dispatch, return a\n// `clearInterval` cleanup. The factory absorbs both halves of the lifecycle\n// (subscribe + cleanup); the caller keeps the intent — which Sub variant,\n// which Msg to dispatch.\n//\n// Timing data (`intervalMs`) lives on the Sub itself, not in the closure.\n// The substrate already reconciles Subs by id on every transition; carrying\n// the period on the Sub means a `subscriptions(state)` function that\n// returns `{ id, type, intervalMs: 30_000 }` in one state and the same id\n// with `intervalMs: 60_000` later... will NOT re-wire the interval (the id\n// is unchanged, the reconcile pass leaves it running). That's a tradeoff\n// the substrate's reconcile contract pins, not this factory — if you want\n// the interval to change, change the Sub's id too.\n//\n// Strengthens invariant 9 (named, small surface) and invariant 2 (the Sub\n// carries the data; the lifecycle is in the factory; the reducer never\n// sees the timer).\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { SubscribeHandler } from \"./types\";\n\ntype IntervalSubData = { intervalMs: number };\n\nexport function fromInterval<S extends Sub & IntervalSubData, M>(\n msgFn: (sub: S) => M,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const handle = setInterval(() => {\n dispatch(msgFn(sub));\n }, sub.intervalMs);\n return () => {\n clearInterval(handle);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromPort — universal `Runtime.subscribePort`-shaped Sub factory.\n//\n// The canonical cross-runtime coordination pattern in @demlik/tea: one\n// runtime emits to a typed Port; another runtime's machine declares an\n// `audit-idle`-shaped Sub whose subscribe handler attaches to the\n// publisher's Port. The recurring 4-line shape at every such cell:\n//\n// \"audit-idle\": (_sub, ctx, dispatch) =>\n// ctx.auditRuntime.subscribePort(auditIdlePort, (value) => {\n// dispatch(buildMsg(value));\n// });\n//\n// The factory absorbs the cleanup-returning subscribePort call AND the\n// dispatch-on-emit wiring. The caller keeps the three pieces of intent:\n//\n// 1. `selectRuntime(ctx)` — which sibling Runtime publishes the Port.\n// This is a selector (not a direct ctx.auditRuntime reference) so\n// the factory does NOT pin the ctx shape — any machine whose ctx\n// exposes some Runtime<...> reference is compatible by selecting it.\n// A direct `Runtime` parameter would have made the factory shape\n// depend on having that Runtime in scope at machine-definition\n// time, which is a worse match for the \"ctx is the boundary\"\n// contract this substrate already commits to.\n//\n// 2. `port: Port<T>` — the typed channel itself. Identity is by\n// reference (the substrate enforces unique names at definePort\n// time); pass the same `Port<T>` value the publisher emits to.\n//\n// 3. `msgFn(value, sub) => M | null` — the value-to-Msg mapping, with\n// drop-on-null semantics matching every other factory in this\n// subpath. The published value's type `T` flows through to `msgFn`'s\n// first param so the caller never re-asserts the payload shape.\n//\n// Strengthens invariant 9 (the cross-runtime Port topology is named at\n// one factory site instead of redrawn at every subscribe cell).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime, Sub } from \"../index\";\nimport type { SubscribeHandler } from \"./types\";\n\n// `RS` / `RM` carry the sibling Runtime's concrete State and Msg through the\n// selector: a caller that selects a `Runtime<AuditState, AuditMsg>` keeps that\n// type at the seam instead of collapsing it to `Runtime<unknown, {type:string}>`.\n// They default to the prior broad pair so existing call sites that select a\n// less-specific Runtime still compile — the factory still only ever calls\n// `subscribePort`, so it does not read RS/RM, but the boundary no longer\n// hardcodes the widest type when the caller knows a narrower one.\nexport function fromPort<\n S extends Sub,\n M,\n Ctx,\n T,\n RS = unknown,\n RM extends { type: string } = { type: string },\n>(\n selectRuntime: (ctx: Ctx) => Runtime<RS, RM>,\n port: Port<T>,\n msgFn: (value: T, sub: S) => M | null,\n): SubscribeHandler<S, M, Ctx> {\n return (sub, ctx, dispatch) => {\n const runtime = selectRuntime(ctx);\n return runtime.subscribePort(port, (value) => {\n const msg = msgFn(value, sub);\n if (msg !== null) dispatch(msg);\n });\n };\n}\n","// ---------------------------------------------------------------------------\n// fromReconnectingWebSocket — a `WebSocket` Sub factory that survives drops.\n//\n// `fromWebSocket` is the SSE-sibling ONE-SHOT factory: its cleanup `close()`s\n// the socket and it never re-opens on an unexpected drop (issue #188). That is\n// the right shape for a connection the caller manages, but any tea client over\n// an unreliable transport — games, presence, collab — needs the connection to\n// recover transparently, without a page reload. Rather than re-roll a backoff\n// loop in every consumer (vortex's `ReconnectingSocket` is the proven shape,\n// services/vortex/src/reconnecting-socket.ts), this is the reconnecting variant\n// folded into the substrate's Sub surface.\n//\n// The reconnect contract, mirroring vortex's wrapper:\n//\n// - On an UNEXPECTED close (`onclose` the substrate didn't initiate) it\n// re-opens after a CAPPED exponential backoff: `min(base * 2^attempt, max)`.\n// A successful handshake resets `attempt` to 0, so the next drop retries\n// fast again.\n//\n// - A CALLER-INITIATED close (the Sub's cleanup, e.g. the machine\n// transitioned the Sub away) suppresses reconnect: handlers are detached\n// BEFORE `close(1000)` so the teardown can't fire `onClose` or schedule a\n// reconnect. After cleanup the factory never re-opens.\n//\n// - `connect` and `schedule` are INJECTABLE (defaulting to the global\n// `WebSocket` and `setTimeout`/`clearTimeout`), the test seam that lets the\n// reconnect/backoff behavior be unit-tested with a fake socket + a manual\n// clock — no DOM, no real network, no timers — exactly as vortex's wrapper\n// is tested.\n//\n// Callback contract (same `M | null` \"null drops the dispatch\" shape as\n// `fromWebSocket`, plus one reconnect-specific hook):\n//\n// - `onMessage(data, sub)` — required. Raw `unknown` frame; the caller parses.\n// - `onOpen(sub)` — optional. Fires on EVERY successful handshake — the first\n// open AND every reconnect — so a \"connected\" indicator stays accurate.\n// - `onError(event, sub)` — optional. The `error` event; `close` still drives\n// the reconnect (one path), so this is purely a notification.\n// - `onClose(code, reason, sub)` — optional. Fires on every close the\n// substrate observes (a drop that will be retried, too) — NOT on the\n// caller's own teardown, whose handlers are detached first.\n// - `onReconnect(attempt, sub)` — optional. Fires when a RE-open (any open\n// after the first) succeeds, carrying the 1-based reconnect count. This is\n// the resync hook: the place to dispatch a \"re-fetch the authoritative\n// snapshot, discard predicted state\" Msg after the transport recovered.\n//\n// Like `fromWebSocket`, `wsUrl` lives on the Sub (`WebSocketSubData`); every\n// reconnect reads `sub.wsUrl` afresh, so a reconcile that re-subscribes with a\n// new url reconnects to the new endpoint.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { WebSocketSubData } from \"./from-web-socket\";\nimport type {\n MinimalEvent,\n MinimalWebSocket,\n MinimalWebSocketCtor,\n} from \"./platform\";\nimport type { SubscribeHandler } from \"./types\";\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\n/** Cancels a scheduled reconnect timer (the inverse of `schedule`). */\nexport type CancelTimer = () => void;\n\nexport interface ReconnectingWebSocketFactoryOpts<S, M> {\n onMessage: (data: unknown, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onClose?: (code: number, reason: string, sub: S) => M | null;\n /**\n * Fires when a reconnection (any open after the first) succeeds, carrying the\n * 1-based reconnect count — the resync hook. Omitting drops reconnect events\n * silently.\n */\n onReconnect?: (attempt: number, sub: S) => M | null;\n /** First backoff delay in ms (doubles each failed attempt). Default 250. */\n backoffBaseMs?: number;\n /** Backoff ceiling in ms. Default 5000. */\n backoffMaxMs?: number;\n /** Inject the socket constructor (defaults to the global `WebSocket`); the test seam. */\n connect?: (url: string) => MinimalWebSocket;\n /** Inject the timer (defaults to `setTimeout`/`clearTimeout`); the test seam. */\n schedule?: (fn: () => void, ms: number) => CancelTimer;\n}\n\nconst defaultConnect = (url: string): MinimalWebSocket => new WebSocket(url);\n\nconst defaultSchedule = (fn: () => void, ms: number): CancelTimer => {\n const handle = setTimeout(fn, ms);\n return () => clearTimeout(handle);\n};\n\nexport function fromReconnectingWebSocket<S extends Sub & WebSocketSubData, M>(\n opts: ReconnectingWebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n const backoffBaseMs = opts.backoffBaseMs ?? 250;\n const backoffMaxMs = opts.backoffMaxMs ?? 5000;\n const connect = opts.connect ?? defaultConnect;\n const schedule = opts.schedule ?? defaultSchedule;\n\n const { onMessage, onOpen, onError, onClose, onReconnect } = opts;\n\n return (sub, _ctx, dispatch) => {\n let ws: MinimalWebSocket | null = null;\n let attempt = 0;\n let opened = false;\n let closedByCaller = false;\n let cancelReconnect: CancelTimer | null = null;\n\n const emit = (msg: M | null): void => {\n if (msg !== null) dispatch(msg);\n };\n\n const handleClose = (code: number, reason: string): void => {\n ws = null;\n if (onClose) emit(onClose(code, reason, sub));\n // A teardown the substrate initiated detaches handlers first, so this\n // path only runs for an UNEXPECTED close — reconnect with capped backoff.\n if (closedByCaller) return;\n const delay = Math.min(backoffBaseMs * 2 ** attempt, backoffMaxMs);\n attempt += 1;\n cancelReconnect = schedule(() => {\n cancelReconnect = null;\n if (!closedByCaller) open();\n }, delay);\n };\n\n function open(): void {\n const socket = connect(sub.wsUrl);\n ws = socket;\n\n socket.onopen = (): void => {\n // `opened` BEFORE this open marks it a reconnect; `attempt` is the\n // pending count for the drop that triggered it (1-based once we reset).\n const reconnected = opened;\n const reconnectCount = attempt;\n attempt = 0;\n opened = true;\n if (onOpen) emit(onOpen(sub));\n if (reconnected && onReconnect) emit(onReconnect(reconnectCount, sub));\n };\n\n socket.onmessage = (event): void => {\n emit(onMessage(event.data, sub));\n };\n\n // `error` precedes `close`; let `close` drive the reconnect so there is\n // one path. The handler is still wired (as a no-op when the caller didn't\n // supply `onError`) so a platform error event has a sink.\n socket.onerror = onError\n ? (event): void => emit(onError(event, sub))\n : (): void => {};\n\n socket.onclose = (event): void => {\n handleClose(event.code, event.reason);\n };\n }\n\n open();\n\n return () => {\n // Deliberate teardown: cancel any pending reconnect, detach handlers so\n // the cleanup `close()` can't fire `onClose` or schedule a reconnect,\n // then close the live socket. After this the factory never re-opens.\n closedByCaller = true;\n if (cancelReconnect !== null) {\n cancelReconnect();\n cancelReconnect = null;\n }\n const socket = ws;\n ws = null;\n if (socket !== null) {\n socket.onopen = null;\n socket.onmessage = null;\n socket.onerror = null;\n socket.onclose = null;\n // 1000 = \"normal closure\" per RFC 6455 §7.4.1.\n socket.close(1000);\n }\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromWebSocket — universal `WebSocket` Sub factory. The symmetric sibling\n// of `fromEventSource`.\n//\n// WebSocket is the bidirectional streaming protocol used wherever the\n// server pushes events AND the client pushes commands on the same\n// connection. The constructor opens the connection, `onopen` fires once\n// on handshake, `onmessage` fires per server-pushed frame, `onerror`\n// fires on transient or permanent failure, `onclose` fires once on\n// teardown carrying `code` + `reason`. `close(code)` initiates a clean\n// shutdown from the client side.\n//\n// Four callbacks because four observable events ARE four concerns the\n// caller routinely cares about. The Sub's `wsUrl` lives on the Sub itself\n// (`WebSocketSubData`) — same id + different `wsUrl` is silent under the\n// reconcile pass, identical to `fromEventSource`'s `url` contract.\n//\n// - `onMessage(data, sub)` — required. The platform delivers `data` as\n// `string | Blob | ArrayBuffer` depending on `binaryType` and the\n// server's frame type. The factory does NOT bake in a parse contract:\n// the callback receives the raw `unknown` and decides parse + Msg\n// shape. Returns `M | null`; null drops the dispatch.\n//\n// - `onOpen(sub)` — optional. Connection handshake succeeded. Useful\n// for \"show 'connected' indicator\" Msgs. Omitting drops open events\n// silently.\n//\n// - `onError(event, sub)` — optional. WebSocket's `error` event fires\n// on transient drops AND permanent failures; like SSE, most consumers\n// want one Msg (\"connection-errored\") without folding both into\n// `onMessage`'s discriminant. Omitting drops error events silently.\n//\n// - `onClose(code, reason, sub)` — optional. The connection is gone.\n// `code` is the WebSocket close code (1000 = clean, 1006 = abnormal,\n// etc.). `reason` is the server-supplied string (often empty).\n// Omitting drops close events silently.\n//\n// Cleanup is `close(1000)` — the IANA-registered clean-shutdown code.\n// The factory intentionally does NOT bake in registry semantics (e.g.\n// the extension's `ctx.wsRegistry` so `interpret.send_ws` can reach the\n// live socket): that's consumer glue. A consumer that needs the live\n// socket for outbound sends wires it from inside the subscribe handler\n// or keeps a hand-written Sub alongside this factory.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalCloseEvent,\n MinimalEvent,\n MinimalMessageEvent,\n MinimalWebSocketCtor,\n} from \"./platform\";\nimport type { SubscribeHandler } from \"./types\";\n\nexport type WebSocketSubData = { wsUrl: string };\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\nexport interface WebSocketFactoryOpts<S, M> {\n onMessage: (data: unknown, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onClose?: (code: number, reason: string, sub: S) => M | null;\n}\n\nexport function fromWebSocket<S extends Sub & WebSocketSubData, M>(\n opts: WebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const ws = new WebSocket(sub.wsUrl);\n\n ws.onmessage = (event: MinimalMessageEvent): void => {\n // The platform delivers `data` as `unknown` — the factory hands it\n // through untouched and the caller chooses the parse strategy.\n const msg = opts.onMessage(event.data, sub);\n if (msg !== null) dispatch(msg);\n };\n\n const onOpen = opts.onOpen;\n if (onOpen) {\n ws.onopen = (): void => {\n const msg = onOpen(sub);\n if (msg !== null) dispatch(msg);\n };\n }\n\n const onError = opts.onError;\n if (onError) {\n ws.onerror = (event: MinimalEvent): void => {\n const msg = onError(event, sub);\n if (msg !== null) dispatch(msg);\n };\n }\n\n const onClose = opts.onClose;\n if (onClose) {\n ws.onclose = (event: MinimalCloseEvent): void => {\n const msg = onClose(event.code, event.reason, sub);\n if (msg !== null) dispatch(msg);\n };\n }\n\n return () => {\n // Detach the lifecycle handlers BEFORE close() so the consumer's\n // `onClose` Msg isn't fired from a teardown the substrate initiated.\n // Mirrors fromEventSource's removeEventListener pass.\n ws.onmessage = null;\n ws.onopen = null;\n ws.onerror = null;\n ws.onclose = null;\n // 1000 = \"normal closure\" per RFC 6455 §7.4.1.\n ws.close(1000);\n };\n };\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/trace-replay/index.ts"],"names":[],"mappings":"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","file":"chunk-ON24RKSE.js","sourcesContent":["// ---------------------------------------------------------------------------\n// trace-replay — replay a recorded Trace against a machine and diff the state.\n//\n// This is the back half of the record/replay feature. `../recorder` captures\n// a run (`loaded` + `msgs` + `finalState`); this module feeds that input\n// through the core `replay(machine, { msgs, ctx, loaded })` fold and asks:\n//\n// does the current reducer still produce the SAME final state for this\n// recorded input?\n//\n// Equal ⇒ the reducer is behavior-compatible with the recording. Diverged ⇒\n// the reducer changed for that input, and the divergence path\n// (`state.queue[2].status`) points at exactly the cell that drifted. This is\n// the regression / production-repro tool: record a prod incident, replay it\n// against the fixed reducer locally, confirm the fix changes the state at the\n// path you expected — or catch an unintended drift before it ships.\n//\n// Standalone by design. It imports ONLY the `Trace` type from `../recorder`\n// and the `replay` engine from `../index`. The deep-equal + first-divergence\n// path finder is self-contained (no external diff lib) — a richer pretty-diff\n// lands in `../devtools` later; trace-replay stays dependency-free so it can\n// run anywhere a machine runs (CI, a Worker, a Node script).\n// ---------------------------------------------------------------------------\n\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\nimport type { Trace } from \"../recorder\";\n\n/**\n * The outcome of {@link replayTrace}.\n *\n * @typeParam S - the machine's State type.\n */\nexport interface ReplayResult<S> {\n /** `true` iff the recomputed final state deep-equals the trace's `finalState`. */\n matches: boolean;\n /** The trace's recorded `finalState` — what the run originally produced. */\n expected: S;\n /** The state the current reducer produced for the same input. */\n actual: S;\n /**\n * Present iff `matches` is `false`. The FIRST point of divergence found by\n * a deterministic pre-order walk:\n * - `path` — a dotted/indexed access path from the state root, e.g.\n * `state.queue[2].status` or `state` for a root-level\n * scalar mismatch.\n * - `expected` — the recorded value at that path.\n * - `actual` — the recomputed value at that path.\n */\n divergence?: { path: string; expected: unknown; actual: unknown };\n}\n\n/**\n * Replay a recorded {@link Trace} against `machine` and diff the recomputed\n * final state against the recorded one.\n *\n * Runs the core pure fold `replay(machine, { msgs: trace.msgs, ctx,\n * loaded: trace.loaded })` — init + update only, never `interpret`, never a\n * `Store`, never a live subscription (see `replay` in ../index). The `ctx` is\n * supplied fresh by the caller: the trace is the INPUT (loaded + msgs); ctx is\n * the ENVIRONMENT the reducer runs against now.\n *\n * @param machine - the machine to replay against. Mutating its reducer (the\n * whole point of a regression check) is what makes a recorded\n * trace diverge.\n * @param trace - a Trace from `../recorder` (live `dump()` or a re-hydrated\n * `parseJSONL(...)`).\n * @param ctx - the runtime ctx, supplied fresh at replay time.\n */\nexport function replayTrace<\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 trace: Trace<S, M>,\n ctx: Ctx,\n): ReplayResult<S> {\n const result = replay(machine, {\n msgs: trace.msgs,\n ctx,\n loaded: trace.loaded,\n });\n const actual = result.state;\n const expected = trace.finalState;\n\n const divergence = firstDivergence(expected, actual, \"state\");\n if (divergence === null) {\n return { matches: true, expected, actual };\n }\n return { matches: false, expected, actual, divergence };\n}\n\n/**\n * Order-insensitive structural deep-equality — the single comparator this\n * package owns, exposed so consumers (e.g. `@demlik/tea/parity`'s `diff`)\n * reuse it rather than adding a second deep-compare. `true` iff `a` and `b`\n * are structurally equal under {@link firstDivergence}'s walk (object keys\n * compared order-insensitively, arrays index-by-index).\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n return firstDivergence(a, b, \"state\") === null;\n}\n\n// === self-contained deep-equal + first-divergence path finder ===\n//\n// One walk does both jobs: it returns the FIRST point where `expected` and\n// `actual` differ (as a path + the two values), or `null` when they are\n// deeply equal. \"First\" is defined by a deterministic pre-order traversal:\n// the node itself, then its children in a stable key order (array index\n// order; object keys sorted so the result does not depend on insertion\n// order — two states with the same data but different key order are equal,\n// and the reported path for a real mismatch is reproducible run-to-run).\n//\n// Scope: TEA state is plain JSON-shaped data (the substrate's __DEV__ guards\n// reject closures in Cmds, and states are conventionally serializable). So\n// the walk handles: primitives, null/undefined, arrays, and plain objects.\n// It treats other object kinds (Date, Map, Set, class instances) by reference\n// identity + a structural fallback via Object.entries — sufficient for the\n// JSON-shaped states this tool targets; a richer differ lives in ../devtools.\n\n/** A divergence record, or `null` when the two values are deeply equal. */\ntype Divergence = { path: string; expected: unknown; actual: unknown } | null;\n\nfunction firstDivergence(\n expected: unknown,\n actual: unknown,\n path: string,\n): Divergence {\n // Identity / primitive fast path. Handles same-reference, and equal\n // primitives. `Object.is` so NaN === NaN and +0 ≠ -0 (matches structural\n // equality intent better than ===).\n if (Object.is(expected, actual)) return null;\n\n const te = typeOf(expected);\n const ta = typeOf(actual);\n // Different kinds at this node (e.g. object vs array, number vs string,\n // present vs null) — this node IS the divergence.\n if (te !== ta) return { path, expected, actual };\n\n if (te === \"array\") {\n const ae = expected as unknown[];\n const aa = actual as unknown[];\n if (ae.length !== aa.length) {\n // Length mismatch reported at the array node itself — the shape differs,\n // which is more informative than an arbitrary element index.\n return { path, expected, actual };\n }\n for (let i = 0; i < ae.length; i++) {\n const d = firstDivergence(ae[i], aa[i], `${path}[${i}]`);\n if (d !== null) return d;\n }\n return null;\n }\n\n if (te === \"object\") {\n const oe = expected as Record<string, unknown>;\n const oa = actual as Record<string, unknown>;\n // Union of keys, sorted for deterministic traversal. A key present in one\n // but absent in the other diverges at that child path (its value is\n // `undefined` on the missing side).\n const keys = [...new Set([...Object.keys(oe), ...Object.keys(oa)])].sort();\n for (const key of keys) {\n const d = firstDivergence(oe[key], oa[key], `${path}.${key}`);\n if (d !== null) return d;\n }\n return null;\n }\n\n // Primitives (string/number/boolean/bigint/symbol/function) that failed the\n // Object.is fast path, or null/undefined that differ — this node diverges.\n return { path, expected, actual };\n}\n\n/** Coarse type tag used by the divergence walk to decide how to recurse. */\nfunction typeOf(\n v: unknown,\n): \"null\" | \"undefined\" | \"array\" | \"object\" | \"primitive\" {\n if (v === null) return \"null\";\n if (v === undefined) return \"undefined\";\n if (Array.isArray(v)) return \"array\";\n if (typeof v === \"object\") return \"object\";\n return \"primitive\";\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/llm-call/index.ts"],"names":["msg"],"mappings":"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","file":"chunk-PS23H6KQ.js","sourcesContent":["/**\n * @demlik/tea/llm-call — `resilient-call` + structured-output parse + a typed\n * failure variant, around a purpose-discriminated LLM invocation.\n *\n * This is the ~60-line `call_llm` handler from the audit-agent seed\n * (`interpret.ts` / `effects.ts`) collapsed into the uniform L2 knob: a config\n * object, the resilient-call slice it inherits, and one interpret handler that\n * assembles messages, binds the structured-output schema for the call's\n * `purpose`, invokes the model (retry composed from `../resilient-call`, NOT\n * reinvented), parses the output, and RETURNS the enriched resilient settle Msg\n * (`resilient_ok` / `resilient_err`) so it re-enters the host reducer and drives\n * the inherited succeed/fail → backoff → onTimer loop.\n *\n * ## What it adds over `../resilient-call`\n *\n * - **Two DI ports** (the seed's two seams):\n * 1. `model: (modelId) => LLM` — the model factory. Tests pass a fake so\n * the handler runs end-to-end without touching a real provider; the\n * production worker wires `createChatModel(env, …)`.\n * 2. `loadMessages: Loader` — the SDK / message loader. The seed lazy-\n * imports `@langchain/core/messages` (a top-level `import type` blows\n * up the workers test runner); the loader keeps that import lazy and\n * stubbable. It produces the `BaseMessage[]` for a given call.\n * - **Structured-output parse** — each `purpose` maps to a `Schema` in\n * `config.schemas`. The handler binds `model.withStructuredOutput(schema)`\n * so the invoke resolves to a typed object; a `Schema.parse` that throws\n * becomes a `resilient_err` carrying an `LlmErr` (a parse failure is a\n * failure, not a stall — \"errors are data\"). `withStructuredOutput` is the\n * seed's brain-only path; the fake model in tests returns the typed object\n * directly.\n * - **A typed failure variant** — every failure (model throw, timeout-via-\n * retry-exhaustion, schema parse) is surfaced as the `LlmErr` carried on the\n * enriched `resilient_err` settle Msg, tagged with the `purpose` so the\n * consumer's reducer routes per-stage. The seed dispatched\n * `llm_failed{purpose}`; the consumer rebuilds that shape in its reducer arm\n * from `m.error: LlmErr`.\n *\n * ## Inheriting resilient-call by composition (not reinvention)\n *\n * The retry / backoff lives in `../resilient-call`: `createLlmCall` builds a\n * `createResilientCall` knob over the model-invoke port and DELEGATES `init`,\n * `attempt`, `succeed`, `fail`, `onTimer`, `subs` to it verbatim — the slice is\n * literally resilient-call's slice (the spec's \"retry lives here\"). There is no\n * second backoff implementation here. The handler runs the invoke through the\n * resilient-call handler so a transient model failure backs off exactly as a\n * resilient HTTP call would; the `purpose`-branch + structured parse wrap the\n * port the resilient handler drives.\n *\n * ## The two non-negotiables (canon, inherited)\n *\n * - **Durable** — the slice is resilient-call's plain-data slice (a Model\n * field). Input carried on the `resilient_run` Cmd is the plain\n * `LlmCall` request (purpose + modelId + payload) — no closures.\n * - **Replayable** — every transition is a resilient-call verb; the handler\n * is the only impurity, and the one clock read is `Date.now()` at the\n * effect boundary (inside the handler), exactly as the seed stamped its\n * `llm_responded` / `llm_failed` Msgs.\n *\n * ## Typical wiring\n *\n * const llm = createLlmCall<MyPurpose, MyOutputs>({\n * model: (id) => createChatModel(env, id),\n * schemas: { plan: planSchema, report: reportSchema },\n * retry: defaultRetryPolicy,\n * loadMessages: defaultMessagesLoader,\n * });\n *\n * // in the machine:\n * init: () => [{ resilience: llm.init() }, []],\n * update: {\n * call_llm: (s, m) => lift(s, llm.attempt(s.resilience, m.input, m.at)),\n * // `resilient_ok` / `resilient_err` RE-ENTER from `handlers` (the settle\n * // Msg the interpret handler returns). Run the inherited verb FIRST so the\n * // succeed/fail → backoff → onTimer loop advances, THEN fold the enriched\n * // payload (`m.result: LlmOk` / `m.error: LlmErr`) into the host's own\n * // state — the enrichment lives in the reducer arm, not the handler.\n * resilient_ok: (s, m) => {\n * const [slice, cmds] = llm.succeed(s.resilience, m.key, m);\n * return [{ ...s, resilience: slice, output: m.result.output }, cmds];\n * },\n * resilient_err: (s, m) => {\n * const [slice, cmds] = llm.fail(s.resilience, m.key, m);\n * return [{ ...s, resilience: slice, failure: m.error }, cmds];\n * },\n * deadline_exceeded: (s, m) => lift(s, llm.onTimer(s.resilience, m)),\n * },\n * subscriptions: (s) => llm.subs(s.resilience),\n * subscribe: { deadline: subscribeDeadline },\n * interpret: llm.handlers(),\n */\n\nimport type { Cmd } from \"../index\";\nimport { MsgType } from \"../protocol\";\nimport {\n createResilientCall,\n type DeadlineExceeded,\n type DeadlineSub,\n deadlineSub,\n type FailMsg,\n liftResilience,\n type ResilientConfig,\n type ResilientState,\n type RunCmd,\n type SucceedMsg,\n subscribeDeadline,\n} from \"../resilient-call\";\nimport type { RetryPolicy } from \"../retry-backoff\";\n\n// ===========================================================================\n// The DI port surfaces — model factory + structured output + message loader.\n// ===========================================================================\n\n/**\n * The minimal structured-output schema contract: `parse(unknown) => T`, the\n * zod-style call the handler uses to validate the model's output before it\n * settles `resilient_ok`. A throwing `parse` (the zod contract on invalid\n * input) propagates out of `invokeOne` and is caught by the resilient handler,\n * which settles `resilient_err` (enriched to `LlmErr`) — a parse failure is a\n * failure, never a silent pass. Structural so a real `z.ZodType<T>` satisfies\n * it without an import.\n */\nexport interface Schema<T> {\n /** Validate + narrow `value` to `T`, or throw on mismatch (the zod contract). */\n parse(value: unknown): T;\n}\n\n/**\n * The minimal chat-model contract every model the handler talks to must\n * satisfy — the seed's `InjectableChatModel`, trimmed to the one operation\n * llm-call drives for brain-only stages:\n *\n * `withStructuredOutput(schema)` → a runnable whose `invoke(messages)`\n * resolves to a typed object matching `schema`.\n *\n * `BaseChatModel` from `@langchain/core` is the runtime type; this surface\n * names only what llm-call calls so a test fake can ignore the rest. Generic\n * over the message shape `Msg` so a consumer's loader and model agree on it\n * without llm-call inspecting messages.\n */\nexport interface Llm<Msg> {\n withStructuredOutput<T>(schema: Schema<T>): {\n invoke(messages: readonly Msg[]): Promise<T>;\n };\n}\n\n/**\n * Build the `Msg[]` the handler hands to the bound model for a given call. The\n * SDK / message-assembly seam — the seed's `buildBaseMessages` + the lazy\n * `loadMessages` loader rolled into one injected port. Async because the seed\n * lazy-imports the SDK (a top-level `import type` of the langchain messages\n * package explodes the workers test runner). Receives the full `LlmCall` so it\n * can branch on `purpose` exactly as the seed's `buildBaseMessages` did.\n */\nexport type MessageLoader<P extends string, Msg> = (\n call: LlmCall<P>,\n) => Promise<readonly Msg[]>;\n\n/**\n * The model factory — the first DI port. `(modelId) => Llm`. Tests pass a fake\n * builder; production wires `createChatModel(env, getModelConfig(id))`. `null`\n * means \"the host's default model\" (the seed's `string | null`).\n */\nexport type ModelFactory<Msg> = (modelId: string | null) => Llm<Msg>;\n\n// ===========================================================================\n// Config — the knob. `model` + `schemas` required (the two DI ports + the\n// per-purpose parse target); `retry` + `loadMessages` optional.\n// ===========================================================================\n\n/**\n * The llm-call knob. `model` and `schemas` are the load-bearing pair — the\n * model factory DI port and the per-purpose structured-output targets; the\n * rest is optional, exactly the resilient-call \"omit a brick → omit its gate\"\n * story for `retry`:\n *\n * - `model` — DI port 1: the model factory.\n * - `schemas` — one `Schema` per `Purpose`. The handler binds\n * `schemas[call.purpose]` as the structured-output target.\n * - `retry` — backoff policy, composed straight into `../resilient-\n * call`. Omit it and a model failure is terminal (no\n * backoff), exactly as resilient-call with no retry brick.\n * - `loadMessages` — DI port 2: the SDK / message loader. Omit it and the\n * handler invokes the bound model with `[]` (a degenerate\n * but valid call) — provide it to assemble real messages.\n *\n * `P` is the purpose union (e.g. `\"plan\" | \"report\"`); `O` maps each purpose to\n * its parsed output type; `Msg` is the model's message shape (threaded through\n * the loader + model so llm-call never inspects a message).\n */\nexport interface LlmCallConfig<\n P extends string,\n O extends Record<P, unknown>,\n Msg = unknown,\n> {\n /** DI port 1 — the model factory. `(modelId) => Llm`. */\n readonly model: ModelFactory<Msg>;\n /** One structured-output schema per purpose; the parse target the handler binds. */\n readonly schemas: { readonly [K in P]: Schema<O[K]> };\n /** Backoff policy, composed into `../resilient-call`. Omit → no backoff. */\n readonly retry?: RetryPolicy;\n /** DI port 2 — the SDK / message loader. Omit → the handler invokes with `[]`. */\n readonly loadMessages?: MessageLoader<P, Msg>;\n}\n\n// ===========================================================================\n// The request / response shapes the knob speaks.\n// ===========================================================================\n\n/**\n * One LLM call request — the resilient-call `input` for this knob, carried on\n * the `resilient_run` Cmd as plain data (no closures, invariant 3). Mirrors the\n * seed's purpose-discriminated `CallLlmCmd`: a `purpose` selecting the schema +\n * prompt assembly, the `model` id, and the opaque per-purpose `payload` the\n * loader consumes.\n *\n * `key` defaults to `purpose` when the consumer calls `attempt(s, purpose, …)`,\n * so one in-flight call per purpose is tracked under the resilient-call slice —\n * the common single-call-per-stage shape. A consumer that fans out many calls\n * of one purpose passes a distinct `key`.\n */\nexport interface LlmCall<P extends string> {\n /** The stage / schema selector — drives both `schemas[purpose]` and message assembly. */\n readonly purpose: P;\n /** The model id to invoke; `null` = the host's default model. */\n readonly model: string | null;\n /** The per-purpose prompt payload the `MessageLoader` consumes. Opaque to the knob. */\n readonly payload: unknown;\n}\n\n/** The parsed, typed success carried on the `resilient_ok` settle Msg, tagged with its purpose. */\nexport interface LlmOk<P extends string, O extends Record<P, unknown>> {\n readonly key: string;\n readonly purpose: P;\n readonly output: O[P];\n}\n\n/** The typed failure variant — every failure path surfaces this, tagged by purpose. */\nexport interface LlmErr<P extends string> {\n readonly key: string;\n readonly purpose: P;\n /** A human-readable cause (model throw, retry-exhaustion, or schema parse). */\n readonly reason: string;\n /** The original error, carried untouched for the consumer to inspect. */\n readonly error: unknown;\n}\n\n/**\n * The settle Msgs llm-call's handler RETURNS from `interpret` so the substrate\n * enqueues them as follow-up Msgs (re-entry) into the host reducer — exactly as\n * `../resilient-call` does. They are the resilient-call settle Msgs with the\n * payloads ENRICHED to llm-call's typed variants: `result` is the parsed,\n * purpose-tagged `LlmOk`; `error` is the typed `LlmErr` (purpose + reason +\n * raw). The host wires `resilient_ok` → `succeed` and `resilient_err` → `fail`\n * reducer arms (the doc-comment wiring), and folds the enriched payload into its\n * own state there — llm-call never forces a Msg vocabulary on the host, and the\n * settle Msg drives the inherited succeed/fail → backoff → onTimer loop instead\n * of bypassing it.\n */\n//\n// The success Msg is `../resilient-call`'s `SucceedMsg` specialized to the parsed\n// `LlmOk` result — NOT a re-declared shape — so the verb returns (`succeed`'s\n// argument, the handler's resolve) thread through with no cast. The failure Msg\n// is the resilient `FailMsg` with its `error: unknown` NARROWED to the typed\n// `LlmErr`: every value the handler enriches is an `LlmErr`, and `LlmErr` is\n// assignable to `unknown`, so `LlmFailMsg` flows into `rc.fail` (which takes the\n// wide `FailMsg`) directly while the host reducer reads the narrow `error` type.\nexport type LlmSucceedMsg<\n P extends string,\n O extends Record<P, unknown>,\n> = SucceedMsg<LlmOk<P, O>>;\nexport type LlmFailMsg<P extends string> = Omit<FailMsg, \"error\"> & {\n readonly error: LlmErr<P>;\n};\n\n/** The retry / deadline timer Msg — `DeadlineExceeded`, inherited from resilient-call. */\nexport type LlmTimerMsg = DeadlineExceeded;\n\n/**\n * The ports the LEGACY detached `handlers(ports)` form takes. Two outbound Msg\n * builders the handler hands the typed Ok / Err; the consumer returns its own\n * Msg (or `undefined`). KEPT only for `../agent`, which still inherits the\n * detached shape; that path does NOT drive the retry loop (it dispatches the\n * consumer's Msg directly and never re-enters the resilient settle Msg). New\n * consumers use the no-arg `handlers()` form, which RETURNS the settle Msg for\n * re-entry and drives the inherited succeed/fail → backoff → onTimer loop.\n * `../agent` carries the same retry-loop gap and is fixed separately.\n *\n * The builders return `M | undefined` — `undefined` is the \"dispatch nothing\"\n * sentinel the detached form checks (`!== undefined`), spelled as `undefined`\n * rather than `void` so the union is unambiguous (a `void` member of a union is\n * confusing and reads as \"may return anything\").\n */\nexport interface LlmCallPorts<\n P extends string,\n O extends Record<P, unknown>,\n M,\n> {\n /** Build the Msg dispatched on a parsed success. Return `undefined` to dispatch nothing. */\n readonly onOk: (ok: LlmOk<P, O>) => M | undefined;\n /** Build the Msg dispatched on any failure (throw / retry-exhausted / parse). */\n readonly onErr: (err: LlmErr<P>) => M | undefined;\n}\n\n/**\n * The effect Cmd the knob emits: run the LLM call for `key` with `input`. The\n * `input` is the plain `LlmCall` request — the handler reads `purpose` /\n * `model` / `payload` off it (invariant 3: Cmds are data).\n *\n * It is `../resilient-call`'s `RunCmd` specialized to the `LlmCall` input — NOT a\n * re-declared shape — so the resilient verbs' return tuples (`[State, RunCmd[]]`)\n * are this exact type and thread through `attempt` / `succeed` / `fail` /\n * `onTimer` with no cast.\n */\nexport type LlmRunCmd<P extends string> = RunCmd<LlmCall<P>>;\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build an llm-call knob from `config`. `rng` is injected for the inherited\n * retry jitter (pass a fixed `() => 0.5` in tests to pin backoff; defaults to\n * `Math.random`, read only at the resilient-call verb boundary).\n *\n * Returns the uniform L2 knob contract. The slice + verbs (`init`, `attempt`,\n * `succeed`, `fail`, `onTimer`, `subs`) are DELEGATED to a `../resilient-call`\n * knob built over the structured-output model-invoke port — no second backoff\n * here. `handlers` is the one new piece: the purpose-branching, schema-parsing\n * wrapper that returns the enriched resilient settle Msg for re-entry.\n *\n * `P` is the purpose union, `O` the purpose→output map, `Msg` the model's\n * message shape.\n */\nexport function createLlmCall<\n P extends string,\n O extends Record<P, unknown>,\n Msg = unknown,\n>(config: LlmCallConfig<P, O, Msg>, rng: () => number = Math.random) {\n // ---- The composed resilient-call knob ----------------------------------\n //\n // The resilient-call input is the full `LlmCall` request; its result is the\n // PARSED, typed `LlmOk`. Only the `retry` brick is forwarded — llm-call does\n // not expose circuit / rate-limit / cache / deadline knobs (a stage call is\n // a single brain invocation, not a keyed downstream target). Omitting those\n // bricks omits their gates, exactly as resilient-call documents.\n const resilientConfig: ResilientConfig = {\n ...(config.retry === undefined ? {} : { retry: config.retry }),\n };\n const rc = createResilientCall<LlmCall<P>, LlmOk<P, O>>(resilientConfig, rng);\n\n /** The slice this knob owns — resilient-call's slice verbatim. */\n type State = ResilientState<LlmCall<P>, LlmOk<P, O>>;\n\n /** The starting slice — resilient-call's. */\n function init(): State {\n return rc.init();\n }\n\n /**\n * Start (or restart) an LLM call. `keyOrPurpose` defaults the resilient-call\n * `key` to the call's `purpose` so one in-flight call per stage is tracked\n * under the slice (the common shape); pass a distinct key to fan out. PURE —\n * delegates straight to resilient-call's gate.\n */\n function attempt(\n s: State,\n input: LlmCall<P>,\n at: number,\n key: string = input.purpose,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.attempt(s, key, input, at);\n }\n\n /** Record a parsed success for `key`. PURE — resilient-call's `succeed`. */\n function succeed(\n s: State,\n key: string,\n msg: LlmSucceedMsg<P, O>,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.succeed(s, key, msg);\n }\n\n /**\n * Record a failure for `key`: back off via the inherited retry, or settle\n * `failed`. PURE — resilient-call's `fail`. The `error` is the typed\n * `LlmErr`, carried on the call's `failed` phase for the consumer to read.\n */\n function fail(\n s: State,\n key: string,\n msg: LlmFailMsg<P>,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.fail(s, key, msg);\n }\n\n /** A retry / deadline timer fired. PURE — resilient-call's `onTimer`. */\n function onTimer(\n s: State,\n msg: LlmTimerMsg,\n ): readonly [State, readonly LlmRunCmd<P>[]] {\n return rc.onTimer(s, msg);\n }\n\n /** Pre-wired subs — resilient-call's retry-timer subscriptions. */\n function subs(s: State): readonly DeadlineSub[] {\n return rc.subs(s);\n }\n\n // ---- The model-invoke port the resilient handler drives ----------------\n\n /**\n * Run ONE LLM call: assemble messages via the loader, bind the purpose's\n * structured-output schema, invoke, and parse. Returns the typed `LlmOk` on\n * success; throws on any failure (model throw OR schema parse) so the\n * resilient-call handler routes it to a failure Msg + backoff. This is the\n * single port resilient-call wraps — the retry / timeout machinery lives in\n * resilient-call, never here (\"do NOT reinvent backoff\").\n *\n * The seed's two paths collapse to one: every brain-only purpose goes through\n * `withStructuredOutput(schema)`. The `parse` re-validates the output (a fake\n * model might skip validation, and a real provider can drift) so a malformed\n * structured response is a failure, not a corrupt success.\n */\n async function invokeOne(input: LlmCall<P>): Promise<LlmOk<P, O>> {\n const model = config.model(input.model);\n const messages = config.loadMessages\n ? await config.loadMessages(input)\n : [];\n const schema = config.schemas[input.purpose];\n const structured = model.withStructuredOutput(schema);\n const raw = await structured.invoke(messages);\n // Re-validate at the boundary — a parse throw becomes a `resilient_err`\n // (enriched to `LlmErr`) settle Msg upstream, never a corrupt success.\n const output = schema.parse(raw);\n return { key: input.purpose, purpose: input.purpose, output };\n }\n\n // ---- Handlers — the one new piece, composed onto resilient-call --------\n\n /**\n * Pre-wired interpret handler for `resilient_run`. Composes\n * `../resilient-call`'s `resilient_run` handler over the `invokeOne` port and\n * RETURNS the resilient settle Msg — it does NOT dispatch the consumer's Msg\n * itself. Returning the settle Msg is the whole point: the substrate enqueues\n * an interpret handler's returned Msg as a FOLLOW-UP (re-entry) onto the\n * dispatch tail, so the host reducer's `resilient_ok` / `resilient_err` arms\n * run `succeed` / `fail`, which advances the inherited retry loop\n * (succeed/fail → backoff → onTimer → re-issue). A handler that instead\n * `dispatch`ed an enriched Msg directly would settle ONE invoke and bypass\n * that loop entirely — the slice would never leave `running`, the breaker\n * would never trip/close, and the retry counter would never reset.\n *\n * Resilience is composed, not reimplemented: the body delegates to the\n * resilient-call handler's Railway-routed invoke (Ok → `resilient_ok`, Err →\n * `resilient_err`), so a transient model failure backs off via the inherited\n * retry exactly as resilient-call decides. The ONE enrichment llm-call adds is\n * mapping the settle PAYLOADS into its typed variants:\n *\n * - success → `result` is already the parsed `LlmOk` (`invokeOne` returns\n * it), so the resilient `resilient_ok` Msg is the llm-call `LlmSucceedMsg`\n * as-is.\n * - failure → the resilient `resilient_err` carries the RAW throw (a model\n * 503 or a `schema.parse` throw). We rebuild it into the typed `LlmErr`\n * (purpose + reason + raw) so the host reducer routes per-stage.\n *\n * The host folds the enriched payload into its own state in the reducer arm\n * (the doc-comment wiring), NOT here — the handler stays the one impurity\n * (the `invokeOne` await + the `Date.now()` stamp the resilient handler makes)\n * and the reducer stays pure.\n */\n // The resilient-call `resilient_run` handler over `invokeOne` — the shared\n // Railway-routed invoke (Ok → resilient_ok Msg, Err → resilient_err Msg). Both\n // handler forms below build on it; the settle shape is composed, not reinvented.\n const resilientRunHandler = rc.handlers({\n run: (input) => invokeOne(input),\n }).resilient_run;\n\n /**\n * Await one composed invoke and resolve to the ENRICHED resilient settle Msg\n * (`resilient_ok` carrying the parsed `LlmOk`, or `resilient_err` carrying the\n * typed `LlmErr`). The shared core both handler forms reuse.\n */\n async function settleOf(\n cmd: LlmRunCmd<P>,\n ): Promise<LlmSucceedMsg<P, O> | LlmFailMsg<P>> {\n // `resilientRunHandler` never rejects (tryInterpret contract) — it resolves\n // to a `resilient_ok` / `resilient_err` settle Msg stamped with `Date.now()`.\n // Its `ctx` slot is `NoCtx` (the resilient-call work fn reads no ctx — a\n // DELIBERATE context-free seam, not accidental `unknown`), so the empty\n // record satisfies it without a cast.\n const settle = await resilientRunHandler(cmd, {});\n if (settle.type === MsgType.ResilientOk) {\n // `result` is the parsed `LlmOk` from `invokeOne`, and the narrowed\n // `SucceedMsg<LlmOk>` IS `LlmSucceedMsg` — returned as-is, no cast.\n return settle;\n }\n // resilient_err — enrich the raw throw into the typed `LlmErr`, keeping the\n // resilient Msg's `key` / `at` so the host reducer's `fail` verb re-issues\n // from the remembered input and backs off on schedule.\n const rawError: unknown = settle.error;\n const err: LlmErr<P> = {\n key: cmd.key,\n purpose: cmd.input.purpose,\n reason: rawError instanceof Error ? rawError.message : String(rawError),\n error: rawError,\n };\n return {\n type: MsgType.ResilientErr,\n key: settle.key,\n error: err,\n at: settle.at,\n };\n }\n\n /**\n * Pre-wired interpret handler for `resilient_run`. TWO forms:\n *\n * - `handlers()` (no args, the FIXED primary) — RETURNS the enriched\n * resilient settle Msg. The substrate enqueues an interpret handler's\n * returned Msg as a FOLLOW-UP (re-entry) onto the dispatch tail, so the\n * host reducer's `resilient_ok` / `resilient_err` arms run `succeed` /\n * `fail`, advancing the inherited retry loop (succeed/fail → backoff →\n * onTimer → re-issue). This is the correct wiring; the module doc shows it.\n *\n * - `handlers(ports)` (LEGACY, detached) — runs the invoke inside\n * `ctx.waitUntil` and dispatches the consumer's `onOk` / `onErr` Msg\n * directly. KEPT only for `../agent`, which still inherits this shape. It\n * does NOT drive the retry loop (it never re-enters the settle Msg, so\n * `succeed` / `fail` never run) — `../agent` carries the same gap and is\n * fixed in its own pass. New consumers MUST use the no-arg form.\n *\n * Resilience is composed, not reimplemented: both forms delegate to the\n * resilient-call handler's Railway-routed invoke and add only the typed\n * `LlmOk` / `LlmErr` enrichment. The ONE impurity is the `invokeOne` await +\n * the `Date.now()` stamp the resilient handler makes; the reducer stays pure.\n */\n function handlers(): {\n resilient_run: (\n cmd: LlmRunCmd<P>,\n ) => Promise<LlmSucceedMsg<P, O> | LlmFailMsg<P>>;\n };\n function handlers<M>(ports: LlmCallPorts<P, O, M>): {\n resilient_run: (\n cmd: LlmRunCmd<P>,\n ctx: { waitUntil(p: Promise<unknown>): void; dispatch(msg: M): unknown },\n ) => void;\n };\n function handlers<M>(ports?: LlmCallPorts<P, O, M>) {\n if (ports === undefined) {\n // FIXED primary: return the settle Msg for re-entry — drives the loop.\n return { resilient_run: (cmd: LlmRunCmd<P>) => settleOf(cmd) };\n }\n // LEGACY detached form — kept for `../agent` (see doc above). Dispatches the\n // consumer's Msg directly; does not re-enter the settle Msg.\n return {\n resilient_run: (\n cmd: LlmRunCmd<P>,\n ctx: {\n waitUntil(p: Promise<unknown>): void;\n dispatch(msg: M): unknown;\n },\n ): void => {\n const fired = (async () => {\n const settle = await settleOf(cmd);\n if (settle.type === MsgType.ResilientOk) {\n const msg = ports.onOk(settle.result);\n if (msg !== undefined) await ctx.dispatch(msg);\n return;\n }\n const msg = ports.onErr(settle.error);\n if (msg !== undefined) await ctx.dispatch(msg);\n })();\n ctx.waitUntil(fired);\n },\n };\n }\n\n return { init, attempt, succeed, fail, onTimer, subs, handlers, invokeOne };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the\n * slice lives at `state.resilience` — the same convenience `../resilient-call`\n * ships, re-typed for the llm-call slice so consumers wire one import. Pure.\n */\nexport function liftLlmCall<\n S extends { resilience: ResilientState<LlmCall<P>, LlmOk<P, O>> },\n P extends string,\n O extends Record<P, unknown>,\n C extends Cmd,\n>(\n state: S,\n result: readonly [ResilientState<LlmCall<P>, LlmOk<P, O>>, readonly C[]],\n): readonly [S, readonly C[]] {\n return liftResilience(state, result);\n}\n\n/**\n * Re-export the deadline Sub primitives (inherited from resilient-call) so\n * consumers wire one import: `subscribeDeadline` is the `subscribe` cell,\n * `deadlineSub` builds the Sub literal `subs` emits.\n */\nexport { subscribeDeadline, deadlineSub };\nexport type { DeadlineSub, DeadlineExceeded, ResilientState };\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pure/core.ts"],"names":[],"mappings":"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","file":"chunk-QLA76GIT.js","sourcesContent":["/**\n * @demlik/tea — pure-core leaf (ADR 0006).\n *\n * This is the runtime-free pure core of the substrate. It holds the\n * client-prediction fold seam (`foldMsgs`), the form reader\n * (`formOf`/`detectUpdateForm`), and the pure type vocabulary (`Machine`,\n * `Reducer`, `Transitions`, `Cmd`, `Sub`, `Port`, …).\n *\n * **Dependency direction (the actual decoupling):** this module imports\n * NOTHING from the runtime — no `better-result`, no `run`/host/`Store`. The\n * runtime (`run`, the host, interpret, `Store`, subscribe — all in\n * `../index.ts`) imports *from* here; never the reverse. The\n * `@demlik/tea/pure` subpath re-exports this surface, and\n * `pure/import-graph.test.ts` is the regression fence asserting the pure\n * entrypoint's import graph never reaches `run`.\n */\n\n// === Dev-mode invariant enforcement ===\n//\n// Three runtime guards that catch TEA invariant violations during development.\n// Dead-code-eliminated in production by any bundler that replaces\n// `import.meta.env.DEV` (Vite) or `process.env.NODE_ENV` (webpack/esbuild).\n//\n// 1. deepFreeze(state) before passing to update — catches mutation\n// 2. Thenable check on return value — catches async update\n// 3. Walk cmd fields for function types — catches closures-as-data\n//\n// These enforce invariants 1 and 2 at runtime where the type system cannot.\nexport const __DEV__: boolean = (() => {\n try {\n const meta = import.meta as unknown as { env?: { DEV?: boolean } };\n if (meta.env?.DEV !== undefined) {\n return meta.env.DEV;\n }\n } catch {\n // import.meta not available (Node CJS, service workers)\n }\n try {\n return (\n typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\"\n );\n } catch {\n return false;\n }\n})();\n\nexport function deepFreeze<T>(obj: T, seen?: WeakSet<object>): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== Array.prototype && proto !== null)\n return obj;\n const visited = seen ?? new WeakSet();\n if (visited.has(obj as object)) return obj;\n visited.add(obj as object);\n Object.freeze(obj);\n for (const v of Object.values(obj as Record<string, unknown>))\n deepFreeze(v, visited);\n return obj;\n}\n\nexport function hasFunctionValues(obj: unknown): string | null {\n if (obj === null || typeof obj !== \"object\") return null;\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (typeof value === \"function\") return key;\n }\n return null;\n}\n\nexport function assertPureResult(result: unknown, msgType: string): void {\n if (result && typeof (result as { then?: unknown }).then === \"function\") {\n throw new Error(\n `@demlik/tea: update cell \"${msgType}\" returned a Promise. ` +\n `update must be synchronous. Move async work to interpret.`,\n );\n }\n if (\n !Array.isArray(result) ||\n result.length < 2 ||\n !Array.isArray(result[1])\n ) {\n throw new Error(\n `@demlik/tea: update cell \"${msgType}\" returned a non-tuple. ` +\n `Expected [State, Cmd[]], got ${typeof result}.`,\n );\n }\n const cmds = result[1] as readonly { type: string }[];\n for (const cmd of cmds) {\n const fnField = hasFunctionValues(cmd);\n if (fnField !== null) {\n throw new Error(\n `@demlik/tea: Cmd \"${cmd.type}\" has function field \"${fnField}\". ` +\n `Cmds must be plain data, not closures.`,\n );\n }\n }\n}\n\n// === Cmd: tagged-union, one-shot effect ===\nexport type Cmd<T extends string = string> = { type: T };\n\n// === SyncReturn<S, C>: the compile-time reentrancy guard (ADR 0003 #5) ===\n//\n// The canon rule (`.patterns/tea-do/reentrancy.md`,\n// `.patterns/tea-do/reentrancy-deadlock.md`): a reducer must never\n// inline-`await` a re-entrant result. \"Non-reentrant by default\" ≡ \"the reducer\n// is pure AND synchronous\": a reducer that returns a `Promise` (because it is\n// `async`, or inline-`await`s) is the suspended-but-still-blocking turn Orleans\n// serializes against — it occupies the single-writer slot across a suspension\n// point and can be re-entered mid-flight → deadlock.\n//\n// So the guard is purely type-level: every reducer/transition cell returns\n// `SyncReturn<S, C>` — the synchronous result tuple, intersected with `{ then?:\n// never }` to make it *non-thenable*. A `Promise<readonly [S, readonly C[]]>`\n// is an object with a `then` method, so it fails to satisfy `then?: never`; an\n// `async (s, m) => [...]` (or one that inline-`await`s) is therefore\n// unrepresentable at compile time — `tsc` rejects it *at the cell*, not at a\n// runtime hang. A plain synchronous tuple `[next, cmds]` has no `then` property,\n// so the optional-absent `then?: never` is satisfied and existing pure reducers\n// compile unchanged. `SyncReturn<S, C>` is also assignable *to* `readonly [S,\n// readonly C[]]` (the intersection is narrower), so the runtime dispatch in\n// `run`/`replay` consumes a cell result exactly as before.\n//\n// This mirrors `retry-backoff`'s `Rng` brand (#63): the obligation lives in the\n// type at the construction boundary (\"parse, don't validate\"), not in a comment.\nexport type SyncReturn<S, C extends Cmd> = readonly [S, readonly C[]] & {\n // A thenable carries a callable `then`; forbidding it rejects every Promise.\n // Optional + `never` means \"absent on a sync tuple, impossible on a Promise\".\n readonly then?: never;\n};\n\n// === update form: Reducer vs Transitions, tagged once ===\n//\n// The substrate accepts two `update` shapes (see the `Machine.update` union and\n// the `defineMachine` overloads):\n//\n// - \"reducer\" — flat record keyed by Msg.type, every cell a function.\n// - \"transitions\" — 2D table keyed by State.type then Msg.type, every cell a\n// record of functions.\n//\n// Which one a given machine is must be known at runtime by `run`, `replay`, and\n// the `withX` wrappers (their dispatch / key-enumeration / reserved-namespace\n// scans differ per form). Rather than re-derive it structurally at every reader\n// — a `typeof update[firstKey] === \"function\"` heuristic that breaks the day a\n// reducer cell is itself an object-with-a-call — `defineMachine` computes the\n// form ONCE at construction and stamps it on the machine as a non-enumerable\n// `__form` (so it never serializes, never collides with a Msg.type key, never\n// shows up in `Object.keys(machine)`). Every reader calls `formOf(machine)`.\nexport type UpdateForm = \"reducer\" | \"transitions\";\n\n// The single structural heuristic, defined ONCE. Used only by `defineMachine`\n// (and `formOf`'s fallback for machines built without it, e.g. a plain object\n// literal annotated as `Machine`). A Reducer's first own value is a function; a\n// Transitions table's first own value is a record (of functions). An empty\n// record (`M` is `never`) can never dispatch a Msg, so the form is irrelevant —\n// \"reducer\" is returned arbitrarily.\nexport function detectUpdateForm(update: object): UpdateForm {\n const firstKey = Object.keys(update)[0];\n if (firstKey === undefined) return \"reducer\";\n const firstValue = (update as Record<string, unknown>)[firstKey];\n return typeof firstValue === \"function\" ? \"reducer\" : \"transitions\";\n}\n\n// The single reader every form-sensitive site goes through. Prefers the\n// `__form` tag stamped by `defineMachine` (authoritative — computed once at the\n// typed construction boundary); falls back to `detectUpdateForm` only for a\n// machine that never passed through `defineMachine`. No reader re-implements the\n// heuristic.\nexport function formOf(machine: {\n update: object;\n __form?: UpdateForm;\n}): UpdateForm {\n return machine.__form ?? detectUpdateForm(machine.update);\n}\n\n// === applyCell: THE single reducer-vs-transitions dispatch primitive ===\n//\n// Applies the one update cell selected by `(formOf(machine), state, msg)` and\n// returns its `[nextState, cmds]` verbatim. Every site that steps a machine —\n// `run`'s applyUpdate, `foldUpdates` (replay/foldMsgs), the PBT fold runner,\n// and the withX wrappers — dispatches through THIS function, so production and\n// the verification tools agree on the update form by construction (#275).\n// Pure and dev-check-free: `deepFreeze`/`assertPureResult` stay at the call\n// sites that want them.\nexport function applyCell<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n state: S,\n msg: M,\n): readonly [S, readonly C[]] {\n type CellFn = (state: S, msg: M) => readonly [S, readonly C[]];\n if (formOf(machine) === \"reducer\") {\n const record = machine.update as Record<string, CellFn>;\n // biome-ignore lint/style/noNonNullAssertion: the Reducer mapped type guarantees a cell for every dispatched Msg.type; `!` required under noUncheckedIndexedAccess and cannot miss for a Msg the machine accepts\n return record[msg.type]!(state, msg);\n }\n const table = machine.update as Record<string, Record<string, CellFn>>;\n // The ONE sanctioned `state as unknown as { type }` read: the Transitions\n // overload constrains S to `{ type: string }` at the `defineMachine`\n // boundary, but that constraint is erased on the runtime-facing `object`\n // here — every former per-site copy of this double-cast collapsed into\n // this line (#275).\n const stateKey = (state as unknown as { type: string }).type;\n // biome-ignore lint/style/noNonNullAssertion: the Transitions overload guarantees every (state.type × msg.type) cell exists; `!` required under noUncheckedIndexedAccess and cannot miss at runtime\n return table[stateKey]![msg.type]!(state, msg);\n}\n\n// === msgKeysOf: recover the Msg.type set from either update form ===\n//\n// A Reducer's own keys ARE the Msg.type set. A Transitions table's keys are\n// state.type; its INNER keys are the Msg.type set (uniform across phases by\n// the mapped-type contract), so the first phase's inner keys are read. An\n// empty update (`M` is `never`) yields `[]`. Keyed on `formOf` — the withX\n// wrappers and the PBT `msgTypeKeys` all read through this one helper (#275).\nexport function msgKeysOf(machine: {\n update: object;\n __form?: UpdateForm;\n}): readonly string[] {\n const keys = Object.keys(machine.update);\n const firstKey = keys[0];\n if (firstKey === undefined) return [];\n if (formOf(machine) === \"reducer\") return keys;\n const firstValue = (machine.update as Record<string, unknown>)[firstKey];\n return Object.keys(firstValue as object);\n}\n\n// === Reducer<S, M, C>: record-of-handlers form of `update` ===\n//\n// Flat dispatch table keyed by `Msg.type`. Each cell is a pure transition for\n// a single Msg variant, narrowed via `Extract<M, { type: K }>`. The mapped type\n// is *load-bearing*: adding a Msg variant without a matching key in the\n// Reducer record is a compile error — no `absurd()` helper at call sites, no\n// silent fall-through, no default branch hiding impurity.\n//\n// Strengthens invariant 2 (pure transitions — the record form has no\n// fall-through default to hide impurity behind) and invariant 7 (identity is\n// explicit — the Msg variant set is load-bearing at the type level).\n//\n// `defineMachine` accepts the Reducer record form via overload. The runtime\n// dispatches via `update[msg.type](state, msg)`.\nexport type Reducer<S, M extends { type: string }, C extends Cmd> = {\n [K in M[\"type\"]]: (\n state: S,\n msg: Extract<M, { type: K }>,\n // `SyncReturn<S, C>` (not `readonly [S, readonly C[]]`) is the reentrancy\n // guard: a non-thenable return type, so an `async`/inline-`await`ing cell —\n // which returns `Promise<...>` — fails to compile at the cell. See the\n // `SyncReturn` doc above.\n ) => SyncReturn<S, C>;\n};\n\n// === Transitions<S, M, C>: table form of `update` for state-machine-shaped machines ===\n//\n// When `State` is itself a discriminated union (`State.type` is the active\n// phase), the table form makes every (state.type × msg.type) cell explicit at\n// the type level. Missing a cell — e.g. forgetting how `phase: \"settling\"`\n// handles `msg.type: \"tab_stepped\"` — fails to compile. No nested switch, no\n// fall-through, no `_ -> (state, [])` default branch hiding regressions.\n//\n// Each cell receives the narrowed `State` for its phase and the narrowed\n// `Msg` for its variant via two `Extract` lookups. Phantom-narrow at the\n// type level; pure data at runtime.\n//\n// `defineMachine` accepts a third `update` form for `Transitions<S, M, C>` —\n// the runtime dispatches via `update[state.type][msg.type](state, msg)`.\n//\n// Strengthens invariant 2 (table form has no fall-through default),\n// invariant 6 (runtime walks the table predictably, no hidden dispatch\n// fallback), and invariant 7 (both state.type and msg.type are load-bearing\n// at the type level).\nexport type Transitions<\n S extends { type: string },\n M extends { type: string },\n C extends Cmd,\n> = {\n [P in S[\"type\"]]: {\n [K in M[\"type\"]]: (\n state: Extract<S, { type: P }>,\n msg: Extract<M, { type: K }>,\n // Same reentrancy guard as `Reducer`: a transition cell that returns a\n // Promise (async / inline-await) is the suspended-blocking turn the canon\n // forbids, so its return is the non-thenable `SyncReturn<S, C>`.\n ) => SyncReturn<S, C>;\n };\n};\n\n// === Cmd: namespace for conditional Cmd emission ===\n//\n// Two recurring frictions in reducer cells:\n//\n// 1. \"Emit cmd X only when condition Y holds.\"\n// Hand-rolled: `cond ? [{ type: \"x\", ... }] : []`\n// With Cmd: `Cmd.when(cond, { type: \"x\", ... })`\n//\n// 2. \"Emit cmd X carrying a value that may be undefined; if undefined,\n// emit nothing.\"\n// Hand-rolled: `value !== undefined ? [{ type: \"x\", value }] : []`\n// With Cmd: `Cmd.whenDefined(value, (v) => ({ type: \"x\", value: v }))`\n//\n// Both forms return `readonly C[]` so they spread cleanly into the cmds\n// array returned by a Transitions cell:\n//\n// return [next, [\n// ...Cmd.when(state.tabId !== undefined, { type: \"detach_debugger\", tabId: state.tabId }),\n// ...Cmd.whenDefined(state.queueItemId, (id) => ({\n// type: \"queue:complete\", queueItemId: id, status: \"done\",\n// })),\n// ]];\n//\n// `Cmd` as a value namespace coexists with `Cmd<T>` as a type — TypeScript's\n// declaration merging puts them in different name spaces (type vs. value).\n// The reader sees `Cmd<...>` in type position and `Cmd.when(...)` in\n// expression position; no ambiguity at use sites.\n//\n// Why a namespace and not top-level `when` / `whenDefined`: `when` at module\n// scope reads as a generic conditional, but these helpers are specifically\n// for *Cmd emission* (return `readonly C[]`, the cmds-array contract of\n// every Transitions cell). The namespace pins that intent at the call site.\nexport const Cmd = {\n /**\n * The empty Cmd array. Typed `readonly never[]` so it's assignable to any\n * `readonly C[]` for any `C extends Cmd`. Use in `init` returns and\n * Transitions cells that emit zero effects:\n *\n * init: (loaded) => [loaded ?? initial, Cmd.none],\n * tick: (state) => [state, Cmd.none],\n *\n * Elm's `Cmd.none` analogue. Cultural signal alongside the runtime help:\n * `[state, Cmd.none]` reads as intent (\"this transition emits nothing\"),\n * `[state, []]` reads as \"empty array of what\".\n *\n * Frozen at runtime so a downstream consumer can't `.push()` into the\n * shared reference.\n */\n none: Object.freeze([]) as readonly never[],\n\n /**\n * Flat concat of cmd arrays. Use when a cell composes effects from\n * multiple conditional sources:\n *\n * return [next, Cmd.batch(\n * Cmd.whenDefined(state.queueItemId, (id) => ({ type: \"complete\", id })),\n * Cmd.when(state.windowId !== undefined, { type: \"close_window\", windowId }),\n * [{ type: \"detach_debugger\", tabId: state.tabId }],\n * )];\n *\n * `<const C>` keeps inline cmd literals' discriminants narrow (same\n * rationale as `when` / `whenDefined` above).\n */\n batch: <const C extends Cmd>(\n ...arrs: readonly (readonly C[])[]\n ): readonly C[] => {\n const out: C[] = [];\n for (const arr of arrs) out.push(...arr);\n return out;\n },\n\n /**\n * Emit `cmd` wrapped in a single-element array iff `cond` is true.\n * Otherwise return the empty array. Spreads cleanly into the cmds array\n * returned by a Transitions cell.\n *\n * `const C` (TypeScript 5.0+) keeps the inferred `type:` discriminator\n * literal narrow when called with an object literal — so `Cmd.when(b,\n * { type: \"x\", ... })` infers as `{ type: \"x\", ... }`, not `{ type:\n * string, ... }`, and stays assignable to a discriminated-union arm\n * without `as const` at the call site.\n */\n when: <const C extends Cmd>(cond: boolean, cmd: C): readonly C[] =>\n cond ? [cmd] : [],\n\n /**\n * If `value` is defined, call `build(value)` and emit the resulting cmd\n * wrapped in a single-element array. If `value` is `undefined`, return\n * the empty array. `value` is narrowed to `T` inside `build`.\n *\n * `const C` on the return type keeps the callback's object-literal\n * `type:` field narrow (see `when` above for the full rationale).\n */\n whenDefined: <T, const C extends Cmd>(\n value: T | undefined,\n build: (value: T) => C,\n ): readonly C[] => (value !== undefined ? [build(value)] : []),\n} as const;\n\n// === SubId: branded string for Sub identity ===\n//\n// Subs are reconciled by id on every transition (canon §2.5, invariant 7).\n// Before branding, `Sub.id` was a raw `string` — a typo at one call site and a\n// different typo at another would silently produce two distinct subs that\n// looked identical to a human reader. The brand makes id construction explicit\n// (`subId(\"...\")`) and catches accidental string-where-SubId at the type\n// level.\n//\n// The brand is structural: `string & { __brand: \"SubId\" }`. The `subId(s)`\n// constructor is the ONE permitted cast in the substrate — every other call\n// site must go through it.\nexport type SubId = string & { readonly __brand: \"SubId\" };\n\n/**\n * Construct a `SubId` from a string. Use this everywhere a Sub literal is\n * built — including ids derived from runtime data (`subId(\\`ws:${auditId}\\`)`).\n * The brand is structural, not nominal, so dynamic ids are permitted; the\n * constructor's job is to make the identity decision explicit at every call\n * site so the type system can catch accidental raw-string drift.\n *\n * Strengthens invariant 7 (identity is explicit).\n */\nexport function subId(s: string): SubId {\n return s as SubId;\n}\n\n// === Sub: tagged-union, continuous source of msgs; stable id used for diff/reconcile ===\nexport type Sub<T extends string = string> = { id: SubId; type: T };\n\n// === Port<T>: typed escape hatch for \"data leaving the runtime\" ===\n//\n// A Port is a named, typed channel that Cmd handlers can `emit` to via the\n// augmented `ctx.emit(port, value)`. Subscribers attach via\n// `runtime.subscribePort(port, listener)` and receive every emitted value\n// synchronously (same fanout discipline as `runtime.observe`).\n//\n// Why Ports are a substrate primitive, distinct from State and Observe:\n// - **State** is \"the world as the program sees it.\" Folding outgoing\n// announcements into State turns ephemeral signals into persisted facts —\n// the classic anti-pattern that motivated this primitive.\n// - **Observe** sees every transition `(msg, state)`. That's the right channel\n// for devtools and logging, but it forces every consumer to filter the\n// entire firehose for a single signal.\n// - **Ports** are typed and selective: one channel per concept, subscribers\n// only see what the handler chose to emit. Mirrors Elm's outgoing-port\n// semantics — a typed declaration with `port` keyword whose values flow\n// out of the program to the host.\n//\n// Port identity is **by reference** — the returned object IS the identity.\n// Name is metadata for debugging. To prevent the \"two modules definePort the\n// same name expecting to share a channel, silently get distinct ports\" class\n// of bug, `definePort` asserts the name has not been seen in this process —\n// the symmetric runtime check to `SubId` (canon §2.12, invariant 7). Each\n// definePort call must use a unique name; if two modules need the same port,\n// one module exports it and the other imports it.\nexport interface Port<T> {\n readonly __brand: \"port\";\n readonly name: string;\n // Phantom field — never assigned, never read at runtime. Carries `T` for\n // inference at `ctx.emit` and `subscribePort` call sites. Using `?` so\n // `definePort` can produce a Port without constructing a value of `T`.\n readonly __t?: T;\n}\n\n/**\n * Augmentation injected onto `ctx` inside Cmd handlers. Handlers receive\n * `ctx & PortEmitter` so they can call `ctx.emit(port, value)` synchronously.\n * Subscribers registered via `runtime.subscribePort(port, listener)` receive\n * the value immediately.\n *\n * Emitting to a port with no subscribers is a no-op (does not throw).\n */\nexport interface PortEmitter {\n emit<T>(port: Port<T>, value: T): void;\n}\n\n// === NoCtx: the context-free marker ===\n//\n// A handler, Sub, or machine that reads NOTHING from `ctx` says so by using\n// `NoCtx` for its Ctx slot. This is the DELIBERATE absence of context — not\n// the accidental `unknown` looseness that erodes a boundary (see issue #64).\n//\n// `unknown` at a Ctx seam is ambiguous: it can mean \"I intentionally need no\n// context\" OR \"I gave up tightening this type.\" `NoCtx` resolves that\n// ambiguity at the type level. A reader (and the next agent) sees `NoCtx` and\n// knows the context-free-ness is a choice the author made, not a hole.\n//\n// Shape: an empty readonly record. A `Ctx & PortEmitter` is-a `NoCtx`, so a\n// context-free handler still composes at a richer call site. It carries no\n// own fields, so the name's claim (\"reads nothing from ctx\") is what callers\n// read at the seam.\n//\n// Strengthens invariant 6 (no silent looseness — a context-free seam is named,\n// not inferred) and invariant 8 (the boundary is legible; `unknown` stays\n// reserved for genuine wire-edge erasure, not for \"didn't bother\").\nexport type NoCtx = Readonly<Record<never, never>>;\n\n/** Spelled-out alias for `NoCtx`, for sites that prefer the long name. */\nexport type ContextFree = NoCtx;\n\n// === Interpret<M, C, Ctx>: record-of-handlers form of `interpret` ===\n//\n// Flat dispatch table keyed by `Cmd.type`. Each cell receives the narrowed Cmd\n// and the runtime-augmented Ctx (`Ctx & PortEmitter`) and resolves to a\n// follow-up Msg or `void` (fire-and-forget). The mapped type makes a missing\n// handler a compile error — `defineMachine` cannot accept the dictionary until\n// every Cmd variant has one.\n//\n// Hoisted out of `Machine.interpret` so consumers can type a free-standing\n// handler dictionary with `Interpret<MyMsg, MyCmd, MyCtx>` instead of\n// re-declaring the mapped type at every effects module.\n//\n// Strengthens invariant 2 (the record form has no fall-through default to\n// hide impurity behind) and invariant 7 (identity is explicit — the Cmd\n// variant set is load-bearing at the type level).\nexport type Interpret<M extends { type: string }, C extends Cmd, Ctx> = {\n [K in C[\"type\"]]: (\n cmd: Extract<C, { type: K }>,\n ctx: Ctx & PortEmitter,\n // biome-ignore lint/suspicious/noConfusingVoidType: an interpret handler returns a follow-up Msg or nothing; `void` permits no-return bodies that `M | undefined` would reject\n ) => Promise<M | void>;\n};\n\n// === Subscribe<M, U, Ctx>: record-of-handlers form of `subscribe` ===\n//\n// Flat dispatch table keyed by `Sub.type`. Each cell receives the narrowed\n// Sub, the Ctx, and a `dispatch` to fire follow-up Msgs. Returns a cleanup\n// function the substrate calls when the Sub is reconciled out (state\n// transitioned away). The mapped type guarantees every Sub variant has a\n// handler at the type level; runtime dispatch is a single property lookup.\n//\n// Hoisted out of `Machine.subscribe` so consumers can type a free-standing\n// handler dictionary with `Subscribe<MyMsg, MySub, MyCtx>` instead of\n// re-declaring the mapped type at every subs module.\n//\n// Strengthens invariant 7 (identity is explicit — the Sub variant set is\n// load-bearing at the type level).\nexport type Subscribe<M extends { type: string }, U extends Sub, Ctx> = {\n [K in U[\"type\"]]: (\n sub: Extract<U, { type: K }>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n};\n\n// === Machine: pure data, host-agnostic ===\n//\n// `update` is stored as a union of the two record forms — the runtime branches\n// on shape at dispatch time. The public `defineMachine` overloads enforce one\n// shape per call so consumers never see the union themselves.\n//\n// - Reducer form: `Reducer<S, M, C>` — flat record keyed by Msg.type;\n// exhaustiveness enforced by the mapped type.\n// - Transitions form: `Transitions<S, M, C>` — 2D table keyed by\n// `state.type` then `msg.type`; only available when `S extends { type:\n// string }`. Conditional in the union so machines without a discriminated\n// State don't widen to include this branch.\n//\n// `M` is constrained to `{ type: string }` because both record forms require\n// a string discriminant.\n//\n// `interpret` is conditionally optional: when `C` is `Cmd<never>` the\n// interpret map is keyed by `never`, so the only valid value is `{}`. Forcing\n// every cmdless machine to write `interpret: {} as never` is ceremony for a\n// shape the type already pins. The tuple-wrap (`[C] extends [Cmd<never>]`)\n// disables distributive conditional so a real cmd union (e.g.\n// `Cmd<\"a\"> | Cmd<\"b\">`) doesn't degrade to optional just because one arm\n// happens to be `Cmd<never>`. Same trick the `update` field uses.\nexport type Machine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n> = {\n /**\n * Boot the runtime. Called once by `run(...)` with whatever `Store.load()`\n * returned: `null` on fresh boot, the persisted state on rehydrate.\n *\n * **Contract:** when `loaded !== null`, init MUST return `[loaded, []]` —\n * no Cmds. Init's rehydrate branch is the migration / parse boundary, not\n * the boot-effect hook. See Invariant 2 in `.patterns/tea/tea-invariants.md`.\n *\n * Boot effects routes:\n * - Stateless infrastructure → host module top, outside TEA.\n * - State-conditional resume → a `boot` Msg the host dispatches once\n * after `run(...)` returns.\n *\n * Violations are caught at runtime by `replay` (which throws with a\n * pointer to the alternatives).\n */\n init: (loaded: S | null, ctx: Ctx) => readonly [S, readonly C[]];\n update:\n | Reducer<S, M, C>\n // Wrap in tuple to disable distributive conditional behavior. Without\n // `[S]`, TS distributes the conditional across `S`'s union members,\n // producing `Transitions<A,...> | Transitions<B,...>` instead of the\n // desired `Transitions<A|B,...>` — the table form needs a single\n // mapping over the full union of state types, not separate tables per\n // member.\n | ([S] extends [{ type: string }] ? Transitions<S, M, C> : never);\n subscriptions?: (state: S) => readonly U[];\n subscribe?: Subscribe<M, U, Ctx>;\n /**\n * The update form (\"reducer\" | \"transitions\"), stamped non-enumerably by\n * `defineMachine` at construction (see `UpdateForm` / `formOf`). Optional in\n * the type so the structural `Machine` annotation form keeps accepting plain\n * object literals; readers go through `formOf`, which falls back to\n * `detectUpdateForm` when the tag is absent. Never written by hand.\n */\n readonly __form?: UpdateForm;\n} & ([C] extends [Cmd<never>]\n ? { interpret?: Interpret<M, C, Ctx> }\n : { interpret: Interpret<M, C, Ctx> });\n\n// === foldUpdates: the single internal fold `replay` and `foldMsgs` share ===\n//\n// Folds `machine.update` over `msgs` from `initialState`, dispatching each\n// Msg through `applyCell` — the same primitive `run` uses — so every fold site\n// agrees on the reducer-vs-transitions form by construction (no second copy of\n// the dispatch to drift). Returns the final state plus the Cmds the cells emitted\n// along the way; the caller keeps them (`replay`) or discards them (`foldMsgs`).\n//\n// Touches no `Store`, no `interpret` handler, and starts no subscription — it\n// only calls `update` cells. The two public folds differ only in how they\n// enter and what they return: `replay` enters via `init` and returns\n// `{ state, cmds, subs }`; `foldMsgs` enters from a base state and returns `S`.\nexport function foldUpdates<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n initialState: S,\n msgs: readonly M[],\n): { state: S; cmds: C[] } {\n let state: S = initialState;\n const cmds: C[] = [];\n\n for (const msg of msgs) {\n if (__DEV__) deepFreeze(state);\n\n const result = applyCell<S, M, C>(machine, state, msg);\n\n if (__DEV__) assertPureResult(result, msg.type);\n\n const [next, emitted] = result;\n state = next;\n cmds.push(...emitted);\n }\n\n return { state, cmds };\n}\n\n// === foldMsgs: runtime-free client-prediction fold seam (ADR 0006, #211) ===\n//\n// Folds `machine.update` over an ordered `Msg[]` starting from a caller-supplied\n// `base` state and returns the resulting state ONLY. This is the client-side\n// replay primitive a prediction loop needs (the Gambetta/Valve\n// authoritative-server reconcile step): re-simulate a queue of un-acked inputs\n// on top of an authoritative snapshot —\n// `foldMsgs(machine, snapshot, pendingInputs)`.\n//\n// Distinct from `replay` (the test idiom), by design (ADR 0006):\n// - Enters from a direct `base` parameter, NOT via `init`/`loaded` — so\n// reconciliation correctness never depends on the machine author's `init`\n// rehydrate discipline. Takes no `ctx` (the fold calls `update` only).\n// - Returns just the final `S` — not `{ state, cmds, subs }`. During prediction\n// the inputs' effects were already sent to the server; re-emitting their Cmds\n// on the client would double-fire, and `subs` are a runtime concern. Returning\n// only `S` makes \"replay fires no effects\" structural, not caller discipline.\n// A caller that wants the emitted Cmds for assertions uses `replay`.\n//\n// Invokes no `Store`, no `interpret` handler, and starts no subscription — it\n// shares `replay`'s `foldUpdates` fold, which only calls `update` cells, and\n// reads the reducer-vs-transitions form via the same `formOf(machine)` reader\n// `run`/`replay` use, so it agrees with them by construction.\n//\n// Per ADR 0006 the runtime-free *guarantee* lives on a dedicated\n// `@demlik/tea/pure` subpath whose module graph never reaches `run`; that\n// subpath export + import-graph guard are #213's scope. Here `foldMsgs` ships\n// as a reachable public API from root; #213 formalizes the boundary.\nexport function foldMsgs<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(machine: Machine<S, M, C, U, Ctx>, base: S, msgs: readonly M[]): S {\n return foldUpdates<S, M, C>(machine, base, msgs).state;\n}\n"]}
|