machina 6.3.0 → 7.0.1
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 +35 -13
- package/dist/index.cjs +333 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +650 -236
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +650 -236
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +333 -60
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/emitter.ts","../src/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/emitter.ts","../src/behavioral-fsm.ts","../src/types.ts","../src/fsm.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -49,6 +49,121 @@ var Emitter = class {
|
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/json-safe.ts
|
|
54
|
+
/**
|
|
55
|
+
* Thrown internally when the walk hits a value that can't survive a
|
|
56
|
+
* serialization boundary. Callers catch this to attach FSM/input context
|
|
57
|
+
* before re-throwing a fully descriptive error — see `path`/`label` below.
|
|
58
|
+
*/
|
|
59
|
+
var NonSerializableValueError = class extends Error {
|
|
60
|
+
constructor(path, label) {
|
|
61
|
+
super(`non-serializable value at ${path} (${label})`);
|
|
62
|
+
this.path = path;
|
|
63
|
+
this.label = label;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Deep-clones `value`, throwing `NonSerializableValueError` the moment it
|
|
68
|
+
* finds anything that isn't `null`, a boolean, a finite number, a string, a
|
|
69
|
+
* plain array, or a plain object. `rootPath` seeds the path used in error
|
|
70
|
+
* messages — e.g. `"args"` so a nested failure reads as `args[1].onComplete`.
|
|
71
|
+
*
|
|
72
|
+
* Cloning (rather than just validating) is what keeps a returned snapshot
|
|
73
|
+
* from aliasing live FSM state: mutating the snapshot afterward — or the
|
|
74
|
+
* original value passed into a deferred `handle()` call — can't reach back
|
|
75
|
+
* into the FSM's internal deferred queue.
|
|
76
|
+
*/
|
|
77
|
+
const cloneJsonSafe = (value, rootPath) => {
|
|
78
|
+
return cloneNode(value, rootPath, /* @__PURE__ */ new Set());
|
|
79
|
+
};
|
|
80
|
+
const cloneNode = (value, path, ancestors) => {
|
|
81
|
+
if (value === null) return null;
|
|
82
|
+
switch (typeof value) {
|
|
83
|
+
case "string":
|
|
84
|
+
case "boolean": return value;
|
|
85
|
+
case "number":
|
|
86
|
+
if (!Number.isFinite(value)) throw new NonSerializableValueError(path, describeNonFiniteNumber(value));
|
|
87
|
+
return value;
|
|
88
|
+
case "object": return cloneObject(value, path, ancestors);
|
|
89
|
+
default: throw new NonSerializableValueError(path, typeof value);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const cloneObject = (obj, path, ancestors) => {
|
|
93
|
+
if (ancestors.has(obj)) throw new NonSerializableValueError(path, "circular reference");
|
|
94
|
+
if (Array.isArray(obj)) {
|
|
95
|
+
if (obj.length !== Object.keys(obj).length) throw new NonSerializableValueError(path, "sparse array or array with non-index properties");
|
|
96
|
+
ancestors.add(obj);
|
|
97
|
+
const cloned = obj.map((item, i) => cloneNode(item, `${path}[${i}]`, ancestors));
|
|
98
|
+
ancestors.delete(obj);
|
|
99
|
+
return cloned;
|
|
100
|
+
}
|
|
101
|
+
const proto = Object.getPrototypeOf(obj);
|
|
102
|
+
if (proto !== Object.prototype && proto !== null) throw new NonSerializableValueError(path, obj.constructor?.name ?? "object");
|
|
103
|
+
ancestors.add(obj);
|
|
104
|
+
const cloned = {};
|
|
105
|
+
for (const key of Object.keys(obj)) {
|
|
106
|
+
const value = cloneNode(obj[key], `${path}.${key}`, ancestors);
|
|
107
|
+
Object.defineProperty(cloned, key, {
|
|
108
|
+
value,
|
|
109
|
+
enumerable: true,
|
|
110
|
+
writable: true,
|
|
111
|
+
configurable: true
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
ancestors.delete(obj);
|
|
115
|
+
return cloned;
|
|
116
|
+
};
|
|
117
|
+
const describeNonFiniteNumber = (n) => {
|
|
118
|
+
if (Number.isNaN(n)) return "NaN";
|
|
119
|
+
return n > 0 ? "Infinity" : "-Infinity";
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Deep-clones `value` WITHOUT validating it — unlike `cloneJsonSafe`, this
|
|
123
|
+
* never throws. Plain objects/arrays are cloned recursively (so a caller
|
|
124
|
+
* can't alias state back into whatever holds the result); anything else
|
|
125
|
+
* (functions, `Date`/`Map`/class instances, symbols, non-finite numbers,
|
|
126
|
+
* etc.) is passed through by reference as-is.
|
|
127
|
+
*
|
|
128
|
+
* This is `rehydrate()`'s side of the aliasing guarantee: `dehydrate()`
|
|
129
|
+
* validates-and-clones on the way OUT (via `cloneJsonSafe`), but `rehydrate()`
|
|
130
|
+
* trusts the snapshot it's given is already valid data — re-validating on
|
|
131
|
+
* the way IN would be a redundant, unwanted asymmetry (see the build plan's
|
|
132
|
+
* disclosed known gaps). We still need the clone so mutating the caller's
|
|
133
|
+
* snapshot object after `rehydrate()` returns can't reach into the live
|
|
134
|
+
* FSM's internal deferred queue.
|
|
135
|
+
*
|
|
136
|
+
* Cycle-safe: a value that contains itself is returned as-is (by reference)
|
|
137
|
+
* rather than cloned infinitely — there's no validation step here to make
|
|
138
|
+
* that throw, so silently keeping the shared reference is the only option
|
|
139
|
+
* that doesn't hang.
|
|
140
|
+
*/
|
|
141
|
+
const cloneDeep = (value, ancestors = /* @__PURE__ */ new Set()) => {
|
|
142
|
+
if (value === null || typeof value !== "object") return value;
|
|
143
|
+
if (ancestors.has(value)) return value;
|
|
144
|
+
if (Array.isArray(value)) {
|
|
145
|
+
ancestors.add(value);
|
|
146
|
+
const cloned = value.map((item) => cloneDeep(item, ancestors));
|
|
147
|
+
ancestors.delete(value);
|
|
148
|
+
return cloned;
|
|
149
|
+
}
|
|
150
|
+
const proto = Object.getPrototypeOf(value);
|
|
151
|
+
if (proto !== Object.prototype && proto !== null) return value;
|
|
152
|
+
ancestors.add(value);
|
|
153
|
+
const cloned = {};
|
|
154
|
+
for (const key of Object.keys(value)) {
|
|
155
|
+
const clonedValue = cloneDeep(value[key], ancestors);
|
|
156
|
+
Object.defineProperty(cloned, key, {
|
|
157
|
+
value: clonedValue,
|
|
158
|
+
enumerable: true,
|
|
159
|
+
writable: true,
|
|
160
|
+
configurable: true
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
ancestors.delete(value);
|
|
164
|
+
return cloned;
|
|
165
|
+
};
|
|
166
|
+
|
|
52
167
|
//#endregion
|
|
53
168
|
//#region src/types.ts
|
|
54
169
|
/**
|
|
@@ -76,6 +191,9 @@ const MAX_TRANSITION_DEPTH = 20;
|
|
|
76
191
|
* so it can serve as a WeakMap key.
|
|
77
192
|
* @typeParam TStateNames - String literal union of valid state names.
|
|
78
193
|
* @typeParam TInputNames - String literal union of valid input names.
|
|
194
|
+
* @typeParam TBubbles - String literal union of inputs this FSM declares via
|
|
195
|
+
* `bubbles`. Type-only — carried so `BubblesOfInstance` can extract it from
|
|
196
|
+
* a constructed instance; nothing at runtime reads this generic.
|
|
79
197
|
*/
|
|
80
198
|
var BehavioralFsm = class {
|
|
81
199
|
id;
|
|
@@ -124,15 +242,20 @@ var BehavioralFsm = class {
|
|
|
124
242
|
}
|
|
125
243
|
/**
|
|
126
244
|
* Returns true if the client's current state has a handler for `inputName`
|
|
127
|
-
* (or a catch-all `"*"` handler)
|
|
128
|
-
*
|
|
129
|
-
*
|
|
245
|
+
* (or a catch-all `"*"` handler), or if the current state's `_child` chain
|
|
246
|
+
* can handle it — the check recurses to the same depth `handle()`'s
|
|
247
|
+
* delegation can actually reach, so a grandchild-only input answers true
|
|
248
|
+
* from the root. Does NOT initialize the client — no `_onEnter`, no
|
|
249
|
+
* events, no side effects. Unseen clients are treated as if they were
|
|
250
|
+
* already in `initialState`. Returns false when disposed.
|
|
130
251
|
*/
|
|
131
252
|
canHandle(client, inputName) {
|
|
132
253
|
if (this.disposed) return false;
|
|
133
254
|
const state = this.clients.get(client)?.state ?? this.initialState;
|
|
134
255
|
const stateObj = this.states[state];
|
|
135
|
-
|
|
256
|
+
if (stateObj?.[inputName] ?? stateObj?.["*"]) return true;
|
|
257
|
+
const childLink = stateObj?._child;
|
|
258
|
+
return childLink ? childLink.canHandle(client, inputName) : false;
|
|
136
259
|
}
|
|
137
260
|
/**
|
|
138
261
|
* Transition the client back to `initialState`, firing `_onEnter` and
|
|
@@ -167,7 +290,7 @@ var BehavioralFsm = class {
|
|
|
167
290
|
const meta = this.getOrCreateClientMeta(client);
|
|
168
291
|
const fromState = meta.state;
|
|
169
292
|
if (toState === fromState) return;
|
|
170
|
-
if (!(
|
|
293
|
+
if (!Object.hasOwn(this.states, toState)) {
|
|
171
294
|
this.emitter.emit("invalidstate", {
|
|
172
295
|
stateName: toState,
|
|
173
296
|
client
|
|
@@ -223,25 +346,96 @@ var BehavioralFsm = class {
|
|
|
223
346
|
}
|
|
224
347
|
return meta.state;
|
|
225
348
|
}
|
|
349
|
+
rehydrate(client, input) {
|
|
350
|
+
if (this.disposed) return;
|
|
351
|
+
if (typeof input === "string") {
|
|
352
|
+
this.rehydrateCompositePath(client, input);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const writes = this.planSnapshotWrites(client, input);
|
|
356
|
+
for (const write of writes) write();
|
|
357
|
+
}
|
|
226
358
|
/**
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
359
|
+
* Snapshot everything machina tracks for `client`: current state, pending
|
|
360
|
+
* deferred inputs, and — recursively — the same for every `_child` that has
|
|
361
|
+
* ever seen this client, active or not. Feed the result to the object form
|
|
362
|
+
* of `rehydrate()` to restore it later, deferrals included.
|
|
230
363
|
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* is placed at each level of the hierarchy in turn. Subsequent `handle()` calls
|
|
234
|
-
* proceed as if the client had reached that state through normal transitions.
|
|
364
|
+
* Returns `undefined` for a client this FSM has never seen (mirrors
|
|
365
|
+
* `currentState()`) — the call does NOT trigger initialization.
|
|
235
366
|
*
|
|
236
|
-
* Throws
|
|
237
|
-
*
|
|
367
|
+
* Throws if any deferred input's args contain a non-serializable value
|
|
368
|
+
* (function, undefined, symbol, bigint, non-finite number, Date/Map/class
|
|
369
|
+
* instance, or a circular reference) — naming the input, its `until` target
|
|
370
|
+
* if any, the FSM id, and the exact value path. Throws for an Fsm child
|
|
371
|
+
* that's on `client`'s active path *relative to the true root* (consistent
|
|
372
|
+
* with `rehydrate()`'s conditional throw) — an Fsm owns its own context, so
|
|
373
|
+
* there's nothing per-client to snapshot. An Fsm child declared at a state
|
|
374
|
+
* `client` never visited, OR nested under a `BehavioralFsm` child that is
|
|
375
|
+
* itself off-path from the root, is skipped rather than throwing — neither
|
|
376
|
+
* has any per-client state to lose, so one Fsm child anywhere in the
|
|
377
|
+
* hierarchy doesn't disable `dehydrate()` for clients that never reach that
|
|
378
|
+
* branch, no matter how deeply nested the Fsm child is.
|
|
238
379
|
*
|
|
239
|
-
*
|
|
380
|
+
* Meant for clients at rest between `handle()` calls — `currentActionArgs`
|
|
381
|
+
* (the in-flight args mid-handler) has no meaning here and is excluded.
|
|
382
|
+
*
|
|
383
|
+
* @param isOnActivePath - @internal Whether this FSM itself is currently
|
|
384
|
+
* reachable from the true root's active path. Defaults to `true` for the
|
|
385
|
+
* public entry point (this FSM IS the root from its own perspective); the
|
|
386
|
+
* `ChildLink` adapter passes `false` down when recursing into a nested
|
|
387
|
+
* `BehavioralFsm` child that is itself off-path, so that child's own
|
|
388
|
+
* Fsm-child checks don't recompute reachability from its dormant local
|
|
389
|
+
* state alone.
|
|
240
390
|
*/
|
|
241
|
-
|
|
242
|
-
|
|
391
|
+
dehydrate(client, isOnActivePath = true) {
|
|
392
|
+
const meta = this.clients.get(client);
|
|
393
|
+
if (!meta) return;
|
|
394
|
+
const snapshot = {
|
|
395
|
+
state: meta.state,
|
|
396
|
+
deferred: meta.deferredQueue.map((item) => this.snapshotDeferredInput(item))
|
|
397
|
+
};
|
|
398
|
+
const children = this.collectChildSnapshots(client, meta.state, isOnActivePath);
|
|
399
|
+
if (children) snapshot.children = children;
|
|
400
|
+
return snapshot;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* @internal
|
|
404
|
+
* Validates `snapshot` against this level's state graph and recurses into
|
|
405
|
+
* every declared child, returning write thunks to run only once the WHOLE
|
|
406
|
+
* tree — every level — validates successfully. Nothing is written here.
|
|
407
|
+
* Called by the object-form of `rehydrate()` and, recursively, by ChildLink
|
|
408
|
+
* so a nested BehavioralFsm participates in the same validate-then-write
|
|
409
|
+
* pass. Not part of the public persistence API — call `rehydrate()` instead.
|
|
410
|
+
*/
|
|
411
|
+
planSnapshotWrites(client, snapshot) {
|
|
412
|
+
if (this.disposed) return [];
|
|
413
|
+
const { state, deferred, children } = snapshot;
|
|
414
|
+
if (!Object.hasOwn(this.states, state)) throw new Error(`rehydrate: unknown state "${state}" in FSM "${this.id}". Valid states: ${Object.keys(this.states).join(", ")}`);
|
|
415
|
+
const writes = [];
|
|
416
|
+
if (children) for (const stateName of Object.keys(children)) {
|
|
417
|
+
if (!Object.hasOwn(this.states, stateName)) throw new Error(`rehydrate: unknown state "${stateName}" in FSM "${this.id}" referenced by snapshot.children.`);
|
|
418
|
+
const childLink = this.states[stateName]?._child;
|
|
419
|
+
if (!childLink) throw new Error(`rehydrate: state "${stateName}" in FSM "${this.id}" has no _child, but the snapshot has a children["${stateName}"] entry.`);
|
|
420
|
+
writes.push(...childLink.planRehydrate(client, children[stateName]));
|
|
421
|
+
}
|
|
422
|
+
const deferredQueue = deferred.map((item) => ({
|
|
423
|
+
inputName: item.inputName,
|
|
424
|
+
args: cloneDeep(item.args),
|
|
425
|
+
...item.untilState !== void 0 ? { untilState: item.untilState } : {}
|
|
426
|
+
}));
|
|
427
|
+
writes.push(() => {
|
|
428
|
+
if (!this.clients.has(client)) this.knownClients.add(new WeakRef(client));
|
|
429
|
+
this.clients.set(client, {
|
|
430
|
+
state,
|
|
431
|
+
deferredQueue
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
return writes;
|
|
435
|
+
}
|
|
436
|
+
rehydrateCompositePath(client, compositeState) {
|
|
243
437
|
const [state, ...rest] = compositeState.split(".");
|
|
244
|
-
if (!(
|
|
438
|
+
if (!Object.hasOwn(this.states, state)) throw new Error(`rehydrate: unknown state "${state}" in FSM "${this.id}". Valid states: ${Object.keys(this.states).join(", ")}`);
|
|
245
439
|
if (rest.length > 0) {
|
|
246
440
|
const childPath = rest.join(".");
|
|
247
441
|
const childLink = this.states[state]?._child;
|
|
@@ -254,6 +448,80 @@ var BehavioralFsm = class {
|
|
|
254
448
|
deferredQueue: []
|
|
255
449
|
});
|
|
256
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* Walks every declared state's `_child`, dehydrating each one that has ever
|
|
453
|
+
* seen `client`. The same child instance can be declared under multiple
|
|
454
|
+
* state names (shared child) — it's dehydrated once and the result is
|
|
455
|
+
* reused under every declaring state name, matching how the engine already
|
|
456
|
+
* treats shared children elsewhere (dispose, event subscriptions).
|
|
457
|
+
*
|
|
458
|
+
* Declaring names are grouped by `childLink.instance` (the actual FSM
|
|
459
|
+
* instance, not the `ChildLink` wrapper — `wrapChildLinks()` mints a fresh
|
|
460
|
+
* wrapper per declaring state, so grouping by wrapper would never hit for a
|
|
461
|
+
* shared child) BEFORE any `onPath` is computed or any child is dehydrated.
|
|
462
|
+
* A shared child's `onPath` is the OR of `stateName === activeState` across
|
|
463
|
+
* every declaring name in its group: at most one declaring name can ever
|
|
464
|
+
* equal `activeState`, so this combined flag is correct and, critically,
|
|
465
|
+
* independent of `Object.keys(this.states)` iteration order — computing
|
|
466
|
+
* `onPath` per-declaring-name and caching whichever one happened to run
|
|
467
|
+
* first would silently launder an on-path client's Fsm-child throw through
|
|
468
|
+
* an unrelated off-path declaring name.
|
|
469
|
+
*
|
|
470
|
+
* An off-path Fsm child (declared only at states other than `activeState`,
|
|
471
|
+
* OR nested anywhere under a `BehavioralFsm` child that is itself off-path
|
|
472
|
+
* relative to the true root) is skipped entirely rather than dehydrated:
|
|
473
|
+
* Fsm state isn't tracked per-client to begin with, so an off-path Fsm
|
|
474
|
+
* child has nothing to lose by being skipped — unlike a BehavioralFsm
|
|
475
|
+
* child's off-path meta, which is real per-client data. `isOnActivePath`
|
|
476
|
+
* is the inherited "am I even reachable from the root" flag; combining it
|
|
477
|
+
* with the group's `stateNames.includes(activeState)` check (rather than
|
|
478
|
+
* using that check alone) is what keeps a nested Fsm grandchild from
|
|
479
|
+
* throwing when its immediate BehavioralFsm parent is itself off-path —
|
|
480
|
+
* the parent's own dormant `activeState` is irrelevant once the parent
|
|
481
|
+
* isn't reachable. This keeps one Fsm child anywhere in the hierarchy from
|
|
482
|
+
* disabling `dehydrate()` for every client, only for clients actually on
|
|
483
|
+
* that branch.
|
|
484
|
+
*/
|
|
485
|
+
collectChildSnapshots(client, activeState, isOnActivePath) {
|
|
486
|
+
const declaringNamesByInstance = /* @__PURE__ */ new Map();
|
|
487
|
+
for (const stateName of Object.keys(this.states)) {
|
|
488
|
+
const childLink = this.states[stateName]?._child;
|
|
489
|
+
if (!childLink) continue;
|
|
490
|
+
const entry = declaringNamesByInstance.get(childLink.instance);
|
|
491
|
+
if (entry) entry.stateNames.push(stateName);
|
|
492
|
+
else declaringNamesByInstance.set(childLink.instance, {
|
|
493
|
+
childLink,
|
|
494
|
+
stateNames: [stateName]
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
let children;
|
|
498
|
+
for (const { childLink, stateNames } of declaringNamesByInstance.values()) {
|
|
499
|
+
const onPath = isOnActivePath && stateNames.includes(activeState);
|
|
500
|
+
if (childLink.instance[MACHINA_TYPE] === "Fsm" && !onPath) continue;
|
|
501
|
+
const childSnapshot = childLink.dehydrate(client, onPath);
|
|
502
|
+
if (childSnapshot) {
|
|
503
|
+
children ??= {};
|
|
504
|
+
for (const stateName of stateNames) children[stateName] = childSnapshot;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return children;
|
|
508
|
+
}
|
|
509
|
+
snapshotDeferredInput(item) {
|
|
510
|
+
let clonedArgs;
|
|
511
|
+
try {
|
|
512
|
+
clonedArgs = cloneJsonSafe(item.args, "args");
|
|
513
|
+
} catch (err) {
|
|
514
|
+
if (!(err instanceof NonSerializableValueError)) throw err;
|
|
515
|
+
const untilPart = item.untilState ? ` (until "${item.untilState}")` : "";
|
|
516
|
+
throw new Error(`dehydrate: deferred input "${item.inputName}"${untilPart} in FSM "${this.id}" has a non-serializable value at ${err.path} (${err.label})`);
|
|
517
|
+
}
|
|
518
|
+
const snapshot = {
|
|
519
|
+
inputName: item.inputName,
|
|
520
|
+
args: clonedArgs
|
|
521
|
+
};
|
|
522
|
+
if (item.untilState !== void 0) snapshot.untilState = item.untilState;
|
|
523
|
+
return snapshot;
|
|
524
|
+
}
|
|
257
525
|
on(eventName, callback) {
|
|
258
526
|
if (this.disposed) return { off() {} };
|
|
259
527
|
return this.emitter.on(eventName, callback);
|
|
@@ -280,8 +548,8 @@ var BehavioralFsm = class {
|
|
|
280
548
|
const seen = /* @__PURE__ */ new Set();
|
|
281
549
|
for (const stateName of Object.keys(this.states)) {
|
|
282
550
|
const childLink = this.states[stateName]?._child;
|
|
283
|
-
if (childLink && !seen.has(childLink)) {
|
|
284
|
-
seen.add(childLink);
|
|
551
|
+
if (childLink && !seen.has(childLink.instance)) {
|
|
552
|
+
seen.add(childLink.instance);
|
|
285
553
|
childLink.dispose();
|
|
286
554
|
}
|
|
287
555
|
}
|
|
@@ -307,15 +575,23 @@ var BehavioralFsm = class {
|
|
|
307
575
|
/**
|
|
308
576
|
* Walks all states at construction time, finds states with _child, and
|
|
309
577
|
* subscribes once to each unique child's wildcard events. Subscriptions are
|
|
310
|
-
* stored for cleanup in dispose(). We deduplicate by
|
|
311
|
-
*
|
|
578
|
+
* stored for cleanup in dispose(). We deduplicate by `childLink.instance`
|
|
579
|
+
* (the underlying Fsm/BehavioralFsm instance), not the `ChildLink` wrapper —
|
|
580
|
+
* `wrapChildLinks()` mints a fresh wrapper per declaring state, so a child
|
|
581
|
+
* shared across states would otherwise get one subscription PER declaring
|
|
582
|
+
* state, each independently walking known clients and relaying events. That
|
|
583
|
+
* silently double-fires client-less relays (Fsm-child events, or a
|
|
584
|
+
* BehavioralFsm child's custom `emit()` with no `client` in the payload)
|
|
585
|
+
* whenever two different clients are active on two different declaring
|
|
586
|
+
* names at once — the same wrapper-vs-instance identity bug
|
|
587
|
+
* `collectChildSnapshots()` had before its #184 fix.
|
|
312
588
|
*/
|
|
313
589
|
setupChildSubscriptions() {
|
|
314
|
-
const
|
|
590
|
+
const seenInstances = /* @__PURE__ */ new Set();
|
|
315
591
|
for (const stateName of Object.keys(this.states)) {
|
|
316
592
|
const childLink = this.states[stateName]?._child;
|
|
317
|
-
if (!childLink ||
|
|
318
|
-
|
|
593
|
+
if (!childLink || seenInstances.has(childLink.instance)) continue;
|
|
594
|
+
seenInstances.add(childLink.instance);
|
|
319
595
|
const sub = childLink.onAny((eventName, data) => {
|
|
320
596
|
if (eventName === "nohandler") {
|
|
321
597
|
const payload = data;
|
|
@@ -361,13 +637,22 @@ var BehavioralFsm = class {
|
|
|
361
637
|
}
|
|
362
638
|
/**
|
|
363
639
|
* Returns true if the given client is currently in a parent state whose
|
|
364
|
-
* _child
|
|
365
|
-
* meta (never initialized) or is in a state
|
|
640
|
+
* _child resolves to the same underlying instance as childLink. Returns
|
|
641
|
+
* false if the client has no meta (never initialized) or is in a state
|
|
642
|
+
* with a different (or no) child.
|
|
643
|
+
*
|
|
644
|
+
* Compares `.instance`, not the `ChildLink` wrapper itself — setupChildSubscriptions()
|
|
645
|
+
* dedupes subscriptions by instance and keeps only ONE representative wrapper
|
|
646
|
+
* per shared child, so the client's actual active declaring state may hold a
|
|
647
|
+
* DIFFERENT wrapper for that same instance (wrapChildLinks() mints one per
|
|
648
|
+
* declaring state). Comparing wrappers directly would only ever match the one
|
|
649
|
+
* declaring state whose wrapper happened to be kept for the subscription,
|
|
650
|
+
* silently breaking relay for every other declaring name of a shared child.
|
|
366
651
|
*/
|
|
367
652
|
isChildActiveForClient(client, childLink) {
|
|
368
653
|
const meta = this.clients.get(client);
|
|
369
654
|
if (!meta) return false;
|
|
370
|
-
return this.states[meta.state]?._child === childLink;
|
|
655
|
+
return (this.states[meta.state]?._child)?.instance === childLink.instance;
|
|
371
656
|
}
|
|
372
657
|
/**
|
|
373
658
|
* The inner handler dispatch — no delegation, no initialization side effects
|
|
@@ -478,6 +763,12 @@ function createChildLink(child) {
|
|
|
478
763
|
rehydrate(client, compositeState) {
|
|
479
764
|
child.rehydrate(client, compositeState);
|
|
480
765
|
},
|
|
766
|
+
dehydrate(client, isOnActivePath) {
|
|
767
|
+
return child.dehydrate(client, isOnActivePath);
|
|
768
|
+
},
|
|
769
|
+
planRehydrate(client, snapshot) {
|
|
770
|
+
return child.planSnapshotWrites(client, snapshot);
|
|
771
|
+
},
|
|
481
772
|
dispose() {
|
|
482
773
|
child.dispose();
|
|
483
774
|
}
|
|
@@ -502,43 +793,20 @@ function createChildLink(child) {
|
|
|
502
793
|
rehydrate(_client, _compositeState) {
|
|
503
794
|
throw new Error("rehydrate: cannot rehydrate an Fsm child. Fsm owns its own context; rehydrate is only valid for BehavioralFsm hierarchies.");
|
|
504
795
|
},
|
|
796
|
+
dehydrate(_client, _isOnActivePath) {
|
|
797
|
+
throw new Error("dehydrate: cannot dehydrate an Fsm child. Fsm owns its own context; dehydrate is only valid for BehavioralFsm hierarchies.");
|
|
798
|
+
},
|
|
799
|
+
planRehydrate(_client, _snapshot) {
|
|
800
|
+
throw new Error("rehydrate: cannot rehydrate an Fsm child. Fsm owns its own context; rehydrate is only valid for BehavioralFsm hierarchies.");
|
|
801
|
+
},
|
|
505
802
|
dispose() {
|
|
506
803
|
child.dispose();
|
|
507
804
|
}
|
|
508
805
|
};
|
|
509
806
|
throw new Error(`createChildLink: expected an Fsm or BehavioralFsm instance, got [MACHINA_TYPE] = ${String(childType ?? "undefined")}`);
|
|
510
807
|
}
|
|
511
|
-
/**
|
|
512
|
-
* Create a behavioral FSM (one definition, many clients) from a config object.
|
|
513
|
-
*
|
|
514
|
-
* Generic parameters are inferred automatically:
|
|
515
|
-
* - `TClient` must be provided explicitly as a type parameter (it can't be
|
|
516
|
-
* inferred from the config since no `context` property exists at the FSM level).
|
|
517
|
-
* - `TStates` is captured with `const` inference to preserve string literal
|
|
518
|
-
* types, enabling compile-time validation of transition targets and `handle()`
|
|
519
|
-
* input names.
|
|
520
|
-
*
|
|
521
|
-
* State names, input names, and all handler signatures derive from `TStates`.
|
|
522
|
-
*
|
|
523
|
-
* @example
|
|
524
|
-
* ```ts
|
|
525
|
-
* interface Connection { url: string; retries: number; }
|
|
526
|
-
*
|
|
527
|
-
* const connFsm = createBehavioralFsm<Connection>({
|
|
528
|
-
* id: "connectivity",
|
|
529
|
-
* initialState: "disconnected",
|
|
530
|
-
* states: {
|
|
531
|
-
* disconnected: { connect: "connecting" },
|
|
532
|
-
* connecting: { connected: "online", failed: "disconnected" },
|
|
533
|
-
* online: { disconnect: "disconnected" },
|
|
534
|
-
* },
|
|
535
|
-
* });
|
|
536
|
-
*
|
|
537
|
-
* const conn = { url: "wss://example.com", retries: 0 };
|
|
538
|
-
* connFsm.handle(conn, "connect");
|
|
539
|
-
* ```
|
|
540
|
-
*/
|
|
541
808
|
function createBehavioralFsm(config) {
|
|
809
|
+
if (config === void 0) return (curriedConfig) => new BehavioralFsm(curriedConfig);
|
|
542
810
|
return new BehavioralFsm(config);
|
|
543
811
|
}
|
|
544
812
|
|
|
@@ -556,6 +824,9 @@ function createBehavioralFsm(config) {
|
|
|
556
824
|
* @typeParam TCtx - The context type, inferred from `config.context`.
|
|
557
825
|
* @typeParam TStateNames - String literal union of valid state names.
|
|
558
826
|
* @typeParam TInputNames - String literal union of valid input names.
|
|
827
|
+
* @typeParam TBubbles - String literal union of inputs this FSM declares via
|
|
828
|
+
* `bubbles`. Type-only — carried so `BubblesOfInstance` can extract it from
|
|
829
|
+
* a constructed instance; nothing at runtime reads this generic.
|
|
559
830
|
*/
|
|
560
831
|
var Fsm = class {
|
|
561
832
|
id;
|
|
@@ -592,8 +863,10 @@ var Fsm = class {
|
|
|
592
863
|
}
|
|
593
864
|
/**
|
|
594
865
|
* Returns true if the current state has a handler for `inputName`
|
|
595
|
-
* (or a catch-all `"*"` handler)
|
|
596
|
-
*
|
|
866
|
+
* (or a catch-all `"*"` handler), or if the current state's `_child`
|
|
867
|
+
* chain can handle it (checked recursively, matching `handle()`'s
|
|
868
|
+
* delegation reach). Does not trigger initialization or any side
|
|
869
|
+
* effects. Returns false when disposed.
|
|
597
870
|
*/
|
|
598
871
|
canHandle(inputName) {
|
|
599
872
|
if (this.disposed) return false;
|