@camcima/finita 2.1.0 → 3.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 +71 -50
- package/dist/index.cjs +789 -520
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +314 -127
- package/dist/index.d.ts +314 -127
- package/dist/index.js +778 -516
- package/dist/index.js.map +1 -1
- package/package.json +21 -7
package/dist/index.cjs
CHANGED
|
@@ -22,19 +22,24 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AbstractNamedProcessDetector: () => AbstractNamedProcessDetector,
|
|
24
24
|
ActiveTransitionFilter: () => ActiveTransitionFilter,
|
|
25
|
+
AmbiguousTransitionError: () => AmbiguousTransitionError,
|
|
25
26
|
AndComposite: () => AndComposite,
|
|
27
|
+
AutomaticTransitionCycleError: () => AutomaticTransitionCycleError,
|
|
26
28
|
CallbackCondition: () => CallbackCondition,
|
|
27
29
|
CallbackObserver: () => CallbackObserver,
|
|
28
30
|
Contradiction: () => Contradiction,
|
|
29
|
-
Dispatcher: () => Dispatcher,
|
|
30
31
|
DuplicateStateError: () => DuplicateStateError,
|
|
32
|
+
DuplicateTransitionError: () => DuplicateTransitionError,
|
|
31
33
|
Event: () => Event,
|
|
32
34
|
Factory: () => Factory,
|
|
33
35
|
FilterStateByEvent: () => FilterStateByEvent,
|
|
34
36
|
FilterStateByFinalState: () => FilterStateByFinalState,
|
|
35
37
|
FilterStateByTransition: () => FilterStateByTransition,
|
|
36
38
|
FilterTransitionByEvent: () => FilterTransitionByEvent,
|
|
39
|
+
FinitaError: () => FinitaError,
|
|
37
40
|
GraphBuilder: () => GraphBuilder,
|
|
41
|
+
GraphValidationError: () => GraphValidationError,
|
|
42
|
+
InvalidSubjectError: () => InvalidSubjectError,
|
|
38
43
|
LockAdapterMutex: () => LockAdapterMutex,
|
|
39
44
|
LockCanNotBeAcquiredError: () => LockCanNotBeAcquiredError,
|
|
40
45
|
MutexFactory: () => MutexFactory,
|
|
@@ -44,12 +49,14 @@ __export(index_exports, {
|
|
|
44
49
|
OneOrNoneActiveTransition: () => OneOrNoneActiveTransition,
|
|
45
50
|
OrComposite: () => OrComposite,
|
|
46
51
|
Process: () => Process,
|
|
52
|
+
ProcessBuilder: () => ProcessBuilder,
|
|
53
|
+
ProcessFinalizedError: () => ProcessFinalizedError,
|
|
54
|
+
ProcessNotFoundError: () => ProcessNotFoundError,
|
|
47
55
|
ScoreTransition: () => ScoreTransition,
|
|
48
|
-
SetupHelper: () => SetupHelper,
|
|
49
56
|
SingleProcessDetector: () => SingleProcessDetector,
|
|
50
57
|
State: () => State,
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
StateEventNotFoundError: () => StateEventNotFoundError,
|
|
59
|
+
StateNotFoundError: () => StateNotFoundError,
|
|
53
60
|
StatefulStateNameDetector: () => StatefulStateNameDetector,
|
|
54
61
|
StatefulStatusChanger: () => StatefulStatusChanger,
|
|
55
62
|
Statemachine: () => Statemachine,
|
|
@@ -116,37 +123,148 @@ var Event = class {
|
|
|
116
123
|
}
|
|
117
124
|
};
|
|
118
125
|
|
|
119
|
-
// src/
|
|
120
|
-
var
|
|
126
|
+
// src/internal/InternalConstruction.ts
|
|
127
|
+
var INTERNAL_CONSTRUCTION_KEY = /* @__PURE__ */ Symbol(
|
|
128
|
+
"@camcima/finita/InternalConstruction"
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// src/error/FinitaError.ts
|
|
132
|
+
var FinitaError = class _FinitaError extends Error {
|
|
133
|
+
constructor(message) {
|
|
134
|
+
super(message);
|
|
135
|
+
if (new.target === _FinitaError) {
|
|
136
|
+
throw new TypeError(
|
|
137
|
+
"FinitaError is abstract and cannot be instantiated directly"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/error/StateNotFoundError.ts
|
|
144
|
+
var StateNotFoundError = class extends FinitaError {
|
|
145
|
+
code = "stateNotFound";
|
|
146
|
+
stateName;
|
|
147
|
+
availableStates;
|
|
148
|
+
constructor(stateName, availableStates) {
|
|
149
|
+
const list = Array.from(availableStates);
|
|
150
|
+
const display = list.length > 0 ? list.map((n) => `"${n}"`).join(", ") : "(none)";
|
|
151
|
+
super(`State "${stateName}" not found. Available: ${display}`);
|
|
152
|
+
this.name = "StateNotFoundError";
|
|
153
|
+
this.stateName = stateName;
|
|
154
|
+
this.availableStates = Object.freeze([...list]);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// src/StateCollection.ts
|
|
159
|
+
var StateCollection = class {
|
|
160
|
+
states;
|
|
161
|
+
constructor(states) {
|
|
162
|
+
const map = /* @__PURE__ */ new Map();
|
|
163
|
+
for (const s of states) {
|
|
164
|
+
map.set(s.getName(), s);
|
|
165
|
+
}
|
|
166
|
+
this.states = map;
|
|
167
|
+
}
|
|
168
|
+
getStates() {
|
|
169
|
+
return this.states.values();
|
|
170
|
+
}
|
|
171
|
+
getState(name) {
|
|
172
|
+
const s = this.states.get(name);
|
|
173
|
+
if (!s) {
|
|
174
|
+
throw new StateNotFoundError(name, this.states.keys());
|
|
175
|
+
}
|
|
176
|
+
return s;
|
|
177
|
+
}
|
|
178
|
+
hasState(name) {
|
|
179
|
+
return this.states.has(name);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// src/Process.ts
|
|
184
|
+
var Process = class {
|
|
121
185
|
name;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
186
|
+
initialState;
|
|
187
|
+
states;
|
|
188
|
+
constructor(key, name, initialState, states) {
|
|
189
|
+
if (key !== INTERNAL_CONSTRUCTION_KEY) {
|
|
190
|
+
throw new Error("Process is not user-constructible; use ProcessBuilder.");
|
|
191
|
+
}
|
|
126
192
|
this.name = name;
|
|
193
|
+
this.initialState = initialState;
|
|
194
|
+
this.states = new StateCollection(states);
|
|
195
|
+
Object.freeze(this);
|
|
127
196
|
}
|
|
128
197
|
getName() {
|
|
129
198
|
return this.name;
|
|
130
199
|
}
|
|
131
|
-
|
|
132
|
-
return this.
|
|
200
|
+
getInitialState() {
|
|
201
|
+
return this.initialState;
|
|
202
|
+
}
|
|
203
|
+
getStates() {
|
|
204
|
+
return this.states.getStates();
|
|
133
205
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
206
|
+
getState(name) {
|
|
207
|
+
return this.states.getState(name);
|
|
208
|
+
}
|
|
209
|
+
hasState(name) {
|
|
210
|
+
return this.states.hasState(name);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/error/StateEventNotFoundError.ts
|
|
215
|
+
var StateEventNotFoundError = class extends FinitaError {
|
|
216
|
+
code = "stateEventNotFound";
|
|
217
|
+
stateName;
|
|
218
|
+
eventName;
|
|
219
|
+
constructor(stateName, eventName) {
|
|
220
|
+
super(`State "${stateName}" has no event "${eventName}"`);
|
|
221
|
+
this.name = "StateEventNotFoundError";
|
|
222
|
+
this.stateName = stateName;
|
|
223
|
+
this.eventName = eventName;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// src/State.ts
|
|
228
|
+
var State = class {
|
|
229
|
+
name;
|
|
230
|
+
_transitions = null;
|
|
231
|
+
events;
|
|
232
|
+
metadata;
|
|
233
|
+
constructor(key, name, eventNames, metadata) {
|
|
234
|
+
if (key !== INTERNAL_CONSTRUCTION_KEY) {
|
|
235
|
+
throw new Error("State is not user-constructible; use ProcessBuilder.");
|
|
137
236
|
}
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
if (existing.getTargetState().getName() === targetName && existing.getEventName() === eventName && existing.getConditionName() === conditionName) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
237
|
+
this.name = name;
|
|
238
|
+
const events = /* @__PURE__ */ new Map();
|
|
239
|
+
for (const en of eventNames) {
|
|
240
|
+
events.set(en, new Event(en));
|
|
145
241
|
}
|
|
146
|
-
this.
|
|
147
|
-
|
|
148
|
-
|
|
242
|
+
this.events = events;
|
|
243
|
+
this.metadata = new Map(metadata);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Internal: populate transitions after State construction.
|
|
247
|
+
* May only be called once and only with the construction key.
|
|
248
|
+
* Used by ProcessBuilder to break the cycle: State must exist before
|
|
249
|
+
* Transitions can target it, but State needs its transitions to be useful.
|
|
250
|
+
*/
|
|
251
|
+
_initTransitions(key, transitions) {
|
|
252
|
+
if (key !== INTERNAL_CONSTRUCTION_KEY) {
|
|
253
|
+
throw new Error("_initTransitions is internal");
|
|
254
|
+
}
|
|
255
|
+
if (this._transitions !== null) {
|
|
256
|
+
throw new Error(`State "${this.name}" transitions already set`);
|
|
257
|
+
}
|
|
258
|
+
this._transitions = new Set(transitions);
|
|
259
|
+
}
|
|
260
|
+
getName() {
|
|
261
|
+
return this.name;
|
|
262
|
+
}
|
|
263
|
+
getTransitions() {
|
|
264
|
+
if (this._transitions === null) {
|
|
265
|
+
return [];
|
|
149
266
|
}
|
|
267
|
+
return this._transitions;
|
|
150
268
|
}
|
|
151
269
|
getEventNames() {
|
|
152
270
|
return Array.from(this.events.keys());
|
|
@@ -155,10 +273,9 @@ var State = class {
|
|
|
155
273
|
return this.events.has(name);
|
|
156
274
|
}
|
|
157
275
|
getEvent(name) {
|
|
158
|
-
|
|
276
|
+
const event = this.events.get(name);
|
|
159
277
|
if (!event) {
|
|
160
|
-
|
|
161
|
-
this.events.set(name, event);
|
|
278
|
+
throw new StateEventNotFoundError(this.name, name);
|
|
162
279
|
}
|
|
163
280
|
return event;
|
|
164
281
|
}
|
|
@@ -168,15 +285,9 @@ var State = class {
|
|
|
168
285
|
getMetadataValue(key) {
|
|
169
286
|
return this.metadata.get(key);
|
|
170
287
|
}
|
|
171
|
-
setMetadataValue(key, value) {
|
|
172
|
-
this.metadata.set(key, value);
|
|
173
|
-
}
|
|
174
288
|
hasMetadataValue(key) {
|
|
175
289
|
return this.metadata.has(key);
|
|
176
290
|
}
|
|
177
|
-
deleteMetadataValue(key) {
|
|
178
|
-
this.metadata.delete(key);
|
|
179
|
-
}
|
|
180
291
|
};
|
|
181
292
|
|
|
182
293
|
// src/Transition.ts
|
|
@@ -184,11 +295,17 @@ var Transition = class {
|
|
|
184
295
|
targetState;
|
|
185
296
|
eventName;
|
|
186
297
|
condition;
|
|
187
|
-
weight
|
|
188
|
-
constructor(targetState, eventName
|
|
298
|
+
weight;
|
|
299
|
+
constructor(key, targetState, eventName, condition, weight) {
|
|
300
|
+
if (key !== INTERNAL_CONSTRUCTION_KEY) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
"Transition is not user-constructible; use ProcessBuilder."
|
|
303
|
+
);
|
|
304
|
+
}
|
|
189
305
|
this.targetState = targetState;
|
|
190
306
|
this.eventName = eventName;
|
|
191
307
|
this.condition = condition;
|
|
308
|
+
this.weight = weight;
|
|
192
309
|
}
|
|
193
310
|
getTargetState() {
|
|
194
311
|
return this.targetState;
|
|
@@ -197,117 +314,31 @@ var Transition = class {
|
|
|
197
314
|
return this.eventName;
|
|
198
315
|
}
|
|
199
316
|
getConditionName() {
|
|
200
|
-
|
|
201
|
-
return this.condition.getName();
|
|
202
|
-
}
|
|
203
|
-
return null;
|
|
317
|
+
return this.condition ? this.condition.getName() : null;
|
|
204
318
|
}
|
|
205
319
|
getCondition() {
|
|
206
320
|
return this.condition;
|
|
207
321
|
}
|
|
208
322
|
async isActive(subject, context, event) {
|
|
209
|
-
let
|
|
323
|
+
let active;
|
|
210
324
|
if (event) {
|
|
211
|
-
|
|
325
|
+
active = event.getName() === this.eventName;
|
|
212
326
|
} else {
|
|
213
|
-
|
|
327
|
+
active = this.eventName === null;
|
|
214
328
|
}
|
|
215
|
-
if (this.condition &&
|
|
216
|
-
|
|
329
|
+
if (this.condition && active) {
|
|
330
|
+
active = await this.condition.checkCondition(subject, context);
|
|
217
331
|
}
|
|
218
|
-
return
|
|
332
|
+
return active;
|
|
219
333
|
}
|
|
220
334
|
getWeight() {
|
|
221
335
|
return this.weight;
|
|
222
336
|
}
|
|
223
|
-
setWeight(weight) {
|
|
224
|
-
this.weight = weight;
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
// src/util/StateCollectionMerger.ts
|
|
229
|
-
var StateCollectionMerger = class {
|
|
230
|
-
targetCollection;
|
|
231
|
-
stateNamePrefix = "";
|
|
232
|
-
constructor(targetCollection) {
|
|
233
|
-
this.targetCollection = targetCollection;
|
|
234
|
-
}
|
|
235
|
-
getStateNamePrefix() {
|
|
236
|
-
return this.stateNamePrefix;
|
|
237
|
-
}
|
|
238
|
-
setStateNamePrefix(prefix) {
|
|
239
|
-
this.stateNamePrefix = prefix;
|
|
240
|
-
}
|
|
241
|
-
getTargetCollection() {
|
|
242
|
-
return this.targetCollection;
|
|
243
|
-
}
|
|
244
|
-
createState(name) {
|
|
245
|
-
return new State(name);
|
|
246
|
-
}
|
|
247
|
-
findOrCreateState(name) {
|
|
248
|
-
const prefixedName = this.stateNamePrefix + name;
|
|
249
|
-
if (this.targetCollection.hasState(prefixedName)) {
|
|
250
|
-
return this.targetCollection.getState(prefixedName);
|
|
251
|
-
}
|
|
252
|
-
const state = this.createState(prefixedName);
|
|
253
|
-
this.targetCollection.addState(state);
|
|
254
|
-
return state;
|
|
255
|
-
}
|
|
256
|
-
createCondition(sourceTransition) {
|
|
257
|
-
return sourceTransition.getCondition();
|
|
258
|
-
}
|
|
259
|
-
createTransition(sourceTransition) {
|
|
260
|
-
const targetStateName = sourceTransition.getTargetState().getName();
|
|
261
|
-
const targetState = this.findOrCreateState(targetStateName);
|
|
262
|
-
this.mergeMetadata(sourceTransition.getTargetState(), targetState);
|
|
263
|
-
const eventName = sourceTransition.getEventName();
|
|
264
|
-
const condition = this.createCondition(sourceTransition);
|
|
265
|
-
const transition = new Transition(targetState, eventName, condition);
|
|
266
|
-
transition.setWeight(sourceTransition.getWeight());
|
|
267
|
-
return transition;
|
|
268
|
-
}
|
|
269
|
-
mergeMetadata(source, target) {
|
|
270
|
-
const metadata = source.getMetadata();
|
|
271
|
-
for (const [key, value] of Object.entries(metadata)) {
|
|
272
|
-
target.setMetadataValue(key, value);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
mergeEvent(source, target, eventName) {
|
|
276
|
-
const sourceEvent = source.getEvent(eventName);
|
|
277
|
-
const targetEvent = target.getEvent(eventName);
|
|
278
|
-
const sourceMetadata = sourceEvent.getMetadata();
|
|
279
|
-
for (const [key, value] of Object.entries(sourceMetadata)) {
|
|
280
|
-
targetEvent.setMetadataValue(key, value);
|
|
281
|
-
}
|
|
282
|
-
for (const observer of sourceEvent.getObservers()) {
|
|
283
|
-
targetEvent.attach(observer);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
mergeState(sourceState) {
|
|
287
|
-
const targetState = this.findOrCreateState(sourceState.getName());
|
|
288
|
-
this.mergeMetadata(sourceState, targetState);
|
|
289
|
-
for (const sourceTransition of sourceState.getTransitions()) {
|
|
290
|
-
const targetTransition = this.createTransition(sourceTransition);
|
|
291
|
-
targetState.addTransition(targetTransition);
|
|
292
|
-
}
|
|
293
|
-
for (const eventName of sourceState.getEventNames()) {
|
|
294
|
-
this.mergeEvent(sourceState, targetState, eventName);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
merge(source) {
|
|
298
|
-
if ("getStates" in source && typeof source.getStates === "function") {
|
|
299
|
-
const collection = source;
|
|
300
|
-
for (const state of collection.getStates()) {
|
|
301
|
-
this.mergeState(state);
|
|
302
|
-
}
|
|
303
|
-
} else {
|
|
304
|
-
this.mergeState(source);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
337
|
};
|
|
308
338
|
|
|
309
339
|
// src/error/DuplicateStateError.ts
|
|
310
|
-
var DuplicateStateError = class extends
|
|
340
|
+
var DuplicateStateError = class extends FinitaError {
|
|
341
|
+
code = "duplicateState";
|
|
311
342
|
stateName;
|
|
312
343
|
constructor(stateName) {
|
|
313
344
|
super(
|
|
@@ -318,81 +349,309 @@ var DuplicateStateError = class extends Error {
|
|
|
318
349
|
}
|
|
319
350
|
};
|
|
320
351
|
|
|
321
|
-
// src/
|
|
322
|
-
var
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
getStates() {
|
|
333
|
-
return this.states.values();
|
|
334
|
-
}
|
|
335
|
-
hasState(name) {
|
|
336
|
-
return this.states.has(name);
|
|
337
|
-
}
|
|
338
|
-
addState(state) {
|
|
339
|
-
const existing = this.states.get(state.getName());
|
|
340
|
-
if (existing && existing !== state) {
|
|
341
|
-
throw new DuplicateStateError(state.getName());
|
|
342
|
-
}
|
|
343
|
-
this.states.set(state.getName(), state);
|
|
352
|
+
// src/error/ProcessFinalizedError.ts
|
|
353
|
+
var ProcessFinalizedError = class extends FinitaError {
|
|
354
|
+
code = "processFinalized";
|
|
355
|
+
processName;
|
|
356
|
+
constructor(processName) {
|
|
357
|
+
super(
|
|
358
|
+
`Process "${processName}" has already been built; ProcessBuilder.build() may only be called once`
|
|
359
|
+
);
|
|
360
|
+
this.name = "ProcessFinalizedError";
|
|
361
|
+
this.processName = processName;
|
|
344
362
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// src/error/GraphValidationError.ts
|
|
366
|
+
var GraphValidationError = class extends FinitaError {
|
|
367
|
+
code;
|
|
368
|
+
details;
|
|
369
|
+
constructor(code, message, details = {}) {
|
|
370
|
+
super(`[${code}] ${message}`);
|
|
371
|
+
this.name = "GraphValidationError";
|
|
372
|
+
this.code = code;
|
|
373
|
+
this.details = Object.freeze({ ...details });
|
|
350
374
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// src/error/DuplicateTransitionError.ts
|
|
378
|
+
var DuplicateTransitionError = class extends FinitaError {
|
|
379
|
+
code = "duplicateTransition";
|
|
380
|
+
conflict;
|
|
381
|
+
constructor(conflict) {
|
|
382
|
+
const eventLabel = conflict.eventName ?? "<automatic>";
|
|
383
|
+
const existing = conflict.existingConditionName ?? "<no condition>";
|
|
384
|
+
const incoming = conflict.newConditionName ?? "<no condition>";
|
|
385
|
+
super(
|
|
386
|
+
`Conflicting transition declarations from "${conflict.fromState}" to "${conflict.toState}" on event "${eventLabel}": existing condition "${existing}" vs new condition "${incoming}"`
|
|
387
|
+
);
|
|
388
|
+
this.name = "DuplicateTransitionError";
|
|
389
|
+
this.conflict = Object.freeze({ ...conflict });
|
|
354
390
|
}
|
|
355
391
|
};
|
|
356
392
|
|
|
357
|
-
// src/
|
|
358
|
-
var
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
this.
|
|
365
|
-
|
|
366
|
-
|
|
393
|
+
// src/ProcessBuilder.ts
|
|
394
|
+
var ProcessBuilder = class {
|
|
395
|
+
processName;
|
|
396
|
+
stateSpecs = /* @__PURE__ */ new Map();
|
|
397
|
+
transitionSpecs = [];
|
|
398
|
+
built = false;
|
|
399
|
+
constructor(processName) {
|
|
400
|
+
this.processName = processName;
|
|
401
|
+
}
|
|
402
|
+
addState(name, options = {}) {
|
|
403
|
+
if (this.built) {
|
|
404
|
+
throw new ProcessFinalizedError(this.processName);
|
|
405
|
+
}
|
|
406
|
+
if (this.stateSpecs.has(name)) {
|
|
407
|
+
throw new DuplicateStateError(name);
|
|
408
|
+
}
|
|
409
|
+
this.stateSpecs.set(name, {
|
|
410
|
+
name,
|
|
411
|
+
initial: options.initial === true,
|
|
412
|
+
metadata: new Map(Object.entries(options.metadata ?? {}))
|
|
413
|
+
});
|
|
414
|
+
return this;
|
|
367
415
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
416
|
+
addTransition(fromState, toState, options = {}) {
|
|
417
|
+
if (this.built) {
|
|
418
|
+
throw new ProcessFinalizedError(this.processName);
|
|
419
|
+
}
|
|
420
|
+
let eventName = null;
|
|
421
|
+
if (options.event !== void 0) {
|
|
422
|
+
const raw = options.event;
|
|
423
|
+
if (raw.trim() === "" || raw !== raw.trim()) {
|
|
424
|
+
throw new GraphValidationError(
|
|
425
|
+
"invalidEventName",
|
|
426
|
+
`addTransition called with an empty or whitespace-padded event name from "${fromState}" to "${toState}"`,
|
|
427
|
+
{ fromState, toState, eventName: raw }
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
eventName = raw;
|
|
431
|
+
}
|
|
432
|
+
if (options.condition) {
|
|
433
|
+
const conditionName = options.condition.getName();
|
|
434
|
+
if (conditionName.trim() === "") {
|
|
435
|
+
throw new GraphValidationError(
|
|
436
|
+
"invalidConditionName",
|
|
437
|
+
`addTransition called with an empty/whitespace condition name from "${fromState}" to "${toState}"`,
|
|
438
|
+
{ fromState, toState, conditionName }
|
|
439
|
+
);
|
|
373
440
|
}
|
|
374
|
-
return;
|
|
375
441
|
}
|
|
376
|
-
this.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
442
|
+
this.transitionSpecs.push({
|
|
443
|
+
fromState,
|
|
444
|
+
toState,
|
|
445
|
+
eventName,
|
|
446
|
+
condition: options.condition ?? null,
|
|
447
|
+
weight: options.weight ?? 1
|
|
448
|
+
});
|
|
449
|
+
return this;
|
|
450
|
+
}
|
|
451
|
+
build(options = {}) {
|
|
452
|
+
if (this.built) {
|
|
453
|
+
throw new ProcessFinalizedError(this.processName);
|
|
454
|
+
}
|
|
455
|
+
this.built = true;
|
|
456
|
+
this.validateInitialState();
|
|
457
|
+
this.validateTransitionEndpoints();
|
|
458
|
+
this.validateNoConflictingDuplicates();
|
|
459
|
+
const initialName = this.findInitialStateName();
|
|
460
|
+
const eventNamesByState = this.collectEventNamesByState();
|
|
461
|
+
const finalStates = this.buildAllStates(eventNamesByState);
|
|
462
|
+
if (options.strictOrphans) {
|
|
463
|
+
this.validateOrphans(finalStates, initialName);
|
|
464
|
+
}
|
|
465
|
+
const initialState = finalStates.get(initialName);
|
|
466
|
+
return new Process(
|
|
467
|
+
INTERNAL_CONSTRUCTION_KEY,
|
|
468
|
+
this.processName,
|
|
469
|
+
initialState,
|
|
470
|
+
finalStates.values()
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
// --- private helpers ---
|
|
474
|
+
validateInitialState() {
|
|
475
|
+
const initials = Array.from(this.stateSpecs.values()).filter(
|
|
476
|
+
(s) => s.initial
|
|
477
|
+
);
|
|
478
|
+
if (initials.length === 0) {
|
|
479
|
+
throw new GraphValidationError(
|
|
480
|
+
"missingInitialState",
|
|
481
|
+
`Process "${this.processName}" has no state declared with { initial: true }`,
|
|
482
|
+
{ processName: this.processName }
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
if (initials.length > 1) {
|
|
486
|
+
throw new GraphValidationError(
|
|
487
|
+
"multipleInitialStates",
|
|
488
|
+
`Process "${this.processName}" declares multiple initial states: ${initials.map((s) => `"${s.name}"`).join(", ")}`,
|
|
489
|
+
{
|
|
490
|
+
processName: this.processName,
|
|
491
|
+
initialStates: initials.map((s) => s.name)
|
|
492
|
+
}
|
|
493
|
+
);
|
|
380
494
|
}
|
|
381
495
|
}
|
|
382
|
-
|
|
383
|
-
return this.name;
|
|
496
|
+
findInitialStateName() {
|
|
497
|
+
return Array.from(this.stateSpecs.values()).find((s) => s.initial).name;
|
|
384
498
|
}
|
|
385
|
-
|
|
386
|
-
|
|
499
|
+
validateTransitionEndpoints() {
|
|
500
|
+
for (const t of this.transitionSpecs) {
|
|
501
|
+
if (!this.stateSpecs.has(t.fromState)) {
|
|
502
|
+
throw new GraphValidationError(
|
|
503
|
+
"unknownSource",
|
|
504
|
+
`Transition source state "${t.fromState}" was not declared with addState`,
|
|
505
|
+
{
|
|
506
|
+
fromState: t.fromState,
|
|
507
|
+
toState: t.toState,
|
|
508
|
+
eventName: t.eventName
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
if (!this.stateSpecs.has(t.toState)) {
|
|
513
|
+
throw new GraphValidationError(
|
|
514
|
+
"unknownTarget",
|
|
515
|
+
`Transition target state "${t.toState}" was not declared with addState`,
|
|
516
|
+
{
|
|
517
|
+
fromState: t.fromState,
|
|
518
|
+
toState: t.toState,
|
|
519
|
+
eventName: t.eventName
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
387
524
|
}
|
|
388
|
-
|
|
389
|
-
|
|
525
|
+
validateNoConflictingDuplicates() {
|
|
526
|
+
const seen = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const t of this.transitionSpecs) {
|
|
528
|
+
const key = `${t.fromState}\0${t.eventName ?? ""}\0${t.toState}`;
|
|
529
|
+
const existing = seen.get(key);
|
|
530
|
+
if (!existing) {
|
|
531
|
+
seen.set(key, t);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
if (existing.condition !== t.condition) {
|
|
535
|
+
throw new DuplicateTransitionError({
|
|
536
|
+
fromState: t.fromState,
|
|
537
|
+
toState: t.toState,
|
|
538
|
+
eventName: t.eventName,
|
|
539
|
+
existingConditionName: existing.condition ? existing.condition.getName() : null,
|
|
540
|
+
newConditionName: t.condition ? t.condition.getName() : null
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
390
544
|
}
|
|
391
|
-
|
|
392
|
-
|
|
545
|
+
collectEventNamesByState() {
|
|
546
|
+
const out = /* @__PURE__ */ new Map();
|
|
547
|
+
for (const t of this.transitionSpecs) {
|
|
548
|
+
if (t.eventName === null) continue;
|
|
549
|
+
let bucket = out.get(t.fromState);
|
|
550
|
+
if (!bucket) {
|
|
551
|
+
bucket = /* @__PURE__ */ new Set();
|
|
552
|
+
out.set(t.fromState, bucket);
|
|
553
|
+
}
|
|
554
|
+
bucket.add(t.eventName);
|
|
555
|
+
}
|
|
556
|
+
return new Map(
|
|
557
|
+
Array.from(out.entries()).map(([k, v]) => [k, Array.from(v)])
|
|
558
|
+
);
|
|
393
559
|
}
|
|
394
|
-
|
|
395
|
-
|
|
560
|
+
/**
|
|
561
|
+
* Two-phase construction:
|
|
562
|
+
* Phase 1 — create all final State instances with no transitions.
|
|
563
|
+
* Phase 2 — build Transitions targeting the Phase-1 States, then attach
|
|
564
|
+
* them via State._initTransitions.
|
|
565
|
+
*
|
|
566
|
+
* Because every Transition is created after every State exists, target
|
|
567
|
+
* identity holds by construction for any graph topology (acyclic, cyclic,
|
|
568
|
+
* self-loop).
|
|
569
|
+
*/
|
|
570
|
+
buildAllStates(eventNamesByState) {
|
|
571
|
+
const built = /* @__PURE__ */ new Map();
|
|
572
|
+
for (const spec of this.stateSpecs.values()) {
|
|
573
|
+
built.set(
|
|
574
|
+
spec.name,
|
|
575
|
+
new State(
|
|
576
|
+
INTERNAL_CONSTRUCTION_KEY,
|
|
577
|
+
spec.name,
|
|
578
|
+
eventNamesByState.get(spec.name) ?? [],
|
|
579
|
+
spec.metadata
|
|
580
|
+
)
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
const dedupSeen = /* @__PURE__ */ new Set();
|
|
584
|
+
const conditionId = /* @__PURE__ */ new Map();
|
|
585
|
+
let nextConditionId = 0;
|
|
586
|
+
const idForCondition = (cond) => {
|
|
587
|
+
if (cond === null) return "";
|
|
588
|
+
let id = conditionId.get(cond);
|
|
589
|
+
if (id === void 0) {
|
|
590
|
+
id = ++nextConditionId;
|
|
591
|
+
conditionId.set(cond, id);
|
|
592
|
+
}
|
|
593
|
+
return String(id);
|
|
594
|
+
};
|
|
595
|
+
const transitionsByState = /* @__PURE__ */ new Map();
|
|
596
|
+
for (const spec of this.stateSpecs.values()) {
|
|
597
|
+
transitionsByState.set(spec.name, []);
|
|
598
|
+
}
|
|
599
|
+
for (const tSpec of this.transitionSpecs) {
|
|
600
|
+
const dedupKey = `${tSpec.fromState}\0${tSpec.eventName ?? ""}\0${tSpec.toState}\0${idForCondition(tSpec.condition)}`;
|
|
601
|
+
if (dedupSeen.has(dedupKey)) continue;
|
|
602
|
+
dedupSeen.add(dedupKey);
|
|
603
|
+
const targetState = built.get(tSpec.toState);
|
|
604
|
+
const transition = new Transition(
|
|
605
|
+
INTERNAL_CONSTRUCTION_KEY,
|
|
606
|
+
targetState,
|
|
607
|
+
tSpec.eventName,
|
|
608
|
+
tSpec.condition,
|
|
609
|
+
tSpec.weight
|
|
610
|
+
);
|
|
611
|
+
transitionsByState.get(tSpec.fromState).push(transition);
|
|
612
|
+
}
|
|
613
|
+
for (const [name, transitions] of transitionsByState) {
|
|
614
|
+
built.get(name)._initTransitions(
|
|
615
|
+
INTERNAL_CONSTRUCTION_KEY,
|
|
616
|
+
transitions
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
return built;
|
|
620
|
+
}
|
|
621
|
+
validateOrphans(states, initialName) {
|
|
622
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
623
|
+
const queue = [initialName];
|
|
624
|
+
while (queue.length > 0) {
|
|
625
|
+
const name = queue.shift();
|
|
626
|
+
if (reachable.has(name)) continue;
|
|
627
|
+
reachable.add(name);
|
|
628
|
+
const s = states.get(name);
|
|
629
|
+
for (const t of s.getTransitions()) {
|
|
630
|
+
queue.push(t.getTargetState().getName());
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
const orphans = [];
|
|
634
|
+
for (const name of states.keys()) {
|
|
635
|
+
if (!reachable.has(name)) orphans.push(name);
|
|
636
|
+
}
|
|
637
|
+
if (orphans.length > 0) {
|
|
638
|
+
throw new GraphValidationError(
|
|
639
|
+
"orphanState",
|
|
640
|
+
`Process "${this.processName}" has unreachable states: ${orphans.map((n) => `"${n}"`).join(", ")}`,
|
|
641
|
+
{ processName: this.processName, orphanStates: orphans }
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
// src/error/AmbiguousTransitionError.ts
|
|
648
|
+
var AmbiguousTransitionError = class extends FinitaError {
|
|
649
|
+
code = "ambiguousTransition";
|
|
650
|
+
activeCount;
|
|
651
|
+
constructor(activeCount) {
|
|
652
|
+
super(`More than one transition is active! (active count: ${activeCount})`);
|
|
653
|
+
this.name = "AmbiguousTransitionError";
|
|
654
|
+
this.activeCount = activeCount;
|
|
396
655
|
}
|
|
397
656
|
};
|
|
398
657
|
|
|
@@ -406,7 +665,7 @@ var OneOrNoneActiveTransition = class {
|
|
|
406
665
|
case 1:
|
|
407
666
|
return arr[0];
|
|
408
667
|
default:
|
|
409
|
-
throw new
|
|
668
|
+
throw new AmbiguousTransitionError(arr.length);
|
|
410
669
|
}
|
|
411
670
|
}
|
|
412
671
|
};
|
|
@@ -430,19 +689,15 @@ var NullMutex = class {
|
|
|
430
689
|
}
|
|
431
690
|
};
|
|
432
691
|
|
|
433
|
-
// src/Dispatcher.ts
|
|
692
|
+
// src/internal/Dispatcher.ts
|
|
434
693
|
var Dispatcher = class {
|
|
435
694
|
commands = [];
|
|
436
|
-
onReadyCallbacks = [];
|
|
437
695
|
ready = false;
|
|
438
|
-
dispatch(event, args = []
|
|
696
|
+
dispatch(event, args = []) {
|
|
439
697
|
if (this.ready) {
|
|
440
698
|
throw new Error("Was already invoked!");
|
|
441
699
|
}
|
|
442
700
|
this.commands.push({ event, args });
|
|
443
|
-
if (onReadyCallback) {
|
|
444
|
-
this.onReadyCallbacks.push(onReadyCallback);
|
|
445
|
-
}
|
|
446
701
|
}
|
|
447
702
|
async invoke() {
|
|
448
703
|
if (this.ready) {
|
|
@@ -452,12 +707,20 @@ var Dispatcher = class {
|
|
|
452
707
|
await event.invoke(...args);
|
|
453
708
|
}
|
|
454
709
|
this.ready = true;
|
|
455
|
-
for (const callback of this.onReadyCallbacks) {
|
|
456
|
-
await callback.invoke();
|
|
457
|
-
}
|
|
458
710
|
}
|
|
459
|
-
|
|
460
|
-
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// src/internal/OperationQueue.ts
|
|
714
|
+
var OperationQueue = class {
|
|
715
|
+
items = [];
|
|
716
|
+
enqueue(op) {
|
|
717
|
+
this.items.push(op);
|
|
718
|
+
}
|
|
719
|
+
dequeue() {
|
|
720
|
+
return this.items.shift();
|
|
721
|
+
}
|
|
722
|
+
isEmpty() {
|
|
723
|
+
return this.items.length === 0;
|
|
461
724
|
}
|
|
462
725
|
};
|
|
463
726
|
|
|
@@ -475,7 +738,8 @@ var ActiveTransitionFilter = class {
|
|
|
475
738
|
};
|
|
476
739
|
|
|
477
740
|
// src/error/WrongEventForStateError.ts
|
|
478
|
-
var WrongEventForStateError = class extends
|
|
741
|
+
var WrongEventForStateError = class extends FinitaError {
|
|
742
|
+
code = "wrongEventForState";
|
|
479
743
|
stateName;
|
|
480
744
|
eventName;
|
|
481
745
|
constructor(stateName, eventName) {
|
|
@@ -487,41 +751,52 @@ var WrongEventForStateError = class extends Error {
|
|
|
487
751
|
};
|
|
488
752
|
|
|
489
753
|
// src/error/LockCanNotBeAcquiredError.ts
|
|
490
|
-
var LockCanNotBeAcquiredError = class extends
|
|
754
|
+
var LockCanNotBeAcquiredError = class extends FinitaError {
|
|
755
|
+
code = "lockCanNotBeAcquired";
|
|
491
756
|
constructor(message = "Lock can not be acquired!") {
|
|
492
757
|
super(message);
|
|
493
758
|
this.name = "LockCanNotBeAcquiredError";
|
|
494
759
|
}
|
|
495
760
|
};
|
|
496
761
|
|
|
762
|
+
// src/error/AutomaticTransitionCycleError.ts
|
|
763
|
+
var AutomaticTransitionCycleError = class extends FinitaError {
|
|
764
|
+
code = "automaticTransitionCycle";
|
|
765
|
+
targetStateName;
|
|
766
|
+
visitedStateNames;
|
|
767
|
+
constructor(targetStateName, visitedStateNames) {
|
|
768
|
+
const visited = Array.from(visitedStateNames);
|
|
769
|
+
super(
|
|
770
|
+
`Automatic transition cycle detected: state "${targetStateName}" was already visited \u2014 this would cause infinite recursion`
|
|
771
|
+
);
|
|
772
|
+
this.name = "AutomaticTransitionCycleError";
|
|
773
|
+
this.targetStateName = targetStateName;
|
|
774
|
+
this.visitedStateNames = Object.freeze([...visited]);
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
|
|
497
778
|
// src/Statemachine.ts
|
|
498
779
|
var Statemachine = class {
|
|
499
780
|
subject;
|
|
500
|
-
currentState;
|
|
501
|
-
lastState = null;
|
|
502
|
-
transitionSelector;
|
|
503
|
-
selectedTransition = null;
|
|
504
781
|
process;
|
|
782
|
+
transitionSelector;
|
|
505
783
|
mutex;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
784
|
+
currentState;
|
|
785
|
+
lastState = null;
|
|
786
|
+
autoreleaseLock;
|
|
787
|
+
queue = new OperationQueue();
|
|
788
|
+
running = false;
|
|
789
|
+
beforeObservers = [];
|
|
790
|
+
afterObservers = [];
|
|
791
|
+
constructor(subject, process, options = {}) {
|
|
512
792
|
this.subject = subject;
|
|
513
|
-
if (stateName) {
|
|
514
|
-
this.currentState = process.getState(stateName);
|
|
515
|
-
} else {
|
|
516
|
-
this.currentState = process.getInitialState();
|
|
517
|
-
}
|
|
518
|
-
this.transitionSelector = transitionSelector ?? new OneOrNoneActiveTransition();
|
|
519
793
|
this.process = process;
|
|
520
|
-
this.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
794
|
+
this.currentState = options.initialStateName ? process.getState(options.initialStateName) : process.getInitialState();
|
|
795
|
+
this.transitionSelector = options.transitionSelector ?? new OneOrNoneActiveTransition();
|
|
796
|
+
this.mutex = options.mutex ?? new NullMutex();
|
|
797
|
+
this.autoreleaseLock = options.autoreleaseLock ?? true;
|
|
524
798
|
}
|
|
799
|
+
// --- public getters ---
|
|
525
800
|
getCurrentState() {
|
|
526
801
|
return this.currentState;
|
|
527
802
|
}
|
|
@@ -531,147 +806,32 @@ var Statemachine = class {
|
|
|
531
806
|
getSubject() {
|
|
532
807
|
return this.subject;
|
|
533
808
|
}
|
|
534
|
-
|
|
535
|
-
return this.
|
|
536
|
-
}
|
|
537
|
-
getCurrentContext() {
|
|
538
|
-
return this.currentContext;
|
|
539
|
-
}
|
|
540
|
-
async doCheckTransitions(context, event, automaticVisited) {
|
|
541
|
-
try {
|
|
542
|
-
const transitions = this.currentState.getTransitions();
|
|
543
|
-
const activeTransitions = await ActiveTransitionFilter.filter(
|
|
544
|
-
transitions,
|
|
545
|
-
this.subject,
|
|
546
|
-
context,
|
|
547
|
-
event
|
|
548
|
-
);
|
|
549
|
-
this.selectedTransition = this.transitionSelector.selectTransition(activeTransitions);
|
|
550
|
-
if (this.selectedTransition) {
|
|
551
|
-
const targetState = this.selectedTransition.getTargetState();
|
|
552
|
-
if (this.selectedTransition.getEventName() === null) {
|
|
553
|
-
if (!automaticVisited) {
|
|
554
|
-
automaticVisited = /* @__PURE__ */ new Set();
|
|
555
|
-
}
|
|
556
|
-
automaticVisited.add(this.currentState);
|
|
557
|
-
if (automaticVisited.has(targetState)) {
|
|
558
|
-
throw new Error(
|
|
559
|
-
`Automatic transition cycle detected: state "${targetState.getName()}" was already visited \u2014 this would cause infinite recursion`
|
|
560
|
-
);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
if (this.currentState !== targetState) {
|
|
564
|
-
this.lastState = this.currentState;
|
|
565
|
-
this.currentState = targetState;
|
|
566
|
-
this.currentContext = context;
|
|
567
|
-
this.currentEvent = event ?? null;
|
|
568
|
-
try {
|
|
569
|
-
await this.notify();
|
|
570
|
-
} finally {
|
|
571
|
-
this.currentContext = null;
|
|
572
|
-
this.currentEvent = null;
|
|
573
|
-
this.selectedTransition = null;
|
|
574
|
-
this.lastState = null;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
await this.doCheckTransitions(context, void 0, automaticVisited);
|
|
578
|
-
}
|
|
579
|
-
} catch (error) {
|
|
580
|
-
let message = `Exception was thrown when doing a transition from current state "${this.currentState.getName()}"`;
|
|
581
|
-
if (this.currentEvent) {
|
|
582
|
-
message += ` with event "${this.currentEvent.getName()}"`;
|
|
583
|
-
}
|
|
584
|
-
const named = this.subject;
|
|
585
|
-
if (named && typeof named.getName === "function") {
|
|
586
|
-
message += ` for "${named.getName()}"`;
|
|
587
|
-
}
|
|
588
|
-
throw new Error(message, { cause: error });
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
async onDispatcherReady() {
|
|
592
|
-
if (this.dispatcher && this.dispatcher.isReady()) {
|
|
593
|
-
const context = this.currentContext;
|
|
594
|
-
const event = this.currentEvent ?? void 0;
|
|
595
|
-
this.dispatcher = null;
|
|
596
|
-
this.currentContext = null;
|
|
597
|
-
this.currentEvent = null;
|
|
598
|
-
try {
|
|
599
|
-
await this.doCheckTransitions(context, event);
|
|
600
|
-
} finally {
|
|
601
|
-
if (this.autoreleaseLock && this.isLockAcquired()) {
|
|
602
|
-
await this.releaseLock();
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
809
|
+
getProcess() {
|
|
810
|
+
return this.process;
|
|
606
811
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
}
|
|
611
|
-
if (this.currentState.hasEvent(name)) {
|
|
612
|
-
await this.acquireLockOrThrowException();
|
|
613
|
-
try {
|
|
614
|
-
this.dispatcher = dispatcher;
|
|
615
|
-
this.currentContext = context ?? /* @__PURE__ */ new Map();
|
|
616
|
-
this.currentEvent = this.currentState.getEvent(name);
|
|
617
|
-
dispatcher.dispatch(
|
|
618
|
-
this.currentEvent,
|
|
619
|
-
[this.subject, this.currentContext],
|
|
620
|
-
{ invoke: () => this.onDispatcherReady() }
|
|
621
|
-
);
|
|
622
|
-
} catch (error) {
|
|
623
|
-
this.dispatcher = null;
|
|
624
|
-
this.currentContext = null;
|
|
625
|
-
this.currentEvent = null;
|
|
626
|
-
if (this.autoreleaseLock) {
|
|
627
|
-
await this.releaseLock();
|
|
628
|
-
}
|
|
629
|
-
throw error;
|
|
630
|
-
}
|
|
631
|
-
} else {
|
|
632
|
-
throw new WrongEventForStateError(this.currentState.getName(), name);
|
|
633
|
-
}
|
|
812
|
+
// --- public observer attach/detach ---
|
|
813
|
+
attachBefore(observer) {
|
|
814
|
+
this.beforeObservers.push(observer);
|
|
634
815
|
}
|
|
635
|
-
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
await this.dispatchEvent(dispatcher, name, context);
|
|
639
|
-
await dispatcher.invoke();
|
|
640
|
-
} finally {
|
|
641
|
-
this.dispatcher = null;
|
|
642
|
-
this.currentContext = null;
|
|
643
|
-
this.currentEvent = null;
|
|
644
|
-
if (this.autoreleaseLock && this.isLockAcquired()) {
|
|
645
|
-
await this.releaseLock();
|
|
646
|
-
}
|
|
647
|
-
}
|
|
816
|
+
detachBefore(observer) {
|
|
817
|
+
const idx = this.beforeObservers.indexOf(observer);
|
|
818
|
+
if (idx >= 0) this.beforeObservers.splice(idx, 1);
|
|
648
819
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const ctx = context ?? /* @__PURE__ */ new Map();
|
|
652
|
-
try {
|
|
653
|
-
await this.doCheckTransitions(ctx);
|
|
654
|
-
} finally {
|
|
655
|
-
if (this.autoreleaseLock && this.isLockAcquired()) {
|
|
656
|
-
await this.releaseLock();
|
|
657
|
-
}
|
|
658
|
-
}
|
|
820
|
+
getBeforeObservers() {
|
|
821
|
+
return this.beforeObservers;
|
|
659
822
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
throw new LockCanNotBeAcquiredError("Lock can not be acquired!");
|
|
663
|
-
}
|
|
823
|
+
attachAfter(observer) {
|
|
824
|
+
this.afterObservers.push(observer);
|
|
664
825
|
}
|
|
665
|
-
|
|
666
|
-
|
|
826
|
+
detachAfter(observer) {
|
|
827
|
+
const idx = this.afterObservers.indexOf(observer);
|
|
828
|
+
if (idx >= 0) this.afterObservers.splice(idx, 1);
|
|
667
829
|
}
|
|
668
|
-
|
|
669
|
-
this.
|
|
830
|
+
getAfterObservers() {
|
|
831
|
+
return this.afterObservers;
|
|
670
832
|
}
|
|
833
|
+
// --- public locking ---
|
|
671
834
|
async acquireLock() {
|
|
672
|
-
if (this.mutex.isAcquired()) {
|
|
673
|
-
return true;
|
|
674
|
-
}
|
|
675
835
|
return this.mutex.acquireLock();
|
|
676
836
|
}
|
|
677
837
|
async releaseLock() {
|
|
@@ -680,19 +840,179 @@ var Statemachine = class {
|
|
|
680
840
|
isLockAcquired() {
|
|
681
841
|
return this.mutex.isAcquired();
|
|
682
842
|
}
|
|
683
|
-
|
|
684
|
-
this.
|
|
843
|
+
isAutoreleaseLock() {
|
|
844
|
+
return this.autoreleaseLock;
|
|
685
845
|
}
|
|
686
|
-
|
|
687
|
-
this.
|
|
846
|
+
setAutoreleaseLock(autorelease) {
|
|
847
|
+
this.autoreleaseLock = autorelease;
|
|
688
848
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
849
|
+
// --- public top-level operations ---
|
|
850
|
+
triggerEvent(name, context) {
|
|
851
|
+
return new Promise((resolve, reject) => {
|
|
852
|
+
this.queue.enqueue({
|
|
853
|
+
kind: "triggerEvent",
|
|
854
|
+
eventName: name,
|
|
855
|
+
context: context ?? /* @__PURE__ */ new Map(),
|
|
856
|
+
resolve,
|
|
857
|
+
reject
|
|
858
|
+
});
|
|
859
|
+
void this.runIfIdle();
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
checkTransitions(context) {
|
|
863
|
+
return new Promise((resolve, reject) => {
|
|
864
|
+
this.queue.enqueue({
|
|
865
|
+
kind: "checkTransitions",
|
|
866
|
+
eventName: null,
|
|
867
|
+
context: context ?? /* @__PURE__ */ new Map(),
|
|
868
|
+
resolve,
|
|
869
|
+
reject
|
|
870
|
+
});
|
|
871
|
+
void this.runIfIdle();
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
// --- internal runner ---
|
|
875
|
+
async runIfIdle() {
|
|
876
|
+
if (this.running) return;
|
|
877
|
+
this.running = true;
|
|
878
|
+
try {
|
|
879
|
+
while (!this.queue.isEmpty()) {
|
|
880
|
+
const op = this.queue.dequeue();
|
|
881
|
+
await this.runOperation(op);
|
|
882
|
+
}
|
|
883
|
+
} finally {
|
|
884
|
+
this.running = false;
|
|
692
885
|
}
|
|
693
886
|
}
|
|
694
|
-
|
|
695
|
-
|
|
887
|
+
async runOperation(op) {
|
|
888
|
+
let acquiredHere = false;
|
|
889
|
+
try {
|
|
890
|
+
if (!this.mutex.isAcquired()) {
|
|
891
|
+
if (!await this.mutex.acquireLock()) {
|
|
892
|
+
throw new LockCanNotBeAcquiredError("Lock can not be acquired!");
|
|
893
|
+
}
|
|
894
|
+
acquiredHere = true;
|
|
895
|
+
}
|
|
896
|
+
const event = op.kind === "triggerEvent" ? this.resolveEvent(op.eventName) : null;
|
|
897
|
+
await this.processOperation(event, op.context);
|
|
898
|
+
op.resolve();
|
|
899
|
+
} catch (err) {
|
|
900
|
+
op.reject(err);
|
|
901
|
+
} finally {
|
|
902
|
+
if (acquiredHere && this.autoreleaseLock) {
|
|
903
|
+
try {
|
|
904
|
+
await this.mutex.releaseLock();
|
|
905
|
+
} catch {
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
resolveEvent(name) {
|
|
911
|
+
if (!this.currentState.hasEvent(name)) {
|
|
912
|
+
throw new WrongEventForStateError(this.currentState.getName(), name);
|
|
913
|
+
}
|
|
914
|
+
return this.currentState.getEvent(name);
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Drive transitions starting from the current state, following automatic
|
|
918
|
+
* transitions until quiescent. The first iteration may use the supplied
|
|
919
|
+
* event; subsequent iterations are automatic.
|
|
920
|
+
*/
|
|
921
|
+
async processOperation(initialEvent, context) {
|
|
922
|
+
let event = initialEvent;
|
|
923
|
+
const automaticVisited = /* @__PURE__ */ new Set();
|
|
924
|
+
if (event) {
|
|
925
|
+
const dispatcher = new Dispatcher();
|
|
926
|
+
dispatcher.dispatch(event, [this.subject, context]);
|
|
927
|
+
await dispatcher.invoke();
|
|
928
|
+
}
|
|
929
|
+
while (true) {
|
|
930
|
+
const transitions = this.currentState.getTransitions();
|
|
931
|
+
const active = await ActiveTransitionFilter.filter(
|
|
932
|
+
transitions,
|
|
933
|
+
this.subject,
|
|
934
|
+
context,
|
|
935
|
+
event ?? void 0
|
|
936
|
+
);
|
|
937
|
+
const selected = this.transitionSelector.selectTransition(
|
|
938
|
+
active
|
|
939
|
+
);
|
|
940
|
+
if (!selected) {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const target = selected.getTargetState();
|
|
944
|
+
if (selected.getEventName() === null) {
|
|
945
|
+
automaticVisited.add(this.currentState);
|
|
946
|
+
if (automaticVisited.has(target)) {
|
|
947
|
+
throw new AutomaticTransitionCycleError(
|
|
948
|
+
target.getName(),
|
|
949
|
+
Array.from(automaticVisited).map((s) => s.getName())
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (this.currentState !== target) {
|
|
954
|
+
const proposedFrame = Object.freeze({
|
|
955
|
+
fromState: this.currentState,
|
|
956
|
+
toState: target,
|
|
957
|
+
transition: selected,
|
|
958
|
+
event,
|
|
959
|
+
condition: selected.getCondition(),
|
|
960
|
+
context: this.readonlyContext(context),
|
|
961
|
+
timestamp: Date.now(),
|
|
962
|
+
machineName: this.process.getName()
|
|
963
|
+
});
|
|
964
|
+
for (const observer of this.beforeObservers) {
|
|
965
|
+
await observer.notify(proposedFrame);
|
|
966
|
+
}
|
|
967
|
+
const fromState = this.currentState;
|
|
968
|
+
this.lastState = fromState;
|
|
969
|
+
this.currentState = target;
|
|
970
|
+
const committedFrame = Object.freeze({
|
|
971
|
+
fromState,
|
|
972
|
+
toState: target,
|
|
973
|
+
transition: selected,
|
|
974
|
+
event,
|
|
975
|
+
condition: selected.getCondition(),
|
|
976
|
+
context: this.readonlyContext(context),
|
|
977
|
+
timestamp: proposedFrame.timestamp,
|
|
978
|
+
machineName: this.process.getName()
|
|
979
|
+
});
|
|
980
|
+
const enqueueCtx = {
|
|
981
|
+
enqueue: (chainedEventName, chainedCtx) => {
|
|
982
|
+
this.queue.enqueue({
|
|
983
|
+
kind: "triggerEvent",
|
|
984
|
+
eventName: chainedEventName,
|
|
985
|
+
context: chainedCtx ?? /* @__PURE__ */ new Map(),
|
|
986
|
+
resolve: () => {
|
|
987
|
+
},
|
|
988
|
+
reject: () => {
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
const errors = [];
|
|
994
|
+
for (const observer of this.afterObservers) {
|
|
995
|
+
try {
|
|
996
|
+
await observer.notify(committedFrame, enqueueCtx);
|
|
997
|
+
} catch (err) {
|
|
998
|
+
errors.push(err);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (errors.length === 1) {
|
|
1002
|
+
throw errors[0];
|
|
1003
|
+
}
|
|
1004
|
+
if (errors.length > 1) {
|
|
1005
|
+
throw new AggregateError(
|
|
1006
|
+
errors,
|
|
1007
|
+
`${errors.length} after-transition observer(s) threw`
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
event = null;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
readonlyContext(ctx) {
|
|
1015
|
+
return new Map(ctx);
|
|
696
1016
|
}
|
|
697
1017
|
};
|
|
698
1018
|
|
|
@@ -847,62 +1167,36 @@ var CallbackObserver = class {
|
|
|
847
1167
|
};
|
|
848
1168
|
|
|
849
1169
|
// src/observer/StatefulStatusChanger.ts
|
|
850
|
-
function isStatemachine(obj) {
|
|
851
|
-
return typeof obj === "object" && obj !== null && "getCurrentState" in obj && "getSubject" in obj;
|
|
852
|
-
}
|
|
853
|
-
function isStateful(obj) {
|
|
854
|
-
return typeof obj === "object" && obj !== null && "setCurrentStateName" in obj && typeof obj.setCurrentStateName === "function";
|
|
855
|
-
}
|
|
856
1170
|
var StatefulStatusChanger = class {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
}
|
|
864
|
-
}
|
|
1171
|
+
subject;
|
|
1172
|
+
constructor(subject) {
|
|
1173
|
+
this.subject = subject;
|
|
1174
|
+
}
|
|
1175
|
+
notify(frame) {
|
|
1176
|
+
this.subject.setCurrentStateName(frame.toState.getName());
|
|
865
1177
|
}
|
|
866
1178
|
};
|
|
867
1179
|
|
|
868
1180
|
// src/observer/OnEnterObserver.ts
|
|
869
|
-
function isStatemachine2(obj) {
|
|
870
|
-
return typeof obj === "object" && obj !== null && "getCurrentState" in obj && "triggerEvent" in obj;
|
|
871
|
-
}
|
|
872
1181
|
var OnEnterObserver = class _OnEnterObserver {
|
|
873
1182
|
static DEFAULT_EVENT_NAME = "onEnter";
|
|
874
1183
|
eventName;
|
|
875
1184
|
constructor(eventName = _OnEnterObserver.DEFAULT_EVENT_NAME) {
|
|
876
1185
|
this.eventName = eventName;
|
|
877
1186
|
}
|
|
878
|
-
|
|
879
|
-
if (
|
|
880
|
-
|
|
881
|
-
const autorelease = sm.isAutoreleaseLock();
|
|
882
|
-
sm.setAutoreleaseLock(false);
|
|
883
|
-
try {
|
|
884
|
-
await sm.triggerEvent(
|
|
885
|
-
this.eventName,
|
|
886
|
-
sm.getCurrentContext() ?? void 0
|
|
887
|
-
);
|
|
888
|
-
} finally {
|
|
889
|
-
sm.setAutoreleaseLock(autorelease);
|
|
890
|
-
}
|
|
1187
|
+
notify(frame, ctx) {
|
|
1188
|
+
if (frame.toState.hasEvent(this.eventName)) {
|
|
1189
|
+
ctx.enqueue(this.eventName, new Map(frame.context));
|
|
891
1190
|
}
|
|
892
1191
|
}
|
|
893
1192
|
};
|
|
894
1193
|
|
|
895
1194
|
// src/observer/TransitionLogger.ts
|
|
896
|
-
function isStatemachine3(obj) {
|
|
897
|
-
return typeof obj === "object" && obj !== null && "getCurrentState" in obj && "getSubject" in obj;
|
|
898
|
-
}
|
|
899
1195
|
function isNamed(obj) {
|
|
900
1196
|
return typeof obj === "object" && obj !== null && "getName" in obj && typeof obj.getName === "function";
|
|
901
1197
|
}
|
|
902
|
-
function
|
|
903
|
-
if (isNamed(obj))
|
|
904
|
-
return obj.getName();
|
|
905
|
-
}
|
|
1198
|
+
function asString(obj) {
|
|
1199
|
+
if (isNamed(obj)) return obj.getName();
|
|
906
1200
|
return String(obj);
|
|
907
1201
|
}
|
|
908
1202
|
var TransitionLogger = class {
|
|
@@ -912,40 +1206,23 @@ var TransitionLogger = class {
|
|
|
912
1206
|
this.logger = logger;
|
|
913
1207
|
this.loggerLevel = loggerLevel;
|
|
914
1208
|
}
|
|
915
|
-
|
|
916
|
-
if (!isStatemachine3(subject)) {
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
const context = {};
|
|
920
|
-
context["subject"] = subject.getSubject();
|
|
921
|
-
context["currentState"] = subject.getCurrentState();
|
|
922
|
-
context["lastState"] = subject.getLastState();
|
|
923
|
-
context["transition"] = subject.getSelectedTransition();
|
|
1209
|
+
notify(frame) {
|
|
924
1210
|
let message = "Transition";
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
if (
|
|
929
|
-
message +=
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if (eventName) {
|
|
941
|
-
message += ` event "${eventName}"`;
|
|
942
|
-
}
|
|
943
|
-
if (condition) {
|
|
944
|
-
message += ` condition "${condition}"`;
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
this.logger.log(this.loggerLevel, message, context);
|
|
1211
|
+
message += ` from "${asString(frame.fromState)}" to "${asString(frame.toState)}"`;
|
|
1212
|
+
const eventName = frame.event ? frame.event.getName() : null;
|
|
1213
|
+
const conditionName = frame.condition ? frame.condition.getName() : null;
|
|
1214
|
+
if (eventName || conditionName) {
|
|
1215
|
+
message += " with";
|
|
1216
|
+
if (eventName) message += ` event "${eventName}"`;
|
|
1217
|
+
if (conditionName) message += ` condition "${conditionName}"`;
|
|
1218
|
+
}
|
|
1219
|
+
this.logger.log(this.loggerLevel, message, {
|
|
1220
|
+
fromState: frame.fromState,
|
|
1221
|
+
toState: frame.toState,
|
|
1222
|
+
event: frame.event,
|
|
1223
|
+
transition: frame.transition,
|
|
1224
|
+
machineName: frame.machineName
|
|
1225
|
+
});
|
|
949
1226
|
}
|
|
950
1227
|
};
|
|
951
1228
|
|
|
@@ -1111,7 +1388,8 @@ var MutexFactory = class {
|
|
|
1111
1388
|
var Factory = class {
|
|
1112
1389
|
processDetector;
|
|
1113
1390
|
stateNameDetector;
|
|
1114
|
-
|
|
1391
|
+
beforeObservers = /* @__PURE__ */ new Set();
|
|
1392
|
+
afterObservers = /* @__PURE__ */ new Set();
|
|
1115
1393
|
transitionSelector = null;
|
|
1116
1394
|
mutexFactory = null;
|
|
1117
1395
|
constructor(processDetector, stateNameDetector) {
|
|
@@ -1124,30 +1402,30 @@ var Factory = class {
|
|
|
1124
1402
|
setTransitionSelector(selector) {
|
|
1125
1403
|
this.transitionSelector = selector;
|
|
1126
1404
|
}
|
|
1127
|
-
|
|
1128
|
-
this.
|
|
1405
|
+
attachBeforeObserver(observer) {
|
|
1406
|
+
this.beforeObservers.add(observer);
|
|
1129
1407
|
}
|
|
1130
|
-
|
|
1131
|
-
this.
|
|
1408
|
+
detachBeforeObserver(observer) {
|
|
1409
|
+
this.beforeObservers.delete(observer);
|
|
1132
1410
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1411
|
+
attachAfterObserver(observer) {
|
|
1412
|
+
this.afterObservers.add(observer);
|
|
1413
|
+
}
|
|
1414
|
+
detachAfterObserver(observer) {
|
|
1415
|
+
this.afterObservers.delete(observer);
|
|
1135
1416
|
}
|
|
1136
1417
|
async createStatemachine(subject) {
|
|
1137
1418
|
const process = this.processDetector.detectProcess(subject);
|
|
1138
|
-
const stateName = this.stateNameDetector ? this.stateNameDetector.detectCurrentStateName(subject) :
|
|
1139
|
-
const mutex = this.mutexFactory ? await this.mutexFactory.createMutex(subject) :
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
);
|
|
1147
|
-
|
|
1148
|
-
statemachine.attach(observer);
|
|
1149
|
-
}
|
|
1150
|
-
return statemachine;
|
|
1419
|
+
const stateName = this.stateNameDetector ? this.stateNameDetector.detectCurrentStateName(subject) : void 0;
|
|
1420
|
+
const mutex = this.mutexFactory ? await this.mutexFactory.createMutex(subject) : void 0;
|
|
1421
|
+
const sm = new Statemachine(subject, process, {
|
|
1422
|
+
initialStateName: stateName ?? void 0,
|
|
1423
|
+
transitionSelector: this.transitionSelector ?? void 0,
|
|
1424
|
+
mutex: mutex ?? void 0
|
|
1425
|
+
});
|
|
1426
|
+
for (const o of this.beforeObservers) sm.attachBefore(o);
|
|
1427
|
+
for (const o of this.afterObservers) sm.attachAfter(o);
|
|
1428
|
+
return sm;
|
|
1151
1429
|
}
|
|
1152
1430
|
};
|
|
1153
1431
|
|
|
@@ -1162,6 +1440,21 @@ var SingleProcessDetector = class {
|
|
|
1162
1440
|
}
|
|
1163
1441
|
};
|
|
1164
1442
|
|
|
1443
|
+
// src/error/ProcessNotFoundError.ts
|
|
1444
|
+
var ProcessNotFoundError = class extends FinitaError {
|
|
1445
|
+
code = "processNotFound";
|
|
1446
|
+
processName;
|
|
1447
|
+
availableProcesses;
|
|
1448
|
+
constructor(processName, availableProcesses) {
|
|
1449
|
+
const list = Array.from(availableProcesses);
|
|
1450
|
+
const display = list.length > 0 ? list.map((n) => `"${n}"`).join(", ") : "(none)";
|
|
1451
|
+
super(`Process "${processName}" not found. Available: ${display}`);
|
|
1452
|
+
this.name = "ProcessNotFoundError";
|
|
1453
|
+
this.processName = processName;
|
|
1454
|
+
this.availableProcesses = Object.freeze([...list]);
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1165
1458
|
// src/factory/AbstractNamedProcessDetector.ts
|
|
1166
1459
|
var AbstractNamedProcessDetector = class {
|
|
1167
1460
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -1175,74 +1468,39 @@ var AbstractNamedProcessDetector = class {
|
|
|
1175
1468
|
const name = this.detectProcessName(subject);
|
|
1176
1469
|
const process = this.processes.get(name);
|
|
1177
1470
|
if (!process) {
|
|
1178
|
-
throw new
|
|
1471
|
+
throw new ProcessNotFoundError(name, this.processes.keys());
|
|
1179
1472
|
}
|
|
1180
1473
|
return process;
|
|
1181
1474
|
}
|
|
1182
1475
|
};
|
|
1183
1476
|
|
|
1477
|
+
// src/error/InvalidSubjectError.ts
|
|
1478
|
+
var InvalidSubjectError = class extends FinitaError {
|
|
1479
|
+
code = "invalidSubject";
|
|
1480
|
+
expectedInterface;
|
|
1481
|
+
missingMembers;
|
|
1482
|
+
constructor(expectedInterface, missingMembers) {
|
|
1483
|
+
const members = Array.from(missingMembers);
|
|
1484
|
+
const memberList = members.map((m) => `"${m}"`).join(", ");
|
|
1485
|
+
super(
|
|
1486
|
+
`Subject does not satisfy ${expectedInterface}; missing member(s): ${memberList || "(unknown)"}`
|
|
1487
|
+
);
|
|
1488
|
+
this.name = "InvalidSubjectError";
|
|
1489
|
+
this.expectedInterface = expectedInterface;
|
|
1490
|
+
this.missingMembers = Object.freeze([...members]);
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
|
|
1184
1494
|
// src/factory/StatefulStateNameDetector.ts
|
|
1185
|
-
function
|
|
1495
|
+
function isStateful(obj) {
|
|
1186
1496
|
return typeof obj === "object" && obj !== null && "getCurrentStateName" in obj && typeof obj.getCurrentStateName === "function";
|
|
1187
1497
|
}
|
|
1188
1498
|
var StatefulStateNameDetector = class {
|
|
1189
1499
|
detectCurrentStateName(subject) {
|
|
1190
|
-
if (
|
|
1500
|
+
if (isStateful(subject)) {
|
|
1191
1501
|
return subject.getCurrentStateName();
|
|
1192
1502
|
}
|
|
1193
|
-
throw new
|
|
1194
|
-
}
|
|
1195
|
-
};
|
|
1196
|
-
|
|
1197
|
-
// src/util/SetupHelper.ts
|
|
1198
|
-
var SetupHelper = class {
|
|
1199
|
-
stateCollection;
|
|
1200
|
-
constructor(stateCollection) {
|
|
1201
|
-
this.stateCollection = stateCollection;
|
|
1202
|
-
}
|
|
1203
|
-
findOrCreateState(name) {
|
|
1204
|
-
if (!this.stateCollection.hasState(name)) {
|
|
1205
|
-
this.stateCollection.addState(new State(name));
|
|
1206
|
-
}
|
|
1207
|
-
return this.stateCollection.getState(name);
|
|
1208
|
-
}
|
|
1209
|
-
findTransition(sourceState, targetState, eventName = null, condition = null) {
|
|
1210
|
-
const conditionName = condition ? condition.getName() : null;
|
|
1211
|
-
for (const transition of sourceState.getTransitions()) {
|
|
1212
|
-
const hasSameTargetState = transition.getTargetState() === targetState;
|
|
1213
|
-
const hasSameCondition = transition.getConditionName() === conditionName;
|
|
1214
|
-
const hasSameEvent = transition.getEventName() === eventName;
|
|
1215
|
-
if (hasSameTargetState && hasSameCondition && hasSameEvent) {
|
|
1216
|
-
return transition;
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
return null;
|
|
1220
|
-
}
|
|
1221
|
-
findOrCreateTransition(sourceStateName, targetStateName, eventName = null, condition = null) {
|
|
1222
|
-
const sourceState = this.findOrCreateState(sourceStateName);
|
|
1223
|
-
const targetState = this.findOrCreateState(targetStateName);
|
|
1224
|
-
let transition = this.findTransition(
|
|
1225
|
-
sourceState,
|
|
1226
|
-
targetState,
|
|
1227
|
-
eventName,
|
|
1228
|
-
condition
|
|
1229
|
-
);
|
|
1230
|
-
if (!transition) {
|
|
1231
|
-
transition = new Transition(targetState, eventName, condition);
|
|
1232
|
-
sourceState.addTransition(transition);
|
|
1233
|
-
}
|
|
1234
|
-
return transition;
|
|
1235
|
-
}
|
|
1236
|
-
findOrCreateEvent(sourceStateName, eventName) {
|
|
1237
|
-
const sourceState = this.findOrCreateState(sourceStateName);
|
|
1238
|
-
return sourceState.getEvent(eventName);
|
|
1239
|
-
}
|
|
1240
|
-
addCommand(sourceStateName, eventName, command) {
|
|
1241
|
-
this.findOrCreateEvent(sourceStateName, eventName).attach(command);
|
|
1242
|
-
}
|
|
1243
|
-
addCommandAndSelfTransition(sourceStateName, eventName, command) {
|
|
1244
|
-
this.addCommand(sourceStateName, eventName, command);
|
|
1245
|
-
this.findOrCreateTransition(sourceStateName, sourceStateName, eventName);
|
|
1503
|
+
throw new InvalidSubjectError("StatefulInterface", ["getCurrentStateName"]);
|
|
1246
1504
|
}
|
|
1247
1505
|
};
|
|
1248
1506
|
|
|
@@ -1262,7 +1520,7 @@ function toMermaidId(name) {
|
|
|
1262
1520
|
function escapeMermaidLabel(str) {
|
|
1263
1521
|
return str.replace(/"/g, "#quot;");
|
|
1264
1522
|
}
|
|
1265
|
-
function
|
|
1523
|
+
function convertToString(obj) {
|
|
1266
1524
|
if (isNamed2(obj)) {
|
|
1267
1525
|
return obj.getName();
|
|
1268
1526
|
}
|
|
@@ -1271,6 +1529,7 @@ function convertToString2(obj) {
|
|
|
1271
1529
|
var GraphBuilder = class {
|
|
1272
1530
|
nodes = /* @__PURE__ */ new Map();
|
|
1273
1531
|
edges = [];
|
|
1532
|
+
statesWithEdges = /* @__PURE__ */ new Set();
|
|
1274
1533
|
getOrCreateNode(state) {
|
|
1275
1534
|
const name = state.getName();
|
|
1276
1535
|
let node = this.nodes.get(name);
|
|
@@ -1288,7 +1547,7 @@ var GraphBuilder = class {
|
|
|
1288
1547
|
const event = state.getEvent(eventName);
|
|
1289
1548
|
const observerNames = [];
|
|
1290
1549
|
for (const observer of event.getObservers()) {
|
|
1291
|
-
observerNames.push(
|
|
1550
|
+
observerNames.push(convertToString(observer));
|
|
1292
1551
|
}
|
|
1293
1552
|
if (observerNames.length > 0) {
|
|
1294
1553
|
parts.push(`C: ${observerNames.join(", ")}`);
|
|
@@ -1303,6 +1562,9 @@ var GraphBuilder = class {
|
|
|
1303
1562
|
}
|
|
1304
1563
|
addState(state) {
|
|
1305
1564
|
this.getOrCreateNode(state);
|
|
1565
|
+
const name = state.getName();
|
|
1566
|
+
if (this.statesWithEdges.has(name)) return;
|
|
1567
|
+
this.statesWithEdges.add(name);
|
|
1306
1568
|
for (const transition of state.getTransitions()) {
|
|
1307
1569
|
const sourceNode = this.getOrCreateNode(state);
|
|
1308
1570
|
const targetNode = this.getOrCreateNode(transition.getTargetState());
|
|
@@ -1381,19 +1643,24 @@ var GraphBuilder = class {
|
|
|
1381
1643
|
0 && (module.exports = {
|
|
1382
1644
|
AbstractNamedProcessDetector,
|
|
1383
1645
|
ActiveTransitionFilter,
|
|
1646
|
+
AmbiguousTransitionError,
|
|
1384
1647
|
AndComposite,
|
|
1648
|
+
AutomaticTransitionCycleError,
|
|
1385
1649
|
CallbackCondition,
|
|
1386
1650
|
CallbackObserver,
|
|
1387
1651
|
Contradiction,
|
|
1388
|
-
Dispatcher,
|
|
1389
1652
|
DuplicateStateError,
|
|
1653
|
+
DuplicateTransitionError,
|
|
1390
1654
|
Event,
|
|
1391
1655
|
Factory,
|
|
1392
1656
|
FilterStateByEvent,
|
|
1393
1657
|
FilterStateByFinalState,
|
|
1394
1658
|
FilterStateByTransition,
|
|
1395
1659
|
FilterTransitionByEvent,
|
|
1660
|
+
FinitaError,
|
|
1396
1661
|
GraphBuilder,
|
|
1662
|
+
GraphValidationError,
|
|
1663
|
+
InvalidSubjectError,
|
|
1397
1664
|
LockAdapterMutex,
|
|
1398
1665
|
LockCanNotBeAcquiredError,
|
|
1399
1666
|
MutexFactory,
|
|
@@ -1403,12 +1670,14 @@ var GraphBuilder = class {
|
|
|
1403
1670
|
OneOrNoneActiveTransition,
|
|
1404
1671
|
OrComposite,
|
|
1405
1672
|
Process,
|
|
1673
|
+
ProcessBuilder,
|
|
1674
|
+
ProcessFinalizedError,
|
|
1675
|
+
ProcessNotFoundError,
|
|
1406
1676
|
ScoreTransition,
|
|
1407
|
-
SetupHelper,
|
|
1408
1677
|
SingleProcessDetector,
|
|
1409
1678
|
State,
|
|
1410
|
-
|
|
1411
|
-
|
|
1679
|
+
StateEventNotFoundError,
|
|
1680
|
+
StateNotFoundError,
|
|
1412
1681
|
StatefulStateNameDetector,
|
|
1413
1682
|
StatefulStatusChanger,
|
|
1414
1683
|
Statemachine,
|