@mantaq/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -28
- package/dist/index.d.mts +163 -5
- package/dist/index.mjs +1 -766
- package/package.json +3 -3
- package/dist/actor-internal-B4iAxUG_.d.mts +0 -152
- package/dist/internal.d.mts +0 -26
- package/dist/internal.mjs +0 -31
package/README.md
CHANGED
|
@@ -10,19 +10,19 @@ npm install @mantaq/core
|
|
|
10
10
|
|
|
11
11
|
## Quick Reference
|
|
12
12
|
|
|
13
|
-
| Pattern | Correct
|
|
14
|
-
| --------------- |
|
|
15
|
-
| Type context | `context: {} as MyContext`
|
|
16
|
-
| Write context | `context.
|
|
17
|
-
| Type events | `event("ID")<Payload>()`
|
|
18
|
-
| Send events | `actor.send(signInEvent.create(data))`
|
|
19
|
-
| Effect data | Use state payload
|
|
20
|
-
| Error handling | Emit internal event
|
|
21
|
-
| Internal events | Declare in `internal: [...]`
|
|
22
|
-
| Signal check | `if (signal.aborted) return`
|
|
23
|
-
| Regions | Child outputs in parent `internal`
|
|
24
|
-
| Snapshot | `snapshot()` carries path + context + payload
|
|
25
|
-
| Any handler | Universal events only (CANCEL)
|
|
13
|
+
| Pattern | Correct | Anti-pattern |
|
|
14
|
+
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
|
15
|
+
| Type context | `context: {} as MyContext` | casting in every handler |
|
|
16
|
+
| Write context | `const s = context.get(); s.x = y; context.set(s)` | mutate without `set()` |
|
|
17
|
+
| Type events | `event("ID")<Payload>()` | `event("ID")()` without generic |
|
|
18
|
+
| Send events | `actor.send(signInEvent.create(data))` | raw `{ type: "ID", payload }` objects |
|
|
19
|
+
| Effect data | Use state payload | Depend on `event` in effect |
|
|
20
|
+
| Error handling | Emit internal event | Throw in effect or transition |
|
|
21
|
+
| Internal events | Declare in `internal: [...]` | Put user events in internal |
|
|
22
|
+
| Signal check | `if (signal.aborted) return` | Skip abort check in async work |
|
|
23
|
+
| Regions | Child outputs in parent `internal` | Forget to declare child outputs |
|
|
24
|
+
| Snapshot | `snapshot()` carries path + context + payload | Re-serialize context by hand |
|
|
25
|
+
| Any handler | Universal events only (CANCEL) | State-specific logic in `onAny` |
|
|
26
26
|
|
|
27
27
|
## Contents
|
|
28
28
|
|
|
@@ -97,20 +97,24 @@ const actor = new Actor({
|
|
|
97
97
|
setup: (m) => {
|
|
98
98
|
m.on(idleState, signInEvent, (event, opts) => {
|
|
99
99
|
// ✅ typed — context.get()/set() flow the constructor type
|
|
100
|
-
|
|
100
|
+
const s = opts!.context.get();
|
|
101
|
+
s.phoneNumber = event.payload.phoneNumber;
|
|
102
|
+
opts!.context.set(s);
|
|
101
103
|
return { state: signingInState };
|
|
102
104
|
});
|
|
103
105
|
},
|
|
104
106
|
});
|
|
105
107
|
```
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
Context is user land — mutate it freely. `set()` is the write signal: any `set()` triggers `change`, even with the same reference. Call it after you mutate:
|
|
108
110
|
|
|
109
111
|
```ts
|
|
110
|
-
|
|
112
|
+
const s = opts.context.get();
|
|
113
|
+
s.phoneNumber = event.payload.phoneNumber;
|
|
114
|
+
opts.context.set(s);
|
|
111
115
|
```
|
|
112
116
|
|
|
113
|
-
**Never mutate
|
|
117
|
+
**Never mutate without `set()`:**
|
|
114
118
|
|
|
115
119
|
```ts
|
|
116
120
|
// ❌ mutation without set() — never signals; subscribers and persistence stay silent
|
|
@@ -123,7 +127,9 @@ opts!.context.phoneNumber = event.payload.phoneNumber;
|
|
|
123
127
|
setup: (m) => {
|
|
124
128
|
m.on(idleState, signInEvent, (event, opts) => {
|
|
125
129
|
const c = opts!.context as AuthContext; // ❌ unnecessary — already typed
|
|
126
|
-
|
|
130
|
+
const s = c.get();
|
|
131
|
+
s.phoneNumber = event.payload.phoneNumber;
|
|
132
|
+
c.set(s);
|
|
127
133
|
return { state: signingInState };
|
|
128
134
|
});
|
|
129
135
|
},
|
|
@@ -214,7 +220,9 @@ const actor = new Actor({
|
|
|
214
220
|
return { state: loadingState.create({ url: event.payload.url }) };
|
|
215
221
|
|
|
216
222
|
// ⚠️ context — works but shared across all states
|
|
217
|
-
|
|
223
|
+
const s = opts!.context.get();
|
|
224
|
+
s.url = event.payload.url;
|
|
225
|
+
opts!.context.set(s);
|
|
218
226
|
return { state: loadingState };
|
|
219
227
|
```
|
|
220
228
|
|
|
@@ -304,10 +312,9 @@ function createActor() {
|
|
|
304
312
|
m.on(idleState, startEvent, () => ({ state: workingState }));
|
|
305
313
|
m.on(workingState, doneEvent, () => ({ state: doneState }));
|
|
306
314
|
m.on(workingState, failedEvent, (_event, opts) => {
|
|
307
|
-
opts!.context.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
});
|
|
315
|
+
const s = opts!.context.get();
|
|
316
|
+
s.retryCount += 1;
|
|
317
|
+
opts!.context.set(s);
|
|
311
318
|
return { state: errorState };
|
|
312
319
|
});
|
|
313
320
|
},
|
|
@@ -439,7 +446,7 @@ if (snap.error) {
|
|
|
439
446
|
|
|
440
447
|
Two deliberate exceptions to "every failure is loud":
|
|
441
448
|
|
|
442
|
-
- **Subscribers only watch.** They read snapshots and never change the machine, so a throwing `on("change")`/`on("done")`/`on("transition")` callback is swallowed — the machine and its callers are unaffected.
|
|
449
|
+
- **Subscribers only watch.** They read snapshots and never change the machine, so a throwing `on("change")`/`on("done")`/`on("transition")`/`on("error")` callback is swallowed — the machine and its callers are unaffected.
|
|
443
450
|
- **Unhandled external events are ignored.** An event with no handler in the current state is dropped by design (broadcast fan-out, cross-state sends). An **internal** event with no handler, however, is a machine-authoring bug and routes to the error state (`reason: "unhandled"`).
|
|
444
451
|
|
|
445
452
|
A dead machine can be manually resumed with `actor.recover({ state, context })` — an explicit, **inherently dangerous** escape hatch (the caller injects state and context, so determinism no longer holds). Effects are not re-run and timers are not re-armed; processing resumes on the next event. Prefer fixing the root cause and recreating the actor.
|
|
@@ -496,7 +503,9 @@ Prefer storing the error in context and transitioning to an error state:
|
|
|
496
503
|
```ts
|
|
497
504
|
m.on(idleState, submitEvent, (event, opts) => {
|
|
498
505
|
if (!event.payload.data) {
|
|
499
|
-
|
|
506
|
+
const s = opts!.context.get();
|
|
507
|
+
s.error = "missing data";
|
|
508
|
+
opts!.context.set(s);
|
|
500
509
|
return { state: errorState };
|
|
501
510
|
}
|
|
502
511
|
return { state: doneState };
|
|
@@ -511,7 +520,9 @@ Use `onAny` to intercept events across all states — useful for universal error
|
|
|
511
520
|
setup: (m) => {
|
|
512
521
|
// CANCEL works from any state
|
|
513
522
|
m.onAny(cancelEvent, (_event, opts) => {
|
|
514
|
-
|
|
523
|
+
const s = opts!.context.get();
|
|
524
|
+
s.cancelled = true;
|
|
525
|
+
opts!.context.set(s);
|
|
515
526
|
return { state: cancelledState };
|
|
516
527
|
});
|
|
517
528
|
// State-specific handlers still run for their state
|
|
@@ -525,13 +536,17 @@ setup: (m) => {
|
|
|
525
536
|
setup: (m) => {
|
|
526
537
|
// ❌ state-specific logic in onAny defeats the purpose
|
|
527
538
|
m.onAny(submitBasicInfoEvent, (event, opts) => {
|
|
528
|
-
|
|
539
|
+
const s = opts!.context.get();
|
|
540
|
+
s.basicInfo = event; // wrong — only makes sense in basicInfo state
|
|
541
|
+
opts!.context.set(s);
|
|
529
542
|
return { state: shippingAddressState };
|
|
530
543
|
});
|
|
531
544
|
|
|
532
545
|
// ✅ keep state-specific logic in state handlers
|
|
533
546
|
m.on(basicInfoState, submitBasicInfoEvent, (event, opts) => {
|
|
534
|
-
|
|
547
|
+
const s = opts!.context.get();
|
|
548
|
+
s.basicInfo = event;
|
|
549
|
+
opts!.context.set(s);
|
|
535
550
|
return { state: shippingAddressState };
|
|
536
551
|
});
|
|
537
552
|
},
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,68 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/state.d.ts
|
|
2
|
+
declare function state<const T extends string>(id: T): <Payload = unknown>() => StateRef<T, Payload, false>;
|
|
3
|
+
type AnyStateRef = StateRef<string, unknown, boolean>;
|
|
4
|
+
interface RegionOptions {
|
|
5
|
+
initial: string;
|
|
6
|
+
states: Record<string, AnyStateRef>;
|
|
7
|
+
}
|
|
8
|
+
type RegionsOptions = Record<string, RegionOptions>;
|
|
9
|
+
declare class StateRef<T extends string, Payload = unknown, IsFinal extends boolean = false> {
|
|
10
|
+
readonly name: T;
|
|
11
|
+
readonly isFinal: IsFinal;
|
|
12
|
+
/** @internal */ _regions: RegionsOptions | undefined;
|
|
13
|
+
constructor(name: T, isFinal: IsFinal);
|
|
14
|
+
regions(options: RegionsOptions): this;
|
|
15
|
+
final(): StateRef<T, Payload, true>;
|
|
16
|
+
create(payload: Payload): {
|
|
17
|
+
state: StateRef<T, Payload, IsFinal>;
|
|
18
|
+
payload: Payload;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/event.d.ts
|
|
23
|
+
declare function event<const T extends string>(type: T): <Payload extends object | void = void>() => EventRef<T, Payload>;
|
|
24
|
+
type AnyEventRef = EventRef<string, object | void>;
|
|
25
|
+
type InternalEvent = {
|
|
26
|
+
type: string;
|
|
27
|
+
payload?: unknown;
|
|
28
|
+
};
|
|
29
|
+
type CreatedOfEvent<T extends string, P> = P extends void ? {
|
|
30
|
+
type: T;
|
|
31
|
+
} : {
|
|
32
|
+
type: T;
|
|
33
|
+
payload: P;
|
|
34
|
+
};
|
|
35
|
+
declare class EventRef<const T extends string, Payload extends object | void = void> {
|
|
36
|
+
readonly type: T;
|
|
37
|
+
constructor(type: T);
|
|
38
|
+
is(anyEvent: unknown): anyEvent is CreatedOfEvent<T, Payload>;
|
|
39
|
+
create(): Payload extends void ? {
|
|
40
|
+
type: T;
|
|
41
|
+
} : void;
|
|
42
|
+
create(payload: Payload): Payload extends void ? {
|
|
43
|
+
type: T;
|
|
44
|
+
} : {
|
|
45
|
+
type: T;
|
|
46
|
+
payload: Payload;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/clock.d.ts
|
|
51
|
+
interface Clock {
|
|
52
|
+
setTimeout(ms: number, cb: () => void, options?: {
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
eventName?: string;
|
|
55
|
+
}): number;
|
|
56
|
+
clearTimeout(id: number): void;
|
|
57
|
+
setInterval(ms: number, cb: () => void, options?: {
|
|
58
|
+
signal?: AbortSignal;
|
|
59
|
+
}): number;
|
|
60
|
+
clearInterval(id: number): void;
|
|
61
|
+
now(): number;
|
|
62
|
+
/** Optional hook for draining queues after clock advances (VirtualClock). */
|
|
63
|
+
setDrain?(fn: () => void): void;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
2
66
|
//#region src/real-clock.d.ts
|
|
3
67
|
declare class RealClock implements Clock {
|
|
4
68
|
#private;
|
|
@@ -38,6 +102,96 @@ declare class VirtualClock implements Clock {
|
|
|
38
102
|
setDrain(fn: () => void): void;
|
|
39
103
|
}
|
|
40
104
|
//#endregion
|
|
105
|
+
//#region src/context.d.ts
|
|
106
|
+
declare class Context<T> {
|
|
107
|
+
#private;
|
|
108
|
+
constructor(get: () => T, set: (value: T) => void);
|
|
109
|
+
get(): T;
|
|
110
|
+
set(value: T): void;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/actor-types.d.ts
|
|
114
|
+
type CreatedOf<E extends AnyEventRef> = E extends EventRef<infer Type, infer P> ? CreatedOfEvent<Type, P> : never;
|
|
115
|
+
type NonFinalStateRef<States extends AnyStateRef[]> = Extract<States[number], {
|
|
116
|
+
isFinal: false;
|
|
117
|
+
}>;
|
|
118
|
+
interface EffectInput<ActorContext, Payload = unknown> {
|
|
119
|
+
signal: AbortSignal;
|
|
120
|
+
state: {
|
|
121
|
+
name: string;
|
|
122
|
+
payload: Payload;
|
|
123
|
+
};
|
|
124
|
+
event: InternalEvent;
|
|
125
|
+
context: Context<ActorContext>;
|
|
126
|
+
emit: (event: InternalEvent) => void;
|
|
127
|
+
clock: Clock;
|
|
128
|
+
}
|
|
129
|
+
type EffectFn<ActorContext, Payload = unknown> = (input: EffectInput<ActorContext, Payload>) => void | Promise<void>;
|
|
130
|
+
type ErrorReason = "transition" | "effect" | "budget" | "output" | "internal" | "async" | "unhandled";
|
|
131
|
+
interface ErrorInfo {
|
|
132
|
+
error: unknown;
|
|
133
|
+
state: AnyStateRef;
|
|
134
|
+
context: unknown;
|
|
135
|
+
event: InternalEvent;
|
|
136
|
+
reason: ErrorReason;
|
|
137
|
+
}
|
|
138
|
+
interface TransitionInfo {
|
|
139
|
+
event: InternalEvent;
|
|
140
|
+
from: string;
|
|
141
|
+
to: string;
|
|
142
|
+
transitioned: boolean;
|
|
143
|
+
}
|
|
144
|
+
type ErrorState = StateRef<"__error", unknown, true>;
|
|
145
|
+
type TransitionResult<AllowedState extends AnyStateRef = AnyStateRef, AllowedEmit extends string = string> = {
|
|
146
|
+
state?: AllowedState | {
|
|
147
|
+
state: AllowedState;
|
|
148
|
+
payload?: unknown;
|
|
149
|
+
};
|
|
150
|
+
payload?: unknown;
|
|
151
|
+
emit?: Array<{
|
|
152
|
+
type: AllowedEmit;
|
|
153
|
+
payload?: unknown;
|
|
154
|
+
}>;
|
|
155
|
+
};
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/actor-internal.d.ts
|
|
158
|
+
interface Snapshot<C = unknown> {
|
|
159
|
+
path: string[];
|
|
160
|
+
context: C;
|
|
161
|
+
payload?: unknown;
|
|
162
|
+
regions: Record<string, Snapshot<unknown>>;
|
|
163
|
+
done?: boolean;
|
|
164
|
+
error?: ErrorInfo;
|
|
165
|
+
}
|
|
166
|
+
interface AnyActor<C = Record<string, unknown>> {
|
|
167
|
+
state: AnyStateRef;
|
|
168
|
+
clock: Clock;
|
|
169
|
+
regions: Record<string, AnyActor>;
|
|
170
|
+
send(event: AnyEventRef | InternalEvent): void;
|
|
171
|
+
snapshot(): Snapshot<C>;
|
|
172
|
+
on(event: "change", fn: (snapshot: Snapshot<C>, prev: Snapshot<C>) => void): () => void;
|
|
173
|
+
on(event: "done", fn: () => void): () => void;
|
|
174
|
+
on(event: "transition", fn: (info: TransitionInfo) => void): () => void;
|
|
175
|
+
on(event: "error", fn: (info: ErrorInfo) => void): () => void;
|
|
176
|
+
on(event: "output", fn: (event: InternalEvent) => void): () => void;
|
|
177
|
+
recover(target: {
|
|
178
|
+
state: AnyStateRef;
|
|
179
|
+
context: C;
|
|
180
|
+
}): void;
|
|
181
|
+
settled(): Promise<void>;
|
|
182
|
+
context?: C;
|
|
183
|
+
options?: {
|
|
184
|
+
transitions?: Record<string, Record<string, unknown>>;
|
|
185
|
+
effects?: Record<string, unknown[]>;
|
|
186
|
+
states?: ReadonlyArray<{
|
|
187
|
+
name: string;
|
|
188
|
+
isFinal: boolean;
|
|
189
|
+
}>;
|
|
190
|
+
};
|
|
191
|
+
inject(event: InternalEvent): void;
|
|
192
|
+
dispose(): void;
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
41
195
|
//#region src/builder.d.ts
|
|
42
196
|
type EventTypeOf<E extends AnyEventRef> = E extends EventRef<infer Type, object | void> ? Type : never;
|
|
43
197
|
type PayloadOf<S extends AnyStateRef> = S extends StateRef<infer _Name, infer Payload, infer _IsFinal> ? Payload : never;
|
|
@@ -104,19 +258,23 @@ declare class Actor<const States extends readonly AnyStateRef[], const Inputs ex
|
|
|
104
258
|
on(event: "change", fn: (snapshot: Snapshot<ActorContext>, prev: Snapshot<ActorContext>) => void): () => void;
|
|
105
259
|
on(event: "done", fn: () => void): () => void;
|
|
106
260
|
on(event: "transition", fn: (info: TransitionInfo) => void): () => void;
|
|
261
|
+
on(event: "error", fn: (info: ErrorInfo) => void): () => void;
|
|
262
|
+
on(event: "output", fn: (event: InternalEvent) => void): () => void;
|
|
107
263
|
settled(): Promise<void>;
|
|
108
264
|
send(event: CreatedOf$1<Inputs[number]>): void;
|
|
109
265
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* longer deterministic ("same inputs, same trace"). Effects are not re-run
|
|
113
|
-
* and timers are not re-armed — processing resumes on the next event.
|
|
266
|
+
* Restore a live state after `__error`. DANGER: caller supplies state+context,
|
|
267
|
+
* breaking determinism. Effects not re-run; processing resumes on next event.
|
|
114
268
|
*/
|
|
115
269
|
recover(target: {
|
|
116
270
|
state: States[number];
|
|
117
271
|
context: ActorContext;
|
|
118
272
|
}): void;
|
|
119
273
|
snapshot(): Snapshot<ActorContext>;
|
|
274
|
+
/** Push an internal event and drain. Unknown types route to output subscribers. */
|
|
275
|
+
inject(event: InternalEvent): void;
|
|
276
|
+
/** Stop for good: abort effect, clear queue and subscribers. Later sends/injects ignored. */
|
|
277
|
+
dispose(): void;
|
|
120
278
|
}
|
|
121
279
|
//#endregion
|
|
122
280
|
export { Actor, type ActorBuilder, type ActorOptions, type AnyActor, type AnyEventRef, type AnyStateRef, type BuiltMaps, type Clock, Context, type CreatedOf, type CreatedOfEvent, type EffectFn, type EffectInput, type ErrorInfo, type ErrorReason, type ErrorState, EventRef, type EventTypeOf, type InitialState, type InternalEvent, type NonFinalStateRef, type PayloadOf, RealClock, type SetupFn, type Snapshot, StateRef, type TransitionHandler, type TransitionInfo, type TransitionResult, VirtualClock, event, state };
|
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){return()=>new n(e,!1)}var n=class e{name;isFinal;_regions;constructor(e,t){this.name=e,this.isFinal=t}regions(e){return this._regions=e,this}final(){let t=new e(this.name,!0);return t._regions=this._regions,t}create(e){return{state:this,payload:e}}},r=class{#e=Date.now();now(){return Date.now()-this.#e}setTimeout(e,t,n){if(n?.signal?.aborted)return-1;let r,i=Number(globalThis.setTimeout(()=>{r&&n?.signal&&n.signal.removeEventListener(`abort`,r),t()},e));return n?.signal&&(r=()=>globalThis.clearTimeout(i),n.signal.addEventListener(`abort`,r,{once:!0})),i}clearTimeout(e){globalThis.clearTimeout(e)}setInterval(e,t,n){let r=Number(globalThis.setInterval(t,e));return n?.signal&&n.signal.addEventListener(`abort`,()=>globalThis.clearInterval(r),{once:!0}),r}clearInterval(e){globalThis.clearInterval(e)}};function i(e,t,n){let r=e?()=>{n.delete(t)}:void 0;return e&&r&&e.addEventListener(`abort`,r,{once:!0}),r}function a(e){e.signal&&e.onAbort&&e.signal.removeEventListener(`abort`,e.onAbort)}var o=class{#e=0;#t=new Map;#n=new Map;#r=1;#i=null;now(){return this.#e}#a(e,t,n){if(!Number.isFinite(e))throw RangeError(`[VirtualClock] invalid ${t} ms value: ${e}`);return e<=0?+!!n:e}setTimeout(e,t,n){let r=n?.signal;if(r?.aborted)return-1;let a=this.#r++,o=i(r,a,this.#t);return this.#t.set(a,{deadline:this.#e+this.#a(e,`setTimeout`,!1),cb:t,signal:r,onAbort:o,eventName:n?.eventName}),a}clearTimeout(e){let t=this.#t.get(e);t&&(a(t),this.#t.delete(e))}setInterval(e,t,n){let r=n?.signal;if(r?.aborted)return-1;let a=this.#r++,o=i(r,a,this.#n),s=this.#a(e,`setInterval`,!0);return this.#n.set(a,{ms:s,next:this.#e+s,cb:t,signal:r,onAbort:o}),a}clearInterval(e){let t=this.#n.get(e);t&&(a(t),this.#n.delete(e))}#o(e){let t=e,n=!1;for(let r of this.#t.values())r.deadline<=e&&r.deadline<=t&&(t=r.deadline,n=!0);for(let r of this.#n.values())r.next<=e&&r.next<=t&&(t=r.next,n=!0);return n?t:null}#s(e){let t=[];for(let[n,r]of this.#t)r.deadline===e&&t.push(n);for(let e of t){let t=this.#t.get(e);t&&(a(t),this.#t.delete(e),t.cb())}}#c(e){let t=[];for(let[n,r]of this.#n)r.next===e&&t.push(n);for(let e of t){let t=this.#n.get(e);t&&(t.cb(),this.#n.has(e)&&(t.next=this.#e+t.ms))}}advance(e){if(!Number.isFinite(e))throw RangeError(`[VirtualClock] invalid advance ms value: ${e}`);let t=this.#e+Math.max(0,e);for(;;){let e=this.#o(t);if(e===null)break;this.#e=e,this.#s(e),this.#c(e)}this.#e=t,this.#i?.()}hasPending(){return this.#t.size>0||this.#n.size>0}pendingTimers(){let e=[];for(let[t,n]of this.#t)e.push({id:t,deadline:n.deadline,ms:n.deadline-this.#e,eventName:n.eventName});return e}setDrain(e){this.#i=e}},s=class{#e=[];#t=0;#n=!1;#r=!1;#i=[];get length(){return this.#e.length-this.#t}push(...e){this.#r||this.#e.push(...e)}clear(){let e=this.#i.splice(0);this.#e.length=0,this.#t=0;for(let t of e)t()}settled(){return this.length===0&&!this.#n?Promise.resolve():new Promise(e=>{this.#i.push(e)})}processCancellable(e){this.#a(e)}#a(e){if(!this.#n){this.#n=!0;try{for(;this.#t<this.#e.length&&!this.#r;){let t=this.#e[this.#t++];if(!e(t)){this.#r=!0;break}}}finally{this.#e.length=0,this.#t=0,this.#n=!1,this.#r=!1;let e=this.#i.splice(0);for(let t of e)t()}}}},c=class{change=new Set;done=new Set;transition=new Set;error=new Set;#e=null;seed(e){this.#e=e}addChange(e){this.change.add(e);let t=this.#e;return t&&this.#t(()=>e(t,t)),()=>this.change.delete(e)}addDone(e){return this.done.add(e),()=>this.done.delete(e)}addTransition(e){return this.transition.add(e),()=>this.transition.delete(e)}addError(e){this.error.add(e);let t=this.#e?.error;return t&&this.#t(()=>e(t)),()=>this.error.delete(e)}emitChange(e){let t=this.#e??e;this.#e=e;for(let n of this.change)this.#t(()=>n(e,t))}emitDone(){for(let e of this.done)this.#t(e)}emitTransition(e){for(let t of this.transition)this.#t(()=>t(e))}emitError(e){for(let t of this.error)this.#t(()=>t(e))}clear(){this.change.clear(),this.done.clear(),this.transition.clear(),this.error.clear(),this.output.clear()}#t(t){e.from(t)}output=new Set;addOutput(e){return this.output.add(e),()=>this.output.delete(e)}emitOutput(e){for(let t of this.output)t(e)}};function l(e,t,n,r={}){let i=[e.name],a={};for(let[e,n]of Object.entries(t))a[e]=n.snapshot();let o={path:i,context:n,regions:a};return r.payload!==void 0&&(o.payload=r.payload),e.isFinal&&(o.done=!0),r.error!==void 0&&(o.error=r.error),o}function u(t){let n=[],r=t.effects[t.state.name];if(!r)return{pending:n};for(let i of r){if(t.abort.signal.aborted)break;let r=e.from(()=>i({signal:t.abort.signal,state:{name:t.state.name,payload:t.statePayload},event:t.event,context:t.context,emit:t.emit,clock:t.clock}));if(r[0]!==void 0){t.onError(r[0],t.lastGood);break}let a=r[1];a instanceof Promise&&n.push(a.catch(e=>{t.abort.signal.aborted||t.onError(e,t.lastGood)}))}return{pending:n}}function d(e){if(e.state)return e.state instanceof n?{state:e.state,payload:e.payload}:{state:e.state.state,payload:e.state.payload}}var f=class{#e;#t;constructor(e,t){this.#e=e,this.#t=t}get(){return this.#e()}set(e){this.#t(e)}},p=class{#e={};#t={};on(e,t,n){let r=e.name,i=t.type;return(this.#e[r]??={})[i]=n,this}onAny(e,t){let n=e.type;return(this.#e.Any??={})[n]=t,this}effect(e,t){return(this.#t[e.name]??=[]).push(t),this}build(){return{transitions:this.#e,effects:this.#t}}};function m(e){return e instanceof n?e:e.state}var h=class{state;clock;#e;#t;#n=!1;#r;#i;#a={};#o=new s;#s=new c;#c=null;#l;#u;#d;#f=!1;get context(){return this.#e}get regions(){return this.#a}get options(){return this.#i}constructor(e){let t=new p;e.setup(t);let i=t.build();this.#i={...e,transitions:i.transitions,effects:i.effects},this.#l=new Set((e.internal??[]).map(e=>e.type)),this.#u=new Set(e.inputs.map(e=>e.type)),this.#d=e.internalBudget??1e4,this.clock=e.clock??new r,this.clock.setDrain?.(()=>this.#x());let a=m(e.initial),o=new Set(e.states.map(e=>e.name));if(!o.has(a.name))throw Error(`[Actor] initial state "${a.name}" not found in declared states [${[...o].join(`, `)}]`);if(this.state=a,this.#S=e.initial instanceof n?void 0:e.initial.payload,this.#e=e.context??{},this.#r=a,this.#t=new f(()=>this.#e,e=>{this.#e=e,this.#n=!0}),e.regions)for(let[t,n]of Object.entries(e.regions))this.#a[t]=n,n.on(`output`,e=>{this.#o.push(e),this.#x()});this.#b({type:`__init`},this.#S),this.#s.seed(this.snapshot())}on(e,t){return{change:e=>this.#s.addChange(e),done:e=>this.#s.addDone(e),transition:e=>this.#s.addTransition(e),error:e=>this.#s.addError(e),output:e=>this.#s.addOutput(e)}[e](t)}settled(){return Promise.all([this.#o.settled(),...this.#C]).then(()=>void 0)}send(e){this.#k||(this.#m(e),this.#p())}recover(e){this.#T===null||this.#k||(this.#T=null,this.#o=new s,this.#c=null,this.state=e.state,this.#S=void 0,this.#e=e.context,this.#r=e.state,this.#n=!1,this.#E=null,this.#C=[],this.#s.emitChange(this.snapshot()))}#p(){this.state===this.#r&&!this.#n||(this.#r=this.state,this.#n=!1,this.#s.emitChange(this.snapshot()))}#m(e){if(this.#T!==null||this.state.isFinal)return;let t={...e,payload:e.payload??{}},n=this.state.name,r=this.#E;this.#E={state:this.state,context:this.#e};try{let r=this.#i.transitions,i=r[this.state.name]?.[e.type],a=r.Any?.[e.type],o=this.#h(t,i,a);this.#g(t,n,!!(i||a),o),o.emitted||this.#o.length>0?this.#x():!i&&!a&&this.#l.has(e.type)&&this.#O(`unhandled`,e,Error(`[Actor] internal event "${e.type}" emitted but no handler in state "${this.state.name}"`))}finally{this.#E=r}}#h(e,t,n){let r=t?this.#_(e,t):{applied:!1};if(this.#T===null){let t=this.#v(e,n,!r.applied);return{emitted:t.emitted,transitioned:r.applied||t.transitioned,target:r.target??t.target}}return{emitted:!1,transitioned:!1}}#g(e,t,n,r){this.#T===null&&n&&this.#s.emitTransition({event:e,from:t,to:r.target??this.state.name,transitioned:r.transitioned})}#_(e,t){let n=!1,r;return this.#D(`transition`,e,()=>{let i=t(e,{context:this.#t,actor:this});i.emit&&this.#o.push(...i.emit),i.state&&(r=this.#y(e,i),n=!0)}),{applied:n,target:r}}#v(e,t,n){if(t===void 0)return{emitted:!1,transitioned:!1};let r=!1,i=!1,a;return this.#D(`transition`,e,()=>{let o=t(e,{context:this.#t,actor:this});o.state&&n&&(a=this.#y(e,o),i=!0),o.emit&&(this.#o.push(...o.emit),r=!0)}),{emitted:r,transitioned:i,target:a}}snapshot(){return l(this.state,this.#a,this.#e,{error:this.#T??void 0,payload:this.#S})}#y(e,t){if(this.#T!==null||!t.state)return;let r=d(t);if(!r)return;if(!(r.state instanceof n)){this.#O(`transition`,e,Error(`invalid transition target`));return}this.#c?.abort(),this.state=r.state;let i=this.state.name;return this.#S=r.payload,this.#E={state:this.state,context:this.#e},this.#b(e,r.payload),r.state.isFinal&&this.#T===null&&this.#s.emitDone(),i}#b(e,t){if(this.#k)return;let n=this.#i.effects[this.state.name];if(!n){this.#c=null;return}let r=new AbortController;this.#c=r;let i=this.#E??{state:this.state,context:this.#e},a=u({effects:{[this.state.name]:n},state:this.state,statePayload:t,event:e,context:this.#t,emit:e=>{this.#T!==null||r.signal.aborted||(this.#o.push(e),this.#x())},clock:this.clock,abort:r,lastGood:i,onError:t=>this.#O(`effect`,e,t,i)});this.#C.push(...a.pending)}#x(){if(!(this.#f||this.#k)){this.#f=!0;try{let e=this.#d,t=0;this.#o.processCancellable(n=>{if(this.#T!==null)return!1;if(t>=e)return this.#O(`budget`,n,Error(`internal event budget exceeded`)),!1;if(t++,this.#l.has(n.type)||this.#u.has(n.type))this.#m(n);else if(!this.#D(`output`,n,()=>this.#s.emitOutput(n)))return!1;return!0})}finally{this.#f=!1,this.#p()}}}#S;#C=[];#w=new n(`__error`,!0);#T=null;#E=null;#D(t,n,r){let i=e.from(r);return i[0]===void 0||(this.#O(t,n,i[0]),!1)}#O(e,t,n,r){if(this.#T!==null)return;let i=r??this.#E??{state:this.state,context:this.#e};this.#T={error:n,state:i.state,context:i.context,event:t,reason:e},this.#c?.abort(),this.state=this.#w,this.#S=void 0,this.#r=this.#w,this.#n=!1,this.#s.emitError(this.#T),this.#s.emitChange(this.snapshot()),this.#s.emitDone()}inject(e){this.#k||(this.#o.push(e),this.#x())}dispose(){this.#k||(this.#k=!0,this.#c?.abort(),this.#c=null,this.#o.clear(),this.#s.clear())}#k=!1};function g(e){return()=>new _(e)}var _=class{type;constructor(e){this.type=e}is(e){return!!e&&typeof e==`object`&&`type`in e&&e.type===this.type}create(e){return e===void 0?{type:this.type}:{type:this.type,payload:e}}};export{h as Actor,f as Context,_ as EventRef,r as RealClock,n as StateRef,o as VirtualClock,g as event,t as state};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mantaq/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "State machine runtime with actor model, event system, state hierarchy, effects, and virtual clock.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
"dist"
|
|
13
13
|
],
|
|
14
14
|
"type": "module",
|
|
15
|
+
"sideEffects": false,
|
|
15
16
|
"exports": {
|
|
16
17
|
".": "./dist/index.mjs",
|
|
17
|
-
"./internal": "./dist/internal.mjs",
|
|
18
18
|
"./package.json": "./package.json"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@mantaq/utils": "0.0.
|
|
24
|
+
"@mantaq/utils": "0.0.3"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@stryker-mutator/core": "^9",
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
//#region src/state.d.ts
|
|
2
|
-
declare function state<const T extends string>(id: T): <Payload = unknown>() => StateRef<T, Payload, false>;
|
|
3
|
-
type AnyStateRef = StateRef<string, unknown, boolean>;
|
|
4
|
-
interface RegionOptions {
|
|
5
|
-
initial: string;
|
|
6
|
-
states: Record<string, AnyStateRef>;
|
|
7
|
-
}
|
|
8
|
-
type RegionsOptions = Record<string, RegionOptions>;
|
|
9
|
-
declare class StateRef<T extends string, Payload = unknown, IsFinal extends boolean = false> {
|
|
10
|
-
readonly name: T;
|
|
11
|
-
readonly isFinal: IsFinal;
|
|
12
|
-
/** @internal */ _regions: RegionsOptions | undefined;
|
|
13
|
-
constructor(name: T, isFinal: IsFinal);
|
|
14
|
-
regions(options: RegionsOptions): this;
|
|
15
|
-
final(): StateRef<T, Payload, true>;
|
|
16
|
-
create(payload: Payload): {
|
|
17
|
-
state: StateRef<T, Payload, IsFinal>;
|
|
18
|
-
payload: Payload;
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
//#endregion
|
|
22
|
-
//#region src/event.d.ts
|
|
23
|
-
declare function event<const T extends string>(type: T): <Payload extends object | void = void>() => EventRef<T, Payload>;
|
|
24
|
-
type AnyEventRef = EventRef<string, object | void>;
|
|
25
|
-
type InternalEvent = {
|
|
26
|
-
type: string;
|
|
27
|
-
payload?: unknown;
|
|
28
|
-
};
|
|
29
|
-
type CreatedOfEvent<T extends string, P> = P extends void ? {
|
|
30
|
-
type: T;
|
|
31
|
-
} : {
|
|
32
|
-
type: T;
|
|
33
|
-
payload: P;
|
|
34
|
-
};
|
|
35
|
-
declare class EventRef<const T extends string, Payload extends object | void = void> {
|
|
36
|
-
readonly type: T;
|
|
37
|
-
constructor(type: T);
|
|
38
|
-
is(anyEvent: unknown): anyEvent is CreatedOfEvent<T, Payload>;
|
|
39
|
-
create(): Payload extends void ? {
|
|
40
|
-
type: T;
|
|
41
|
-
} : void;
|
|
42
|
-
create(payload: Payload): Payload extends void ? {
|
|
43
|
-
type: T;
|
|
44
|
-
} : {
|
|
45
|
-
type: T;
|
|
46
|
-
payload: Payload;
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
//#endregion
|
|
50
|
-
//#region src/clock.d.ts
|
|
51
|
-
interface Clock {
|
|
52
|
-
setTimeout(ms: number, cb: () => void, options?: {
|
|
53
|
-
signal?: AbortSignal;
|
|
54
|
-
eventName?: string;
|
|
55
|
-
}): number;
|
|
56
|
-
clearTimeout(id: number): void;
|
|
57
|
-
setInterval(ms: number, cb: () => void, options?: {
|
|
58
|
-
signal?: AbortSignal;
|
|
59
|
-
}): number;
|
|
60
|
-
clearInterval(id: number): void;
|
|
61
|
-
now(): number;
|
|
62
|
-
/** Optional hook for draining queues after clock advances (VirtualClock). */
|
|
63
|
-
setDrain?(fn: () => void): void;
|
|
64
|
-
}
|
|
65
|
-
//#endregion
|
|
66
|
-
//#region src/context.d.ts
|
|
67
|
-
declare class Context<T> {
|
|
68
|
-
#private;
|
|
69
|
-
constructor(get: () => T, set: (value: T) => void);
|
|
70
|
-
get(): T;
|
|
71
|
-
set(value: T): void;
|
|
72
|
-
}
|
|
73
|
-
//#endregion
|
|
74
|
-
//#region src/actor-types.d.ts
|
|
75
|
-
type CreatedOf<E extends AnyEventRef> = E extends EventRef<infer Type, infer P> ? CreatedOfEvent<Type, P> : never;
|
|
76
|
-
type NonFinalStateRef<States extends AnyStateRef[]> = Extract<States[number], {
|
|
77
|
-
isFinal: false;
|
|
78
|
-
}>;
|
|
79
|
-
interface EffectInput<ActorContext, Payload = unknown> {
|
|
80
|
-
signal: AbortSignal;
|
|
81
|
-
state: {
|
|
82
|
-
name: string;
|
|
83
|
-
payload: Payload;
|
|
84
|
-
};
|
|
85
|
-
event: InternalEvent;
|
|
86
|
-
context: Context<ActorContext>;
|
|
87
|
-
emit: (event: InternalEvent) => void;
|
|
88
|
-
clock: Clock;
|
|
89
|
-
}
|
|
90
|
-
type EffectFn<ActorContext, Payload = unknown> = (input: EffectInput<ActorContext, Payload>) => void | Promise<void>;
|
|
91
|
-
type ErrorReason = "transition" | "effect" | "budget" | "output" | "internal" | "async" | "unhandled";
|
|
92
|
-
interface ErrorInfo {
|
|
93
|
-
error: unknown;
|
|
94
|
-
state: AnyStateRef;
|
|
95
|
-
context: unknown;
|
|
96
|
-
event: InternalEvent;
|
|
97
|
-
reason: ErrorReason;
|
|
98
|
-
}
|
|
99
|
-
interface TransitionInfo {
|
|
100
|
-
event: InternalEvent;
|
|
101
|
-
from: string;
|
|
102
|
-
to: string;
|
|
103
|
-
transitioned: boolean;
|
|
104
|
-
}
|
|
105
|
-
type ErrorState = StateRef<"__error", unknown, true>;
|
|
106
|
-
type TransitionResult<AllowedState extends AnyStateRef = AnyStateRef, AllowedEmit extends string = string> = {
|
|
107
|
-
state?: AllowedState | {
|
|
108
|
-
state: AllowedState;
|
|
109
|
-
payload?: unknown;
|
|
110
|
-
};
|
|
111
|
-
payload?: unknown;
|
|
112
|
-
emit?: Array<{
|
|
113
|
-
type: AllowedEmit;
|
|
114
|
-
payload?: unknown;
|
|
115
|
-
}>;
|
|
116
|
-
};
|
|
117
|
-
//#endregion
|
|
118
|
-
//#region src/actor-internal.d.ts
|
|
119
|
-
interface Snapshot<C = unknown> {
|
|
120
|
-
path: string[];
|
|
121
|
-
context: C;
|
|
122
|
-
payload?: unknown;
|
|
123
|
-
regions: Record<string, Snapshot<unknown>>;
|
|
124
|
-
done?: boolean;
|
|
125
|
-
error?: ErrorInfo;
|
|
126
|
-
}
|
|
127
|
-
interface AnyActor<C = Record<string, unknown>> {
|
|
128
|
-
state: AnyStateRef;
|
|
129
|
-
clock: Clock;
|
|
130
|
-
regions: Record<string, AnyActor>;
|
|
131
|
-
send(event: AnyEventRef | InternalEvent): void;
|
|
132
|
-
snapshot(): Snapshot<C>;
|
|
133
|
-
on(event: "change", fn: (snapshot: Snapshot<C>, prev: Snapshot<C>) => void): () => void;
|
|
134
|
-
on(event: "done", fn: () => void): () => void;
|
|
135
|
-
on(event: "transition", fn: (info: TransitionInfo) => void): () => void;
|
|
136
|
-
recover(target: {
|
|
137
|
-
state: AnyStateRef;
|
|
138
|
-
context: C;
|
|
139
|
-
}): void;
|
|
140
|
-
settled(): Promise<void>;
|
|
141
|
-
context?: C;
|
|
142
|
-
options?: {
|
|
143
|
-
transitions?: Record<string, Record<string, unknown>>;
|
|
144
|
-
effects?: Record<string, unknown[]>;
|
|
145
|
-
states?: ReadonlyArray<{
|
|
146
|
-
name: string;
|
|
147
|
-
isFinal: boolean;
|
|
148
|
-
}>;
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
//#endregion
|
|
152
|
-
export { InternalEvent as _, EffectInput as a, StateRef as b, ErrorState as c, TransitionResult as d, Context as f, EventRef as g, CreatedOfEvent as h, EffectFn as i, NonFinalStateRef as l, AnyEventRef as m, Snapshot as n, ErrorInfo as o, Clock as p, CreatedOf as r, ErrorReason as s, AnyActor as t, TransitionInfo as u, event as v, state as x, AnyStateRef as y };
|
package/dist/internal.d.mts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { _ as InternalEvent, t as AnyActor } from "./actor-internal-B4iAxUG_.mjs";
|
|
2
|
-
import { Either } from "@mantaq/utils";
|
|
3
|
-
//#region src/internal-registry.d.ts
|
|
4
|
-
interface ActorInternal {
|
|
5
|
-
children: Map<string, AnyActor>;
|
|
6
|
-
getOutputHandler(): ((event: InternalEvent) => void) | null;
|
|
7
|
-
setOutputHandler(fn: ((event: InternalEvent) => void) | null): void;
|
|
8
|
-
pushInternal(event: InternalEvent): void;
|
|
9
|
-
drainInternal(): void;
|
|
10
|
-
abortEffects(): void;
|
|
11
|
-
}
|
|
12
|
-
interface RegistryError {
|
|
13
|
-
message: string;
|
|
14
|
-
}
|
|
15
|
-
declare global {
|
|
16
|
-
var __mantaqCoreInternalRegistry: WeakMap<object, ActorInternal> | undefined;
|
|
17
|
-
}
|
|
18
|
-
declare function registerActor(actor: object, internal: ActorInternal): void;
|
|
19
|
-
declare function getChildren(actor: object): Either<RegistryError, Map<string, AnyActor>>;
|
|
20
|
-
declare function getOutputHandler(actor: object): Either<RegistryError, ((event: InternalEvent) => void) | null>;
|
|
21
|
-
declare function setOutputHandler(actor: object, fn: ((event: InternalEvent) => void) | null): Either<RegistryError, void>;
|
|
22
|
-
declare function pushInternal(actor: object, event: InternalEvent): Either<RegistryError, void>;
|
|
23
|
-
declare function drainInternal(actor: object): Either<RegistryError, void>;
|
|
24
|
-
declare function abortEffects(actor: object): Either<RegistryError, void>;
|
|
25
|
-
//#endregion
|
|
26
|
-
export { ActorInternal, RegistryError, abortEffects, drainInternal, getChildren, getOutputHandler, pushInternal, registerActor, setOutputHandler };
|
package/dist/internal.mjs
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Either } from "@mantaq/utils";
|
|
2
|
-
//#region src/internal-registry.ts
|
|
3
|
-
const registry = globalThis.__mantaqCoreInternalRegistry ??= /* @__PURE__ */ new WeakMap();
|
|
4
|
-
const UNREGISTERED = { message: "[mantaq] actor is not registered with the internal registry" };
|
|
5
|
-
function withInternal(actor, fn) {
|
|
6
|
-
const internal = registry.get(actor);
|
|
7
|
-
return internal === void 0 ? Either.left(UNREGISTERED) : [void 0, fn(internal)];
|
|
8
|
-
}
|
|
9
|
-
function registerActor(actor, internal) {
|
|
10
|
-
registry.set(actor, internal);
|
|
11
|
-
}
|
|
12
|
-
function getChildren(actor) {
|
|
13
|
-
return withInternal(actor, (internal) => internal.children);
|
|
14
|
-
}
|
|
15
|
-
function getOutputHandler(actor) {
|
|
16
|
-
return withInternal(actor, (internal) => internal.getOutputHandler());
|
|
17
|
-
}
|
|
18
|
-
function setOutputHandler(actor, fn) {
|
|
19
|
-
return withInternal(actor, (internal) => internal.setOutputHandler(fn));
|
|
20
|
-
}
|
|
21
|
-
function pushInternal(actor, event) {
|
|
22
|
-
return withInternal(actor, (internal) => internal.pushInternal(event));
|
|
23
|
-
}
|
|
24
|
-
function drainInternal(actor) {
|
|
25
|
-
return withInternal(actor, (internal) => internal.drainInternal());
|
|
26
|
-
}
|
|
27
|
-
function abortEffects(actor) {
|
|
28
|
-
return withInternal(actor, (internal) => internal.abortEffects());
|
|
29
|
-
}
|
|
30
|
-
//#endregion
|
|
31
|
-
export { abortEffects, drainInternal, getChildren, getOutputHandler, pushInternal, registerActor, setOutputHandler };
|