@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.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);
|
|
@@ -184,6 +186,7 @@ var State = class {
|
|
|
184
186
|
throw new Error(`State "${this.name}" transitions already set`);
|
|
185
187
|
}
|
|
186
188
|
this._transitions = new Set(transitions);
|
|
189
|
+
Object.freeze(this);
|
|
187
190
|
}
|
|
188
191
|
getName() {
|
|
189
192
|
return this.name;
|
|
@@ -234,6 +237,7 @@ var Transition = class {
|
|
|
234
237
|
this.eventName = eventName;
|
|
235
238
|
this.condition = condition;
|
|
236
239
|
this.weight = weight;
|
|
240
|
+
Object.freeze(this);
|
|
237
241
|
}
|
|
238
242
|
getTargetState() {
|
|
239
243
|
return this.targetState;
|
|
@@ -368,12 +372,20 @@ var ProcessBuilder = class _ProcessBuilder {
|
|
|
368
372
|
{ fromState, toState, conditionName }
|
|
369
373
|
);
|
|
370
374
|
}
|
|
375
|
+
const weight = options.weight ?? 1;
|
|
376
|
+
if (!Number.isFinite(weight)) {
|
|
377
|
+
throw new GraphValidationError(
|
|
378
|
+
"invalidTransitionWeight",
|
|
379
|
+
`addTransition from "${fromState}" to "${toState}": weight must be a finite number; got ${String(weight)}`,
|
|
380
|
+
{ fromState, toState, eventName, weight }
|
|
381
|
+
);
|
|
382
|
+
}
|
|
371
383
|
this.transitionSpecs.push({
|
|
372
384
|
fromState,
|
|
373
385
|
toState,
|
|
374
386
|
eventName,
|
|
375
387
|
condition: options.condition ?? null,
|
|
376
|
-
weight
|
|
388
|
+
weight
|
|
377
389
|
});
|
|
378
390
|
return this;
|
|
379
391
|
}
|
|
@@ -381,7 +393,6 @@ var ProcessBuilder = class _ProcessBuilder {
|
|
|
381
393
|
if (this.built) {
|
|
382
394
|
throw new ProcessFinalizedError(this.processName);
|
|
383
395
|
}
|
|
384
|
-
this.built = true;
|
|
385
396
|
this.validateInitialState();
|
|
386
397
|
this.validateTransitionEndpoints();
|
|
387
398
|
this.validateNoConflictingDuplicates();
|
|
@@ -392,6 +403,7 @@ var ProcessBuilder = class _ProcessBuilder {
|
|
|
392
403
|
this.validateOrphans(finalStates, initialName);
|
|
393
404
|
}
|
|
394
405
|
const initialState = finalStates.get(initialName);
|
|
406
|
+
this.built = true;
|
|
395
407
|
return new Process(
|
|
396
408
|
INTERNAL_CONSTRUCTION_KEY,
|
|
397
409
|
this.processName,
|
|
@@ -583,12 +595,30 @@ var ProcessBuilder = class _ProcessBuilder {
|
|
|
583
595
|
var AmbiguousTransitionError = class extends FinitaError {
|
|
584
596
|
code = "ambiguousTransition";
|
|
585
597
|
activeCount;
|
|
586
|
-
|
|
587
|
-
|
|
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
|
+
);
|
|
588
606
|
this.name = "AmbiguousTransitionError";
|
|
589
607
|
this.activeCount = activeCount;
|
|
608
|
+
this.candidates = Object.freeze(list);
|
|
590
609
|
}
|
|
591
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
|
+
}
|
|
592
622
|
|
|
593
623
|
// src/selector/OneOrNoneActiveTransition.ts
|
|
594
624
|
var OneOrNoneActiveTransition = class {
|
|
@@ -600,7 +630,15 @@ var OneOrNoneActiveTransition = class {
|
|
|
600
630
|
case 1:
|
|
601
631
|
return arr[0];
|
|
602
632
|
default:
|
|
603
|
-
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
|
+
);
|
|
604
642
|
}
|
|
605
643
|
}
|
|
606
644
|
};
|
|
@@ -636,6 +674,9 @@ var OperationQueue = class {
|
|
|
636
674
|
isEmpty() {
|
|
637
675
|
return this.items.length === 0;
|
|
638
676
|
}
|
|
677
|
+
size() {
|
|
678
|
+
return this.items.length;
|
|
679
|
+
}
|
|
639
680
|
};
|
|
640
681
|
|
|
641
682
|
// src/filter/ActiveTransitionFilter.ts
|
|
@@ -674,6 +715,15 @@ var LockCanNotBeAcquiredError = class extends FinitaError {
|
|
|
674
715
|
}
|
|
675
716
|
};
|
|
676
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
|
+
|
|
677
727
|
// src/error/AutomaticTransitionCycleError.ts
|
|
678
728
|
var AutomaticTransitionCycleError = class extends FinitaError {
|
|
679
729
|
code = "automaticTransitionCycle";
|
|
@@ -700,6 +750,17 @@ var ReentrancyError = class extends FinitaError {
|
|
|
700
750
|
}
|
|
701
751
|
};
|
|
702
752
|
|
|
753
|
+
// src/error/QueueLimitExceededError.ts
|
|
754
|
+
var QueueLimitExceededError = class extends FinitaError {
|
|
755
|
+
code = "queueLimitExceeded";
|
|
756
|
+
constructor(limit, eventName) {
|
|
757
|
+
super(
|
|
758
|
+
`${eventName === null ? "checkTransitions()" : `triggerEvent("${eventName}")`} rejected: the operation queue already holds ${limit} pending operation(s) (maxQueueLength = ${limit}).`
|
|
759
|
+
);
|
|
760
|
+
this.name = "QueueLimitExceededError";
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
|
|
703
764
|
// src/Statemachine.ts
|
|
704
765
|
var Statemachine = class {
|
|
705
766
|
subject;
|
|
@@ -710,11 +771,15 @@ var Statemachine = class {
|
|
|
710
771
|
lastState = null;
|
|
711
772
|
autoreleaseLock;
|
|
712
773
|
maxAutomaticHops;
|
|
774
|
+
maxQueueLength;
|
|
713
775
|
queue = new OperationQueue();
|
|
714
776
|
running = false;
|
|
777
|
+
idleWaiters = [];
|
|
715
778
|
inSyncCallback = false;
|
|
716
779
|
beforeObservers = [];
|
|
717
780
|
afterObservers = [];
|
|
781
|
+
onChainedOperationError;
|
|
782
|
+
onReleaseError;
|
|
718
783
|
constructor(subject, process, options = {}) {
|
|
719
784
|
this.subject = subject;
|
|
720
785
|
this.process = process;
|
|
@@ -729,6 +794,15 @@ var Statemachine = class {
|
|
|
729
794
|
);
|
|
730
795
|
}
|
|
731
796
|
this.maxAutomaticHops = hops;
|
|
797
|
+
const maxQueue = options.maxQueueLength ?? Infinity;
|
|
798
|
+
if (maxQueue !== Infinity && (!Number.isInteger(maxQueue) || maxQueue < 1)) {
|
|
799
|
+
throw new RangeError(
|
|
800
|
+
`maxQueueLength must be a positive integer; got ${String(options.maxQueueLength)}`
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
this.maxQueueLength = maxQueue;
|
|
804
|
+
this.onChainedOperationError = options.onChainedOperationError;
|
|
805
|
+
this.onReleaseError = options.onReleaseError;
|
|
732
806
|
}
|
|
733
807
|
// --- public getters ---
|
|
734
808
|
getCurrentState() {
|
|
@@ -745,31 +819,43 @@ var Statemachine = class {
|
|
|
745
819
|
}
|
|
746
820
|
// --- public observer attach/detach ---
|
|
747
821
|
attachBefore(observer) {
|
|
822
|
+
if (this.beforeObservers.includes(observer)) return;
|
|
748
823
|
this.beforeObservers.push(observer);
|
|
749
824
|
}
|
|
750
825
|
detachBefore(observer) {
|
|
751
826
|
const idx = this.beforeObservers.indexOf(observer);
|
|
752
827
|
if (idx >= 0) this.beforeObservers.splice(idx, 1);
|
|
753
828
|
}
|
|
829
|
+
/** Snapshot — detaching later does not change an already-returned list,
|
|
830
|
+
* and mutating it does not change the machine's registrations. */
|
|
754
831
|
getBeforeObservers() {
|
|
755
|
-
return this.beforeObservers;
|
|
832
|
+
return [...this.beforeObservers];
|
|
756
833
|
}
|
|
757
834
|
attachAfter(observer) {
|
|
835
|
+
if (this.afterObservers.includes(observer)) return;
|
|
758
836
|
this.afterObservers.push(observer);
|
|
759
837
|
}
|
|
760
838
|
detachAfter(observer) {
|
|
761
839
|
const idx = this.afterObservers.indexOf(observer);
|
|
762
840
|
if (idx >= 0) this.afterObservers.splice(idx, 1);
|
|
763
841
|
}
|
|
842
|
+
/** Snapshot — see getBeforeObservers. */
|
|
764
843
|
getAfterObservers() {
|
|
765
|
-
return this.afterObservers;
|
|
844
|
+
return [...this.afterObservers];
|
|
766
845
|
}
|
|
767
846
|
// --- public locking ---
|
|
768
847
|
async acquireLock() {
|
|
769
848
|
return this.mutex.acquireLock();
|
|
770
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
|
+
*/
|
|
771
857
|
async releaseLock() {
|
|
772
|
-
await this.
|
|
858
|
+
await this.releaseMutex();
|
|
773
859
|
}
|
|
774
860
|
isLockAcquired() {
|
|
775
861
|
return this.mutex.isAcquired();
|
|
@@ -793,6 +879,27 @@ var Statemachine = class {
|
|
|
793
879
|
this.enqueueOperation(null, context, resolve, reject);
|
|
794
880
|
});
|
|
795
881
|
}
|
|
882
|
+
/**
|
|
883
|
+
* Resolves once the operation queue is empty and the runner is idle —
|
|
884
|
+
* i.e. every operation enqueued so far, including operations chained via
|
|
885
|
+
* EnqueueContext.enqueue(), has completed. Resolves immediately if the
|
|
886
|
+
* machine is already idle. Note this is a quiescence point, not a
|
|
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.
|
|
893
|
+
*/
|
|
894
|
+
whenIdle() {
|
|
895
|
+
this.assertNotReentrant("whenIdle()");
|
|
896
|
+
if (!this.running && this.queue.isEmpty()) {
|
|
897
|
+
return Promise.resolve();
|
|
898
|
+
}
|
|
899
|
+
return new Promise((resolve) => {
|
|
900
|
+
this.idleWaiters.push(resolve);
|
|
901
|
+
});
|
|
902
|
+
}
|
|
796
903
|
/** Runs fn with the re-entrancy flag set for its SYNCHRONOUS portion only:
|
|
797
904
|
* the flag is cleared as soon as fn returns (before any promise it returned
|
|
798
905
|
* is awaited), so concurrent external callers are never affected. This
|
|
@@ -814,6 +921,9 @@ var Statemachine = class {
|
|
|
814
921
|
}
|
|
815
922
|
/** Single entry point to the operation queue — every enqueue kicks the runner. */
|
|
816
923
|
enqueueOperation(eventName, context, resolve, reject, ifStateName) {
|
|
924
|
+
if (this.queue.size() >= this.maxQueueLength) {
|
|
925
|
+
throw new QueueLimitExceededError(this.maxQueueLength, eventName);
|
|
926
|
+
}
|
|
817
927
|
this.queue.enqueue({
|
|
818
928
|
eventName,
|
|
819
929
|
context: context ?? /* @__PURE__ */ new Map(),
|
|
@@ -834,6 +944,11 @@ var Statemachine = class {
|
|
|
834
944
|
}
|
|
835
945
|
} finally {
|
|
836
946
|
this.running = false;
|
|
947
|
+
if (this.queue.isEmpty() && this.idleWaiters.length > 0) {
|
|
948
|
+
const waiters = this.idleWaiters;
|
|
949
|
+
this.idleWaiters = [];
|
|
950
|
+
for (const waiter of waiters) waiter();
|
|
951
|
+
}
|
|
837
952
|
}
|
|
838
953
|
}
|
|
839
954
|
async runOperation(op) {
|
|
@@ -856,11 +971,8 @@ var Statemachine = class {
|
|
|
856
971
|
failure = { err };
|
|
857
972
|
} finally {
|
|
858
973
|
if (acquiredHere && this.autoreleaseLock) {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
} catch (err) {
|
|
862
|
-
if (!failure) failure = { err };
|
|
863
|
-
}
|
|
974
|
+
const releaseFailure = await this.releaseMutex();
|
|
975
|
+
if (releaseFailure && !failure) failure = releaseFailure;
|
|
864
976
|
}
|
|
865
977
|
}
|
|
866
978
|
if (failure) {
|
|
@@ -869,6 +981,36 @@ var Statemachine = class {
|
|
|
869
981
|
op.resolve();
|
|
870
982
|
}
|
|
871
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
|
+
}
|
|
872
1014
|
resolveEvent(name) {
|
|
873
1015
|
if (!this.currentState.hasEvent(name)) {
|
|
874
1016
|
throw new WrongEventForStateError(this.currentState.getName(), name);
|
|
@@ -939,7 +1081,13 @@ var Statemachine = class {
|
|
|
939
1081
|
chainedCtx,
|
|
940
1082
|
() => {
|
|
941
1083
|
},
|
|
942
|
-
() => {
|
|
1084
|
+
(err) => {
|
|
1085
|
+
try {
|
|
1086
|
+
this.onChainedOperationError?.(err, {
|
|
1087
|
+
eventName: chainedEventName
|
|
1088
|
+
});
|
|
1089
|
+
} catch {
|
|
1090
|
+
}
|
|
943
1091
|
},
|
|
944
1092
|
ifStateName
|
|
945
1093
|
);
|
|
@@ -1304,6 +1452,11 @@ var WeightTransition = class {
|
|
|
1304
1452
|
innerSelector;
|
|
1305
1453
|
epsilon;
|
|
1306
1454
|
constructor(innerSelector, epsilon = 1e-3) {
|
|
1455
|
+
if (!Number.isFinite(epsilon) || epsilon <= 0) {
|
|
1456
|
+
throw new RangeError(
|
|
1457
|
+
`WeightTransition epsilon must be a finite number greater than 0; got ${String(epsilon)}`
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1307
1460
|
this.innerSelector = innerSelector ?? new OneOrNoneActiveTransition();
|
|
1308
1461
|
this.epsilon = epsilon;
|
|
1309
1462
|
}
|
|
@@ -1312,6 +1465,11 @@ var WeightTransition = class {
|
|
|
1312
1465
|
let maxWeight = Number.NEGATIVE_INFINITY;
|
|
1313
1466
|
for (const transition of all) {
|
|
1314
1467
|
const weight = transition.getWeight();
|
|
1468
|
+
if (!Number.isFinite(weight)) {
|
|
1469
|
+
throw new RangeError(
|
|
1470
|
+
`WeightTransition: transition weights must be finite numbers; got ${String(weight)}`
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1315
1473
|
if (weight > maxWeight) maxWeight = weight;
|
|
1316
1474
|
}
|
|
1317
1475
|
const best = all.filter(
|
|
@@ -1326,15 +1484,31 @@ var LockAdapterMutex = class {
|
|
|
1326
1484
|
lockAdapter;
|
|
1327
1485
|
resourceName;
|
|
1328
1486
|
acquired = false;
|
|
1487
|
+
pendingAcquire = null;
|
|
1329
1488
|
constructor(lockAdapter, resourceName) {
|
|
1330
1489
|
this.lockAdapter = lockAdapter;
|
|
1331
1490
|
this.resourceName = resourceName;
|
|
1332
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
|
+
*/
|
|
1333
1499
|
async acquireLock() {
|
|
1334
|
-
if (
|
|
1335
|
-
|
|
1336
|
-
}
|
|
1337
|
-
|
|
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;
|
|
1338
1512
|
}
|
|
1339
1513
|
async releaseLock() {
|
|
1340
1514
|
if (this.acquired) {
|
|
@@ -1378,9 +1552,18 @@ var Factory = class {
|
|
|
1378
1552
|
afterObservers = /* @__PURE__ */ new Set();
|
|
1379
1553
|
transitionSelector = null;
|
|
1380
1554
|
mutexFactory = null;
|
|
1381
|
-
|
|
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 = {}) {
|
|
1382
1564
|
this.processDetector = processDetector;
|
|
1383
1565
|
this.stateNameDetector = stateNameDetector ?? null;
|
|
1566
|
+
this.options = { ...options };
|
|
1384
1567
|
}
|
|
1385
1568
|
setMutexFactory(factory) {
|
|
1386
1569
|
this.mutexFactory = factory;
|
|
@@ -1405,6 +1588,7 @@ var Factory = class {
|
|
|
1405
1588
|
const stateName = this.stateNameDetector ? this.stateNameDetector.detectCurrentStateName(subject) : void 0;
|
|
1406
1589
|
const mutex = this.mutexFactory ? await this.mutexFactory.createMutex(subject) : void 0;
|
|
1407
1590
|
const sm = new Statemachine(subject, process, {
|
|
1591
|
+
...this.options,
|
|
1408
1592
|
initialStateName: stateName ?? void 0,
|
|
1409
1593
|
transitionSelector: this.transitionSelector ?? void 0,
|
|
1410
1594
|
mutex: mutex ?? void 0
|
|
@@ -1486,6 +1670,14 @@ function toMermaidId(name) {
|
|
|
1486
1670
|
function escapeMermaidLabel(str) {
|
|
1487
1671
|
return str.replace(/\\/g, "#92;").replace(/"/g, "#quot;");
|
|
1488
1672
|
}
|
|
1673
|
+
var VALID_DIRECTIONS = /* @__PURE__ */ new Set(["TB", "BT", "LR", "RL"]);
|
|
1674
|
+
function assertDirection(value, optionName) {
|
|
1675
|
+
if (!VALID_DIRECTIONS.has(value)) {
|
|
1676
|
+
throw new RangeError(
|
|
1677
|
+
`${optionName} must be one of "TB", "BT", "LR", "RL"; got ${JSON.stringify(value)}`
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1489
1681
|
var GraphBuilder = class {
|
|
1490
1682
|
nodes = /* @__PURE__ */ new Map();
|
|
1491
1683
|
edges = [];
|
|
@@ -1561,6 +1753,7 @@ var GraphBuilder = class {
|
|
|
1561
1753
|
toDot(options) {
|
|
1562
1754
|
const graph = this.getGraph();
|
|
1563
1755
|
const rankdir = options?.rankdir ?? "LR";
|
|
1756
|
+
assertDirection(rankdir, "rankdir");
|
|
1564
1757
|
const lines = [];
|
|
1565
1758
|
lines.push("digraph {");
|
|
1566
1759
|
lines.push(` rankdir=${rankdir};`);
|
|
@@ -1580,6 +1773,7 @@ var GraphBuilder = class {
|
|
|
1580
1773
|
toMermaid(options) {
|
|
1581
1774
|
const graph = this.getGraph();
|
|
1582
1775
|
const direction = options?.direction ?? "LR";
|
|
1776
|
+
assertDirection(direction, "direction");
|
|
1583
1777
|
const lines = [];
|
|
1584
1778
|
lines.push(`stateDiagram-v2`);
|
|
1585
1779
|
lines.push(` direction ${direction}`);
|
|
@@ -1624,6 +1818,7 @@ export {
|
|
|
1624
1818
|
InvalidSubjectError,
|
|
1625
1819
|
LockAdapterMutex,
|
|
1626
1820
|
LockCanNotBeAcquiredError,
|
|
1821
|
+
LockCanNotBeReleasedError,
|
|
1627
1822
|
MutexFactory,
|
|
1628
1823
|
Not,
|
|
1629
1824
|
NullMutex,
|
|
@@ -1634,6 +1829,7 @@ export {
|
|
|
1634
1829
|
ProcessBuilder,
|
|
1635
1830
|
ProcessFinalizedError,
|
|
1636
1831
|
ProcessNotFoundError,
|
|
1832
|
+
QueueLimitExceededError,
|
|
1637
1833
|
ReentrancyError,
|
|
1638
1834
|
ScoreTransition,
|
|
1639
1835
|
SingleProcessDetector,
|