@camcima/finita 4.1.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 +125 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -9
- package/dist/index.d.ts +88 -9
- package/dist/index.js +124 -21
- 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;
|
|
@@ -402,11 +404,21 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
402
404
|
getProcess(): ProcessInterface;
|
|
403
405
|
attachBefore(observer: BeforeTransitionObserver<TSubject>): void;
|
|
404
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. */
|
|
405
409
|
getBeforeObservers(): Iterable<BeforeTransitionObserver<TSubject>>;
|
|
406
410
|
attachAfter(observer: AfterTransitionObserver<TSubject>): void;
|
|
407
411
|
detachAfter(observer: AfterTransitionObserver<TSubject>): void;
|
|
412
|
+
/** Snapshot — see getBeforeObservers. */
|
|
408
413
|
getAfterObservers(): Iterable<AfterTransitionObserver<TSubject>>;
|
|
409
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
|
+
*/
|
|
410
422
|
releaseLock(): Promise<void>;
|
|
411
423
|
isLockAcquired(): boolean;
|
|
412
424
|
isAutoreleaseLock(): boolean;
|
|
@@ -419,6 +431,11 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
419
431
|
* EnqueueContext.enqueue(), has completed. Resolves immediately if the
|
|
420
432
|
* machine is already idle. Note this is a quiescence point, not a
|
|
421
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.
|
|
422
439
|
*/
|
|
423
440
|
whenIdle(): Promise<void>;
|
|
424
441
|
/** Runs fn with the re-entrancy flag set for its SYNCHRONOUS portion only:
|
|
@@ -433,6 +450,20 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
433
450
|
private enqueueOperation;
|
|
434
451
|
private runIfIdle;
|
|
435
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;
|
|
436
467
|
private resolveEvent;
|
|
437
468
|
/**
|
|
438
469
|
* Drive transitions starting from the current state, following automatic
|
|
@@ -480,11 +511,11 @@ interface LastStateHasChangedDateInterface {
|
|
|
480
511
|
getLastStateHasChangedDate(): Date;
|
|
481
512
|
}
|
|
482
513
|
|
|
483
|
-
/** @deprecated No longer used internally; will be removed in
|
|
514
|
+
/** @deprecated No longer used internally; will be removed in v5. */
|
|
484
515
|
interface CallbackInterface {
|
|
485
516
|
invoke(): MaybePromise<void>;
|
|
486
517
|
}
|
|
487
|
-
/** @deprecated No longer used internally; will be removed in
|
|
518
|
+
/** @deprecated No longer used internally; will be removed in v5. */
|
|
488
519
|
interface DispatcherInterface extends CallbackInterface {
|
|
489
520
|
dispatch(event: EventInterface, args?: unknown[]): void;
|
|
490
521
|
invoke(): Promise<void>;
|
|
@@ -555,11 +586,11 @@ declare class Not<TSubject = unknown> implements ConditionInterface<TSubject> {
|
|
|
555
586
|
}
|
|
556
587
|
|
|
557
588
|
/**
|
|
558
|
-
*
|
|
589
|
+
* Observer for Event observers (commands attached to specific events).
|
|
559
590
|
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
591
|
+
* This is not a Statemachine observer. To run a callback after every
|
|
592
|
+
* transition, implement AfterTransitionObserver directly or compose a small
|
|
593
|
+
* wrapper.
|
|
563
594
|
*/
|
|
564
595
|
declare class CallbackObserver implements Observer {
|
|
565
596
|
private readonly callback;
|
|
@@ -666,7 +697,15 @@ declare class LockAdapterMutex implements MutexInterface {
|
|
|
666
697
|
private readonly lockAdapter;
|
|
667
698
|
private readonly resourceName;
|
|
668
699
|
private acquired;
|
|
700
|
+
private pendingAcquire;
|
|
669
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
|
+
*/
|
|
670
709
|
acquireLock(): Promise<boolean>;
|
|
671
710
|
releaseLock(): Promise<boolean>;
|
|
672
711
|
isAcquired(): boolean;
|
|
@@ -681,6 +720,15 @@ declare class MutexFactory<TSubject = unknown> implements MutexFactoryInterface<
|
|
|
681
720
|
createMutex(subject: TSubject): MutexInterface;
|
|
682
721
|
}
|
|
683
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">;
|
|
684
732
|
declare class Factory<TSubject = unknown> implements FactoryInterface<TSubject> {
|
|
685
733
|
private readonly processDetector;
|
|
686
734
|
private readonly stateNameDetector;
|
|
@@ -688,7 +736,15 @@ declare class Factory<TSubject = unknown> implements FactoryInterface<TSubject>
|
|
|
688
736
|
private readonly afterObservers;
|
|
689
737
|
private transitionSelector;
|
|
690
738
|
private mutexFactory;
|
|
691
|
-
|
|
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>);
|
|
692
748
|
setMutexFactory(factory: MutexFactoryInterface<TSubject> | null): void;
|
|
693
749
|
setTransitionSelector(selector: TransitionSelectorInterface<TSubject>): void;
|
|
694
750
|
attachBeforeObserver(observer: BeforeTransitionObserver<TSubject>): void;
|
|
@@ -769,6 +825,20 @@ declare class LockCanNotBeAcquiredError extends FinitaError {
|
|
|
769
825
|
constructor(message?: string);
|
|
770
826
|
}
|
|
771
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
|
+
|
|
772
842
|
declare class DuplicateStateError extends FinitaError {
|
|
773
843
|
readonly code = "duplicateState";
|
|
774
844
|
readonly stateName: string;
|
|
@@ -831,10 +901,19 @@ declare class InvalidSubjectError extends FinitaError {
|
|
|
831
901
|
constructor(expectedInterface: string, missingMembers: Iterable<string>);
|
|
832
902
|
}
|
|
833
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
|
+
}
|
|
834
911
|
declare class AmbiguousTransitionError extends FinitaError {
|
|
835
912
|
readonly code = "ambiguousTransition";
|
|
836
913
|
readonly activeCount: number;
|
|
837
|
-
|
|
914
|
+
/** The competing transitions — what you need to resolve the ambiguity. */
|
|
915
|
+
readonly candidates: readonly Readonly<AmbiguousTransitionCandidate>[];
|
|
916
|
+
constructor(activeCount: number, candidates?: Iterable<AmbiguousTransitionCandidate>);
|
|
838
917
|
}
|
|
839
918
|
|
|
840
919
|
declare class AutomaticTransitionCycleError extends FinitaError {
|
|
@@ -854,4 +933,4 @@ declare class QueueLimitExceededError extends FinitaError {
|
|
|
854
933
|
constructor(limit: number, eventName: string | null);
|
|
855
934
|
}
|
|
856
935
|
|
|
857
|
-
export { AbstractNamedProcessDetector, ActiveTransitionFilter, type AddStateOptions, type AddTransitionOptions, type AfterTransitionObserver, 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, FilterStateByEvent, FilterStateByFinalState, FilterStateByTransition, FilterTransitionByEvent, FinitaError, type Graph, GraphBuilder, type GraphDirection, type GraphEdge, type GraphNode, type GraphValidationCode, GraphValidationError, InvalidSubjectError, type LastStateHasChangedDateInterface, type LockAdapterInterface, LockAdapterMutex, LockCanNotBeAcquiredError, 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 };
|
|
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;
|
|
@@ -402,11 +404,21 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
402
404
|
getProcess(): ProcessInterface;
|
|
403
405
|
attachBefore(observer: BeforeTransitionObserver<TSubject>): void;
|
|
404
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. */
|
|
405
409
|
getBeforeObservers(): Iterable<BeforeTransitionObserver<TSubject>>;
|
|
406
410
|
attachAfter(observer: AfterTransitionObserver<TSubject>): void;
|
|
407
411
|
detachAfter(observer: AfterTransitionObserver<TSubject>): void;
|
|
412
|
+
/** Snapshot — see getBeforeObservers. */
|
|
408
413
|
getAfterObservers(): Iterable<AfterTransitionObserver<TSubject>>;
|
|
409
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
|
+
*/
|
|
410
422
|
releaseLock(): Promise<void>;
|
|
411
423
|
isLockAcquired(): boolean;
|
|
412
424
|
isAutoreleaseLock(): boolean;
|
|
@@ -419,6 +431,11 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
419
431
|
* EnqueueContext.enqueue(), has completed. Resolves immediately if the
|
|
420
432
|
* machine is already idle. Note this is a quiescence point, not a
|
|
421
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.
|
|
422
439
|
*/
|
|
423
440
|
whenIdle(): Promise<void>;
|
|
424
441
|
/** Runs fn with the re-entrancy flag set for its SYNCHRONOUS portion only:
|
|
@@ -433,6 +450,20 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
433
450
|
private enqueueOperation;
|
|
434
451
|
private runIfIdle;
|
|
435
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;
|
|
436
467
|
private resolveEvent;
|
|
437
468
|
/**
|
|
438
469
|
* Drive transitions starting from the current state, following automatic
|
|
@@ -480,11 +511,11 @@ interface LastStateHasChangedDateInterface {
|
|
|
480
511
|
getLastStateHasChangedDate(): Date;
|
|
481
512
|
}
|
|
482
513
|
|
|
483
|
-
/** @deprecated No longer used internally; will be removed in
|
|
514
|
+
/** @deprecated No longer used internally; will be removed in v5. */
|
|
484
515
|
interface CallbackInterface {
|
|
485
516
|
invoke(): MaybePromise<void>;
|
|
486
517
|
}
|
|
487
|
-
/** @deprecated No longer used internally; will be removed in
|
|
518
|
+
/** @deprecated No longer used internally; will be removed in v5. */
|
|
488
519
|
interface DispatcherInterface extends CallbackInterface {
|
|
489
520
|
dispatch(event: EventInterface, args?: unknown[]): void;
|
|
490
521
|
invoke(): Promise<void>;
|
|
@@ -555,11 +586,11 @@ declare class Not<TSubject = unknown> implements ConditionInterface<TSubject> {
|
|
|
555
586
|
}
|
|
556
587
|
|
|
557
588
|
/**
|
|
558
|
-
*
|
|
589
|
+
* Observer for Event observers (commands attached to specific events).
|
|
559
590
|
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
591
|
+
* This is not a Statemachine observer. To run a callback after every
|
|
592
|
+
* transition, implement AfterTransitionObserver directly or compose a small
|
|
593
|
+
* wrapper.
|
|
563
594
|
*/
|
|
564
595
|
declare class CallbackObserver implements Observer {
|
|
565
596
|
private readonly callback;
|
|
@@ -666,7 +697,15 @@ declare class LockAdapterMutex implements MutexInterface {
|
|
|
666
697
|
private readonly lockAdapter;
|
|
667
698
|
private readonly resourceName;
|
|
668
699
|
private acquired;
|
|
700
|
+
private pendingAcquire;
|
|
669
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
|
+
*/
|
|
670
709
|
acquireLock(): Promise<boolean>;
|
|
671
710
|
releaseLock(): Promise<boolean>;
|
|
672
711
|
isAcquired(): boolean;
|
|
@@ -681,6 +720,15 @@ declare class MutexFactory<TSubject = unknown> implements MutexFactoryInterface<
|
|
|
681
720
|
createMutex(subject: TSubject): MutexInterface;
|
|
682
721
|
}
|
|
683
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">;
|
|
684
732
|
declare class Factory<TSubject = unknown> implements FactoryInterface<TSubject> {
|
|
685
733
|
private readonly processDetector;
|
|
686
734
|
private readonly stateNameDetector;
|
|
@@ -688,7 +736,15 @@ declare class Factory<TSubject = unknown> implements FactoryInterface<TSubject>
|
|
|
688
736
|
private readonly afterObservers;
|
|
689
737
|
private transitionSelector;
|
|
690
738
|
private mutexFactory;
|
|
691
|
-
|
|
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>);
|
|
692
748
|
setMutexFactory(factory: MutexFactoryInterface<TSubject> | null): void;
|
|
693
749
|
setTransitionSelector(selector: TransitionSelectorInterface<TSubject>): void;
|
|
694
750
|
attachBeforeObserver(observer: BeforeTransitionObserver<TSubject>): void;
|
|
@@ -769,6 +825,20 @@ declare class LockCanNotBeAcquiredError extends FinitaError {
|
|
|
769
825
|
constructor(message?: string);
|
|
770
826
|
}
|
|
771
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
|
+
|
|
772
842
|
declare class DuplicateStateError extends FinitaError {
|
|
773
843
|
readonly code = "duplicateState";
|
|
774
844
|
readonly stateName: string;
|
|
@@ -831,10 +901,19 @@ declare class InvalidSubjectError extends FinitaError {
|
|
|
831
901
|
constructor(expectedInterface: string, missingMembers: Iterable<string>);
|
|
832
902
|
}
|
|
833
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
|
+
}
|
|
834
911
|
declare class AmbiguousTransitionError extends FinitaError {
|
|
835
912
|
readonly code = "ambiguousTransition";
|
|
836
913
|
readonly activeCount: number;
|
|
837
|
-
|
|
914
|
+
/** The competing transitions — what you need to resolve the ambiguity. */
|
|
915
|
+
readonly candidates: readonly Readonly<AmbiguousTransitionCandidate>[];
|
|
916
|
+
constructor(activeCount: number, candidates?: Iterable<AmbiguousTransitionCandidate>);
|
|
838
917
|
}
|
|
839
918
|
|
|
840
919
|
declare class AutomaticTransitionCycleError extends FinitaError {
|
|
@@ -854,4 +933,4 @@ declare class QueueLimitExceededError extends FinitaError {
|
|
|
854
933
|
constructor(limit: number, eventName: string | null);
|
|
855
934
|
}
|
|
856
935
|
|
|
857
|
-
export { AbstractNamedProcessDetector, ActiveTransitionFilter, type AddStateOptions, type AddTransitionOptions, type AfterTransitionObserver, 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, FilterStateByEvent, FilterStateByFinalState, FilterStateByTransition, FilterTransitionByEvent, FinitaError, type Graph, GraphBuilder, type GraphDirection, type GraphEdge, type GraphNode, type GraphValidationCode, GraphValidationError, InvalidSubjectError, type LastStateHasChangedDateInterface, type LockAdapterInterface, LockAdapterMutex, LockCanNotBeAcquiredError, 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 };
|
|
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.js
CHANGED
|
@@ -31,8 +31,10 @@ var Event = class {
|
|
|
31
31
|
await observer.update(this, args);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
/** Snapshot — detaching later does not change an already-returned list,
|
|
35
|
+
* and mutating it does not change the event's registrations. */
|
|
34
36
|
getObservers() {
|
|
35
|
-
return this.observers;
|
|
37
|
+
return [...this.observers];
|
|
36
38
|
}
|
|
37
39
|
getMetadata() {
|
|
38
40
|
return Object.fromEntries(this.metadata);
|
|
@@ -593,12 +595,30 @@ var ProcessBuilder = class _ProcessBuilder {
|
|
|
593
595
|
var AmbiguousTransitionError = class extends FinitaError {
|
|
594
596
|
code = "ambiguousTransition";
|
|
595
597
|
activeCount;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
+
/** The competing transitions — what you need to resolve the ambiguity. */
|
|
599
|
+
candidates;
|
|
600
|
+
constructor(activeCount, candidates = []) {
|
|
601
|
+
const list = Array.from(candidates, (c) => Object.freeze({ ...c }));
|
|
602
|
+
const detail = list.length > 0 ? ` Candidates: ${list.map(describeCandidate).join("; ")}.` : "";
|
|
603
|
+
super(
|
|
604
|
+
`More than one transition is active! (active count: ${activeCount})${detail}`
|
|
605
|
+
);
|
|
598
606
|
this.name = "AmbiguousTransitionError";
|
|
599
607
|
this.activeCount = activeCount;
|
|
608
|
+
this.candidates = Object.freeze(list);
|
|
600
609
|
}
|
|
601
610
|
};
|
|
611
|
+
function describeCandidate(candidate) {
|
|
612
|
+
const parts = [`-> "${candidate.targetStateName}"`];
|
|
613
|
+
parts.push(
|
|
614
|
+
candidate.eventName === null ? "on <automatic>" : `on event "${candidate.eventName}"`
|
|
615
|
+
);
|
|
616
|
+
if (candidate.conditionName !== null) {
|
|
617
|
+
parts.push(`if ${candidate.conditionName}`);
|
|
618
|
+
}
|
|
619
|
+
parts.push(`weight ${candidate.weight}`);
|
|
620
|
+
return parts.join(" ");
|
|
621
|
+
}
|
|
602
622
|
|
|
603
623
|
// src/selector/OneOrNoneActiveTransition.ts
|
|
604
624
|
var OneOrNoneActiveTransition = class {
|
|
@@ -610,7 +630,15 @@ var OneOrNoneActiveTransition = class {
|
|
|
610
630
|
case 1:
|
|
611
631
|
return arr[0];
|
|
612
632
|
default:
|
|
613
|
-
throw new AmbiguousTransitionError(
|
|
633
|
+
throw new AmbiguousTransitionError(
|
|
634
|
+
arr.length,
|
|
635
|
+
arr.map((transition) => ({
|
|
636
|
+
targetStateName: transition.getTargetState().getName(),
|
|
637
|
+
eventName: transition.getEventName(),
|
|
638
|
+
conditionName: transition.getConditionName(),
|
|
639
|
+
weight: transition.getWeight()
|
|
640
|
+
}))
|
|
641
|
+
);
|
|
614
642
|
}
|
|
615
643
|
}
|
|
616
644
|
};
|
|
@@ -687,6 +715,15 @@ var LockCanNotBeAcquiredError = class extends FinitaError {
|
|
|
687
715
|
}
|
|
688
716
|
};
|
|
689
717
|
|
|
718
|
+
// src/error/LockCanNotBeReleasedError.ts
|
|
719
|
+
var LockCanNotBeReleasedError = class extends FinitaError {
|
|
720
|
+
code = "lockCanNotBeReleased";
|
|
721
|
+
constructor(message = "Lock can not be released! releaseLock() returned false; the lock may still be held.") {
|
|
722
|
+
super(message);
|
|
723
|
+
this.name = "LockCanNotBeReleasedError";
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
|
|
690
727
|
// src/error/AutomaticTransitionCycleError.ts
|
|
691
728
|
var AutomaticTransitionCycleError = class extends FinitaError {
|
|
692
729
|
code = "automaticTransitionCycle";
|
|
@@ -789,8 +826,10 @@ var Statemachine = class {
|
|
|
789
826
|
const idx = this.beforeObservers.indexOf(observer);
|
|
790
827
|
if (idx >= 0) this.beforeObservers.splice(idx, 1);
|
|
791
828
|
}
|
|
829
|
+
/** Snapshot — detaching later does not change an already-returned list,
|
|
830
|
+
* and mutating it does not change the machine's registrations. */
|
|
792
831
|
getBeforeObservers() {
|
|
793
|
-
return this.beforeObservers;
|
|
832
|
+
return [...this.beforeObservers];
|
|
794
833
|
}
|
|
795
834
|
attachAfter(observer) {
|
|
796
835
|
if (this.afterObservers.includes(observer)) return;
|
|
@@ -800,15 +839,23 @@ var Statemachine = class {
|
|
|
800
839
|
const idx = this.afterObservers.indexOf(observer);
|
|
801
840
|
if (idx >= 0) this.afterObservers.splice(idx, 1);
|
|
802
841
|
}
|
|
842
|
+
/** Snapshot — see getBeforeObservers. */
|
|
803
843
|
getAfterObservers() {
|
|
804
|
-
return this.afterObservers;
|
|
844
|
+
return [...this.afterObservers];
|
|
805
845
|
}
|
|
806
846
|
// --- public locking ---
|
|
807
847
|
async acquireLock() {
|
|
808
848
|
return this.mutex.acquireLock();
|
|
809
849
|
}
|
|
850
|
+
/**
|
|
851
|
+
* Releases the mutex. A failed release — whether the mutex throws or
|
|
852
|
+
* returns false — is reported to the onReleaseError hook; it is not thrown,
|
|
853
|
+
* so manual lock management keeps its existing control flow. Inspect
|
|
854
|
+
* isLockAcquired() (or the hook) to learn whether the lock was actually
|
|
855
|
+
* freed.
|
|
856
|
+
*/
|
|
810
857
|
async releaseLock() {
|
|
811
|
-
await this.
|
|
858
|
+
await this.releaseMutex();
|
|
812
859
|
}
|
|
813
860
|
isLockAcquired() {
|
|
814
861
|
return this.mutex.isAcquired();
|
|
@@ -838,8 +885,14 @@ var Statemachine = class {
|
|
|
838
885
|
* EnqueueContext.enqueue(), has completed. Resolves immediately if the
|
|
839
886
|
* machine is already idle. Note this is a quiescence point, not a
|
|
840
887
|
* receipt: work scheduled later (e.g. from a timer) starts a new drain.
|
|
888
|
+
*
|
|
889
|
+
* Like triggerEvent/checkTransitions, this may not be called from inside an
|
|
890
|
+
* observer or condition of the same machine: the machine cannot reach idle
|
|
891
|
+
* while the runner is blocked on that very callback, so awaiting it there
|
|
892
|
+
* always deadlocks.
|
|
841
893
|
*/
|
|
842
894
|
whenIdle() {
|
|
895
|
+
this.assertNotReentrant("whenIdle()");
|
|
843
896
|
if (!this.running && this.queue.isEmpty()) {
|
|
844
897
|
return Promise.resolve();
|
|
845
898
|
}
|
|
@@ -918,15 +971,8 @@ var Statemachine = class {
|
|
|
918
971
|
failure = { err };
|
|
919
972
|
} finally {
|
|
920
973
|
if (acquiredHere && this.autoreleaseLock) {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
} catch (err) {
|
|
924
|
-
try {
|
|
925
|
-
this.onReleaseError?.(err);
|
|
926
|
-
} catch {
|
|
927
|
-
}
|
|
928
|
-
if (!failure) failure = { err };
|
|
929
|
-
}
|
|
974
|
+
const releaseFailure = await this.releaseMutex();
|
|
975
|
+
if (releaseFailure && !failure) failure = releaseFailure;
|
|
930
976
|
}
|
|
931
977
|
}
|
|
932
978
|
if (failure) {
|
|
@@ -935,6 +981,36 @@ var Statemachine = class {
|
|
|
935
981
|
op.resolve();
|
|
936
982
|
}
|
|
937
983
|
}
|
|
984
|
+
/**
|
|
985
|
+
* Releases the mutex, normalizing its two failure modes into one result: a
|
|
986
|
+
* thrown error, and a false return — the failure signal MutexInterface /
|
|
987
|
+
* LockAdapterInterface define (a PostgreSQL advisory unlock that returns
|
|
988
|
+
* false, a Redis DEL that removed nothing). A false return means the lock
|
|
989
|
+
* may still be held, so it must never be mistaken for a successful release.
|
|
990
|
+
*
|
|
991
|
+
* Every failure is surfaced through the diagnostic hook — when the
|
|
992
|
+
* operation also failed, the rejection carries the operation error and this
|
|
993
|
+
* hook is the only place the release error appears.
|
|
994
|
+
*
|
|
995
|
+
* @returns null on success, or the failure wrapped for the caller to raise.
|
|
996
|
+
*/
|
|
997
|
+
async releaseMutex() {
|
|
998
|
+
let failure = null;
|
|
999
|
+
try {
|
|
1000
|
+
if (!await this.mutex.releaseLock()) {
|
|
1001
|
+
failure = { err: new LockCanNotBeReleasedError() };
|
|
1002
|
+
}
|
|
1003
|
+
} catch (err) {
|
|
1004
|
+
failure = { err };
|
|
1005
|
+
}
|
|
1006
|
+
if (failure) {
|
|
1007
|
+
try {
|
|
1008
|
+
this.onReleaseError?.(failure.err);
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return failure;
|
|
1013
|
+
}
|
|
938
1014
|
resolveEvent(name) {
|
|
939
1015
|
if (!this.currentState.hasEvent(name)) {
|
|
940
1016
|
throw new WrongEventForStateError(this.currentState.getName(), name);
|
|
@@ -1408,15 +1484,31 @@ var LockAdapterMutex = class {
|
|
|
1408
1484
|
lockAdapter;
|
|
1409
1485
|
resourceName;
|
|
1410
1486
|
acquired = false;
|
|
1487
|
+
pendingAcquire = null;
|
|
1411
1488
|
constructor(lockAdapter, resourceName) {
|
|
1412
1489
|
this.lockAdapter = lockAdapter;
|
|
1413
1490
|
this.resourceName = resourceName;
|
|
1414
1491
|
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Overlapping calls share one underlying acquire: the `acquired` flag is
|
|
1494
|
+
* only set after the adapter resolves, so without this both callers would
|
|
1495
|
+
* pass the check and acquire twice on a non-idempotent adapter (database
|
|
1496
|
+
* advisory locks, redis SET NX). The pending promise is cleared once it
|
|
1497
|
+
* settles, so a failed acquire can still be retried.
|
|
1498
|
+
*/
|
|
1415
1499
|
async acquireLock() {
|
|
1416
|
-
if (
|
|
1417
|
-
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1500
|
+
if (this.acquired) {
|
|
1501
|
+
return true;
|
|
1502
|
+
}
|
|
1503
|
+
this.pendingAcquire ??= (async () => {
|
|
1504
|
+
try {
|
|
1505
|
+
this.acquired = await this.lockAdapter.acquireLock(this.resourceName);
|
|
1506
|
+
return this.acquired;
|
|
1507
|
+
} finally {
|
|
1508
|
+
this.pendingAcquire = null;
|
|
1509
|
+
}
|
|
1510
|
+
})();
|
|
1511
|
+
return this.pendingAcquire;
|
|
1420
1512
|
}
|
|
1421
1513
|
async releaseLock() {
|
|
1422
1514
|
if (this.acquired) {
|
|
@@ -1460,9 +1552,18 @@ var Factory = class {
|
|
|
1460
1552
|
afterObservers = /* @__PURE__ */ new Set();
|
|
1461
1553
|
transitionSelector = null;
|
|
1462
1554
|
mutexFactory = null;
|
|
1463
|
-
|
|
1555
|
+
options;
|
|
1556
|
+
/**
|
|
1557
|
+
* @param options Engine options applied to every machine this factory
|
|
1558
|
+
* creates — back-pressure (maxQueueLength), the automatic-hop bound, lock
|
|
1559
|
+
* autorelease, and the onChainedOperationError / onReleaseError diagnostic
|
|
1560
|
+
* sinks. Without them, factory-created machines would silently run on
|
|
1561
|
+
* defaults, which is precisely where those sinks matter most.
|
|
1562
|
+
*/
|
|
1563
|
+
constructor(processDetector, stateNameDetector, options = {}) {
|
|
1464
1564
|
this.processDetector = processDetector;
|
|
1465
1565
|
this.stateNameDetector = stateNameDetector ?? null;
|
|
1566
|
+
this.options = { ...options };
|
|
1466
1567
|
}
|
|
1467
1568
|
setMutexFactory(factory) {
|
|
1468
1569
|
this.mutexFactory = factory;
|
|
@@ -1487,6 +1588,7 @@ var Factory = class {
|
|
|
1487
1588
|
const stateName = this.stateNameDetector ? this.stateNameDetector.detectCurrentStateName(subject) : void 0;
|
|
1488
1589
|
const mutex = this.mutexFactory ? await this.mutexFactory.createMutex(subject) : void 0;
|
|
1489
1590
|
const sm = new Statemachine(subject, process, {
|
|
1591
|
+
...this.options,
|
|
1490
1592
|
initialStateName: stateName ?? void 0,
|
|
1491
1593
|
transitionSelector: this.transitionSelector ?? void 0,
|
|
1492
1594
|
mutex: mutex ?? void 0
|
|
@@ -1716,6 +1818,7 @@ export {
|
|
|
1716
1818
|
InvalidSubjectError,
|
|
1717
1819
|
LockAdapterMutex,
|
|
1718
1820
|
LockCanNotBeAcquiredError,
|
|
1821
|
+
LockCanNotBeReleasedError,
|
|
1719
1822
|
MutexFactory,
|
|
1720
1823
|
Not,
|
|
1721
1824
|
NullMutex,
|