@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/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};