aifsmjs 0.5.5 → 0.5.8
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 +44 -513
- package/README_ZHTW.md +44 -505
- package/dist/chunk-2MME5V4F.cjs +384 -0
- package/dist/chunk-2MME5V4F.cjs.map +1 -0
- package/dist/chunk-3B2USJ3H.cjs +18 -0
- package/dist/chunk-3B2USJ3H.cjs.map +1 -0
- package/dist/chunk-A7U7QQL5.js +52 -0
- package/dist/chunk-A7U7QQL5.js.map +1 -0
- package/dist/chunk-CDK25FTD.cjs +59 -0
- package/dist/chunk-CDK25FTD.cjs.map +1 -0
- package/dist/chunk-FHTQ7LSQ.cjs +21 -0
- package/dist/chunk-FHTQ7LSQ.cjs.map +1 -0
- package/dist/chunk-I354FONA.cjs +153 -0
- package/dist/chunk-I354FONA.cjs.map +1 -0
- package/dist/chunk-JKZAOPQC.js +16 -0
- package/dist/chunk-JKZAOPQC.js.map +1 -0
- package/dist/chunk-NEJYZAKR.js +19 -0
- package/dist/chunk-NEJYZAKR.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +9 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-Q7SFCCGT.cjs +11 -0
- package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
- package/dist/chunk-VZCSHTOI.js +374 -0
- package/dist/chunk-VZCSHTOI.js.map +1 -0
- package/dist/chunk-ZLQ7HZCE.js +142 -0
- package/dist/chunk-ZLQ7HZCE.js.map +1 -0
- package/dist/effects/index.cjs +8 -14
- package/dist/effects/index.cjs.map +1 -1
- package/dist/effects/index.js +5 -14
- package/dist/effects/index.js.map +1 -1
- package/dist/guards/index.cjs +9 -13
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.js +8 -12
- package/dist/guards/index.js.map +1 -1
- package/dist/index.cjs +101 -591
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -571
- package/dist/index.js.map +1 -1
- package/dist/inspect/index.cjs +2 -0
- package/dist/inspect/index.cjs.map +1 -1
- package/dist/inspect/index.js +2 -0
- package/dist/inspect/index.js.map +1 -1
- package/dist/pbt/index.cjs +26 -525
- package/dist/pbt/index.cjs.map +1 -1
- package/dist/pbt/index.d.cts +7 -3
- package/dist/pbt/index.d.ts +7 -3
- package/dist/pbt/index.js +12 -511
- package/dist/pbt/index.js.map +1 -1
- package/dist/replay/index.cjs +9 -195
- package/dist/replay/index.cjs.map +1 -1
- package/dist/replay/index.js +5 -198
- package/dist/replay/index.js.map +1 -1
- package/dist/timer/index.cjs +30 -9
- package/dist/timer/index.cjs.map +1 -1
- package/dist/timer/index.js +30 -9
- package/dist/timer/index.js.map +1 -1
- package/llms-full.txt +107 -932
- package/llms.txt +8 -35
- package/package.json +5 -3
package/dist/pbt/index.js
CHANGED
|
@@ -1,195 +1,11 @@
|
|
|
1
|
+
import { createRuntime, initialSnapshot } from '../chunk-VZCSHTOI.js';
|
|
2
|
+
import { replay } from '../chunk-NEJYZAKR.js';
|
|
3
|
+
import { step, mergeContext, normalizeTransitions } from '../chunk-ZLQ7HZCE.js';
|
|
4
|
+
import '../chunk-JKZAOPQC.js';
|
|
5
|
+
import '../chunk-A7U7QQL5.js';
|
|
6
|
+
import { __export } from '../chunk-PZ5AY32C.js';
|
|
1
7
|
import * as fc2 from 'fast-check';
|
|
2
8
|
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __export = (target, all) => {
|
|
5
|
-
for (var name in all)
|
|
6
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
// src/effects/enqueuer.ts
|
|
10
|
-
function createEnqueuer(sink) {
|
|
11
|
-
return Object.freeze({
|
|
12
|
-
effect(type, payload) {
|
|
13
|
-
if (payload === void 0) {
|
|
14
|
-
sink.push(Object.freeze({ type }));
|
|
15
|
-
} else {
|
|
16
|
-
sink.push(Object.freeze({ type, payload }));
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// src/fsm/evaluator.ts
|
|
23
|
-
var UnknownGuardError = class extends Error {
|
|
24
|
-
guardName;
|
|
25
|
-
constructor(guardName) {
|
|
26
|
-
super(`aifsmjs: guard "${guardName}" not found in implementations.guards`);
|
|
27
|
-
this.name = "UnknownGuardError";
|
|
28
|
-
this.guardName = guardName;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
var AsyncGuardError = class extends Error {
|
|
32
|
-
guardName;
|
|
33
|
-
constructor(guardName) {
|
|
34
|
-
super(
|
|
35
|
-
`aifsmjs: guard "${guardName}" must be sync; received a Promise. Async guards break determinism and replay. Move I/O into an effect.`
|
|
36
|
-
);
|
|
37
|
-
this.name = "AsyncGuardError";
|
|
38
|
-
this.guardName = guardName;
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
function isAsyncGuardFn(fn) {
|
|
42
|
-
if (typeof fn !== "function") return false;
|
|
43
|
-
return fn.constructor?.name === "AsyncFunction";
|
|
44
|
-
}
|
|
45
|
-
function isThenable(x) {
|
|
46
|
-
return x !== null && (typeof x === "object" || typeof x === "function") && typeof x.then === "function";
|
|
47
|
-
}
|
|
48
|
-
function resolveGuard(ref, impl) {
|
|
49
|
-
if (typeof ref === "function") return ref;
|
|
50
|
-
const fn = impl.guards?.[ref];
|
|
51
|
-
if (!fn) throw new UnknownGuardError(ref);
|
|
52
|
-
return fn;
|
|
53
|
-
}
|
|
54
|
-
function evalGuard(ref, context, event, impl, value) {
|
|
55
|
-
const fn = resolveGuard(ref, impl);
|
|
56
|
-
const guardName = typeof ref === "string" ? ref : fn.name || "<inline>";
|
|
57
|
-
if (isAsyncGuardFn(fn)) {
|
|
58
|
-
throw new AsyncGuardError(guardName);
|
|
59
|
-
}
|
|
60
|
-
const args = { context, event };
|
|
61
|
-
const guardsMap = impl.guards;
|
|
62
|
-
if (guardsMap) args.guards = guardsMap;
|
|
63
|
-
if (value !== void 0) args.value = value;
|
|
64
|
-
const result = fn(args);
|
|
65
|
-
if (isThenable(result)) {
|
|
66
|
-
throw new AsyncGuardError(guardName);
|
|
67
|
-
}
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// src/fsm/resolver.ts
|
|
72
|
-
function normalizeTransition(entry) {
|
|
73
|
-
return typeof entry === "string" ? { target: entry } : entry;
|
|
74
|
-
}
|
|
75
|
-
function normalizeTransitions(entry) {
|
|
76
|
-
if (entry === void 0) return [];
|
|
77
|
-
if (Array.isArray(entry)) {
|
|
78
|
-
return entry.some((t) => typeof t === "string") ? entry.map((t) => normalizeTransition(t)) : entry;
|
|
79
|
-
}
|
|
80
|
-
return [normalizeTransition(entry)];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// src/fsm/snapshot.ts
|
|
84
|
-
var IS_DEV = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.NODE_ENV !== "production";
|
|
85
|
-
function isPlainObject(value) {
|
|
86
|
-
if (value === null || typeof value !== "object") return false;
|
|
87
|
-
const proto = Object.getPrototypeOf(value);
|
|
88
|
-
return proto === Object.prototype || proto === null;
|
|
89
|
-
}
|
|
90
|
-
function deepFreeze(value) {
|
|
91
|
-
if (value === null || typeof value !== "object") return value;
|
|
92
|
-
if (Object.isFrozen(value)) return value;
|
|
93
|
-
Object.freeze(value);
|
|
94
|
-
if (Array.isArray(value)) {
|
|
95
|
-
for (const item of value) deepFreeze(item);
|
|
96
|
-
} else if (isPlainObject(value)) {
|
|
97
|
-
for (const key of Object.keys(value)) {
|
|
98
|
-
deepFreeze(value[key]);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return value;
|
|
102
|
-
}
|
|
103
|
-
function freezeSnapshot(snap) {
|
|
104
|
-
return IS_DEV ? deepFreeze(snap) : Object.freeze(snap);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// src/fsm/updater.ts
|
|
108
|
-
function isPlainRecord(value) {
|
|
109
|
-
if (value === null || typeof value !== "object") return false;
|
|
110
|
-
const proto = Object.getPrototypeOf(value);
|
|
111
|
-
return proto === Object.prototype || proto === null;
|
|
112
|
-
}
|
|
113
|
-
function mergeContext(current, patch) {
|
|
114
|
-
if (patch === void 0 || patch === null) return current;
|
|
115
|
-
if (isPlainRecord(current) && isPlainRecord(patch)) {
|
|
116
|
-
return { ...current, ...patch };
|
|
117
|
-
}
|
|
118
|
-
return patch;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/fsm/lifecycle.ts
|
|
122
|
-
var UnknownActionError = class extends Error {
|
|
123
|
-
actionName;
|
|
124
|
-
constructor(actionName) {
|
|
125
|
-
super(`aifsmjs: action "${actionName}" not found in implementations.actions`);
|
|
126
|
-
this.name = "UnknownActionError";
|
|
127
|
-
this.actionName = actionName;
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
function resolveAction(ref, impl) {
|
|
131
|
-
if (typeof ref === "function") return ref;
|
|
132
|
-
const fn = impl.actions?.[ref];
|
|
133
|
-
if (!fn) throw new UnknownActionError(ref);
|
|
134
|
-
return fn;
|
|
135
|
-
}
|
|
136
|
-
function runActions(refs, ctx, event, impl, effectSink) {
|
|
137
|
-
if (!refs || refs.length === 0) return ctx;
|
|
138
|
-
const enqueue = createEnqueuer(effectSink);
|
|
139
|
-
let current = ctx;
|
|
140
|
-
for (const ref of refs) {
|
|
141
|
-
const fn = resolveAction(ref, impl);
|
|
142
|
-
const patch = fn({ context: current, event, enqueue });
|
|
143
|
-
current = mergeContext(current, patch);
|
|
144
|
-
}
|
|
145
|
-
return current;
|
|
146
|
-
}
|
|
147
|
-
function pickTransition(candidates, ctx, event, impl, value) {
|
|
148
|
-
for (const t of candidates) {
|
|
149
|
-
if (!t.guard) return t;
|
|
150
|
-
if (evalGuard(t.guard, ctx, event, impl, value)) return t;
|
|
151
|
-
}
|
|
152
|
-
return void 0;
|
|
153
|
-
}
|
|
154
|
-
function step(def, snapshot, event, impl) {
|
|
155
|
-
if (snapshot.status === "final") {
|
|
156
|
-
return Object.freeze({ snapshot, effects: [], changed: false });
|
|
157
|
-
}
|
|
158
|
-
const state = def.states[snapshot.value];
|
|
159
|
-
if (!state) {
|
|
160
|
-
return Object.freeze({ snapshot, effects: [], changed: false });
|
|
161
|
-
}
|
|
162
|
-
const candidateList = normalizeTransitions(state.on?.[event.type]);
|
|
163
|
-
const chosen = pickTransition(candidateList, snapshot.context, event, impl, snapshot.value);
|
|
164
|
-
if (!chosen) {
|
|
165
|
-
return Object.freeze({ snapshot, effects: [], changed: false });
|
|
166
|
-
}
|
|
167
|
-
const isExternal = chosen.target !== void 0;
|
|
168
|
-
const nextStateValue = chosen.target ?? snapshot.value;
|
|
169
|
-
const nextState = def.states[nextStateValue];
|
|
170
|
-
const effectSink = [];
|
|
171
|
-
let ctx = snapshot.context;
|
|
172
|
-
if (isExternal) {
|
|
173
|
-
ctx = runActions(state.exit, ctx, event, impl, effectSink);
|
|
174
|
-
}
|
|
175
|
-
ctx = runActions(chosen.actions, ctx, event, impl, effectSink);
|
|
176
|
-
if (isExternal && nextState) {
|
|
177
|
-
ctx = runActions(nextState.entry, ctx, event, impl, effectSink);
|
|
178
|
-
}
|
|
179
|
-
const status = nextState?.final === true ? "final" : "active";
|
|
180
|
-
const nextSnapshot = freezeSnapshot({
|
|
181
|
-
value: nextStateValue,
|
|
182
|
-
context: ctx,
|
|
183
|
-
status
|
|
184
|
-
});
|
|
185
|
-
return Object.freeze({
|
|
186
|
-
snapshot: nextSnapshot,
|
|
187
|
-
effects: Object.freeze(effectSink.slice()),
|
|
188
|
-
changed: true
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// src/pbt/commands.ts
|
|
193
9
|
function initialModel(def) {
|
|
194
10
|
return {
|
|
195
11
|
value: def.initial,
|
|
@@ -248,327 +64,6 @@ __export(properties_exports, {
|
|
|
248
64
|
snapshotAlwaysFrozen: () => snapshotAlwaysFrozen,
|
|
249
65
|
unknownEventNoOp: () => unknownEventNoOp
|
|
250
66
|
});
|
|
251
|
-
|
|
252
|
-
// src/fsm/types.ts
|
|
253
|
-
var RESET_EVENT_TYPE = "@@aifsmjs/RESET";
|
|
254
|
-
|
|
255
|
-
// src/fsm/runtime.ts
|
|
256
|
-
var RuntimeDisposedError = class extends Error {
|
|
257
|
-
constructor() {
|
|
258
|
-
super("aifsmjs: runtime has been disposed; send()/reset() are not allowed");
|
|
259
|
-
this.name = "RuntimeDisposedError";
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
var SubMachineError = class extends Error {
|
|
263
|
-
parentState;
|
|
264
|
-
phase;
|
|
265
|
-
cause;
|
|
266
|
-
constructor(parentState, phase, cause) {
|
|
267
|
-
super(`aifsmjs: sub-machine ${phase} failed at parent state "${parentState}"`, { cause });
|
|
268
|
-
this.name = "SubMachineError";
|
|
269
|
-
this.parentState = parentState;
|
|
270
|
-
this.phase = phase;
|
|
271
|
-
this.cause = cause;
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
var RESET_EVENT = Object.freeze({ type: RESET_EVENT_TYPE });
|
|
275
|
-
function composeMiddleware(middleware) {
|
|
276
|
-
return (ctx, finalNext) => {
|
|
277
|
-
let index = -1;
|
|
278
|
-
const dispatch = (i) => {
|
|
279
|
-
if (i <= index) throw new Error("aifsmjs: next() called multiple times in middleware");
|
|
280
|
-
index = i;
|
|
281
|
-
const fn = middleware[i];
|
|
282
|
-
if (!fn) {
|
|
283
|
-
finalNext();
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
fn(ctx, () => dispatch(i + 1));
|
|
287
|
-
};
|
|
288
|
-
dispatch(0);
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
function createRuntime(def, impl, opts = {}) {
|
|
292
|
-
let snapshot = initialSnapshot(def);
|
|
293
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
294
|
-
const middlewareChain = opts.middleware && opts.middleware.length > 0 ? composeMiddleware(opts.middleware) : void 0;
|
|
295
|
-
const shouldDispatch = opts.dispatchEffects !== false;
|
|
296
|
-
const controller = new AbortController();
|
|
297
|
-
let disposed = false;
|
|
298
|
-
let childRuntime;
|
|
299
|
-
let childAbortCleanup;
|
|
300
|
-
const eventListeners = {
|
|
301
|
-
transition: /* @__PURE__ */ new Set(),
|
|
302
|
-
error: /* @__PURE__ */ new Set(),
|
|
303
|
-
dispose: /* @__PURE__ */ new Set()
|
|
304
|
-
};
|
|
305
|
-
const externalAbortCleanups = /* @__PURE__ */ new Set();
|
|
306
|
-
function emit(type, payload) {
|
|
307
|
-
for (const fn of eventListeners[type]) fn(payload);
|
|
308
|
-
}
|
|
309
|
-
function notify(committed) {
|
|
310
|
-
const captured = committed ?? snapshot;
|
|
311
|
-
for (const l of listeners) l(captured);
|
|
312
|
-
}
|
|
313
|
-
function runMiddleware(prev, event, effects, changed) {
|
|
314
|
-
if (!middlewareChain) return;
|
|
315
|
-
middlewareChain(deepFreeze({ prev, next: snapshot, event, effects, changed }), () => {
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
function dispatchEffects(effects, context, event) {
|
|
319
|
-
if (!impl.effects || effects.length === 0) return;
|
|
320
|
-
for (const eff of effects) {
|
|
321
|
-
const handler = impl.effects[eff.type];
|
|
322
|
-
if (!handler) continue;
|
|
323
|
-
const r = handler(eff, { context, event, signal: controller.signal });
|
|
324
|
-
if (r instanceof Promise) {
|
|
325
|
-
r.catch((err) => {
|
|
326
|
-
emit("error", { error: err, event });
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
function wireChildAbort(child) {
|
|
332
|
-
if (controller.signal.aborted) {
|
|
333
|
-
try {
|
|
334
|
-
child.dispose();
|
|
335
|
-
} catch {
|
|
336
|
-
}
|
|
337
|
-
return () => {
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
const onAbort = () => {
|
|
341
|
-
try {
|
|
342
|
-
child.dispose();
|
|
343
|
-
} catch {
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
347
|
-
return () => controller.signal.removeEventListener("abort", onAbort);
|
|
348
|
-
}
|
|
349
|
-
function findChosenIsExternal(value, event, context) {
|
|
350
|
-
const state = def.states[value];
|
|
351
|
-
if (!state?.on) return false;
|
|
352
|
-
const list = normalizeTransitions(state.on[event.type]);
|
|
353
|
-
if (list.length === 0) return false;
|
|
354
|
-
for (const t of list) {
|
|
355
|
-
if (!t.guard || evalGuard(t.guard, context, event, impl, value)) {
|
|
356
|
-
return t.target !== void 0;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return false;
|
|
360
|
-
}
|
|
361
|
-
function applySubLifecycle(prevValue, nextValue) {
|
|
362
|
-
const prevStateDef = def.states[prevValue];
|
|
363
|
-
const nextStateDef = def.states[nextValue];
|
|
364
|
-
if (prevStateDef?.sub !== void 0 && childRuntime !== void 0) {
|
|
365
|
-
const child = childRuntime;
|
|
366
|
-
childRuntime = void 0;
|
|
367
|
-
childAbortCleanup?.();
|
|
368
|
-
childAbortCleanup = void 0;
|
|
369
|
-
try {
|
|
370
|
-
child.dispose();
|
|
371
|
-
} catch (cause) {
|
|
372
|
-
throw new SubMachineError(prevValue, "dispose", cause);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
if (nextStateDef?.sub !== void 0) {
|
|
376
|
-
let newChild;
|
|
377
|
-
try {
|
|
378
|
-
newChild = createRuntime(nextStateDef.sub, nextStateDef.subImpl ?? {});
|
|
379
|
-
} catch (cause) {
|
|
380
|
-
throw new SubMachineError(nextValue, "init", cause);
|
|
381
|
-
}
|
|
382
|
-
childRuntime = newChild;
|
|
383
|
-
childAbortCleanup = wireChildAbort(newChild);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
function send(event) {
|
|
387
|
-
if (disposed) throw new RuntimeDisposedError();
|
|
388
|
-
const prev = snapshot;
|
|
389
|
-
const result = step(def, prev, event, impl);
|
|
390
|
-
const isExternal = result.changed && (prev.value !== result.snapshot.value || findChosenIsExternal(prev.value, event, prev.context));
|
|
391
|
-
if (result.changed && isExternal) applySubLifecycle(prev.value, result.snapshot.value);
|
|
392
|
-
snapshot = result.snapshot;
|
|
393
|
-
const committed = result.snapshot;
|
|
394
|
-
runMiddleware(prev, event, result.effects, result.changed);
|
|
395
|
-
if (shouldDispatch) dispatchEffects(result.effects, committed.context, event);
|
|
396
|
-
if (result.changed) {
|
|
397
|
-
notify(committed);
|
|
398
|
-
emit("transition", {
|
|
399
|
-
prev,
|
|
400
|
-
next: committed,
|
|
401
|
-
event,
|
|
402
|
-
effects: result.effects,
|
|
403
|
-
changed: true
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
return snapshot;
|
|
407
|
-
}
|
|
408
|
-
function reset(event) {
|
|
409
|
-
if (disposed) throw new RuntimeDisposedError();
|
|
410
|
-
const prev = snapshot;
|
|
411
|
-
const nextSnap = initialSnapshot(def);
|
|
412
|
-
const changed = prev.value !== nextSnap.value;
|
|
413
|
-
if (childRuntime) {
|
|
414
|
-
const child = childRuntime;
|
|
415
|
-
childRuntime = void 0;
|
|
416
|
-
childAbortCleanup?.();
|
|
417
|
-
childAbortCleanup = void 0;
|
|
418
|
-
try {
|
|
419
|
-
child.dispose();
|
|
420
|
-
} catch (cause) {
|
|
421
|
-
throw new SubMachineError(prev.value, "dispose", cause);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
const initStateDef = def.states[nextSnap.value];
|
|
425
|
-
if (initStateDef?.sub) {
|
|
426
|
-
let newChild;
|
|
427
|
-
try {
|
|
428
|
-
newChild = createRuntime(initStateDef.sub, initStateDef.subImpl ?? {});
|
|
429
|
-
} catch (cause) {
|
|
430
|
-
throw new SubMachineError(nextSnap.value, "init", cause);
|
|
431
|
-
}
|
|
432
|
-
childRuntime = newChild;
|
|
433
|
-
childAbortCleanup = wireChildAbort(newChild);
|
|
434
|
-
}
|
|
435
|
-
snapshot = nextSnap;
|
|
436
|
-
const triggerEvent = event ?? RESET_EVENT;
|
|
437
|
-
runMiddleware(prev, triggerEvent, [], changed);
|
|
438
|
-
if (changed) {
|
|
439
|
-
notify();
|
|
440
|
-
emit("transition", {
|
|
441
|
-
prev,
|
|
442
|
-
next: snapshot,
|
|
443
|
-
event: triggerEvent,
|
|
444
|
-
effects: [],
|
|
445
|
-
changed: true
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
return snapshot;
|
|
449
|
-
}
|
|
450
|
-
function can(event) {
|
|
451
|
-
if (disposed || snapshot.status === "final") return false;
|
|
452
|
-
const state = def.states[snapshot.value];
|
|
453
|
-
if (!state) return false;
|
|
454
|
-
const list = normalizeTransitions(state.on?.[event.type]);
|
|
455
|
-
if (list.length === 0) return false;
|
|
456
|
-
for (const t of list) {
|
|
457
|
-
if (!t.guard) return true;
|
|
458
|
-
if (evalGuard(t.guard, snapshot.context, event, impl, snapshot.value)) return true;
|
|
459
|
-
}
|
|
460
|
-
return false;
|
|
461
|
-
}
|
|
462
|
-
function on(type, listener, options) {
|
|
463
|
-
if (disposed || options?.signal?.aborted) return () => {
|
|
464
|
-
};
|
|
465
|
-
const target = eventListeners[type];
|
|
466
|
-
let detachAbort;
|
|
467
|
-
const cleanup = () => {
|
|
468
|
-
target.delete(wrapped);
|
|
469
|
-
if (detachAbort) {
|
|
470
|
-
detachAbort();
|
|
471
|
-
externalAbortCleanups.delete(detachAbort);
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
let wrapped = listener;
|
|
475
|
-
if (options?.once) {
|
|
476
|
-
wrapped = (payload) => {
|
|
477
|
-
cleanup();
|
|
478
|
-
listener(payload);
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
target.add(wrapped);
|
|
482
|
-
const signal = options?.signal;
|
|
483
|
-
if (signal) {
|
|
484
|
-
const onAbort = () => cleanup();
|
|
485
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
486
|
-
detachAbort = () => signal.removeEventListener("abort", onAbort);
|
|
487
|
-
externalAbortCleanups.add(detachAbort);
|
|
488
|
-
}
|
|
489
|
-
return cleanup;
|
|
490
|
-
}
|
|
491
|
-
function dispose() {
|
|
492
|
-
if (disposed) return;
|
|
493
|
-
disposed = true;
|
|
494
|
-
if (childRuntime) {
|
|
495
|
-
childAbortCleanup?.();
|
|
496
|
-
childAbortCleanup = void 0;
|
|
497
|
-
try {
|
|
498
|
-
childRuntime.dispose();
|
|
499
|
-
} catch {
|
|
500
|
-
}
|
|
501
|
-
childRuntime = void 0;
|
|
502
|
-
}
|
|
503
|
-
controller.abort();
|
|
504
|
-
listeners.clear();
|
|
505
|
-
emit("dispose", void 0);
|
|
506
|
-
for (const set of Object.values(eventListeners)) set.clear();
|
|
507
|
-
for (const cleanup of externalAbortCleanups) cleanup();
|
|
508
|
-
externalAbortCleanups.clear();
|
|
509
|
-
}
|
|
510
|
-
const runtime = {
|
|
511
|
-
getSnapshot: () => snapshot,
|
|
512
|
-
snapshot: () => snapshot,
|
|
513
|
-
send,
|
|
514
|
-
can,
|
|
515
|
-
reset,
|
|
516
|
-
dispose,
|
|
517
|
-
on,
|
|
518
|
-
get disposed() {
|
|
519
|
-
return disposed;
|
|
520
|
-
},
|
|
521
|
-
get signal() {
|
|
522
|
-
return controller.signal;
|
|
523
|
-
},
|
|
524
|
-
subscribe(listener) {
|
|
525
|
-
if (disposed) return () => {
|
|
526
|
-
};
|
|
527
|
-
listeners.add(listener);
|
|
528
|
-
return () => listeners.delete(listener);
|
|
529
|
-
},
|
|
530
|
-
subRuntime: () => childRuntime,
|
|
531
|
-
onTransition: (handler, options) => on("transition", handler, options)
|
|
532
|
-
};
|
|
533
|
-
const bootStateDef = def.states[snapshot.value];
|
|
534
|
-
if (bootStateDef?.sub) {
|
|
535
|
-
let newChild;
|
|
536
|
-
try {
|
|
537
|
-
newChild = createRuntime(bootStateDef.sub, bootStateDef.subImpl ?? {});
|
|
538
|
-
} catch (cause) {
|
|
539
|
-
throw new SubMachineError(snapshot.value, "init", cause);
|
|
540
|
-
}
|
|
541
|
-
childRuntime = newChild;
|
|
542
|
-
childAbortCleanup = wireChildAbort(newChild);
|
|
543
|
-
}
|
|
544
|
-
return runtime;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// src/fsm/definition.ts
|
|
548
|
-
function initialSnapshot(def) {
|
|
549
|
-
const isFinal = def.states[def.initial]?.final === true;
|
|
550
|
-
return freezeSnapshot({
|
|
551
|
-
value: def.initial,
|
|
552
|
-
context: def.context,
|
|
553
|
-
status: isFinal ? "final" : "active"
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// src/replay/index.ts
|
|
558
|
-
function replay(initial, events, def, impl) {
|
|
559
|
-
let snapshot = initial;
|
|
560
|
-
const effects = [];
|
|
561
|
-
for (const event of events) {
|
|
562
|
-
const r = step(def, snapshot, event, impl);
|
|
563
|
-
snapshot = r.snapshot;
|
|
564
|
-
if (r.effects.length > 0) {
|
|
565
|
-
for (const eff of r.effects) effects.push(eff);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
return Object.freeze({ snapshot, effects: Object.freeze(effects.slice()) });
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// src/pbt/properties.ts
|
|
572
67
|
function buildAssertOpts(opts) {
|
|
573
68
|
const out = {};
|
|
574
69
|
if (opts?.numRuns !== void 0) out.numRuns = opts.numRuns;
|
|
@@ -636,13 +131,19 @@ function guardsFalseNoTransition(def, impl, eventArbitraries, opts) {
|
|
|
636
131
|
...impl,
|
|
637
132
|
guards: blockedGuards
|
|
638
133
|
};
|
|
134
|
+
const isFullyGuarded = (value, eventType) => {
|
|
135
|
+
const candidates = normalizeTransitions(def.states[value]?.on?.[eventType]);
|
|
136
|
+
return candidates.length > 0 && candidates.every((t) => t.guard !== void 0);
|
|
137
|
+
};
|
|
639
138
|
fc2.assert(
|
|
640
139
|
fc2.property(
|
|
641
140
|
fc2.array(fc2.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),
|
|
642
141
|
(events) => {
|
|
643
142
|
let snap = initialSnapshot(def);
|
|
644
143
|
for (const e of events) {
|
|
144
|
+
const fullyGuarded = isFullyGuarded(snap.value, e.type);
|
|
645
145
|
const r = step(def, snap, e, blockedImpl);
|
|
146
|
+
if (fullyGuarded && r.changed !== false) return false;
|
|
646
147
|
snap = r.snapshot;
|
|
647
148
|
}
|
|
648
149
|
return true;
|