@camcima/finita 3.0.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +247 -212
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -31
- package/dist/index.d.ts +113 -31
- package/dist/index.js +246 -212
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -9,16 +9,26 @@ interface Metadata {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
interface Observer {
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @param args The arguments the notification was invoked with — for
|
|
14
|
+
* Statemachine events, [subject, context]. Passed per-call so shared
|
|
15
|
+
* Event instances carry no per-invocation state.
|
|
16
|
+
*/
|
|
17
|
+
update(subject: ObservableSubject, args?: readonly unknown[]): MaybePromise<void>;
|
|
13
18
|
}
|
|
14
19
|
interface ObservableSubject {
|
|
15
20
|
attach(observer: Observer): void;
|
|
16
21
|
detach(observer: Observer): void;
|
|
17
|
-
notify(): Promise<void>;
|
|
22
|
+
notify(args?: readonly unknown[]): Promise<void>;
|
|
18
23
|
getObservers(): Iterable<Observer>;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
interface EventInterface extends Named, Metadata, ObservableSubject {
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Always returns []. Invoke args are now passed directly to
|
|
29
|
+
* Observer.update — reading them from the event was racy when one
|
|
30
|
+
* Process served multiple Statemachines.
|
|
31
|
+
*/
|
|
22
32
|
getInvokeArgs(): unknown[];
|
|
23
33
|
invoke(...args: unknown[]): Promise<void>;
|
|
24
34
|
getMetadataValue(key: string): unknown;
|
|
@@ -31,14 +41,18 @@ declare class Event implements EventInterface {
|
|
|
31
41
|
private readonly name;
|
|
32
42
|
private readonly observers;
|
|
33
43
|
private readonly metadata;
|
|
34
|
-
private invokeArgs;
|
|
35
44
|
constructor(name: string);
|
|
36
45
|
getName(): string;
|
|
46
|
+
/**
|
|
47
|
+
* @deprecated Always returns []. Invoke args are now passed directly to
|
|
48
|
+
* Observer.update — reading them from the event was racy when one
|
|
49
|
+
* Process served multiple Statemachines.
|
|
50
|
+
*/
|
|
37
51
|
getInvokeArgs(): unknown[];
|
|
38
52
|
invoke(...args: unknown[]): Promise<void>;
|
|
39
53
|
attach(observer: Observer): void;
|
|
40
54
|
detach(observer: Observer): void;
|
|
41
|
-
notify(): Promise<void>;
|
|
55
|
+
notify(args?: readonly unknown[]): Promise<void>;
|
|
42
56
|
getObservers(): Iterable<Observer>;
|
|
43
57
|
getMetadata(): Record<string, unknown>;
|
|
44
58
|
getMetadataValue(key: string): unknown;
|
|
@@ -167,9 +181,14 @@ declare class ProcessBuilder<TSubject = unknown> {
|
|
|
167
181
|
addState(name: string, options?: AddStateOptions): this;
|
|
168
182
|
addTransition(fromState: string, toState: string, options?: AddTransitionOptions<TSubject>): this;
|
|
169
183
|
build(options?: BuildOptions): Process;
|
|
184
|
+
/** One name rule for every named entity: non-empty, no leading/trailing whitespace. */
|
|
185
|
+
private validateName;
|
|
170
186
|
private validateInitialState;
|
|
171
187
|
private findInitialStateName;
|
|
172
188
|
private validateTransitionEndpoints;
|
|
189
|
+
/** Transition identity: (fromState, eventName, toState). Used by both the
|
|
190
|
+
* conflict check and the build-time dedup — keep them in lockstep. */
|
|
191
|
+
private static transitionKey;
|
|
173
192
|
private validateNoConflictingDuplicates;
|
|
174
193
|
private collectEventNamesByState;
|
|
175
194
|
/**
|
|
@@ -187,13 +206,18 @@ declare class ProcessBuilder<TSubject = unknown> {
|
|
|
187
206
|
}
|
|
188
207
|
|
|
189
208
|
/**
|
|
190
|
-
* Immutable snapshot passed to
|
|
209
|
+
* Immutable snapshot passed to transition observers.
|
|
191
210
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
211
|
+
* For AfterTransitionObserver.notify() the transition has committed:
|
|
212
|
+
* state has already moved from fromState to toState. For
|
|
213
|
+
* BeforeTransitionObserver.notify() the same shape represents the
|
|
214
|
+
* *proposed* transition — fromState is still the current state, and
|
|
215
|
+
* throwing aborts the commit. Reading any field is safe and stable for
|
|
216
|
+
* the duration of the observer call (and beyond — the frame is frozen).
|
|
195
217
|
*/
|
|
196
218
|
interface TransitionFrame<TSubject = unknown> {
|
|
219
|
+
/** The subject this machine drives — identifies whose transition this is. */
|
|
220
|
+
readonly subject: TSubject;
|
|
197
221
|
readonly fromState: StateInterface;
|
|
198
222
|
readonly toState: StateInterface;
|
|
199
223
|
readonly transition: TransitionInterface<TSubject>;
|
|
@@ -204,14 +228,10 @@ interface TransitionFrame<TSubject = unknown> {
|
|
|
204
228
|
readonly machineName: string | null;
|
|
205
229
|
}
|
|
206
230
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* Same shape as TransitionFrame but represents a *proposed* transition
|
|
210
|
-
* — fromState is still the current state at notification time. Throwing
|
|
211
|
-
* from a before-observer aborts the transition; otherwise commit proceeds.
|
|
231
|
+
* The frame as seen by BeforeTransitionObserver — same shape; the
|
|
232
|
+
* distinct name documents the pre-commit timing.
|
|
212
233
|
*/
|
|
213
|
-
|
|
214
|
-
}
|
|
234
|
+
type ProposedTransitionFrame<TSubject = unknown> = TransitionFrame<TSubject>;
|
|
215
235
|
|
|
216
236
|
/**
|
|
217
237
|
* Runs before a transition commits. Throwing aborts the transition —
|
|
@@ -219,7 +239,9 @@ interface ProposedTransitionFrame<TSubject = unknown> extends TransitionFrame<TS
|
|
|
219
239
|
* the thrown error.
|
|
220
240
|
*
|
|
221
241
|
* Implementations must be pure relative to the FSM: they MUST NOT call
|
|
222
|
-
* triggerEvent / checkTransitions on the same Statemachine.
|
|
242
|
+
* triggerEvent / checkTransitions on the same Statemachine. Doing so throws
|
|
243
|
+
* ReentrancyError when the call happens before the observer's first await;
|
|
244
|
+
* calls made after an await cannot be detected and will deadlock. There is no
|
|
223
245
|
* enqueue handle in the before phase by design — vetoes and validations
|
|
224
246
|
* complete synchronously per observer; chained behaviour belongs in
|
|
225
247
|
* AfterTransitionObserver.
|
|
@@ -237,7 +259,14 @@ interface BeforeTransitionObserver<TSubject = unknown> {
|
|
|
237
259
|
* (and any auto-follow-on transitions) completes.
|
|
238
260
|
*/
|
|
239
261
|
interface EnqueueContext {
|
|
240
|
-
|
|
262
|
+
/**
|
|
263
|
+
* @param ifStateName When provided, the enqueued event is silently
|
|
264
|
+
* skipped unless the machine is still in that state when the operation
|
|
265
|
+
* is dequeued — the machine may have moved on in the meantime.
|
|
266
|
+
* If the machine leaves and returns to that state, the op is not skipped
|
|
267
|
+
* — only the state name is compared, not entry identity or count.
|
|
268
|
+
*/
|
|
269
|
+
enqueue(event: string, context?: Map<string, unknown>, ifStateName?: string): void;
|
|
241
270
|
}
|
|
242
271
|
/**
|
|
243
272
|
* Runs after a transition has committed. State has already moved.
|
|
@@ -291,6 +320,16 @@ interface StatemachineOptions<TSubject = unknown> {
|
|
|
291
320
|
mutex?: MutexInterface;
|
|
292
321
|
/** When true, the engine releases the mutex at the end of each top-level operation. Defaults to true. */
|
|
293
322
|
autoreleaseLock?: boolean;
|
|
323
|
+
/**
|
|
324
|
+
* Maximum number of automatic (eventless) transitions a single operation
|
|
325
|
+
* may take before AutomaticTransitionCycleError is thrown. Guards against
|
|
326
|
+
* non-terminating automatic loops while allowing legitimate bounded loops
|
|
327
|
+
* (e.g. condition-terminated retry cycles). Note: transitions committed
|
|
328
|
+
* before the limit is hit are NOT rolled back. Must be a positive integer;
|
|
329
|
+
* constructing a Statemachine with a value < 1 throws a RangeError.
|
|
330
|
+
* Defaults to 100.
|
|
331
|
+
*/
|
|
332
|
+
maxAutomaticHops?: number;
|
|
294
333
|
}
|
|
295
334
|
|
|
296
335
|
declare class Statemachine<TSubject = unknown> implements StatemachineInterface<TSubject> {
|
|
@@ -301,8 +340,10 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
301
340
|
private currentState;
|
|
302
341
|
private lastState;
|
|
303
342
|
private autoreleaseLock;
|
|
343
|
+
private readonly maxAutomaticHops;
|
|
304
344
|
private readonly queue;
|
|
305
345
|
private running;
|
|
346
|
+
private inSyncCallback;
|
|
306
347
|
private readonly beforeObservers;
|
|
307
348
|
private readonly afterObservers;
|
|
308
349
|
constructor(subject: TSubject, process: ProcessInterface, options?: StatemachineOptions<TSubject>);
|
|
@@ -323,6 +364,16 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
323
364
|
setAutoreleaseLock(autorelease: boolean): void;
|
|
324
365
|
triggerEvent(name: string, context?: Map<string, unknown>): Promise<void>;
|
|
325
366
|
checkTransitions(context?: Map<string, unknown>): Promise<void>;
|
|
367
|
+
/** Runs fn with the re-entrancy flag set for its SYNCHRONOUS portion only:
|
|
368
|
+
* the flag is cleared as soon as fn returns (before any promise it returned
|
|
369
|
+
* is awaited), so concurrent external callers are never affected. This
|
|
370
|
+
* catches triggerEvent/checkTransitions calls made before a callback's first
|
|
371
|
+
* await; calls made after a prior await are not detectable without
|
|
372
|
+
* AsyncLocalStorage (Node-only) and will still deadlock — a documented gap. */
|
|
373
|
+
private guardSync;
|
|
374
|
+
private assertNotReentrant;
|
|
375
|
+
/** Single entry point to the operation queue — every enqueue kicks the runner. */
|
|
376
|
+
private enqueueOperation;
|
|
326
377
|
private runIfIdle;
|
|
327
378
|
private runOperation;
|
|
328
379
|
private resolveEvent;
|
|
@@ -372,9 +423,11 @@ interface LastStateHasChangedDateInterface {
|
|
|
372
423
|
getLastStateHasChangedDate(): Date;
|
|
373
424
|
}
|
|
374
425
|
|
|
426
|
+
/** @deprecated No longer used internally; will be removed in v4. */
|
|
375
427
|
interface CallbackInterface {
|
|
376
428
|
invoke(): MaybePromise<void>;
|
|
377
429
|
}
|
|
430
|
+
/** @deprecated No longer used internally; will be removed in v4. */
|
|
378
431
|
interface DispatcherInterface extends CallbackInterface {
|
|
379
432
|
dispatch(event: EventInterface, args?: unknown[]): void;
|
|
380
433
|
invoke(): Promise<void>;
|
|
@@ -416,19 +469,24 @@ declare class Timeout implements ConditionInterface {
|
|
|
416
469
|
checkCondition(subject: unknown, context: Map<string, unknown>): boolean;
|
|
417
470
|
}
|
|
418
471
|
|
|
419
|
-
declare class
|
|
420
|
-
|
|
472
|
+
declare abstract class CompositeCondition<TSubject = unknown> implements ConditionInterface<TSubject> {
|
|
473
|
+
protected readonly conditions: ConditionInterface<TSubject>[];
|
|
474
|
+
private readonly joinWord;
|
|
475
|
+
constructor(joinWord: string, condition: ConditionInterface<TSubject>);
|
|
476
|
+
protected addCondition(condition: ConditionInterface<TSubject>): this;
|
|
477
|
+
getName(): string;
|
|
478
|
+
abstract checkCondition(subject: TSubject, context: Map<string, unknown>): Promise<boolean>;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
declare class AndComposite<TSubject = unknown> extends CompositeCondition<TSubject> {
|
|
421
482
|
constructor(condition: ConditionInterface<TSubject>);
|
|
422
483
|
addAnd(condition: ConditionInterface<TSubject>): this;
|
|
423
|
-
getName(): string;
|
|
424
484
|
checkCondition(subject: TSubject, context: Map<string, unknown>): Promise<boolean>;
|
|
425
485
|
}
|
|
426
486
|
|
|
427
|
-
declare class OrComposite<TSubject = unknown>
|
|
428
|
-
private readonly conditions;
|
|
487
|
+
declare class OrComposite<TSubject = unknown> extends CompositeCondition<TSubject> {
|
|
429
488
|
constructor(condition: ConditionInterface<TSubject>);
|
|
430
489
|
addOr(condition: ConditionInterface<TSubject>): this;
|
|
431
|
-
getName(): string;
|
|
432
490
|
checkCondition(subject: TSubject, context: Map<string, unknown>): Promise<boolean>;
|
|
433
491
|
}
|
|
434
492
|
|
|
@@ -449,12 +507,18 @@ declare class Not<TSubject = unknown> implements ConditionInterface<TSubject> {
|
|
|
449
507
|
declare class CallbackObserver implements Observer {
|
|
450
508
|
private readonly callback;
|
|
451
509
|
constructor(callback: (...args: unknown[]) => MaybePromise<void>);
|
|
452
|
-
update(subject: ObservableSubject): MaybePromise<void>;
|
|
510
|
+
update(subject: ObservableSubject, args?: readonly unknown[]): MaybePromise<void>;
|
|
453
511
|
}
|
|
454
512
|
|
|
455
513
|
declare class StatefulStatusChanger<TSubject extends StatefulInterface> implements AfterTransitionObserver<TSubject> {
|
|
456
514
|
private readonly subject;
|
|
457
|
-
|
|
515
|
+
/**
|
|
516
|
+
* @param subject Optional explicit subject to write to. When omitted
|
|
517
|
+
* (recommended), the observer writes to frame.subject — the subject of
|
|
518
|
+
* whichever machine fired the transition — so a single instance can be
|
|
519
|
+
* shared safely across every machine a Factory creates.
|
|
520
|
+
*/
|
|
521
|
+
constructor(subject?: TSubject);
|
|
458
522
|
notify(frame: TransitionFrame<TSubject>): void;
|
|
459
523
|
}
|
|
460
524
|
|
|
@@ -466,6 +530,10 @@ declare class StatefulStatusChanger<TSubject extends StatefulInterface> implemen
|
|
|
466
530
|
* top-level operation after the current operation completes. Other
|
|
467
531
|
* after-observers registered after OnEnterObserver still see the original
|
|
468
532
|
* frame, not the chained one.
|
|
533
|
+
*
|
|
534
|
+
* The chained event only fires if the machine is still in the entered state
|
|
535
|
+
* when the queue drains — states passed through transiently by automatic
|
|
536
|
+
* transitions do not fire onEnter.
|
|
469
537
|
*/
|
|
470
538
|
declare class OnEnterObserver<TSubject = unknown> implements AfterTransitionObserver<TSubject> {
|
|
471
539
|
static readonly DEFAULT_EVENT_NAME = "onEnter";
|
|
@@ -482,7 +550,14 @@ declare class TransitionLogger<TSubject = unknown> implements AfterTransitionObs
|
|
|
482
550
|
}
|
|
483
551
|
|
|
484
552
|
declare class ActiveTransitionFilter {
|
|
485
|
-
static filter<TSubject = unknown>(transitions: Iterable<TransitionInterface<TSubject>>, subject: TSubject, context: Map<string, unknown>, event?: EventInterface
|
|
553
|
+
static filter<TSubject = unknown>(transitions: Iterable<TransitionInterface<TSubject>>, subject: TSubject, context: Map<string, unknown>, event?: EventInterface,
|
|
554
|
+
/**
|
|
555
|
+
* Optional wrapper run around each individual isActive() evaluation. The
|
|
556
|
+
* Statemachine passes its re-entrancy guard here so that every condition —
|
|
557
|
+
* not just the first — is evaluated with the guard active; without per-item
|
|
558
|
+
* wrapping a re-entrant condition on a later transition would deadlock.
|
|
559
|
+
*/
|
|
560
|
+
wrap?: <T>(fn: () => T) => T): Promise<TransitionInterface<TSubject>[]>;
|
|
486
561
|
}
|
|
487
562
|
|
|
488
563
|
declare class FilterStateByEvent {
|
|
@@ -648,7 +723,7 @@ declare class ProcessFinalizedError extends FinitaError {
|
|
|
648
723
|
constructor(processName: string);
|
|
649
724
|
}
|
|
650
725
|
|
|
651
|
-
type GraphValidationCode = "unknownTarget" | "unknownSource" | "missingInitialState" | "multipleInitialStates" | "invalidEventName" | "invalidConditionName" | "orphanState";
|
|
726
|
+
type GraphValidationCode = "unknownTarget" | "unknownSource" | "missingInitialState" | "multipleInitialStates" | "invalidStateName" | "invalidEventName" | "invalidConditionName" | "orphanState";
|
|
652
727
|
declare class GraphValidationError extends FinitaError {
|
|
653
728
|
readonly code: GraphValidationCode;
|
|
654
729
|
readonly details: Readonly<Record<string, unknown>>;
|
|
@@ -661,6 +736,8 @@ interface DuplicateTransitionConflict {
|
|
|
661
736
|
eventName: string | null;
|
|
662
737
|
existingConditionName: string | null;
|
|
663
738
|
newConditionName: string | null;
|
|
739
|
+
existingWeight?: number;
|
|
740
|
+
newWeight?: number;
|
|
664
741
|
}
|
|
665
742
|
declare class DuplicateTransitionError extends FinitaError {
|
|
666
743
|
readonly code = "duplicateTransition";
|
|
@@ -704,9 +781,14 @@ declare class AmbiguousTransitionError extends FinitaError {
|
|
|
704
781
|
|
|
705
782
|
declare class AutomaticTransitionCycleError extends FinitaError {
|
|
706
783
|
readonly code = "automaticTransitionCycle";
|
|
707
|
-
readonly
|
|
708
|
-
readonly
|
|
709
|
-
constructor(
|
|
784
|
+
readonly stateName: string;
|
|
785
|
+
readonly hopLimit: number;
|
|
786
|
+
constructor(stateName: string, hopLimit: number);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
declare class ReentrancyError extends FinitaError {
|
|
790
|
+
readonly code = "reentrancy";
|
|
791
|
+
constructor(operation: string);
|
|
710
792
|
}
|
|
711
793
|
|
|
712
|
-
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 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, 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 };
|
|
794
|
+
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 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, 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
|
@@ -9,16 +9,26 @@ interface Metadata {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
interface Observer {
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @param args The arguments the notification was invoked with — for
|
|
14
|
+
* Statemachine events, [subject, context]. Passed per-call so shared
|
|
15
|
+
* Event instances carry no per-invocation state.
|
|
16
|
+
*/
|
|
17
|
+
update(subject: ObservableSubject, args?: readonly unknown[]): MaybePromise<void>;
|
|
13
18
|
}
|
|
14
19
|
interface ObservableSubject {
|
|
15
20
|
attach(observer: Observer): void;
|
|
16
21
|
detach(observer: Observer): void;
|
|
17
|
-
notify(): Promise<void>;
|
|
22
|
+
notify(args?: readonly unknown[]): Promise<void>;
|
|
18
23
|
getObservers(): Iterable<Observer>;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
interface EventInterface extends Named, Metadata, ObservableSubject {
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Always returns []. Invoke args are now passed directly to
|
|
29
|
+
* Observer.update — reading them from the event was racy when one
|
|
30
|
+
* Process served multiple Statemachines.
|
|
31
|
+
*/
|
|
22
32
|
getInvokeArgs(): unknown[];
|
|
23
33
|
invoke(...args: unknown[]): Promise<void>;
|
|
24
34
|
getMetadataValue(key: string): unknown;
|
|
@@ -31,14 +41,18 @@ declare class Event implements EventInterface {
|
|
|
31
41
|
private readonly name;
|
|
32
42
|
private readonly observers;
|
|
33
43
|
private readonly metadata;
|
|
34
|
-
private invokeArgs;
|
|
35
44
|
constructor(name: string);
|
|
36
45
|
getName(): string;
|
|
46
|
+
/**
|
|
47
|
+
* @deprecated Always returns []. Invoke args are now passed directly to
|
|
48
|
+
* Observer.update — reading them from the event was racy when one
|
|
49
|
+
* Process served multiple Statemachines.
|
|
50
|
+
*/
|
|
37
51
|
getInvokeArgs(): unknown[];
|
|
38
52
|
invoke(...args: unknown[]): Promise<void>;
|
|
39
53
|
attach(observer: Observer): void;
|
|
40
54
|
detach(observer: Observer): void;
|
|
41
|
-
notify(): Promise<void>;
|
|
55
|
+
notify(args?: readonly unknown[]): Promise<void>;
|
|
42
56
|
getObservers(): Iterable<Observer>;
|
|
43
57
|
getMetadata(): Record<string, unknown>;
|
|
44
58
|
getMetadataValue(key: string): unknown;
|
|
@@ -167,9 +181,14 @@ declare class ProcessBuilder<TSubject = unknown> {
|
|
|
167
181
|
addState(name: string, options?: AddStateOptions): this;
|
|
168
182
|
addTransition(fromState: string, toState: string, options?: AddTransitionOptions<TSubject>): this;
|
|
169
183
|
build(options?: BuildOptions): Process;
|
|
184
|
+
/** One name rule for every named entity: non-empty, no leading/trailing whitespace. */
|
|
185
|
+
private validateName;
|
|
170
186
|
private validateInitialState;
|
|
171
187
|
private findInitialStateName;
|
|
172
188
|
private validateTransitionEndpoints;
|
|
189
|
+
/** Transition identity: (fromState, eventName, toState). Used by both the
|
|
190
|
+
* conflict check and the build-time dedup — keep them in lockstep. */
|
|
191
|
+
private static transitionKey;
|
|
173
192
|
private validateNoConflictingDuplicates;
|
|
174
193
|
private collectEventNamesByState;
|
|
175
194
|
/**
|
|
@@ -187,13 +206,18 @@ declare class ProcessBuilder<TSubject = unknown> {
|
|
|
187
206
|
}
|
|
188
207
|
|
|
189
208
|
/**
|
|
190
|
-
* Immutable snapshot passed to
|
|
209
|
+
* Immutable snapshot passed to transition observers.
|
|
191
210
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
211
|
+
* For AfterTransitionObserver.notify() the transition has committed:
|
|
212
|
+
* state has already moved from fromState to toState. For
|
|
213
|
+
* BeforeTransitionObserver.notify() the same shape represents the
|
|
214
|
+
* *proposed* transition — fromState is still the current state, and
|
|
215
|
+
* throwing aborts the commit. Reading any field is safe and stable for
|
|
216
|
+
* the duration of the observer call (and beyond — the frame is frozen).
|
|
195
217
|
*/
|
|
196
218
|
interface TransitionFrame<TSubject = unknown> {
|
|
219
|
+
/** The subject this machine drives — identifies whose transition this is. */
|
|
220
|
+
readonly subject: TSubject;
|
|
197
221
|
readonly fromState: StateInterface;
|
|
198
222
|
readonly toState: StateInterface;
|
|
199
223
|
readonly transition: TransitionInterface<TSubject>;
|
|
@@ -204,14 +228,10 @@ interface TransitionFrame<TSubject = unknown> {
|
|
|
204
228
|
readonly machineName: string | null;
|
|
205
229
|
}
|
|
206
230
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* Same shape as TransitionFrame but represents a *proposed* transition
|
|
210
|
-
* — fromState is still the current state at notification time. Throwing
|
|
211
|
-
* from a before-observer aborts the transition; otherwise commit proceeds.
|
|
231
|
+
* The frame as seen by BeforeTransitionObserver — same shape; the
|
|
232
|
+
* distinct name documents the pre-commit timing.
|
|
212
233
|
*/
|
|
213
|
-
|
|
214
|
-
}
|
|
234
|
+
type ProposedTransitionFrame<TSubject = unknown> = TransitionFrame<TSubject>;
|
|
215
235
|
|
|
216
236
|
/**
|
|
217
237
|
* Runs before a transition commits. Throwing aborts the transition —
|
|
@@ -219,7 +239,9 @@ interface ProposedTransitionFrame<TSubject = unknown> extends TransitionFrame<TS
|
|
|
219
239
|
* the thrown error.
|
|
220
240
|
*
|
|
221
241
|
* Implementations must be pure relative to the FSM: they MUST NOT call
|
|
222
|
-
* triggerEvent / checkTransitions on the same Statemachine.
|
|
242
|
+
* triggerEvent / checkTransitions on the same Statemachine. Doing so throws
|
|
243
|
+
* ReentrancyError when the call happens before the observer's first await;
|
|
244
|
+
* calls made after an await cannot be detected and will deadlock. There is no
|
|
223
245
|
* enqueue handle in the before phase by design — vetoes and validations
|
|
224
246
|
* complete synchronously per observer; chained behaviour belongs in
|
|
225
247
|
* AfterTransitionObserver.
|
|
@@ -237,7 +259,14 @@ interface BeforeTransitionObserver<TSubject = unknown> {
|
|
|
237
259
|
* (and any auto-follow-on transitions) completes.
|
|
238
260
|
*/
|
|
239
261
|
interface EnqueueContext {
|
|
240
|
-
|
|
262
|
+
/**
|
|
263
|
+
* @param ifStateName When provided, the enqueued event is silently
|
|
264
|
+
* skipped unless the machine is still in that state when the operation
|
|
265
|
+
* is dequeued — the machine may have moved on in the meantime.
|
|
266
|
+
* If the machine leaves and returns to that state, the op is not skipped
|
|
267
|
+
* — only the state name is compared, not entry identity or count.
|
|
268
|
+
*/
|
|
269
|
+
enqueue(event: string, context?: Map<string, unknown>, ifStateName?: string): void;
|
|
241
270
|
}
|
|
242
271
|
/**
|
|
243
272
|
* Runs after a transition has committed. State has already moved.
|
|
@@ -291,6 +320,16 @@ interface StatemachineOptions<TSubject = unknown> {
|
|
|
291
320
|
mutex?: MutexInterface;
|
|
292
321
|
/** When true, the engine releases the mutex at the end of each top-level operation. Defaults to true. */
|
|
293
322
|
autoreleaseLock?: boolean;
|
|
323
|
+
/**
|
|
324
|
+
* Maximum number of automatic (eventless) transitions a single operation
|
|
325
|
+
* may take before AutomaticTransitionCycleError is thrown. Guards against
|
|
326
|
+
* non-terminating automatic loops while allowing legitimate bounded loops
|
|
327
|
+
* (e.g. condition-terminated retry cycles). Note: transitions committed
|
|
328
|
+
* before the limit is hit are NOT rolled back. Must be a positive integer;
|
|
329
|
+
* constructing a Statemachine with a value < 1 throws a RangeError.
|
|
330
|
+
* Defaults to 100.
|
|
331
|
+
*/
|
|
332
|
+
maxAutomaticHops?: number;
|
|
294
333
|
}
|
|
295
334
|
|
|
296
335
|
declare class Statemachine<TSubject = unknown> implements StatemachineInterface<TSubject> {
|
|
@@ -301,8 +340,10 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
301
340
|
private currentState;
|
|
302
341
|
private lastState;
|
|
303
342
|
private autoreleaseLock;
|
|
343
|
+
private readonly maxAutomaticHops;
|
|
304
344
|
private readonly queue;
|
|
305
345
|
private running;
|
|
346
|
+
private inSyncCallback;
|
|
306
347
|
private readonly beforeObservers;
|
|
307
348
|
private readonly afterObservers;
|
|
308
349
|
constructor(subject: TSubject, process: ProcessInterface, options?: StatemachineOptions<TSubject>);
|
|
@@ -323,6 +364,16 @@ declare class Statemachine<TSubject = unknown> implements StatemachineInterface<
|
|
|
323
364
|
setAutoreleaseLock(autorelease: boolean): void;
|
|
324
365
|
triggerEvent(name: string, context?: Map<string, unknown>): Promise<void>;
|
|
325
366
|
checkTransitions(context?: Map<string, unknown>): Promise<void>;
|
|
367
|
+
/** Runs fn with the re-entrancy flag set for its SYNCHRONOUS portion only:
|
|
368
|
+
* the flag is cleared as soon as fn returns (before any promise it returned
|
|
369
|
+
* is awaited), so concurrent external callers are never affected. This
|
|
370
|
+
* catches triggerEvent/checkTransitions calls made before a callback's first
|
|
371
|
+
* await; calls made after a prior await are not detectable without
|
|
372
|
+
* AsyncLocalStorage (Node-only) and will still deadlock — a documented gap. */
|
|
373
|
+
private guardSync;
|
|
374
|
+
private assertNotReentrant;
|
|
375
|
+
/** Single entry point to the operation queue — every enqueue kicks the runner. */
|
|
376
|
+
private enqueueOperation;
|
|
326
377
|
private runIfIdle;
|
|
327
378
|
private runOperation;
|
|
328
379
|
private resolveEvent;
|
|
@@ -372,9 +423,11 @@ interface LastStateHasChangedDateInterface {
|
|
|
372
423
|
getLastStateHasChangedDate(): Date;
|
|
373
424
|
}
|
|
374
425
|
|
|
426
|
+
/** @deprecated No longer used internally; will be removed in v4. */
|
|
375
427
|
interface CallbackInterface {
|
|
376
428
|
invoke(): MaybePromise<void>;
|
|
377
429
|
}
|
|
430
|
+
/** @deprecated No longer used internally; will be removed in v4. */
|
|
378
431
|
interface DispatcherInterface extends CallbackInterface {
|
|
379
432
|
dispatch(event: EventInterface, args?: unknown[]): void;
|
|
380
433
|
invoke(): Promise<void>;
|
|
@@ -416,19 +469,24 @@ declare class Timeout implements ConditionInterface {
|
|
|
416
469
|
checkCondition(subject: unknown, context: Map<string, unknown>): boolean;
|
|
417
470
|
}
|
|
418
471
|
|
|
419
|
-
declare class
|
|
420
|
-
|
|
472
|
+
declare abstract class CompositeCondition<TSubject = unknown> implements ConditionInterface<TSubject> {
|
|
473
|
+
protected readonly conditions: ConditionInterface<TSubject>[];
|
|
474
|
+
private readonly joinWord;
|
|
475
|
+
constructor(joinWord: string, condition: ConditionInterface<TSubject>);
|
|
476
|
+
protected addCondition(condition: ConditionInterface<TSubject>): this;
|
|
477
|
+
getName(): string;
|
|
478
|
+
abstract checkCondition(subject: TSubject, context: Map<string, unknown>): Promise<boolean>;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
declare class AndComposite<TSubject = unknown> extends CompositeCondition<TSubject> {
|
|
421
482
|
constructor(condition: ConditionInterface<TSubject>);
|
|
422
483
|
addAnd(condition: ConditionInterface<TSubject>): this;
|
|
423
|
-
getName(): string;
|
|
424
484
|
checkCondition(subject: TSubject, context: Map<string, unknown>): Promise<boolean>;
|
|
425
485
|
}
|
|
426
486
|
|
|
427
|
-
declare class OrComposite<TSubject = unknown>
|
|
428
|
-
private readonly conditions;
|
|
487
|
+
declare class OrComposite<TSubject = unknown> extends CompositeCondition<TSubject> {
|
|
429
488
|
constructor(condition: ConditionInterface<TSubject>);
|
|
430
489
|
addOr(condition: ConditionInterface<TSubject>): this;
|
|
431
|
-
getName(): string;
|
|
432
490
|
checkCondition(subject: TSubject, context: Map<string, unknown>): Promise<boolean>;
|
|
433
491
|
}
|
|
434
492
|
|
|
@@ -449,12 +507,18 @@ declare class Not<TSubject = unknown> implements ConditionInterface<TSubject> {
|
|
|
449
507
|
declare class CallbackObserver implements Observer {
|
|
450
508
|
private readonly callback;
|
|
451
509
|
constructor(callback: (...args: unknown[]) => MaybePromise<void>);
|
|
452
|
-
update(subject: ObservableSubject): MaybePromise<void>;
|
|
510
|
+
update(subject: ObservableSubject, args?: readonly unknown[]): MaybePromise<void>;
|
|
453
511
|
}
|
|
454
512
|
|
|
455
513
|
declare class StatefulStatusChanger<TSubject extends StatefulInterface> implements AfterTransitionObserver<TSubject> {
|
|
456
514
|
private readonly subject;
|
|
457
|
-
|
|
515
|
+
/**
|
|
516
|
+
* @param subject Optional explicit subject to write to. When omitted
|
|
517
|
+
* (recommended), the observer writes to frame.subject — the subject of
|
|
518
|
+
* whichever machine fired the transition — so a single instance can be
|
|
519
|
+
* shared safely across every machine a Factory creates.
|
|
520
|
+
*/
|
|
521
|
+
constructor(subject?: TSubject);
|
|
458
522
|
notify(frame: TransitionFrame<TSubject>): void;
|
|
459
523
|
}
|
|
460
524
|
|
|
@@ -466,6 +530,10 @@ declare class StatefulStatusChanger<TSubject extends StatefulInterface> implemen
|
|
|
466
530
|
* top-level operation after the current operation completes. Other
|
|
467
531
|
* after-observers registered after OnEnterObserver still see the original
|
|
468
532
|
* frame, not the chained one.
|
|
533
|
+
*
|
|
534
|
+
* The chained event only fires if the machine is still in the entered state
|
|
535
|
+
* when the queue drains — states passed through transiently by automatic
|
|
536
|
+
* transitions do not fire onEnter.
|
|
469
537
|
*/
|
|
470
538
|
declare class OnEnterObserver<TSubject = unknown> implements AfterTransitionObserver<TSubject> {
|
|
471
539
|
static readonly DEFAULT_EVENT_NAME = "onEnter";
|
|
@@ -482,7 +550,14 @@ declare class TransitionLogger<TSubject = unknown> implements AfterTransitionObs
|
|
|
482
550
|
}
|
|
483
551
|
|
|
484
552
|
declare class ActiveTransitionFilter {
|
|
485
|
-
static filter<TSubject = unknown>(transitions: Iterable<TransitionInterface<TSubject>>, subject: TSubject, context: Map<string, unknown>, event?: EventInterface
|
|
553
|
+
static filter<TSubject = unknown>(transitions: Iterable<TransitionInterface<TSubject>>, subject: TSubject, context: Map<string, unknown>, event?: EventInterface,
|
|
554
|
+
/**
|
|
555
|
+
* Optional wrapper run around each individual isActive() evaluation. The
|
|
556
|
+
* Statemachine passes its re-entrancy guard here so that every condition —
|
|
557
|
+
* not just the first — is evaluated with the guard active; without per-item
|
|
558
|
+
* wrapping a re-entrant condition on a later transition would deadlock.
|
|
559
|
+
*/
|
|
560
|
+
wrap?: <T>(fn: () => T) => T): Promise<TransitionInterface<TSubject>[]>;
|
|
486
561
|
}
|
|
487
562
|
|
|
488
563
|
declare class FilterStateByEvent {
|
|
@@ -648,7 +723,7 @@ declare class ProcessFinalizedError extends FinitaError {
|
|
|
648
723
|
constructor(processName: string);
|
|
649
724
|
}
|
|
650
725
|
|
|
651
|
-
type GraphValidationCode = "unknownTarget" | "unknownSource" | "missingInitialState" | "multipleInitialStates" | "invalidEventName" | "invalidConditionName" | "orphanState";
|
|
726
|
+
type GraphValidationCode = "unknownTarget" | "unknownSource" | "missingInitialState" | "multipleInitialStates" | "invalidStateName" | "invalidEventName" | "invalidConditionName" | "orphanState";
|
|
652
727
|
declare class GraphValidationError extends FinitaError {
|
|
653
728
|
readonly code: GraphValidationCode;
|
|
654
729
|
readonly details: Readonly<Record<string, unknown>>;
|
|
@@ -661,6 +736,8 @@ interface DuplicateTransitionConflict {
|
|
|
661
736
|
eventName: string | null;
|
|
662
737
|
existingConditionName: string | null;
|
|
663
738
|
newConditionName: string | null;
|
|
739
|
+
existingWeight?: number;
|
|
740
|
+
newWeight?: number;
|
|
664
741
|
}
|
|
665
742
|
declare class DuplicateTransitionError extends FinitaError {
|
|
666
743
|
readonly code = "duplicateTransition";
|
|
@@ -704,9 +781,14 @@ declare class AmbiguousTransitionError extends FinitaError {
|
|
|
704
781
|
|
|
705
782
|
declare class AutomaticTransitionCycleError extends FinitaError {
|
|
706
783
|
readonly code = "automaticTransitionCycle";
|
|
707
|
-
readonly
|
|
708
|
-
readonly
|
|
709
|
-
constructor(
|
|
784
|
+
readonly stateName: string;
|
|
785
|
+
readonly hopLimit: number;
|
|
786
|
+
constructor(stateName: string, hopLimit: number);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
declare class ReentrancyError extends FinitaError {
|
|
790
|
+
readonly code = "reentrancy";
|
|
791
|
+
constructor(operation: string);
|
|
710
792
|
}
|
|
711
793
|
|
|
712
|
-
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 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, 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 };
|
|
794
|
+
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 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, 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 };
|