@mantaq/core 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -113
- package/dist/index.d.mts +237 -71
- package/dist/index.mjs +1 -766
- package/package.json +5 -5
- package/dist/actor-internal-B4iAxUG_.d.mts +0 -152
- package/dist/internal.d.mts +0 -26
- package/dist/internal.mjs +0 -31
package/dist/index.mjs
CHANGED
|
@@ -1,766 +1 @@
|
|
|
1
|
-
import { registerActor, setOutputHandler } from "./internal.mjs";
|
|
2
|
-
import { Either } from "@mantaq/utils";
|
|
3
|
-
//#region src/state.ts
|
|
4
|
-
function state(id) {
|
|
5
|
-
return () => new StateRef(id, false);
|
|
6
|
-
}
|
|
7
|
-
var StateRef = class StateRef {
|
|
8
|
-
name;
|
|
9
|
-
isFinal;
|
|
10
|
-
/** @internal */ _regions;
|
|
11
|
-
constructor(name, isFinal) {
|
|
12
|
-
this.name = name;
|
|
13
|
-
this.isFinal = isFinal;
|
|
14
|
-
}
|
|
15
|
-
regions(options) {
|
|
16
|
-
this._regions = options;
|
|
17
|
-
return this;
|
|
18
|
-
}
|
|
19
|
-
final() {
|
|
20
|
-
const next = new StateRef(this.name, true);
|
|
21
|
-
next._regions = this._regions;
|
|
22
|
-
return next;
|
|
23
|
-
}
|
|
24
|
-
create(payload) {
|
|
25
|
-
return {
|
|
26
|
-
state: this,
|
|
27
|
-
payload
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
//#endregion
|
|
32
|
-
//#region src/real-clock.ts
|
|
33
|
-
var RealClock = class {
|
|
34
|
-
#start = Date.now();
|
|
35
|
-
now() {
|
|
36
|
-
return Date.now() - this.#start;
|
|
37
|
-
}
|
|
38
|
-
setTimeout(ms, cb, options) {
|
|
39
|
-
if (options?.signal?.aborted) return -1;
|
|
40
|
-
let onAbort;
|
|
41
|
-
const id = Number(globalThis.setTimeout(() => {
|
|
42
|
-
if (onAbort && options?.signal) options.signal.removeEventListener("abort", onAbort);
|
|
43
|
-
cb();
|
|
44
|
-
}, ms));
|
|
45
|
-
if (options?.signal) {
|
|
46
|
-
onAbort = () => globalThis.clearTimeout(id);
|
|
47
|
-
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
48
|
-
}
|
|
49
|
-
return id;
|
|
50
|
-
}
|
|
51
|
-
clearTimeout(id) {
|
|
52
|
-
globalThis.clearTimeout(id);
|
|
53
|
-
}
|
|
54
|
-
setInterval(ms, cb, options) {
|
|
55
|
-
const id = Number(globalThis.setInterval(cb, ms));
|
|
56
|
-
if (options?.signal) {
|
|
57
|
-
const onAbort = () => globalThis.clearInterval(id);
|
|
58
|
-
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
59
|
-
}
|
|
60
|
-
return id;
|
|
61
|
-
}
|
|
62
|
-
clearInterval(id) {
|
|
63
|
-
globalThis.clearInterval(id);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/abort-tracker.ts
|
|
68
|
-
function trackAbort(signal, id, map) {
|
|
69
|
-
const onAbort = signal ? () => {
|
|
70
|
-
map.delete(id);
|
|
71
|
-
} : void 0;
|
|
72
|
-
if (signal && onAbort) signal.addEventListener("abort", onAbort, { once: true });
|
|
73
|
-
return onAbort;
|
|
74
|
-
}
|
|
75
|
-
function clearAbort(timer) {
|
|
76
|
-
if (timer.signal && timer.onAbort) timer.signal.removeEventListener("abort", timer.onAbort);
|
|
77
|
-
}
|
|
78
|
-
//#endregion
|
|
79
|
-
//#region src/virtual-clock.ts
|
|
80
|
-
var VirtualClock = class {
|
|
81
|
-
#now = 0;
|
|
82
|
-
#timers = /* @__PURE__ */ new Map();
|
|
83
|
-
#intervals = /* @__PURE__ */ new Map();
|
|
84
|
-
#nextId = 1;
|
|
85
|
-
#drain = null;
|
|
86
|
-
now() {
|
|
87
|
-
return this.#now;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Delay normalization: NaN and ±Infinity throw (programmer error); negative
|
|
91
|
-
* and 0 clamp to 0 for timeouts like the platform, to a 1ms floor for
|
|
92
|
-
* intervals (a 0ms interval would spin the synchronous advance loop
|
|
93
|
-
* forever). Finite positive values schedule at their real deadline.
|
|
94
|
-
*/
|
|
95
|
-
#delay(ms, method, interval) {
|
|
96
|
-
if (!Number.isFinite(ms)) throw new RangeError(`[VirtualClock] invalid ${method} ms value: ${ms}`);
|
|
97
|
-
return ms <= 0 ? interval ? 1 : 0 : ms;
|
|
98
|
-
}
|
|
99
|
-
setTimeout(ms, cb, options) {
|
|
100
|
-
const signal = options?.signal;
|
|
101
|
-
if (signal?.aborted) return -1;
|
|
102
|
-
const id = this.#nextId++;
|
|
103
|
-
const onAbort = trackAbort(signal, id, this.#timers);
|
|
104
|
-
this.#timers.set(id, {
|
|
105
|
-
deadline: this.#now + this.#delay(ms, "setTimeout", false),
|
|
106
|
-
cb,
|
|
107
|
-
signal,
|
|
108
|
-
onAbort,
|
|
109
|
-
eventName: options?.eventName
|
|
110
|
-
});
|
|
111
|
-
return id;
|
|
112
|
-
}
|
|
113
|
-
clearTimeout(id) {
|
|
114
|
-
const timer = this.#timers.get(id);
|
|
115
|
-
if (timer) {
|
|
116
|
-
clearAbort(timer);
|
|
117
|
-
this.#timers.delete(id);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
setInterval(ms, cb, options) {
|
|
121
|
-
const signal = options?.signal;
|
|
122
|
-
if (signal?.aborted) return -1;
|
|
123
|
-
const id = this.#nextId++;
|
|
124
|
-
const onAbort = trackAbort(signal, id, this.#intervals);
|
|
125
|
-
const d = this.#delay(ms, "setInterval", true);
|
|
126
|
-
this.#intervals.set(id, {
|
|
127
|
-
ms: d,
|
|
128
|
-
next: this.#now + d,
|
|
129
|
-
cb,
|
|
130
|
-
signal,
|
|
131
|
-
onAbort
|
|
132
|
-
});
|
|
133
|
-
return id;
|
|
134
|
-
}
|
|
135
|
-
clearInterval(id) {
|
|
136
|
-
const interval = this.#intervals.get(id);
|
|
137
|
-
if (interval) {
|
|
138
|
-
clearAbort(interval);
|
|
139
|
-
this.#intervals.delete(id);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
#findEarliestDeadline(target) {
|
|
143
|
-
let earliest = target;
|
|
144
|
-
let found = false;
|
|
145
|
-
for (const t of this.#timers.values()) if (t.deadline <= target && t.deadline <= earliest) {
|
|
146
|
-
earliest = t.deadline;
|
|
147
|
-
found = true;
|
|
148
|
-
}
|
|
149
|
-
for (const t of this.#intervals.values()) if (t.next <= target && t.next <= earliest) {
|
|
150
|
-
earliest = t.next;
|
|
151
|
-
found = true;
|
|
152
|
-
}
|
|
153
|
-
return found ? earliest : null;
|
|
154
|
-
}
|
|
155
|
-
#fireTimersAt(deadline) {
|
|
156
|
-
const ids = [];
|
|
157
|
-
for (const [id, t] of this.#timers) if (t.deadline === deadline) ids.push(id);
|
|
158
|
-
for (const id of ids) {
|
|
159
|
-
const timer = this.#timers.get(id);
|
|
160
|
-
if (timer) {
|
|
161
|
-
clearAbort(timer);
|
|
162
|
-
this.#timers.delete(id);
|
|
163
|
-
timer.cb();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
#fireIntervalsAt(deadline) {
|
|
168
|
-
const ids = [];
|
|
169
|
-
for (const [id, t] of this.#intervals) if (t.next === deadline) ids.push(id);
|
|
170
|
-
for (const id of ids) {
|
|
171
|
-
const interval = this.#intervals.get(id);
|
|
172
|
-
if (interval) {
|
|
173
|
-
interval.cb();
|
|
174
|
-
if (this.#intervals.has(id)) interval.next = this.#now + interval.ms;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
advance(ms) {
|
|
179
|
-
if (!Number.isFinite(ms)) throw new RangeError(`[VirtualClock] invalid advance ms value: ${ms}`);
|
|
180
|
-
const target = this.#now + Math.max(0, ms);
|
|
181
|
-
while (true) {
|
|
182
|
-
const deadline = this.#findEarliestDeadline(target);
|
|
183
|
-
if (deadline === null) break;
|
|
184
|
-
this.#now = deadline;
|
|
185
|
-
this.#fireTimersAt(deadline);
|
|
186
|
-
this.#fireIntervalsAt(deadline);
|
|
187
|
-
}
|
|
188
|
-
this.#now = target;
|
|
189
|
-
this.#drain?.();
|
|
190
|
-
}
|
|
191
|
-
hasPending() {
|
|
192
|
-
return this.#timers.size > 0 || this.#intervals.size > 0;
|
|
193
|
-
}
|
|
194
|
-
pendingTimers() {
|
|
195
|
-
const result = [];
|
|
196
|
-
for (const [id, t] of this.#timers) result.push({
|
|
197
|
-
id,
|
|
198
|
-
deadline: t.deadline,
|
|
199
|
-
ms: t.deadline - this.#now,
|
|
200
|
-
eventName: t.eventName
|
|
201
|
-
});
|
|
202
|
-
return result;
|
|
203
|
-
}
|
|
204
|
-
setDrain(fn) {
|
|
205
|
-
this.#drain = fn;
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
//#endregion
|
|
209
|
-
//#region src/queue.ts
|
|
210
|
-
var InternalQueue = class {
|
|
211
|
-
#queue = [];
|
|
212
|
-
#index = 0;
|
|
213
|
-
#processing = false;
|
|
214
|
-
#stopped = false;
|
|
215
|
-
#settledResolvers = [];
|
|
216
|
-
get length() {
|
|
217
|
-
return this.#queue.length - this.#index;
|
|
218
|
-
}
|
|
219
|
-
push(...events) {
|
|
220
|
-
if (this.#stopped) return;
|
|
221
|
-
this.#queue.push(...events);
|
|
222
|
-
}
|
|
223
|
-
settled() {
|
|
224
|
-
if (this.length === 0 && !this.#processing) return Promise.resolve();
|
|
225
|
-
return new Promise((resolve) => {
|
|
226
|
-
this.#settledResolvers.push(resolve);
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
processCancellable(processEvent) {
|
|
230
|
-
this.#process(processEvent);
|
|
231
|
-
}
|
|
232
|
-
#process(processEvent) {
|
|
233
|
-
if (this.#processing) return;
|
|
234
|
-
this.#processing = true;
|
|
235
|
-
try {
|
|
236
|
-
while (this.#index < this.#queue.length) {
|
|
237
|
-
if (this.#stopped) break;
|
|
238
|
-
const event = this.#queue[this.#index++];
|
|
239
|
-
if (!processEvent(event)) {
|
|
240
|
-
this.#stopped = true;
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
} finally {
|
|
245
|
-
this.#queue.length = 0;
|
|
246
|
-
this.#index = 0;
|
|
247
|
-
this.#processing = false;
|
|
248
|
-
this.#stopped = false;
|
|
249
|
-
if (this.#settledResolvers.length > 0) {
|
|
250
|
-
const resolvers = this.#settledResolvers.splice(0);
|
|
251
|
-
for (const resolve of resolvers) resolve();
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
//#endregion
|
|
257
|
-
//#region src/subscribers.ts
|
|
258
|
-
var Subscribers = class {
|
|
259
|
-
change = /* @__PURE__ */ new Set();
|
|
260
|
-
done = /* @__PURE__ */ new Set();
|
|
261
|
-
transition = /* @__PURE__ */ new Set();
|
|
262
|
-
#last = null;
|
|
263
|
-
seed(snapshot) {
|
|
264
|
-
this.#last = snapshot;
|
|
265
|
-
}
|
|
266
|
-
addChange(fn) {
|
|
267
|
-
this.change.add(fn);
|
|
268
|
-
const last = this.#last;
|
|
269
|
-
if (last) this.#safe(() => fn(last, last));
|
|
270
|
-
return () => this.change.delete(fn);
|
|
271
|
-
}
|
|
272
|
-
addDone(fn) {
|
|
273
|
-
this.done.add(fn);
|
|
274
|
-
return () => this.done.delete(fn);
|
|
275
|
-
}
|
|
276
|
-
addTransition(fn) {
|
|
277
|
-
this.transition.add(fn);
|
|
278
|
-
return () => this.transition.delete(fn);
|
|
279
|
-
}
|
|
280
|
-
emitChange(snapshot) {
|
|
281
|
-
const prev = this.#last ?? snapshot;
|
|
282
|
-
this.#last = snapshot;
|
|
283
|
-
for (const fn of this.change) this.#safe(() => fn(snapshot, prev));
|
|
284
|
-
}
|
|
285
|
-
emitDone() {
|
|
286
|
-
for (const fn of this.done) this.#safe(fn);
|
|
287
|
-
}
|
|
288
|
-
emitTransition(info) {
|
|
289
|
-
for (const fn of this.transition) this.#safe(() => fn(info));
|
|
290
|
-
}
|
|
291
|
-
clear() {
|
|
292
|
-
this.change.clear();
|
|
293
|
-
this.done.clear();
|
|
294
|
-
this.transition.clear();
|
|
295
|
-
}
|
|
296
|
-
#safe(fn) {
|
|
297
|
-
Either.from(fn);
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
//#endregion
|
|
301
|
-
//#region src/snapshot.ts
|
|
302
|
-
function buildSnapshot(s, regions, context, opts = {}) {
|
|
303
|
-
const path = [s.name];
|
|
304
|
-
const regionSnapshots = {};
|
|
305
|
-
for (const [regionName, child] of Object.entries(regions)) regionSnapshots[regionName] = child.snapshot();
|
|
306
|
-
const snap = {
|
|
307
|
-
path,
|
|
308
|
-
context,
|
|
309
|
-
regions: regionSnapshots
|
|
310
|
-
};
|
|
311
|
-
if (opts.payload !== void 0) snap.payload = opts.payload;
|
|
312
|
-
if (s.isFinal) snap.done = true;
|
|
313
|
-
if (opts.error !== void 0) snap.error = opts.error;
|
|
314
|
-
return snap;
|
|
315
|
-
}
|
|
316
|
-
//#endregion
|
|
317
|
-
//#region src/effects.ts
|
|
318
|
-
function runEffects(options) {
|
|
319
|
-
const pending = [];
|
|
320
|
-
const list = options.effects[options.state.name];
|
|
321
|
-
if (!list) return { pending };
|
|
322
|
-
for (const effectFn of list) {
|
|
323
|
-
if (options.abort.signal.aborted) break;
|
|
324
|
-
const attempt = Either.from(() => effectFn({
|
|
325
|
-
signal: options.abort.signal,
|
|
326
|
-
state: {
|
|
327
|
-
name: options.state.name,
|
|
328
|
-
payload: options.statePayload
|
|
329
|
-
},
|
|
330
|
-
event: options.event,
|
|
331
|
-
context: options.context,
|
|
332
|
-
emit: options.emit,
|
|
333
|
-
clock: options.clock
|
|
334
|
-
}));
|
|
335
|
-
if (attempt[0] !== void 0) {
|
|
336
|
-
options.onError(attempt[0], options.lastGood);
|
|
337
|
-
break;
|
|
338
|
-
}
|
|
339
|
-
const out = attempt[1];
|
|
340
|
-
if (out instanceof Promise) pending.push(out.catch((error) => {
|
|
341
|
-
if (options.abort.signal.aborted) return;
|
|
342
|
-
options.onError(error, options.lastGood);
|
|
343
|
-
}));
|
|
344
|
-
}
|
|
345
|
-
return { pending };
|
|
346
|
-
}
|
|
347
|
-
//#endregion
|
|
348
|
-
//#region src/dispatch.ts
|
|
349
|
-
function parseTarget(step) {
|
|
350
|
-
if (!step.state) return void 0;
|
|
351
|
-
if (step.state instanceof StateRef) return {
|
|
352
|
-
state: step.state,
|
|
353
|
-
payload: step.payload
|
|
354
|
-
};
|
|
355
|
-
return {
|
|
356
|
-
state: step.state.state,
|
|
357
|
-
payload: step.state.payload
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
//#endregion
|
|
361
|
-
//#region src/context.ts
|
|
362
|
-
var Context = class {
|
|
363
|
-
#get;
|
|
364
|
-
#set;
|
|
365
|
-
constructor(get, set) {
|
|
366
|
-
this.#get = get;
|
|
367
|
-
this.#set = set;
|
|
368
|
-
}
|
|
369
|
-
get() {
|
|
370
|
-
return this.#get();
|
|
371
|
-
}
|
|
372
|
-
set(value) {
|
|
373
|
-
this.#set(value);
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
//#endregion
|
|
377
|
-
//#region src/builder.ts
|
|
378
|
-
var ActorBuilder = class {
|
|
379
|
-
#transitions = {};
|
|
380
|
-
#effects = {};
|
|
381
|
-
on(stateRef, eventRef, fn) {
|
|
382
|
-
const sName = stateRef.name;
|
|
383
|
-
const eType = eventRef.type;
|
|
384
|
-
(this.#transitions[sName] ??= {})[eType] = fn;
|
|
385
|
-
return this;
|
|
386
|
-
}
|
|
387
|
-
onAny(eventRef, fn) {
|
|
388
|
-
const eType = eventRef.type;
|
|
389
|
-
(this.#transitions["Any"] ??= {})[eType] = fn;
|
|
390
|
-
return this;
|
|
391
|
-
}
|
|
392
|
-
effect(stateRef, fn) {
|
|
393
|
-
(this.#effects[stateRef.name] ??= []).push(fn);
|
|
394
|
-
return this;
|
|
395
|
-
}
|
|
396
|
-
/** @internal */ build() {
|
|
397
|
-
return {
|
|
398
|
-
transitions: this.#transitions,
|
|
399
|
-
effects: this.#effects
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
};
|
|
403
|
-
//#endregion
|
|
404
|
-
//#region src/actor.ts
|
|
405
|
-
function resolveInitial(initial) {
|
|
406
|
-
return initial instanceof StateRef ? initial : initial.state;
|
|
407
|
-
}
|
|
408
|
-
var Actor = class {
|
|
409
|
-
state;
|
|
410
|
-
clock;
|
|
411
|
-
#context;
|
|
412
|
-
#contextHandle;
|
|
413
|
-
#contextWritten = false;
|
|
414
|
-
#lastState;
|
|
415
|
-
#options;
|
|
416
|
-
#regions = {};
|
|
417
|
-
#children = /* @__PURE__ */ new Map();
|
|
418
|
-
#queue = new InternalQueue();
|
|
419
|
-
#subs = new Subscribers();
|
|
420
|
-
#effectAbort = null;
|
|
421
|
-
#outputHandler = null;
|
|
422
|
-
#internalIds;
|
|
423
|
-
#inputIds;
|
|
424
|
-
#internalBudget;
|
|
425
|
-
#draining = false;
|
|
426
|
-
get context() {
|
|
427
|
-
return this.#context;
|
|
428
|
-
}
|
|
429
|
-
get regions() {
|
|
430
|
-
return this.#regions;
|
|
431
|
-
}
|
|
432
|
-
get options() {
|
|
433
|
-
return this.#options;
|
|
434
|
-
}
|
|
435
|
-
constructor(options) {
|
|
436
|
-
registerActor(this, {
|
|
437
|
-
children: this.#children,
|
|
438
|
-
getOutputHandler: () => this.#outputHandler,
|
|
439
|
-
setOutputHandler: (fn) => {
|
|
440
|
-
this.#outputHandler = fn;
|
|
441
|
-
},
|
|
442
|
-
pushInternal: (event) => this.#queue.push(event),
|
|
443
|
-
drainInternal: () => this.#drainInternal(),
|
|
444
|
-
abortEffects: () => this.#abortEffects()
|
|
445
|
-
});
|
|
446
|
-
const builder = new ActorBuilder();
|
|
447
|
-
options.setup(builder);
|
|
448
|
-
const built = builder.build();
|
|
449
|
-
this.#options = {
|
|
450
|
-
...options,
|
|
451
|
-
transitions: built.transitions,
|
|
452
|
-
effects: built.effects
|
|
453
|
-
};
|
|
454
|
-
this.#internalIds = new Set((options.internal ?? []).map((e) => e.type));
|
|
455
|
-
this.#inputIds = new Set(options.inputs.map((e) => e.type));
|
|
456
|
-
this.#internalBudget = options.internalBudget ?? 1e4;
|
|
457
|
-
this.clock = options.clock ?? new RealClock();
|
|
458
|
-
this.clock.setDrain?.(() => this.#drainInternal());
|
|
459
|
-
const initState = resolveInitial(options.initial);
|
|
460
|
-
const stateNames = new Set(options.states.map((s) => s.name));
|
|
461
|
-
if (!stateNames.has(initState.name)) throw new Error(`[Actor] initial state "${initState.name}" not found in declared states [${[...stateNames].join(", ")}]`);
|
|
462
|
-
this.state = initState;
|
|
463
|
-
this.#statePayload = options.initial instanceof StateRef ? void 0 : options.initial.payload;
|
|
464
|
-
this.#context = options.context ?? {};
|
|
465
|
-
this.#lastState = initState;
|
|
466
|
-
this.#contextHandle = new Context(() => this.#context, (value) => {
|
|
467
|
-
this.#context = value;
|
|
468
|
-
this.#contextWritten = true;
|
|
469
|
-
});
|
|
470
|
-
if (options.regions) for (const [key, child] of Object.entries(options.regions)) {
|
|
471
|
-
this.#regions[key] = child;
|
|
472
|
-
const result = setOutputHandler(child, (event) => {
|
|
473
|
-
this.#queue.push(event);
|
|
474
|
-
this.#drainInternal();
|
|
475
|
-
});
|
|
476
|
-
if (result[0] !== void 0) throw new Error(result[0].message);
|
|
477
|
-
this.#children.set(key, child);
|
|
478
|
-
}
|
|
479
|
-
this.#runEffects({ type: "__init" }, this.#statePayload);
|
|
480
|
-
this.#subs.seed(this.snapshot());
|
|
481
|
-
}
|
|
482
|
-
on(event, fn) {
|
|
483
|
-
if (event === "change") {
|
|
484
|
-
const cb = fn;
|
|
485
|
-
return this.#subs.addChange(cb);
|
|
486
|
-
}
|
|
487
|
-
if (event === "done") {
|
|
488
|
-
const cb = fn;
|
|
489
|
-
return this.#subs.addDone(cb);
|
|
490
|
-
}
|
|
491
|
-
const cb = fn;
|
|
492
|
-
return this.#subs.addTransition(cb);
|
|
493
|
-
}
|
|
494
|
-
settled() {
|
|
495
|
-
return Promise.all([this.#queue.settled(), ...this.#pendingEffects]).then(() => void 0);
|
|
496
|
-
}
|
|
497
|
-
send(event) {
|
|
498
|
-
this.#dispatch(event);
|
|
499
|
-
this.#emitChangeIfDirty();
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* Manually restore the actor to a live state after a fatal `__error`.
|
|
503
|
-
* DANGER: the caller supplies state and context, so the trajectory is no
|
|
504
|
-
* longer deterministic ("same inputs, same trace"). Effects are not re-run
|
|
505
|
-
* and timers are not re-armed — processing resumes on the next event.
|
|
506
|
-
*/
|
|
507
|
-
recover(target) {
|
|
508
|
-
if (this.#error === null) return;
|
|
509
|
-
this.#error = null;
|
|
510
|
-
this.#queue = new InternalQueue();
|
|
511
|
-
this.#effectAbort = null;
|
|
512
|
-
this.state = target.state;
|
|
513
|
-
this.#statePayload = void 0;
|
|
514
|
-
this.#context = target.context;
|
|
515
|
-
this.#lastState = target.state;
|
|
516
|
-
this.#contextWritten = false;
|
|
517
|
-
this.#entry = null;
|
|
518
|
-
this.#pendingEffects = [];
|
|
519
|
-
this.#subs.emitChange(this.snapshot());
|
|
520
|
-
}
|
|
521
|
-
#emitChangeIfDirty() {
|
|
522
|
-
if (this.state === this.#lastState && !this.#contextWritten) return;
|
|
523
|
-
this.#lastState = this.state;
|
|
524
|
-
this.#contextWritten = false;
|
|
525
|
-
this.#subs.emitChange(this.snapshot());
|
|
526
|
-
}
|
|
527
|
-
#dispatch(event) {
|
|
528
|
-
if (this.#error !== null || this.state.isFinal) return;
|
|
529
|
-
const normalized = {
|
|
530
|
-
...event,
|
|
531
|
-
payload: event.payload ?? {}
|
|
532
|
-
};
|
|
533
|
-
const from = this.state.name;
|
|
534
|
-
const prev = this.#entry;
|
|
535
|
-
this.#entry = {
|
|
536
|
-
state: this.state,
|
|
537
|
-
context: this.#context
|
|
538
|
-
};
|
|
539
|
-
try {
|
|
540
|
-
const transitions = this.#options.transitions;
|
|
541
|
-
const stateTransition = transitions[this.state.name]?.[event.type];
|
|
542
|
-
const anyTransition = transitions["Any"]?.[event.type];
|
|
543
|
-
const result = this.#step(normalized, stateTransition, anyTransition);
|
|
544
|
-
this.#maybeEmitTransition(normalized, from, Boolean(stateTransition || anyTransition), result);
|
|
545
|
-
if (result.emitted || this.#queue.length > 0) this.#drainInternal();
|
|
546
|
-
else if (!stateTransition && !anyTransition) {
|
|
547
|
-
if (this.#internalIds.has(event.type)) this.#enterError("unhandled", event, /* @__PURE__ */ new Error(`[Actor] internal event "${event.type}" emitted but no handler in state "${this.state.name}"`));
|
|
548
|
-
}
|
|
549
|
-
} finally {
|
|
550
|
-
this.#entry = prev;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
#step(normalized, stateTransition, anyTransition) {
|
|
554
|
-
const stateOutcome = stateTransition ? this.#applyStateStep(normalized, stateTransition) : { applied: false };
|
|
555
|
-
if (this.#error === null) {
|
|
556
|
-
const anyOutcome = this.#applyAnyStep(normalized, anyTransition, !stateOutcome.applied);
|
|
557
|
-
return {
|
|
558
|
-
emitted: anyOutcome.emitted,
|
|
559
|
-
transitioned: stateOutcome.applied || anyOutcome.transitioned,
|
|
560
|
-
target: stateOutcome.target ?? anyOutcome.target
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
return {
|
|
564
|
-
emitted: false,
|
|
565
|
-
transitioned: false
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
#maybeEmitTransition(event, from, handled, outcome) {
|
|
569
|
-
if (this.#error !== null) return;
|
|
570
|
-
if (!handled) return;
|
|
571
|
-
this.#subs.emitTransition({
|
|
572
|
-
event,
|
|
573
|
-
from,
|
|
574
|
-
to: outcome.target ?? this.state.name,
|
|
575
|
-
transitioned: outcome.transitioned
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
#applyStateStep(event, transition) {
|
|
579
|
-
let applied = false;
|
|
580
|
-
let target;
|
|
581
|
-
this.#safe("transition", event, () => {
|
|
582
|
-
const step = transition(event, {
|
|
583
|
-
context: this.#contextHandle,
|
|
584
|
-
actor: this
|
|
585
|
-
});
|
|
586
|
-
if (step.emit) this.#queue.push(...step.emit);
|
|
587
|
-
if (step.state) {
|
|
588
|
-
target = this.#applyTransition(event, step);
|
|
589
|
-
applied = true;
|
|
590
|
-
}
|
|
591
|
-
});
|
|
592
|
-
return {
|
|
593
|
-
applied,
|
|
594
|
-
target
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
#applyAnyStep(event, transition, allowTransition) {
|
|
598
|
-
if (transition === void 0) return {
|
|
599
|
-
emitted: false,
|
|
600
|
-
transitioned: false
|
|
601
|
-
};
|
|
602
|
-
let emitted = false;
|
|
603
|
-
let transitioned = false;
|
|
604
|
-
let target;
|
|
605
|
-
this.#safe("transition", event, () => {
|
|
606
|
-
const step = transition(event, {
|
|
607
|
-
context: this.#contextHandle,
|
|
608
|
-
actor: this
|
|
609
|
-
});
|
|
610
|
-
if (step.state && allowTransition) {
|
|
611
|
-
target = this.#applyTransition(event, step);
|
|
612
|
-
transitioned = true;
|
|
613
|
-
}
|
|
614
|
-
if (step.emit) {
|
|
615
|
-
this.#queue.push(...step.emit);
|
|
616
|
-
emitted = true;
|
|
617
|
-
}
|
|
618
|
-
});
|
|
619
|
-
return {
|
|
620
|
-
emitted,
|
|
621
|
-
transitioned,
|
|
622
|
-
target
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
snapshot() {
|
|
626
|
-
return buildSnapshot(this.state, this.#regions, this.#context, {
|
|
627
|
-
error: this.#error ?? void 0,
|
|
628
|
-
payload: this.#statePayload
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
#abortEffects() {
|
|
632
|
-
this.#effectAbort?.abort();
|
|
633
|
-
this.#subs.clear();
|
|
634
|
-
}
|
|
635
|
-
#applyTransition(event, step) {
|
|
636
|
-
if (this.#error !== null || !step.state) return void 0;
|
|
637
|
-
const resolved = parseTarget(step);
|
|
638
|
-
if (!resolved) return void 0;
|
|
639
|
-
if (!(resolved.state instanceof StateRef)) {
|
|
640
|
-
this.#enterError("transition", event, /* @__PURE__ */ new Error("invalid transition target"));
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
this.#effectAbort?.abort();
|
|
644
|
-
this.state = resolved.state;
|
|
645
|
-
const target = this.state.name;
|
|
646
|
-
this.#statePayload = resolved.payload;
|
|
647
|
-
this.#entry = {
|
|
648
|
-
state: this.state,
|
|
649
|
-
context: this.#context
|
|
650
|
-
};
|
|
651
|
-
this.#runEffects(event, resolved.payload);
|
|
652
|
-
if (resolved.state.isFinal && this.#error === null) this.#subs.emitDone();
|
|
653
|
-
return target;
|
|
654
|
-
}
|
|
655
|
-
#runEffects(event, statePayload) {
|
|
656
|
-
const list = this.#options.effects[this.state.name];
|
|
657
|
-
if (!list) {
|
|
658
|
-
this.#effectAbort = null;
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
const abort = new AbortController();
|
|
662
|
-
this.#effectAbort = abort;
|
|
663
|
-
const lastGood = this.#entry ?? {
|
|
664
|
-
state: this.state,
|
|
665
|
-
context: this.#context
|
|
666
|
-
};
|
|
667
|
-
const result = runEffects({
|
|
668
|
-
effects: { [this.state.name]: list },
|
|
669
|
-
state: this.state,
|
|
670
|
-
statePayload,
|
|
671
|
-
event,
|
|
672
|
-
context: this.#contextHandle,
|
|
673
|
-
emit: (e) => {
|
|
674
|
-
if (this.#error !== null || abort.signal.aborted) return;
|
|
675
|
-
this.#queue.push(e);
|
|
676
|
-
this.#drainInternal();
|
|
677
|
-
},
|
|
678
|
-
clock: this.clock,
|
|
679
|
-
abort,
|
|
680
|
-
lastGood,
|
|
681
|
-
onError: (error) => this.#enterError("effect", event, error, lastGood)
|
|
682
|
-
});
|
|
683
|
-
this.#pendingEffects.push(...result.pending);
|
|
684
|
-
}
|
|
685
|
-
#drainInternal() {
|
|
686
|
-
if (this.#draining) return;
|
|
687
|
-
this.#draining = true;
|
|
688
|
-
try {
|
|
689
|
-
const budget = this.#internalBudget;
|
|
690
|
-
let count = 0;
|
|
691
|
-
this.#queue.processCancellable((event) => {
|
|
692
|
-
if (this.#error !== null) return false;
|
|
693
|
-
if (count >= budget) {
|
|
694
|
-
this.#enterError("budget", event, /* @__PURE__ */ new Error("internal event budget exceeded"));
|
|
695
|
-
return false;
|
|
696
|
-
}
|
|
697
|
-
count++;
|
|
698
|
-
if (this.#internalIds.has(event.type) || this.#inputIds.has(event.type)) this.#dispatch(event);
|
|
699
|
-
else {
|
|
700
|
-
const outputHandler = this.#outputHandler;
|
|
701
|
-
if (outputHandler) {
|
|
702
|
-
if (!this.#safe("output", event, () => outputHandler(event))) return false;
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
return true;
|
|
706
|
-
});
|
|
707
|
-
} finally {
|
|
708
|
-
this.#draining = false;
|
|
709
|
-
this.#emitChangeIfDirty();
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
#statePayload;
|
|
713
|
-
#pendingEffects = [];
|
|
714
|
-
#errorState = new StateRef("__error", true);
|
|
715
|
-
#error = null;
|
|
716
|
-
#entry = null;
|
|
717
|
-
#safe(reason, event, fn) {
|
|
718
|
-
const attempt = Either.from(fn);
|
|
719
|
-
if (attempt[0] === void 0) return true;
|
|
720
|
-
this.#enterError(reason, event, attempt[0]);
|
|
721
|
-
return false;
|
|
722
|
-
}
|
|
723
|
-
#enterError(reason, event, error, lastGood) {
|
|
724
|
-
if (this.#error !== null) return;
|
|
725
|
-
const entry = lastGood ?? this.#entry ?? {
|
|
726
|
-
state: this.state,
|
|
727
|
-
context: this.#context
|
|
728
|
-
};
|
|
729
|
-
this.#error = {
|
|
730
|
-
error,
|
|
731
|
-
state: entry.state,
|
|
732
|
-
context: entry.context,
|
|
733
|
-
event,
|
|
734
|
-
reason
|
|
735
|
-
};
|
|
736
|
-
this.#effectAbort?.abort();
|
|
737
|
-
this.state = this.#errorState;
|
|
738
|
-
this.#statePayload = void 0;
|
|
739
|
-
this.#lastState = this.#errorState;
|
|
740
|
-
this.#contextWritten = false;
|
|
741
|
-
this.#subs.emitChange(this.snapshot());
|
|
742
|
-
this.#subs.emitDone();
|
|
743
|
-
}
|
|
744
|
-
};
|
|
745
|
-
//#endregion
|
|
746
|
-
//#region src/event.ts
|
|
747
|
-
function event(type) {
|
|
748
|
-
return () => new EventRef(type);
|
|
749
|
-
}
|
|
750
|
-
var EventRef = class {
|
|
751
|
-
type;
|
|
752
|
-
constructor(type) {
|
|
753
|
-
this.type = type;
|
|
754
|
-
}
|
|
755
|
-
is(anyEvent) {
|
|
756
|
-
return !!anyEvent && typeof anyEvent === "object" && "type" in anyEvent && anyEvent.type === this.type;
|
|
757
|
-
}
|
|
758
|
-
create(payload) {
|
|
759
|
-
return payload === void 0 ? { type: this.type } : {
|
|
760
|
-
type: this.type,
|
|
761
|
-
payload
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
};
|
|
765
|
-
//#endregion
|
|
766
|
-
export { Actor, Context, EventRef, RealClock, StateRef, VirtualClock, event, state };
|
|
1
|
+
import{Either as e}from"@mantaq/utils";function t(e){let t=e;return typeof t==`function`?t:()=>({})}function n(e){return e}function r(){let e={},r={},i={on(n,{eventRef:r,handler:a}){let o=n.name,s=r.type;return(e[o]??={})[s]=t(a),i},onAny(n){let r=n.eventRef.type;return(e.Any??={})[r]=t(n.handler),i},effect(e,{name:t,fn:a}){return(r[e.name]??=[]).push({name:t,fn:n(a)}),i},build(){return{transitions:e,effects:r}}};return i}function i(e){return{get:e.get,set:e.set}}function a(e){return()=>c(e,{isFinal:!1})}const o=new WeakSet;function s(e){return typeof e==`object`&&!!e&&o.has(e)}function c(e,t){let n={name:e,isFinal:t.isFinal,regions(e){return n._regions=e,n},final(){let e=c(n.name,{isFinal:!0});return e._regions=n._regions,e},create(e){return{state:n,payload:e}}};return o.add(n),n}function l(e){let t=e.state;if(t)return`state`in t?{state:t.state,payload:t.payload}:{state:t,payload:e.payload}}function u(e){if(e instanceof Promise)return!0;let t=e;return typeof t==`object`&&!!t&&typeof t.then==`function`}function d(t){let n=[],r=[],i=t.effects[t.state.name];if(!i)return{pending:n,ran:r};for(let{name:a,fn:o}of i){if(t.abort.signal.aborted)break;let i,s=e.from(()=>(i=o({signal:t.abort.signal,state:{name:t.state.name,payload:t.statePayload},event:t.event,context:t.context,emit:t.emit,clock:t.clock}),!0));if(s[0]!==void 0){t.onError(s[0],t.lastGood);break}if(r.push(a),u(i)){let e=Promise.resolve(i).then(()=>void 0,e=>{t.abort.signal.aborted||t.onError(e,t.lastGood)});n.push(e)}}return{pending:n,ran:r}}function ee(e){return e??{}}function f(){let e=[],t=0,n=!1,r=!1,i=[];function a(){let e=i.splice(0);for(let t of e)t()}function o(i){if(!n){n=!0;try{for(;t<e.length&&!r;){let n=e[t];if(t++,!i(n)){r=!0;break}}}finally{e.length=0,t=0,n=!1,r=!1,a()}}}return{get length(){return e.length-t},push(...t){r||e.push(...t)},clear(){e.length=0,t=0,a()},settled(){return e.length-t===0&&!n?Promise.resolve():new Promise(e=>{i.push(e)})},processCancellable(e){o(e)}}}function p(){let e=Date.now(),t=new Map;function n(e){let n=t.get(e);n&&(n.signal.removeEventListener(`abort`,n.onAbort),t.delete(e))}return{now(){return Date.now()-e},setTimeout(e,{signal:r,cb:i}){if(r?.aborted)return-1;let a=Number(globalThis.setTimeout(()=>{n(a),i()},e));if(r){function e(){globalThis.clearTimeout(a),n(a)}t.set(a,{signal:r,onAbort:e}),r.addEventListener(`abort`,e,{once:!0})}return a},clearTimeout(e){globalThis.clearTimeout(e),n(e)},setInterval(e,{signal:r,cb:i}){if(r?.aborted)return-1;let a=Number(globalThis.setInterval(i,e));if(r){function e(){globalThis.clearInterval(a),n(a)}t.set(a,{signal:r,onAbort:e}),r.addEventListener(`abort`,e,{once:!0})}return a},clearInterval(e){globalThis.clearInterval(e),n(e)}}}function m(e){let{stateRef:t,regions:n,context:r}=e,i=[t.name],a={};for(let[e,t]of Object.entries(n))a[e]=t.snapshot();let o={path:i,context:r,regions:a};return e.payload!==void 0&&(o.payload=e.payload),t.isFinal&&(o.done=!0),e.error!==void 0&&(o.error={...e.error,context:h(e.error.context)}),o}function h(e){return typeof structuredClone==`function`?structuredClone(e):JSON.parse(JSON.stringify(e))}function g(){let t=new Set,n=new Set,r=new Set,i=new Set,a=new Set,o;function s(t){e.from(()=>(t(),!0))}return{change:t,done:n,transition:r,error:i,output:a,seed(e){o=e},addChange(e){t.add(e);let n=o;return n&&s(()=>e(n,n)),()=>t.delete(e)},addDone(e){return n.add(e),()=>n.delete(e)},addTransition(e){return r.add(e),()=>r.delete(e)},addError(e){i.add(e);let t=o?.error;return t&&s(()=>e(t)),()=>i.delete(e)},addOutput(e){return a.add(e),()=>a.delete(e)},emitChange(e){let n=o??e;o=e;for(let r of t)s(()=>r(e,n))},emitDone(){for(let e of n)s(e)},emitTransition(e){for(let t of r)s(()=>t(e))},emitError(e){for(let t of i)s(()=>t(e))},emitOutput(e){for(let t of a)t(e)},clear(){t.clear(),n.clear(),r.clear(),i.clear(),a.clear()}}}function _(e,t){let n=e?()=>{t.entries.delete(t.timerId)}:void 0;return e&&n&&e.addEventListener(`abort`,n,{once:!0}),n}function v(e){e.signal&&e.onAbort&&e.signal.removeEventListener(`abort`,e.onAbort)}function y(){let e=0,t=new Map,n=new Map,r=1,i=new Set;function a(e,t){if(!Number.isFinite(e))throw RangeError(`[VirtualClock] invalid ${t.method} ms value: ${e}`);return!0}function o(e,t){return a(e,{method:t.method}),e<=0?+!!t.interval:e}function s(e){let r=e,i=!1;for(let n of t.values())n.deadline<=e&&n.deadline<=r&&(r=n.deadline,i=!0);for(let t of n.values())t.next<=e&&t.next<=r&&(r=t.next,i=!0);return i?r:void 0}function c(e){let n=[];for(let[r,i]of t)i.deadline===e&&n.push(r);for(let e of n){let n=t.get(e);n&&(v(n),t.delete(e),n.cb())}}function l(t){let r=[];for(let[e,i]of n)i.next===t&&r.push(e);for(let t of r){let r=n.get(t);r&&(r.cb(),n.has(t)&&(r.next=e+r.ms))}}return{now(){return e},setTimeout(n,{signal:i,cb:a,eventName:s}){if(i?.aborted)return-1;let c=r++,l=_(i,{timerId:c,entries:t});return t.set(c,{deadline:e+o(n,{method:`setTimeout`,interval:!1}),cb:a,signal:i,onAbort:l,eventName:s}),c},clearTimeout(e){let n=t.get(e);n&&(v(n),t.delete(e))},setInterval(t,{signal:i,cb:a}){if(i?.aborted)return-1;let s=r++,c=_(i,{timerId:s,entries:n}),l=o(t,{method:`setInterval`,interval:!0});return n.set(s,{ms:l,next:e+l,cb:a,signal:i,onAbort:c}),s},clearInterval(e){let t=n.get(e);t&&(v(t),n.delete(e))},advance(t){a(t,{method:`advance`});let n=e+Math.max(0,t),r=-1,o=0;for(;;){let t=s(n);if(t===void 0)break;if(t===r){if(++o>=1e6)break}else o=0,r=t;e=t,c(t),l(t)}e=n;for(let e of i)e()},hasPending(){return t.size>0||n.size>0},pendingTimers(){let n=[];for(let[r,i]of t)n.push({id:r,deadline:i.deadline,ms:i.deadline-e,eventName:i.eventName});return n},setDrain(e){i.add(e)}}}function b(e){return`state`in e?e.state:e}function x(e,t){if(!e.has(t.candidate.name))throw Error(`[Actor] initial state "${t.candidate.name}" not found in declared states [${[...e].join(`, `)}]`);return!0}function S(t){let n=r();t.setup(n);let a=n.build(),o={...t,transitions:a.transitions,effects:a.effects},u=new Set((t.internal??[]).map(e=>e.type)),_=new Set(t.inputs.map(e=>e.type)),v=t.internalBudget??1e4,y=t.clock??p();y.setDrain?.(Y);let S,C,w=ee(t.context),T,E=!0,D,O={},k=f(),A=g(),j,M=!1,N=[],P,F,I=!1,L=!1,R=c(`__error`,{isFinal:!0}),z=i({get:()=>w,set:e=>{w=e,L=!0,E=!0}}),B=b(t.initial);x(new Set(t.states.map(e=>e.name)),{candidate:B}),S=B;let V=t.initial;if(C=!s(V)&&`payload`in V?V.payload:void 0,D=B,t.regions)for(let[e,n]of Object.entries(t.regions))O[e]=n,n.on(`output`,{fn:e=>{k.push(e),Y()}});let H={get state(){return S},clock:y,get regions(){return O},get context(){return w},get options(){return o},on(e,t){return{change:e=>A.addChange(e),done:e=>A.addDone(e),transition:e=>A.addTransition(e),error:e=>A.addError(e),output:e=>A.addOutput(e)}[e](t.fn)},settled(){return Q()},pendingEffectCount(){return N.length},send(e){I||(G(e),U())},recover:W,snapshot(){return K()},inject(e){ae(e)},dispose(){oe()}};J({type:`__init`},{payloadForState:C}),A.seed(K());function U(){S===D&&!L||(D=S,L=!1,A.emitChange(K()))}function W(e){P===void 0||I||(j?.abort(),j=void 0,k.clear(),k=f(),P=void 0,S=e.state,C=void 0,w=e.context,D=e.state,L=!1,E=!0,T=void 0,F=void 0,N=[],A.emitChange(K()))}function G(e){if(P!==void 0||S.isFinal)return;let t={...e,payload:e.payload??{}},n=S.name,r=F;F={state:S,context:w};try{let r=o.transitions,i=r[S.name]?.[e.type],a=r.Any?.[e.type],s=te(t,{stateTransition:i,anyTransition:a});ne(t,{from:n,handled:!!(i||a),outcome:s}),s.emitted||k.length>0?Y():!i&&!a&&u.has(e.type)&&Z(`unhandled`,{event:e,error:Error(`[Actor] internal event "${e.type}" emitted but no handler in state "${S.name}"`)})}finally{F=r}}function te(e,t){let n=t.stateTransition?re(e,{transition:t.stateTransition}):{applied:!1,target:void 0,effects:void 0};if(P===void 0){let r=ie(e,{transition:t.anyTransition,allowTransition:!n.applied});return{emitted:r.emitted,transitioned:n.applied||r.transitioned,target:n.target??r.target,effects:n.effects??r.effects}}return{emitted:!1,transitioned:!1}}function ne(e,t){P===void 0&&t.handled&&A.emitTransition({event:e,from:t.from,to:t.outcome.target??S.name,transitioned:t.outcome.transitioned,effects:t.outcome.effects??[]})}function re(e,t){let n=!1,r,i;return X(`transition`,{event:e,operation:()=>{let a=t.transition(e,{context:z,actor:$()});if(a.emit&&k.push(...a.emit),a.state){let t=q(e,{step:a});r=t.target,i=t.effects,n=!0}}}),{applied:n,target:r,effects:i}}function ie(e,t){if(t.transition===void 0)return{emitted:!1,transitioned:!1};let n=t.transition,r=!1,i=!1,a,o;return X(`transition`,{event:e,operation:()=>{let s=n(e,{context:z,actor:$()});if(s.state&&t.allowTransition){let t=q(e,{step:s});a=t.target,o=t.effects,i=!0}s.emit&&(k.push(...s.emit),r=!0)}}),{emitted:r,transitioned:i,target:a,effects:o}}function K(){return(T===void 0||E)&&(T=h(w),E=!1),m({stateRef:S,regions:O,context:T,error:P,payload:C})}function q(e,t){if(P!==void 0||!t.step.state)return{};let n=l(t.step);if(!n)return{};if(!s(n.state))return Z(`transition`,{event:e,error:Error(`invalid transition target`)}),{};j?.abort(),S=n.state;let r=S.name;C=n.payload,F={state:S,context:w};let i=J(e,{payloadForState:n.payload});return n.state.isFinal&&P===void 0&&A.emitDone(),{target:r,effects:i}}function J(e,t){if(I)return[];let n=o.effects[S.name];if(!n)return j=void 0,[];let r=new AbortController;j=r;let i=F??{state:S,context:w},a=d({effects:{[S.name]:n},state:S,statePayload:t.payloadForState,event:e,context:z,emit:e=>{P!==void 0||r.signal.aborted||(k.push(e),Y())},clock:y,abort:r,lastGood:i,onError:t=>Z(`effect`,{event:e,error:t})});for(let e of a.pending)N.push(e),e.finally(()=>{let t=N.indexOf(e);t!==-1&&N.splice(t,1)}).catch(()=>{});return a.ran}function Y(){if(!(M||I)){M=!0;try{let e=0;k.processCancellable(t=>{if(P!==void 0)return!1;if(e>=v)return Z(`budget`,{event:t,error:Error(`internal event budget exceeded`)}),!1;if(e++,u.has(t.type)||_.has(t.type))G(t);else if(!X(`output`,{event:t,operation:()=>A.emitOutput(t)}))return!1;return!0})}finally{M=!1,U()}}}function X(t,n){let r=e.from(()=>(n.operation(),!0));return r[0]===void 0||(Z(t,{event:n.event,error:r[0]}),!1)}function Z(e,t){if(P!==void 0)return;let n=t.lastGood??F??{state:S,context:w};P={error:t.error,state:n.state,context:n.context,event:t.event,reason:e},j?.abort(),S=R,C=void 0,D=R,L=!1,A.emitError(P),A.emitChange(K()),A.emitDone()}function ae(e){I||(k.push(e),Y())}function oe(){if(!I){I=!0,j?.abort(),j=void 0;for(let e of Object.values(O))e.dispose();O={},k.clear(),A.clear(),N=[]}}function Q(){return k.settled().then(()=>{if(N.length!==0)return Promise.all(N).then(()=>Q())})}function $(){return H}return H}const C=new Map;function w(e){let t=C.get(e);return t||(t=Symbol(`mantaq:event:${e}`),C.set(e,t)),t}const T=new WeakMap;function E(e){return()=>D(e)}function D(e){let t=w(e),n=function(e){return typeof e==`object`&&!!e&&T.get(e)?.has(t)===!0};function r(n){let r=n===void 0?{type:e}:{type:e,payload:n},i=T.get(r);return i||(i=new Set,T.set(r,i)),i.add(t),r}return{type:e,is:n,create:r}}export{S as Actor,i as Context,D as EventRef,p as RealClock,c as StateRef,y as VirtualClock,E as event,s as isStateRef,a as state};
|