@mantaq/core 0.0.3 → 0.2.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 +99 -62
- package/dist/actor-internal-B4iAxUG_.d.mts +152 -0
- package/dist/index.d.mts +21 -59
- package/dist/index.mjs +274 -101
- package/dist/internal.d.mts +1 -1
- package/package.json +2 -2
- package/dist/actor-internal-CBU_EDPZ.d.mts +0 -90
package/README.md
CHANGED
|
@@ -10,18 +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
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
13
|
+
| Pattern | Correct | Anti-pattern |
|
|
14
|
+
| --------------- | --------------------------------------------- | ------------------------------------- |
|
|
15
|
+
| Type context | `context: {} as MyContext` | casting in every handler |
|
|
16
|
+
| Write context | `context.set({ ...context.get() })` | 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` |
|
|
25
26
|
|
|
26
27
|
## Contents
|
|
27
28
|
|
|
@@ -41,6 +42,7 @@ npm install @mantaq/core
|
|
|
41
42
|
- [Any Handler](#any-handler)
|
|
42
43
|
- [Testing with VirtualClock](#testing-with-virtualclock)
|
|
43
44
|
- [Development](#development)
|
|
45
|
+
- [Testing](#testing)
|
|
44
46
|
- [License](#license)
|
|
45
47
|
|
|
46
48
|
## Quick Start
|
|
@@ -94,20 +96,34 @@ const actor = new Actor({
|
|
|
94
96
|
context: {} as AuthContext,
|
|
95
97
|
setup: (m) => {
|
|
96
98
|
m.on(idleState, signInEvent, (event, opts) => {
|
|
97
|
-
|
|
99
|
+
// ✅ typed — context.get()/set() flow the constructor type
|
|
100
|
+
opts!.context.set({ ...opts!.context.get(), phoneNumber: event.payload.phoneNumber });
|
|
98
101
|
return { state: signingInState };
|
|
99
102
|
});
|
|
100
103
|
},
|
|
101
104
|
});
|
|
102
105
|
```
|
|
103
106
|
|
|
107
|
+
`set()` is the write signal — always call it to write, even when the new value is the same reference. Any `set()` triggers `change`. Spread the current value:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
opts.context.set({ ...opts.context.get(), phoneNumber: event.payload.phoneNumber });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Never mutate in place:**
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// ❌ mutation without set() — never signals; subscribers and persistence stay silent
|
|
117
|
+
opts!.context.phoneNumber = event.payload.phoneNumber;
|
|
118
|
+
```
|
|
119
|
+
|
|
104
120
|
**Anti-pattern — casting in every handler:**
|
|
105
121
|
|
|
106
122
|
```ts
|
|
107
123
|
setup: (m) => {
|
|
108
124
|
m.on(idleState, signInEvent, (event, opts) => {
|
|
109
125
|
const c = opts!.context as AuthContext; // ❌ unnecessary — already typed
|
|
110
|
-
c.phoneNumber
|
|
126
|
+
c.set({ ...c.get(), phoneNumber: event.payload.phoneNumber });
|
|
111
127
|
return { state: signingInState };
|
|
112
128
|
});
|
|
113
129
|
},
|
|
@@ -132,7 +148,7 @@ Handlers receive the correct payload type:
|
|
|
132
148
|
```ts
|
|
133
149
|
setup: (m) => {
|
|
134
150
|
m.on(idleState, signInEvent, (event) => {
|
|
135
|
-
event.phoneNumber; // ✅ string
|
|
151
|
+
event.payload.phoneNumber; // ✅ string
|
|
136
152
|
return { state: signingInState };
|
|
137
153
|
});
|
|
138
154
|
},
|
|
@@ -155,12 +171,12 @@ const goodEvent = event("SIGN_IN")<{ phoneNumber: string }>();
|
|
|
155
171
|
actor.send(signInEvent.create({ phoneNumber: "+1234567890" }));
|
|
156
172
|
|
|
157
173
|
// ❌ bypasses type checking
|
|
158
|
-
actor.send({
|
|
174
|
+
actor.send({ type: "SIGN_IN", payload: { phoneNumber: "+1234567890" } });
|
|
159
175
|
```
|
|
160
176
|
|
|
161
177
|
### Effects and Event Typing
|
|
162
178
|
|
|
163
|
-
Effects run when entering a state. The `event` parameter in effects is typed
|
|
179
|
+
Effects run when entering a state. The `event` parameter in effects is typed `InternalEvent` — `{ type, payload? }` with `payload: unknown` — not the specific event that triggered the transition. This is by design: effects live on states, not transitions. Multiple transitions can lead to the same state, so the effect cannot know which event caused entry.
|
|
164
180
|
|
|
165
181
|
**Pass data to effects via state payload.** Set the payload in the transition return, read it from `state.payload` in the effect.
|
|
166
182
|
|
|
@@ -184,7 +200,7 @@ const actor = new Actor({
|
|
|
184
200
|
});
|
|
185
201
|
});
|
|
186
202
|
m.on(idleState, fetchEvent, (event) => {
|
|
187
|
-
return { state: loadingState.create({ url: event.url }) };
|
|
203
|
+
return { state: loadingState.create({ url: event.payload.url }) };
|
|
188
204
|
});
|
|
189
205
|
m.on(loadingState, doneEvent, () => ({ state: loadedState }));
|
|
190
206
|
},
|
|
@@ -195,10 +211,10 @@ const actor = new Actor({
|
|
|
195
211
|
|
|
196
212
|
```ts
|
|
197
213
|
// ✅ state payload — scoped to this state entry
|
|
198
|
-
return { state: loadingState.create({ url: event.url }) };
|
|
214
|
+
return { state: loadingState.create({ url: event.payload.url }) };
|
|
199
215
|
|
|
200
216
|
// ⚠️ context — works but shared across all states
|
|
201
|
-
opts!.context.url
|
|
217
|
+
opts!.context.set({ ...opts!.context.get(), url: event.payload.url });
|
|
202
218
|
return { state: loadingState };
|
|
203
219
|
```
|
|
204
220
|
|
|
@@ -206,9 +222,8 @@ return { state: loadingState };
|
|
|
206
222
|
|
|
207
223
|
```ts
|
|
208
224
|
m.effect(loadingState, ({ event, emit }) => {
|
|
209
|
-
// ❌ event is
|
|
210
|
-
// event.url
|
|
211
|
-
emit(doneEvent.create()); // fine — but event.url would be a type error
|
|
225
|
+
// ❌ event is InternalEvent — payload is unknown, not the triggering event's payload
|
|
226
|
+
emit(doneEvent.create()); // fine — but event.payload.url would be a type error
|
|
212
227
|
});
|
|
213
228
|
```
|
|
214
229
|
|
|
@@ -289,7 +304,10 @@ function createActor() {
|
|
|
289
304
|
m.on(idleState, startEvent, () => ({ state: workingState }));
|
|
290
305
|
m.on(workingState, doneEvent, () => ({ state: doneState }));
|
|
291
306
|
m.on(workingState, failedEvent, (_event, opts) => {
|
|
292
|
-
opts!.context.
|
|
307
|
+
opts!.context.set({
|
|
308
|
+
...opts!.context.get(),
|
|
309
|
+
retryCount: opts!.context.get().retryCount + 1,
|
|
310
|
+
});
|
|
293
311
|
return { state: errorState };
|
|
294
312
|
});
|
|
295
313
|
},
|
|
@@ -320,45 +338,29 @@ m.effect(workingState, ({ signal, emit, clock }) => {
|
|
|
320
338
|
|
|
321
339
|
### Snapshot & Restore
|
|
322
340
|
|
|
323
|
-
`actor.snapshot()` returns a
|
|
341
|
+
`actor.snapshot()` returns a `Snapshot` — `{ path, context, payload?, regions, done?, error? }`. Context is included; `payload` is the payload the current state was entered with (present only when the transition carried one). Use it to save/restore actor state across sessions.
|
|
324
342
|
|
|
325
343
|
```ts
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
regions: Record<string, unknown>;
|
|
329
|
-
context: Record<string, unknown>;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function saveActor(actor: { snapshot(): Snapshot; context: unknown }): SerializedActor {
|
|
333
|
-
return {
|
|
334
|
-
...actor.snapshot(),
|
|
335
|
-
context: { ...(actor.context as Record<string, unknown>) },
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Usage
|
|
340
|
-
const saved = saveActor(actor);
|
|
344
|
+
// Snapshot carries path, context, and regions — serialize it directly
|
|
345
|
+
const saved = actor.snapshot();
|
|
341
346
|
localStorage.setItem("actor", JSON.stringify(saved));
|
|
342
347
|
|
|
343
348
|
const raw = localStorage.getItem("actor");
|
|
344
349
|
if (raw) {
|
|
345
|
-
const data = JSON.parse(raw) as
|
|
346
|
-
//
|
|
350
|
+
const data = JSON.parse(raw) as Snapshot;
|
|
351
|
+
// rebuild the actor from data.path (re-seed context from data.context if needed)
|
|
347
352
|
}
|
|
348
353
|
```
|
|
349
354
|
|
|
350
|
-
**Anti-pattern —
|
|
355
|
+
**Anti-pattern — re-serializing context by hand:**
|
|
351
356
|
|
|
352
357
|
```ts
|
|
353
|
-
// ❌ snapshot()
|
|
358
|
+
// ❌ snapshot() already includes context — no manual step needed
|
|
354
359
|
const snap = actor.snapshot();
|
|
355
|
-
snap.context; //
|
|
360
|
+
snap.context; // exists
|
|
356
361
|
|
|
357
|
-
// ✅
|
|
358
|
-
const stateData =
|
|
359
|
-
path: actor.snapshot().path,
|
|
360
|
-
context: { ...actor.context },
|
|
361
|
-
};
|
|
362
|
+
// ✅ save the snapshot as-is
|
|
363
|
+
const stateData = actor.snapshot();
|
|
362
364
|
```
|
|
363
365
|
|
|
364
366
|
### Dynamic Children (Regions)
|
|
@@ -374,10 +376,10 @@ const healthMonitor = new Actor({
|
|
|
374
376
|
initial: unknownState,
|
|
375
377
|
setup: (m) => {
|
|
376
378
|
m.on(unknownState, healthCheckResult, (event) => ({
|
|
377
|
-
state: event.healthy ? healthyState : degradedState,
|
|
379
|
+
state: event.payload.healthy ? healthyState : degradedState,
|
|
378
380
|
}));
|
|
379
381
|
m.on(healthyState, healthCheckResult, (event) => ({
|
|
380
|
-
state: event.healthy ? healthyState : degradedState,
|
|
382
|
+
state: event.payload.healthy ? healthyState : degradedState,
|
|
381
383
|
}));
|
|
382
384
|
},
|
|
383
385
|
});
|
|
@@ -391,7 +393,7 @@ const manager = new Actor({
|
|
|
391
393
|
setup: (m) => {
|
|
392
394
|
m.on(connectedState, healthCheckResult, (event, opts) => {
|
|
393
395
|
// access child via actor.regions
|
|
394
|
-
manager.regions.health.send(healthCheckResult.create({ healthy: event.healthy }));
|
|
396
|
+
manager.regions.health.send(healthCheckResult.create({ healthy: event.payload.healthy }));
|
|
395
397
|
return {};
|
|
396
398
|
});
|
|
397
399
|
},
|
|
@@ -424,7 +426,23 @@ const parent = new Actor({
|
|
|
424
426
|
|
|
425
427
|
### Error Handling
|
|
426
428
|
|
|
427
|
-
Never throw from effects or transitions. Emit an error as an internal event and let a transition handle it.
|
|
429
|
+
Never throw from effects or transitions. Emit an error as an internal event and let a transition handle it. If user code throws anyway, the machine does **not** misbehave silently: it dies into a built-in terminal `__error` state, records `snapshot().error` (the thrown value, the state/context at the point of failure, the bad event), drops remaining events, and ignores later `send`s. Errors never escape `send()` and the machine stays deterministic — same inputs, same trace.
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
const snap = actor.snapshot();
|
|
433
|
+
if (snap.error) {
|
|
434
|
+
snap.error.reason; // "transition" | "effect" | "budget" | "output" | "internal" | "async" | "unhandled"
|
|
435
|
+
snap.error.state.name; // the state at the point of failure
|
|
436
|
+
snap.error.event.type; // the bad event
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Two deliberate exceptions to "every failure is loud":
|
|
441
|
+
|
|
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.
|
|
443
|
+
- **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
|
+
|
|
445
|
+
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.
|
|
428
446
|
|
|
429
447
|
Catch errors inside effects and emit recovery events:
|
|
430
448
|
|
|
@@ -448,7 +466,7 @@ setup: (m) => {
|
|
|
448
466
|
m.on(workingState, doneEvent, () => ({ state: doneState }));
|
|
449
467
|
m.on(workingState, failedEvent, (event) => {
|
|
450
468
|
// handle error in transition, not in effect
|
|
451
|
-
return { state: errorState.create({ error: event.error }) };
|
|
469
|
+
return { state: errorState.create({ error: event.payload.error }) };
|
|
452
470
|
});
|
|
453
471
|
},
|
|
454
472
|
```
|
|
@@ -460,7 +478,7 @@ try {
|
|
|
460
478
|
const result = riskyOperation();
|
|
461
479
|
emit(doneEvent.create());
|
|
462
480
|
} catch (err) {
|
|
463
|
-
throw err; // ❌ throw in effect —
|
|
481
|
+
throw err; // ❌ throw in effect — the machine dies into __error
|
|
464
482
|
}
|
|
465
483
|
```
|
|
466
484
|
|
|
@@ -468,7 +486,7 @@ try {
|
|
|
468
486
|
|
|
469
487
|
```ts
|
|
470
488
|
m.on(idleState, submitEvent, (event) => {
|
|
471
|
-
if (!event.data) throw new Error("missing data"); // ❌
|
|
489
|
+
if (!event.payload.data) throw new Error("missing data"); // ❌ kills the machine
|
|
472
490
|
return { state: doneState };
|
|
473
491
|
});
|
|
474
492
|
```
|
|
@@ -477,8 +495,8 @@ Prefer storing the error in context and transitioning to an error state:
|
|
|
477
495
|
|
|
478
496
|
```ts
|
|
479
497
|
m.on(idleState, submitEvent, (event, opts) => {
|
|
480
|
-
if (!event.data) {
|
|
481
|
-
opts!.context.error
|
|
498
|
+
if (!event.payload.data) {
|
|
499
|
+
opts!.context.set({ ...opts!.context.get(), error: "missing data" });
|
|
482
500
|
return { state: errorState };
|
|
483
501
|
}
|
|
484
502
|
return { state: doneState };
|
|
@@ -493,7 +511,7 @@ Use `onAny` to intercept events across all states — useful for universal error
|
|
|
493
511
|
setup: (m) => {
|
|
494
512
|
// CANCEL works from any state
|
|
495
513
|
m.onAny(cancelEvent, (_event, opts) => {
|
|
496
|
-
opts!.context.cancelled
|
|
514
|
+
opts!.context.set({ ...opts!.context.get(), cancelled: true });
|
|
497
515
|
return { state: cancelledState };
|
|
498
516
|
});
|
|
499
517
|
// State-specific handlers still run for their state
|
|
@@ -507,13 +525,13 @@ setup: (m) => {
|
|
|
507
525
|
setup: (m) => {
|
|
508
526
|
// ❌ state-specific logic in onAny defeats the purpose
|
|
509
527
|
m.onAny(submitBasicInfoEvent, (event, opts) => {
|
|
510
|
-
opts!.context.basicInfo
|
|
528
|
+
opts!.context.set({ ...opts!.context.get(), basicInfo: event }); // wrong — only makes sense in basicInfo state
|
|
511
529
|
return { state: shippingAddressState };
|
|
512
530
|
});
|
|
513
531
|
|
|
514
532
|
// ✅ keep state-specific logic in state handlers
|
|
515
533
|
m.on(basicInfoState, submitBasicInfoEvent, (event, opts) => {
|
|
516
|
-
opts!.context.basicInfo
|
|
534
|
+
opts!.context.set({ ...opts!.context.get(), basicInfo: event });
|
|
517
535
|
return { state: shippingAddressState };
|
|
518
536
|
});
|
|
519
537
|
},
|
|
@@ -652,6 +670,25 @@ vp test
|
|
|
652
670
|
vp pack
|
|
653
671
|
```
|
|
654
672
|
|
|
673
|
+
## Testing
|
|
674
|
+
|
|
675
|
+
The package ships with a full test suite you can run and audit:
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
vp test # feature + error + property tests
|
|
679
|
+
vp check # format, lint, typecheck (includes type-level contract tests)
|
|
680
|
+
vp run mutation:core # mutation score must stay ≥ 90
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
**Test contract.** Behavior is pinned by four kinds of tests, split by filename:
|
|
684
|
+
|
|
685
|
+
- `*.test.ts` — features and happy paths, hand-maintained.
|
|
686
|
+
- `*.error.test.ts` — failure paths: warnings, budgets, abort, unregistered, the `__error` state.
|
|
687
|
+
- `*.property.test.ts` — property-based invariants against a reference model (fast-check, replayable via `MANTAQ_SEED`).
|
|
688
|
+
- `*.mutation.test.ts` — directed tests that kill specific mutants; regenerable and thrown away between stryker runs.
|
|
689
|
+
|
|
690
|
+
Coverage is analyzed per test (stryker `perTest` coverage analysis) so every mutant is attributed to the test that pins it. The mutation break threshold is 90 — a change that drops the score below 90 fails the build.
|
|
691
|
+
|
|
655
692
|
## License
|
|
656
693
|
|
|
657
694
|
MIT
|
|
@@ -0,0 +1,152 @@
|
|
|
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/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { _ as InternalEvent, a as EffectInput, b as StateRef, c as ErrorState, d as TransitionResult, f as Context, g as EventRef, h as CreatedOfEvent, i as EffectFn, l as NonFinalStateRef, m as AnyEventRef, n as Snapshot, o as ErrorInfo, p as Clock, r as CreatedOf, s as ErrorReason, t as AnyActor, u as TransitionInfo, v as event, x as state, y as AnyStateRef } from "./actor-internal-B4iAxUG_.mjs";
|
|
2
2
|
//#region src/real-clock.d.ts
|
|
3
3
|
declare class RealClock implements Clock {
|
|
4
4
|
#private;
|
|
@@ -38,44 +38,8 @@ declare class VirtualClock implements Clock {
|
|
|
38
38
|
setDrain(fn: () => void): void;
|
|
39
39
|
}
|
|
40
40
|
//#endregion
|
|
41
|
-
//#region src/context.d.ts
|
|
42
|
-
declare class Context<T> {
|
|
43
|
-
#private;
|
|
44
|
-
constructor(get: () => T, set: (value: T) => void);
|
|
45
|
-
get(): T;
|
|
46
|
-
set(value: T): void;
|
|
47
|
-
}
|
|
48
|
-
//#endregion
|
|
49
|
-
//#region src/actor-types.d.ts
|
|
50
|
-
type CreatedOf<E extends AnyEventRef> = E extends EventRef<infer Id, infer P> ? CreatedOfEvent<Id, P> : never;
|
|
51
|
-
type NonFinalStateRef<States extends AnyStateRef[]> = Extract<States[number], {
|
|
52
|
-
isFinal: false;
|
|
53
|
-
}>;
|
|
54
|
-
interface EffectInput<ActorContext, Payload = unknown> {
|
|
55
|
-
signal: AbortSignal;
|
|
56
|
-
state: {
|
|
57
|
-
name: string;
|
|
58
|
-
payload: Payload;
|
|
59
|
-
};
|
|
60
|
-
event: InternalEvent;
|
|
61
|
-
context: Context<ActorContext>;
|
|
62
|
-
emit: (event: InternalEvent) => void;
|
|
63
|
-
clock: Clock;
|
|
64
|
-
}
|
|
65
|
-
type EffectFn<ActorContext, Payload = unknown> = (input: EffectInput<ActorContext, Payload>) => void;
|
|
66
|
-
type TransitionResult<AllowedState extends AnyStateRef = AnyStateRef, AllowedEmit extends string = string> = {
|
|
67
|
-
state?: AllowedState | {
|
|
68
|
-
state: AllowedState;
|
|
69
|
-
payload?: unknown;
|
|
70
|
-
};
|
|
71
|
-
payload?: unknown;
|
|
72
|
-
emit?: Array<{
|
|
73
|
-
id: AllowedEmit;
|
|
74
|
-
}>;
|
|
75
|
-
};
|
|
76
|
-
//#endregion
|
|
77
41
|
//#region src/builder.d.ts
|
|
78
|
-
type
|
|
42
|
+
type EventTypeOf<E extends AnyEventRef> = E extends EventRef<infer Type, object | void> ? Type : never;
|
|
79
43
|
type PayloadOf<S extends AnyStateRef> = S extends StateRef<infer _Name, infer Payload, infer _IsFinal> ? Payload : never;
|
|
80
44
|
type TransitionHandler<States extends readonly AnyStateRef[], ActorContext> = (event: InternalEvent, opts: {
|
|
81
45
|
context: Context<ActorContext>;
|
|
@@ -87,25 +51,21 @@ interface BuiltMaps<States extends readonly AnyStateRef[], ActorContext> {
|
|
|
87
51
|
}
|
|
88
52
|
declare class ActorBuilder<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext> {
|
|
89
53
|
#private;
|
|
90
|
-
on<S extends States[number], E extends Inputs[number] | Internal[number]>(stateRef: S, eventRef: E, fn: (event: E extends EventRef<infer
|
|
54
|
+
on<S extends States[number], E extends Inputs[number] | Internal[number]>(stateRef: S, eventRef: E, fn: (event: E extends EventRef<infer Type, infer P> ? CreatedOfEvent<Type, P> : never, opts: {
|
|
91
55
|
context: Context<ActorContext>;
|
|
92
56
|
actor: AnyActor;
|
|
93
|
-
}) => TransitionResult<States[number],
|
|
94
|
-
onAny<E extends Inputs[number] | Internal[number]>(eventRef: E, fn: (event: E extends EventRef<infer
|
|
57
|
+
}) => TransitionResult<States[number], EventTypeOf<Outputs[number]>>): this;
|
|
58
|
+
onAny<E extends Inputs[number] | Internal[number]>(eventRef: E, fn: (event: E extends EventRef<infer Type, infer P> ? CreatedOfEvent<Type, P> : never, opts: {
|
|
95
59
|
context: Context<ActorContext>;
|
|
96
60
|
actor: AnyActor;
|
|
97
|
-
}) => TransitionResult<States[number],
|
|
61
|
+
}) => TransitionResult<States[number], EventTypeOf<Outputs[number]>>): this;
|
|
98
62
|
effect<S extends States[number]>(stateRef: S, fn: EffectFn<ActorContext, PayloadOf<S>>): this;
|
|
99
63
|
/** @internal */ build(): BuiltMaps<States, ActorContext>;
|
|
100
64
|
}
|
|
101
65
|
type SetupFn<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext> = (m: ActorBuilder<States, Inputs, Internal, Outputs, ActorContext>) => void;
|
|
102
66
|
//#endregion
|
|
103
67
|
//#region src/actor.d.ts
|
|
104
|
-
type CreatedOf$1<E extends AnyEventRef> = E extends EventRef<infer
|
|
105
|
-
id: Id;
|
|
106
|
-
} : P & {
|
|
107
|
-
id: Id;
|
|
108
|
-
}) : never;
|
|
68
|
+
type CreatedOf$1<E extends AnyEventRef> = E extends EventRef<infer Type, infer P> ? CreatedOfEvent<Type, P> : never;
|
|
109
69
|
type InitialState<S extends AnyStateRef> = S extends StateRef<infer _N extends string, infer P> ? [unknown] extends [P] ? S | {
|
|
110
70
|
state: S;
|
|
111
71
|
payload?: P;
|
|
@@ -125,18 +85,9 @@ interface ActorOptions<States extends readonly AnyStateRef[], Inputs extends rea
|
|
|
125
85
|
setup: SetupFn<States, Inputs, Internal, Outputs, ActorContext>;
|
|
126
86
|
regions?: Record<string, AnyActor>;
|
|
127
87
|
}
|
|
128
|
-
interface InternalActorOptions<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext = Record<string, unknown>> {
|
|
129
|
-
inputs: Inputs;
|
|
130
|
-
outputs?: Outputs;
|
|
131
|
-
internal?: Internal;
|
|
132
|
-
states: States;
|
|
133
|
-
context?: ActorContext;
|
|
134
|
-
initial: InitialState<States[number]>;
|
|
135
|
-
clock?: Clock;
|
|
136
|
-
internalBudget?: number;
|
|
88
|
+
interface InternalActorOptions<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext = Record<string, unknown>> extends Omit<ActorOptions<States, Inputs, Internal, Outputs, ActorContext>, "setup"> {
|
|
137
89
|
transitions: TransitionDispatch<States, ActorContext>;
|
|
138
90
|
effects: Record<string, Array<EffectFn<ActorContext>>>;
|
|
139
|
-
regions?: Record<string, AnyActor>;
|
|
140
91
|
}
|
|
141
92
|
type TransitionDispatch<States extends readonly AnyStateRef[], ActorContext> = Record<string, Record<string, ((event: InternalEvent, options: {
|
|
142
93
|
context: Context<ActorContext>;
|
|
@@ -144,7 +95,7 @@ type TransitionDispatch<States extends readonly AnyStateRef[], ActorContext> = R
|
|
|
144
95
|
}) => TransitionResult<States[number], string>) | undefined>>;
|
|
145
96
|
declare class Actor<const States extends readonly AnyStateRef[], const Inputs extends readonly AnyEventRef[], const Internal extends readonly AnyEventRef[] = readonly [], const Outputs extends readonly AnyEventRef[] = readonly [], ActorContext = Record<string, unknown>> {
|
|
146
97
|
#private;
|
|
147
|
-
state: States[number];
|
|
98
|
+
state: States[number] | ErrorState;
|
|
148
99
|
readonly clock: Clock;
|
|
149
100
|
get context(): ActorContext;
|
|
150
101
|
get regions(): Record<string, AnyActor>;
|
|
@@ -152,9 +103,20 @@ declare class Actor<const States extends readonly AnyStateRef[], const Inputs ex
|
|
|
152
103
|
constructor(options: ActorOptions<States, Inputs, Internal, Outputs, ActorContext>);
|
|
153
104
|
on(event: "change", fn: (snapshot: Snapshot<ActorContext>, prev: Snapshot<ActorContext>) => void): () => void;
|
|
154
105
|
on(event: "done", fn: () => void): () => void;
|
|
106
|
+
on(event: "transition", fn: (info: TransitionInfo) => void): () => void;
|
|
155
107
|
settled(): Promise<void>;
|
|
156
108
|
send(event: CreatedOf$1<Inputs[number]>): void;
|
|
109
|
+
/**
|
|
110
|
+
* Manually restore the actor to a live state after a fatal `__error`.
|
|
111
|
+
* DANGER: the caller supplies state and context, so the trajectory is no
|
|
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.
|
|
114
|
+
*/
|
|
115
|
+
recover(target: {
|
|
116
|
+
state: States[number];
|
|
117
|
+
context: ActorContext;
|
|
118
|
+
}): void;
|
|
157
119
|
snapshot(): Snapshot<ActorContext>;
|
|
158
120
|
}
|
|
159
121
|
//#endregion
|
|
160
|
-
export { Actor, type ActorBuilder, type AnyActor, type AnyEventRef, type AnyStateRef, type Clock, Context, type CreatedOf, type CreatedOfEvent, type EffectFn, type EffectInput, EventRef, type InternalEvent, type NonFinalStateRef, RealClock, type SetupFn, type Snapshot, StateRef, VirtualClock, event, state };
|
|
122
|
+
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,4 +1,5 @@
|
|
|
1
1
|
import { registerActor, setOutputHandler } from "./internal.mjs";
|
|
2
|
+
import { Either } from "@mantaq/utils";
|
|
2
3
|
//#region src/state.ts
|
|
3
4
|
function state(id) {
|
|
4
5
|
return () => new StateRef(id, false);
|
|
@@ -85,13 +86,23 @@ var VirtualClock = class {
|
|
|
85
86
|
now() {
|
|
86
87
|
return this.#now;
|
|
87
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
|
+
}
|
|
88
99
|
setTimeout(ms, cb, options) {
|
|
89
100
|
const signal = options?.signal;
|
|
90
101
|
if (signal?.aborted) return -1;
|
|
91
102
|
const id = this.#nextId++;
|
|
92
103
|
const onAbort = trackAbort(signal, id, this.#timers);
|
|
93
104
|
this.#timers.set(id, {
|
|
94
|
-
deadline: this.#now + ms,
|
|
105
|
+
deadline: this.#now + this.#delay(ms, "setTimeout", false),
|
|
95
106
|
cb,
|
|
96
107
|
signal,
|
|
97
108
|
onAbort,
|
|
@@ -111,9 +122,10 @@ var VirtualClock = class {
|
|
|
111
122
|
if (signal?.aborted) return -1;
|
|
112
123
|
const id = this.#nextId++;
|
|
113
124
|
const onAbort = trackAbort(signal, id, this.#intervals);
|
|
125
|
+
const d = this.#delay(ms, "setInterval", true);
|
|
114
126
|
this.#intervals.set(id, {
|
|
115
|
-
ms,
|
|
116
|
-
next: this.#now +
|
|
127
|
+
ms: d,
|
|
128
|
+
next: this.#now + d,
|
|
117
129
|
cb,
|
|
118
130
|
signal,
|
|
119
131
|
onAbort
|
|
@@ -164,7 +176,8 @@ var VirtualClock = class {
|
|
|
164
176
|
}
|
|
165
177
|
}
|
|
166
178
|
advance(ms) {
|
|
167
|
-
|
|
179
|
+
if (!Number.isFinite(ms)) throw new RangeError(`[VirtualClock] invalid advance ms value: ${ms}`);
|
|
180
|
+
const target = this.#now + Math.max(0, ms);
|
|
168
181
|
while (true) {
|
|
169
182
|
const deadline = this.#findEarliestDeadline(target);
|
|
170
183
|
if (deadline === null) break;
|
|
@@ -203,9 +216,6 @@ var InternalQueue = class {
|
|
|
203
216
|
get length() {
|
|
204
217
|
return this.#queue.length - this.#index;
|
|
205
218
|
}
|
|
206
|
-
get isProcessing() {
|
|
207
|
-
return this.#processing;
|
|
208
|
-
}
|
|
209
219
|
push(...events) {
|
|
210
220
|
if (this.#stopped) return;
|
|
211
221
|
this.#queue.push(...events);
|
|
@@ -216,12 +226,6 @@ var InternalQueue = class {
|
|
|
216
226
|
this.#settledResolvers.push(resolve);
|
|
217
227
|
});
|
|
218
228
|
}
|
|
219
|
-
process(processEvent) {
|
|
220
|
-
this.#process((event) => {
|
|
221
|
-
processEvent(event);
|
|
222
|
-
return true;
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
229
|
processCancellable(processEvent) {
|
|
226
230
|
this.#process(processEvent);
|
|
227
231
|
}
|
|
@@ -254,35 +258,48 @@ var InternalQueue = class {
|
|
|
254
258
|
var Subscribers = class {
|
|
255
259
|
change = /* @__PURE__ */ new Set();
|
|
256
260
|
done = /* @__PURE__ */ new Set();
|
|
261
|
+
transition = /* @__PURE__ */ new Set();
|
|
257
262
|
#last = null;
|
|
258
263
|
seed(snapshot) {
|
|
259
264
|
this.#last = snapshot;
|
|
260
265
|
}
|
|
261
266
|
addChange(fn) {
|
|
262
267
|
this.change.add(fn);
|
|
263
|
-
|
|
268
|
+
const last = this.#last;
|
|
269
|
+
if (last) this.#safe(() => fn(last, last));
|
|
264
270
|
return () => this.change.delete(fn);
|
|
265
271
|
}
|
|
266
272
|
addDone(fn) {
|
|
267
273
|
this.done.add(fn);
|
|
268
274
|
return () => this.done.delete(fn);
|
|
269
275
|
}
|
|
276
|
+
addTransition(fn) {
|
|
277
|
+
this.transition.add(fn);
|
|
278
|
+
return () => this.transition.delete(fn);
|
|
279
|
+
}
|
|
270
280
|
emitChange(snapshot) {
|
|
271
281
|
const prev = this.#last ?? snapshot;
|
|
272
282
|
this.#last = snapshot;
|
|
273
|
-
for (const fn of this.change) fn(snapshot, prev);
|
|
283
|
+
for (const fn of this.change) this.#safe(() => fn(snapshot, prev));
|
|
274
284
|
}
|
|
275
285
|
emitDone() {
|
|
276
|
-
for (const fn of this.done) fn
|
|
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));
|
|
277
290
|
}
|
|
278
291
|
clear() {
|
|
279
292
|
this.change.clear();
|
|
280
293
|
this.done.clear();
|
|
294
|
+
this.transition.clear();
|
|
295
|
+
}
|
|
296
|
+
#safe(fn) {
|
|
297
|
+
Either.from(fn);
|
|
281
298
|
}
|
|
282
299
|
};
|
|
283
300
|
//#endregion
|
|
284
301
|
//#region src/snapshot.ts
|
|
285
|
-
function buildSnapshot(s, regions, context) {
|
|
302
|
+
function buildSnapshot(s, regions, context, opts = {}) {
|
|
286
303
|
const path = [s.name];
|
|
287
304
|
const regionSnapshots = {};
|
|
288
305
|
for (const [regionName, child] of Object.entries(regions)) regionSnapshots[regionName] = child.snapshot();
|
|
@@ -291,28 +308,41 @@ function buildSnapshot(s, regions, context) {
|
|
|
291
308
|
context,
|
|
292
309
|
regions: regionSnapshots
|
|
293
310
|
};
|
|
311
|
+
if (opts.payload !== void 0) snap.payload = opts.payload;
|
|
294
312
|
if (s.isFinal) snap.done = true;
|
|
313
|
+
if (opts.error !== void 0) snap.error = opts.error;
|
|
295
314
|
return snap;
|
|
296
315
|
}
|
|
297
316
|
//#endregion
|
|
298
317
|
//#region src/effects.ts
|
|
299
318
|
function runEffects(options) {
|
|
300
|
-
|
|
319
|
+
const pending = [];
|
|
301
320
|
const list = options.effects[options.state.name];
|
|
302
|
-
if (!list
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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 };
|
|
316
346
|
}
|
|
317
347
|
//#endregion
|
|
318
348
|
//#region src/dispatch.ts
|
|
@@ -350,13 +380,13 @@ var ActorBuilder = class {
|
|
|
350
380
|
#effects = {};
|
|
351
381
|
on(stateRef, eventRef, fn) {
|
|
352
382
|
const sName = stateRef.name;
|
|
353
|
-
const
|
|
354
|
-
(this.#transitions[sName] ??= {})[
|
|
383
|
+
const eType = eventRef.type;
|
|
384
|
+
(this.#transitions[sName] ??= {})[eType] = fn;
|
|
355
385
|
return this;
|
|
356
386
|
}
|
|
357
387
|
onAny(eventRef, fn) {
|
|
358
|
-
const
|
|
359
|
-
(this.#transitions["Any"] ??= {})[
|
|
388
|
+
const eType = eventRef.type;
|
|
389
|
+
(this.#transitions["Any"] ??= {})[eType] = fn;
|
|
360
390
|
return this;
|
|
361
391
|
}
|
|
362
392
|
effect(stateRef, fn) {
|
|
@@ -372,11 +402,8 @@ var ActorBuilder = class {
|
|
|
372
402
|
};
|
|
373
403
|
//#endregion
|
|
374
404
|
//#region src/actor.ts
|
|
375
|
-
function pickState(initial) {
|
|
376
|
-
return initial instanceof StateRef ? initial : initial.state;
|
|
377
|
-
}
|
|
378
405
|
function resolveInitial(initial) {
|
|
379
|
-
return
|
|
406
|
+
return initial instanceof StateRef ? initial : initial.state;
|
|
380
407
|
}
|
|
381
408
|
var Actor = class {
|
|
382
409
|
state;
|
|
@@ -412,7 +439,7 @@ var Actor = class {
|
|
|
412
439
|
setOutputHandler: (fn) => {
|
|
413
440
|
this.#outputHandler = fn;
|
|
414
441
|
},
|
|
415
|
-
pushInternal: (event) => this.#
|
|
442
|
+
pushInternal: (event) => this.#queue.push(event),
|
|
416
443
|
drainInternal: () => this.#drainInternal(),
|
|
417
444
|
abortEffects: () => this.#abortEffects()
|
|
418
445
|
});
|
|
@@ -424,47 +451,73 @@ var Actor = class {
|
|
|
424
451
|
transitions: built.transitions,
|
|
425
452
|
effects: built.effects
|
|
426
453
|
};
|
|
427
|
-
this.#internalIds = new Set((options.internal ?? []).map((e) => e.
|
|
428
|
-
this.#inputIds = new Set(options.inputs.map((e) => e.
|
|
454
|
+
this.#internalIds = new Set((options.internal ?? []).map((e) => e.type));
|
|
455
|
+
this.#inputIds = new Set(options.inputs.map((e) => e.type));
|
|
429
456
|
this.#internalBudget = options.internalBudget ?? 1e4;
|
|
430
457
|
this.clock = options.clock ?? new RealClock();
|
|
431
458
|
this.clock.setDrain?.(() => this.#drainInternal());
|
|
432
459
|
const initState = resolveInitial(options.initial);
|
|
433
460
|
const stateNames = new Set(options.states.map((s) => s.name));
|
|
434
|
-
if (!stateNames.has(initState.name))
|
|
461
|
+
if (!stateNames.has(initState.name)) throw new Error(`[Actor] initial state "${initState.name}" not found in declared states [${[...stateNames].join(", ")}]`);
|
|
435
462
|
this.state = initState;
|
|
463
|
+
this.#statePayload = options.initial instanceof StateRef ? void 0 : options.initial.payload;
|
|
436
464
|
this.#context = options.context ?? {};
|
|
437
465
|
this.#lastState = initState;
|
|
438
466
|
this.#contextHandle = new Context(() => this.#context, (value) => {
|
|
439
467
|
this.#context = value;
|
|
440
468
|
this.#contextWritten = true;
|
|
441
469
|
});
|
|
442
|
-
this.#subs.seed(this.snapshot());
|
|
443
470
|
if (options.regions) for (const [key, child] of Object.entries(options.regions)) {
|
|
444
471
|
this.#regions[key] = child;
|
|
445
472
|
const result = setOutputHandler(child, (event) => {
|
|
446
473
|
this.#queue.push(event);
|
|
447
474
|
this.#drainInternal();
|
|
448
475
|
});
|
|
449
|
-
if (result[0] !== void 0)
|
|
476
|
+
if (result[0] !== void 0) throw new Error(result[0].message);
|
|
450
477
|
this.#children.set(key, child);
|
|
451
478
|
}
|
|
479
|
+
this.#runEffects({ type: "__init" }, this.#statePayload);
|
|
480
|
+
this.#subs.seed(this.snapshot());
|
|
452
481
|
}
|
|
453
482
|
on(event, fn) {
|
|
454
483
|
if (event === "change") {
|
|
455
484
|
const cb = fn;
|
|
456
485
|
return this.#subs.addChange(cb);
|
|
457
486
|
}
|
|
487
|
+
if (event === "done") {
|
|
488
|
+
const cb = fn;
|
|
489
|
+
return this.#subs.addDone(cb);
|
|
490
|
+
}
|
|
458
491
|
const cb = fn;
|
|
459
|
-
return this.#subs.
|
|
492
|
+
return this.#subs.addTransition(cb);
|
|
460
493
|
}
|
|
461
494
|
settled() {
|
|
462
|
-
return this.#queue.settled();
|
|
495
|
+
return Promise.all([this.#queue.settled(), ...this.#pendingEffects]).then(() => void 0);
|
|
463
496
|
}
|
|
464
497
|
send(event) {
|
|
465
498
|
this.#dispatch(event);
|
|
466
499
|
this.#emitChangeIfDirty();
|
|
467
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
|
+
}
|
|
468
521
|
#emitChangeIfDirty() {
|
|
469
522
|
if (this.state === this.#lastState && !this.#contextWritten) return;
|
|
470
523
|
this.#lastState = this.state;
|
|
@@ -472,120 +525,240 @@ var Actor = class {
|
|
|
472
525
|
this.#subs.emitChange(this.snapshot());
|
|
473
526
|
}
|
|
474
527
|
#dispatch(event) {
|
|
475
|
-
if (this.state.isFinal) return;
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
+
});
|
|
482
577
|
}
|
|
483
578
|
#applyStateStep(event, transition) {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
+
}
|
|
487
591
|
});
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
return false;
|
|
592
|
+
return {
|
|
593
|
+
applied,
|
|
594
|
+
target
|
|
595
|
+
};
|
|
494
596
|
}
|
|
495
597
|
#applyAnyStep(event, transition, allowTransition) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
+
}
|
|
499
618
|
});
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
return false;
|
|
619
|
+
return {
|
|
620
|
+
emitted,
|
|
621
|
+
transitioned,
|
|
622
|
+
target
|
|
623
|
+
};
|
|
506
624
|
}
|
|
507
625
|
snapshot() {
|
|
508
|
-
return buildSnapshot(this.state, this.#regions, this.#context
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
626
|
+
return buildSnapshot(this.state, this.#regions, this.#context, {
|
|
627
|
+
error: this.#error ?? void 0,
|
|
628
|
+
payload: this.#statePayload
|
|
629
|
+
});
|
|
512
630
|
}
|
|
513
631
|
#abortEffects() {
|
|
514
632
|
this.#effectAbort?.abort();
|
|
515
633
|
this.#subs.clear();
|
|
516
634
|
}
|
|
517
635
|
#applyTransition(event, step) {
|
|
518
|
-
if (!step.state) return;
|
|
519
|
-
this.#effectAbort?.abort();
|
|
636
|
+
if (this.#error !== null || !step.state) return void 0;
|
|
520
637
|
const resolved = parseTarget(step);
|
|
521
|
-
if (!resolved) return;
|
|
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();
|
|
522
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
|
+
};
|
|
523
651
|
this.#runEffects(event, resolved.payload);
|
|
524
|
-
if (
|
|
652
|
+
if (resolved.state.isFinal && this.#error === null) this.#subs.emitDone();
|
|
653
|
+
return target;
|
|
525
654
|
}
|
|
526
655
|
#runEffects(event, statePayload) {
|
|
527
656
|
const list = this.#options.effects[this.state.name];
|
|
528
|
-
if (!list
|
|
657
|
+
if (!list) {
|
|
529
658
|
this.#effectAbort = null;
|
|
530
659
|
return;
|
|
531
660
|
}
|
|
532
|
-
const abort =
|
|
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({
|
|
533
668
|
effects: { [this.state.name]: list },
|
|
534
669
|
state: this.state,
|
|
535
670
|
statePayload,
|
|
536
671
|
event,
|
|
537
672
|
context: this.#contextHandle,
|
|
538
673
|
emit: (e) => {
|
|
674
|
+
if (this.#error !== null || abort.signal.aborted) return;
|
|
539
675
|
this.#queue.push(e);
|
|
540
676
|
this.#drainInternal();
|
|
541
677
|
},
|
|
542
|
-
clock: this.clock
|
|
678
|
+
clock: this.clock,
|
|
679
|
+
abort,
|
|
680
|
+
lastGood,
|
|
681
|
+
onError: (error) => this.#enterError("effect", event, error, lastGood)
|
|
543
682
|
});
|
|
544
|
-
this.#
|
|
683
|
+
this.#pendingEffects.push(...result.pending);
|
|
545
684
|
}
|
|
546
685
|
#drainInternal() {
|
|
547
686
|
if (this.#draining) return;
|
|
548
687
|
this.#draining = true;
|
|
549
|
-
let budgetExceeded = false;
|
|
550
688
|
try {
|
|
551
689
|
const budget = this.#internalBudget;
|
|
552
690
|
let count = 0;
|
|
553
691
|
this.#queue.processCancellable((event) => {
|
|
692
|
+
if (this.#error !== null) return false;
|
|
554
693
|
if (count >= budget) {
|
|
555
|
-
|
|
556
|
-
this.#abortEffects();
|
|
694
|
+
this.#enterError("budget", event, /* @__PURE__ */ new Error("internal event budget exceeded"));
|
|
557
695
|
return false;
|
|
558
696
|
}
|
|
559
697
|
count++;
|
|
560
|
-
if (this.#internalIds.has(event.
|
|
561
|
-
else
|
|
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
|
+
}
|
|
562
705
|
return true;
|
|
563
706
|
});
|
|
564
707
|
} finally {
|
|
565
708
|
this.#draining = false;
|
|
566
709
|
this.#emitChangeIfDirty();
|
|
567
710
|
}
|
|
568
|
-
|
|
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();
|
|
569
743
|
}
|
|
570
744
|
};
|
|
571
745
|
//#endregion
|
|
572
746
|
//#region src/event.ts
|
|
573
|
-
function event(
|
|
574
|
-
return () => new EventRef(
|
|
747
|
+
function event(type) {
|
|
748
|
+
return () => new EventRef(type);
|
|
575
749
|
}
|
|
576
750
|
var EventRef = class {
|
|
577
|
-
|
|
578
|
-
constructor(
|
|
579
|
-
this.
|
|
751
|
+
type;
|
|
752
|
+
constructor(type) {
|
|
753
|
+
this.type = type;
|
|
580
754
|
}
|
|
581
755
|
is(anyEvent) {
|
|
582
|
-
return !!anyEvent && typeof anyEvent === "object" && "
|
|
756
|
+
return !!anyEvent && typeof anyEvent === "object" && "type" in anyEvent && anyEvent.type === this.type;
|
|
583
757
|
}
|
|
584
758
|
create(payload) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
id: this.id
|
|
759
|
+
return payload === void 0 ? { type: this.type } : {
|
|
760
|
+
type: this.type,
|
|
761
|
+
payload
|
|
589
762
|
};
|
|
590
763
|
}
|
|
591
764
|
};
|
package/dist/internal.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as InternalEvent, t as AnyActor } from "./actor-internal-B4iAxUG_.mjs";
|
|
2
2
|
import { Either } from "@mantaq/utils";
|
|
3
3
|
//#region src/internal-registry.d.ts
|
|
4
4
|
interface ActorInternal {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mantaq/core",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.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": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@mantaq/utils": "0.0.
|
|
24
|
+
"@mantaq/utils": "0.0.2"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@stryker-mutator/core": "^9",
|
|
@@ -1,90 +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>(id: T): <Payload extends object | void = void>() => EventRef<T, Payload>;
|
|
24
|
-
type AnyEventRef = EventRef<string, object | void>;
|
|
25
|
-
type InternalEvent = {
|
|
26
|
-
id: string;
|
|
27
|
-
} & Record<string, unknown>;
|
|
28
|
-
type CreatedOfEvent<Id extends string, P> = P extends void ? {
|
|
29
|
-
id: Id;
|
|
30
|
-
} : P & {
|
|
31
|
-
id: Id;
|
|
32
|
-
};
|
|
33
|
-
declare class EventRef<const T extends string, Payload extends object | void = void> {
|
|
34
|
-
readonly id: T;
|
|
35
|
-
constructor(id: T);
|
|
36
|
-
is(anyEvent: unknown): anyEvent is CreatedOfEvent<T, Payload>;
|
|
37
|
-
create(): Payload extends void ? {
|
|
38
|
-
id: T;
|
|
39
|
-
} : void;
|
|
40
|
-
create(payload: Payload): Payload extends void ? {
|
|
41
|
-
id: T;
|
|
42
|
-
} : Payload & {
|
|
43
|
-
id: T;
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
//#endregion
|
|
47
|
-
//#region src/clock.d.ts
|
|
48
|
-
interface Clock {
|
|
49
|
-
setTimeout(ms: number, cb: () => void, options?: {
|
|
50
|
-
signal?: AbortSignal;
|
|
51
|
-
eventName?: string;
|
|
52
|
-
}): number;
|
|
53
|
-
clearTimeout(id: number): void;
|
|
54
|
-
setInterval(ms: number, cb: () => void, options?: {
|
|
55
|
-
signal?: AbortSignal;
|
|
56
|
-
}): number;
|
|
57
|
-
clearInterval(id: number): void;
|
|
58
|
-
now(): number;
|
|
59
|
-
/** Optional hook for draining queues after clock advances (VirtualClock). */
|
|
60
|
-
setDrain?(fn: () => void): void;
|
|
61
|
-
}
|
|
62
|
-
//#endregion
|
|
63
|
-
//#region src/actor-internal.d.ts
|
|
64
|
-
interface Snapshot<C = unknown> {
|
|
65
|
-
path: string[];
|
|
66
|
-
context: C;
|
|
67
|
-
regions: Record<string, Snapshot<unknown>>;
|
|
68
|
-
done?: boolean;
|
|
69
|
-
}
|
|
70
|
-
interface AnyActor<C = Record<string, unknown>> {
|
|
71
|
-
state: AnyStateRef;
|
|
72
|
-
clock: Clock;
|
|
73
|
-
regions: Record<string, AnyActor>;
|
|
74
|
-
send(event: AnyEventRef | InternalEvent): void;
|
|
75
|
-
snapshot(): Snapshot<C>;
|
|
76
|
-
on(event: "change", fn: (snapshot: Snapshot<C>, prev: Snapshot<C>) => void): () => void;
|
|
77
|
-
on(event: "done", fn: () => void): () => void;
|
|
78
|
-
settled(): Promise<void>;
|
|
79
|
-
context?: C;
|
|
80
|
-
options?: {
|
|
81
|
-
transitions?: Record<string, Record<string, unknown>>;
|
|
82
|
-
effects?: Record<string, unknown[]>;
|
|
83
|
-
states?: ReadonlyArray<{
|
|
84
|
-
name: string;
|
|
85
|
-
isFinal: boolean;
|
|
86
|
-
}>;
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
//#endregion
|
|
90
|
-
export { CreatedOfEvent as a, event as c, state as d, AnyEventRef as i, AnyStateRef as l, Snapshot as n, EventRef as o, Clock as r, InternalEvent as s, AnyActor as t, StateRef as u };
|