machina 6.0.0 → 6.1.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 +6 -1
- package/dist/index.cjs +661 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +654 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +319 -293
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +638 -634
- package/dist/index.mjs.map +1 -1
- package/package.json +27 -9
- package/dist/index.d.ts +0 -628
- package/dist/index.js +0 -656
- package/dist/index.js.map +0 -1
package/dist/index.js
DELETED
|
@@ -1,656 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// src/emitter.ts
|
|
4
|
-
var Emitter = class {
|
|
5
|
-
listeners = /* @__PURE__ */ new Map();
|
|
6
|
-
on(event, cb) {
|
|
7
|
-
let set = this.listeners.get(event);
|
|
8
|
-
if (!set) {
|
|
9
|
-
set = /* @__PURE__ */ new Set();
|
|
10
|
-
this.listeners.set(event, set);
|
|
11
|
-
}
|
|
12
|
-
set.add(cb);
|
|
13
|
-
return {
|
|
14
|
-
off: () => {
|
|
15
|
-
set.delete(cb);
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Emit a named event, notifying wildcard listeners first, then named listeners.
|
|
21
|
-
*
|
|
22
|
-
* Wildcard listeners receive `(eventName, data)`; named listeners receive
|
|
23
|
-
* only `data`. The firing order (wildcards before named) is intentional —
|
|
24
|
-
* it lets relay listeners (like Fsm's bfsm proxy) observe all events
|
|
25
|
-
* before specific subscribers react.
|
|
26
|
-
*/
|
|
27
|
-
emit(event, data) {
|
|
28
|
-
const wildcards = this.listeners.get("*");
|
|
29
|
-
if (wildcards) {
|
|
30
|
-
for (const cb of wildcards) {
|
|
31
|
-
cb(event, data);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
const named = this.listeners.get(event);
|
|
35
|
-
if (named) {
|
|
36
|
-
for (const cb of named) {
|
|
37
|
-
cb(data);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Remove all listeners (named and wildcard).
|
|
43
|
-
*
|
|
44
|
-
* Existing `Subscription` objects remain valid — their `off()` closures
|
|
45
|
-
* hold a reference to the now-empty Set, so calling them is harmless.
|
|
46
|
-
* Called by `dispose()` to prevent memory retention after an FSM shuts down.
|
|
47
|
-
*/
|
|
48
|
-
clear() {
|
|
49
|
-
this.listeners.clear();
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// src/types.ts
|
|
54
|
-
var MACHINA_TYPE = /* @__PURE__ */ Symbol("machina.type");
|
|
55
|
-
|
|
56
|
-
// src/behavioral-fsm.ts
|
|
57
|
-
var MAX_TRANSITION_DEPTH = 20;
|
|
58
|
-
var BehavioralFsm = class {
|
|
59
|
-
id;
|
|
60
|
-
initialState;
|
|
61
|
-
// Type discriminant — lets ChildLink adapter identify this at runtime
|
|
62
|
-
[MACHINA_TYPE] = "BehavioralFsm";
|
|
63
|
-
states;
|
|
64
|
-
emitter = new Emitter();
|
|
65
|
-
clients = /* @__PURE__ */ new WeakMap();
|
|
66
|
-
// Tracks all initialized clients so the Fsm-child nohandler listener can
|
|
67
|
-
// find which clients need bubbling (Fsm events have no client in payload).
|
|
68
|
-
// WeakRef prevents retention; clients clean themselves up naturally.
|
|
69
|
-
knownClients = /* @__PURE__ */ new Set();
|
|
70
|
-
// Subscriptions to child FSM wildcard events, keyed by state name.
|
|
71
|
-
// Set up once during construction, torn down in dispose().
|
|
72
|
-
childSubscriptions = [];
|
|
73
|
-
disposed = false;
|
|
74
|
-
transitionDepth = 0;
|
|
75
|
-
constructor(config) {
|
|
76
|
-
this.id = config.id;
|
|
77
|
-
this.initialState = config.initialState;
|
|
78
|
-
this.states = config.states;
|
|
79
|
-
this.wrapChildLinks();
|
|
80
|
-
this.setupChildSubscriptions();
|
|
81
|
-
}
|
|
82
|
-
// -------------------------------------------------------------------------
|
|
83
|
-
// Public API
|
|
84
|
-
// -------------------------------------------------------------------------
|
|
85
|
-
/**
|
|
86
|
-
* Dispatch an input to the given client's current state handler.
|
|
87
|
-
*
|
|
88
|
-
* Delegation order: if the current state has a `_child` FSM that can
|
|
89
|
-
* handle the input, it is dispatched there. If the child emits `nohandler`,
|
|
90
|
-
* the input bubbles up to this FSM's local handler. If no handler exists
|
|
91
|
-
* here either, `nohandler` is emitted on this FSM's emitter.
|
|
92
|
-
*
|
|
93
|
-
* No-ops silently when disposed.
|
|
94
|
-
*/
|
|
95
|
-
handle(client, inputName, ...args) {
|
|
96
|
-
if (this.disposed) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const meta = this.getOrCreateClientMeta(client);
|
|
100
|
-
meta.currentActionArgs = args;
|
|
101
|
-
const stateObj = this.states[meta.state];
|
|
102
|
-
const childLink = stateObj?._child;
|
|
103
|
-
if (childLink) {
|
|
104
|
-
if (childLink.canHandle(client, inputName)) {
|
|
105
|
-
try {
|
|
106
|
-
childLink.handle(client, inputName, ...args);
|
|
107
|
-
} finally {
|
|
108
|
-
meta.currentActionArgs = void 0;
|
|
109
|
-
}
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
this.handleLocally(client, inputName, args, meta);
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Returns true if the client's current state has a handler for `inputName`
|
|
117
|
-
* (or a catch-all `"*"` handler). Does NOT initialize the client — no
|
|
118
|
-
* `_onEnter`, no events, no side effects. Unseen clients are treated as
|
|
119
|
-
* if they were already in `initialState`. Returns false when disposed.
|
|
120
|
-
*/
|
|
121
|
-
canHandle(client, inputName) {
|
|
122
|
-
if (this.disposed) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
const state = this.clients.get(client)?.state ?? this.initialState;
|
|
126
|
-
const stateObj = this.states[state];
|
|
127
|
-
return !!(stateObj?.[inputName] ?? stateObj?.["*"]);
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Transition the client back to `initialState`, firing `_onEnter` and
|
|
131
|
-
* lifecycle events as if entering it fresh. No-ops when disposed.
|
|
132
|
-
*/
|
|
133
|
-
reset(client) {
|
|
134
|
-
if (this.disposed) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
this.transition(client, this.initialState);
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Returns the client's current state, or `undefined` if the client has
|
|
141
|
-
* never been initialized (i.e. `handle()`, `transition()`, or `reset()`
|
|
142
|
-
* have never been called for it). Does NOT trigger initialization.
|
|
143
|
-
*/
|
|
144
|
-
currentState(client) {
|
|
145
|
-
return this.clients.get(client)?.state;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Directly transition `client` to `toState`, running the full lifecycle:
|
|
149
|
-
* `_onExit` for the current state → `transitioning` event → update state →
|
|
150
|
-
* `_onEnter` for new state → `transitioned` event → child reset → deferred
|
|
151
|
-
* queue replay → bounce (if `_onEnter` returned a state name).
|
|
152
|
-
*
|
|
153
|
-
* Same-state transitions are silently ignored. Transitions to unknown state
|
|
154
|
-
* names emit `invalidstate` instead of throwing. Throws if the transition
|
|
155
|
-
* depth exceeds `MAX_TRANSITION_DEPTH` (likely an `_onEnter` → transition loop).
|
|
156
|
-
*
|
|
157
|
-
* No-ops when disposed.
|
|
158
|
-
*/
|
|
159
|
-
transition(client, toState) {
|
|
160
|
-
if (this.disposed) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
const meta = this.getOrCreateClientMeta(client);
|
|
164
|
-
const fromState = meta.state;
|
|
165
|
-
if (toState === fromState) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (!(toState in this.states)) {
|
|
169
|
-
this.emitter.emit("invalidstate", { stateName: toState, client });
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
this.transitionDepth++;
|
|
173
|
-
if (this.transitionDepth > MAX_TRANSITION_DEPTH) {
|
|
174
|
-
this.transitionDepth = 0;
|
|
175
|
-
throw new Error(
|
|
176
|
-
`Max transition depth (${MAX_TRANSITION_DEPTH}) exceeded in FSM "${this.id}". Likely an infinite _onEnter \u2192 transition loop.`
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
try {
|
|
180
|
-
const curStateObj = this.states[fromState];
|
|
181
|
-
const newStateObj = this.states[toState];
|
|
182
|
-
if (curStateObj?._onExit && typeof curStateObj._onExit === "function") {
|
|
183
|
-
const exitArgs = this.buildHandlerArgs(client, "", meta);
|
|
184
|
-
curStateObj._onExit(exitArgs);
|
|
185
|
-
}
|
|
186
|
-
meta.state = toState;
|
|
187
|
-
const payload = { fromState, toState, client };
|
|
188
|
-
this.emitter.emit("transitioning", payload);
|
|
189
|
-
let bounceTarget = void 0;
|
|
190
|
-
if (newStateObj?._onEnter && typeof newStateObj._onEnter === "function") {
|
|
191
|
-
const enterArgs = this.buildHandlerArgs(client, "", meta);
|
|
192
|
-
bounceTarget = newStateObj._onEnter(enterArgs);
|
|
193
|
-
}
|
|
194
|
-
this.emitter.emit("transitioned", payload);
|
|
195
|
-
const childLink = newStateObj?._child;
|
|
196
|
-
if (childLink) {
|
|
197
|
-
childLink.reset(client);
|
|
198
|
-
}
|
|
199
|
-
this.processQueue(client, meta);
|
|
200
|
-
if (typeof bounceTarget === "string" && meta.state === toState) {
|
|
201
|
-
this.transition(client, bounceTarget);
|
|
202
|
-
}
|
|
203
|
-
} finally {
|
|
204
|
-
this.transitionDepth--;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Returns the client's state as a dot-delimited path including any active
|
|
209
|
-
* child FSM states (e.g. `"active.connecting.retrying"`). Returns just the
|
|
210
|
-
* current state name when no child is active. Returns `""` for clients that
|
|
211
|
-
* have never been initialized (unlike `currentState()` which returns `undefined`).
|
|
212
|
-
*/
|
|
213
|
-
compositeState(client) {
|
|
214
|
-
const meta = this.clients.get(client);
|
|
215
|
-
if (!meta) {
|
|
216
|
-
return "";
|
|
217
|
-
}
|
|
218
|
-
const stateObj = this.states[meta.state];
|
|
219
|
-
const childLink = stateObj?._child;
|
|
220
|
-
if (childLink) {
|
|
221
|
-
const childComposite = childLink.compositeState(client);
|
|
222
|
-
if (childComposite) {
|
|
223
|
-
return `${meta.state}.${childComposite}`;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return meta.state;
|
|
227
|
-
}
|
|
228
|
-
on(eventName, callback) {
|
|
229
|
-
if (this.disposed) {
|
|
230
|
-
return { off() {
|
|
231
|
-
} };
|
|
232
|
-
}
|
|
233
|
-
return this.emitter.on(eventName, callback);
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Emit a custom event through the FSM. Built-in lifecycle events are
|
|
237
|
-
* emitted automatically — this is for user-defined events from handlers.
|
|
238
|
-
* No-ops when disposed.
|
|
239
|
-
*/
|
|
240
|
-
emit(eventName, data) {
|
|
241
|
-
if (this.disposed) {
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
this.emitter.emit(eventName, data);
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Permanently shut down this FSM. Irreversible — all subsequent method
|
|
248
|
-
* calls become silent no-ops. Tears down child subscriptions, clears all
|
|
249
|
-
* listeners, and cascades disposal to child FSMs (unless `preserveChildren`
|
|
250
|
-
* is set). The same child appearing in multiple states is disposed once.
|
|
251
|
-
*/
|
|
252
|
-
dispose(options) {
|
|
253
|
-
this.disposed = true;
|
|
254
|
-
for (const sub of this.childSubscriptions) {
|
|
255
|
-
sub.off();
|
|
256
|
-
}
|
|
257
|
-
if (!options?.preserveChildren) {
|
|
258
|
-
const seen = /* @__PURE__ */ new Set();
|
|
259
|
-
for (const stateName of Object.keys(this.states)) {
|
|
260
|
-
const childLink = this.states[stateName]?._child;
|
|
261
|
-
if (childLink && !seen.has(childLink)) {
|
|
262
|
-
seen.add(childLink);
|
|
263
|
-
childLink.dispose();
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
this.emitter.clear();
|
|
268
|
-
}
|
|
269
|
-
// -------------------------------------------------------------------------
|
|
270
|
-
// Internal
|
|
271
|
-
// -------------------------------------------------------------------------
|
|
272
|
-
/**
|
|
273
|
-
* Walks all states at construction time, detects raw FSM instances assigned
|
|
274
|
-
* to _child, and wraps them into ChildLink adapters via createChildLink().
|
|
275
|
-
* Must run BEFORE setupChildSubscriptions() so the subscriptions see
|
|
276
|
-
* ChildLink objects, not raw FSM instances.
|
|
277
|
-
*/
|
|
278
|
-
wrapChildLinks() {
|
|
279
|
-
for (const stateName of Object.keys(this.states)) {
|
|
280
|
-
const stateObj = this.states[stateName];
|
|
281
|
-
const rawChild = stateObj?._child;
|
|
282
|
-
if (!rawChild) {
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
if (typeof rawChild !== "object") {
|
|
286
|
-
throw new Error(
|
|
287
|
-
`State "${stateName}"._child: expected an Fsm or BehavioralFsm instance, got ${String(rawChild)}`
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
if (!(MACHINA_TYPE in rawChild)) {
|
|
291
|
-
throw new Error(
|
|
292
|
-
`State "${stateName}"._child: expected an Fsm or BehavioralFsm instance, got a plain object`
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
stateObj._child = createChildLink(rawChild);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Walks all states at construction time, finds states with _child, and
|
|
300
|
-
* subscribes once to each unique child's wildcard events. Subscriptions are
|
|
301
|
-
* stored for cleanup in dispose(). We deduplicate by child reference to
|
|
302
|
-
* avoid double-subscribing when the same child appears in multiple states.
|
|
303
|
-
*/
|
|
304
|
-
setupChildSubscriptions() {
|
|
305
|
-
const seenChildren = /* @__PURE__ */ new Set();
|
|
306
|
-
for (const stateName of Object.keys(this.states)) {
|
|
307
|
-
const stateObj = this.states[stateName];
|
|
308
|
-
const childLink = stateObj?._child;
|
|
309
|
-
if (!childLink || seenChildren.has(childLink)) {
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
seenChildren.add(childLink);
|
|
313
|
-
const sub = childLink.onAny((eventName, data) => {
|
|
314
|
-
if (eventName === "nohandler") {
|
|
315
|
-
const payload2 = data;
|
|
316
|
-
if (payload2.client !== void 0) {
|
|
317
|
-
this.bubbleNohandler(
|
|
318
|
-
payload2.client,
|
|
319
|
-
childLink,
|
|
320
|
-
payload2.inputName,
|
|
321
|
-
payload2.args ?? []
|
|
322
|
-
);
|
|
323
|
-
} else {
|
|
324
|
-
for (const ref of this.knownClients) {
|
|
325
|
-
const client = ref.deref();
|
|
326
|
-
if (client === void 0) {
|
|
327
|
-
this.knownClients.delete(ref);
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
this.bubbleNohandler(
|
|
331
|
-
client,
|
|
332
|
-
childLink,
|
|
333
|
-
payload2.inputName,
|
|
334
|
-
payload2.args ?? []
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
const payload = data;
|
|
341
|
-
if (payload && typeof payload === "object" && "client" in payload) {
|
|
342
|
-
if (this.isChildActiveForClient(payload.client, childLink)) {
|
|
343
|
-
this.emitter.emit(eventName, data);
|
|
344
|
-
}
|
|
345
|
-
} else {
|
|
346
|
-
for (const ref of this.knownClients) {
|
|
347
|
-
const client = ref.deref();
|
|
348
|
-
if (!client) {
|
|
349
|
-
this.knownClients.delete(ref);
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
if (this.isChildActiveForClient(client, childLink)) {
|
|
353
|
-
this.emitter.emit(eventName, data);
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
this.childSubscriptions.push(sub);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Bubbles a child nohandler to the parent for the given client.
|
|
364
|
-
* Only fires if the client is currently in a state that has this childLink.
|
|
365
|
-
* Extracted from the lambda in setupChildSubscriptions to keep it readable.
|
|
366
|
-
*/
|
|
367
|
-
bubbleNohandler(client, childLink, inputName, args) {
|
|
368
|
-
if (!this.isChildActiveForClient(client, childLink)) {
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
const meta = this.clients.get(client);
|
|
372
|
-
meta.currentActionArgs = args;
|
|
373
|
-
this.handleLocally(client, inputName, args, meta);
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Returns true if the given client is currently in a parent state whose
|
|
377
|
-
* _child is the specified childLink. Returns false if the client has no
|
|
378
|
-
* meta (never initialized) or is in a state with a different (or no) child.
|
|
379
|
-
*/
|
|
380
|
-
isChildActiveForClient(client, childLink) {
|
|
381
|
-
const meta = this.clients.get(client);
|
|
382
|
-
if (!meta) {
|
|
383
|
-
return false;
|
|
384
|
-
}
|
|
385
|
-
const parentStateObj = this.states[meta.state];
|
|
386
|
-
return parentStateObj?._child === childLink;
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* The inner handler dispatch — no delegation, no initialization side effects
|
|
390
|
-
* beyond what getOrCreateClientMeta already did. Called by handle() after
|
|
391
|
-
* the delegation check, and by the nohandler child listener for bubbling.
|
|
392
|
-
*/
|
|
393
|
-
handleLocally(client, inputName, args, meta) {
|
|
394
|
-
const stateObj = this.states[meta.state];
|
|
395
|
-
const handler = stateObj?.[inputName] ?? stateObj?.["*"];
|
|
396
|
-
if (!handler) {
|
|
397
|
-
this.emitter.emit("nohandler", { inputName, args, client });
|
|
398
|
-
meta.currentActionArgs = void 0;
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
try {
|
|
402
|
-
this.emitter.emit("handling", { inputName, client });
|
|
403
|
-
const handlerArgs = this.buildHandlerArgs(client, inputName, meta);
|
|
404
|
-
let targetState = void 0;
|
|
405
|
-
if (typeof handler === "string") {
|
|
406
|
-
targetState = handler;
|
|
407
|
-
} else if (typeof handler === "function") {
|
|
408
|
-
targetState = handler(handlerArgs, ...args);
|
|
409
|
-
}
|
|
410
|
-
this.emitter.emit("handled", { inputName, client });
|
|
411
|
-
if (typeof targetState === "string") {
|
|
412
|
-
this.transition(client, targetState);
|
|
413
|
-
}
|
|
414
|
-
} finally {
|
|
415
|
-
meta.currentActionArgs = void 0;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
getOrCreateClientMeta(client) {
|
|
419
|
-
let meta = this.clients.get(client);
|
|
420
|
-
if (meta) {
|
|
421
|
-
return meta;
|
|
422
|
-
}
|
|
423
|
-
meta = {
|
|
424
|
-
state: void 0,
|
|
425
|
-
deferredQueue: []
|
|
426
|
-
};
|
|
427
|
-
this.clients.set(client, meta);
|
|
428
|
-
this.knownClients.add(new WeakRef(client));
|
|
429
|
-
this.transition(client, this.initialState);
|
|
430
|
-
return meta;
|
|
431
|
-
}
|
|
432
|
-
buildHandlerArgs(client, inputName, meta) {
|
|
433
|
-
return {
|
|
434
|
-
ctx: client,
|
|
435
|
-
inputName,
|
|
436
|
-
defer: (opts) => {
|
|
437
|
-
if (!meta.currentActionArgs) {
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
const deferred = {
|
|
441
|
-
inputName,
|
|
442
|
-
args: [...meta.currentActionArgs],
|
|
443
|
-
untilState: opts?.until
|
|
444
|
-
};
|
|
445
|
-
meta.deferredQueue.push(deferred);
|
|
446
|
-
this.emitter.emit("deferred", { inputName, client });
|
|
447
|
-
},
|
|
448
|
-
emit: (evtName, evtData) => {
|
|
449
|
-
this.emitter.emit(evtName, evtData);
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
processQueue(client, meta) {
|
|
454
|
-
const toReplay = [];
|
|
455
|
-
const remaining = [];
|
|
456
|
-
for (const item of meta.deferredQueue) {
|
|
457
|
-
if (item.untilState === void 0 || item.untilState === meta.state) {
|
|
458
|
-
toReplay.push(item);
|
|
459
|
-
} else {
|
|
460
|
-
remaining.push(item);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
meta.deferredQueue = remaining;
|
|
464
|
-
for (const item of toReplay) {
|
|
465
|
-
this.handle(client, item.inputName, ...item.args);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
function createChildLink(child) {
|
|
470
|
-
if (!child || typeof child !== "object") {
|
|
471
|
-
throw new Error(
|
|
472
|
-
`createChildLink: expected an Fsm or BehavioralFsm instance, got ${String(child)}`
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
const childType = child[MACHINA_TYPE];
|
|
476
|
-
if (childType === "BehavioralFsm") {
|
|
477
|
-
return {
|
|
478
|
-
canHandle(client, inputName) {
|
|
479
|
-
return child.canHandle(client, inputName);
|
|
480
|
-
},
|
|
481
|
-
handle(client, inputName, ...args) {
|
|
482
|
-
child.handle(client, inputName, ...args);
|
|
483
|
-
},
|
|
484
|
-
reset(client) {
|
|
485
|
-
child.transition(client, child.initialState);
|
|
486
|
-
},
|
|
487
|
-
onAny(callback) {
|
|
488
|
-
return child.on("*", callback);
|
|
489
|
-
},
|
|
490
|
-
compositeState(client) {
|
|
491
|
-
return child.compositeState(client);
|
|
492
|
-
},
|
|
493
|
-
dispose() {
|
|
494
|
-
child.dispose();
|
|
495
|
-
}
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
if (childType === "Fsm") {
|
|
499
|
-
return {
|
|
500
|
-
canHandle(_client, inputName) {
|
|
501
|
-
return child.canHandle(inputName);
|
|
502
|
-
},
|
|
503
|
-
handle(_client, inputName, ...args) {
|
|
504
|
-
child.handle(inputName, ...args);
|
|
505
|
-
},
|
|
506
|
-
reset(_client) {
|
|
507
|
-
child.reset();
|
|
508
|
-
},
|
|
509
|
-
onAny(callback) {
|
|
510
|
-
return child.on("*", callback);
|
|
511
|
-
},
|
|
512
|
-
compositeState(_client) {
|
|
513
|
-
return child.compositeState();
|
|
514
|
-
},
|
|
515
|
-
dispose() {
|
|
516
|
-
child.dispose();
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
throw new Error(
|
|
521
|
-
`createChildLink: expected an Fsm or BehavioralFsm instance, got [MACHINA_TYPE] = ${String(childType ?? "undefined")}`
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
function createBehavioralFsm(config) {
|
|
525
|
-
return new BehavioralFsm(config);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// src/fsm.ts
|
|
529
|
-
var Fsm = class {
|
|
530
|
-
id;
|
|
531
|
-
// Type discriminant — lets ChildLink adapter identify this at runtime
|
|
532
|
-
[MACHINA_TYPE] = "Fsm";
|
|
533
|
-
bfsm;
|
|
534
|
-
context;
|
|
535
|
-
// FsmEventMap is an interface, which lacks the implicit index signature
|
|
536
|
-
// that Emitter's Record<string, unknown> constraint needs. The mapped type
|
|
537
|
-
// BehavioralFsmEventMap doesn't have this issue. Intersect with Record to
|
|
538
|
-
// satisfy the constraint while preserving typed event signatures.
|
|
539
|
-
emitter = new Emitter();
|
|
540
|
-
disposed = false;
|
|
541
|
-
constructor(config) {
|
|
542
|
-
this.id = config.id;
|
|
543
|
-
this.context = config.context ?? {};
|
|
544
|
-
this.bfsm = new BehavioralFsm(config);
|
|
545
|
-
this.bfsm.on("*", (eventName, data) => {
|
|
546
|
-
if (data && typeof data === "object" && "client" in data) {
|
|
547
|
-
const { client: _, ...payload } = data;
|
|
548
|
-
this.emitter.emit(eventName, payload);
|
|
549
|
-
} else {
|
|
550
|
-
this.emitter.emit(eventName, data);
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
this.bfsm.transition(this.context, config.initialState);
|
|
554
|
-
}
|
|
555
|
-
// -------------------------------------------------------------------------
|
|
556
|
-
// Public API — same as BehavioralFsm, minus the client parameter
|
|
557
|
-
// -------------------------------------------------------------------------
|
|
558
|
-
/**
|
|
559
|
-
* Dispatch an input to the current state's handler.
|
|
560
|
-
* If a `_child` FSM in the current state can handle it, delegation occurs
|
|
561
|
-
* there first; unhandled inputs bubble up to the parent.
|
|
562
|
-
* No-ops silently when disposed.
|
|
563
|
-
*/
|
|
564
|
-
handle(inputName, ...args) {
|
|
565
|
-
if (this.disposed) {
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
this.bfsm.handle(this.context, inputName, ...args);
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Returns true if the current state has a handler for `inputName`
|
|
572
|
-
* (or a catch-all `"*"` handler). Does not trigger initialization
|
|
573
|
-
* or any side effects. Returns false when disposed.
|
|
574
|
-
*/
|
|
575
|
-
canHandle(inputName) {
|
|
576
|
-
if (this.disposed) {
|
|
577
|
-
return false;
|
|
578
|
-
}
|
|
579
|
-
return this.bfsm.canHandle(this.context, inputName);
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* Transition back to `initialState`, firing `_onEnter` and lifecycle
|
|
583
|
-
* events as if entering it fresh. No-ops silently when disposed.
|
|
584
|
-
*/
|
|
585
|
-
reset() {
|
|
586
|
-
if (this.disposed) {
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
this.bfsm.reset(this.context);
|
|
590
|
-
}
|
|
591
|
-
/**
|
|
592
|
-
* Returns the current state name. Always defined — Fsm eagerly
|
|
593
|
-
* initializes into `initialState` during construction.
|
|
594
|
-
*/
|
|
595
|
-
currentState() {
|
|
596
|
-
return this.bfsm.currentState(this.context);
|
|
597
|
-
}
|
|
598
|
-
/**
|
|
599
|
-
* Directly transition to `toState`, firing `_onExit`, `_onEnter`, and
|
|
600
|
-
* lifecycle events. Same-state transitions are silently ignored.
|
|
601
|
-
* No-ops when disposed.
|
|
602
|
-
*/
|
|
603
|
-
transition(toState) {
|
|
604
|
-
if (this.disposed) {
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
this.bfsm.transition(this.context, toState);
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* Returns the current state as a dot-delimited path that includes
|
|
611
|
-
* any active child FSM states (e.g. `"active.connecting.retrying"`).
|
|
612
|
-
* Returns just the current state name when no child is active.
|
|
613
|
-
*/
|
|
614
|
-
compositeState() {
|
|
615
|
-
return this.bfsm.compositeState(this.context);
|
|
616
|
-
}
|
|
617
|
-
on(eventName, callback) {
|
|
618
|
-
if (this.disposed) {
|
|
619
|
-
return { off() {
|
|
620
|
-
} };
|
|
621
|
-
}
|
|
622
|
-
return this.emitter.on(eventName, callback);
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* Emit a custom event through the FSM. Built-in lifecycle events are
|
|
626
|
-
* emitted automatically — this is for user-defined events from handlers.
|
|
627
|
-
* Routes through the BehavioralFsm so all relay paths are consistent.
|
|
628
|
-
* No-ops when disposed.
|
|
629
|
-
*/
|
|
630
|
-
emit(eventName, data) {
|
|
631
|
-
if (this.disposed) {
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
this.bfsm.emit(eventName, data);
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
637
|
-
* Permanently shut down this FSM. Irreversible — all subsequent method
|
|
638
|
-
* calls become silent no-ops. Clears all listeners and cascades disposal
|
|
639
|
-
* to child FSMs (unless `preserveChildren` is set).
|
|
640
|
-
*/
|
|
641
|
-
dispose(options) {
|
|
642
|
-
this.disposed = true;
|
|
643
|
-
this.bfsm.dispose(options);
|
|
644
|
-
this.emitter.clear();
|
|
645
|
-
}
|
|
646
|
-
};
|
|
647
|
-
function createFsm(config) {
|
|
648
|
-
return new Fsm(config);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
exports.BehavioralFsm = BehavioralFsm;
|
|
652
|
-
exports.Fsm = Fsm;
|
|
653
|
-
exports.createBehavioralFsm = createBehavioralFsm;
|
|
654
|
-
exports.createFsm = createFsm;
|
|
655
|
-
//# sourceMappingURL=index.js.map
|
|
656
|
-
//# sourceMappingURL=index.js.map
|