@mantaq/core 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -87
- package/dist/index.d.mts +140 -132
- package/dist/index.mjs +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @mantaq/core
|
|
2
2
|
|
|
3
|
-
State machine library. Small API surface
|
|
3
|
+
State machine library. Small API surface. Cook the primitives, not the framework.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -81,7 +81,7 @@ See [anderscan.github.io/mantaq](https://anderscan.github.io/mantaq) for full do
|
|
|
81
81
|
|
|
82
82
|
### Typed Actor Context
|
|
83
83
|
|
|
84
|
-
Cast the context type once at the constructor level. Handlers receive the typed context automatically
|
|
84
|
+
Cast the context type once at the constructor level. Handlers receive the typed context automatically. No casting needed per-handler.
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
87
|
interface AuthContext {
|
|
@@ -106,7 +106,7 @@ const actor = new Actor({
|
|
|
106
106
|
});
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
Context is user land
|
|
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:
|
|
110
110
|
|
|
111
111
|
```ts
|
|
112
112
|
const s = opts.context.get();
|
|
@@ -121,7 +121,7 @@ opts.context.set(s);
|
|
|
121
121
|
opts!.context.phoneNumber = event.payload.phoneNumber;
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
**Anti-pattern
|
|
124
|
+
**Anti-pattern. Casting in every handler:**
|
|
125
125
|
|
|
126
126
|
```ts
|
|
127
127
|
setup: (m) => {
|
|
@@ -160,7 +160,7 @@ setup: (m) => {
|
|
|
160
160
|
},
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
**Anti-pattern
|
|
163
|
+
**Anti-pattern. Skipping the generic parameter:**
|
|
164
164
|
|
|
165
165
|
```ts
|
|
166
166
|
// ❌ payload is `unknown`, no type safety
|
|
@@ -170,7 +170,7 @@ const badEvent = event("SIGN_IN")();
|
|
|
170
170
|
const goodEvent = event("SIGN_IN")<{ phoneNumber: string }>();
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
-
**Anti-pattern
|
|
173
|
+
**Anti-pattern. Sending raw objects instead of using `.create()`:**
|
|
174
174
|
|
|
175
175
|
```ts
|
|
176
176
|
// ✅ typed
|
|
@@ -182,7 +182,7 @@ actor.send({ type: "SIGN_IN", payload: { phoneNumber: "+1234567890" } });
|
|
|
182
182
|
|
|
183
183
|
### Effects and Event Typing
|
|
184
184
|
|
|
185
|
-
Effects run when entering a state. The `event` parameter in effects is typed `InternalEvent
|
|
185
|
+
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.
|
|
186
186
|
|
|
187
187
|
**Pass data to effects via state payload.** Set the payload in the transition return, read it from `state.payload` in the effect.
|
|
188
188
|
|
|
@@ -198,12 +198,15 @@ const actor = new Actor({
|
|
|
198
198
|
states: [idleState, loadingState, loadedState],
|
|
199
199
|
initial: idleState,
|
|
200
200
|
setup: (m) => {
|
|
201
|
-
m.effect(loadingState,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
201
|
+
m.effect(loadingState, {
|
|
202
|
+
name: "fetchUrl",
|
|
203
|
+
fn: ({ state, emit, clock }) => {
|
|
204
|
+
// state.payload is typed to the state's declared payload: { url: string }
|
|
205
|
+
const url = state.payload.url;
|
|
206
|
+
clock.setTimeout(1000, () => {
|
|
207
|
+
emit(doneEvent.create());
|
|
208
|
+
});
|
|
209
|
+
},
|
|
207
210
|
});
|
|
208
211
|
m.on(idleState, fetchEvent, (event) => {
|
|
209
212
|
return { state: loadingState.create({ url: event.payload.url }) };
|
|
@@ -213,7 +216,7 @@ const actor = new Actor({
|
|
|
213
216
|
});
|
|
214
217
|
```
|
|
215
218
|
|
|
216
|
-
**Context is also possible but less precise.** Context is shared across all states
|
|
219
|
+
**Context is also possible but less precise.** Context is shared across all states. Writing to context in one state leaks into others. Prefer state payload for data that belongs to a specific state entry.
|
|
217
220
|
|
|
218
221
|
```ts
|
|
219
222
|
// ✅ state payload — scoped to this state entry
|
|
@@ -226,12 +229,15 @@ opts!.context.set(s);
|
|
|
226
229
|
return { state: loadingState };
|
|
227
230
|
```
|
|
228
231
|
|
|
229
|
-
**Anti-pattern
|
|
232
|
+
**Anti-pattern. Depending on `event` in effects:**
|
|
230
233
|
|
|
231
234
|
```ts
|
|
232
|
-
m.effect(loadingState,
|
|
233
|
-
|
|
234
|
-
|
|
235
|
+
m.effect(loadingState, {
|
|
236
|
+
name: "fetchUrl",
|
|
237
|
+
fn: ({ event, emit }) => {
|
|
238
|
+
// ❌ event is InternalEvent — payload is unknown, not the triggering event's payload
|
|
239
|
+
emit(doneEvent.create()); // fine — but event.payload.url would be a type error
|
|
240
|
+
},
|
|
235
241
|
});
|
|
236
242
|
```
|
|
237
243
|
|
|
@@ -241,9 +247,9 @@ The `event` parameter exists for convenience (e.g., logging), not for business l
|
|
|
241
247
|
|
|
242
248
|
Mantaq uses two event queues: **external** (user-sent) and **internal** (effect-emitted). Understanding the difference prevents subtle bugs.
|
|
243
249
|
|
|
244
|
-
**External events** are sent via `actor.send()
|
|
250
|
+
**External events** are sent via `actor.send()`. They trigger transitions on the current state.
|
|
245
251
|
|
|
246
|
-
**Internal events** are emitted from effects via `emit()
|
|
252
|
+
**Internal events** are emitted from effects via `emit()`. They are queued and processed after the current transition completes.
|
|
247
253
|
|
|
248
254
|
```ts
|
|
249
255
|
const tickEvent = event("TICK")();
|
|
@@ -255,10 +261,13 @@ const actor = new Actor({
|
|
|
255
261
|
states: [idleState, connectedState],
|
|
256
262
|
initial: idleState,
|
|
257
263
|
setup: (m) => {
|
|
258
|
-
m.effect(idleState,
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
264
|
+
m.effect(idleState, {
|
|
265
|
+
name: "scheduleTick",
|
|
266
|
+
fn: ({ emit, clock }) => {
|
|
267
|
+
clock.setTimeout(1000, () => {
|
|
268
|
+
emit(tickEvent.create()); // goes to internal queue
|
|
269
|
+
});
|
|
270
|
+
},
|
|
262
271
|
});
|
|
263
272
|
m.on(idleState, connectEvent, () => ({ state: connectedState })); // external handler
|
|
264
273
|
m.on(idleState, tickEvent, () => ({})); // internal handler
|
|
@@ -266,9 +275,9 @@ const actor = new Actor({
|
|
|
266
275
|
});
|
|
267
276
|
```
|
|
268
277
|
|
|
269
|
-
**Ordering:** External events process one at a time. Internal events emitted during a transition process depth-first after that transition. Internal events from effects process after the effect completes
|
|
278
|
+
**Ordering:** External events process one at a time. Internal events emitted during a transition process depth-first after that transition. Internal events from effects process after the effect completes. This is a common source of bugs. If an effect emits an internal event and the actor is in a different state by the time that event processes, the handler may not exist or may behave unexpectedly.
|
|
270
279
|
|
|
271
|
-
**Anti-pattern
|
|
280
|
+
**Anti-pattern. Mixing internal/external incorrectly:**
|
|
272
281
|
|
|
273
282
|
```ts
|
|
274
283
|
// ❌ declaring a user-sent event as internal
|
|
@@ -280,7 +289,7 @@ inputs: [connectEvent],
|
|
|
280
289
|
|
|
281
290
|
### Effect Pattern
|
|
282
291
|
|
|
283
|
-
Effects run on state entry. They receive typed context, an AbortSignal, and an emit function.
|
|
292
|
+
Effects run on state entry. They receive typed context, an AbortSignal, and an emit function. Each effect carries a required camelCase `name` describing what it does — tests can assert on it (`harness.assertEffectRan(stateName, effectName)`) and executed effects appear in history as `{ stateName, effectName }`.
|
|
284
293
|
|
|
285
294
|
```ts
|
|
286
295
|
type MyContext = { retryCount: number; maxRetries: number };
|
|
@@ -297,17 +306,20 @@ function createActor() {
|
|
|
297
306
|
initial: idleState,
|
|
298
307
|
context: { retryCount: 0, maxRetries: 3 } as MyContext,
|
|
299
308
|
setup: (m) => {
|
|
300
|
-
m.effect(workingState,
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
m.effect(workingState, {
|
|
310
|
+
name: "attemptWork",
|
|
311
|
+
fn: ({ signal, context, emit, clock }) => {
|
|
312
|
+
// context is MyContext — typed from the constructor generic
|
|
313
|
+
if (context.retryCount >= context.maxRetries) {
|
|
314
|
+
emit(failedEvent.create({ error: "Max retries exceeded" }));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
clock.setTimeout(2000, () => {
|
|
319
|
+
if (signal.aborted) return; // ✅ check signal before emitting
|
|
320
|
+
emit(doneEvent.create());
|
|
321
|
+
});
|
|
322
|
+
},
|
|
311
323
|
});
|
|
312
324
|
m.on(idleState, startEvent, () => ({ state: workingState }));
|
|
313
325
|
m.on(workingState, doneEvent, () => ({ state: doneState }));
|
|
@@ -322,30 +334,36 @@ function createActor() {
|
|
|
322
334
|
}
|
|
323
335
|
```
|
|
324
336
|
|
|
325
|
-
**Anti-pattern
|
|
337
|
+
**Anti-pattern. Not checking `signal.aborted`:**
|
|
326
338
|
|
|
327
339
|
```ts
|
|
328
|
-
m.effect(workingState,
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
340
|
+
m.effect(workingState, {
|
|
341
|
+
name: "attemptWork",
|
|
342
|
+
fn: ({ signal, emit, clock }) => {
|
|
343
|
+
// ❌ if state changes before timeout, this still fires
|
|
344
|
+
clock.setTimeout(2000, () => {
|
|
345
|
+
emit(doneEvent.create());
|
|
346
|
+
});
|
|
347
|
+
},
|
|
333
348
|
});
|
|
334
349
|
```
|
|
335
350
|
|
|
336
351
|
```ts
|
|
337
|
-
m.effect(workingState,
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
352
|
+
m.effect(workingState, {
|
|
353
|
+
name: "attemptWork",
|
|
354
|
+
fn: ({ signal, emit, clock }) => {
|
|
355
|
+
// ✅ guard with abort check
|
|
356
|
+
clock.setTimeout(2000, () => {
|
|
357
|
+
if (signal.aborted) return;
|
|
358
|
+
emit(doneEvent.create());
|
|
359
|
+
});
|
|
360
|
+
},
|
|
343
361
|
});
|
|
344
362
|
```
|
|
345
363
|
|
|
346
364
|
### Snapshot & Restore
|
|
347
365
|
|
|
348
|
-
`actor.snapshot()` returns a `Snapshot
|
|
366
|
+
`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.
|
|
349
367
|
|
|
350
368
|
```ts
|
|
351
369
|
// Snapshot carries path, context, and regions — serialize it directly
|
|
@@ -359,7 +377,7 @@ if (raw) {
|
|
|
359
377
|
}
|
|
360
378
|
```
|
|
361
379
|
|
|
362
|
-
**Anti-pattern
|
|
380
|
+
**Anti-pattern. Re-serializing context by hand:**
|
|
363
381
|
|
|
364
382
|
```ts
|
|
365
383
|
// ❌ snapshot() already includes context — no manual step needed
|
|
@@ -410,7 +428,7 @@ const manager = new Actor({
|
|
|
410
428
|
matches(manager, "connected.health.healthy"); // ✅
|
|
411
429
|
```
|
|
412
430
|
|
|
413
|
-
**Anti-pattern
|
|
431
|
+
**Anti-pattern. Child outputs not declared as parent internal events:**
|
|
414
432
|
|
|
415
433
|
```ts
|
|
416
434
|
const child = new Actor({
|
|
@@ -433,7 +451,7 @@ const parent = new Actor({
|
|
|
433
451
|
|
|
434
452
|
### Error Handling
|
|
435
453
|
|
|
436
|
-
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
|
|
454
|
+
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.
|
|
437
455
|
|
|
438
456
|
```ts
|
|
439
457
|
const snap = actor.snapshot();
|
|
@@ -446,10 +464,10 @@ if (snap.error) {
|
|
|
446
464
|
|
|
447
465
|
Two deliberate exceptions to "every failure is loud":
|
|
448
466
|
|
|
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
|
|
467
|
+
- **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.
|
|
450
468
|
- **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"`).
|
|
451
469
|
|
|
452
|
-
A dead machine can be manually resumed with `actor.recover({ state, context })
|
|
470
|
+
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.
|
|
453
471
|
|
|
454
472
|
Catch errors inside effects and emit recovery events:
|
|
455
473
|
|
|
@@ -458,17 +476,20 @@ const doneEvent = event("WORK_DONE")();
|
|
|
458
476
|
const failedEvent = event("WORK_FAILED")<{ error: string }>();
|
|
459
477
|
|
|
460
478
|
setup: (m) => {
|
|
461
|
-
m.effect(workingState,
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
479
|
+
m.effect(workingState, {
|
|
480
|
+
name: "runRiskyOperation",
|
|
481
|
+
fn: ({ signal, emit, clock }) => {
|
|
482
|
+
clock.setTimeout(100, () => {
|
|
483
|
+
if (signal.aborted) return;
|
|
484
|
+
try {
|
|
485
|
+
const result = riskyOperation();
|
|
486
|
+
emit(doneEvent.create());
|
|
487
|
+
} catch (err) {
|
|
488
|
+
// ✅ emit error as internal event — lets transition handle it
|
|
489
|
+
emit(failedEvent.create({ error: String(err) }));
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
},
|
|
472
493
|
});
|
|
473
494
|
m.on(workingState, doneEvent, () => ({ state: doneState }));
|
|
474
495
|
m.on(workingState, failedEvent, (event) => {
|
|
@@ -478,7 +499,7 @@ setup: (m) => {
|
|
|
478
499
|
},
|
|
479
500
|
```
|
|
480
501
|
|
|
481
|
-
**Anti-pattern
|
|
502
|
+
**Anti-pattern. Re-throwing in effects:**
|
|
482
503
|
|
|
483
504
|
```ts
|
|
484
505
|
try {
|
|
@@ -489,7 +510,7 @@ try {
|
|
|
489
510
|
}
|
|
490
511
|
```
|
|
491
512
|
|
|
492
|
-
**Anti-pattern
|
|
513
|
+
**Anti-pattern. Throwing in transition handlers:**
|
|
493
514
|
|
|
494
515
|
```ts
|
|
495
516
|
m.on(idleState, submitEvent, (event) => {
|
|
@@ -514,7 +535,7 @@ m.on(idleState, submitEvent, (event, opts) => {
|
|
|
514
535
|
|
|
515
536
|
### Any Handler
|
|
516
537
|
|
|
517
|
-
Use `onAny` to intercept events across all states
|
|
538
|
+
Use `onAny` to intercept events across all states. Useful for universal error handling, cleanup, or logging.
|
|
518
539
|
|
|
519
540
|
```ts
|
|
520
541
|
setup: (m) => {
|
|
@@ -530,7 +551,7 @@ setup: (m) => {
|
|
|
530
551
|
},
|
|
531
552
|
```
|
|
532
553
|
|
|
533
|
-
**Anti-pattern
|
|
554
|
+
**Anti-pattern. Using `onAny` for everything:**
|
|
534
555
|
|
|
535
556
|
```ts
|
|
536
557
|
setup: (m) => {
|
|
@@ -574,8 +595,11 @@ const actor = new Actor({
|
|
|
574
595
|
initial: idle,
|
|
575
596
|
clock,
|
|
576
597
|
setup: (m) => {
|
|
577
|
-
m.effect(idle,
|
|
578
|
-
|
|
598
|
+
m.effect(idle, {
|
|
599
|
+
name: "armIdleTimeout",
|
|
600
|
+
fn: ({ emit, clock }) => {
|
|
601
|
+
clock.setTimeout(5000, () => emit(timer.create()));
|
|
602
|
+
},
|
|
579
603
|
});
|
|
580
604
|
m.on(idle, timer, () => ({ state: timedOut }));
|
|
581
605
|
},
|
|
@@ -588,7 +612,7 @@ expect(actor.state.name).toBe("timedOut");
|
|
|
588
612
|
expect(clock.hasPending()).toBe(false);
|
|
589
613
|
```
|
|
590
614
|
|
|
591
|
-
**Anti-pattern
|
|
615
|
+
**Anti-pattern. Using `new Date()` or `setTimeout` directly:**
|
|
592
616
|
|
|
593
617
|
```ts
|
|
594
618
|
// ❌ real timers — slow, flaky, non-deterministic
|
|
@@ -618,11 +642,14 @@ const actor = new Actor({
|
|
|
618
642
|
initial: loading,
|
|
619
643
|
clock,
|
|
620
644
|
setup: (m) => {
|
|
621
|
-
m.effect(loading,
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
645
|
+
m.effect(loading, {
|
|
646
|
+
name: "scheduleWork",
|
|
647
|
+
fn: ({ signal, clock }) => {
|
|
648
|
+
const id = clock.setTimeout(5000, () => {
|
|
649
|
+
/* ... */
|
|
650
|
+
});
|
|
651
|
+
signal.addEventListener("abort", () => clock.clearTimeout(id));
|
|
652
|
+
},
|
|
626
653
|
});
|
|
627
654
|
m.on(loading, cancel, () => ({ state: success }));
|
|
628
655
|
},
|
|
@@ -651,8 +678,11 @@ const actor = new Actor({
|
|
|
651
678
|
initial: active,
|
|
652
679
|
clock,
|
|
653
680
|
setup: (m) => {
|
|
654
|
-
m.effect(active,
|
|
655
|
-
|
|
681
|
+
m.effect(active, {
|
|
682
|
+
name: "startTicker",
|
|
683
|
+
fn: ({ emit, clock }) => {
|
|
684
|
+
clock.setInterval(1000, () => emit(tick.create()));
|
|
685
|
+
},
|
|
656
686
|
});
|
|
657
687
|
m.on(active, tick, () => {
|
|
658
688
|
count++;
|
|
@@ -665,7 +695,7 @@ clock.advance(3500);
|
|
|
665
695
|
expect(count).toBe(3); // fired at 1000, 2000, 3000
|
|
666
696
|
```
|
|
667
697
|
|
|
668
|
-
**Anti-pattern
|
|
698
|
+
**Anti-pattern. Expecting single `advance` to fire interval once:**
|
|
669
699
|
|
|
670
700
|
```ts
|
|
671
701
|
// ❌ advance(5000) fires interval at 1000, 2000, 3000, 4000, 5000
|
|
@@ -697,12 +727,12 @@ vp run mutation:core # mutation score must stay ≥ 90
|
|
|
697
727
|
|
|
698
728
|
**Test contract.** Behavior is pinned by four kinds of tests, split by filename:
|
|
699
729
|
|
|
700
|
-
- `*.test.ts
|
|
701
|
-
- `*.error.test.ts
|
|
702
|
-
- `*.property.test.ts
|
|
703
|
-
- `*.mutation.test.ts
|
|
730
|
+
- `*.test.ts`. Features and happy paths, hand-maintained.
|
|
731
|
+
- `*.error.test.ts`. Failure paths: warnings, budgets, abort, unregistered, the `__error` state.
|
|
732
|
+
- `*.property.test.ts`. Property-based invariants against a reference model (fast-check, replayable via `MANTAQ_SEED`).
|
|
733
|
+
- `*.mutation.test.ts`. Directed tests that kill specific mutants. Regenerable and thrown away between stryker runs.
|
|
704
734
|
|
|
705
|
-
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
|
|
735
|
+
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.
|
|
706
736
|
|
|
707
737
|
## License
|
|
708
738
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,40 +1,7 @@
|
|
|
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
1
|
//#region src/event.d.ts
|
|
23
2
|
declare function event<const T extends string>(type: T): <Payload extends object | void = void>() => EventRef<T, Payload>;
|
|
24
|
-
|
|
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> {
|
|
3
|
+
interface EventRef<T extends string = string, Payload extends object | void = void> {
|
|
36
4
|
readonly type: T;
|
|
37
|
-
constructor(type: T);
|
|
38
5
|
is(anyEvent: unknown): anyEvent is CreatedOfEvent<T, Payload>;
|
|
39
6
|
create(): Payload extends void ? {
|
|
40
7
|
type: T;
|
|
@@ -46,72 +13,72 @@ declare class EventRef<const T extends string, Payload extends object | void = v
|
|
|
46
13
|
payload: Payload;
|
|
47
14
|
};
|
|
48
15
|
}
|
|
16
|
+
type AnyEventRef = EventRef<string, object | void>;
|
|
17
|
+
type CreatedOfEvent<T extends string, P> = P extends void ? {
|
|
18
|
+
type: T;
|
|
19
|
+
} : {
|
|
20
|
+
type: T;
|
|
21
|
+
payload: P;
|
|
22
|
+
};
|
|
23
|
+
declare function EventRef<const T extends string, Payload extends object | void = void>(type: T): EventRef<T, Payload>;
|
|
49
24
|
//#endregion
|
|
50
25
|
//#region src/clock.d.ts
|
|
51
26
|
interface Clock {
|
|
52
|
-
setTimeout(ms: number,
|
|
53
|
-
signal?: AbortSignal;
|
|
54
|
-
eventName?: string;
|
|
55
|
-
}): number;
|
|
27
|
+
setTimeout(ms: number, options: ClockTimerOptions): number;
|
|
56
28
|
clearTimeout(id: number): void;
|
|
57
|
-
setInterval(ms: number,
|
|
58
|
-
signal?: AbortSignal;
|
|
59
|
-
}): number;
|
|
29
|
+
setInterval(ms: number, options: ClockIntervalOptions): number;
|
|
60
30
|
clearInterval(id: number): void;
|
|
61
31
|
now(): number;
|
|
62
32
|
/** Optional hook for draining queues after clock advances (VirtualClock). */
|
|
63
33
|
setDrain?(fn: () => void): void;
|
|
64
34
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
now(): number;
|
|
70
|
-
setTimeout(ms: number, cb: () => void, options?: {
|
|
71
|
-
signal?: AbortSignal;
|
|
72
|
-
eventName?: string;
|
|
73
|
-
}): number;
|
|
74
|
-
clearTimeout(id: number): void;
|
|
75
|
-
setInterval(ms: number, cb: () => void, options?: {
|
|
76
|
-
signal?: AbortSignal;
|
|
77
|
-
}): number;
|
|
78
|
-
clearInterval(id: number): void;
|
|
35
|
+
interface ClockTimerOptions {
|
|
36
|
+
cb: () => void;
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
eventName?: string;
|
|
79
39
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#private;
|
|
84
|
-
now(): number;
|
|
85
|
-
setTimeout(ms: number, cb: () => void, options?: {
|
|
86
|
-
signal?: AbortSignal;
|
|
87
|
-
eventName?: string;
|
|
88
|
-
}): number;
|
|
89
|
-
clearTimeout(id: number): void;
|
|
90
|
-
setInterval(ms: number, cb: () => void, options?: {
|
|
91
|
-
signal?: AbortSignal;
|
|
92
|
-
}): number;
|
|
93
|
-
clearInterval(id: number): void;
|
|
94
|
-
advance(ms: number): void;
|
|
95
|
-
hasPending(): boolean;
|
|
96
|
-
pendingTimers(): Array<{
|
|
97
|
-
id: number;
|
|
98
|
-
deadline: number;
|
|
99
|
-
ms: number;
|
|
100
|
-
eventName?: string;
|
|
101
|
-
}>;
|
|
102
|
-
setDrain(fn: () => void): void;
|
|
40
|
+
interface ClockIntervalOptions {
|
|
41
|
+
cb: () => void;
|
|
42
|
+
signal?: AbortSignal;
|
|
103
43
|
}
|
|
104
44
|
//#endregion
|
|
105
45
|
//#region src/context.d.ts
|
|
106
|
-
|
|
107
|
-
#private;
|
|
108
|
-
constructor(get: () => T, set: (value: T) => void);
|
|
46
|
+
interface Context<T> {
|
|
109
47
|
get(): T;
|
|
110
48
|
set(value: T): void;
|
|
111
49
|
}
|
|
50
|
+
/** Two-way handle over an owner's context: reads see live values, writes flag dirty state upstream. */
|
|
51
|
+
declare function Context<T>(options: {
|
|
52
|
+
get: () => T;
|
|
53
|
+
set: (value: T) => void;
|
|
54
|
+
}): Context<T>;
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/state.d.ts
|
|
57
|
+
declare function state<const T extends string>(name: T): <Payload = unknown>() => StateRef<T, Payload, false>;
|
|
58
|
+
type AnyStateRef = StateRef<string, unknown, boolean>;
|
|
59
|
+
interface RegionOptions {
|
|
60
|
+
initial: string;
|
|
61
|
+
states: Record<string, AnyStateRef>;
|
|
62
|
+
}
|
|
63
|
+
type RegionsOptions = Record<string, RegionOptions>;
|
|
64
|
+
interface StateRef<T extends string = string, Payload = unknown, IsFinal extends boolean = false> {
|
|
65
|
+
readonly name: T;
|
|
66
|
+
readonly isFinal: IsFinal;
|
|
67
|
+
/** @internal */ _regions?: RegionsOptions;
|
|
68
|
+
regions(options: RegionsOptions): this;
|
|
69
|
+
final(): StateRef<T, Payload, true>;
|
|
70
|
+
create(payload: Payload): {
|
|
71
|
+
state: StateRef<T, Payload, IsFinal>;
|
|
72
|
+
payload: Payload;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
declare function isStateRef(value: unknown): value is StateRef;
|
|
76
|
+
declare function StateRef<T extends string, Payload = unknown, IsFinal extends boolean = false>(name: T, options: {
|
|
77
|
+
isFinal: IsFinal;
|
|
78
|
+
}): StateRef<T, Payload, IsFinal>;
|
|
112
79
|
//#endregion
|
|
113
80
|
//#region src/actor-types.d.ts
|
|
114
|
-
type CreatedOf<E extends
|
|
81
|
+
type CreatedOf<E extends EventRef<string, object | void>> = E extends EventRef<infer Type, infer P> ? CreatedOfEvent<Type, P> : never;
|
|
115
82
|
type NonFinalStateRef<States extends AnyStateRef[]> = Extract<States[number], {
|
|
116
83
|
isFinal: false;
|
|
117
84
|
}>;
|
|
@@ -126,7 +93,9 @@ interface EffectInput<ActorContext, Payload = unknown> {
|
|
|
126
93
|
emit: (event: InternalEvent) => void;
|
|
127
94
|
clock: Clock;
|
|
128
95
|
}
|
|
129
|
-
type EffectFn<ActorContext, Payload = unknown> = (input: EffectInput<ActorContext, Payload>) => void | Promise<void
|
|
96
|
+
type EffectFn<ActorContext, Payload = unknown> = (input: EffectInput<ActorContext, Payload>) => void | Promise<void> | {
|
|
97
|
+
then(onFulfilled: unknown, onRejected: unknown): unknown;
|
|
98
|
+
};
|
|
130
99
|
type ErrorReason = "transition" | "effect" | "budget" | "output" | "internal" | "async" | "unhandled";
|
|
131
100
|
interface ErrorInfo {
|
|
132
101
|
error: unknown;
|
|
@@ -140,6 +109,8 @@ interface TransitionInfo {
|
|
|
140
109
|
from: string;
|
|
141
110
|
to: string;
|
|
142
111
|
transitioned: boolean;
|
|
112
|
+
/** Names of effects invoked on entry into `to`, in registration order. */
|
|
113
|
+
effects: string[];
|
|
143
114
|
}
|
|
144
115
|
type ErrorState = StateRef<"__error", unknown, true>;
|
|
145
116
|
type TransitionResult<AllowedState extends AnyStateRef = AnyStateRef, AllowedEmit extends string = string> = {
|
|
@@ -163,17 +134,17 @@ interface Snapshot<C = unknown> {
|
|
|
163
134
|
done?: boolean;
|
|
164
135
|
error?: ErrorInfo;
|
|
165
136
|
}
|
|
137
|
+
type SubscriberEventName = "change" | "done" | "transition" | "error" | "output";
|
|
138
|
+
type SubscriberHandler<C, E extends SubscriberEventName> = E extends "change" ? (snapshot: Snapshot<C>, prev: Snapshot<C>) => void : E extends "done" ? () => void : E extends "transition" ? (info: TransitionInfo) => void : E extends "error" ? (info: ErrorInfo) => void : E extends "output" ? (event: InternalEvent) => void : never;
|
|
166
139
|
interface AnyActor<C = Record<string, unknown>> {
|
|
167
140
|
state: AnyStateRef;
|
|
168
141
|
clock: Clock;
|
|
169
142
|
regions: Record<string, AnyActor>;
|
|
170
|
-
send(event:
|
|
143
|
+
send(event: InternalEvent): void;
|
|
171
144
|
snapshot(): Snapshot<C>;
|
|
172
|
-
on(event:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
on(event: "error", fn: (info: ErrorInfo) => void): () => void;
|
|
176
|
-
on(event: "output", fn: (event: InternalEvent) => void): () => void;
|
|
145
|
+
on<E extends SubscriberEventName>(event: E, options: {
|
|
146
|
+
fn: SubscriberHandler<C, E>;
|
|
147
|
+
}): () => void;
|
|
177
148
|
recover(target: {
|
|
178
149
|
state: AnyStateRef;
|
|
179
150
|
context: C;
|
|
@@ -192,42 +163,80 @@ interface AnyActor<C = Record<string, unknown>> {
|
|
|
192
163
|
dispose(): void;
|
|
193
164
|
}
|
|
194
165
|
//#endregion
|
|
166
|
+
//#region src/parse-handler.d.ts
|
|
167
|
+
type RuntimeTransitionHandler<States extends readonly AnyStateRef[], ActorContext> = (event: InternalEvent, options: {
|
|
168
|
+
context: Context<ActorContext>;
|
|
169
|
+
actor: AnyActor<ActorContext>;
|
|
170
|
+
}) => TransitionResult<States[number], string>;
|
|
171
|
+
//#endregion
|
|
195
172
|
//#region src/builder.d.ts
|
|
196
173
|
type EventTypeOf<E extends AnyEventRef> = E extends EventRef<infer Type, object | void> ? Type : never;
|
|
197
174
|
type PayloadOf<S extends AnyStateRef> = S extends StateRef<infer _Name, infer Payload, infer _IsFinal> ? Payload : never;
|
|
198
|
-
type TransitionHandler<States extends readonly AnyStateRef[], ActorContext> =
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
175
|
+
type TransitionHandler<States extends readonly AnyStateRef[], ActorContext> = RuntimeTransitionHandler<States, ActorContext>;
|
|
176
|
+
interface EffectEntry<ActorContext> {
|
|
177
|
+
name: string;
|
|
178
|
+
fn: EffectFn<ActorContext>;
|
|
179
|
+
}
|
|
202
180
|
interface BuiltMaps<States extends readonly AnyStateRef[], ActorContext> {
|
|
203
181
|
transitions: Record<string, Record<string, TransitionHandler<States, ActorContext>>>;
|
|
204
|
-
effects: Record<string, Array<
|
|
182
|
+
effects: Record<string, Array<EffectEntry<ActorContext>>>;
|
|
205
183
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
184
|
+
type HandlerFn<States extends readonly AnyStateRef[], _InputsInternal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext, E extends AnyEventRef> = (event: E extends EventRef<infer Type, infer P> ? CreatedOfEvent<Type, P> : never, opts: {
|
|
185
|
+
context: Context<ActorContext>;
|
|
186
|
+
actor: AnyActor<ActorContext>;
|
|
187
|
+
}) => TransitionResult<States[number], EventTypeOf<Outputs[number]>>;
|
|
188
|
+
interface ActorBuilder<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext> {
|
|
189
|
+
on<S extends States[number], E extends Inputs[number] | Internal[number]>(stateRef: S, options: {
|
|
190
|
+
eventRef: E;
|
|
191
|
+
handler: HandlerFn<States, Inputs | Internal, Outputs, ActorContext, E>;
|
|
192
|
+
}): ActorBuilder<States, Inputs, Internal, Outputs, ActorContext>;
|
|
193
|
+
onAny<E extends Inputs[number] | Internal[number]>(options: {
|
|
194
|
+
eventRef: E;
|
|
195
|
+
handler: HandlerFn<States, Inputs | Internal, Outputs, ActorContext, E>;
|
|
196
|
+
}): ActorBuilder<States, Inputs, Internal, Outputs, ActorContext>;
|
|
197
|
+
effect<S extends States[number]>(stateRef: S, definition: {
|
|
198
|
+
name: string;
|
|
199
|
+
fn: EffectFn<ActorContext, PayloadOf<S>>;
|
|
200
|
+
}): ActorBuilder<States, Inputs, Internal, Outputs, ActorContext>;
|
|
201
|
+
/** @internal */
|
|
202
|
+
build(): BuiltMaps<States, ActorContext>;
|
|
218
203
|
}
|
|
204
|
+
declare function ActorBuilder<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext>(): ActorBuilder<States, Inputs, Internal, Outputs, ActorContext>;
|
|
219
205
|
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;
|
|
220
206
|
//#endregion
|
|
207
|
+
//#region src/real-clock.d.ts
|
|
208
|
+
declare function RealClock(): Clock;
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/virtual-clock.d.ts
|
|
211
|
+
interface VirtualClock {
|
|
212
|
+
now(): number;
|
|
213
|
+
setTimeout(ms: number, options: ClockTimerOptions): number;
|
|
214
|
+
clearTimeout(id: number): void;
|
|
215
|
+
setInterval(ms: number, options: ClockIntervalOptions): number;
|
|
216
|
+
clearInterval(id: number): void;
|
|
217
|
+
advance(ms: number): void;
|
|
218
|
+
hasPending(): boolean;
|
|
219
|
+
pendingTimers(): Array<{
|
|
220
|
+
id: number;
|
|
221
|
+
deadline: number;
|
|
222
|
+
ms: number;
|
|
223
|
+
eventName?: string;
|
|
224
|
+
}>;
|
|
225
|
+
setDrain(fn: () => void): void;
|
|
226
|
+
}
|
|
227
|
+
declare function VirtualClock(): VirtualClock;
|
|
228
|
+
//#endregion
|
|
221
229
|
//#region src/actor.d.ts
|
|
222
|
-
type CreatedOf$1<E extends AnyEventRef> = E extends EventRef<infer Type, infer P> ? CreatedOfEvent<Type, P> : never;
|
|
223
|
-
type
|
|
230
|
+
type CreatedOf$1<E extends AnyEventRef$1> = E extends EventRef<infer Type, infer P> ? CreatedOfEvent<Type, P> : never;
|
|
231
|
+
type AnyEventRef$1 = AnyEventRef;
|
|
232
|
+
type InitialState<S extends AnyStateRef> = S extends StateRef<infer _N extends string, infer P, infer _I extends boolean> ? [unknown] extends [P] ? S | {
|
|
224
233
|
state: S;
|
|
225
234
|
payload?: P;
|
|
226
235
|
} : {
|
|
227
236
|
state: S;
|
|
228
237
|
payload: P;
|
|
229
238
|
} : never;
|
|
230
|
-
interface ActorOptions<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef[], Internal extends readonly AnyEventRef[], Outputs extends readonly AnyEventRef[], ActorContext = Record<string, unknown>> {
|
|
239
|
+
interface ActorOptions<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef$1[], Internal extends readonly AnyEventRef$1[], Outputs extends readonly AnyEventRef$1[], ActorContext = Record<string, unknown>> {
|
|
231
240
|
inputs: Inputs;
|
|
232
241
|
outputs?: Outputs;
|
|
233
242
|
internal?: Internal;
|
|
@@ -239,42 +248,41 @@ interface ActorOptions<States extends readonly AnyStateRef[], Inputs extends rea
|
|
|
239
248
|
setup: SetupFn<States, Inputs, Internal, Outputs, ActorContext>;
|
|
240
249
|
regions?: Record<string, AnyActor>;
|
|
241
250
|
}
|
|
242
|
-
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"> {
|
|
251
|
+
interface InternalActorOptions<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef$1[], Internal extends readonly AnyEventRef$1[], Outputs extends readonly AnyEventRef$1[], ActorContext = Record<string, unknown>> extends Omit<ActorOptions<States, Inputs, Internal, Outputs, ActorContext>, "setup"> {
|
|
243
252
|
transitions: TransitionDispatch<States, ActorContext>;
|
|
244
|
-
effects: Record<string, Array<
|
|
253
|
+
effects: Record<string, Array<EffectEntry<ActorContext>>>;
|
|
245
254
|
}
|
|
246
|
-
type TransitionDispatch<States extends readonly AnyStateRef[], ActorContext> = Record<string, Record<string,
|
|
255
|
+
type TransitionDispatch<States extends readonly AnyStateRef[], ActorContext> = Record<string, Record<string, TransitionStepFn<States, ActorContext> | undefined>>;
|
|
256
|
+
type TransitionStepFn<States extends readonly AnyStateRef[], ActorContext> = (event: InternalEvent, options: {
|
|
247
257
|
context: Context<ActorContext>;
|
|
248
|
-
actor: AnyActor
|
|
249
|
-
}) => TransitionResult<States[number], string
|
|
250
|
-
|
|
251
|
-
#private;
|
|
258
|
+
actor: AnyActor<ActorContext>;
|
|
259
|
+
}) => TransitionResult<States[number], string>;
|
|
260
|
+
interface Actor<States extends readonly AnyStateRef[], Inputs extends readonly AnyEventRef$1[], Internal extends readonly AnyEventRef$1[] = readonly [], Outputs extends readonly AnyEventRef$1[] = readonly [], ActorContext = Record<string, unknown>> extends AnyActor<ActorContext> {
|
|
252
261
|
state: States[number] | ErrorState;
|
|
253
262
|
readonly clock: Clock;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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;
|
|
263
|
+
readonly context: ActorContext;
|
|
264
|
+
readonly regions: Record<string, AnyActor>;
|
|
265
|
+
readonly options: InternalActorOptions<States, Inputs, Internal, Outputs, ActorContext>;
|
|
266
|
+
on<E extends SubscriberEventName>(event: E, options: {
|
|
267
|
+
fn: SubscriberHandler<ActorContext, E>;
|
|
268
|
+
}): () => void;
|
|
263
269
|
settled(): Promise<void>;
|
|
270
|
+
pendingEffectCount(): number;
|
|
264
271
|
send(event: CreatedOf$1<Inputs[number]>): void;
|
|
265
|
-
/**
|
|
266
|
-
* Restore a live state after `__error`. DANGER: caller supplies state+context,
|
|
267
|
-
* breaking determinism. Effects not re-run; processing resumes on next event.
|
|
268
|
-
*/
|
|
269
272
|
recover(target: {
|
|
270
273
|
state: States[number];
|
|
271
274
|
context: ActorContext;
|
|
272
275
|
}): void;
|
|
273
276
|
snapshot(): Snapshot<ActorContext>;
|
|
274
|
-
/** Push an internal event and drain. Unknown types route to output subscribers. */
|
|
275
277
|
inject(event: InternalEvent): void;
|
|
276
|
-
/** Stop for good: abort effect, clear queue and subscribers. Later sends/injects ignored. */
|
|
277
278
|
dispose(): void;
|
|
278
279
|
}
|
|
280
|
+
declare function Actor<const States extends readonly AnyStateRef[], const Inputs extends readonly AnyEventRef$1[], const Internal extends readonly AnyEventRef$1[] = readonly [], const Outputs extends readonly AnyEventRef$1[] = readonly [], ActorContext = Record<string, unknown>>(options: ActorOptions<States, Inputs, Internal, Outputs, ActorContext>): Actor<States, Inputs, Internal, Outputs, ActorContext>;
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/index.d.ts
|
|
283
|
+
type InternalEvent = {
|
|
284
|
+
type: string;
|
|
285
|
+
payload?: unknown;
|
|
286
|
+
};
|
|
279
287
|
//#endregion
|
|
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,
|
|
288
|
+
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, InternalEvent, type NonFinalStateRef, type PayloadOf, RealClock, type SetupFn, type Snapshot, StateRef, type TransitionHandler, type TransitionInfo, type TransitionResult, VirtualClock, event, isStateRef, state };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
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};
|
|
1
|
+
import{Either as e}from"@mantaq/utils";function t(e){let t=e;return typeof t==`function`?t:()=>({})}function n(e){return e}function r(){let e={},r={},i={on(n,{eventRef:r,handler:a}){let o=n.name,s=r.type;return(e[o]??={})[s]=t(a),i},onAny(n){let r=n.eventRef.type;return(e.Any??={})[r]=t(n.handler),i},effect(e,{name:t,fn:a}){return(r[e.name]??=[]).push({name:t,fn:n(a)}),i},build(){return{transitions:e,effects:r}}};return i}function i(e){return{get:e.get,set:e.set}}function a(e){return()=>c(e,{isFinal:!1})}const o=new WeakSet;function s(e){return typeof e==`object`&&!!e&&o.has(e)}function c(e,t){let n={name:e,isFinal:t.isFinal,regions(e){return n._regions=e,n},final(){let e=c(n.name,{isFinal:!0});return e._regions=n._regions,e},create(e){return{state:n,payload:e}}};return o.add(n),n}function l(e){let t=e.state;if(t)return`state`in t?{state:t.state,payload:t.payload}:{state:t,payload:e.payload}}function u(e){if(e instanceof Promise)return!0;let t=e;return typeof t==`object`&&!!t&&typeof t.then==`function`}function d(t){let n=[],r=[],i=t.effects[t.state.name];if(!i)return{pending:n,ran:r};for(let{name:a,fn:o}of i){if(t.abort.signal.aborted)break;let i,s=e.from(()=>(i=o({signal:t.abort.signal,state:{name:t.state.name,payload:t.statePayload},event:t.event,context:t.context,emit:t.emit,clock:t.clock}),!0));if(s[0]!==void 0){t.onError(s[0],t.lastGood);break}if(r.push(a),u(i)){let e=Promise.resolve(i).then(()=>void 0,e=>{t.abort.signal.aborted||t.onError(e,t.lastGood)});n.push(e)}}return{pending:n,ran:r}}function ee(e){return e??{}}function f(){let e=[],t=0,n=!1,r=!1,i=[];function a(){let e=i.splice(0);for(let t of e)t()}function o(i){if(!n){n=!0;try{for(;t<e.length&&!r;){let n=e[t];if(t++,!i(n)){r=!0;break}}}finally{e.length=0,t=0,n=!1,r=!1,a()}}}return{get length(){return e.length-t},push(...t){r||e.push(...t)},clear(){e.length=0,t=0,a()},settled(){return e.length-t===0&&!n?Promise.resolve():new Promise(e=>{i.push(e)})},processCancellable(e){o(e)}}}function p(){let e=Date.now(),t=new Map;function n(e){let n=t.get(e);n&&(n.signal.removeEventListener(`abort`,n.onAbort),t.delete(e))}return{now(){return Date.now()-e},setTimeout(e,{signal:r,cb:i}){if(r?.aborted)return-1;let a=Number(globalThis.setTimeout(()=>{n(a),i()},e));if(r){function e(){globalThis.clearTimeout(a),n(a)}t.set(a,{signal:r,onAbort:e}),r.addEventListener(`abort`,e,{once:!0})}return a},clearTimeout(e){globalThis.clearTimeout(e),n(e)},setInterval(e,{signal:r,cb:i}){if(r?.aborted)return-1;let a=Number(globalThis.setInterval(i,e));if(r){function e(){globalThis.clearInterval(a),n(a)}t.set(a,{signal:r,onAbort:e}),r.addEventListener(`abort`,e,{once:!0})}return a},clearInterval(e){globalThis.clearInterval(e),n(e)}}}function m(e){let{stateRef:t,regions:n,context:r}=e,i=[t.name],a={};for(let[e,t]of Object.entries(n))a[e]=t.snapshot();let o={path:i,context:r,regions:a};return e.payload!==void 0&&(o.payload=e.payload),t.isFinal&&(o.done=!0),e.error!==void 0&&(o.error={...e.error,context:h(e.error.context)}),o}function h(e){return typeof structuredClone==`function`?structuredClone(e):JSON.parse(JSON.stringify(e))}function g(){let t=new Set,n=new Set,r=new Set,i=new Set,a=new Set,o;function s(t){e.from(()=>(t(),!0))}return{change:t,done:n,transition:r,error:i,output:a,seed(e){o=e},addChange(e){t.add(e);let n=o;return n&&s(()=>e(n,n)),()=>t.delete(e)},addDone(e){return n.add(e),()=>n.delete(e)},addTransition(e){return r.add(e),()=>r.delete(e)},addError(e){i.add(e);let t=o?.error;return t&&s(()=>e(t)),()=>i.delete(e)},addOutput(e){return a.add(e),()=>a.delete(e)},emitChange(e){let n=o??e;o=e;for(let r of t)s(()=>r(e,n))},emitDone(){for(let e of n)s(e)},emitTransition(e){for(let t of r)s(()=>t(e))},emitError(e){for(let t of i)s(()=>t(e))},emitOutput(e){for(let t of a)t(e)},clear(){t.clear(),n.clear(),r.clear(),i.clear(),a.clear()}}}function _(e,t){let n=e?()=>{t.entries.delete(t.timerId)}:void 0;return e&&n&&e.addEventListener(`abort`,n,{once:!0}),n}function v(e){e.signal&&e.onAbort&&e.signal.removeEventListener(`abort`,e.onAbort)}function y(){let e=0,t=new Map,n=new Map,r=1,i=new Set;function a(e,t){if(!Number.isFinite(e))throw RangeError(`[VirtualClock] invalid ${t.method} ms value: ${e}`);return!0}function o(e,t){return a(e,{method:t.method}),e<=0?+!!t.interval:e}function s(e){let r=e,i=!1;for(let n of t.values())n.deadline<=e&&n.deadline<=r&&(r=n.deadline,i=!0);for(let t of n.values())t.next<=e&&t.next<=r&&(r=t.next,i=!0);return i?r:void 0}function c(e){let n=[];for(let[r,i]of t)i.deadline===e&&n.push(r);for(let e of n){let n=t.get(e);n&&(v(n),t.delete(e),n.cb())}}function l(t){let r=[];for(let[e,i]of n)i.next===t&&r.push(e);for(let t of r){let r=n.get(t);r&&(r.cb(),n.has(t)&&(r.next=e+r.ms))}}return{now(){return e},setTimeout(n,{signal:i,cb:a,eventName:s}){if(i?.aborted)return-1;let c=r++,l=_(i,{timerId:c,entries:t});return t.set(c,{deadline:e+o(n,{method:`setTimeout`,interval:!1}),cb:a,signal:i,onAbort:l,eventName:s}),c},clearTimeout(e){let n=t.get(e);n&&(v(n),t.delete(e))},setInterval(t,{signal:i,cb:a}){if(i?.aborted)return-1;let s=r++,c=_(i,{timerId:s,entries:n}),l=o(t,{method:`setInterval`,interval:!0});return n.set(s,{ms:l,next:e+l,cb:a,signal:i,onAbort:c}),s},clearInterval(e){let t=n.get(e);t&&(v(t),n.delete(e))},advance(t){a(t,{method:`advance`});let n=e+Math.max(0,t),r=-1,o=0;for(;;){let t=s(n);if(t===void 0)break;if(t===r){if(++o>=1e6)break}else o=0,r=t;e=t,c(t),l(t)}e=n;for(let e of i)e()},hasPending(){return t.size>0||n.size>0},pendingTimers(){let n=[];for(let[r,i]of t)n.push({id:r,deadline:i.deadline,ms:i.deadline-e,eventName:i.eventName});return n},setDrain(e){i.add(e)}}}function b(e){return`state`in e?e.state:e}function x(e,t){if(!e.has(t.candidate.name))throw Error(`[Actor] initial state "${t.candidate.name}" not found in declared states [${[...e].join(`, `)}]`);return!0}function S(t){let n=r();t.setup(n);let a=n.build(),o={...t,transitions:a.transitions,effects:a.effects},u=new Set((t.internal??[]).map(e=>e.type)),_=new Set(t.inputs.map(e=>e.type)),v=t.internalBudget??1e4,y=t.clock??p();y.setDrain?.(Y);let S,C,w=ee(t.context),T,E=!0,D,O={},k=f(),A=g(),j,M=!1,N=[],P,F,I=!1,L=!1,R=c(`__error`,{isFinal:!0}),z=i({get:()=>w,set:e=>{w=e,L=!0,E=!0}}),B=b(t.initial);x(new Set(t.states.map(e=>e.name)),{candidate:B}),S=B;let V=t.initial;if(C=!s(V)&&`payload`in V?V.payload:void 0,D=B,t.regions)for(let[e,n]of Object.entries(t.regions))O[e]=n,n.on(`output`,{fn:e=>{k.push(e),Y()}});let H={get state(){return S},clock:y,get regions(){return O},get context(){return w},get options(){return o},on(e,t){return{change:e=>A.addChange(e),done:e=>A.addDone(e),transition:e=>A.addTransition(e),error:e=>A.addError(e),output:e=>A.addOutput(e)}[e](t.fn)},settled(){return Q()},pendingEffectCount(){return N.length},send(e){I||(G(e),U())},recover:W,snapshot(){return K()},inject(e){ae(e)},dispose(){oe()}};J({type:`__init`},{payloadForState:C}),A.seed(K());function U(){S===D&&!L||(D=S,L=!1,A.emitChange(K()))}function W(e){P===void 0||I||(j?.abort(),j=void 0,k.clear(),k=f(),P=void 0,S=e.state,C=void 0,w=e.context,D=e.state,L=!1,E=!0,T=void 0,F=void 0,N=[],A.emitChange(K()))}function G(e){if(P!==void 0||S.isFinal)return;let t={...e,payload:e.payload??{}},n=S.name,r=F;F={state:S,context:w};try{let r=o.transitions,i=r[S.name]?.[e.type],a=r.Any?.[e.type],s=te(t,{stateTransition:i,anyTransition:a});ne(t,{from:n,handled:!!(i||a),outcome:s}),s.emitted||k.length>0?Y():!i&&!a&&u.has(e.type)&&Z(`unhandled`,{event:e,error:Error(`[Actor] internal event "${e.type}" emitted but no handler in state "${S.name}"`)})}finally{F=r}}function te(e,t){let n=t.stateTransition?re(e,{transition:t.stateTransition}):{applied:!1,target:void 0,effects:void 0};if(P===void 0){let r=ie(e,{transition:t.anyTransition,allowTransition:!n.applied});return{emitted:r.emitted,transitioned:n.applied||r.transitioned,target:n.target??r.target,effects:n.effects??r.effects}}return{emitted:!1,transitioned:!1}}function ne(e,t){P===void 0&&t.handled&&A.emitTransition({event:e,from:t.from,to:t.outcome.target??S.name,transitioned:t.outcome.transitioned,effects:t.outcome.effects??[]})}function re(e,t){let n=!1,r,i;return X(`transition`,{event:e,operation:()=>{let a=t.transition(e,{context:z,actor:$()});if(a.emit&&k.push(...a.emit),a.state){let t=q(e,{step:a});r=t.target,i=t.effects,n=!0}}}),{applied:n,target:r,effects:i}}function ie(e,t){if(t.transition===void 0)return{emitted:!1,transitioned:!1};let n=t.transition,r=!1,i=!1,a,o;return X(`transition`,{event:e,operation:()=>{let s=n(e,{context:z,actor:$()});if(s.state&&t.allowTransition){let t=q(e,{step:s});a=t.target,o=t.effects,i=!0}s.emit&&(k.push(...s.emit),r=!0)}}),{emitted:r,transitioned:i,target:a,effects:o}}function K(){return(T===void 0||E)&&(T=h(w),E=!1),m({stateRef:S,regions:O,context:T,error:P,payload:C})}function q(e,t){if(P!==void 0||!t.step.state)return{};let n=l(t.step);if(!n)return{};if(!s(n.state))return Z(`transition`,{event:e,error:Error(`invalid transition target`)}),{};j?.abort(),S=n.state;let r=S.name;C=n.payload,F={state:S,context:w};let i=J(e,{payloadForState:n.payload});return n.state.isFinal&&P===void 0&&A.emitDone(),{target:r,effects:i}}function J(e,t){if(I)return[];let n=o.effects[S.name];if(!n)return j=void 0,[];let r=new AbortController;j=r;let i=F??{state:S,context:w},a=d({effects:{[S.name]:n},state:S,statePayload:t.payloadForState,event:e,context:z,emit:e=>{P!==void 0||r.signal.aborted||(k.push(e),Y())},clock:y,abort:r,lastGood:i,onError:t=>Z(`effect`,{event:e,error:t})});for(let e of a.pending)N.push(e),e.finally(()=>{let t=N.indexOf(e);t!==-1&&N.splice(t,1)}).catch(()=>{});return a.ran}function Y(){if(!(M||I)){M=!0;try{let e=0;k.processCancellable(t=>{if(P!==void 0)return!1;if(e>=v)return Z(`budget`,{event:t,error:Error(`internal event budget exceeded`)}),!1;if(e++,u.has(t.type)||_.has(t.type))G(t);else if(!X(`output`,{event:t,operation:()=>A.emitOutput(t)}))return!1;return!0})}finally{M=!1,U()}}}function X(t,n){let r=e.from(()=>(n.operation(),!0));return r[0]===void 0||(Z(t,{event:n.event,error:r[0]}),!1)}function Z(e,t){if(P!==void 0)return;let n=t.lastGood??F??{state:S,context:w};P={error:t.error,state:n.state,context:n.context,event:t.event,reason:e},j?.abort(),S=R,C=void 0,D=R,L=!1,A.emitError(P),A.emitChange(K()),A.emitDone()}function ae(e){I||(k.push(e),Y())}function oe(){if(!I){I=!0,j?.abort(),j=void 0;for(let e of Object.values(O))e.dispose();O={},k.clear(),A.clear(),N=[]}}function Q(){return k.settled().then(()=>{if(N.length!==0)return Promise.all(N).then(()=>Q())})}function $(){return H}return H}const C=new Map;function w(e){let t=C.get(e);return t||(t=Symbol(`mantaq:event:${e}`),C.set(e,t)),t}const T=new WeakMap;function E(e){return()=>D(e)}function D(e){let t=w(e),n=function(e){return typeof e==`object`&&!!e&&T.get(e)?.has(t)===!0};function r(n){let r=n===void 0?{type:e}:{type:e,payload:n},i=T.get(r);return i||(i=new Set,T.set(r,i)),i.add(t),r}return{type:e,is:n,create:r}}export{S as Actor,i as Context,D as EventRef,p as RealClock,c as StateRef,y as VirtualClock,E as event,s as isStateRef,a as state};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mantaq/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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.4"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@stryker-mutator/core": "^9",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"@types/node": "^26.2.0",
|
|
30
30
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
31
31
|
"@vitest/coverage-v8": "4.1.10",
|
|
32
|
-
"typescript": "
|
|
32
|
+
"typescript": "6.0.3",
|
|
33
33
|
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.8",
|
|
34
34
|
"vite-plus": "0.2.8",
|
|
35
35
|
"vitest": "4.1.10",
|
|
36
|
-
"@mantaq/pbt": "0.0.
|
|
36
|
+
"@mantaq/pbt": "0.0.3"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "vp pack",
|