ihsm 0.0.21 → 0.0.22
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 +161 -8
- package/lib/cjs/index.d.ts +517 -56
- package/lib/cjs/index.js +279 -5
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/internal/defs.private.d.ts +10 -0
- package/lib/cjs/internal/dispatch.debug.js +2 -1
- package/lib/cjs/internal/dispatch.debug.js.map +1 -1
- package/lib/cjs/internal/dispatch.production.js +2 -1
- package/lib/cjs/internal/dispatch.production.js.map +1 -1
- package/lib/cjs/internal/dispatch.trace.js +8 -1
- package/lib/cjs/internal/dispatch.trace.js.map +1 -1
- package/lib/cjs/internal/hsm.d.ts +7 -1
- package/lib/cjs/internal/hsm.js +36 -3
- package/lib/cjs/internal/hsm.js.map +1 -1
- package/lib/cjs/internal/lookup.d.ts +15 -0
- package/lib/cjs/internal/lookup.js +32 -0
- package/lib/cjs/internal/lookup.js.map +1 -0
- package/lib/cjs/testing.d.ts +279 -0
- package/lib/cjs/testing.js +392 -0
- package/lib/cjs/testing.js.map +1 -0
- package/lib/esm/index.d.ts +517 -56
- package/lib/esm/index.js +275 -5
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/internal/defs.private.d.ts +10 -0
- package/lib/esm/internal/dispatch.debug.js +2 -1
- package/lib/esm/internal/dispatch.debug.js.map +1 -1
- package/lib/esm/internal/dispatch.production.js +2 -1
- package/lib/esm/internal/dispatch.production.js.map +1 -1
- package/lib/esm/internal/dispatch.trace.js +8 -1
- package/lib/esm/internal/dispatch.trace.js.map +1 -1
- package/lib/esm/internal/hsm.d.ts +7 -1
- package/lib/esm/internal/hsm.js +36 -3
- package/lib/esm/internal/hsm.js.map +1 -1
- package/lib/esm/internal/lookup.d.ts +15 -0
- package/lib/esm/internal/lookup.js +29 -0
- package/lib/esm/internal/lookup.js.map +1 -0
- package/lib/esm/testing.d.ts +279 -0
- package/lib/esm/testing.js +370 -0
- package/lib/esm/testing.js.map +1 -0
- package/package.json +20 -4
package/lib/esm/index.d.ts
CHANGED
|
@@ -199,7 +199,7 @@ export interface Properties<Context, Protocol extends {} | undefined> {
|
|
|
199
199
|
/**
|
|
200
200
|
* Fire-and-forget event posting API available on both {@link Hsm} (clients) and {@link State} (handlers).
|
|
201
201
|
*
|
|
202
|
-
* Events enqueue on the
|
|
202
|
+
* Events enqueue on the actor and run **one at a time, to completion** — no re-entrancy while a
|
|
203
203
|
* handler is active.
|
|
204
204
|
*
|
|
205
205
|
* @category State machine
|
|
@@ -208,7 +208,7 @@ export interface Base<Context, Protocol extends {} | undefined> extends Properti
|
|
|
208
208
|
/**
|
|
209
209
|
* Enqueue a **normal-priority** event for later dispatch on the active state.
|
|
210
210
|
*
|
|
211
|
-
* Returns immediately; the handler runs asynchronously when the
|
|
211
|
+
* Returns immediately; the handler runs asynchronously when the actor reaches this job.
|
|
212
212
|
* Dispatch walks the **prototype chain** from the current leaf upward until a method named
|
|
213
213
|
* `eventName` is found.
|
|
214
214
|
*
|
|
@@ -246,34 +246,6 @@ export interface Base<Context, Protocol extends {} | undefined> extends Properti
|
|
|
246
246
|
* ```
|
|
247
247
|
*/
|
|
248
248
|
post<EventName extends keyof Protocol>(eventName: PostedEvent<Protocol, EventName>, ...eventPayload: EventPayload<Protocol, EventName>): void;
|
|
249
|
-
/**
|
|
250
|
-
* Schedule a normal-priority {@link post} after a wall-clock delay.
|
|
251
|
-
*
|
|
252
|
-
* Uses `setTimeout` internally; when the timer fires, the event is enqueued like an ordinary
|
|
253
|
-
* `post`. Timers are **not** cancelled if the machine transitions or the scheduling handler throws.
|
|
254
|
-
*
|
|
255
|
-
* @typeParam EventName - Literal key of `Protocol` being scheduled
|
|
256
|
-
* @param millis - Delay in milliseconds before enqueueing (≥ 0). Subject to event-loop timer granularity
|
|
257
|
-
* @param eventName - Event name (same constraints as {@link post})
|
|
258
|
-
* @param eventPayload - Handler arguments tuple (same as {@link post})
|
|
259
|
-
*
|
|
260
|
-
* @remarks
|
|
261
|
-
* Available on {@link State} and {@link Hsm}. Typical pattern: handler schedules reminder,
|
|
262
|
-
* client waits with `await sleep(millis); await hsm.sync()`.
|
|
263
|
-
*
|
|
264
|
-
* Does **not** block the calling handler — returns as soon as the timer is registered.
|
|
265
|
-
*
|
|
266
|
-
* @example
|
|
267
|
-
* ```ts
|
|
268
|
-
* scheduleReminder(text: string): void {
|
|
269
|
-
* this.deferredPost(50, 'deliver', text);
|
|
270
|
-
* }
|
|
271
|
-
* deliver(text: string): void {
|
|
272
|
-
* this.ctx.message = text;
|
|
273
|
-
* }
|
|
274
|
-
* ```
|
|
275
|
-
*/
|
|
276
|
-
deferredPost<EventName extends keyof Protocol>(millis: number, eventName: PostedEvent<Protocol, EventName>, ...eventPayload: EventPayload<Protocol, EventName>): void;
|
|
277
249
|
}
|
|
278
250
|
/**
|
|
279
251
|
* Handler-facing API available inside state class methods (`this` / `this.hsm`).
|
|
@@ -347,9 +319,9 @@ export interface State<Context, Protocol extends {} | undefined> extends Base<Co
|
|
|
347
319
|
/**
|
|
348
320
|
* Pause the **current handler** without blocking the JavaScript event loop.
|
|
349
321
|
*
|
|
350
|
-
* Returns a Promise resolved after `millis` milliseconds via `setTimeout`. The
|
|
351
|
-
* remains **locked** to this handler until the Promise settles — other
|
|
352
|
-
* queue but do not run.
|
|
322
|
+
* Returns a Promise resolved after `millis` milliseconds via `setTimeout`. The actor
|
|
323
|
+
* remains **locked** to this handler until the Promise settles (run-to-completion) — other
|
|
324
|
+
* `post`/`call` jobs queue but do not run.
|
|
353
325
|
*
|
|
354
326
|
* @param millis - Sleep duration in milliseconds (≥ 0)
|
|
355
327
|
* @returns Promise that resolves (never rejects) when the delay elapses
|
|
@@ -367,6 +339,35 @@ export interface State<Context, Protocol extends {} | undefined> extends Base<Co
|
|
|
367
339
|
* ```
|
|
368
340
|
*/
|
|
369
341
|
sleep(millis: number): Promise<void>;
|
|
342
|
+
/**
|
|
343
|
+
* Schedule a normal-priority {@link post} after a wall-clock delay. **Handler-only.**
|
|
344
|
+
*
|
|
345
|
+
* Backed by the machine's {@link Port} timer service: when no custom port is supplied the
|
|
346
|
+
* runtime instantiates a {@link Port} whose `setTimeout`-based timer is used here. When
|
|
347
|
+
* the timer fires, the event is enqueued like an ordinary `post`. This is intentionally **not**
|
|
348
|
+
* on the external {@link Hsm} / {@link Actor} surface — clients schedule work via {@link post}.
|
|
349
|
+
*
|
|
350
|
+
* @typeParam EventName - Literal key of `Protocol` being scheduled
|
|
351
|
+
* @param millis - Delay in milliseconds before enqueueing (≥ 0). Subject to timer granularity
|
|
352
|
+
* @param eventName - Event name (same constraints as {@link post})
|
|
353
|
+
* @param eventPayload - Handler arguments tuple (same as {@link post})
|
|
354
|
+
*
|
|
355
|
+
* @remarks
|
|
356
|
+
* Does **not** block the calling handler — returns as soon as the timer is registered. Timers
|
|
357
|
+
* are **not** cancelled if the machine transitions or the scheduling handler throws. (Richer
|
|
358
|
+
* timer services — cancellation, virtual clocks — will arrive later through the port.)
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```ts
|
|
362
|
+
* scheduleReminder(text: string): void {
|
|
363
|
+
* this.deferredPost(50, 'deliver', text);
|
|
364
|
+
* }
|
|
365
|
+
* deliver(text: string): void {
|
|
366
|
+
* this.ctx.message = text;
|
|
367
|
+
* }
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
deferredPost<EventName extends keyof Protocol>(millis: number, eventName: PostedEvent<Protocol, EventName>, ...eventPayload: EventPayload<Protocol, EventName>): void;
|
|
370
371
|
/**
|
|
371
372
|
* Enqueue a **hi-priority** event processed before normal {@link post} jobs from the same turn.
|
|
372
373
|
*
|
|
@@ -409,7 +410,7 @@ export interface Hsm<Context = Any, Protocol extends {} | undefined = undefined>
|
|
|
409
410
|
/** @inheritdoc State.ctx */
|
|
410
411
|
readonly ctx: Context;
|
|
411
412
|
/**
|
|
412
|
-
* Wait until all previously enqueued
|
|
413
|
+
* Wait until all previously enqueued actor work completes through a **sync marker**.
|
|
413
414
|
*
|
|
414
415
|
* Returns a Promise resolved when the marker job reaches the front of the queue and runs —
|
|
415
416
|
* meaning every job enqueued **before** this `sync()` call has finished (handlers, transitions,
|
|
@@ -437,7 +438,7 @@ export interface Hsm<Context = Any, Protocol extends {} | undefined = undefined>
|
|
|
437
438
|
* Atomically replace the active leaf state and context **without** running `onExit` / `onEntry`.
|
|
438
439
|
*
|
|
439
440
|
* Used for persistence rehydration, snapshot restore, time-travel debugging, and test fixtures.
|
|
440
|
-
* Does not enqueue
|
|
441
|
+
* Does not enqueue dispatch jobs — the next `post`/`call` runs from the restored configuration.
|
|
441
442
|
*
|
|
442
443
|
* @param state - Leaf or composite state **class** to activate (prototype switched immediately)
|
|
443
444
|
* @param ctx - New context object (replaces {@link ctx} reference entirely)
|
|
@@ -455,7 +456,7 @@ export interface Hsm<Context = Any, Protocol extends {} | undefined = undefined>
|
|
|
455
456
|
*/
|
|
456
457
|
restore(state: StateClass<Context, Protocol>, ctx: Context): void;
|
|
457
458
|
/**
|
|
458
|
-
* Invoke a **service** handler and await its typed result over the
|
|
459
|
+
* Invoke a **service** handler and await its typed result over the actor's run-to-completion dispatch.
|
|
459
460
|
*
|
|
460
461
|
* Enqueues a dispatch job like {@link post}, but the runtime prepends `resolve` and `reject`
|
|
461
462
|
* callbacks to the handler invocation. The returned Promise settles when the handler calls
|
|
@@ -471,7 +472,7 @@ export interface Hsm<Context = Any, Protocol extends {} | undefined = undefined>
|
|
|
471
472
|
* {@link UnhandledEventError} if dispatch fails before the service runs
|
|
472
473
|
*
|
|
473
474
|
* @remarks
|
|
474
|
-
* - Same **serialized
|
|
475
|
+
* - Same **serialized**, run-to-completion dispatch as `post` — no concurrent handler re-entrancy
|
|
475
476
|
* - Return type {@link ServiceResponse} is inferred from `Protocol[eventName]`
|
|
476
477
|
* - Use {@link ResolveCallback} / {@link RejectCallback} in handler signatures for clarity
|
|
477
478
|
* - `async` handlers should `await` work then call `resolve(result)`
|
|
@@ -493,10 +494,12 @@ export interface Hsm<Context = Any, Protocol extends {} | undefined = undefined>
|
|
|
493
494
|
*
|
|
494
495
|
* @remarks
|
|
495
496
|
* Collisions with `keyof State` become `never`, preventing `post('transition', …)` at compile time.
|
|
497
|
+
* Service-shaped keys (see {@link IsServiceMethod}) also become `never` — they must be invoked with
|
|
498
|
+
* {@link Hsm.call}, so `post('getBalance', …)` is a compile error (proposal T2).
|
|
496
499
|
*
|
|
497
500
|
* @category Event handler
|
|
498
501
|
*/
|
|
499
|
-
export type PostedEvent<Protocol extends {} | undefined, EventName extends keyof Protocol> = Protocol extends undefined ? string : EventName extends keyof State<any, any> ? never : EventName;
|
|
502
|
+
export type PostedEvent<Protocol extends {} | undefined, EventName extends keyof Protocol> = Protocol extends undefined ? string : EventName extends keyof State<any, any> ? never : IsServiceMethod<Protocol[EventName]> extends true ? never : EventName;
|
|
500
503
|
/**
|
|
501
504
|
* Tuple of arguments for {@link Base.post} after the event name, inferred from the handler signature.
|
|
502
505
|
*
|
|
@@ -520,11 +523,378 @@ export type EventPayload<Protocol extends {} | undefined, EventName extends keyo
|
|
|
520
523
|
* @typeParam Context - Domain context carried by the machine
|
|
521
524
|
* @typeParam Protocol - Event/service vocabulary
|
|
522
525
|
*
|
|
526
|
+
* @remarks
|
|
527
|
+
* The prototype constraint is the **port-less** handler contract ({@link State} &
|
|
528
|
+
* {@link StateEvents}) rather than {@link TopState} itself. This keeps the optional
|
|
529
|
+
* {@link TopState.port} member (which varies with the `Port` type parameter) out of
|
|
530
|
+
* transition / `restore` typing, so machines that declare a port remain assignable
|
|
531
|
+
* wherever a plain `StateClass` is expected.
|
|
532
|
+
*
|
|
523
533
|
* @category State machine
|
|
524
534
|
*/
|
|
525
535
|
export type StateClass<Context = Any, Protocol extends {} | undefined = undefined> = Function & {
|
|
526
|
-
prototype:
|
|
536
|
+
prototype: State<Context, Protocol> & StateEvents<Context, Protocol>;
|
|
527
537
|
};
|
|
538
|
+
/**
|
|
539
|
+
* Resource teardown handle returned alongside subscription-style port results.
|
|
540
|
+
*
|
|
541
|
+
* `dispose()` must be **idempotent** — calling it more than once is a no-op. Ports hand
|
|
542
|
+
* one back via {@link ResultWithSubscription}; the state machine owns it and disposes it
|
|
543
|
+
* when the corresponding observation is no longer wanted.
|
|
544
|
+
*
|
|
545
|
+
* @category Port
|
|
546
|
+
*/
|
|
547
|
+
export interface Disposable {
|
|
548
|
+
/** Release the resource / detach listeners. Safe to call repeatedly. */
|
|
549
|
+
dispose(): void;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* A port result paired with the {@link Disposable} that tears down its subscription.
|
|
553
|
+
*
|
|
554
|
+
* Returned by port methods that both produce a value (e.g. a process id) **and** wire
|
|
555
|
+
* ongoing observations. The machine stores `value` and is responsible for calling
|
|
556
|
+
* `subscription.dispose()` during teardown.
|
|
557
|
+
*
|
|
558
|
+
* @typeParam Result - The immediate value produced by the port call
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```ts
|
|
562
|
+
* spawn(spec: SpawnSpec): ResultWithSubscription<number> {
|
|
563
|
+
* const child = spawnProcess(spec);
|
|
564
|
+
* const bag = wireListeners(child, this.hsm());
|
|
565
|
+
* return { value: child.pid, subscription: bag };
|
|
566
|
+
* }
|
|
567
|
+
* ```
|
|
568
|
+
*
|
|
569
|
+
* @category Port
|
|
570
|
+
*/
|
|
571
|
+
export interface ResultWithSubscription<Result> {
|
|
572
|
+
/** Immediate result of the port call. */
|
|
573
|
+
readonly value: Result;
|
|
574
|
+
/** Teardown handle for the observations the call established. */
|
|
575
|
+
readonly subscription: Disposable;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* A single recorded interaction: an event name plus its payload.
|
|
579
|
+
*
|
|
580
|
+
* Produced both by {@link testing!TestPort} (the messages a test double records) and by the
|
|
581
|
+
* {@link testing!TestActor} `subscribe` observer stream (every event posted through the machine).
|
|
582
|
+
*
|
|
583
|
+
* @category Testing
|
|
584
|
+
*/
|
|
585
|
+
export interface TracedMessage {
|
|
586
|
+
/** Event/service name, or a free-form label recorded by a {@link testing!TestPort}. */
|
|
587
|
+
readonly event: string;
|
|
588
|
+
/** Arguments captured with the event (a defensive copy). */
|
|
589
|
+
readonly payload: readonly unknown[];
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Observer invoked for **every** event posted through a {@link testing!TestActor}.
|
|
593
|
+
*
|
|
594
|
+
* Registered via {@link testing!TestActor} `subscribe` — a capability unique to the test surface.
|
|
595
|
+
* Wire it to {@link testing!TestPort.record} to build a golden trace on the port under test.
|
|
596
|
+
*
|
|
597
|
+
* @category Testing
|
|
598
|
+
*/
|
|
599
|
+
export type EventObserver = (message: TracedMessage) => void;
|
|
600
|
+
/**
|
|
601
|
+
* The effective protocol a machine **dispatches** over: the public {@link Protocol} merged
|
|
602
|
+
* with its `InternalProtocol`.
|
|
603
|
+
*
|
|
604
|
+
* Handlers (and the `Port` back-channel) see this union; external clients see only the
|
|
605
|
+
* public half. Legacy untyped machines (`Protocol extends undefined`) stay untyped.
|
|
606
|
+
*
|
|
607
|
+
* @typeParam Protocol - Public event/service vocabulary (client-callable)
|
|
608
|
+
* @typeParam InternalProtocol - Events only a port may post (e.g. `onSpawn`, `onExit`)
|
|
609
|
+
*
|
|
610
|
+
* @category State machine
|
|
611
|
+
*/
|
|
612
|
+
export type Dispatch<Protocol extends {} | undefined, InternalProtocol extends {}> = {} extends InternalProtocol ? Protocol : Protocol extends undefined ? undefined : Protocol & InternalProtocol;
|
|
613
|
+
/**
|
|
614
|
+
* Compile-time guard asserting the public and internal protocols share **no** event names.
|
|
615
|
+
*
|
|
616
|
+
* Resolves to `true` when `keyof Public` and `keyof Internal` are disjoint; otherwise to a
|
|
617
|
+
* descriptive tuple that fails the `extends true` constraint on {@link makeActor} /
|
|
618
|
+
* {@link testing!makeTestActor}, surfacing the overlapping keys at the call site.
|
|
619
|
+
*
|
|
620
|
+
* @typeParam Public - Public protocol
|
|
621
|
+
* @typeParam Internal - Internal protocol
|
|
622
|
+
*
|
|
623
|
+
* @category State machine
|
|
624
|
+
*/
|
|
625
|
+
export type Disjoint<Public, Internal> = Extract<keyof Public, keyof Internal> extends never ? true : ['ihsm: public and internal protocols must not share keys', Extract<keyof Public, keyof Internal>];
|
|
626
|
+
/**
|
|
627
|
+
* Phantom type carrier that lets a {@link TopState} subclass expose its four type parameters
|
|
628
|
+
* for extraction. It never exists at runtime — it is a `declare`d marker (see {@link TopState}).
|
|
629
|
+
*
|
|
630
|
+
* @typeParam Context - Domain context
|
|
631
|
+
* @typeParam Public - Public protocol
|
|
632
|
+
* @typeParam Internal - Internal (port-driven) protocol
|
|
633
|
+
* @typeParam Port - Port type
|
|
634
|
+
*
|
|
635
|
+
* @category State machine
|
|
636
|
+
*/
|
|
637
|
+
export interface MachineTypes<Context, Public, Internal, Port> {
|
|
638
|
+
readonly context: Context;
|
|
639
|
+
readonly public: Public;
|
|
640
|
+
readonly internal: Internal;
|
|
641
|
+
readonly port: Port;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Extracts the **context** type from a {@link TopState} subclass — making the `TopState` the
|
|
645
|
+
* single point where the four machine types are declared.
|
|
646
|
+
*
|
|
647
|
+
* @typeParam T - A {@link TopState} subclass (instance type, e.g. `ConnTop`)
|
|
648
|
+
*
|
|
649
|
+
* @category State machine
|
|
650
|
+
*/
|
|
651
|
+
export type MachineContext<T> = T extends {
|
|
652
|
+
readonly __ihsm: MachineTypes<infer Context, any, any, any>;
|
|
653
|
+
} ? Context : never;
|
|
654
|
+
/**
|
|
655
|
+
* Extracts the **public** protocol from a {@link TopState} subclass.
|
|
656
|
+
*
|
|
657
|
+
* @typeParam T - A {@link TopState} subclass (instance type)
|
|
658
|
+
*
|
|
659
|
+
* @category State machine
|
|
660
|
+
*/
|
|
661
|
+
export type MachinePublic<T> = T extends {
|
|
662
|
+
readonly __ihsm: MachineTypes<any, infer Public, any, any>;
|
|
663
|
+
} ? Public : never;
|
|
664
|
+
/**
|
|
665
|
+
* Extracts the **internal** protocol from a {@link TopState} subclass. The result is always
|
|
666
|
+
* within `{} | undefined`, so it can drive {@link PostedEvent} / {@link EventPayload} directly.
|
|
667
|
+
*
|
|
668
|
+
* @typeParam T - A {@link TopState} subclass (instance type)
|
|
669
|
+
*
|
|
670
|
+
* @category State machine
|
|
671
|
+
*/
|
|
672
|
+
export type MachineInternal<T> = T extends {
|
|
673
|
+
readonly __ihsm: MachineTypes<any, any, infer Internal, any>;
|
|
674
|
+
} ? (Internal extends {} | undefined ? Internal : never) : never;
|
|
675
|
+
/**
|
|
676
|
+
* Extracts the **port** type from a {@link TopState} subclass.
|
|
677
|
+
*
|
|
678
|
+
* @typeParam T - A {@link TopState} subclass (instance type)
|
|
679
|
+
*
|
|
680
|
+
* @category State machine
|
|
681
|
+
*/
|
|
682
|
+
export type MachinePort<T> = T extends {
|
|
683
|
+
readonly __ihsm: MachineTypes<any, any, any, infer Port>;
|
|
684
|
+
} ? Port : never;
|
|
685
|
+
/**
|
|
686
|
+
* Restricted handle a {@link Port} uses to post **internal** events back into its machine.
|
|
687
|
+
*
|
|
688
|
+
* It is the {@link Base} surface narrowed to the `InternalProtocol`, so a port can only
|
|
689
|
+
* `post` the events it is allowed to raise — never public commands.
|
|
690
|
+
*
|
|
691
|
+
* @typeParam Context - Domain context
|
|
692
|
+
* @typeParam InternalProtocol - Events the port may post
|
|
693
|
+
*
|
|
694
|
+
* @category Port
|
|
695
|
+
*/
|
|
696
|
+
export type InboundPoster<Context, InternalProtocol extends {} | undefined> = Base<Context, InternalProtocol>;
|
|
697
|
+
/**
|
|
698
|
+
* Outbound boundary between a machine and the impure world (processes, sockets, timers).
|
|
699
|
+
*
|
|
700
|
+
* Passed as the `port` instance to {@link makeActor} / {@link testing!makeTestActor} (or defaulted to a
|
|
701
|
+
* {@link Port}), and surfaced to handlers as {@link TopState.port}. The factory binds the
|
|
702
|
+
* port's {@link PortHandle.actor | actor} lazily, so the port can post internal events back via
|
|
703
|
+
* {@link PortHandle.hsm}.
|
|
704
|
+
*
|
|
705
|
+
* @typeParam Context - Domain context
|
|
706
|
+
* @typeParam InternalProtocol - Events this port may post inward
|
|
707
|
+
*
|
|
708
|
+
* @category Port
|
|
709
|
+
*/
|
|
710
|
+
export interface PortHandle<Context = Any, InternalProtocol extends {} | undefined = undefined> {
|
|
711
|
+
/**
|
|
712
|
+
* The machine handle this port posts internal events through. **Bound lazily** by the runtime
|
|
713
|
+
* ({@link makeHsm} / {@link makeActor} / {@link testing!makeTestActor}) right after the actor is
|
|
714
|
+
* constructed — so a port is created with no constructor arguments and wired up afterwards.
|
|
715
|
+
* `undefined` before binding / after teardown.
|
|
716
|
+
*/
|
|
717
|
+
actor: InboundPoster<Context, InternalProtocol> | undefined;
|
|
718
|
+
/** The bound machine handle (same as {@link PortHandle.actor}); `undefined` before binding. */
|
|
719
|
+
hsm(): InboundPoster<Context, InternalProtocol> | undefined;
|
|
720
|
+
}
|
|
721
|
+
/** Opaque timer handle returned by {@link Port.setTimeout} / {@link Port.setInterval}. */
|
|
722
|
+
export type TimerHandle = number;
|
|
723
|
+
/**
|
|
724
|
+
* Standard JavaScript random-generation surface exposed by {@link Port} and mocked by
|
|
725
|
+
* {@link testing!TestPort}.
|
|
726
|
+
*
|
|
727
|
+
* Route every nondeterministic draw through the machine's port — never `Math.random()` or
|
|
728
|
+
* `crypto.*` directly in handlers — so tests can script values with
|
|
729
|
+
* {@link testing!TestPort.feedRandom | feedRandom} / {@link testing!TestPort.feedCryptoRandom | feedCryptoRandom} /
|
|
730
|
+
* {@link testing!TestPort.feedUUID | feedUUID} / {@link testing!TestPort.feedRandomBytes | feedRandomBytes}.
|
|
731
|
+
*
|
|
732
|
+
* @category Port
|
|
733
|
+
*/
|
|
734
|
+
export interface RandomService {
|
|
735
|
+
/** Uniform in `[0, 1)` — `Math.random()`. */
|
|
736
|
+
random(): number;
|
|
737
|
+
/** Uniform in `[0, 1)` — `crypto.random()` when available, otherwise `Math.random()`. */
|
|
738
|
+
cryptoRandom(): number;
|
|
739
|
+
/** RFC 4122 UUID — `crypto.randomUUID()`. */
|
|
740
|
+
randomUUID(): string;
|
|
741
|
+
/** In-place fill — `crypto.getRandomValues()`. */
|
|
742
|
+
getRandomValues<T extends ArrayBufferView>(array: T): T;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* External, public-only view of a machine returned by {@link makeActor}.
|
|
746
|
+
*
|
|
747
|
+
* Identical to {@link Hsm} over the **public** protocol: clients can `post` / `call` only
|
|
748
|
+
* public events — internal (port-driven) events are not in the callable surface.
|
|
749
|
+
*
|
|
750
|
+
* @typeParam Context - Domain context
|
|
751
|
+
* @typeParam Protocol - Public protocol
|
|
752
|
+
*
|
|
753
|
+
* @category State machine
|
|
754
|
+
*/
|
|
755
|
+
export type Actor<Context = Any, Protocol extends {} | undefined = undefined> = Hsm<Context, Protocol>;
|
|
756
|
+
/**
|
|
757
|
+
* Abstract base class for **any** port — production or test.
|
|
758
|
+
*
|
|
759
|
+
* It takes the machine's root {@link TopState} as its single type argument and derives the
|
|
760
|
+
* context and internal protocol from it (via {@link MachineContext} / {@link MachineInternal}),
|
|
761
|
+
* so the `TopState` is the one place those types are declared. Extend {@link Port} (not `BasePort`
|
|
762
|
+
* directly) for production ports so timer and random services are available.
|
|
763
|
+
*
|
|
764
|
+
* The {@link BasePort.actor | actor} link is **bound lazily** by the runtime: construct the port
|
|
765
|
+
* with no arguments and pass the instance to a factory, which wires the actor in afterwards.
|
|
766
|
+
*
|
|
767
|
+
* @typeParam T - The machine's root {@link TopState} subclass (e.g. `ConnTop`)
|
|
768
|
+
*
|
|
769
|
+
* @category Port
|
|
770
|
+
*/
|
|
771
|
+
export declare abstract class BasePort<T> implements PortHandle<MachineContext<T>, MachineInternal<T>> {
|
|
772
|
+
/**
|
|
773
|
+
* Phantom carrier of the root {@link TopState} type, so {@link testing!makeTestPort} can recover `T`
|
|
774
|
+
* (and therefore the port surface, via {@link MachinePort}) from a mock class. Type-only.
|
|
775
|
+
*/
|
|
776
|
+
readonly __topState: T;
|
|
777
|
+
/**
|
|
778
|
+
* @inheritdoc PortHandle.actor
|
|
779
|
+
*
|
|
780
|
+
* Set once by the runtime right after the machine is built; `undefined` before binding.
|
|
781
|
+
*/
|
|
782
|
+
actor: InboundPoster<MachineContext<T>, MachineInternal<T>> | undefined;
|
|
783
|
+
/** @inheritdoc PortHandle.hsm */
|
|
784
|
+
hsm(): InboundPoster<MachineContext<T>, MachineInternal<T>> | undefined;
|
|
785
|
+
/**
|
|
786
|
+
* Post an internal event inward through the bound {@link BasePort.actor | actor}.
|
|
787
|
+
*
|
|
788
|
+
* This is the one channel a port (or a test driving the port) uses to feed the machine its
|
|
789
|
+
* internal protocol. Because emission is explicit — never a side effect of the outbound call
|
|
790
|
+
* the machine made — a single mock works across many tests: the test decides *when* (and
|
|
791
|
+
* whether) to push each internal event.
|
|
792
|
+
*
|
|
793
|
+
* @param eventName - Internal event to post
|
|
794
|
+
* @param payload - Arguments for the event
|
|
795
|
+
* @throws If called before the actor has been bound by a factory
|
|
796
|
+
*/
|
|
797
|
+
send<EventName extends keyof MachineInternal<T>>(eventName: PostedEvent<MachineInternal<T>, EventName>, ...payload: EventPayload<MachineInternal<T>, EventName>): void;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Production port base with standard JavaScript timer and random services.
|
|
801
|
+
*
|
|
802
|
+
* Extend this class for domain ports in production code. It inherits the lazily-bound
|
|
803
|
+
* {@link BasePort.actor | actor}, {@link BasePort.hsm | hsm}, and {@link BasePort.send | send}
|
|
804
|
+
* from {@link BasePort}, and adds {@link Port.setTimeout | setTimeout} /
|
|
805
|
+
* {@link Port.setInterval | setInterval} / {@link Port.clearTimeout | clearTimeout} /
|
|
806
|
+
* {@link Port.clearInterval | clearInterval} plus the {@link RandomService} methods
|
|
807
|
+
* ({@link Port.random | random}, {@link Port.cryptoRandom | cryptoRandom},
|
|
808
|
+
* {@link Port.randomUUID | randomUUID}, {@link Port.getRandomValues | getRandomValues}).
|
|
809
|
+
*
|
|
810
|
+
* {@link State.deferredPost} delegates to {@link Port.setTimeout}. When no custom port is
|
|
811
|
+
* supplied the runtime instantiates a plain `Port` for that purpose.
|
|
812
|
+
*
|
|
813
|
+
* @typeParam T - The machine's root {@link TopState} subclass (e.g. `ConnTop`)
|
|
814
|
+
*
|
|
815
|
+
* @example A minimal domain port
|
|
816
|
+
* ```ts
|
|
817
|
+
* class ConnPortImpl extends ihsm.Port<ConnTop> implements ConnPort {
|
|
818
|
+
* private nextId = 1;
|
|
819
|
+
* connect(host: string): ihsm.ResultWithSubscription<number> {
|
|
820
|
+
* const id = this.nextId++;
|
|
821
|
+
* return { value: id, subscription: { dispose: () => {} } };
|
|
822
|
+
* }
|
|
823
|
+
* disconnect(id: number): void {}
|
|
824
|
+
* }
|
|
825
|
+
* const port = new ConnPortImpl();
|
|
826
|
+
* const conn = ihsm.makeActor(ConnTop, ctx, port); // binds port.actor
|
|
827
|
+
* ```
|
|
828
|
+
*
|
|
829
|
+
* @category Port
|
|
830
|
+
*/
|
|
831
|
+
export declare class Port<T = Any> extends BasePort<T> implements RandomService {
|
|
832
|
+
private _timerSeq;
|
|
833
|
+
private readonly _timeoutHandles;
|
|
834
|
+
private readonly _intervalHandles;
|
|
835
|
+
/**
|
|
836
|
+
* Schedule `callback` after `millis` milliseconds — same argument order as `globalThis.setTimeout`.
|
|
837
|
+
*
|
|
838
|
+
* @returns An opaque handle for {@link Port.clearTimeout}
|
|
839
|
+
*/
|
|
840
|
+
setTimeout(callback: () => void, millis?: number): TimerHandle;
|
|
841
|
+
/** Cancel a pending {@link Port.setTimeout} handle. No-op when `id` is `undefined` or unknown. */
|
|
842
|
+
clearTimeout(id: TimerHandle | undefined): void;
|
|
843
|
+
/**
|
|
844
|
+
* Schedule `callback` every `millis` milliseconds — same argument order as `globalThis.setInterval`.
|
|
845
|
+
*
|
|
846
|
+
* @returns An opaque handle for {@link Port.clearInterval}
|
|
847
|
+
*/
|
|
848
|
+
setInterval(callback: () => void, millis?: number): TimerHandle;
|
|
849
|
+
/** Cancel a pending {@link Port.setInterval} handle. No-op when `id` is `undefined` or unknown. */
|
|
850
|
+
clearInterval(id: TimerHandle | undefined): void;
|
|
851
|
+
/** @inheritdoc RandomService.random */
|
|
852
|
+
random(): number;
|
|
853
|
+
/** @inheritdoc RandomService.cryptoRandom */
|
|
854
|
+
cryptoRandom(): number;
|
|
855
|
+
/** @inheritdoc RandomService.randomUUID */
|
|
856
|
+
randomUUID(): string;
|
|
857
|
+
/** @inheritdoc RandomService.getRandomValues */
|
|
858
|
+
getRandomValues<T extends ArrayBufferView>(array: T): T;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* `true` when handler `M` is **service-shaped** — its parameter list begins with a resolve callback
|
|
862
|
+
* and a reject callback (`(resolve, reject, ...payload) => void | Promise<void>`), matching
|
|
863
|
+
* {@link ResolveCallback} / {@link RejectCallback}; `false` for plain **event** handlers whose
|
|
864
|
+
* parameters are data.
|
|
865
|
+
*
|
|
866
|
+
* This is the single discriminator that routes a protocol key to {@link Hsm.call} (services) versus
|
|
867
|
+
* {@link Base.post} (events): {@link ServiceName} / {@link ServiceKeys} accept only keys where this
|
|
868
|
+
* is `true`, while {@link PostedEvent} / {@link EventKeys} reject them (proposal T2).
|
|
869
|
+
*
|
|
870
|
+
* @remarks
|
|
871
|
+
* The check is **structural and heuristic**: a handler counts as a service iff its first two
|
|
872
|
+
* parameters are callables. A plain event that genuinely takes two leading callbacks (rare) would
|
|
873
|
+
* be classified as a service — give such a handler `(): void` / data parameters, and reserve the
|
|
874
|
+
* leading `(resolve, reject)` shape for real services.
|
|
875
|
+
*
|
|
876
|
+
* @category Event handler
|
|
877
|
+
*/
|
|
878
|
+
export type IsServiceMethod<M> = M extends (...args: infer Args) => Promise<void> | void ? (Args extends [resolve: (result: any) => void, reject: (error: any) => void, ...payload: any[]] ? true : false) : false;
|
|
879
|
+
/**
|
|
880
|
+
* Union of protocol keys whose handlers are **services** — invocable with {@link Hsm.call}. Reserved
|
|
881
|
+
* {@link State} method names are excluded; resolves to `string` for the untyped (`undefined`) protocol.
|
|
882
|
+
*
|
|
883
|
+
* @category Event handler
|
|
884
|
+
*/
|
|
885
|
+
export type ServiceKeys<Protocol extends {} | undefined> = Protocol extends undefined ? string : Exclude<{
|
|
886
|
+
[K in keyof Protocol]-?: IsServiceMethod<Protocol[K]> extends true ? K : never;
|
|
887
|
+
}[keyof Protocol], keyof State<any, any>>;
|
|
888
|
+
/**
|
|
889
|
+
* Union of protocol keys whose handlers are **events** — postable with {@link Base.post}. Reserved
|
|
890
|
+
* {@link State} method names and service-shaped keys are excluded; resolves to `string` for the
|
|
891
|
+
* untyped (`undefined`) protocol.
|
|
892
|
+
*
|
|
893
|
+
* @category Event handler
|
|
894
|
+
*/
|
|
895
|
+
export type EventKeys<Protocol extends {} | undefined> = Protocol extends undefined ? string : Exclude<{
|
|
896
|
+
[K in keyof Protocol]-?: IsServiceMethod<Protocol[K]> extends true ? never : K;
|
|
897
|
+
}[keyof Protocol], keyof State<any, any>>;
|
|
528
898
|
/**
|
|
529
899
|
* Tuple of client-supplied arguments to {@link Hsm.call}, excluding injected resolve/reject.
|
|
530
900
|
*
|
|
@@ -551,14 +921,15 @@ export type ServiceRequest<Protocol, EventName extends keyof Protocol> = Protoco
|
|
|
551
921
|
export type ServiceResponse<Protocol, EventName extends keyof Protocol> = Protocol extends undefined ? any : Protocol[EventName] extends (resolve: infer Reply, reject: infer Error, ...payload: infer Payload) => Promise<void> | void ? Reply : never;
|
|
552
922
|
/**
|
|
553
923
|
* Valid first argument to {@link Hsm.call} — protocol keys whose handlers use the service signature
|
|
554
|
-
* `(resolve, reject, ...payload)`.
|
|
924
|
+
* `(resolve, reject, ...payload)`. Reserved {@link State} names and **event-shaped** keys become
|
|
925
|
+
* `never`, so `call('open')` on a void event is a compile error (proposal T2).
|
|
555
926
|
*
|
|
556
927
|
* @typeParam Protocol - Machine vocabulary interface
|
|
557
|
-
* @typeParam EventName - Candidate key
|
|
928
|
+
* @typeParam EventName - Candidate key being constrained
|
|
558
929
|
*
|
|
559
930
|
* @category Event handler
|
|
560
931
|
*/
|
|
561
|
-
export type ServiceName<Protocol, EventName> = Protocol extends undefined ? string : EventName extends keyof State<any, any> ? never : EventName;
|
|
932
|
+
export type ServiceName<Protocol, EventName> = Protocol extends undefined ? string : EventName extends keyof State<any, any> ? never : EventName extends keyof Protocol ? (IsServiceMethod<Protocol[EventName]> extends true ? EventName : never) : never;
|
|
562
933
|
/**
|
|
563
934
|
* Lifecycle hooks optionally overridden on state classes.
|
|
564
935
|
*
|
|
@@ -634,12 +1005,30 @@ export interface StateEvents<Context, Protocol extends {} | undefined> {
|
|
|
634
1005
|
*
|
|
635
1006
|
* @category State machine
|
|
636
1007
|
*/
|
|
637
|
-
export declare abstract class TopState<Context = Any, Protocol extends {} | undefined = undefined> implements State<Context, Protocol
|
|
1008
|
+
export declare abstract class TopState<Context = Any, Protocol extends {} | undefined = undefined, InternalProtocol extends {} = {}, Port = undefined> implements State<Context, Dispatch<Protocol, InternalProtocol>>, StateEvents<Context, Dispatch<Protocol, InternalProtocol>> {
|
|
638
1009
|
/** Domain context (injected by runtime — do not assign in constructors). */
|
|
639
1010
|
readonly ctx: Context;
|
|
640
1011
|
/** Handler view of the machine (`this` inside methods delegates here for core operations). */
|
|
641
|
-
readonly hsm: State<Context, Protocol
|
|
1012
|
+
readonly hsm: State<Context, Dispatch<Protocol, InternalProtocol>>;
|
|
1013
|
+
/**
|
|
1014
|
+
* Phantom type carrier — **never exists at runtime** (`declare`d, never assigned). It makes a
|
|
1015
|
+
* `TopState` subclass the single configuration point for the four machine types, so helpers
|
|
1016
|
+
* like {@link MachineContext} / {@link MachineInternal} / {@link MachinePort} (and
|
|
1017
|
+
* {@link BasePort} / {@link testing!TestPort}) can derive everything from the root state alone.
|
|
1018
|
+
*
|
|
1019
|
+
* @internal
|
|
1020
|
+
*/
|
|
1021
|
+
readonly __ihsm: MachineTypes<Context, Protocol, InternalProtocol, Port>;
|
|
642
1022
|
constructor();
|
|
1023
|
+
/**
|
|
1024
|
+
* Outbound boundary — the `port` instance passed to {@link makeActor} / {@link testing!makeTestActor};
|
|
1025
|
+
* all impure I/O flows through here.
|
|
1026
|
+
*
|
|
1027
|
+
* Typed `undefined` for machines created without a port (the default), so existing
|
|
1028
|
+
* port-less machines are unaffected. At runtime a {@link Port} always backs such
|
|
1029
|
+
* machines — it is what {@link State.deferredPost} uses for its timer service.
|
|
1030
|
+
*/
|
|
1031
|
+
get port(): Port;
|
|
643
1032
|
/** @inheritdoc Properties.eventName */
|
|
644
1033
|
get eventName(): string;
|
|
645
1034
|
/** @inheritdoc Properties.eventPayload */
|
|
@@ -647,11 +1036,11 @@ export declare abstract class TopState<Context = Any, Protocol extends {} | unde
|
|
|
647
1036
|
/** @inheritdoc Properties.traceHeader */
|
|
648
1037
|
get traceHeader(): string;
|
|
649
1038
|
/** @inheritdoc Properties.topState */
|
|
650
|
-
get topState(): StateClass<Context, Protocol
|
|
1039
|
+
get topState(): StateClass<Context, Dispatch<Protocol, InternalProtocol>>;
|
|
651
1040
|
/** @inheritdoc Properties.currentStateName */
|
|
652
1041
|
get currentStateName(): string;
|
|
653
1042
|
/** @inheritdoc Properties.currentState */
|
|
654
|
-
get currentState(): StateClass<Context, Protocol
|
|
1043
|
+
get currentState(): StateClass<Context, Dispatch<Protocol, InternalProtocol>>;
|
|
655
1044
|
/** @inheritdoc Properties.ctxTypeName */
|
|
656
1045
|
get ctxTypeName(): string;
|
|
657
1046
|
/** @inheritdoc Properties.traceLevel */
|
|
@@ -665,29 +1054,29 @@ export declare abstract class TopState<Context = Any, Protocol extends {} | unde
|
|
|
665
1054
|
/** @inheritdoc Properties.traceWriter */
|
|
666
1055
|
set traceWriter(value: TraceWriter);
|
|
667
1056
|
/** @inheritdoc Properties.dispatchErrorCallback */
|
|
668
|
-
get dispatchErrorCallback(): DispatchErrorCallback<Context, Protocol
|
|
1057
|
+
get dispatchErrorCallback(): DispatchErrorCallback<Context, Dispatch<Protocol, InternalProtocol>>;
|
|
669
1058
|
/** @inheritdoc Properties.dispatchErrorCallback */
|
|
670
|
-
set dispatchErrorCallback(value: DispatchErrorCallback<Context, Protocol
|
|
1059
|
+
set dispatchErrorCallback(value: DispatchErrorCallback<Context, Dispatch<Protocol, InternalProtocol>>);
|
|
671
1060
|
/** @inheritdoc State.transition */
|
|
672
|
-
transition(nextState: StateClass<Context, Protocol
|
|
1061
|
+
transition(nextState: StateClass<Context, Dispatch<Protocol, InternalProtocol>>): void;
|
|
673
1062
|
/** @inheritdoc State.unhandled */
|
|
674
1063
|
unhandled(): never;
|
|
675
1064
|
/** @inheritdoc State.sleep */
|
|
676
1065
|
sleep(millis: number): Promise<void>;
|
|
677
1066
|
/** @inheritdoc Base.post */
|
|
678
|
-
post<EventName extends keyof Protocol
|
|
679
|
-
/** @inheritdoc
|
|
680
|
-
deferredPost<EventName extends keyof Protocol
|
|
1067
|
+
post<EventName extends keyof Dispatch<Protocol, InternalProtocol>>(eventName: PostedEvent<Dispatch<Protocol, InternalProtocol>, EventName>, ...eventPayload: EventPayload<Dispatch<Protocol, InternalProtocol>, EventName>): void;
|
|
1068
|
+
/** @inheritdoc State.deferredPost */
|
|
1069
|
+
deferredPost<EventName extends keyof Dispatch<Protocol, InternalProtocol>>(millis: number, eventName: PostedEvent<Dispatch<Protocol, InternalProtocol>, EventName>, ...eventPayload: EventPayload<Dispatch<Protocol, InternalProtocol>, EventName>): void;
|
|
681
1070
|
/** @inheritdoc State.postNow */
|
|
682
|
-
postNow<EventName extends keyof Protocol
|
|
1071
|
+
postNow<EventName extends keyof Dispatch<Protocol, InternalProtocol>>(eventName: PostedEvent<Dispatch<Protocol, InternalProtocol>, EventName>, ...eventPayload: EventPayload<Dispatch<Protocol, InternalProtocol>, EventName>): void;
|
|
683
1072
|
/** @inheritdoc StateEvents.onExit */
|
|
684
1073
|
onExit(): Promise<void> | void;
|
|
685
1074
|
/** @inheritdoc StateEvents.onEntry */
|
|
686
1075
|
onEntry(): Promise<void> | void;
|
|
687
1076
|
/** @inheritdoc StateEvents.onError */
|
|
688
|
-
onError<EventName extends keyof Protocol
|
|
1077
|
+
onError<EventName extends keyof Dispatch<Protocol, InternalProtocol>>(error: RuntimeError<Context, Dispatch<Protocol, InternalProtocol>, EventName>): Promise<void> | void;
|
|
689
1078
|
/** @inheritdoc StateEvents.onUnhandled */
|
|
690
|
-
onUnhandled<EventName extends keyof Protocol
|
|
1079
|
+
onUnhandled<EventName extends keyof Dispatch<Protocol, InternalProtocol>>(error: UnhandledEventError<Context, Dispatch<Protocol, InternalProtocol>, EventName>): Promise<void> | void;
|
|
691
1080
|
}
|
|
692
1081
|
/**
|
|
693
1082
|
* Base class for all ihsm runtime errors carrying machine context.
|
|
@@ -891,6 +1280,12 @@ export declare function defineStateName<Context, Protocol extends {} | undefined
|
|
|
891
1280
|
* @category State machine
|
|
892
1281
|
*/
|
|
893
1282
|
export declare function registerStateNames(exports: Record<string, unknown>): void;
|
|
1283
|
+
/** @internal — shared by the core factories and (via re-export) the `ihsm/testing` factories. */
|
|
1284
|
+
export declare function defaultDispatchErrorCallback<Context, Protocol extends {} | undefined>(hsm: Base<Context, Protocol>, err: Error): void;
|
|
1285
|
+
/** @internal */
|
|
1286
|
+
export declare const defaultTraceWriter: TraceWriter;
|
|
1287
|
+
/** @internal */
|
|
1288
|
+
export declare const defaultInitialize = true;
|
|
894
1289
|
/**
|
|
895
1290
|
* Creates and optionally initializes a hierarchical state machine **actor** bound to `ctx`.
|
|
896
1291
|
*
|
|
@@ -931,4 +1326,70 @@ export declare function registerStateNames(exports: Record<string, unknown>): vo
|
|
|
931
1326
|
*
|
|
932
1327
|
* @category Factory
|
|
933
1328
|
*/
|
|
934
|
-
export declare function makeHsm<Context, Protocol extends undefined | {}>(topState: StateClass<Context, Protocol>, ctx: Context, initialize?: boolean, traceLevel?: TraceLevel, traceWriter?: TraceWriter, dispatchErrorCallback?: DispatchErrorCallback<Context, Protocol>): Hsm<Context, Protocol>;
|
|
1329
|
+
export declare function makeHsm<Context, Protocol extends undefined | {}>(topState: StateClass<Context, Protocol>, ctx: Context, initialize?: boolean, traceLevel?: TraceLevel, traceWriter?: TraceWriter, dispatchErrorCallback?: DispatchErrorCallback<Context, Protocol>, port?: PortHandle<Context, Protocol>): Hsm<Context, Protocol>;
|
|
1330
|
+
/**
|
|
1331
|
+
* The constrained root-state argument shared by {@link makeActor} / {@link testing!makeTestActor}.
|
|
1332
|
+
*
|
|
1333
|
+
* Its prototype carries the {@link MachineTypes} marker, so `Context`, `Public`, and `Internal` are
|
|
1334
|
+
* **inferred from the `topState`** at the call site — you never pass those generics explicitly.
|
|
1335
|
+
*
|
|
1336
|
+
* @typeParam Context - Domain context type
|
|
1337
|
+
* @typeParam Public - Public, client-callable protocol
|
|
1338
|
+
* @typeParam Internal - Internal protocol — only postable by the port / handlers
|
|
1339
|
+
*
|
|
1340
|
+
* @category Factory
|
|
1341
|
+
*/
|
|
1342
|
+
export type TopStateArg<Context, Public extends undefined | {}, Internal extends {}> = StateClass<Context, Dispatch<Public, Internal>> & {
|
|
1343
|
+
readonly prototype: {
|
|
1344
|
+
readonly __ihsm: MachineTypes<Context, Public, Internal, unknown>;
|
|
1345
|
+
};
|
|
1346
|
+
};
|
|
1347
|
+
/**
|
|
1348
|
+
* Optional tuning passed as the **last** argument to {@link makeActor} / {@link testing!makeTestActor},
|
|
1349
|
+
* after the three mandatory positional arguments (`topState`, `ctx`, `port`). Every field has a
|
|
1350
|
+
* sensible default; omit the whole object to take them all.
|
|
1351
|
+
*
|
|
1352
|
+
* @typeParam Context - Domain context type
|
|
1353
|
+
* @typeParam Public - Public protocol
|
|
1354
|
+
* @typeParam Internal - Internal protocol
|
|
1355
|
+
*
|
|
1356
|
+
* @category Factory
|
|
1357
|
+
*/
|
|
1358
|
+
export interface ActorOptions<Context, Public extends undefined | {}, Internal extends {} = {}> {
|
|
1359
|
+
/** Run the initial `@InitialState` walk (default `true`). */
|
|
1360
|
+
initialize?: boolean;
|
|
1361
|
+
/** Initial {@link TraceLevel} (default {@link TraceLevel.DEBUG}). */
|
|
1362
|
+
traceLevel?: TraceLevel;
|
|
1363
|
+
/** {@link TraceWriter} implementation (default: prefixes with state name, logs to `console`). */
|
|
1364
|
+
traceWriter?: TraceWriter;
|
|
1365
|
+
/** Last-resort error hook (default: trace + rethrow). */
|
|
1366
|
+
dispatchErrorCallback?: DispatchErrorCallback<Context, Dispatch<Public, Internal>>;
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Creates an actor exposing only its **public** protocol, with an optional outbound {@link Port}.
|
|
1370
|
+
*
|
|
1371
|
+
* Like {@link makeHsm} but separates the public, client-callable protocol from an
|
|
1372
|
+
* `InternalProtocol` that only the port may post inward. The returned {@link Actor} surfaces
|
|
1373
|
+
* just the public events; handlers (and the port) may post the merged {@link Dispatch} protocol.
|
|
1374
|
+
*
|
|
1375
|
+
* The trailing `Disjoint` guard is a compile-time gate: if `Public` and `Internal` share an event
|
|
1376
|
+
* name, the call fails to type-check, pointing at the overlapping keys.
|
|
1377
|
+
*
|
|
1378
|
+
* @typeParam Context - Domain context type
|
|
1379
|
+
* @typeParam Public - Public, client-callable protocol
|
|
1380
|
+
* @typeParam Internal - Internal protocol — only postable by the port / handlers
|
|
1381
|
+
* @typeParam P - Concrete {@link Port} type assigned to `this.port`
|
|
1382
|
+
* @param topState - Root state class; `Context` / `Public` / `Internal` are inferred from it (see {@link TopStateArg})
|
|
1383
|
+
* @param ctx - Mutable domain object shared by all states
|
|
1384
|
+
* @param port - Outbound port instance (its `actor` is bound by the factory; use {@link Port} if none)
|
|
1385
|
+
* @param options - Optional tuning: `initialize` / `traceLevel` / `traceWriter` / … (see {@link ActorOptions})
|
|
1386
|
+
* @returns A public-only {@link Actor} handle
|
|
1387
|
+
*
|
|
1388
|
+
* @example
|
|
1389
|
+
* ```ts
|
|
1390
|
+
* const conn = makeActor(ConnTop, new ConnCtx(), port, { traceLevel: TraceLevel.PRODUCTION });
|
|
1391
|
+
* ```
|
|
1392
|
+
*
|
|
1393
|
+
* @category Factory
|
|
1394
|
+
*/
|
|
1395
|
+
export declare function makeActor<Context, Public extends undefined | {}, Internal extends {} = {}, P extends PortHandle<Context, Internal> = Port>(topState: TopStateArg<Context, Public, Internal>, ctx: Context, port: P, options?: ActorOptions<Context, Public, Internal>, ..._disjointGuard: Disjoint<Public, Internal> extends true ? [] : [error: Disjoint<Public, Internal>]): Actor<Context, Public>;
|