@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.js
CHANGED
|
@@ -52,37 +52,148 @@ var Event = class {
|
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
// src/
|
|
56
|
-
var
|
|
55
|
+
// src/internal/InternalConstruction.ts
|
|
56
|
+
var INTERNAL_CONSTRUCTION_KEY = /* @__PURE__ */ Symbol(
|
|
57
|
+
"@camcima/finita/InternalConstruction"
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// src/error/FinitaError.ts
|
|
61
|
+
var FinitaError = class _FinitaError extends Error {
|
|
62
|
+
constructor(message) {
|
|
63
|
+
super(message);
|
|
64
|
+
if (new.target === _FinitaError) {
|
|
65
|
+
throw new TypeError(
|
|
66
|
+
"FinitaError is abstract and cannot be instantiated directly"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/error/StateNotFoundError.ts
|
|
73
|
+
var StateNotFoundError = class extends FinitaError {
|
|
74
|
+
code = "stateNotFound";
|
|
75
|
+
stateName;
|
|
76
|
+
availableStates;
|
|
77
|
+
constructor(stateName, availableStates) {
|
|
78
|
+
const list = Array.from(availableStates);
|
|
79
|
+
const display = list.length > 0 ? list.map((n) => `"${n}"`).join(", ") : "(none)";
|
|
80
|
+
super(`State "${stateName}" not found. Available: ${display}`);
|
|
81
|
+
this.name = "StateNotFoundError";
|
|
82
|
+
this.stateName = stateName;
|
|
83
|
+
this.availableStates = Object.freeze([...list]);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// src/StateCollection.ts
|
|
88
|
+
var StateCollection = class {
|
|
89
|
+
states;
|
|
90
|
+
constructor(states) {
|
|
91
|
+
const map = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const s of states) {
|
|
93
|
+
map.set(s.getName(), s);
|
|
94
|
+
}
|
|
95
|
+
this.states = map;
|
|
96
|
+
}
|
|
97
|
+
getStates() {
|
|
98
|
+
return this.states.values();
|
|
99
|
+
}
|
|
100
|
+
getState(name) {
|
|
101
|
+
const s = this.states.get(name);
|
|
102
|
+
if (!s) {
|
|
103
|
+
throw new StateNotFoundError(name, this.states.keys());
|
|
104
|
+
}
|
|
105
|
+
return s;
|
|
106
|
+
}
|
|
107
|
+
hasState(name) {
|
|
108
|
+
return this.states.has(name);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/Process.ts
|
|
113
|
+
var Process = class {
|
|
57
114
|
name;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
115
|
+
initialState;
|
|
116
|
+
states;
|
|
117
|
+
constructor(key, name, initialState, states) {
|
|
118
|
+
if (key !== INTERNAL_CONSTRUCTION_KEY) {
|
|
119
|
+
throw new Error("Process is not user-constructible; use ProcessBuilder.");
|
|
120
|
+
}
|
|
62
121
|
this.name = name;
|
|
122
|
+
this.initialState = initialState;
|
|
123
|
+
this.states = new StateCollection(states);
|
|
124
|
+
Object.freeze(this);
|
|
63
125
|
}
|
|
64
126
|
getName() {
|
|
65
127
|
return this.name;
|
|
66
128
|
}
|
|
67
|
-
|
|
68
|
-
return this.
|
|
129
|
+
getInitialState() {
|
|
130
|
+
return this.initialState;
|
|
131
|
+
}
|
|
132
|
+
getStates() {
|
|
133
|
+
return this.states.getStates();
|
|
69
134
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
135
|
+
getState(name) {
|
|
136
|
+
return this.states.getState(name);
|
|
137
|
+
}
|
|
138
|
+
hasState(name) {
|
|
139
|
+
return this.states.hasState(name);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/error/StateEventNotFoundError.ts
|
|
144
|
+
var StateEventNotFoundError = class extends FinitaError {
|
|
145
|
+
code = "stateEventNotFound";
|
|
146
|
+
stateName;
|
|
147
|
+
eventName;
|
|
148
|
+
constructor(stateName, eventName) {
|
|
149
|
+
super(`State "${stateName}" has no event "${eventName}"`);
|
|
150
|
+
this.name = "StateEventNotFoundError";
|
|
151
|
+
this.stateName = stateName;
|
|
152
|
+
this.eventName = eventName;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/State.ts
|
|
157
|
+
var State = class {
|
|
158
|
+
name;
|
|
159
|
+
_transitions = null;
|
|
160
|
+
events;
|
|
161
|
+
metadata;
|
|
162
|
+
constructor(key, name, eventNames, metadata) {
|
|
163
|
+
if (key !== INTERNAL_CONSTRUCTION_KEY) {
|
|
164
|
+
throw new Error("State is not user-constructible; use ProcessBuilder.");
|
|
73
165
|
}
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
if (existing.getTargetState().getName() === targetName && existing.getEventName() === eventName && existing.getConditionName() === conditionName) {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
166
|
+
this.name = name;
|
|
167
|
+
const events = /* @__PURE__ */ new Map();
|
|
168
|
+
for (const en of eventNames) {
|
|
169
|
+
events.set(en, new Event(en));
|
|
81
170
|
}
|
|
82
|
-
this.
|
|
83
|
-
|
|
84
|
-
|
|
171
|
+
this.events = events;
|
|
172
|
+
this.metadata = new Map(metadata);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Internal: populate transitions after State construction.
|
|
176
|
+
* May only be called once and only with the construction key.
|
|
177
|
+
* Used by ProcessBuilder to break the cycle: State must exist before
|
|
178
|
+
* Transitions can target it, but State needs its transitions to be useful.
|
|
179
|
+
*/
|
|
180
|
+
_initTransitions(key, transitions) {
|
|
181
|
+
if (key !== INTERNAL_CONSTRUCTION_KEY) {
|
|
182
|
+
throw new Error("_initTransitions is internal");
|
|
183
|
+
}
|
|
184
|
+
if (this._transitions !== null) {
|
|
185
|
+
throw new Error(`State "${this.name}" transitions already set`);
|
|
186
|
+
}
|
|
187
|
+
this._transitions = new Set(transitions);
|
|
188
|
+
}
|
|
189
|
+
getName() {
|
|
190
|
+
return this.name;
|
|
191
|
+
}
|
|
192
|
+
getTransitions() {
|
|
193
|
+
if (this._transitions === null) {
|
|
194
|
+
return [];
|
|
85
195
|
}
|
|
196
|
+
return this._transitions;
|
|
86
197
|
}
|
|
87
198
|
getEventNames() {
|
|
88
199
|
return Array.from(this.events.keys());
|
|
@@ -91,10 +202,9 @@ var State = class {
|
|
|
91
202
|
return this.events.has(name);
|
|
92
203
|
}
|
|
93
204
|
getEvent(name) {
|
|
94
|
-
|
|
205
|
+
const event = this.events.get(name);
|
|
95
206
|
if (!event) {
|
|
96
|
-
|
|
97
|
-
this.events.set(name, event);
|
|
207
|
+
throw new StateEventNotFoundError(this.name, name);
|
|
98
208
|
}
|
|
99
209
|
return event;
|
|
100
210
|
}
|
|
@@ -104,15 +214,9 @@ var State = class {
|
|
|
104
214
|
getMetadataValue(key) {
|
|
105
215
|
return this.metadata.get(key);
|
|
106
216
|
}
|
|
107
|
-
setMetadataValue(key, value) {
|
|
108
|
-
this.metadata.set(key, value);
|
|
109
|
-
}
|
|
110
217
|
hasMetadataValue(key) {
|
|
111
218
|
return this.metadata.has(key);
|
|
112
219
|
}
|
|
113
|
-
deleteMetadataValue(key) {
|
|
114
|
-
this.metadata.delete(key);
|
|
115
|
-
}
|
|
116
220
|
};
|
|
117
221
|
|
|
118
222
|
// src/Transition.ts
|
|
@@ -120,11 +224,17 @@ var Transition = class {
|
|
|
120
224
|
targetState;
|
|
121
225
|
eventName;
|
|
122
226
|
condition;
|
|
123
|
-
weight
|
|
124
|
-
constructor(targetState, eventName
|
|
227
|
+
weight;
|
|
228
|
+
constructor(key, targetState, eventName, condition, weight) {
|
|
229
|
+
if (key !== INTERNAL_CONSTRUCTION_KEY) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
"Transition is not user-constructible; use ProcessBuilder."
|
|
232
|
+
);
|
|
233
|
+
}
|
|
125
234
|
this.targetState = targetState;
|
|
126
235
|
this.eventName = eventName;
|
|
127
236
|
this.condition = condition;
|
|
237
|
+
this.weight = weight;
|
|
128
238
|
}
|
|
129
239
|
getTargetState() {
|
|
130
240
|
return this.targetState;
|
|
@@ -133,117 +243,31 @@ var Transition = class {
|
|
|
133
243
|
return this.eventName;
|
|
134
244
|
}
|
|
135
245
|
getConditionName() {
|
|
136
|
-
|
|
137
|
-
return this.condition.getName();
|
|
138
|
-
}
|
|
139
|
-
return null;
|
|
246
|
+
return this.condition ? this.condition.getName() : null;
|
|
140
247
|
}
|
|
141
248
|
getCondition() {
|
|
142
249
|
return this.condition;
|
|
143
250
|
}
|
|
144
251
|
async isActive(subject, context, event) {
|
|
145
|
-
let
|
|
252
|
+
let active;
|
|
146
253
|
if (event) {
|
|
147
|
-
|
|
254
|
+
active = event.getName() === this.eventName;
|
|
148
255
|
} else {
|
|
149
|
-
|
|
256
|
+
active = this.eventName === null;
|
|
150
257
|
}
|
|
151
|
-
if (this.condition &&
|
|
152
|
-
|
|
258
|
+
if (this.condition && active) {
|
|
259
|
+
active = await this.condition.checkCondition(subject, context);
|
|
153
260
|
}
|
|
154
|
-
return
|
|
261
|
+
return active;
|
|
155
262
|
}
|
|
156
263
|
getWeight() {
|
|
157
264
|
return this.weight;
|
|
158
265
|
}
|
|
159
|
-
setWeight(weight) {
|
|
160
|
-
this.weight = weight;
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
// src/util/StateCollectionMerger.ts
|
|
165
|
-
var StateCollectionMerger = class {
|
|
166
|
-
targetCollection;
|
|
167
|
-
stateNamePrefix = "";
|
|
168
|
-
constructor(targetCollection) {
|
|
169
|
-
this.targetCollection = targetCollection;
|
|
170
|
-
}
|
|
171
|
-
getStateNamePrefix() {
|
|
172
|
-
return this.stateNamePrefix;
|
|
173
|
-
}
|
|
174
|
-
setStateNamePrefix(prefix) {
|
|
175
|
-
this.stateNamePrefix = prefix;
|
|
176
|
-
}
|
|
177
|
-
getTargetCollection() {
|
|
178
|
-
return this.targetCollection;
|
|
179
|
-
}
|
|
180
|
-
createState(name) {
|
|
181
|
-
return new State(name);
|
|
182
|
-
}
|
|
183
|
-
findOrCreateState(name) {
|
|
184
|
-
const prefixedName = this.stateNamePrefix + name;
|
|
185
|
-
if (this.targetCollection.hasState(prefixedName)) {
|
|
186
|
-
return this.targetCollection.getState(prefixedName);
|
|
187
|
-
}
|
|
188
|
-
const state = this.createState(prefixedName);
|
|
189
|
-
this.targetCollection.addState(state);
|
|
190
|
-
return state;
|
|
191
|
-
}
|
|
192
|
-
createCondition(sourceTransition) {
|
|
193
|
-
return sourceTransition.getCondition();
|
|
194
|
-
}
|
|
195
|
-
createTransition(sourceTransition) {
|
|
196
|
-
const targetStateName = sourceTransition.getTargetState().getName();
|
|
197
|
-
const targetState = this.findOrCreateState(targetStateName);
|
|
198
|
-
this.mergeMetadata(sourceTransition.getTargetState(), targetState);
|
|
199
|
-
const eventName = sourceTransition.getEventName();
|
|
200
|
-
const condition = this.createCondition(sourceTransition);
|
|
201
|
-
const transition = new Transition(targetState, eventName, condition);
|
|
202
|
-
transition.setWeight(sourceTransition.getWeight());
|
|
203
|
-
return transition;
|
|
204
|
-
}
|
|
205
|
-
mergeMetadata(source, target) {
|
|
206
|
-
const metadata = source.getMetadata();
|
|
207
|
-
for (const [key, value] of Object.entries(metadata)) {
|
|
208
|
-
target.setMetadataValue(key, value);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
mergeEvent(source, target, eventName) {
|
|
212
|
-
const sourceEvent = source.getEvent(eventName);
|
|
213
|
-
const targetEvent = target.getEvent(eventName);
|
|
214
|
-
const sourceMetadata = sourceEvent.getMetadata();
|
|
215
|
-
for (const [key, value] of Object.entries(sourceMetadata)) {
|
|
216
|
-
targetEvent.setMetadataValue(key, value);
|
|
217
|
-
}
|
|
218
|
-
for (const observer of sourceEvent.getObservers()) {
|
|
219
|
-
targetEvent.attach(observer);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
mergeState(sourceState) {
|
|
223
|
-
const targetState = this.findOrCreateState(sourceState.getName());
|
|
224
|
-
this.mergeMetadata(sourceState, targetState);
|
|
225
|
-
for (const sourceTransition of sourceState.getTransitions()) {
|
|
226
|
-
const targetTransition = this.createTransition(sourceTransition);
|
|
227
|
-
targetState.addTransition(targetTransition);
|
|
228
|
-
}
|
|
229
|
-
for (const eventName of sourceState.getEventNames()) {
|
|
230
|
-
this.mergeEvent(sourceState, targetState, eventName);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
merge(source) {
|
|
234
|
-
if ("getStates" in source && typeof source.getStates === "function") {
|
|
235
|
-
const collection = source;
|
|
236
|
-
for (const state of collection.getStates()) {
|
|
237
|
-
this.mergeState(state);
|
|
238
|
-
}
|
|
239
|
-
} else {
|
|
240
|
-
this.mergeState(source);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
266
|
};
|
|
244
267
|
|
|
245
268
|
// src/error/DuplicateStateError.ts
|
|
246
|
-
var DuplicateStateError = class extends
|
|
269
|
+
var DuplicateStateError = class extends FinitaError {
|
|
270
|
+
code = "duplicateState";
|
|
247
271
|
stateName;
|
|
248
272
|
constructor(stateName) {
|
|
249
273
|
super(
|
|
@@ -254,81 +278,309 @@ var DuplicateStateError = class extends Error {
|
|
|
254
278
|
}
|
|
255
279
|
};
|
|
256
280
|
|
|
257
|
-
// src/
|
|
258
|
-
var
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
getStates() {
|
|
269
|
-
return this.states.values();
|
|
270
|
-
}
|
|
271
|
-
hasState(name) {
|
|
272
|
-
return this.states.has(name);
|
|
273
|
-
}
|
|
274
|
-
addState(state) {
|
|
275
|
-
const existing = this.states.get(state.getName());
|
|
276
|
-
if (existing && existing !== state) {
|
|
277
|
-
throw new DuplicateStateError(state.getName());
|
|
278
|
-
}
|
|
279
|
-
this.states.set(state.getName(), state);
|
|
281
|
+
// src/error/ProcessFinalizedError.ts
|
|
282
|
+
var ProcessFinalizedError = class extends FinitaError {
|
|
283
|
+
code = "processFinalized";
|
|
284
|
+
processName;
|
|
285
|
+
constructor(processName) {
|
|
286
|
+
super(
|
|
287
|
+
`Process "${processName}" has already been built; ProcessBuilder.build() may only be called once`
|
|
288
|
+
);
|
|
289
|
+
this.name = "ProcessFinalizedError";
|
|
290
|
+
this.processName = processName;
|
|
280
291
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// src/error/GraphValidationError.ts
|
|
295
|
+
var GraphValidationError = class extends FinitaError {
|
|
296
|
+
code;
|
|
297
|
+
details;
|
|
298
|
+
constructor(code, message, details = {}) {
|
|
299
|
+
super(`[${code}] ${message}`);
|
|
300
|
+
this.name = "GraphValidationError";
|
|
301
|
+
this.code = code;
|
|
302
|
+
this.details = Object.freeze({ ...details });
|
|
286
303
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// src/error/DuplicateTransitionError.ts
|
|
307
|
+
var DuplicateTransitionError = class extends FinitaError {
|
|
308
|
+
code = "duplicateTransition";
|
|
309
|
+
conflict;
|
|
310
|
+
constructor(conflict) {
|
|
311
|
+
const eventLabel = conflict.eventName ?? "<automatic>";
|
|
312
|
+
const existing = conflict.existingConditionName ?? "<no condition>";
|
|
313
|
+
const incoming = conflict.newConditionName ?? "<no condition>";
|
|
314
|
+
super(
|
|
315
|
+
`Conflicting transition declarations from "${conflict.fromState}" to "${conflict.toState}" on event "${eventLabel}": existing condition "${existing}" vs new condition "${incoming}"`
|
|
316
|
+
);
|
|
317
|
+
this.name = "DuplicateTransitionError";
|
|
318
|
+
this.conflict = Object.freeze({ ...conflict });
|
|
290
319
|
}
|
|
291
320
|
};
|
|
292
321
|
|
|
293
|
-
// src/
|
|
294
|
-
var
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
this.
|
|
301
|
-
|
|
302
|
-
|
|
322
|
+
// src/ProcessBuilder.ts
|
|
323
|
+
var ProcessBuilder = class {
|
|
324
|
+
processName;
|
|
325
|
+
stateSpecs = /* @__PURE__ */ new Map();
|
|
326
|
+
transitionSpecs = [];
|
|
327
|
+
built = false;
|
|
328
|
+
constructor(processName) {
|
|
329
|
+
this.processName = processName;
|
|
330
|
+
}
|
|
331
|
+
addState(name, options = {}) {
|
|
332
|
+
if (this.built) {
|
|
333
|
+
throw new ProcessFinalizedError(this.processName);
|
|
334
|
+
}
|
|
335
|
+
if (this.stateSpecs.has(name)) {
|
|
336
|
+
throw new DuplicateStateError(name);
|
|
337
|
+
}
|
|
338
|
+
this.stateSpecs.set(name, {
|
|
339
|
+
name,
|
|
340
|
+
initial: options.initial === true,
|
|
341
|
+
metadata: new Map(Object.entries(options.metadata ?? {}))
|
|
342
|
+
});
|
|
343
|
+
return this;
|
|
303
344
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
345
|
+
addTransition(fromState, toState, options = {}) {
|
|
346
|
+
if (this.built) {
|
|
347
|
+
throw new ProcessFinalizedError(this.processName);
|
|
348
|
+
}
|
|
349
|
+
let eventName = null;
|
|
350
|
+
if (options.event !== void 0) {
|
|
351
|
+
const raw = options.event;
|
|
352
|
+
if (raw.trim() === "" || raw !== raw.trim()) {
|
|
353
|
+
throw new GraphValidationError(
|
|
354
|
+
"invalidEventName",
|
|
355
|
+
`addTransition called with an empty or whitespace-padded event name from "${fromState}" to "${toState}"`,
|
|
356
|
+
{ fromState, toState, eventName: raw }
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
eventName = raw;
|
|
360
|
+
}
|
|
361
|
+
if (options.condition) {
|
|
362
|
+
const conditionName = options.condition.getName();
|
|
363
|
+
if (conditionName.trim() === "") {
|
|
364
|
+
throw new GraphValidationError(
|
|
365
|
+
"invalidConditionName",
|
|
366
|
+
`addTransition called with an empty/whitespace condition name from "${fromState}" to "${toState}"`,
|
|
367
|
+
{ fromState, toState, conditionName }
|
|
368
|
+
);
|
|
309
369
|
}
|
|
310
|
-
return;
|
|
311
370
|
}
|
|
312
|
-
this.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
371
|
+
this.transitionSpecs.push({
|
|
372
|
+
fromState,
|
|
373
|
+
toState,
|
|
374
|
+
eventName,
|
|
375
|
+
condition: options.condition ?? null,
|
|
376
|
+
weight: options.weight ?? 1
|
|
377
|
+
});
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
build(options = {}) {
|
|
381
|
+
if (this.built) {
|
|
382
|
+
throw new ProcessFinalizedError(this.processName);
|
|
383
|
+
}
|
|
384
|
+
this.built = true;
|
|
385
|
+
this.validateInitialState();
|
|
386
|
+
this.validateTransitionEndpoints();
|
|
387
|
+
this.validateNoConflictingDuplicates();
|
|
388
|
+
const initialName = this.findInitialStateName();
|
|
389
|
+
const eventNamesByState = this.collectEventNamesByState();
|
|
390
|
+
const finalStates = this.buildAllStates(eventNamesByState);
|
|
391
|
+
if (options.strictOrphans) {
|
|
392
|
+
this.validateOrphans(finalStates, initialName);
|
|
393
|
+
}
|
|
394
|
+
const initialState = finalStates.get(initialName);
|
|
395
|
+
return new Process(
|
|
396
|
+
INTERNAL_CONSTRUCTION_KEY,
|
|
397
|
+
this.processName,
|
|
398
|
+
initialState,
|
|
399
|
+
finalStates.values()
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
// --- private helpers ---
|
|
403
|
+
validateInitialState() {
|
|
404
|
+
const initials = Array.from(this.stateSpecs.values()).filter(
|
|
405
|
+
(s) => s.initial
|
|
406
|
+
);
|
|
407
|
+
if (initials.length === 0) {
|
|
408
|
+
throw new GraphValidationError(
|
|
409
|
+
"missingInitialState",
|
|
410
|
+
`Process "${this.processName}" has no state declared with { initial: true }`,
|
|
411
|
+
{ processName: this.processName }
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (initials.length > 1) {
|
|
415
|
+
throw new GraphValidationError(
|
|
416
|
+
"multipleInitialStates",
|
|
417
|
+
`Process "${this.processName}" declares multiple initial states: ${initials.map((s) => `"${s.name}"`).join(", ")}`,
|
|
418
|
+
{
|
|
419
|
+
processName: this.processName,
|
|
420
|
+
initialStates: initials.map((s) => s.name)
|
|
421
|
+
}
|
|
422
|
+
);
|
|
316
423
|
}
|
|
317
424
|
}
|
|
318
|
-
|
|
319
|
-
return this.name;
|
|
425
|
+
findInitialStateName() {
|
|
426
|
+
return Array.from(this.stateSpecs.values()).find((s) => s.initial).name;
|
|
320
427
|
}
|
|
321
|
-
|
|
322
|
-
|
|
428
|
+
validateTransitionEndpoints() {
|
|
429
|
+
for (const t of this.transitionSpecs) {
|
|
430
|
+
if (!this.stateSpecs.has(t.fromState)) {
|
|
431
|
+
throw new GraphValidationError(
|
|
432
|
+
"unknownSource",
|
|
433
|
+
`Transition source state "${t.fromState}" was not declared with addState`,
|
|
434
|
+
{
|
|
435
|
+
fromState: t.fromState,
|
|
436
|
+
toState: t.toState,
|
|
437
|
+
eventName: t.eventName
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
if (!this.stateSpecs.has(t.toState)) {
|
|
442
|
+
throw new GraphValidationError(
|
|
443
|
+
"unknownTarget",
|
|
444
|
+
`Transition target state "${t.toState}" was not declared with addState`,
|
|
445
|
+
{
|
|
446
|
+
fromState: t.fromState,
|
|
447
|
+
toState: t.toState,
|
|
448
|
+
eventName: t.eventName
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
323
453
|
}
|
|
324
|
-
|
|
325
|
-
|
|
454
|
+
validateNoConflictingDuplicates() {
|
|
455
|
+
const seen = /* @__PURE__ */ new Map();
|
|
456
|
+
for (const t of this.transitionSpecs) {
|
|
457
|
+
const key = `${t.fromState}\0${t.eventName ?? ""}\0${t.toState}`;
|
|
458
|
+
const existing = seen.get(key);
|
|
459
|
+
if (!existing) {
|
|
460
|
+
seen.set(key, t);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (existing.condition !== t.condition) {
|
|
464
|
+
throw new DuplicateTransitionError({
|
|
465
|
+
fromState: t.fromState,
|
|
466
|
+
toState: t.toState,
|
|
467
|
+
eventName: t.eventName,
|
|
468
|
+
existingConditionName: existing.condition ? existing.condition.getName() : null,
|
|
469
|
+
newConditionName: t.condition ? t.condition.getName() : null
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
326
473
|
}
|
|
327
|
-
|
|
328
|
-
|
|
474
|
+
collectEventNamesByState() {
|
|
475
|
+
const out = /* @__PURE__ */ new Map();
|
|
476
|
+
for (const t of this.transitionSpecs) {
|
|
477
|
+
if (t.eventName === null) continue;
|
|
478
|
+
let bucket = out.get(t.fromState);
|
|
479
|
+
if (!bucket) {
|
|
480
|
+
bucket = /* @__PURE__ */ new Set();
|
|
481
|
+
out.set(t.fromState, bucket);
|
|
482
|
+
}
|
|
483
|
+
bucket.add(t.eventName);
|
|
484
|
+
}
|
|
485
|
+
return new Map(
|
|
486
|
+
Array.from(out.entries()).map(([k, v]) => [k, Array.from(v)])
|
|
487
|
+
);
|
|
329
488
|
}
|
|
330
|
-
|
|
331
|
-
|
|
489
|
+
/**
|
|
490
|
+
* Two-phase construction:
|
|
491
|
+
* Phase 1 — create all final State instances with no transitions.
|
|
492
|
+
* Phase 2 — build Transitions targeting the Phase-1 States, then attach
|
|
493
|
+
* them via State._initTransitions.
|
|
494
|
+
*
|
|
495
|
+
* Because every Transition is created after every State exists, target
|
|
496
|
+
* identity holds by construction for any graph topology (acyclic, cyclic,
|
|
497
|
+
* self-loop).
|
|
498
|
+
*/
|
|
499
|
+
buildAllStates(eventNamesByState) {
|
|
500
|
+
const built = /* @__PURE__ */ new Map();
|
|
501
|
+
for (const spec of this.stateSpecs.values()) {
|
|
502
|
+
built.set(
|
|
503
|
+
spec.name,
|
|
504
|
+
new State(
|
|
505
|
+
INTERNAL_CONSTRUCTION_KEY,
|
|
506
|
+
spec.name,
|
|
507
|
+
eventNamesByState.get(spec.name) ?? [],
|
|
508
|
+
spec.metadata
|
|
509
|
+
)
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
const dedupSeen = /* @__PURE__ */ new Set();
|
|
513
|
+
const conditionId = /* @__PURE__ */ new Map();
|
|
514
|
+
let nextConditionId = 0;
|
|
515
|
+
const idForCondition = (cond) => {
|
|
516
|
+
if (cond === null) return "";
|
|
517
|
+
let id = conditionId.get(cond);
|
|
518
|
+
if (id === void 0) {
|
|
519
|
+
id = ++nextConditionId;
|
|
520
|
+
conditionId.set(cond, id);
|
|
521
|
+
}
|
|
522
|
+
return String(id);
|
|
523
|
+
};
|
|
524
|
+
const transitionsByState = /* @__PURE__ */ new Map();
|
|
525
|
+
for (const spec of this.stateSpecs.values()) {
|
|
526
|
+
transitionsByState.set(spec.name, []);
|
|
527
|
+
}
|
|
528
|
+
for (const tSpec of this.transitionSpecs) {
|
|
529
|
+
const dedupKey = `${tSpec.fromState}\0${tSpec.eventName ?? ""}\0${tSpec.toState}\0${idForCondition(tSpec.condition)}`;
|
|
530
|
+
if (dedupSeen.has(dedupKey)) continue;
|
|
531
|
+
dedupSeen.add(dedupKey);
|
|
532
|
+
const targetState = built.get(tSpec.toState);
|
|
533
|
+
const transition = new Transition(
|
|
534
|
+
INTERNAL_CONSTRUCTION_KEY,
|
|
535
|
+
targetState,
|
|
536
|
+
tSpec.eventName,
|
|
537
|
+
tSpec.condition,
|
|
538
|
+
tSpec.weight
|
|
539
|
+
);
|
|
540
|
+
transitionsByState.get(tSpec.fromState).push(transition);
|
|
541
|
+
}
|
|
542
|
+
for (const [name, transitions] of transitionsByState) {
|
|
543
|
+
built.get(name)._initTransitions(
|
|
544
|
+
INTERNAL_CONSTRUCTION_KEY,
|
|
545
|
+
transitions
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
return built;
|
|
549
|
+
}
|
|
550
|
+
validateOrphans(states, initialName) {
|
|
551
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
552
|
+
const queue = [initialName];
|
|
553
|
+
while (queue.length > 0) {
|
|
554
|
+
const name = queue.shift();
|
|
555
|
+
if (reachable.has(name)) continue;
|
|
556
|
+
reachable.add(name);
|
|
557
|
+
const s = states.get(name);
|
|
558
|
+
for (const t of s.getTransitions()) {
|
|
559
|
+
queue.push(t.getTargetState().getName());
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const orphans = [];
|
|
563
|
+
for (const name of states.keys()) {
|
|
564
|
+
if (!reachable.has(name)) orphans.push(name);
|
|
565
|
+
}
|
|
566
|
+
if (orphans.length > 0) {
|
|
567
|
+
throw new GraphValidationError(
|
|
568
|
+
"orphanState",
|
|
569
|
+
`Process "${this.processName}" has unreachable states: ${orphans.map((n) => `"${n}"`).join(", ")}`,
|
|
570
|
+
{ processName: this.processName, orphanStates: orphans }
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// src/error/AmbiguousTransitionError.ts
|
|
577
|
+
var AmbiguousTransitionError = class extends FinitaError {
|
|
578
|
+
code = "ambiguousTransition";
|
|
579
|
+
activeCount;
|
|
580
|
+
constructor(activeCount) {
|
|
581
|
+
super(`More than one transition is active! (active count: ${activeCount})`);
|
|
582
|
+
this.name = "AmbiguousTransitionError";
|
|
583
|
+
this.activeCount = activeCount;
|
|
332
584
|
}
|
|
333
585
|
};
|
|
334
586
|
|
|
@@ -342,7 +594,7 @@ var OneOrNoneActiveTransition = class {
|
|
|
342
594
|
case 1:
|
|
343
595
|
return arr[0];
|
|
344
596
|
default:
|
|
345
|
-
throw new
|
|
597
|
+
throw new AmbiguousTransitionError(arr.length);
|
|
346
598
|
}
|
|
347
599
|
}
|
|
348
600
|
};
|
|
@@ -366,19 +618,15 @@ var NullMutex = class {
|
|
|
366
618
|
}
|
|
367
619
|
};
|
|
368
620
|
|
|
369
|
-
// src/Dispatcher.ts
|
|
621
|
+
// src/internal/Dispatcher.ts
|
|
370
622
|
var Dispatcher = class {
|
|
371
623
|
commands = [];
|
|
372
|
-
onReadyCallbacks = [];
|
|
373
624
|
ready = false;
|
|
374
|
-
dispatch(event, args = []
|
|
625
|
+
dispatch(event, args = []) {
|
|
375
626
|
if (this.ready) {
|
|
376
627
|
throw new Error("Was already invoked!");
|
|
377
628
|
}
|
|
378
629
|
this.commands.push({ event, args });
|
|
379
|
-
if (onReadyCallback) {
|
|
380
|
-
this.onReadyCallbacks.push(onReadyCallback);
|
|
381
|
-
}
|
|
382
630
|
}
|
|
383
631
|
async invoke() {
|
|
384
632
|
if (this.ready) {
|
|
@@ -388,12 +636,20 @@ var Dispatcher = class {
|
|
|
388
636
|
await event.invoke(...args);
|
|
389
637
|
}
|
|
390
638
|
this.ready = true;
|
|
391
|
-
for (const callback of this.onReadyCallbacks) {
|
|
392
|
-
await callback.invoke();
|
|
393
|
-
}
|
|
394
639
|
}
|
|
395
|
-
|
|
396
|
-
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// src/internal/OperationQueue.ts
|
|
643
|
+
var OperationQueue = class {
|
|
644
|
+
items = [];
|
|
645
|
+
enqueue(op) {
|
|
646
|
+
this.items.push(op);
|
|
647
|
+
}
|
|
648
|
+
dequeue() {
|
|
649
|
+
return this.items.shift();
|
|
650
|
+
}
|
|
651
|
+
isEmpty() {
|
|
652
|
+
return this.items.length === 0;
|
|
397
653
|
}
|
|
398
654
|
};
|
|
399
655
|
|
|
@@ -411,7 +667,8 @@ var ActiveTransitionFilter = class {
|
|
|
411
667
|
};
|
|
412
668
|
|
|
413
669
|
// src/error/WrongEventForStateError.ts
|
|
414
|
-
var WrongEventForStateError = class extends
|
|
670
|
+
var WrongEventForStateError = class extends FinitaError {
|
|
671
|
+
code = "wrongEventForState";
|
|
415
672
|
stateName;
|
|
416
673
|
eventName;
|
|
417
674
|
constructor(stateName, eventName) {
|
|
@@ -423,41 +680,52 @@ var WrongEventForStateError = class extends Error {
|
|
|
423
680
|
};
|
|
424
681
|
|
|
425
682
|
// src/error/LockCanNotBeAcquiredError.ts
|
|
426
|
-
var LockCanNotBeAcquiredError = class extends
|
|
683
|
+
var LockCanNotBeAcquiredError = class extends FinitaError {
|
|
684
|
+
code = "lockCanNotBeAcquired";
|
|
427
685
|
constructor(message = "Lock can not be acquired!") {
|
|
428
686
|
super(message);
|
|
429
687
|
this.name = "LockCanNotBeAcquiredError";
|
|
430
688
|
}
|
|
431
689
|
};
|
|
432
690
|
|
|
691
|
+
// src/error/AutomaticTransitionCycleError.ts
|
|
692
|
+
var AutomaticTransitionCycleError = class extends FinitaError {
|
|
693
|
+
code = "automaticTransitionCycle";
|
|
694
|
+
targetStateName;
|
|
695
|
+
visitedStateNames;
|
|
696
|
+
constructor(targetStateName, visitedStateNames) {
|
|
697
|
+
const visited = Array.from(visitedStateNames);
|
|
698
|
+
super(
|
|
699
|
+
`Automatic transition cycle detected: state "${targetStateName}" was already visited \u2014 this would cause infinite recursion`
|
|
700
|
+
);
|
|
701
|
+
this.name = "AutomaticTransitionCycleError";
|
|
702
|
+
this.targetStateName = targetStateName;
|
|
703
|
+
this.visitedStateNames = Object.freeze([...visited]);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
|
|
433
707
|
// src/Statemachine.ts
|
|
434
708
|
var Statemachine = class {
|
|
435
709
|
subject;
|
|
436
|
-
currentState;
|
|
437
|
-
lastState = null;
|
|
438
|
-
transitionSelector;
|
|
439
|
-
selectedTransition = null;
|
|
440
710
|
process;
|
|
711
|
+
transitionSelector;
|
|
441
712
|
mutex;
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
713
|
+
currentState;
|
|
714
|
+
lastState = null;
|
|
715
|
+
autoreleaseLock;
|
|
716
|
+
queue = new OperationQueue();
|
|
717
|
+
running = false;
|
|
718
|
+
beforeObservers = [];
|
|
719
|
+
afterObservers = [];
|
|
720
|
+
constructor(subject, process, options = {}) {
|
|
448
721
|
this.subject = subject;
|
|
449
|
-
if (stateName) {
|
|
450
|
-
this.currentState = process.getState(stateName);
|
|
451
|
-
} else {
|
|
452
|
-
this.currentState = process.getInitialState();
|
|
453
|
-
}
|
|
454
|
-
this.transitionSelector = transitionSelector ?? new OneOrNoneActiveTransition();
|
|
455
722
|
this.process = process;
|
|
456
|
-
this.
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
723
|
+
this.currentState = options.initialStateName ? process.getState(options.initialStateName) : process.getInitialState();
|
|
724
|
+
this.transitionSelector = options.transitionSelector ?? new OneOrNoneActiveTransition();
|
|
725
|
+
this.mutex = options.mutex ?? new NullMutex();
|
|
726
|
+
this.autoreleaseLock = options.autoreleaseLock ?? true;
|
|
460
727
|
}
|
|
728
|
+
// --- public getters ---
|
|
461
729
|
getCurrentState() {
|
|
462
730
|
return this.currentState;
|
|
463
731
|
}
|
|
@@ -467,147 +735,32 @@ var Statemachine = class {
|
|
|
467
735
|
getSubject() {
|
|
468
736
|
return this.subject;
|
|
469
737
|
}
|
|
470
|
-
|
|
471
|
-
return this.
|
|
472
|
-
}
|
|
473
|
-
getCurrentContext() {
|
|
474
|
-
return this.currentContext;
|
|
475
|
-
}
|
|
476
|
-
async doCheckTransitions(context, event, automaticVisited) {
|
|
477
|
-
try {
|
|
478
|
-
const transitions = this.currentState.getTransitions();
|
|
479
|
-
const activeTransitions = await ActiveTransitionFilter.filter(
|
|
480
|
-
transitions,
|
|
481
|
-
this.subject,
|
|
482
|
-
context,
|
|
483
|
-
event
|
|
484
|
-
);
|
|
485
|
-
this.selectedTransition = this.transitionSelector.selectTransition(activeTransitions);
|
|
486
|
-
if (this.selectedTransition) {
|
|
487
|
-
const targetState = this.selectedTransition.getTargetState();
|
|
488
|
-
if (this.selectedTransition.getEventName() === null) {
|
|
489
|
-
if (!automaticVisited) {
|
|
490
|
-
automaticVisited = /* @__PURE__ */ new Set();
|
|
491
|
-
}
|
|
492
|
-
automaticVisited.add(this.currentState);
|
|
493
|
-
if (automaticVisited.has(targetState)) {
|
|
494
|
-
throw new Error(
|
|
495
|
-
`Automatic transition cycle detected: state "${targetState.getName()}" was already visited \u2014 this would cause infinite recursion`
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
if (this.currentState !== targetState) {
|
|
500
|
-
this.lastState = this.currentState;
|
|
501
|
-
this.currentState = targetState;
|
|
502
|
-
this.currentContext = context;
|
|
503
|
-
this.currentEvent = event ?? null;
|
|
504
|
-
try {
|
|
505
|
-
await this.notify();
|
|
506
|
-
} finally {
|
|
507
|
-
this.currentContext = null;
|
|
508
|
-
this.currentEvent = null;
|
|
509
|
-
this.selectedTransition = null;
|
|
510
|
-
this.lastState = null;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
await this.doCheckTransitions(context, void 0, automaticVisited);
|
|
514
|
-
}
|
|
515
|
-
} catch (error) {
|
|
516
|
-
let message = `Exception was thrown when doing a transition from current state "${this.currentState.getName()}"`;
|
|
517
|
-
if (this.currentEvent) {
|
|
518
|
-
message += ` with event "${this.currentEvent.getName()}"`;
|
|
519
|
-
}
|
|
520
|
-
const named = this.subject;
|
|
521
|
-
if (named && typeof named.getName === "function") {
|
|
522
|
-
message += ` for "${named.getName()}"`;
|
|
523
|
-
}
|
|
524
|
-
throw new Error(message, { cause: error });
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
async onDispatcherReady() {
|
|
528
|
-
if (this.dispatcher && this.dispatcher.isReady()) {
|
|
529
|
-
const context = this.currentContext;
|
|
530
|
-
const event = this.currentEvent ?? void 0;
|
|
531
|
-
this.dispatcher = null;
|
|
532
|
-
this.currentContext = null;
|
|
533
|
-
this.currentEvent = null;
|
|
534
|
-
try {
|
|
535
|
-
await this.doCheckTransitions(context, event);
|
|
536
|
-
} finally {
|
|
537
|
-
if (this.autoreleaseLock && this.isLockAcquired()) {
|
|
538
|
-
await this.releaseLock();
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
738
|
+
getProcess() {
|
|
739
|
+
return this.process;
|
|
542
740
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
if (this.currentState.hasEvent(name)) {
|
|
548
|
-
await this.acquireLockOrThrowException();
|
|
549
|
-
try {
|
|
550
|
-
this.dispatcher = dispatcher;
|
|
551
|
-
this.currentContext = context ?? /* @__PURE__ */ new Map();
|
|
552
|
-
this.currentEvent = this.currentState.getEvent(name);
|
|
553
|
-
dispatcher.dispatch(
|
|
554
|
-
this.currentEvent,
|
|
555
|
-
[this.subject, this.currentContext],
|
|
556
|
-
{ invoke: () => this.onDispatcherReady() }
|
|
557
|
-
);
|
|
558
|
-
} catch (error) {
|
|
559
|
-
this.dispatcher = null;
|
|
560
|
-
this.currentContext = null;
|
|
561
|
-
this.currentEvent = null;
|
|
562
|
-
if (this.autoreleaseLock) {
|
|
563
|
-
await this.releaseLock();
|
|
564
|
-
}
|
|
565
|
-
throw error;
|
|
566
|
-
}
|
|
567
|
-
} else {
|
|
568
|
-
throw new WrongEventForStateError(this.currentState.getName(), name);
|
|
569
|
-
}
|
|
741
|
+
// --- public observer attach/detach ---
|
|
742
|
+
attachBefore(observer) {
|
|
743
|
+
this.beforeObservers.push(observer);
|
|
570
744
|
}
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
await this.dispatchEvent(dispatcher, name, context);
|
|
575
|
-
await dispatcher.invoke();
|
|
576
|
-
} finally {
|
|
577
|
-
this.dispatcher = null;
|
|
578
|
-
this.currentContext = null;
|
|
579
|
-
this.currentEvent = null;
|
|
580
|
-
if (this.autoreleaseLock && this.isLockAcquired()) {
|
|
581
|
-
await this.releaseLock();
|
|
582
|
-
}
|
|
583
|
-
}
|
|
745
|
+
detachBefore(observer) {
|
|
746
|
+
const idx = this.beforeObservers.indexOf(observer);
|
|
747
|
+
if (idx >= 0) this.beforeObservers.splice(idx, 1);
|
|
584
748
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const ctx = context ?? /* @__PURE__ */ new Map();
|
|
588
|
-
try {
|
|
589
|
-
await this.doCheckTransitions(ctx);
|
|
590
|
-
} finally {
|
|
591
|
-
if (this.autoreleaseLock && this.isLockAcquired()) {
|
|
592
|
-
await this.releaseLock();
|
|
593
|
-
}
|
|
594
|
-
}
|
|
749
|
+
getBeforeObservers() {
|
|
750
|
+
return this.beforeObservers;
|
|
595
751
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
throw new LockCanNotBeAcquiredError("Lock can not be acquired!");
|
|
599
|
-
}
|
|
752
|
+
attachAfter(observer) {
|
|
753
|
+
this.afterObservers.push(observer);
|
|
600
754
|
}
|
|
601
|
-
|
|
602
|
-
|
|
755
|
+
detachAfter(observer) {
|
|
756
|
+
const idx = this.afterObservers.indexOf(observer);
|
|
757
|
+
if (idx >= 0) this.afterObservers.splice(idx, 1);
|
|
603
758
|
}
|
|
604
|
-
|
|
605
|
-
this.
|
|
759
|
+
getAfterObservers() {
|
|
760
|
+
return this.afterObservers;
|
|
606
761
|
}
|
|
762
|
+
// --- public locking ---
|
|
607
763
|
async acquireLock() {
|
|
608
|
-
if (this.mutex.isAcquired()) {
|
|
609
|
-
return true;
|
|
610
|
-
}
|
|
611
764
|
return this.mutex.acquireLock();
|
|
612
765
|
}
|
|
613
766
|
async releaseLock() {
|
|
@@ -616,19 +769,179 @@ var Statemachine = class {
|
|
|
616
769
|
isLockAcquired() {
|
|
617
770
|
return this.mutex.isAcquired();
|
|
618
771
|
}
|
|
619
|
-
|
|
620
|
-
this.
|
|
772
|
+
isAutoreleaseLock() {
|
|
773
|
+
return this.autoreleaseLock;
|
|
621
774
|
}
|
|
622
|
-
|
|
623
|
-
this.
|
|
775
|
+
setAutoreleaseLock(autorelease) {
|
|
776
|
+
this.autoreleaseLock = autorelease;
|
|
624
777
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
778
|
+
// --- public top-level operations ---
|
|
779
|
+
triggerEvent(name, context) {
|
|
780
|
+
return new Promise((resolve, reject) => {
|
|
781
|
+
this.queue.enqueue({
|
|
782
|
+
kind: "triggerEvent",
|
|
783
|
+
eventName: name,
|
|
784
|
+
context: context ?? /* @__PURE__ */ new Map(),
|
|
785
|
+
resolve,
|
|
786
|
+
reject
|
|
787
|
+
});
|
|
788
|
+
void this.runIfIdle();
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
checkTransitions(context) {
|
|
792
|
+
return new Promise((resolve, reject) => {
|
|
793
|
+
this.queue.enqueue({
|
|
794
|
+
kind: "checkTransitions",
|
|
795
|
+
eventName: null,
|
|
796
|
+
context: context ?? /* @__PURE__ */ new Map(),
|
|
797
|
+
resolve,
|
|
798
|
+
reject
|
|
799
|
+
});
|
|
800
|
+
void this.runIfIdle();
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
// --- internal runner ---
|
|
804
|
+
async runIfIdle() {
|
|
805
|
+
if (this.running) return;
|
|
806
|
+
this.running = true;
|
|
807
|
+
try {
|
|
808
|
+
while (!this.queue.isEmpty()) {
|
|
809
|
+
const op = this.queue.dequeue();
|
|
810
|
+
await this.runOperation(op);
|
|
811
|
+
}
|
|
812
|
+
} finally {
|
|
813
|
+
this.running = false;
|
|
628
814
|
}
|
|
629
815
|
}
|
|
630
|
-
|
|
631
|
-
|
|
816
|
+
async runOperation(op) {
|
|
817
|
+
let acquiredHere = false;
|
|
818
|
+
try {
|
|
819
|
+
if (!this.mutex.isAcquired()) {
|
|
820
|
+
if (!await this.mutex.acquireLock()) {
|
|
821
|
+
throw new LockCanNotBeAcquiredError("Lock can not be acquired!");
|
|
822
|
+
}
|
|
823
|
+
acquiredHere = true;
|
|
824
|
+
}
|
|
825
|
+
const event = op.kind === "triggerEvent" ? this.resolveEvent(op.eventName) : null;
|
|
826
|
+
await this.processOperation(event, op.context);
|
|
827
|
+
op.resolve();
|
|
828
|
+
} catch (err) {
|
|
829
|
+
op.reject(err);
|
|
830
|
+
} finally {
|
|
831
|
+
if (acquiredHere && this.autoreleaseLock) {
|
|
832
|
+
try {
|
|
833
|
+
await this.mutex.releaseLock();
|
|
834
|
+
} catch {
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
resolveEvent(name) {
|
|
840
|
+
if (!this.currentState.hasEvent(name)) {
|
|
841
|
+
throw new WrongEventForStateError(this.currentState.getName(), name);
|
|
842
|
+
}
|
|
843
|
+
return this.currentState.getEvent(name);
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Drive transitions starting from the current state, following automatic
|
|
847
|
+
* transitions until quiescent. The first iteration may use the supplied
|
|
848
|
+
* event; subsequent iterations are automatic.
|
|
849
|
+
*/
|
|
850
|
+
async processOperation(initialEvent, context) {
|
|
851
|
+
let event = initialEvent;
|
|
852
|
+
const automaticVisited = /* @__PURE__ */ new Set();
|
|
853
|
+
if (event) {
|
|
854
|
+
const dispatcher = new Dispatcher();
|
|
855
|
+
dispatcher.dispatch(event, [this.subject, context]);
|
|
856
|
+
await dispatcher.invoke();
|
|
857
|
+
}
|
|
858
|
+
while (true) {
|
|
859
|
+
const transitions = this.currentState.getTransitions();
|
|
860
|
+
const active = await ActiveTransitionFilter.filter(
|
|
861
|
+
transitions,
|
|
862
|
+
this.subject,
|
|
863
|
+
context,
|
|
864
|
+
event ?? void 0
|
|
865
|
+
);
|
|
866
|
+
const selected = this.transitionSelector.selectTransition(
|
|
867
|
+
active
|
|
868
|
+
);
|
|
869
|
+
if (!selected) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const target = selected.getTargetState();
|
|
873
|
+
if (selected.getEventName() === null) {
|
|
874
|
+
automaticVisited.add(this.currentState);
|
|
875
|
+
if (automaticVisited.has(target)) {
|
|
876
|
+
throw new AutomaticTransitionCycleError(
|
|
877
|
+
target.getName(),
|
|
878
|
+
Array.from(automaticVisited).map((s) => s.getName())
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (this.currentState !== target) {
|
|
883
|
+
const proposedFrame = Object.freeze({
|
|
884
|
+
fromState: this.currentState,
|
|
885
|
+
toState: target,
|
|
886
|
+
transition: selected,
|
|
887
|
+
event,
|
|
888
|
+
condition: selected.getCondition(),
|
|
889
|
+
context: this.readonlyContext(context),
|
|
890
|
+
timestamp: Date.now(),
|
|
891
|
+
machineName: this.process.getName()
|
|
892
|
+
});
|
|
893
|
+
for (const observer of this.beforeObservers) {
|
|
894
|
+
await observer.notify(proposedFrame);
|
|
895
|
+
}
|
|
896
|
+
const fromState = this.currentState;
|
|
897
|
+
this.lastState = fromState;
|
|
898
|
+
this.currentState = target;
|
|
899
|
+
const committedFrame = Object.freeze({
|
|
900
|
+
fromState,
|
|
901
|
+
toState: target,
|
|
902
|
+
transition: selected,
|
|
903
|
+
event,
|
|
904
|
+
condition: selected.getCondition(),
|
|
905
|
+
context: this.readonlyContext(context),
|
|
906
|
+
timestamp: proposedFrame.timestamp,
|
|
907
|
+
machineName: this.process.getName()
|
|
908
|
+
});
|
|
909
|
+
const enqueueCtx = {
|
|
910
|
+
enqueue: (chainedEventName, chainedCtx) => {
|
|
911
|
+
this.queue.enqueue({
|
|
912
|
+
kind: "triggerEvent",
|
|
913
|
+
eventName: chainedEventName,
|
|
914
|
+
context: chainedCtx ?? /* @__PURE__ */ new Map(),
|
|
915
|
+
resolve: () => {
|
|
916
|
+
},
|
|
917
|
+
reject: () => {
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
const errors = [];
|
|
923
|
+
for (const observer of this.afterObservers) {
|
|
924
|
+
try {
|
|
925
|
+
await observer.notify(committedFrame, enqueueCtx);
|
|
926
|
+
} catch (err) {
|
|
927
|
+
errors.push(err);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (errors.length === 1) {
|
|
931
|
+
throw errors[0];
|
|
932
|
+
}
|
|
933
|
+
if (errors.length > 1) {
|
|
934
|
+
throw new AggregateError(
|
|
935
|
+
errors,
|
|
936
|
+
`${errors.length} after-transition observer(s) threw`
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
event = null;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
readonlyContext(ctx) {
|
|
944
|
+
return new Map(ctx);
|
|
632
945
|
}
|
|
633
946
|
};
|
|
634
947
|
|
|
@@ -783,62 +1096,36 @@ var CallbackObserver = class {
|
|
|
783
1096
|
};
|
|
784
1097
|
|
|
785
1098
|
// src/observer/StatefulStatusChanger.ts
|
|
786
|
-
function isStatemachine(obj) {
|
|
787
|
-
return typeof obj === "object" && obj !== null && "getCurrentState" in obj && "getSubject" in obj;
|
|
788
|
-
}
|
|
789
|
-
function isStateful(obj) {
|
|
790
|
-
return typeof obj === "object" && obj !== null && "setCurrentStateName" in obj && typeof obj.setCurrentStateName === "function";
|
|
791
|
-
}
|
|
792
1099
|
var StatefulStatusChanger = class {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
}
|
|
800
|
-
}
|
|
1100
|
+
subject;
|
|
1101
|
+
constructor(subject) {
|
|
1102
|
+
this.subject = subject;
|
|
1103
|
+
}
|
|
1104
|
+
notify(frame) {
|
|
1105
|
+
this.subject.setCurrentStateName(frame.toState.getName());
|
|
801
1106
|
}
|
|
802
1107
|
};
|
|
803
1108
|
|
|
804
1109
|
// src/observer/OnEnterObserver.ts
|
|
805
|
-
function isStatemachine2(obj) {
|
|
806
|
-
return typeof obj === "object" && obj !== null && "getCurrentState" in obj && "triggerEvent" in obj;
|
|
807
|
-
}
|
|
808
1110
|
var OnEnterObserver = class _OnEnterObserver {
|
|
809
1111
|
static DEFAULT_EVENT_NAME = "onEnter";
|
|
810
1112
|
eventName;
|
|
811
1113
|
constructor(eventName = _OnEnterObserver.DEFAULT_EVENT_NAME) {
|
|
812
1114
|
this.eventName = eventName;
|
|
813
1115
|
}
|
|
814
|
-
|
|
815
|
-
if (
|
|
816
|
-
|
|
817
|
-
const autorelease = sm.isAutoreleaseLock();
|
|
818
|
-
sm.setAutoreleaseLock(false);
|
|
819
|
-
try {
|
|
820
|
-
await sm.triggerEvent(
|
|
821
|
-
this.eventName,
|
|
822
|
-
sm.getCurrentContext() ?? void 0
|
|
823
|
-
);
|
|
824
|
-
} finally {
|
|
825
|
-
sm.setAutoreleaseLock(autorelease);
|
|
826
|
-
}
|
|
1116
|
+
notify(frame, ctx) {
|
|
1117
|
+
if (frame.toState.hasEvent(this.eventName)) {
|
|
1118
|
+
ctx.enqueue(this.eventName, new Map(frame.context));
|
|
827
1119
|
}
|
|
828
1120
|
}
|
|
829
1121
|
};
|
|
830
1122
|
|
|
831
1123
|
// src/observer/TransitionLogger.ts
|
|
832
|
-
function isStatemachine3(obj) {
|
|
833
|
-
return typeof obj === "object" && obj !== null && "getCurrentState" in obj && "getSubject" in obj;
|
|
834
|
-
}
|
|
835
1124
|
function isNamed(obj) {
|
|
836
1125
|
return typeof obj === "object" && obj !== null && "getName" in obj && typeof obj.getName === "function";
|
|
837
1126
|
}
|
|
838
|
-
function
|
|
839
|
-
if (isNamed(obj))
|
|
840
|
-
return obj.getName();
|
|
841
|
-
}
|
|
1127
|
+
function asString(obj) {
|
|
1128
|
+
if (isNamed(obj)) return obj.getName();
|
|
842
1129
|
return String(obj);
|
|
843
1130
|
}
|
|
844
1131
|
var TransitionLogger = class {
|
|
@@ -848,40 +1135,23 @@ var TransitionLogger = class {
|
|
|
848
1135
|
this.logger = logger;
|
|
849
1136
|
this.loggerLevel = loggerLevel;
|
|
850
1137
|
}
|
|
851
|
-
|
|
852
|
-
if (!isStatemachine3(subject)) {
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
const context = {};
|
|
856
|
-
context["subject"] = subject.getSubject();
|
|
857
|
-
context["currentState"] = subject.getCurrentState();
|
|
858
|
-
context["lastState"] = subject.getLastState();
|
|
859
|
-
context["transition"] = subject.getSelectedTransition();
|
|
1138
|
+
notify(frame) {
|
|
860
1139
|
let message = "Transition";
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (
|
|
865
|
-
message +=
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
if (eventName) {
|
|
877
|
-
message += ` event "${eventName}"`;
|
|
878
|
-
}
|
|
879
|
-
if (condition) {
|
|
880
|
-
message += ` condition "${condition}"`;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
this.logger.log(this.loggerLevel, message, context);
|
|
1140
|
+
message += ` from "${asString(frame.fromState)}" to "${asString(frame.toState)}"`;
|
|
1141
|
+
const eventName = frame.event ? frame.event.getName() : null;
|
|
1142
|
+
const conditionName = frame.condition ? frame.condition.getName() : null;
|
|
1143
|
+
if (eventName || conditionName) {
|
|
1144
|
+
message += " with";
|
|
1145
|
+
if (eventName) message += ` event "${eventName}"`;
|
|
1146
|
+
if (conditionName) message += ` condition "${conditionName}"`;
|
|
1147
|
+
}
|
|
1148
|
+
this.logger.log(this.loggerLevel, message, {
|
|
1149
|
+
fromState: frame.fromState,
|
|
1150
|
+
toState: frame.toState,
|
|
1151
|
+
event: frame.event,
|
|
1152
|
+
transition: frame.transition,
|
|
1153
|
+
machineName: frame.machineName
|
|
1154
|
+
});
|
|
885
1155
|
}
|
|
886
1156
|
};
|
|
887
1157
|
|
|
@@ -1047,7 +1317,8 @@ var MutexFactory = class {
|
|
|
1047
1317
|
var Factory = class {
|
|
1048
1318
|
processDetector;
|
|
1049
1319
|
stateNameDetector;
|
|
1050
|
-
|
|
1320
|
+
beforeObservers = /* @__PURE__ */ new Set();
|
|
1321
|
+
afterObservers = /* @__PURE__ */ new Set();
|
|
1051
1322
|
transitionSelector = null;
|
|
1052
1323
|
mutexFactory = null;
|
|
1053
1324
|
constructor(processDetector, stateNameDetector) {
|
|
@@ -1060,30 +1331,30 @@ var Factory = class {
|
|
|
1060
1331
|
setTransitionSelector(selector) {
|
|
1061
1332
|
this.transitionSelector = selector;
|
|
1062
1333
|
}
|
|
1063
|
-
|
|
1064
|
-
this.
|
|
1334
|
+
attachBeforeObserver(observer) {
|
|
1335
|
+
this.beforeObservers.add(observer);
|
|
1065
1336
|
}
|
|
1066
|
-
|
|
1067
|
-
this.
|
|
1337
|
+
detachBeforeObserver(observer) {
|
|
1338
|
+
this.beforeObservers.delete(observer);
|
|
1068
1339
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1340
|
+
attachAfterObserver(observer) {
|
|
1341
|
+
this.afterObservers.add(observer);
|
|
1342
|
+
}
|
|
1343
|
+
detachAfterObserver(observer) {
|
|
1344
|
+
this.afterObservers.delete(observer);
|
|
1071
1345
|
}
|
|
1072
1346
|
async createStatemachine(subject) {
|
|
1073
1347
|
const process = this.processDetector.detectProcess(subject);
|
|
1074
|
-
const stateName = this.stateNameDetector ? this.stateNameDetector.detectCurrentStateName(subject) :
|
|
1075
|
-
const mutex = this.mutexFactory ? await this.mutexFactory.createMutex(subject) :
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
);
|
|
1083
|
-
|
|
1084
|
-
statemachine.attach(observer);
|
|
1085
|
-
}
|
|
1086
|
-
return statemachine;
|
|
1348
|
+
const stateName = this.stateNameDetector ? this.stateNameDetector.detectCurrentStateName(subject) : void 0;
|
|
1349
|
+
const mutex = this.mutexFactory ? await this.mutexFactory.createMutex(subject) : void 0;
|
|
1350
|
+
const sm = new Statemachine(subject, process, {
|
|
1351
|
+
initialStateName: stateName ?? void 0,
|
|
1352
|
+
transitionSelector: this.transitionSelector ?? void 0,
|
|
1353
|
+
mutex: mutex ?? void 0
|
|
1354
|
+
});
|
|
1355
|
+
for (const o of this.beforeObservers) sm.attachBefore(o);
|
|
1356
|
+
for (const o of this.afterObservers) sm.attachAfter(o);
|
|
1357
|
+
return sm;
|
|
1087
1358
|
}
|
|
1088
1359
|
};
|
|
1089
1360
|
|
|
@@ -1098,6 +1369,21 @@ var SingleProcessDetector = class {
|
|
|
1098
1369
|
}
|
|
1099
1370
|
};
|
|
1100
1371
|
|
|
1372
|
+
// src/error/ProcessNotFoundError.ts
|
|
1373
|
+
var ProcessNotFoundError = class extends FinitaError {
|
|
1374
|
+
code = "processNotFound";
|
|
1375
|
+
processName;
|
|
1376
|
+
availableProcesses;
|
|
1377
|
+
constructor(processName, availableProcesses) {
|
|
1378
|
+
const list = Array.from(availableProcesses);
|
|
1379
|
+
const display = list.length > 0 ? list.map((n) => `"${n}"`).join(", ") : "(none)";
|
|
1380
|
+
super(`Process "${processName}" not found. Available: ${display}`);
|
|
1381
|
+
this.name = "ProcessNotFoundError";
|
|
1382
|
+
this.processName = processName;
|
|
1383
|
+
this.availableProcesses = Object.freeze([...list]);
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1101
1387
|
// src/factory/AbstractNamedProcessDetector.ts
|
|
1102
1388
|
var AbstractNamedProcessDetector = class {
|
|
1103
1389
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -1111,74 +1397,39 @@ var AbstractNamedProcessDetector = class {
|
|
|
1111
1397
|
const name = this.detectProcessName(subject);
|
|
1112
1398
|
const process = this.processes.get(name);
|
|
1113
1399
|
if (!process) {
|
|
1114
|
-
throw new
|
|
1400
|
+
throw new ProcessNotFoundError(name, this.processes.keys());
|
|
1115
1401
|
}
|
|
1116
1402
|
return process;
|
|
1117
1403
|
}
|
|
1118
1404
|
};
|
|
1119
1405
|
|
|
1406
|
+
// src/error/InvalidSubjectError.ts
|
|
1407
|
+
var InvalidSubjectError = class extends FinitaError {
|
|
1408
|
+
code = "invalidSubject";
|
|
1409
|
+
expectedInterface;
|
|
1410
|
+
missingMembers;
|
|
1411
|
+
constructor(expectedInterface, missingMembers) {
|
|
1412
|
+
const members = Array.from(missingMembers);
|
|
1413
|
+
const memberList = members.map((m) => `"${m}"`).join(", ");
|
|
1414
|
+
super(
|
|
1415
|
+
`Subject does not satisfy ${expectedInterface}; missing member(s): ${memberList || "(unknown)"}`
|
|
1416
|
+
);
|
|
1417
|
+
this.name = "InvalidSubjectError";
|
|
1418
|
+
this.expectedInterface = expectedInterface;
|
|
1419
|
+
this.missingMembers = Object.freeze([...members]);
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1120
1423
|
// src/factory/StatefulStateNameDetector.ts
|
|
1121
|
-
function
|
|
1424
|
+
function isStateful(obj) {
|
|
1122
1425
|
return typeof obj === "object" && obj !== null && "getCurrentStateName" in obj && typeof obj.getCurrentStateName === "function";
|
|
1123
1426
|
}
|
|
1124
1427
|
var StatefulStateNameDetector = class {
|
|
1125
1428
|
detectCurrentStateName(subject) {
|
|
1126
|
-
if (
|
|
1429
|
+
if (isStateful(subject)) {
|
|
1127
1430
|
return subject.getCurrentStateName();
|
|
1128
1431
|
}
|
|
1129
|
-
throw new
|
|
1130
|
-
}
|
|
1131
|
-
};
|
|
1132
|
-
|
|
1133
|
-
// src/util/SetupHelper.ts
|
|
1134
|
-
var SetupHelper = class {
|
|
1135
|
-
stateCollection;
|
|
1136
|
-
constructor(stateCollection) {
|
|
1137
|
-
this.stateCollection = stateCollection;
|
|
1138
|
-
}
|
|
1139
|
-
findOrCreateState(name) {
|
|
1140
|
-
if (!this.stateCollection.hasState(name)) {
|
|
1141
|
-
this.stateCollection.addState(new State(name));
|
|
1142
|
-
}
|
|
1143
|
-
return this.stateCollection.getState(name);
|
|
1144
|
-
}
|
|
1145
|
-
findTransition(sourceState, targetState, eventName = null, condition = null) {
|
|
1146
|
-
const conditionName = condition ? condition.getName() : null;
|
|
1147
|
-
for (const transition of sourceState.getTransitions()) {
|
|
1148
|
-
const hasSameTargetState = transition.getTargetState() === targetState;
|
|
1149
|
-
const hasSameCondition = transition.getConditionName() === conditionName;
|
|
1150
|
-
const hasSameEvent = transition.getEventName() === eventName;
|
|
1151
|
-
if (hasSameTargetState && hasSameCondition && hasSameEvent) {
|
|
1152
|
-
return transition;
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
return null;
|
|
1156
|
-
}
|
|
1157
|
-
findOrCreateTransition(sourceStateName, targetStateName, eventName = null, condition = null) {
|
|
1158
|
-
const sourceState = this.findOrCreateState(sourceStateName);
|
|
1159
|
-
const targetState = this.findOrCreateState(targetStateName);
|
|
1160
|
-
let transition = this.findTransition(
|
|
1161
|
-
sourceState,
|
|
1162
|
-
targetState,
|
|
1163
|
-
eventName,
|
|
1164
|
-
condition
|
|
1165
|
-
);
|
|
1166
|
-
if (!transition) {
|
|
1167
|
-
transition = new Transition(targetState, eventName, condition);
|
|
1168
|
-
sourceState.addTransition(transition);
|
|
1169
|
-
}
|
|
1170
|
-
return transition;
|
|
1171
|
-
}
|
|
1172
|
-
findOrCreateEvent(sourceStateName, eventName) {
|
|
1173
|
-
const sourceState = this.findOrCreateState(sourceStateName);
|
|
1174
|
-
return sourceState.getEvent(eventName);
|
|
1175
|
-
}
|
|
1176
|
-
addCommand(sourceStateName, eventName, command) {
|
|
1177
|
-
this.findOrCreateEvent(sourceStateName, eventName).attach(command);
|
|
1178
|
-
}
|
|
1179
|
-
addCommandAndSelfTransition(sourceStateName, eventName, command) {
|
|
1180
|
-
this.addCommand(sourceStateName, eventName, command);
|
|
1181
|
-
this.findOrCreateTransition(sourceStateName, sourceStateName, eventName);
|
|
1432
|
+
throw new InvalidSubjectError("StatefulInterface", ["getCurrentStateName"]);
|
|
1182
1433
|
}
|
|
1183
1434
|
};
|
|
1184
1435
|
|
|
@@ -1198,7 +1449,7 @@ function toMermaidId(name) {
|
|
|
1198
1449
|
function escapeMermaidLabel(str) {
|
|
1199
1450
|
return str.replace(/"/g, "#quot;");
|
|
1200
1451
|
}
|
|
1201
|
-
function
|
|
1452
|
+
function convertToString(obj) {
|
|
1202
1453
|
if (isNamed2(obj)) {
|
|
1203
1454
|
return obj.getName();
|
|
1204
1455
|
}
|
|
@@ -1207,6 +1458,7 @@ function convertToString2(obj) {
|
|
|
1207
1458
|
var GraphBuilder = class {
|
|
1208
1459
|
nodes = /* @__PURE__ */ new Map();
|
|
1209
1460
|
edges = [];
|
|
1461
|
+
statesWithEdges = /* @__PURE__ */ new Set();
|
|
1210
1462
|
getOrCreateNode(state) {
|
|
1211
1463
|
const name = state.getName();
|
|
1212
1464
|
let node = this.nodes.get(name);
|
|
@@ -1224,7 +1476,7 @@ var GraphBuilder = class {
|
|
|
1224
1476
|
const event = state.getEvent(eventName);
|
|
1225
1477
|
const observerNames = [];
|
|
1226
1478
|
for (const observer of event.getObservers()) {
|
|
1227
|
-
observerNames.push(
|
|
1479
|
+
observerNames.push(convertToString(observer));
|
|
1228
1480
|
}
|
|
1229
1481
|
if (observerNames.length > 0) {
|
|
1230
1482
|
parts.push(`C: ${observerNames.join(", ")}`);
|
|
@@ -1239,6 +1491,9 @@ var GraphBuilder = class {
|
|
|
1239
1491
|
}
|
|
1240
1492
|
addState(state) {
|
|
1241
1493
|
this.getOrCreateNode(state);
|
|
1494
|
+
const name = state.getName();
|
|
1495
|
+
if (this.statesWithEdges.has(name)) return;
|
|
1496
|
+
this.statesWithEdges.add(name);
|
|
1242
1497
|
for (const transition of state.getTransitions()) {
|
|
1243
1498
|
const sourceNode = this.getOrCreateNode(state);
|
|
1244
1499
|
const targetNode = this.getOrCreateNode(transition.getTargetState());
|
|
@@ -1316,19 +1571,24 @@ var GraphBuilder = class {
|
|
|
1316
1571
|
export {
|
|
1317
1572
|
AbstractNamedProcessDetector,
|
|
1318
1573
|
ActiveTransitionFilter,
|
|
1574
|
+
AmbiguousTransitionError,
|
|
1319
1575
|
AndComposite,
|
|
1576
|
+
AutomaticTransitionCycleError,
|
|
1320
1577
|
CallbackCondition,
|
|
1321
1578
|
CallbackObserver,
|
|
1322
1579
|
Contradiction,
|
|
1323
|
-
Dispatcher,
|
|
1324
1580
|
DuplicateStateError,
|
|
1581
|
+
DuplicateTransitionError,
|
|
1325
1582
|
Event,
|
|
1326
1583
|
Factory,
|
|
1327
1584
|
FilterStateByEvent,
|
|
1328
1585
|
FilterStateByFinalState,
|
|
1329
1586
|
FilterStateByTransition,
|
|
1330
1587
|
FilterTransitionByEvent,
|
|
1588
|
+
FinitaError,
|
|
1331
1589
|
GraphBuilder,
|
|
1590
|
+
GraphValidationError,
|
|
1591
|
+
InvalidSubjectError,
|
|
1332
1592
|
LockAdapterMutex,
|
|
1333
1593
|
LockCanNotBeAcquiredError,
|
|
1334
1594
|
MutexFactory,
|
|
@@ -1338,12 +1598,14 @@ export {
|
|
|
1338
1598
|
OneOrNoneActiveTransition,
|
|
1339
1599
|
OrComposite,
|
|
1340
1600
|
Process,
|
|
1601
|
+
ProcessBuilder,
|
|
1602
|
+
ProcessFinalizedError,
|
|
1603
|
+
ProcessNotFoundError,
|
|
1341
1604
|
ScoreTransition,
|
|
1342
|
-
SetupHelper,
|
|
1343
1605
|
SingleProcessDetector,
|
|
1344
1606
|
State,
|
|
1345
|
-
|
|
1346
|
-
|
|
1607
|
+
StateEventNotFoundError,
|
|
1608
|
+
StateNotFoundError,
|
|
1347
1609
|
StatefulStateNameDetector,
|
|
1348
1610
|
StatefulStatusChanger,
|
|
1349
1611
|
Statemachine,
|