@camcima/finita 4.0.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +218 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +155 -13
- package/dist/index.d.ts +155 -13
- package/dist/index.js +216 -20
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -53,6 +53,8 @@ declare class Event implements EventInterface {
|
|
|
53
53
|
attach(observer: Observer): void;
|
|
54
54
|
detach(observer: Observer): void;
|
|
55
55
|
notify(args?: readonly unknown[]): Promise<void>;
|
|
56
|
+
/** Snapshot — detaching later does not change an already-returned list,
|
|
57
|
+
* and mutating it does not change the event's registrations. */
|
|
56
58
|
getObservers(): Iterable<Observer>;
|
|
57
59
|
getMetadata(): Record<string, unknown>;
|
|
58
60
|
getMetadataValue(key: string): unknown;
|
|
@@ -287,6 +289,12 @@ interface StatemachineInterface<TSubject = unknown> {
|
|
|
287
289
|
getProcess(): ProcessInterface;
|
|
288
290
|
triggerEvent(name: string, context?: Map<string, unknown>): Promise<void>;
|
|
289
291
|
checkTransitions(context?: Map<string, unknown>): Promise<void>;
|
|
292
|
+
/**
|
|
293
|
+
* Resolves once the operation queue is empty and the runner is idle,
|
|
294
|
+
* including operations chained via EnqueueContext.enqueue(). Resolves
|
|
295
|
+
* immediately if the machine is already idle.
|
|
296
|
+
*/
|
|
297
|
+
whenIdle(): Promise<void>;
|
|
290
298
|
attachBefore(observer: BeforeTransitionObserver<TSubject>): void;
|
|
291
299
|
detachBefore(observer: BeforeTransitionObserver<TSubject>): void;
|
|
292
300
|
getBeforeObservers(): Iterable<BeforeTransitionObserver<TSubject>>;
|
|
@@ -316,7 +324,16 @@ interface StatemachineOptions<TSubject = unknown> {
|
|
|
316
324
|
initialStateName?: string;
|
|
317
325
|
/** Defaults to OneOrNoneActiveTransition. */
|
|
318
326
|
transitionSelector?: TransitionSelectorInterface<TSubject>;
|
|
319
|
-
/**
|
|
327
|
+
/**
|
|
328
|
+
* Defaults to NullMutex (no cross-process serialization).
|
|
329
|
+
*
|
|
330
|
+
* Must be exclusive to this machine — never share one MutexInterface
|
|
331
|
+
* instance between machines: the engine reads isAcquired() as "this
|
|
332
|
+
* machine holds the lock", so a shared instance silently disables mutual
|
|
333
|
+
* exclusion. To coordinate machines, share the underlying
|
|
334
|
+
* LockAdapterInterface (same resource name) and construct one mutex per
|
|
335
|
+
* machine, as MutexFactory does.
|
|
336
|
+
*/
|
|
320
337
|
mutex?: MutexInterface;
|
|
321
338
|
/** When true, the engine releases the mutex at the end of each top-level operation. Defaults to true. */
|
|
322
339
|
autoreleaseLock?: boolean;
|
|
@@ -330,6 +347,36 @@ interface StatemachineOptions<TSubject = unknown> {
|
|
|
330
347
|
* Defaults to 100.
|
|
331
348
|
*/
|
|
332
349
|
maxAutomaticHops?: number;
|
|
350
|
+
/**
|
|
351
|
+
* Called when an operation chained via EnqueueContext.enqueue() fails.
|
|
352
|
+
* Chained operations are not awaited by the caller whose transition
|
|
353
|
+
* enqueued them, so without this hook their errors are discarded.
|
|
354
|
+
* Exceptions thrown by the hook itself are swallowed — it must not be
|
|
355
|
+
* able to break the machine's drain loop.
|
|
356
|
+
*/
|
|
357
|
+
onChainedOperationError?: (error: unknown, info: {
|
|
358
|
+
eventName: string;
|
|
359
|
+
}) => void;
|
|
360
|
+
/**
|
|
361
|
+
* Maximum number of operations that may wait in the queue (the running
|
|
362
|
+
* operation does not count). When the limit is reached, further
|
|
363
|
+
* triggerEvent/checkTransitions calls reject with
|
|
364
|
+
* QueueLimitExceededError, and EnqueueContext.enqueue() throws it into
|
|
365
|
+
* the enqueuing after-observer's error path. In that case the original
|
|
366
|
+
* caller's promise rejects even though its transition already
|
|
367
|
+
* committed — check the machine's state, not just the rejection, before
|
|
368
|
+
* retrying. Must be a positive integer when set. Defaults to Infinity
|
|
369
|
+
* (unbounded, the previous behavior).
|
|
370
|
+
*/
|
|
371
|
+
maxQueueLength?: number;
|
|
372
|
+
/**
|
|
373
|
+
* Diagnostic hook called whenever the automatic post-operation lock
|
|
374
|
+
* release throws — including when the operation itself also failed, in
|
|
375
|
+
* which case the caller's rejection carries the operation error and the
|
|
376
|
+
* release error would otherwise be discarded. Does not change rejection
|
|
377
|
+
* behavior. Exceptions thrown by the hook itself are swallowed.
|
|
378
|
+
*/
|
|
379
|
+
onReleaseError?: (error: unknown) => void;
|
|
333
380
|
}
|
|
334
381
|
|
|
335
382
|
declare class Statemachine<TSubject = unknown> implements StatemachineInterface<TSubject> {
|
|
@@ -341,11 +388,15 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
341
388
|
private lastState;
|
|
342
389
|
private autoreleaseLock;
|
|
343
390
|
private readonly maxAutomaticHops;
|
|
391
|
+
private readonly maxQueueLength;
|
|
344
392
|
private readonly queue;
|
|
345
393
|
private running;
|
|
394
|
+
private idleWaiters;
|
|
346
395
|
private inSyncCallback;
|
|
347
396
|
private readonly beforeObservers;
|
|
348
397
|
private readonly afterObservers;
|
|
398
|
+
private readonly onChainedOperationError?;
|
|
399
|
+
private readonly onReleaseError?;
|
|
349
400
|
constructor(subject: TSubject, process: ProcessInterface, options?: StatemachineOptions<TSubject>);
|
|
350
401
|
getCurrentState(): StateInterface;
|
|
351
402
|
getLastState(): StateInterface | null;
|
|
@@ -353,17 +404,40 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
353
404
|
getProcess(): ProcessInterface;
|
|
354
405
|
attachBefore(observer: BeforeTransitionObserver<TSubject>): void;
|
|
355
406
|
detachBefore(observer: BeforeTransitionObserver<TSubject>): void;
|
|
407
|
+
/** Snapshot — detaching later does not change an already-returned list,
|
|
408
|
+
* and mutating it does not change the machine's registrations. */
|
|
356
409
|
getBeforeObservers(): Iterable<BeforeTransitionObserver<TSubject>>;
|
|
357
410
|
attachAfter(observer: AfterTransitionObserver<TSubject>): void;
|
|
358
411
|
detachAfter(observer: AfterTransitionObserver<TSubject>): void;
|
|
412
|
+
/** Snapshot — see getBeforeObservers. */
|
|
359
413
|
getAfterObservers(): Iterable<AfterTransitionObserver<TSubject>>;
|
|
360
414
|
acquireLock(): Promise<boolean>;
|
|
415
|
+
/**
|
|
416
|
+
* Releases the mutex. A failed release — whether the mutex throws or
|
|
417
|
+
* returns false — is reported to the onReleaseError hook; it is not thrown,
|
|
418
|
+
* so manual lock management keeps its existing control flow. Inspect
|
|
419
|
+
* isLockAcquired() (or the hook) to learn whether the lock was actually
|
|
420
|
+
* freed.
|
|
421
|
+
*/
|
|
361
422
|
releaseLock(): Promise<void>;
|
|
362
423
|
isLockAcquired(): boolean;
|
|
363
424
|
isAutoreleaseLock(): boolean;
|
|
364
425
|
setAutoreleaseLock(autorelease: boolean): void;
|
|
365
426
|
triggerEvent(name: string, context?: Map<string, unknown>): Promise<void>;
|
|
366
427
|
checkTransitions(context?: Map<string, unknown>): Promise<void>;
|
|
428
|
+
/**
|
|
429
|
+
* Resolves once the operation queue is empty and the runner is idle —
|
|
430
|
+
* i.e. every operation enqueued so far, including operations chained via
|
|
431
|
+
* EnqueueContext.enqueue(), has completed. Resolves immediately if the
|
|
432
|
+
* machine is already idle. Note this is a quiescence point, not a
|
|
433
|
+
* receipt: work scheduled later (e.g. from a timer) starts a new drain.
|
|
434
|
+
*
|
|
435
|
+
* Like triggerEvent/checkTransitions, this may not be called from inside an
|
|
436
|
+
* observer or condition of the same machine: the machine cannot reach idle
|
|
437
|
+
* while the runner is blocked on that very callback, so awaiting it there
|
|
438
|
+
* always deadlocks.
|
|
439
|
+
*/
|
|
440
|
+
whenIdle(): Promise<void>;
|
|
367
441
|
/** Runs fn with the re-entrancy flag set for its SYNCHRONOUS portion only:
|
|
368
442
|
* the flag is cleared as soon as fn returns (before any promise it returned
|
|
369
443
|
* is awaited), so concurrent external callers are never affected. This
|
|
@@ -376,6 +450,20 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
376
450
|
private enqueueOperation;
|
|
377
451
|
private runIfIdle;
|
|
378
452
|
private runOperation;
|
|
453
|
+
/**
|
|
454
|
+
* Releases the mutex, normalizing its two failure modes into one result: a
|
|
455
|
+
* thrown error, and a false return — the failure signal MutexInterface /
|
|
456
|
+
* LockAdapterInterface define (a PostgreSQL advisory unlock that returns
|
|
457
|
+
* false, a Redis DEL that removed nothing). A false return means the lock
|
|
458
|
+
* may still be held, so it must never be mistaken for a successful release.
|
|
459
|
+
*
|
|
460
|
+
* Every failure is surfaced through the diagnostic hook — when the
|
|
461
|
+
* operation also failed, the rejection carries the operation error and this
|
|
462
|
+
* hook is the only place the release error appears.
|
|
463
|
+
*
|
|
464
|
+
* @returns null on success, or the failure wrapped for the caller to raise.
|
|
465
|
+
*/
|
|
466
|
+
private releaseMutex;
|
|
379
467
|
private resolveEvent;
|
|
380
468
|
/**
|
|
381
469
|
* Drive transitions starting from the current state, following automatic
|
|
@@ -423,11 +511,11 @@ interface LastStateHasChangedDateInterface {
|
|
|
423
511
|
getLastStateHasChangedDate(): Date;
|
|
424
512
|
}
|
|
425
513
|
|
|
426
|
-
/** @deprecated No longer used internally; will be removed in
|
|
514
|
+
/** @deprecated No longer used internally; will be removed in v5. */
|
|
427
515
|
interface CallbackInterface {
|
|
428
516
|
invoke(): MaybePromise<void>;
|
|
429
517
|
}
|
|
430
|
-
/** @deprecated No longer used internally; will be removed in
|
|
518
|
+
/** @deprecated No longer used internally; will be removed in v5. */
|
|
431
519
|
interface DispatcherInterface extends CallbackInterface {
|
|
432
520
|
dispatch(event: EventInterface, args?: unknown[]): void;
|
|
433
521
|
invoke(): Promise<void>;
|
|
@@ -498,11 +586,11 @@ declare class Not<TSubject = unknown> implements ConditionInterface<TSubject> {
|
|
|
498
586
|
}
|
|
499
587
|
|
|
500
588
|
/**
|
|
501
|
-
*
|
|
589
|
+
* Observer for Event observers (commands attached to specific events).
|
|
502
590
|
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
591
|
+
* This is not a Statemachine observer. To run a callback after every
|
|
592
|
+
* transition, implement AfterTransitionObserver directly or compose a small
|
|
593
|
+
* wrapper.
|
|
506
594
|
*/
|
|
507
595
|
declare class CallbackObserver implements Observer {
|
|
508
596
|
private readonly callback;
|
|
@@ -609,7 +697,15 @@ declare class LockAdapterMutex implements MutexInterface {
|
|
|
609
697
|
private readonly lockAdapter;
|
|
610
698
|
private readonly resourceName;
|
|
611
699
|
private acquired;
|
|
700
|
+
private pendingAcquire;
|
|
612
701
|
constructor(lockAdapter: LockAdapterInterface, resourceName: string);
|
|
702
|
+
/**
|
|
703
|
+
* Overlapping calls share one underlying acquire: the `acquired` flag is
|
|
704
|
+
* only set after the adapter resolves, so without this both callers would
|
|
705
|
+
* pass the check and acquire twice on a non-idempotent adapter (database
|
|
706
|
+
* advisory locks, redis SET NX). The pending promise is cleared once it
|
|
707
|
+
* settles, so a failed acquire can still be retried.
|
|
708
|
+
*/
|
|
613
709
|
acquireLock(): Promise<boolean>;
|
|
614
710
|
releaseLock(): Promise<boolean>;
|
|
615
711
|
isAcquired(): boolean;
|
|
@@ -624,6 +720,15 @@ declare class MutexFactory<TSubject = unknown> implements MutexFactoryInterface<
|
|
|
624
720
|
createMutex(subject: TSubject): MutexInterface;
|
|
625
721
|
}
|
|
626
722
|
|
|
723
|
+
/**
|
|
724
|
+
* Engine options applied to every machine the factory creates.
|
|
725
|
+
*
|
|
726
|
+
* `initialStateName`, `mutex` and `transitionSelector` are excluded: the
|
|
727
|
+
* factory derives them per subject from the state-name detector, the mutex
|
|
728
|
+
* factory and setTransitionSelector, so a template value could only
|
|
729
|
+
* contradict them.
|
|
730
|
+
*/
|
|
731
|
+
type FactoryStatemachineOptions<TSubject = unknown> = Omit<StatemachineOptions<TSubject>, "initialStateName" | "mutex" | "transitionSelector">;
|
|
627
732
|
declare class Factory<TSubject = unknown> implements FactoryInterface<TSubject> {
|
|
628
733
|
private readonly processDetector;
|
|
629
734
|
private readonly stateNameDetector;
|
|
@@ -631,7 +736,15 @@ declare class Factory<TSubject = unknown> implements FactoryInterface<TSubject>
|
|
|
631
736
|
private readonly afterObservers;
|
|
632
737
|
private transitionSelector;
|
|
633
738
|
private mutexFactory;
|
|
634
|
-
|
|
739
|
+
private readonly options;
|
|
740
|
+
/**
|
|
741
|
+
* @param options Engine options applied to every machine this factory
|
|
742
|
+
* creates — back-pressure (maxQueueLength), the automatic-hop bound, lock
|
|
743
|
+
* autorelease, and the onChainedOperationError / onReleaseError diagnostic
|
|
744
|
+
* sinks. Without them, factory-created machines would silently run on
|
|
745
|
+
* defaults, which is precisely where those sinks matter most.
|
|
746
|
+
*/
|
|
747
|
+
constructor(processDetector: ProcessDetectorInterface<TSubject>, stateNameDetector?: StateNameDetectorInterface<TSubject> | null, options?: FactoryStatemachineOptions<TSubject>);
|
|
635
748
|
setMutexFactory(factory: MutexFactoryInterface<TSubject> | null): void;
|
|
636
749
|
setTransitionSelector(selector: TransitionSelectorInterface<TSubject>): void;
|
|
637
750
|
attachBeforeObserver(observer: BeforeTransitionObserver<TSubject>): void;
|
|
@@ -674,11 +787,12 @@ interface Graph {
|
|
|
674
787
|
nodes: GraphNode[];
|
|
675
788
|
edges: GraphEdge[];
|
|
676
789
|
}
|
|
790
|
+
type GraphDirection = "TB" | "BT" | "LR" | "RL";
|
|
677
791
|
interface DotOptions {
|
|
678
|
-
rankdir?:
|
|
792
|
+
rankdir?: GraphDirection;
|
|
679
793
|
}
|
|
680
794
|
interface MermaidOptions {
|
|
681
|
-
direction?:
|
|
795
|
+
direction?: GraphDirection;
|
|
682
796
|
}
|
|
683
797
|
declare class GraphBuilder {
|
|
684
798
|
private readonly nodes;
|
|
@@ -711,6 +825,20 @@ declare class LockCanNotBeAcquiredError extends FinitaError {
|
|
|
711
825
|
constructor(message?: string);
|
|
712
826
|
}
|
|
713
827
|
|
|
828
|
+
/**
|
|
829
|
+
* The mutex reported a failed release by returning false, as
|
|
830
|
+
* LockAdapterInterface specifies (e.g. a PostgreSQL advisory unlock that
|
|
831
|
+
* returns false, or a Redis DEL that removed nothing).
|
|
832
|
+
*
|
|
833
|
+
* The lock must be assumed to still be held: the engine surfaces this so a
|
|
834
|
+
* failed release can never be mistaken for a successful one, which would let
|
|
835
|
+
* every later operation piggyback on — and never release — a stuck lock.
|
|
836
|
+
*/
|
|
837
|
+
declare class LockCanNotBeReleasedError extends FinitaError {
|
|
838
|
+
readonly code = "lockCanNotBeReleased";
|
|
839
|
+
constructor(message?: string);
|
|
840
|
+
}
|
|
841
|
+
|
|
714
842
|
declare class DuplicateStateError extends FinitaError {
|
|
715
843
|
readonly code = "duplicateState";
|
|
716
844
|
readonly stateName: string;
|
|
@@ -723,7 +851,7 @@ declare class ProcessFinalizedError extends FinitaError {
|
|
|
723
851
|
constructor(processName: string);
|
|
724
852
|
}
|
|
725
853
|
|
|
726
|
-
type GraphValidationCode = "unknownTarget" | "unknownSource" | "missingInitialState" | "multipleInitialStates" | "invalidStateName" | "invalidEventName" | "invalidConditionName" | "orphanState";
|
|
854
|
+
type GraphValidationCode = "unknownTarget" | "unknownSource" | "missingInitialState" | "multipleInitialStates" | "invalidStateName" | "invalidEventName" | "invalidConditionName" | "invalidTransitionWeight" | "orphanState";
|
|
727
855
|
declare class GraphValidationError extends FinitaError {
|
|
728
856
|
readonly code: GraphValidationCode;
|
|
729
857
|
readonly details: Readonly<Record<string, unknown>>;
|
|
@@ -773,10 +901,19 @@ declare class InvalidSubjectError extends FinitaError {
|
|
|
773
901
|
constructor(expectedInterface: string, missingMembers: Iterable<string>);
|
|
774
902
|
}
|
|
775
903
|
|
|
904
|
+
/** One of the simultaneously-active transitions that caused the ambiguity. */
|
|
905
|
+
interface AmbiguousTransitionCandidate {
|
|
906
|
+
targetStateName: string;
|
|
907
|
+
eventName: string | null;
|
|
908
|
+
conditionName: string | null;
|
|
909
|
+
weight: number;
|
|
910
|
+
}
|
|
776
911
|
declare class AmbiguousTransitionError extends FinitaError {
|
|
777
912
|
readonly code = "ambiguousTransition";
|
|
778
913
|
readonly activeCount: number;
|
|
779
|
-
|
|
914
|
+
/** The competing transitions — what you need to resolve the ambiguity. */
|
|
915
|
+
readonly candidates: readonly Readonly<AmbiguousTransitionCandidate>[];
|
|
916
|
+
constructor(activeCount: number, candidates?: Iterable<AmbiguousTransitionCandidate>);
|
|
780
917
|
}
|
|
781
918
|
|
|
782
919
|
declare class AutomaticTransitionCycleError extends FinitaError {
|
|
@@ -791,4 +928,9 @@ declare class ReentrancyError extends FinitaError {
|
|
|
791
928
|
constructor(operation: string);
|
|
792
929
|
}
|
|
793
930
|
|
|
794
|
-
|
|
931
|
+
declare class QueueLimitExceededError extends FinitaError {
|
|
932
|
+
readonly code = "queueLimitExceeded";
|
|
933
|
+
constructor(limit: number, eventName: string | null);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export { AbstractNamedProcessDetector, ActiveTransitionFilter, type AddStateOptions, type AddTransitionOptions, type AfterTransitionObserver, type AmbiguousTransitionCandidate, AmbiguousTransitionError, AndComposite, AutomaticTransitionCycleError, type BeforeTransitionObserver, type BuildOptions, CallbackCondition, type CallbackInterface, CallbackObserver, type ConditionCallbackFn, type ConditionInterface, Contradiction, type DispatcherInterface, type DotOptions, DuplicateStateError, type DuplicateTransitionConflict, DuplicateTransitionError, type EnqueueContext, Event, type EventInterface, Factory, type FactoryInterface, type FactoryStatemachineOptions, FilterStateByEvent, FilterStateByFinalState, FilterStateByTransition, FilterTransitionByEvent, FinitaError, type Graph, GraphBuilder, type GraphDirection, type GraphEdge, type GraphNode, type GraphValidationCode, GraphValidationError, InvalidSubjectError, type LastStateHasChangedDateInterface, type LockAdapterInterface, LockAdapterMutex, LockCanNotBeAcquiredError, LockCanNotBeReleasedError, type LoggerInterface, type MaybePromise, type MermaidOptions, type Metadata, MutexFactory, type MutexFactoryInterface, type MutexInterface, type Named, Not, NullMutex, type ObservableSubject, type Observer, OnEnterObserver, OneOrNoneActiveTransition, OrComposite, Process, ProcessBuilder, type ProcessDetectorInterface, ProcessFinalizedError, type ProcessInterface, ProcessNotFoundError, type ProposedTransitionFrame, QueueLimitExceededError, ReentrancyError, ScoreTransition, SingleProcessDetector, State, type StateCollectionInterface, StateEventNotFoundError, type StateInterface, type StateNameDetectorInterface, StateNotFoundError, type StatefulInterface, StatefulStateNameDetector, StatefulStatusChanger, Statemachine, type StatemachineInterface, type StatemachineOptions, type StringConverter, Tautology, Timeout, Transition, type TransitionFrame, type TransitionInterface, TransitionLogger, type TransitionSelectorInterface, WeightTransition, type Weighted, WrongEventForStateError };
|
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ declare class Event implements EventInterface {
|
|
|
53
53
|
attach(observer: Observer): void;
|
|
54
54
|
detach(observer: Observer): void;
|
|
55
55
|
notify(args?: readonly unknown[]): Promise<void>;
|
|
56
|
+
/** Snapshot — detaching later does not change an already-returned list,
|
|
57
|
+
* and mutating it does not change the event's registrations. */
|
|
56
58
|
getObservers(): Iterable<Observer>;
|
|
57
59
|
getMetadata(): Record<string, unknown>;
|
|
58
60
|
getMetadataValue(key: string): unknown;
|
|
@@ -287,6 +289,12 @@ interface StatemachineInterface<TSubject = unknown> {
|
|
|
287
289
|
getProcess(): ProcessInterface;
|
|
288
290
|
triggerEvent(name: string, context?: Map<string, unknown>): Promise<void>;
|
|
289
291
|
checkTransitions(context?: Map<string, unknown>): Promise<void>;
|
|
292
|
+
/**
|
|
293
|
+
* Resolves once the operation queue is empty and the runner is idle,
|
|
294
|
+
* including operations chained via EnqueueContext.enqueue(). Resolves
|
|
295
|
+
* immediately if the machine is already idle.
|
|
296
|
+
*/
|
|
297
|
+
whenIdle(): Promise<void>;
|
|
290
298
|
attachBefore(observer: BeforeTransitionObserver<TSubject>): void;
|
|
291
299
|
detachBefore(observer: BeforeTransitionObserver<TSubject>): void;
|
|
292
300
|
getBeforeObservers(): Iterable<BeforeTransitionObserver<TSubject>>;
|
|
@@ -316,7 +324,16 @@ interface StatemachineOptions<TSubject = unknown> {
|
|
|
316
324
|
initialStateName?: string;
|
|
317
325
|
/** Defaults to OneOrNoneActiveTransition. */
|
|
318
326
|
transitionSelector?: TransitionSelectorInterface<TSubject>;
|
|
319
|
-
/**
|
|
327
|
+
/**
|
|
328
|
+
* Defaults to NullMutex (no cross-process serialization).
|
|
329
|
+
*
|
|
330
|
+
* Must be exclusive to this machine — never share one MutexInterface
|
|
331
|
+
* instance between machines: the engine reads isAcquired() as "this
|
|
332
|
+
* machine holds the lock", so a shared instance silently disables mutual
|
|
333
|
+
* exclusion. To coordinate machines, share the underlying
|
|
334
|
+
* LockAdapterInterface (same resource name) and construct one mutex per
|
|
335
|
+
* machine, as MutexFactory does.
|
|
336
|
+
*/
|
|
320
337
|
mutex?: MutexInterface;
|
|
321
338
|
/** When true, the engine releases the mutex at the end of each top-level operation. Defaults to true. */
|
|
322
339
|
autoreleaseLock?: boolean;
|
|
@@ -330,6 +347,36 @@ interface StatemachineOptions<TSubject = unknown> {
|
|
|
330
347
|
* Defaults to 100.
|
|
331
348
|
*/
|
|
332
349
|
maxAutomaticHops?: number;
|
|
350
|
+
/**
|
|
351
|
+
* Called when an operation chained via EnqueueContext.enqueue() fails.
|
|
352
|
+
* Chained operations are not awaited by the caller whose transition
|
|
353
|
+
* enqueued them, so without this hook their errors are discarded.
|
|
354
|
+
* Exceptions thrown by the hook itself are swallowed — it must not be
|
|
355
|
+
* able to break the machine's drain loop.
|
|
356
|
+
*/
|
|
357
|
+
onChainedOperationError?: (error: unknown, info: {
|
|
358
|
+
eventName: string;
|
|
359
|
+
}) => void;
|
|
360
|
+
/**
|
|
361
|
+
* Maximum number of operations that may wait in the queue (the running
|
|
362
|
+
* operation does not count). When the limit is reached, further
|
|
363
|
+
* triggerEvent/checkTransitions calls reject with
|
|
364
|
+
* QueueLimitExceededError, and EnqueueContext.enqueue() throws it into
|
|
365
|
+
* the enqueuing after-observer's error path. In that case the original
|
|
366
|
+
* caller's promise rejects even though its transition already
|
|
367
|
+
* committed — check the machine's state, not just the rejection, before
|
|
368
|
+
* retrying. Must be a positive integer when set. Defaults to Infinity
|
|
369
|
+
* (unbounded, the previous behavior).
|
|
370
|
+
*/
|
|
371
|
+
maxQueueLength?: number;
|
|
372
|
+
/**
|
|
373
|
+
* Diagnostic hook called whenever the automatic post-operation lock
|
|
374
|
+
* release throws — including when the operation itself also failed, in
|
|
375
|
+
* which case the caller's rejection carries the operation error and the
|
|
376
|
+
* release error would otherwise be discarded. Does not change rejection
|
|
377
|
+
* behavior. Exceptions thrown by the hook itself are swallowed.
|
|
378
|
+
*/
|
|
379
|
+
onReleaseError?: (error: unknown) => void;
|
|
333
380
|
}
|
|
334
381
|
|
|
335
382
|
declare class Statemachine<TSubject = unknown> implements StatemachineInterface<TSubject> {
|
|
@@ -341,11 +388,15 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
341
388
|
private lastState;
|
|
342
389
|
private autoreleaseLock;
|
|
343
390
|
private readonly maxAutomaticHops;
|
|
391
|
+
private readonly maxQueueLength;
|
|
344
392
|
private readonly queue;
|
|
345
393
|
private running;
|
|
394
|
+
private idleWaiters;
|
|
346
395
|
private inSyncCallback;
|
|
347
396
|
private readonly beforeObservers;
|
|
348
397
|
private readonly afterObservers;
|
|
398
|
+
private readonly onChainedOperationError?;
|
|
399
|
+
private readonly onReleaseError?;
|
|
349
400
|
constructor(subject: TSubject, process: ProcessInterface, options?: StatemachineOptions<TSubject>);
|
|
350
401
|
getCurrentState(): StateInterface;
|
|
351
402
|
getLastState(): StateInterface | null;
|
|
@@ -353,17 +404,40 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
353
404
|
getProcess(): ProcessInterface;
|
|
354
405
|
attachBefore(observer: BeforeTransitionObserver<TSubject>): void;
|
|
355
406
|
detachBefore(observer: BeforeTransitionObserver<TSubject>): void;
|
|
407
|
+
/** Snapshot — detaching later does not change an already-returned list,
|
|
408
|
+
* and mutating it does not change the machine's registrations. */
|
|
356
409
|
getBeforeObservers(): Iterable<BeforeTransitionObserver<TSubject>>;
|
|
357
410
|
attachAfter(observer: AfterTransitionObserver<TSubject>): void;
|
|
358
411
|
detachAfter(observer: AfterTransitionObserver<TSubject>): void;
|
|
412
|
+
/** Snapshot — see getBeforeObservers. */
|
|
359
413
|
getAfterObservers(): Iterable<AfterTransitionObserver<TSubject>>;
|
|
360
414
|
acquireLock(): Promise<boolean>;
|
|
415
|
+
/**
|
|
416
|
+
* Releases the mutex. A failed release — whether the mutex throws or
|
|
417
|
+
* returns false — is reported to the onReleaseError hook; it is not thrown,
|
|
418
|
+
* so manual lock management keeps its existing control flow. Inspect
|
|
419
|
+
* isLockAcquired() (or the hook) to learn whether the lock was actually
|
|
420
|
+
* freed.
|
|
421
|
+
*/
|
|
361
422
|
releaseLock(): Promise<void>;
|
|
362
423
|
isLockAcquired(): boolean;
|
|
363
424
|
isAutoreleaseLock(): boolean;
|
|
364
425
|
setAutoreleaseLock(autorelease: boolean): void;
|
|
365
426
|
triggerEvent(name: string, context?: Map<string, unknown>): Promise<void>;
|
|
366
427
|
checkTransitions(context?: Map<string, unknown>): Promise<void>;
|
|
428
|
+
/**
|
|
429
|
+
* Resolves once the operation queue is empty and the runner is idle —
|
|
430
|
+
* i.e. every operation enqueued so far, including operations chained via
|
|
431
|
+
* EnqueueContext.enqueue(), has completed. Resolves immediately if the
|
|
432
|
+
* machine is already idle. Note this is a quiescence point, not a
|
|
433
|
+
* receipt: work scheduled later (e.g. from a timer) starts a new drain.
|
|
434
|
+
*
|
|
435
|
+
* Like triggerEvent/checkTransitions, this may not be called from inside an
|
|
436
|
+
* observer or condition of the same machine: the machine cannot reach idle
|
|
437
|
+
* while the runner is blocked on that very callback, so awaiting it there
|
|
438
|
+
* always deadlocks.
|
|
439
|
+
*/
|
|
440
|
+
whenIdle(): Promise<void>;
|
|
367
441
|
/** Runs fn with the re-entrancy flag set for its SYNCHRONOUS portion only:
|
|
368
442
|
* the flag is cleared as soon as fn returns (before any promise it returned
|
|
369
443
|
* is awaited), so concurrent external callers are never affected. This
|
|
@@ -376,6 +450,20 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
376
450
|
private enqueueOperation;
|
|
377
451
|
private runIfIdle;
|
|
378
452
|
private runOperation;
|
|
453
|
+
/**
|
|
454
|
+
* Releases the mutex, normalizing its two failure modes into one result: a
|
|
455
|
+
* thrown error, and a false return — the failure signal MutexInterface /
|
|
456
|
+
* LockAdapterInterface define (a PostgreSQL advisory unlock that returns
|
|
457
|
+
* false, a Redis DEL that removed nothing). A false return means the lock
|
|
458
|
+
* may still be held, so it must never be mistaken for a successful release.
|
|
459
|
+
*
|
|
460
|
+
* Every failure is surfaced through the diagnostic hook — when the
|
|
461
|
+
* operation also failed, the rejection carries the operation error and this
|
|
462
|
+
* hook is the only place the release error appears.
|
|
463
|
+
*
|
|
464
|
+
* @returns null on success, or the failure wrapped for the caller to raise.
|
|
465
|
+
*/
|
|
466
|
+
private releaseMutex;
|
|
379
467
|
private resolveEvent;
|
|
380
468
|
/**
|
|
381
469
|
* Drive transitions starting from the current state, following automatic
|
|
@@ -423,11 +511,11 @@ interface LastStateHasChangedDateInterface {
|
|
|
423
511
|
getLastStateHasChangedDate(): Date;
|
|
424
512
|
}
|
|
425
513
|
|
|
426
|
-
/** @deprecated No longer used internally; will be removed in
|
|
514
|
+
/** @deprecated No longer used internally; will be removed in v5. */
|
|
427
515
|
interface CallbackInterface {
|
|
428
516
|
invoke(): MaybePromise<void>;
|
|
429
517
|
}
|
|
430
|
-
/** @deprecated No longer used internally; will be removed in
|
|
518
|
+
/** @deprecated No longer used internally; will be removed in v5. */
|
|
431
519
|
interface DispatcherInterface extends CallbackInterface {
|
|
432
520
|
dispatch(event: EventInterface, args?: unknown[]): void;
|
|
433
521
|
invoke(): Promise<void>;
|
|
@@ -498,11 +586,11 @@ declare class Not<TSubject = unknown> implements ConditionInterface<TSubject> {
|
|
|
498
586
|
}
|
|
499
587
|
|
|
500
588
|
/**
|
|
501
|
-
*
|
|
589
|
+
* Observer for Event observers (commands attached to specific events).
|
|
502
590
|
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
591
|
+
* This is not a Statemachine observer. To run a callback after every
|
|
592
|
+
* transition, implement AfterTransitionObserver directly or compose a small
|
|
593
|
+
* wrapper.
|
|
506
594
|
*/
|
|
507
595
|
declare class CallbackObserver implements Observer {
|
|
508
596
|
private readonly callback;
|
|
@@ -609,7 +697,15 @@ declare class LockAdapterMutex implements MutexInterface {
|
|
|
609
697
|
private readonly lockAdapter;
|
|
610
698
|
private readonly resourceName;
|
|
611
699
|
private acquired;
|
|
700
|
+
private pendingAcquire;
|
|
612
701
|
constructor(lockAdapter: LockAdapterInterface, resourceName: string);
|
|
702
|
+
/**
|
|
703
|
+
* Overlapping calls share one underlying acquire: the `acquired` flag is
|
|
704
|
+
* only set after the adapter resolves, so without this both callers would
|
|
705
|
+
* pass the check and acquire twice on a non-idempotent adapter (database
|
|
706
|
+
* advisory locks, redis SET NX). The pending promise is cleared once it
|
|
707
|
+
* settles, so a failed acquire can still be retried.
|
|
708
|
+
*/
|
|
613
709
|
acquireLock(): Promise<boolean>;
|
|
614
710
|
releaseLock(): Promise<boolean>;
|
|
615
711
|
isAcquired(): boolean;
|
|
@@ -624,6 +720,15 @@ declare class MutexFactory<TSubject = unknown> implements MutexFactoryInterface<
|
|
|
624
720
|
createMutex(subject: TSubject): MutexInterface;
|
|
625
721
|
}
|
|
626
722
|
|
|
723
|
+
/**
|
|
724
|
+
* Engine options applied to every machine the factory creates.
|
|
725
|
+
*
|
|
726
|
+
* `initialStateName`, `mutex` and `transitionSelector` are excluded: the
|
|
727
|
+
* factory derives them per subject from the state-name detector, the mutex
|
|
728
|
+
* factory and setTransitionSelector, so a template value could only
|
|
729
|
+
* contradict them.
|
|
730
|
+
*/
|
|
731
|
+
type FactoryStatemachineOptions<TSubject = unknown> = Omit<StatemachineOptions<TSubject>, "initialStateName" | "mutex" | "transitionSelector">;
|
|
627
732
|
declare class Factory<TSubject = unknown> implements FactoryInterface<TSubject> {
|
|
628
733
|
private readonly processDetector;
|
|
629
734
|
private readonly stateNameDetector;
|
|
@@ -631,7 +736,15 @@ declare class Factory<TSubject = unknown> implements FactoryInterface<TSubject>
|
|
|
631
736
|
private readonly afterObservers;
|
|
632
737
|
private transitionSelector;
|
|
633
738
|
private mutexFactory;
|
|
634
|
-
|
|
739
|
+
private readonly options;
|
|
740
|
+
/**
|
|
741
|
+
* @param options Engine options applied to every machine this factory
|
|
742
|
+
* creates — back-pressure (maxQueueLength), the automatic-hop bound, lock
|
|
743
|
+
* autorelease, and the onChainedOperationError / onReleaseError diagnostic
|
|
744
|
+
* sinks. Without them, factory-created machines would silently run on
|
|
745
|
+
* defaults, which is precisely where those sinks matter most.
|
|
746
|
+
*/
|
|
747
|
+
constructor(processDetector: ProcessDetectorInterface<TSubject>, stateNameDetector?: StateNameDetectorInterface<TSubject> | null, options?: FactoryStatemachineOptions<TSubject>);
|
|
635
748
|
setMutexFactory(factory: MutexFactoryInterface<TSubject> | null): void;
|
|
636
749
|
setTransitionSelector(selector: TransitionSelectorInterface<TSubject>): void;
|
|
637
750
|
attachBeforeObserver(observer: BeforeTransitionObserver<TSubject>): void;
|
|
@@ -674,11 +787,12 @@ interface Graph {
|
|
|
674
787
|
nodes: GraphNode[];
|
|
675
788
|
edges: GraphEdge[];
|
|
676
789
|
}
|
|
790
|
+
type GraphDirection = "TB" | "BT" | "LR" | "RL";
|
|
677
791
|
interface DotOptions {
|
|
678
|
-
rankdir?:
|
|
792
|
+
rankdir?: GraphDirection;
|
|
679
793
|
}
|
|
680
794
|
interface MermaidOptions {
|
|
681
|
-
direction?:
|
|
795
|
+
direction?: GraphDirection;
|
|
682
796
|
}
|
|
683
797
|
declare class GraphBuilder {
|
|
684
798
|
private readonly nodes;
|
|
@@ -711,6 +825,20 @@ declare class LockCanNotBeAcquiredError extends FinitaError {
|
|
|
711
825
|
constructor(message?: string);
|
|
712
826
|
}
|
|
713
827
|
|
|
828
|
+
/**
|
|
829
|
+
* The mutex reported a failed release by returning false, as
|
|
830
|
+
* LockAdapterInterface specifies (e.g. a PostgreSQL advisory unlock that
|
|
831
|
+
* returns false, or a Redis DEL that removed nothing).
|
|
832
|
+
*
|
|
833
|
+
* The lock must be assumed to still be held: the engine surfaces this so a
|
|
834
|
+
* failed release can never be mistaken for a successful one, which would let
|
|
835
|
+
* every later operation piggyback on — and never release — a stuck lock.
|
|
836
|
+
*/
|
|
837
|
+
declare class LockCanNotBeReleasedError extends FinitaError {
|
|
838
|
+
readonly code = "lockCanNotBeReleased";
|
|
839
|
+
constructor(message?: string);
|
|
840
|
+
}
|
|
841
|
+
|
|
714
842
|
declare class DuplicateStateError extends FinitaError {
|
|
715
843
|
readonly code = "duplicateState";
|
|
716
844
|
readonly stateName: string;
|
|
@@ -723,7 +851,7 @@ declare class ProcessFinalizedError extends FinitaError {
|
|
|
723
851
|
constructor(processName: string);
|
|
724
852
|
}
|
|
725
853
|
|
|
726
|
-
type GraphValidationCode = "unknownTarget" | "unknownSource" | "missingInitialState" | "multipleInitialStates" | "invalidStateName" | "invalidEventName" | "invalidConditionName" | "orphanState";
|
|
854
|
+
type GraphValidationCode = "unknownTarget" | "unknownSource" | "missingInitialState" | "multipleInitialStates" | "invalidStateName" | "invalidEventName" | "invalidConditionName" | "invalidTransitionWeight" | "orphanState";
|
|
727
855
|
declare class GraphValidationError extends FinitaError {
|
|
728
856
|
readonly code: GraphValidationCode;
|
|
729
857
|
readonly details: Readonly<Record<string, unknown>>;
|
|
@@ -773,10 +901,19 @@ declare class InvalidSubjectError extends FinitaError {
|
|
|
773
901
|
constructor(expectedInterface: string, missingMembers: Iterable<string>);
|
|
774
902
|
}
|
|
775
903
|
|
|
904
|
+
/** One of the simultaneously-active transitions that caused the ambiguity. */
|
|
905
|
+
interface AmbiguousTransitionCandidate {
|
|
906
|
+
targetStateName: string;
|
|
907
|
+
eventName: string | null;
|
|
908
|
+
conditionName: string | null;
|
|
909
|
+
weight: number;
|
|
910
|
+
}
|
|
776
911
|
declare class AmbiguousTransitionError extends FinitaError {
|
|
777
912
|
readonly code = "ambiguousTransition";
|
|
778
913
|
readonly activeCount: number;
|
|
779
|
-
|
|
914
|
+
/** The competing transitions — what you need to resolve the ambiguity. */
|
|
915
|
+
readonly candidates: readonly Readonly<AmbiguousTransitionCandidate>[];
|
|
916
|
+
constructor(activeCount: number, candidates?: Iterable<AmbiguousTransitionCandidate>);
|
|
780
917
|
}
|
|
781
918
|
|
|
782
919
|
declare class AutomaticTransitionCycleError extends FinitaError {
|
|
@@ -791,4 +928,9 @@ declare class ReentrancyError extends FinitaError {
|
|
|
791
928
|
constructor(operation: string);
|
|
792
929
|
}
|
|
793
930
|
|
|
794
|
-
|
|
931
|
+
declare class QueueLimitExceededError extends FinitaError {
|
|
932
|
+
readonly code = "queueLimitExceeded";
|
|
933
|
+
constructor(limit: number, eventName: string | null);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export { AbstractNamedProcessDetector, ActiveTransitionFilter, type AddStateOptions, type AddTransitionOptions, type AfterTransitionObserver, type AmbiguousTransitionCandidate, AmbiguousTransitionError, AndComposite, AutomaticTransitionCycleError, type BeforeTransitionObserver, type BuildOptions, CallbackCondition, type CallbackInterface, CallbackObserver, type ConditionCallbackFn, type ConditionInterface, Contradiction, type DispatcherInterface, type DotOptions, DuplicateStateError, type DuplicateTransitionConflict, DuplicateTransitionError, type EnqueueContext, Event, type EventInterface, Factory, type FactoryInterface, type FactoryStatemachineOptions, FilterStateByEvent, FilterStateByFinalState, FilterStateByTransition, FilterTransitionByEvent, FinitaError, type Graph, GraphBuilder, type GraphDirection, type GraphEdge, type GraphNode, type GraphValidationCode, GraphValidationError, InvalidSubjectError, type LastStateHasChangedDateInterface, type LockAdapterInterface, LockAdapterMutex, LockCanNotBeAcquiredError, LockCanNotBeReleasedError, type LoggerInterface, type MaybePromise, type MermaidOptions, type Metadata, MutexFactory, type MutexFactoryInterface, type MutexInterface, type Named, Not, NullMutex, type ObservableSubject, type Observer, OnEnterObserver, OneOrNoneActiveTransition, OrComposite, Process, ProcessBuilder, type ProcessDetectorInterface, ProcessFinalizedError, type ProcessInterface, ProcessNotFoundError, type ProposedTransitionFrame, QueueLimitExceededError, ReentrancyError, ScoreTransition, SingleProcessDetector, State, type StateCollectionInterface, StateEventNotFoundError, type StateInterface, type StateNameDetectorInterface, StateNotFoundError, type StatefulInterface, StatefulStateNameDetector, StatefulStatusChanger, Statemachine, type StatemachineInterface, type StatemachineOptions, type StringConverter, Tautology, Timeout, Transition, type TransitionFrame, type TransitionInterface, TransitionLogger, type TransitionSelectorInterface, WeightTransition, type Weighted, WrongEventForStateError };
|