@demlik/tea 0.14.0 → 0.16.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 +30 -7
- package/dist/agent/index.d.ts +49 -36
- package/dist/agent/index.js +12 -8
- package/dist/chunk-4LPY67E4.js +18 -0
- package/dist/chunk-4LPY67E4.js.map +1 -0
- package/dist/chunk-5XU6ZJO2.js +533 -0
- package/dist/chunk-5XU6ZJO2.js.map +1 -0
- package/dist/{chunk-AQ6ZIMEA.js → chunk-6NG4V5SB.js} +89 -115
- package/dist/chunk-6NG4V5SB.js.map +1 -0
- package/dist/{chunk-HYDWG35Q.js → chunk-7MGXQZPW.js} +11 -9
- package/dist/chunk-7MGXQZPW.js.map +1 -0
- package/dist/{chunk-BLJ2JQSH.js → chunk-BCTEOVOU.js} +3 -3
- package/dist/{chunk-BLJ2JQSH.js.map → chunk-BCTEOVOU.js.map} +1 -1
- package/dist/chunk-CH5KTOEZ.js +710 -0
- package/dist/chunk-CH5KTOEZ.js.map +1 -0
- package/dist/chunk-CIHSHLQX.js +64 -0
- package/dist/chunk-CIHSHLQX.js.map +1 -0
- package/dist/{chunk-Z2L3LSNV.js → chunk-EHUDXAKC.js} +246 -210
- package/dist/chunk-EHUDXAKC.js.map +1 -0
- package/dist/chunk-GOX2CYIM.js +380 -0
- package/dist/chunk-GOX2CYIM.js.map +1 -0
- package/dist/chunk-IVJP3GKJ.js +104 -0
- package/dist/chunk-IVJP3GKJ.js.map +1 -0
- package/dist/{chunk-F7FMAWLA.js → chunk-MVZS5FDM.js} +13 -6
- package/dist/chunk-MVZS5FDM.js.map +1 -0
- package/dist/{chunk-LYLTKJWM.js → chunk-MYNLY3HP.js} +9 -8
- package/dist/chunk-MYNLY3HP.js.map +1 -0
- package/dist/{chunk-I5MQMJDW.js → chunk-NSIAORRY.js} +20 -30
- package/dist/chunk-NSIAORRY.js.map +1 -0
- package/dist/{chunk-MYXFNWAJ.js → chunk-QNGYQMZB.js} +4 -4
- package/dist/chunk-QNGYQMZB.js.map +1 -0
- package/dist/chunk-WT5PBPLB.js +98 -0
- package/dist/chunk-WT5PBPLB.js.map +1 -0
- package/dist/{core-CTzMJ82M.d.ts → core-BbgtiXdg.d.ts} +231 -139
- package/dist/do/index.d.ts +37 -32
- package/dist/do/index.js +21 -14
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-B0-qGQDy.d.ts → durable-effects-7MTK_jnj.d.ts} +1 -1
- package/dist/effect/index.d.ts +151 -0
- package/dist/effect/index.js +98 -0
- package/dist/effect/index.js.map +1 -0
- package/dist/extension/index.d.ts +6 -7
- package/dist/extension/index.js +3 -2
- package/dist/extension/index.js.map +1 -1
- package/dist/flow/index.d.ts +68 -70
- package/dist/flow/index.js +32 -27
- package/dist/flow/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +6 -7
- package/dist/idempotency/index.js +4 -2
- package/dist/{index-DJWIER_3.d.ts → index-7vV8xmsD.d.ts} +1 -1
- package/dist/{index-NjrT3fL2.d.ts → index-Bz5Rh9Ga.d.ts} +19 -96
- package/dist/index-CjFDUBeq.d.ts +207 -0
- package/dist/{index-6A7dB8f6.d.ts → index-CkFYA9Y6.d.ts} +14 -16
- package/dist/{index-B5BZ-_cT.d.ts → index-Cou86-GX.d.ts} +189 -108
- package/dist/{index-TvRb1KVf.d.ts → index-DU6JpQ8G.d.ts} +1 -1
- package/dist/{index-q0hXFeZ0.d.ts → index-Xufl55W8.d.ts} +286 -215
- package/dist/{index-4vZzljCD.d.ts → index-_dOR3xqA.d.ts} +57 -45
- package/dist/index.d.ts +181 -385
- package/dist/index.js +2 -1
- package/dist/jev/index.d.ts +147 -99
- package/dist/jev/index.js +329 -233
- package/dist/jev/index.js.map +1 -1
- package/dist/{machine-CdIcN2Xm.d.ts → machine-BQzTV8On.d.ts} +171 -280
- package/dist/machine-viz/index.d.ts +5 -5
- package/dist/machine-viz/index.js +8 -4
- package/dist/machine-viz/index.js.map +1 -1
- package/dist/mem/index.d.ts +3 -4
- package/dist/mem/index.js +2 -1
- package/dist/mem/index.js.map +1 -1
- package/dist/node/index.d.ts +119 -78
- package/dist/node/index.js +52 -38
- package/dist/node/index.js.map +1 -1
- package/dist/paginate/index.d.ts +27 -33
- package/dist/paginate/index.js +6 -4
- package/dist/parity/index.d.ts +4 -5
- package/dist/parity/index.js +3 -2
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/index.d.ts +2 -2
- package/dist/pbt/index.js +2 -1
- package/dist/pbt/index.js.map +1 -1
- package/dist/persistence/index.d.ts +6 -7
- package/dist/persistence/index.js +5 -3
- package/dist/promise/index.d.ts +186 -0
- package/dist/promise/index.js +5 -0
- package/dist/promise/index.js.map +1 -0
- package/dist/protocol-ClRZBhHQ.d.ts +53 -0
- package/dist/react/index.d.ts +36 -17
- package/dist/react/index.js +27 -4
- package/dist/react/index.js.map +1 -1
- package/dist/resilience/index.d.ts +63 -455
- package/dist/resilience/index.js +37 -483
- package/dist/resilience/index.js.map +1 -1
- package/dist/{runtime-types-LWDoi943.d.ts → runtime-types-CiVpC0HN.d.ts} +131 -388
- package/dist/testing/index.d.ts +165 -15
- package/dist/testing/index.js +104 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/timing/index.d.ts +25 -22
- package/dist/timing/index.js +4 -3
- package/dist/timing/index.js.map +1 -1
- package/dist/{types-DP3f5NIl.d.ts → types-LxZxGYda.d.ts} +2 -2
- package/dist/work-queue/index.d.ts +5 -6
- package/package.json +18 -5
- package/dist/chunk-AQ6ZIMEA.js.map +0 -1
- package/dist/chunk-F2US5BW6.js +0 -72
- package/dist/chunk-F2US5BW6.js.map +0 -1
- package/dist/chunk-F7FMAWLA.js.map +0 -1
- package/dist/chunk-HUKZQX6U.js +0 -97
- package/dist/chunk-HUKZQX6U.js.map +0 -1
- package/dist/chunk-HYDWG35Q.js.map +0 -1
- package/dist/chunk-I5MQMJDW.js.map +0 -1
- package/dist/chunk-LYLTKJWM.js.map +0 -1
- package/dist/chunk-MYXFNWAJ.js.map +0 -1
- package/dist/chunk-RQULHGYH.js +0 -1804
- package/dist/chunk-RQULHGYH.js.map +0 -1
- package/dist/chunk-Z2L3LSNV.js.map +0 -1
- package/dist/index-DMCeMPMJ.d.ts +0 -272
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import { detachWork, DispatchDiscardedError, QuiescenceTimeoutError, RuntimeDiscardedError, IdentityDropNotice, checkedStep, cmdEdgeOver, Outcome, cmdEdge, isFencedStore, RuntimeDiscardNotice, DisposeTimeoutNotice, structuralHash, desiredSub } from './chunk-CH5KTOEZ.js';
|
|
2
|
+
|
|
3
|
+
// src/internal/engine/builtins.ts
|
|
4
|
+
function supervision(declared) {
|
|
5
|
+
const policy = declared === void 0 ? { strategy: "stop" } : typeof declared === "string" ? { strategy: declared } : declared;
|
|
6
|
+
return (loop) => ({
|
|
7
|
+
update: (next) => (state, msg) => {
|
|
8
|
+
try {
|
|
9
|
+
return next(state, msg);
|
|
10
|
+
} catch (reduceError) {
|
|
11
|
+
loop.report(reduceError, "reduce");
|
|
12
|
+
switch (policy.strategy) {
|
|
13
|
+
case "restart":
|
|
14
|
+
return [policy.rehydrate(state, msg, reduceError), []];
|
|
15
|
+
case "escalate":
|
|
16
|
+
throw reduceError;
|
|
17
|
+
default:
|
|
18
|
+
loop.halt();
|
|
19
|
+
throw reduceError;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function identityFilter(identity) {
|
|
26
|
+
return (loop) => {
|
|
27
|
+
if (identity === void 0) return {};
|
|
28
|
+
const misaddressed = (msg, state) => {
|
|
29
|
+
const addressed = identity.ofMsg(msg);
|
|
30
|
+
if (addressed === void 0) return false;
|
|
31
|
+
const own = identity.ofState(state);
|
|
32
|
+
if (own === void 0) return false;
|
|
33
|
+
return structuralHash(addressed) !== structuralHash(own);
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
update: (next) => (state, msg) => {
|
|
37
|
+
if (!misaddressed(msg, state)) return next(state, msg);
|
|
38
|
+
loop.report(new IdentityDropNotice(msg.type), "identity-drop");
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function devChecks() {
|
|
45
|
+
return () => ({
|
|
46
|
+
update: (next) => (state, msg) => checkedStep(state, msg, next)
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function cmdDefinitions(defs, clock) {
|
|
50
|
+
return (loop) => {
|
|
51
|
+
const settle = cmdEdgeOver(defs, clock);
|
|
52
|
+
const defined = new Set(defs.map((d) => d.cmdType));
|
|
53
|
+
return {
|
|
54
|
+
interpret: (next) => async (cmd) => {
|
|
55
|
+
const type = cmd.type;
|
|
56
|
+
try {
|
|
57
|
+
return settle(cmd, await next(cmd));
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (!defined.has(type)) throw err;
|
|
60
|
+
loop.report(err, "interpret");
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
ctx: { ok: Outcome.ok, err: Outcome.err, [cmdEdge]: settle }
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function fencing() {
|
|
69
|
+
return () => ({
|
|
70
|
+
store: (store) => {
|
|
71
|
+
if (!isFencedStore(store)) return store;
|
|
72
|
+
let version = 0;
|
|
73
|
+
return {
|
|
74
|
+
async load() {
|
|
75
|
+
const read = await store.loadFenced();
|
|
76
|
+
version = read.version;
|
|
77
|
+
return read.raw;
|
|
78
|
+
},
|
|
79
|
+
async save(state) {
|
|
80
|
+
version = await store.saveFenced(state, version);
|
|
81
|
+
},
|
|
82
|
+
migrate: (raw) => store.migrate(raw)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function ports() {
|
|
88
|
+
return (loop) => {
|
|
89
|
+
const registry = /* @__PURE__ */ new Map();
|
|
90
|
+
function emit(port, value) {
|
|
91
|
+
const subscribers = registry.get(port);
|
|
92
|
+
if (!subscribers || subscribers.size === 0) return;
|
|
93
|
+
loop.fanout(subscribers, "port-emit", (listener) => listener(value));
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
ctx: { emit },
|
|
97
|
+
handle: {
|
|
98
|
+
subscribePort(port, listener) {
|
|
99
|
+
const key = port;
|
|
100
|
+
const bucket = registry.get(key) ?? /* @__PURE__ */ new Set();
|
|
101
|
+
registry.set(key, bucket);
|
|
102
|
+
const erased = listener;
|
|
103
|
+
bucket.add(erased);
|
|
104
|
+
return () => {
|
|
105
|
+
bucket.delete(erased);
|
|
106
|
+
if (bucket.size === 0 && registry.get(key) === bucket)
|
|
107
|
+
registry.delete(key);
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
emitPort: emit
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function observation() {
|
|
116
|
+
return (loop) => {
|
|
117
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
118
|
+
const observers = /* @__PURE__ */ new Set();
|
|
119
|
+
const bootHandlers = /* @__PURE__ */ new Set();
|
|
120
|
+
let booted = false;
|
|
121
|
+
return {
|
|
122
|
+
commit(msg, state) {
|
|
123
|
+
loop.fanout(listeners, "listener", (listener) => listener());
|
|
124
|
+
if (msg === void 0) {
|
|
125
|
+
booted = true;
|
|
126
|
+
loop.fanout(bootHandlers, "boot", (handler) => handler(state));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
loop.fanout(observers, "observer", (observer) => observer(msg, state));
|
|
130
|
+
},
|
|
131
|
+
handle: {
|
|
132
|
+
subscribe: subscribeTo(listeners),
|
|
133
|
+
observe: subscribeTo(observers),
|
|
134
|
+
onBoot(handler) {
|
|
135
|
+
const state = loop.state();
|
|
136
|
+
if (booted && state !== void 0) {
|
|
137
|
+
loop.fanout([handler], "boot", (h) => h(state));
|
|
138
|
+
return () => {
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return subscribeTo(bootHandlers)(handler);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function subscribeTo(set) {
|
|
148
|
+
return (item) => {
|
|
149
|
+
set.add(item);
|
|
150
|
+
return () => {
|
|
151
|
+
set.delete(item);
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function semanticEvents(project) {
|
|
156
|
+
return (loop) => {
|
|
157
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
158
|
+
return {
|
|
159
|
+
commit(msg, state) {
|
|
160
|
+
if (msg === void 0 || project === void 0 || handlers.size === 0)
|
|
161
|
+
return;
|
|
162
|
+
let events;
|
|
163
|
+
try {
|
|
164
|
+
events = project(msg, state);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
loop.report(err, "event");
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
for (const event of events) {
|
|
170
|
+
const bucket = handlers.get(event.type);
|
|
171
|
+
if (bucket === void 0) continue;
|
|
172
|
+
loop.fanout(bucket, "event", (handler) => handler(event));
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
handle: {
|
|
176
|
+
on(type, handler) {
|
|
177
|
+
const bucket = handlers.get(type) ?? /* @__PURE__ */ new Set();
|
|
178
|
+
handlers.set(type, bucket);
|
|
179
|
+
bucket.add(handler);
|
|
180
|
+
return () => {
|
|
181
|
+
bucket.delete(handler);
|
|
182
|
+
if (bucket.size === 0 && handlers.get(type) === bucket)
|
|
183
|
+
handlers.delete(type);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function terminality(isTerminal) {
|
|
191
|
+
const holds = isTerminal ?? (() => false);
|
|
192
|
+
return (loop) => {
|
|
193
|
+
const waiters = /* @__PURE__ */ new Set();
|
|
194
|
+
return {
|
|
195
|
+
commit(_msg, state) {
|
|
196
|
+
if (waiters.size === 0 || !holds(state)) return;
|
|
197
|
+
const parked = [...waiters];
|
|
198
|
+
waiters.clear();
|
|
199
|
+
for (const resolve of parked) resolve(state);
|
|
200
|
+
},
|
|
201
|
+
handle: {
|
|
202
|
+
result() {
|
|
203
|
+
const current = loop.state();
|
|
204
|
+
return holds(current) ? current : void 0;
|
|
205
|
+
},
|
|
206
|
+
done() {
|
|
207
|
+
const current = loop.state();
|
|
208
|
+
if (holds(current)) return Promise.resolve(current);
|
|
209
|
+
return new Promise((resolve) => {
|
|
210
|
+
waiters.add(resolve);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function telemetry(sink, clock) {
|
|
218
|
+
return (loop) => {
|
|
219
|
+
if (sink === void 0) return {};
|
|
220
|
+
let seq = 0;
|
|
221
|
+
return {
|
|
222
|
+
commit(msg) {
|
|
223
|
+
if (msg === void 0) return;
|
|
224
|
+
seq += 1;
|
|
225
|
+
const event = { seq, msgType: msg.type, at: clock() };
|
|
226
|
+
try {
|
|
227
|
+
Promise.resolve(sink(event)).catch(
|
|
228
|
+
(err) => loop.report(err, "observer")
|
|
229
|
+
);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
loop.report(err, "observer");
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function builtinExtensions(machine, opts) {
|
|
238
|
+
const clock = opts.clock ?? Date.now;
|
|
239
|
+
return [
|
|
240
|
+
supervision(opts.supervision),
|
|
241
|
+
identityFilter(machine.identity),
|
|
242
|
+
devChecks(),
|
|
243
|
+
cmdDefinitions(machine.cmds ?? [], clock),
|
|
244
|
+
fencing(),
|
|
245
|
+
ports(),
|
|
246
|
+
observation(),
|
|
247
|
+
semanticEvents(opts.events),
|
|
248
|
+
terminality(opts.terminal),
|
|
249
|
+
telemetry(opts.telemetry, clock)
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/internal/engine/loop.ts
|
|
254
|
+
var liveWork = /* @__PURE__ */ Symbol("demlik-tea.liveWork");
|
|
255
|
+
function defaultOnError(error, _context) {
|
|
256
|
+
if (error instanceof RuntimeDiscardNotice) {
|
|
257
|
+
console.warn(error);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
setTimeout(() => {
|
|
261
|
+
throw error;
|
|
262
|
+
}, 0);
|
|
263
|
+
}
|
|
264
|
+
function startLoop(config) {
|
|
265
|
+
const { idleCap, disposeTimeoutMs } = config;
|
|
266
|
+
const onError = config.onError ?? defaultOnError;
|
|
267
|
+
function report(error, phase) {
|
|
268
|
+
const context = { phase };
|
|
269
|
+
try {
|
|
270
|
+
onError(error, context);
|
|
271
|
+
} catch (sinkError) {
|
|
272
|
+
defaultOnError(sinkError);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function fanout(items, phase, fn) {
|
|
276
|
+
for (const item of items) {
|
|
277
|
+
try {
|
|
278
|
+
fn(item);
|
|
279
|
+
} catch (err) {
|
|
280
|
+
report(err, phase);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
let state;
|
|
285
|
+
let bootError = null;
|
|
286
|
+
let gate = "open";
|
|
287
|
+
let inFlightCmds = 0;
|
|
288
|
+
const services = {
|
|
289
|
+
report,
|
|
290
|
+
fanout,
|
|
291
|
+
halt() {
|
|
292
|
+
gate = "closed";
|
|
293
|
+
},
|
|
294
|
+
state: () => state
|
|
295
|
+
};
|
|
296
|
+
const extensions = config.extensions.map((make) => make(services));
|
|
297
|
+
const update = extensions.reduceRight(
|
|
298
|
+
(next, ext) => ext.update ? ext.update(next) : next,
|
|
299
|
+
config.reduce
|
|
300
|
+
);
|
|
301
|
+
const interpret = extensions.reduceRight(
|
|
302
|
+
(next, ext) => ext.interpret ? ext.interpret(next) : next,
|
|
303
|
+
callHandler
|
|
304
|
+
);
|
|
305
|
+
const store = config.store === void 0 ? void 0 : extensions.reduceRight(
|
|
306
|
+
(inner, ext) => ext.store ? ext.store(inner) : inner,
|
|
307
|
+
config.store
|
|
308
|
+
);
|
|
309
|
+
const commitCallbacks = extensions.flatMap(
|
|
310
|
+
(ext) => ext.commit ? [ext.commit] : []
|
|
311
|
+
);
|
|
312
|
+
const handlerCtx = Object.assign(
|
|
313
|
+
{},
|
|
314
|
+
config.ctx,
|
|
315
|
+
...extensions.map((ext) => ext.ctx ?? {}),
|
|
316
|
+
{ [detachWork]: detachInFlight }
|
|
317
|
+
);
|
|
318
|
+
let tail = Promise.resolve();
|
|
319
|
+
const subRegistry = /* @__PURE__ */ new Map();
|
|
320
|
+
function stopSubs(ids) {
|
|
321
|
+
for (const id of ids) {
|
|
322
|
+
const running = subRegistry.get(id);
|
|
323
|
+
if (running === void 0) continue;
|
|
324
|
+
try {
|
|
325
|
+
trackDisposal(running.dispose());
|
|
326
|
+
} catch (err) {
|
|
327
|
+
report(err, "sub-cleanup");
|
|
328
|
+
}
|
|
329
|
+
subRegistry.delete(id);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const pendingDisposals = /* @__PURE__ */ new Set();
|
|
333
|
+
function trackDisposal(result) {
|
|
334
|
+
if (!(result instanceof Promise)) return;
|
|
335
|
+
const settling = result.catch((err) => {
|
|
336
|
+
report(err, "sub-cleanup");
|
|
337
|
+
}).finally(() => {
|
|
338
|
+
pendingDisposals.delete(settling);
|
|
339
|
+
});
|
|
340
|
+
pendingDisposals.add(settling);
|
|
341
|
+
}
|
|
342
|
+
async function drainDisposals() {
|
|
343
|
+
if (pendingDisposals.size === 0) return;
|
|
344
|
+
const outstanding = [...pendingDisposals];
|
|
345
|
+
let timer;
|
|
346
|
+
try {
|
|
347
|
+
const outcome = await Promise.race([
|
|
348
|
+
Promise.all(outstanding).then(() => "settled"),
|
|
349
|
+
new Promise((resolve) => {
|
|
350
|
+
timer = setTimeout(() => resolve("timeout"), disposeTimeoutMs);
|
|
351
|
+
})
|
|
352
|
+
]);
|
|
353
|
+
if (outcome === "timeout") {
|
|
354
|
+
report(
|
|
355
|
+
new DisposeTimeoutNotice(outstanding.length, disposeTimeoutMs),
|
|
356
|
+
"discard"
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
} finally {
|
|
360
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function reconcileSubs() {
|
|
364
|
+
let firstError = null;
|
|
365
|
+
const remember = (err) => {
|
|
366
|
+
if (firstError === null) firstError = err;
|
|
367
|
+
};
|
|
368
|
+
const desired = /* @__PURE__ */ new Map();
|
|
369
|
+
const unreadTypes = /* @__PURE__ */ new Set();
|
|
370
|
+
for (const entry of config.subs) {
|
|
371
|
+
try {
|
|
372
|
+
const sub = desiredSub(entry, state);
|
|
373
|
+
if (sub !== null) desired.set(sub.id, sub);
|
|
374
|
+
} catch (err) {
|
|
375
|
+
remember(err);
|
|
376
|
+
unreadTypes.add(entry.type);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
stopSubs(
|
|
380
|
+
[...subRegistry].flatMap(
|
|
381
|
+
([id, running]) => desired.has(id) || unreadTypes.has(running.type) ? [] : [id]
|
|
382
|
+
)
|
|
383
|
+
);
|
|
384
|
+
for (const [id, sub] of desired) {
|
|
385
|
+
if (subRegistry.has(id)) continue;
|
|
386
|
+
const runner = config.runnerFor(sub.type);
|
|
387
|
+
if (runner === void 0) {
|
|
388
|
+
remember(
|
|
389
|
+
new Error(
|
|
390
|
+
`@demlik/tea: no subscribe runner for Sub type "${sub.type}". Pass one to run in \`subscribe\`.`
|
|
391
|
+
)
|
|
392
|
+
);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const dispose = runner(sub, config.ctx, dispatchUnawaited);
|
|
397
|
+
subRegistry.set(id, { type: sub.type, dispose });
|
|
398
|
+
} catch (err) {
|
|
399
|
+
remember(err);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (firstError !== null) throw firstError;
|
|
403
|
+
}
|
|
404
|
+
function dispatchUnawaited(msg) {
|
|
405
|
+
enqueueDispatch(msg).catch(reportUndelivered);
|
|
406
|
+
}
|
|
407
|
+
function reportUndelivered(error) {
|
|
408
|
+
report(
|
|
409
|
+
error,
|
|
410
|
+
error instanceof DispatchDiscardedError ? "discard" : "follow-up"
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
function trackInFlight(work) {
|
|
414
|
+
inFlightCmds++;
|
|
415
|
+
return Promise.resolve(work).finally(() => {
|
|
416
|
+
inFlightCmds--;
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function detachInFlight(work) {
|
|
420
|
+
const tracked = trackInFlight(work);
|
|
421
|
+
tracked.catch(reportUndelivered);
|
|
422
|
+
tail = tail.then(async () => {
|
|
423
|
+
await tracked;
|
|
424
|
+
}).catch(() => {
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function callHandler(cmd) {
|
|
428
|
+
const handler = config.handlerFor(cmd.type);
|
|
429
|
+
if (handler === void 0) return Promise.resolve(void 0);
|
|
430
|
+
return trackInFlight(handler(cmd, handlerCtx, dispatchUnawaited));
|
|
431
|
+
}
|
|
432
|
+
async function runCmds(cmds) {
|
|
433
|
+
for (const cmd of cmds) {
|
|
434
|
+
const follow = await interpret(cmd);
|
|
435
|
+
if (follow !== void 0 && follow !== null) {
|
|
436
|
+
enqueueDispatch(follow).catch(reportUndelivered);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async function commit(next, msg, cmds) {
|
|
441
|
+
state = next;
|
|
442
|
+
if (store) await store.save(next);
|
|
443
|
+
reconcileSubs();
|
|
444
|
+
await runCmds(cmds);
|
|
445
|
+
for (const callback of commitCallbacks) callback(msg, next);
|
|
446
|
+
}
|
|
447
|
+
async function stepDispatch(msg) {
|
|
448
|
+
if (state === void 0) {
|
|
449
|
+
throw new Error("@demlik/tea: runtime not booted");
|
|
450
|
+
}
|
|
451
|
+
const transition = update(state, msg);
|
|
452
|
+
if (transition === null) return;
|
|
453
|
+
await commit(transition[0], msg, transition[1]);
|
|
454
|
+
}
|
|
455
|
+
const storeless = store === void 0 ? config.init(null, config.ctx) : null;
|
|
456
|
+
if (storeless !== null) state = storeless[0];
|
|
457
|
+
async function boot() {
|
|
458
|
+
const [initial, cmds] = storeless ?? config.init(
|
|
459
|
+
store.migrate(await store.load()),
|
|
460
|
+
config.ctx
|
|
461
|
+
);
|
|
462
|
+
await commit(initial, void 0, cmds);
|
|
463
|
+
}
|
|
464
|
+
function enqueueDispatch(msg) {
|
|
465
|
+
if (gate !== "open") {
|
|
466
|
+
return Promise.reject(
|
|
467
|
+
gate === "draining" ? new DispatchDiscardedError(msg.type) : new Error("@demlik/tea: runtime stopped")
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
const next = tail.then(() => {
|
|
471
|
+
if (bootError !== null) throw bootError;
|
|
472
|
+
return stepDispatch(msg);
|
|
473
|
+
});
|
|
474
|
+
tail = next.catch(() => {
|
|
475
|
+
});
|
|
476
|
+
return next;
|
|
477
|
+
}
|
|
478
|
+
async function drainToQuiescence() {
|
|
479
|
+
for (let i = 0; i < idleCap; i++) {
|
|
480
|
+
const observed = tail;
|
|
481
|
+
await observed;
|
|
482
|
+
if (tail === observed) return;
|
|
483
|
+
}
|
|
484
|
+
throw new QuiescenceTimeoutError(idleCap);
|
|
485
|
+
}
|
|
486
|
+
async function dispatchToQuiescence(msg, opts) {
|
|
487
|
+
await enqueueDispatch(msg);
|
|
488
|
+
if (opts?.settle === "once") return;
|
|
489
|
+
await drainToQuiescence();
|
|
490
|
+
}
|
|
491
|
+
const bootPromise = boot();
|
|
492
|
+
tail = bootPromise.catch((err) => {
|
|
493
|
+
bootError = err;
|
|
494
|
+
});
|
|
495
|
+
const core = {
|
|
496
|
+
[liveWork]: () => ({ subs: subRegistry.size, cmds: inFlightCmds }),
|
|
497
|
+
dispatch: dispatchToQuiescence,
|
|
498
|
+
dispatchOnce: enqueueDispatch,
|
|
499
|
+
getState() {
|
|
500
|
+
return state;
|
|
501
|
+
},
|
|
502
|
+
// A failed boot never hands out a handle. The forward reference resolves
|
|
503
|
+
// at `.then` time, after `handle` below is built.
|
|
504
|
+
ready: bootPromise.then(() => handle),
|
|
505
|
+
idle: drainToQuiescence,
|
|
506
|
+
async stop() {
|
|
507
|
+
if (gate === "open") gate = "draining";
|
|
508
|
+
if (inFlightCmds > 0) {
|
|
509
|
+
report(new RuntimeDiscardedError(inFlightCmds), "discard");
|
|
510
|
+
}
|
|
511
|
+
await tail;
|
|
512
|
+
gate = "closed";
|
|
513
|
+
stopSubs([...subRegistry.keys()]);
|
|
514
|
+
await drainDisposals();
|
|
515
|
+
if (store && state !== void 0 && bootError === null) {
|
|
516
|
+
try {
|
|
517
|
+
await store.save(state);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
report(error, "stop-save");
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
const handle = Object.assign(
|
|
525
|
+
core,
|
|
526
|
+
...extensions.map((ext) => ext.handle ?? {})
|
|
527
|
+
);
|
|
528
|
+
return handle;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export { builtinExtensions, liveWork, startLoop };
|
|
532
|
+
//# sourceMappingURL=chunk-5XU6ZJO2.js.map
|
|
533
|
+
//# sourceMappingURL=chunk-5XU6ZJO2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/engine/builtins.ts","../src/internal/engine/loop.ts"],"names":[],"mappings":"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","file":"chunk-5XU6ZJO2.js","sourcesContent":["/**\n * tea's built-ins, each one an {@link Extension} of the core loop (`./loop`,\n * #280). None is a public plugin API (#268): each engine's `run` builds them\n * from its options through {@link builtinExtensions}, which fixes their order.\n */\n\nimport type { AnyCmdDef, Identity, Port } from \"../../pure/core\";\nimport {\n checkedStep,\n cmdEdge,\n cmdEdgeOver,\n Outcome,\n structuralHash,\n} from \"../../pure/core\";\nimport type {\n Store,\n Supervision,\n TelemetryEvent,\n TelemetrySink,\n} from \"../../runtime-types\";\nimport { IdentityDropNotice, isFencedStore } from \"../../runtime-types\";\nimport type { Extension, ExtensionFactory, LoopServices } from \"./loop\";\n\ntype AnyExtension<S, M, C> = ExtensionFactory<S, M, C>;\n\n// === update middleware ===\n\n/**\n * Supervision: a throw from the fold (the reducer, or any update middleware\n * inside this one) is reported under `\"reduce\"` for every strategy, then\n * `restart` commits the host's rehydrated State with no Cmds, `escalate`\n * rethrows with the runtime still live, and `stop` (the default) halts the gate\n * and rethrows, so THIS dispatch rejects too.\n */\nexport function supervision<S, M extends { type: string }, C>(\n declared: Supervision<S, M> | undefined,\n): AnyExtension<S, M, C> {\n const policy =\n declared === undefined\n ? ({ strategy: \"stop\" } as const)\n : typeof declared === \"string\"\n ? ({ strategy: declared } as const)\n : declared;\n return (loop) => ({\n update: (next) => (state, msg) => {\n try {\n return next(state, msg);\n } catch (reduceError) {\n loop.report(reduceError, \"reduce\");\n switch (policy.strategy) {\n case \"restart\":\n // A throw inside `rehydrate` is NOT caught — it reaches the caller.\n return [policy.rehydrate(state, msg, reduceError), []];\n case \"escalate\":\n throw reduceError;\n default:\n loop.halt();\n throw reduceError;\n }\n }\n },\n });\n}\n\n/**\n * The instance-identity filter: drop a Msg addressed to a DIFFERENT instance\n * than the one this State owns, before the reducer sees it. A Msg with no\n * identity, or a State that has none yet, always proceeds. The drop is no\n * transition, and it RESOLVES the dispatch — a mis-addressed Msg is not the\n * caller's error — so it is reported as an `IdentityDropNotice` (warn by\n * default) or the caller could not tell \"applied\" from \"discarded\".\n *\n * Sits INSIDE {@link supervision}, so a throwing `ofMsg` / `ofState` is reported\n * and supervised exactly like a reducer throw (spike #264 run 3).\n */\nexport function identityFilter<S, M extends { type: string }, C>(\n identity: Identity<S, M> | undefined,\n): AnyExtension<S, M, C> {\n return (loop) => {\n if (identity === undefined) return {};\n const misaddressed = (msg: M, state: S): boolean => {\n const addressed = identity.ofMsg(msg);\n if (addressed === undefined) return false;\n const own = identity.ofState(state);\n if (own === undefined) return false;\n return structuralHash(addressed) !== structuralHash(own);\n };\n return {\n update: (next) => (state, msg) => {\n if (!misaddressed(msg, state)) return next(state, msg);\n loop.report(new IdentityDropNotice(msg.type), \"identity-drop\");\n return null;\n },\n };\n };\n}\n\n/**\n * The dev pre/post pair around the reducer (`checkedStep`): freeze the input\n * State so an in-place mutation trips, and check the returned `[state, cmds]`\n * shape. Both compile out of production. Innermost, so it sees exactly what the\n * reducer saw.\n */\nexport function devChecks<S, M extends { type: string }, C>(): AnyExtension<\n S,\n M,\n C\n> {\n return () => ({\n update: (next) => (state, msg) => checkedStep(state, msg, next),\n });\n}\n\n// === interpret middleware ===\n\n/**\n * The `Cmd.define` edge (ADR 0021): a defined Cmd's handler return is minted\n * into its `_ok` / `_err` Msg, its `ok` value parsed against the def's schema\n * and stamped with `at` from `clock`. A defined Cmd's handler failing outside\n * its declared channel goes to the sink under `\"interpret\"`; a hand-written\n * Cmd's handler throw keeps rejecting the dispatch.\n *\n * The same edge rides on ctx under `cmdEdge`, beside the `ok` / `err` builders,\n * so a handler that runs a base handler inside its own settles the base's\n * outcome through this one edge (#66).\n */\nexport function cmdDefinitions<S, M extends { type: string }, C>(\n defs: readonly AnyCmdDef[],\n clock: () => number,\n): AnyExtension<S, M, C> {\n return (loop) => {\n const settle = cmdEdgeOver(defs, clock);\n const defined = new Set(defs.map((d) => d.cmdType));\n return {\n interpret: (next) => async (cmd) => {\n const type = (cmd as { type: string }).type;\n try {\n return settle(cmd as { type: string }, await next(cmd));\n } catch (err) {\n if (!defined.has(type)) throw err;\n loop.report(err, \"interpret\");\n return undefined;\n }\n },\n ctx: { ok: Outcome.ok, err: Outcome.err, [cmdEdge]: settle },\n };\n };\n}\n\n// === store wrapper ===\n\n/**\n * Fencing is a property of the store the caller handed `run`, never a flag\n * (#143). A fenced store is read with its version at boot and every save is a\n * compare-and-swap against the last version this run saw, so a second writer\n * that took over is refused with `StoreConflictError` at this run's next save —\n * before that transition's Cmds run. A plain store passes through unchanged.\n */\nexport function fencing<S, M, C>(): AnyExtension<S, M, C> {\n return () => ({\n store: (store: Store<S>): Store<S> => {\n if (!isFencedStore(store)) return store;\n let version = 0;\n return {\n async load() {\n const read = await store.loadFenced();\n version = read.version;\n return read.raw;\n },\n async save(state) {\n version = await store.saveFenced(state, version);\n },\n migrate: (raw) => store.migrate(raw),\n };\n },\n });\n}\n\n// === ctx + handle: ports ===\n\n/**\n * Ports (`definePort`): `ctx.emit(port, value)` from a handler and\n * `emitPort` / `subscribePort` on the handle. Fanout is synchronous and\n * throw-isolated under `\"port-emit\"`; no subscribers is a no-op.\n */\nexport function ports<S, M, C>(): AnyExtension<S, M, C> {\n return (loop) => {\n const registry = new Map<Port<unknown>, Set<(value: unknown) => void>>();\n function emit<T>(port: Port<T>, value: T): void {\n const subscribers = registry.get(port as Port<unknown>);\n if (!subscribers || subscribers.size === 0) return;\n loop.fanout(subscribers, \"port-emit\", (listener) => listener(value));\n }\n return {\n ctx: { emit },\n handle: {\n subscribePort<T>(\n port: Port<T>,\n listener: (value: T) => void,\n ): () => void {\n const key = port as Port<unknown>;\n const bucket = registry.get(key) ?? new Set();\n registry.set(key, bucket);\n const erased = listener as (value: unknown) => void;\n bucket.add(erased);\n return () => {\n bucket.delete(erased);\n if (bucket.size === 0 && registry.get(key) === bucket)\n registry.delete(key);\n };\n },\n emitPort: emit,\n },\n };\n };\n}\n\n// === commit observers ===\n\n/**\n * `subscribe` (zero-arg change notifiers), then `observe` (every applied\n * transition's `(msg, state)`) or, at boot, `onBoot` (the initial State, once;\n * a handler registered after boot fires at once). Each fanout is\n * throw-isolated under its own phase.\n */\nexport function observation<S, M, C>(): AnyExtension<S, M, C> {\n return (loop) => {\n const listeners = new Set<() => void>();\n const observers = new Set<(msg: M, state: S) => void>();\n const bootHandlers = new Set<(state: S) => void>();\n let booted = false;\n return {\n commit(msg, state) {\n loop.fanout(listeners, \"listener\", (listener) => listener());\n if (msg === undefined) {\n booted = true;\n loop.fanout(bootHandlers, \"boot\", (handler) => handler(state));\n return;\n }\n loop.fanout(observers, \"observer\", (observer) => observer(msg, state));\n },\n handle: {\n subscribe: subscribeTo(listeners),\n observe: subscribeTo(observers),\n onBoot(handler: (state: S) => void): () => void {\n const state = loop.state();\n if (booted && state !== undefined) {\n loop.fanout([handler], \"boot\", (h) => h(state));\n return () => {};\n }\n return subscribeTo(bootHandlers)(handler);\n },\n },\n };\n };\n}\n\nfunction subscribeTo<T>(set: Set<T>): (item: T) => () => void {\n return (item) => {\n set.add(item);\n return () => {\n set.delete(item);\n };\n };\n}\n\n/**\n * The semantic event channel: `project` maps each APPLIED transition to zero or\n * more public events, and `on(type, handler)` receives only its own type. The\n * machine's private Msg names never reach `on`. A throw in the projector or a\n * handler is isolated under `\"event\"`. Boot projects nothing.\n */\nexport function semanticEvents<S, M, C, E extends { type: string }>(\n project: ((msg: M, state: S) => readonly E[]) | undefined,\n): AnyExtension<S, M, C> {\n return (loop) => {\n const handlers = new Map<string, Set<(event: E) => void>>();\n return {\n commit(msg, state) {\n if (msg === undefined || project === undefined || handlers.size === 0)\n return;\n let events: readonly E[];\n try {\n events = project(msg, state);\n } catch (err) {\n loop.report(err, \"event\");\n return;\n }\n for (const event of events) {\n const bucket = handlers.get(event.type);\n if (bucket === undefined) continue;\n loop.fanout(bucket, \"event\", (handler) => handler(event));\n }\n },\n handle: {\n on(type: string, handler: (event: E) => void): () => void {\n const bucket = handlers.get(type) ?? new Set();\n handlers.set(type, bucket);\n bucket.add(handler);\n return () => {\n bucket.delete(handler);\n if (bucket.size === 0 && handlers.get(type) === bucket)\n handlers.delete(type);\n };\n },\n },\n };\n };\n}\n\n/**\n * Run terminality: `result()` reads the State when `isTerminal` holds for it,\n * and `done()` resolves on the first commit (boot included — a rehydrated run\n * can land terminal) where it does. No predicate → never terminal.\n */\nexport function terminality<S, M, C>(\n isTerminal: ((state: S) => boolean) | undefined,\n): AnyExtension<S, M, C> {\n const holds = isTerminal ?? (() => false);\n return (loop) => {\n const waiters = new Set<(state: S) => void>();\n return {\n commit(_msg, state) {\n if (waiters.size === 0 || !holds(state)) return;\n const parked = [...waiters];\n waiters.clear();\n for (const resolve of parked) resolve(state);\n },\n handle: {\n result(): S | undefined {\n const current = loop.state() as S;\n return holds(current) ? current : undefined;\n },\n done(): Promise<S> {\n const current = loop.state() as S;\n if (holds(current)) return Promise.resolve(current);\n return new Promise<S>((resolve) => {\n waiters.add(resolve);\n });\n },\n },\n };\n };\n}\n\n/**\n * Telemetry (#268: `withTelemetry` moved inside tea). Every applied transition\n * hands the sink `{ seq, msgType, at }` — `seq` counts this run's applied\n * transitions from 1, `at` reads `clock`. Fire-and-forget: the loop never waits\n * on the sink, and a sink that throws or rejects reaches `onError` under\n * `\"observer\"` without touching the run.\n */\nexport function telemetry<S, M extends { type: string }, C>(\n sink: TelemetrySink | undefined,\n clock: () => number,\n): AnyExtension<S, M, C> {\n return (loop: LoopServices<S>): Extension<S, M, C> => {\n if (sink === undefined) return {};\n let seq = 0;\n return {\n commit(msg) {\n if (msg === undefined) return;\n seq += 1;\n const event: TelemetryEvent = { seq, msgType: msg.type, at: clock() };\n try {\n Promise.resolve(sink(event)).catch((err: unknown) =>\n loop.report(err, \"observer\"),\n );\n } catch (err) {\n loop.report(err, \"observer\");\n }\n },\n };\n };\n}\n\n// === assembly: the one order both engines run the built-ins in ===\n\n/** The `run` options the built-ins read. Every engine's `run` accepts them. */\nexport interface BuiltinOptions<\n S,\n M extends { type: string },\n E extends { type: string },\n> {\n readonly clock?: () => number;\n readonly events?: (msg: M, state: S) => readonly E[];\n readonly supervision?: Supervision<S, M>;\n readonly terminal?: (state: S) => boolean;\n readonly telemetry?: TelemetrySink;\n}\n\n/**\n * The built-ins, in the one order that gives them their meaning. Every engine\n * builds its loop from this list, so a machine means the same thing on each.\n *\n * Update middleware nests first-outermost: supervision wraps the identity\n * filter (a throwing `ofMsg` is supervised like a reducer throw — spike #264\n * run 3), which wraps the dev checks around the reducer itself. Commit\n * callbacks run in order: change listeners, then `observe` / `onBoot`, then\n * semantic events, then `done()` waiters, then telemetry.\n */\nexport function builtinExtensions<\n S,\n M extends { type: string },\n C,\n E extends { type: string },\n>(\n machine: {\n readonly identity?: Identity<S, M>;\n readonly cmds?: readonly AnyCmdDef[];\n },\n opts: BuiltinOptions<S, M, E>,\n): readonly ExtensionFactory<S, M, C>[] {\n const clock = opts.clock ?? Date.now;\n return [\n supervision(opts.supervision),\n identityFilter(machine.identity),\n devChecks(),\n cmdDefinitions(machine.cmds ?? [], clock),\n fencing(),\n ports(),\n observation(),\n semanticEvents(opts.events),\n terminality(opts.terminal),\n telemetry(opts.telemetry, clock),\n ];\n}\n","/**\n * The core loop both engines run (#280, spike #264). It does five things and\n * nothing else: it keeps one serial tail, folds a Msg into a transition, saves\n * before any effect, reconciles the running Subs, and runs the transition's\n * Cmds, dispatching what they return. It has no special case for any built-in.\n *\n * One loop, not one per engine (#283). The loop calls a handler and a runner\n * through erased signatures and awaits a Promise either way, so an engine's\n * own types never pass through it. The Effect engine turns each handler and\n * runner into a fiber at its own edge, run with the services the caller\n * provided, so Effect services (`R`) reach every handler without the loop\n * carrying a type slot for them — the gap spike #260's generic loop left open.\n * Sharing the loop is also what makes the two engines' traces identical: the\n * fold, the save order and the built-ins are the same code.\n *\n * Everything else `run` offers is an {@link Extension} built on five points:\n *\n * 1. `update` — middleware over the fold. Returning `null` means \"no\n * transition\": no save, no effects, no commit.\n * 2. `interpret` — middleware over one Cmd's handler call; it maps what the\n * handler returned to the Msg to dispatch, if any.\n * 3. `store` — a wrapper over the store the caller handed `run`.\n * 4. `commit` — called after a transition's effects, with the Msg and the\n * committed State. Boot passes `undefined` for the Msg.\n * 5. `ctx` — keys added to the ctx every Cmd handler receives, so a\n * handler that runs another handler inside its own can reach\n * the same machinery the loop applies to it.\n *\n * An extension may also add methods to the run handle (`handle`). Array order\n * is nesting order: the first extension's middleware is the outermost, and\n * commit callbacks run first to last. `builtinExtensions` (`./builtins`) fixes\n * that order for every engine; it is not a user-facing plugin API (#268).\n *\n * `stop()` is the core's own lifecycle, because the gate it drives is what every\n * dispatch passes through: open → draining → closed, never backwards.\n */\n\nimport type { Dispose, Sub, SubEntry } from \"../../pure/core\";\nimport { desiredSub, detachWork } from \"../../pure/core\";\nimport type {\n DispatchSettle,\n OnError,\n RuntimeErrorContext,\n RuntimeErrorPhase,\n Store,\n} from \"../../runtime-types\";\nimport {\n DispatchDiscardedError,\n DisposeTimeoutNotice,\n QuiescenceTimeoutError,\n RuntimeDiscardedError,\n RuntimeDiscardNotice,\n} from \"../../runtime-types\";\n\n// === The extension points ===\n\n/** A fold's result: the next State and the Cmds it emits. */\nexport type Transition<S, C> = readonly [S, readonly C[]];\n\n/** One fold step. `null` means the Msg produced no transition. */\nexport type Step<S, M, C> = (state: S, msg: M) => Transition<S, C> | null;\n\n/** One Cmd's handler call; resolves to the Msg to dispatch, or nothing. */\nexport type InterpretStep<C> = (cmd: C) => Promise<unknown>;\n\n/** What the loop lends an extension. */\nexport interface LoopServices<S> {\n /** Route a failure with no caller to the `onError` sink. */\n report(error: unknown, phase: RuntimeErrorPhase): void;\n /** Call `fn` for each item, routing every throw to the sink under `phase`. */\n fanout<T>(\n items: Iterable<T>,\n phase: RuntimeErrorPhase,\n fn: (item: T) => void,\n ): void;\n /** Close the gate for good: every later dispatch is refused. */\n halt(): void;\n /** The committed State, or `undefined` before boot has produced one. */\n state(): S | undefined;\n}\n\n/** One built-in, as the loop sees it. Every member is optional. */\nexport interface Extension<S, M, C> {\n readonly update?: (next: Step<S, M, C>) => Step<S, M, C>;\n readonly interpret?: (next: InterpretStep<C>) => InterpretStep<C>;\n readonly store?: (store: Store<S>) => Store<S>;\n readonly commit?: (msg: M | undefined, state: S) => void;\n readonly ctx?: object;\n readonly handle?: object;\n}\n\n/** An extension is built once per run, from the loop's services. */\nexport type ExtensionFactory<S, M, C> = (\n loop: LoopServices<S>,\n) => Extension<S, M, C>;\n\n/** A Cmd handler as the loop calls it. */\nexport type LoopHandler<M> = (\n cmd: never,\n ctx: unknown,\n dispatch: (msg: M) => void,\n) => unknown;\n\n/** A Sub runner as the loop calls it. */\nexport type LoopRunner<Ctx, M> = (\n sub: Sub,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n) => Dispose;\n\n/** Everything the loop needs from `run`. */\nexport interface LoopConfig<S, M extends { type: string }, C, Ctx> {\n readonly init: (loaded: S | null, ctx: Ctx) => Transition<S, C>;\n /** The innermost fold: the machine's own `update`. */\n readonly reduce: (state: S, msg: M) => Transition<S, C>;\n readonly subs: readonly SubEntry<S>[];\n readonly runnerFor: (type: string) => LoopRunner<Ctx, M> | undefined;\n readonly handlerFor: (type: string) => LoopHandler<M> | undefined;\n readonly store: Store<S> | undefined;\n readonly ctx: Ctx;\n readonly onError: OnError | undefined;\n readonly disposeTimeoutMs: number;\n readonly idleCap: number;\n readonly extensions: readonly ExtensionFactory<S, M, C>[];\n}\n\n// === liveWork: \"can anything still transition me without a dispatch?\" ===\n//\n// The one read `driveToDone` needs that the public `Runtime` does not carry:\n// after `start`'s chain quiesces, only a live Sub or an in-flight Cmd can still\n// enqueue a transition; with neither, waiting is a leak (#68). Module-private\n// and symbol-keyed so it reaches no export (the export map is the contract —\n// MAINTAINING.md), yet enumerable so it survives the spread a wrapper does over\n// the handle.\nexport const liveWork: unique symbol = Symbol(\"demlik-tea.liveWork\");\n\n/** The runtime's own sources of a caller-less transition, counted. */\nexport interface LiveWork {\n /** Live Subs — the running `machine.subs` entries. */\n readonly subs: number;\n /** Interpret handlers currently awaiting. */\n readonly cmds: number;\n}\n\nexport interface LiveWorkProbe {\n readonly [liveWork]: () => LiveWork;\n}\n\n/** The members the core itself puts on the run handle. */\nexport interface LoopHandle<S, M> extends LiveWorkProbe {\n dispatch(msg: M, opts?: { readonly settle?: DispatchSettle }): Promise<void>;\n dispatchOnce(msg: M): Promise<void>;\n getState(): S;\n ready: Promise<unknown>;\n idle(): Promise<void>;\n stop(): Promise<void>;\n}\n\n// Default `onError` sink: re-throw on a fresh macrotask so the failure reaches\n// the host's global error handler instead of vanishing — surface, not swallow\n// (invariant 6).\n//\n// A `RuntimeDiscardNotice` is NOT a failure of the runtime's own contract — a\n// host tearing a runtime down with Cmds in flight (a React ctx change, a\n// navigation) is legal, merely lossy. Rethrowing it would make every\n// unmount-during-fetch an uncaught error for consumers who never configured a\n// sink, so the default WARNS: loud enough that the silent discard #365\n// describes cannot happen again, never fatal. A configured `onError` sees\n// `\"discard\"` like any other phase and can route or ignore it.\n//\n// The branch keys on the ERROR CLASS, never on `context.phase`: `report`\n// hands a THROWING consumer sink's own error back here with the phase it was\n// handling, so a phase-keyed branch would warn away a broken sink and re-create\n// the very silent failure this exists to remove.\nfunction defaultOnError(error: unknown, _context: RuntimeErrorContext): void {\n if (error instanceof RuntimeDiscardNotice) {\n console.warn(error);\n return;\n }\n setTimeout(() => {\n throw error;\n }, 0);\n}\n\n/**\n * Start the loop. Returns the handle synchronously; boot runs as the FIRST entry\n * on the serial tail. Save-then-effects ordering is structural: every\n * transition installs State, awaits the save, reconciles Subs, runs the Cmds,\n * then calls the commit callbacks.\n */\nexport function startLoop<S, M extends { type: string }, C, Ctx>(\n config: LoopConfig<S, M, C, Ctx>,\n): LoopHandle<S, M> {\n const { idleCap, disposeTimeoutMs } = config;\n const onError: OnError = config.onError ?? defaultOnError;\n\n // A throwing sink routes THAT throw through `defaultOnError`, with the context\n // the sink was handling, so it cannot re-create a silent failure.\n function report(error: unknown, phase: RuntimeErrorPhase): void {\n const context: RuntimeErrorContext = { phase };\n try {\n onError(error, context);\n } catch (sinkError) {\n defaultOnError(sinkError, context);\n }\n }\n\n // Throw-isolated fanout: one bad consumer never strands its siblings\n // (invariant 6). The single home for that isolation discipline.\n function fanout<T>(\n items: Iterable<T>,\n phase: RuntimeErrorPhase,\n fn: (item: T) => void,\n ): void {\n for (const item of items) {\n try {\n fn(item);\n } catch (err) {\n report(err, phase);\n }\n }\n }\n\n // `state` is late-initialized: synchronously when there is no store to read,\n // otherwise inside the boot step (the head of the tail).\n let state: S | undefined;\n let bootError: unknown = null;\n // The dispatch gate. It only ever ADVANCES — `\"open\"` → `\"draining\"` →\n // `\"closed\"` — so the discard window can never re-open under a second `stop()`.\n //\n // \"open\" — normal operation; `enqueueDispatch` accepts.\n // \"draining\" — inside `stop()`, before the tail has settled. New work is\n // still refused (refusing is what makes the drain terminate),\n // but a Msg refused HERE was discarded BY the teardown: an\n // in-flight Cmd's follow-up, a detached handler's last Msg, or a\n // Sub still live because subs are torn down after the drain. So\n // the rejection is a `DispatchDiscardedError` reported under\n // `phase: \"discard\"` — lossy, legal, warn-only.\n // \"closed\" — halted by an extension, or `stop()` has returned. A dispatch\n // refused here is a consumer using a retired runtime — a real\n // error, and it stays loud.\n let gate: \"open\" | \"draining\" | \"closed\" = \"open\";\n // Interpret handlers currently awaiting. Written only by `trackInFlight`, read\n // by `stop()` to make a mid-flight teardown LOUD instead of silent (#365).\n let inFlightCmds = 0;\n\n const services: LoopServices<S> = {\n report,\n fanout,\n halt() {\n gate = \"closed\";\n },\n state: () => state,\n };\n const extensions = config.extensions.map((make) => make(services));\n\n // The fold, the handler call and the store, each wrapped by the extensions\n // that ask for it. `reduceRight` makes the first extension the outermost.\n const update: Step<S, M, C> = extensions.reduceRight<Step<S, M, C>>(\n (next, ext) => (ext.update ? ext.update(next) : next),\n config.reduce,\n );\n const interpret: InterpretStep<C> = extensions.reduceRight<InterpretStep<C>>(\n (next, ext) => (ext.interpret ? ext.interpret(next) : next),\n callHandler,\n );\n const store: Store<S> | undefined =\n config.store === undefined\n ? undefined\n : extensions.reduceRight<Store<S>>(\n (inner, ext) => (ext.store ? ext.store(inner) : inner),\n config.store,\n );\n const commitCallbacks = extensions.flatMap((ext) =>\n ext.commit ? [ext.commit] : [],\n );\n\n // Copied into a fresh object so handlers get the contributed keys without\n // mutating the caller's ctx (which may be shared across runtimes / tests).\n // `Object.assign`, not a spread: tsc cannot prove a generic `Ctx` is an\n // object type, and a spread refuses it.\n const handlerCtx: unknown = Object.assign(\n {},\n config.ctx,\n ...extensions.map((ext) => ext.ctx ?? {}),\n { [detachWork]: detachInFlight },\n );\n\n // Every step chains onto `tail` — the single concurrency gate.\n let tail: Promise<void> = Promise.resolve();\n\n // The running Subs, keyed by their derived id. An id present here is a runner\n // that started and has not been disposed.\n const subRegistry = new Map<string, { type: string; dispose: Dispose }>();\n\n // Tear down each named sub: run its cleanup (throws routed to the sink under\n // `\"sub-cleanup\"`), then drop it from the registry regardless.\n function stopSubs(ids: Iterable<string>): void {\n for (const id of ids) {\n const running = subRegistry.get(id);\n if (running === undefined) continue;\n try {\n trackDisposal(running.dispose());\n } catch (err) {\n report(err, \"sub-cleanup\");\n }\n subRegistry.delete(id);\n }\n }\n\n // Teardown work that returned a Promise and has not settled. Reconcile runs\n // inside the synchronous transition path (invariant 2) and cannot await a\n // cleanup, so every async disposal is remembered here the moment it starts,\n // and `stop()` drains the set before resolving: `await runtime.stop()` then\n // means \"teardown is done\", the only reading a host evicting an isolate can\n // act on. The rejection is attached HERE, once, so it can never become an\n // unhandled rejection.\n const pendingDisposals = new Set<Promise<void>>();\n\n function trackDisposal(result: void | Promise<void>): void {\n if (!(result instanceof Promise)) return;\n const settling: Promise<void> = result\n .catch((err) => {\n report(err, \"sub-cleanup\");\n })\n .finally(() => {\n pendingDisposals.delete(settling);\n });\n pendingDisposals.add(settling);\n }\n\n /**\n * Wait for the disposals started so far, bounded by `disposeTimeoutMs`. On\n * expiry report a `DisposeTimeoutNotice` and return anyway — `stop()` resolves\n * regardless (contract), and a release that never settles must not become a\n * host that never shuts down.\n */\n async function drainDisposals(): Promise<void> {\n if (pendingDisposals.size === 0) return;\n const outstanding = [...pendingDisposals];\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const outcome = await Promise.race([\n Promise.all(outstanding).then(() => \"settled\" as const),\n new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), disposeTimeoutMs);\n }),\n ]);\n if (outcome === \"timeout\") {\n report(\n new DisposeTimeoutNotice(outstanding.length, disposeTimeoutMs),\n \"discard\",\n );\n }\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n }\n\n /**\n * Reconcile the running Subs against `state`, after every save. An id already\n * running is left alone, a running id no longer wanted is stopped, and a\n * wanted id not yet running is started through its runner.\n *\n * Every failure is collected, never thrown mid-pass, so one bad entry cannot\n * strand its siblings: a throwing `deps` leaves the running Subs of that type\n * untouched, and a throwing runner is not registered. The first error is\n * thrown after the pass. A Sub type with no runner is a failure too — nothing\n * would ever start it.\n */\n function reconcileSubs(): void {\n let firstError: unknown = null;\n const remember = (err: unknown): void => {\n if (firstError === null) firstError = err;\n };\n const desired = new Map<string, Sub>();\n // Types whose entry could not be read this pass: keep what is running.\n const unreadTypes = new Set<string>();\n for (const entry of config.subs) {\n try {\n const sub = desiredSub(entry, state as S);\n if (sub !== null) desired.set(sub.id, sub);\n } catch (err) {\n remember(err);\n unreadTypes.add(entry.type);\n }\n }\n\n stopSubs(\n [...subRegistry].flatMap(([id, running]) =>\n desired.has(id) || unreadTypes.has(running.type) ? [] : [id],\n ),\n );\n\n for (const [id, sub] of desired) {\n if (subRegistry.has(id)) continue;\n const runner = config.runnerFor(sub.type);\n if (runner === undefined) {\n remember(\n new Error(\n `@demlik/tea: no subscribe runner for Sub type \"${sub.type}\". ` +\n \"Pass one to run in `subscribe`.\",\n ),\n );\n continue;\n }\n try {\n // A runner's dispatch is unawaited by construction, so it gets the\n // wrapped form, which also queues the transition behind this step —\n // never on the runner's own stack (spike #260).\n const dispose = runner(sub, config.ctx, dispatchUnawaited);\n subRegistry.set(id, { type: sub.type, dispose });\n } catch (err) {\n remember(err);\n }\n }\n if (firstError !== null) throw firstError;\n }\n\n // The ONE `(msg) => void` handed to every producer that cannot await its own\n // dispatch: a Cmd handler's late tail, a Sub runner. Its rejection has no\n // caller, so it routes to the sink with the phase derived from the error\n // class (`reportUndelivered`). Handing those sites the raw `enqueueDispatch`\n // would turn a Sub firing during `stop()`'s drain into an unhandled rejection\n // the sink never sees.\n function dispatchUnawaited(msg: M): void {\n enqueueDispatch(msg).catch(reportUndelivered);\n }\n\n // A `DispatchDiscardedError` means the teardown refused the Msg (`\"discard\"`,\n // warn-only); anything else is a genuine failure of the follow-up itself.\n function reportUndelivered(error: unknown): void {\n report(\n error,\n error instanceof DispatchDiscardedError ? \"discard\" : \"follow-up\",\n );\n }\n\n /**\n * Count a handler's work as in flight for exactly the lifetime of its\n * promise. `finally`-balanced, so a rejecting handler can never strand the\n * count above zero. A handler that throws SYNCHRONOUSLY never reaches here,\n * which is correct — it was never in flight.\n */\n function trackInFlight<T>(work: T | Promise<T>): Promise<T> {\n inFlightCmds++;\n return Promise.resolve(work).finally(() => {\n inFlightCmds--;\n });\n }\n\n /**\n * Enlist work a handler will NOT return — the `detachWork` key on ctx. A\n * handler that fans out inside its own Cmd (ADR 0018) returns before its\n * effect finishes; handing the promise here puts it back on both accountings\n * a returned promise had: `trackInFlight`, so `stop()` still reports it, and\n * the serial `tail`, so `idle()` drains it. The tail extension is\n * SYNCHRONOUS, so a `drainToQuiescence` that already read `tail` sees the\n * reference change and loops again.\n */\n function detachInFlight(work: Promise<unknown>): void {\n const tracked = trackInFlight(work);\n tracked.catch(reportUndelivered);\n tail = tail\n .then(async () => {\n await tracked;\n })\n .catch(() => {});\n }\n\n // The innermost interpret step: call the Cmd's handler, if one is wired. A\n // missing handler returns nothing, which is invariant 6's forward progress\n // for a miswired consumer.\n function callHandler(cmd: C): Promise<unknown> {\n const handler = config.handlerFor((cmd as { type: string }).type);\n if (handler === undefined) return Promise.resolve(undefined);\n return trackInFlight(handler(cmd as never, handlerCtx, dispatchUnawaited));\n }\n\n /**\n * Run each emitted Cmd. A returned Msg is enqueued onto the tail, never\n * dispatched re-entrantly. The first error stops further handlers in this\n * transition.\n *\n * THIS LOOP IS SERIAL BY RULING, NOT BY OVERSIGHT (ADR 0018). It runs a\n * transition's Cmds one at a time and never interleaves two handlers, so the\n * Msgs it enqueues fold in Cmd-EMISSION order and a replayed log reproduces\n * the same fold — invariant 2's serializability. Overlap in time belongs\n * INSIDE one Cmd's handler, never across the Cmds of one transition.\n */\n async function runCmds(cmds: readonly C[]): Promise<void> {\n for (const cmd of cmds) {\n const follow = await interpret(cmd);\n if (follow !== undefined && follow !== null) {\n enqueueDispatch(follow as M).catch(reportUndelivered);\n }\n }\n }\n\n // Install State, then save → reconcile subs → run Cmds → commit callbacks.\n // Save-before-effects is the hard ordering; tests pin it. Boot and every\n // transition end here; boot passes no Msg.\n async function commit(\n next: S,\n msg: M | undefined,\n cmds: readonly C[],\n ): Promise<void> {\n state = next;\n if (store) await store.save(next);\n reconcileSubs();\n await runCmds(cmds);\n for (const callback of commitCallbacks) callback(msg, next);\n }\n\n // One full transition. A `null` from the fold is no transition at all, and\n // it RESOLVES the dispatch. A throw from the fold rejects it.\n async function stepDispatch(msg: M): Promise<void> {\n if (state === undefined) {\n throw new Error(\"@demlik/tea: runtime not booted\");\n }\n const transition = update(state, msg);\n if (transition === null) return;\n await commit(transition[0], msg, transition[1]);\n }\n\n // With NO store there is nothing to read, so `init` runs IMMEDIATELY and\n // `getState()` is observable synchronously (what `useSyncExternalStore`\n // consumers need to render the first commit without a flicker); an `init`\n // throw leaves `run` itself. With a store, the read is the first thing boot\n // awaits; a `load` or `migrate` throw rejects `ready`.\n const storeless: Transition<S, C> | null =\n store === undefined ? config.init(null, config.ctx) : null;\n if (storeless !== null) state = storeless[0];\n\n async function boot(): Promise<void> {\n // Boundary parse (invariant 8): `store.load()` returns `unknown`;\n // `store.migrate(raw)` is the required parse — `S` on a recognized shape,\n // `null` on an unrecognized one (boots fresh).\n const [initial, cmds] =\n storeless ??\n config.init(\n (store as Store<S>).migrate(await (store as Store<S>).load()),\n config.ctx,\n );\n await commit(initial, undefined, cmds);\n }\n\n /**\n * Enqueue a dispatch on the tail — the single gate re-entrant handler and\n * runner calls also go through. Rejects when the gate is not open (with a\n * `DispatchDiscardedError` while `stop()` drains, a plain stopped Error once\n * it has), boot failed, or the fold / save / sub start / handler throws.\n */\n function enqueueDispatch(msg: M): Promise<void> {\n if (gate !== \"open\") {\n return Promise.reject(\n gate === \"draining\"\n ? new DispatchDiscardedError(msg.type)\n : new Error(\"@demlik/tea: runtime stopped\"),\n );\n }\n const next = tail.then(() => {\n if (bootError !== null) throw bootError;\n return stepDispatch(msg);\n });\n // Swallow the rejection on the tail so one failing dispatch does NOT poison\n // every later one. The original `next` still rejects for the caller.\n tail = next.catch(() => {});\n return next;\n }\n\n /**\n * Drain the serial tail to quiescence. Every follow-up reassigns `tail`\n * SYNCHRONOUSLY before its parent step resolves, so awaiting the current\n * `tail` and re-reading it catches every transitively enqueued follow-up.\n * Bounded by `idleCap` — on cap REJECT with `QuiescenceTimeoutError`, keeping\n * a livelock distinguishable from quiescence (invariant 6).\n */\n async function drainToQuiescence(): Promise<void> {\n for (let i = 0; i < idleCap; i++) {\n const observed = tail;\n await observed;\n if (tail === observed) return;\n }\n throw new QuiescenceTimeoutError(idleCap);\n }\n\n /**\n * The public `dispatch`: await the Msg's OWN transition first (so its own\n * failure surfaces before any drain), then — unless `{ settle: \"once\" }` —\n * drain the transitive follow-up chain.\n */\n async function dispatchToQuiescence(\n msg: M,\n opts?: { readonly settle?: DispatchSettle },\n ): Promise<void> {\n await enqueueDispatch(msg);\n if (opts?.settle === \"once\") return;\n await drainToQuiescence();\n }\n\n // `bootPromise` is the un-swallowed promise `ready` chains off (so callers\n // see the boot error directly); `tail` gets the swallowed branch so a failed\n // boot does not poison every later dispatch's chain (each surfaces\n // `bootError` in `enqueueDispatch`).\n const bootPromise = boot();\n tail = bootPromise.catch((err) => {\n bootError = err;\n });\n\n const core: LoopHandle<S, M> = {\n [liveWork]: () => ({ subs: subRegistry.size, cmds: inFlightCmds }),\n dispatch: dispatchToQuiescence,\n dispatchOnce: enqueueDispatch,\n getState(): S {\n // TOTAL. A booted handle is only obtainable by awaiting `ready`, which\n // resolves AFTER boot set `state`.\n return state as S;\n },\n // A failed boot never hands out a handle. The forward reference resolves\n // at `.then` time, after `handle` below is built.\n ready: bootPromise.then(() => handle),\n idle: drainToQuiescence,\n async stop(): Promise<void> {\n // Open the discard window unless the gate is already `\"closed\"` (a halt,\n // or a redundant second `stop()`): the gate only advances.\n if (gate === \"open\") gate = \"draining\";\n // Report BEFORE the drain: after `await tail` the count is zero by\n // construction, and the fact worth surfacing is what was outstanding at\n // the moment the host let go (#365).\n if (inFlightCmds > 0) {\n report(new RuntimeDiscardedError(inFlightCmds), \"discard\");\n }\n // Drain in-flight work. Tail rejections were swallowed at enqueue time, so\n // this always resolves. It TERMINATES because the gate refuses the\n // follow-ups those handlers return.\n await tail;\n gate = \"closed\";\n stopSubs([...subRegistry.keys()]);\n // …then WAIT for the teardown still settling, bounded by\n // `disposeTimeoutMs` (see `drainDisposals`).\n await drainDisposals();\n // Flush final State. A save throw does not reject `stop()` (contract:\n // resolves regardless) but IS loss of the last write, so it reaches the\n // sink (invariant 6).\n if (store && state !== undefined && bootError === null) {\n try {\n await store.save(state);\n } catch (error) {\n report(error, \"stop-save\");\n }\n }\n },\n };\n const handle: LoopHandle<S, M> = Object.assign(\n core,\n ...extensions.map((ext) => ext.handle ?? {}),\n );\n return handle;\n}\n"]}
|