@doeixd/machine 0.0.6 → 0.0.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 +288 -272
- package/dist/cjs/development/index.js +1269 -16
- package/dist/cjs/development/index.js.map +4 -4
- package/dist/cjs/production/index.js +5 -5
- package/dist/esm/development/index.js +1269 -16
- package/dist/esm/development/index.js.map +4 -4
- package/dist/esm/production/index.js +5 -5
- package/dist/types/extract.d.ts +40 -4
- package/dist/types/extract.d.ts.map +1 -1
- package/dist/types/generators.d.ts +40 -9
- package/dist/types/generators.d.ts.map +1 -1
- package/dist/types/higher-order.d.ts +221 -0
- package/dist/types/higher-order.d.ts.map +1 -0
- package/dist/types/index.d.ts +65 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/middleware.d.ts +1048 -0
- package/dist/types/middleware.d.ts.map +1 -0
- package/dist/types/primitives.d.ts +105 -3
- package/dist/types/primitives.d.ts.map +1 -1
- package/dist/types/runtime-extract.d.ts.map +1 -1
- package/dist/types/utils.d.ts +313 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/adapters.ts +407 -0
- package/src/extract.ts +180 -8
- package/src/generators.ts +25 -25
- package/src/higher-order.ts +364 -0
- package/src/index.ts +215 -9
- package/src/middleware.ts +2325 -0
- package/src/primitives.ts +194 -3
- package/src/runtime-extract.ts +15 -0
- package/src/utils.ts +386 -5
|
@@ -20,20 +20,37 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
BoundMachine: () => BoundMachine,
|
|
23
24
|
META_KEY: () => META_KEY,
|
|
24
25
|
MachineBase: () => MachineBase,
|
|
25
26
|
MultiMachineBase: () => MultiMachineBase,
|
|
26
27
|
RUNTIME_META: () => RUNTIME_META,
|
|
27
28
|
action: () => action,
|
|
29
|
+
bindTransitions: () => bindTransitions,
|
|
30
|
+
branch: () => branch,
|
|
31
|
+
call: () => call,
|
|
32
|
+
chain: () => chain,
|
|
33
|
+
combine: () => combine,
|
|
34
|
+
combineFactories: () => combineFactories,
|
|
35
|
+
compose: () => compose,
|
|
36
|
+
composeTyped: () => composeTyped,
|
|
28
37
|
createAsyncMachine: () => createAsyncMachine,
|
|
38
|
+
createCustomMiddleware: () => createCustomMiddleware,
|
|
29
39
|
createEnsemble: () => createEnsemble,
|
|
40
|
+
createEvent: () => createEvent,
|
|
41
|
+
createFetchMachine: () => createFetchMachine,
|
|
30
42
|
createFlow: () => createFlow,
|
|
31
43
|
createMachine: () => createMachine,
|
|
32
44
|
createMachineBuilder: () => createMachineBuilder,
|
|
33
45
|
createMachineFactory: () => createMachineFactory,
|
|
46
|
+
createMiddleware: () => createMiddleware,
|
|
47
|
+
createMiddlewareRegistry: () => createMiddlewareRegistry,
|
|
34
48
|
createMultiMachine: () => createMultiMachine,
|
|
35
49
|
createMutableMachine: () => createMutableMachine,
|
|
50
|
+
createParallelMachine: () => createParallelMachine,
|
|
51
|
+
createPipeline: () => createPipeline,
|
|
36
52
|
createRunner: () => createRunner,
|
|
53
|
+
delegateToChild: () => delegateToChild,
|
|
37
54
|
describe: () => describe,
|
|
38
55
|
extendTransitions: () => extendTransitions,
|
|
39
56
|
extractFromInstance: () => extractFromInstance,
|
|
@@ -43,13 +60,21 @@ __export(src_exports, {
|
|
|
43
60
|
extractStateNode: () => extractStateNode,
|
|
44
61
|
generateChart: () => generateChart,
|
|
45
62
|
generateStatechart: () => generateStatechart,
|
|
63
|
+
guard: () => guard,
|
|
46
64
|
guarded: () => guarded,
|
|
47
65
|
hasState: () => hasState,
|
|
66
|
+
inDevelopment: () => inDevelopment,
|
|
48
67
|
invoke: () => invoke,
|
|
68
|
+
isConditionalMiddleware: () => isConditionalMiddleware,
|
|
69
|
+
isMiddlewareFn: () => isMiddlewareFn,
|
|
70
|
+
isState: () => isState,
|
|
71
|
+
logState: () => logState,
|
|
49
72
|
matchMachine: () => matchMachine,
|
|
73
|
+
mergeContext: () => mergeContext,
|
|
50
74
|
metadata: () => metadata,
|
|
51
75
|
next: () => next,
|
|
52
76
|
overrideTransitions: () => overrideTransitions,
|
|
77
|
+
pipeTransitions: () => pipeTransitions,
|
|
53
78
|
run: () => run,
|
|
54
79
|
runAsync: () => runAsync,
|
|
55
80
|
runMachine: () => runMachine,
|
|
@@ -60,7 +85,22 @@ __export(src_exports, {
|
|
|
60
85
|
setContext: () => setContext,
|
|
61
86
|
step: () => step,
|
|
62
87
|
stepAsync: () => stepAsync,
|
|
88
|
+
toggle: () => toggle,
|
|
63
89
|
transitionTo: () => transitionTo,
|
|
90
|
+
when: () => when,
|
|
91
|
+
whenContext: () => whenContext,
|
|
92
|
+
whenGuard: () => whenGuard,
|
|
93
|
+
withAnalytics: () => withAnalytics,
|
|
94
|
+
withDebugging: () => withDebugging,
|
|
95
|
+
withErrorReporting: () => withErrorReporting,
|
|
96
|
+
withHistory: () => withHistory,
|
|
97
|
+
withLogging: () => withLogging,
|
|
98
|
+
withPerformanceMonitoring: () => withPerformanceMonitoring,
|
|
99
|
+
withPermissions: () => withPermissions,
|
|
100
|
+
withRetry: () => withRetry,
|
|
101
|
+
withSnapshot: () => withSnapshot,
|
|
102
|
+
withTimeTravel: () => withTimeTravel,
|
|
103
|
+
withValidation: () => withValidation,
|
|
64
104
|
yieldMachine: () => yieldMachine
|
|
65
105
|
});
|
|
66
106
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -164,9 +204,9 @@ function describe(_text, transition) {
|
|
|
164
204
|
});
|
|
165
205
|
return transition;
|
|
166
206
|
}
|
|
167
|
-
function guarded(
|
|
207
|
+
function guarded(guard2, transition) {
|
|
168
208
|
attachRuntimeMeta(transition, {
|
|
169
|
-
guards: [
|
|
209
|
+
guards: [guard2]
|
|
170
210
|
});
|
|
171
211
|
return transition;
|
|
172
212
|
}
|
|
@@ -187,6 +227,62 @@ function action(action2, transition) {
|
|
|
187
227
|
});
|
|
188
228
|
return transition;
|
|
189
229
|
}
|
|
230
|
+
function guard(condition, transition, options = {}) {
|
|
231
|
+
const { onFail = "throw", errorMessage, description } = options;
|
|
232
|
+
const fullOptions = { ...options, onFail, errorMessage, description };
|
|
233
|
+
const guardedTransition = async function(...args) {
|
|
234
|
+
const isMachine = typeof this === "object" && "context" in this;
|
|
235
|
+
const ctx = isMachine ? this.context : this;
|
|
236
|
+
const conditionResult = await Promise.resolve(condition(ctx, ...args));
|
|
237
|
+
if (conditionResult) {
|
|
238
|
+
const contextForTransition = isMachine ? this.context : this;
|
|
239
|
+
return transition.apply(contextForTransition, args);
|
|
240
|
+
} else {
|
|
241
|
+
if (onFail === "throw") {
|
|
242
|
+
const message = errorMessage || "Guard condition failed";
|
|
243
|
+
throw new Error(message);
|
|
244
|
+
} else if (onFail === "ignore") {
|
|
245
|
+
if (isMachine) {
|
|
246
|
+
return this;
|
|
247
|
+
} else {
|
|
248
|
+
throw new Error('Cannot use "ignore" mode with context-only binding. Use full machine binding or provide fallback.');
|
|
249
|
+
}
|
|
250
|
+
} else if (typeof onFail === "function") {
|
|
251
|
+
if (isMachine) {
|
|
252
|
+
return onFail.apply(this, args);
|
|
253
|
+
} else {
|
|
254
|
+
throw new Error("Cannot use function fallback with context-only binding. Use full machine binding.");
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
return onFail;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
Object.defineProperty(guardedTransition, "__guard", { value: true, enumerable: false });
|
|
262
|
+
Object.defineProperty(guardedTransition, "condition", { value: condition, enumerable: false });
|
|
263
|
+
Object.defineProperty(guardedTransition, "transition", { value: transition, enumerable: false });
|
|
264
|
+
Object.defineProperty(guardedTransition, "options", { value: fullOptions, enumerable: false });
|
|
265
|
+
attachRuntimeMeta(guardedTransition, {
|
|
266
|
+
description: description || "Runtime guarded transition",
|
|
267
|
+
guards: [{ name: "runtime_guard", description: description || "Runtime condition check" }]
|
|
268
|
+
});
|
|
269
|
+
return guardedTransition;
|
|
270
|
+
}
|
|
271
|
+
function whenGuard(condition) {
|
|
272
|
+
return {
|
|
273
|
+
/**
|
|
274
|
+
* Define the transition to execute when the condition passes.
|
|
275
|
+
* Returns a guarded transition that can optionally have an else clause.
|
|
276
|
+
*/
|
|
277
|
+
do(transition) {
|
|
278
|
+
const guarded2 = guard(condition, transition);
|
|
279
|
+
guarded2.else = function(fallback) {
|
|
280
|
+
return guard(condition, transition, { onFail: fallback });
|
|
281
|
+
};
|
|
282
|
+
return guarded2;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
190
286
|
function metadata(_meta, value) {
|
|
191
287
|
return value;
|
|
192
288
|
}
|
|
@@ -256,17 +352,17 @@ function parseInvokeService(obj) {
|
|
|
256
352
|
}
|
|
257
353
|
return service;
|
|
258
354
|
}
|
|
259
|
-
function extractFromCallExpression(
|
|
260
|
-
if (!import_ts_morph.Node.isCallExpression(
|
|
355
|
+
function extractFromCallExpression(call2, verbose = false) {
|
|
356
|
+
if (!import_ts_morph.Node.isCallExpression(call2)) {
|
|
261
357
|
return null;
|
|
262
358
|
}
|
|
263
|
-
const expression =
|
|
359
|
+
const expression = call2.getExpression();
|
|
264
360
|
const fnName = import_ts_morph.Node.isIdentifier(expression) ? expression.getText() : null;
|
|
265
361
|
if (!fnName) {
|
|
266
362
|
return null;
|
|
267
363
|
}
|
|
268
364
|
const metadata2 = {};
|
|
269
|
-
const args =
|
|
365
|
+
const args = call2.getArguments();
|
|
270
366
|
switch (fnName) {
|
|
271
367
|
case "transitionTo":
|
|
272
368
|
if (args[0]) {
|
|
@@ -286,9 +382,9 @@ function extractFromCallExpression(call, verbose = false) {
|
|
|
286
382
|
break;
|
|
287
383
|
case "guarded":
|
|
288
384
|
if (args[0]) {
|
|
289
|
-
const
|
|
290
|
-
if (Object.keys(
|
|
291
|
-
metadata2.guards = [
|
|
385
|
+
const guard2 = parseObjectLiteral(args[0]);
|
|
386
|
+
if (Object.keys(guard2).length > 0) {
|
|
387
|
+
metadata2.guards = [guard2];
|
|
292
388
|
}
|
|
293
389
|
}
|
|
294
390
|
if (args[1] && import_ts_morph.Node.isCallExpression(args[1])) {
|
|
@@ -406,6 +502,26 @@ function analyzeStateNode(classSymbol, verbose = false) {
|
|
|
406
502
|
}
|
|
407
503
|
return chartNode;
|
|
408
504
|
}
|
|
505
|
+
function analyzeStateNodeWithNesting(className, classSymbol, sourceFile, childConfig, verbose = false) {
|
|
506
|
+
const stateNode = analyzeStateNode(classSymbol, verbose);
|
|
507
|
+
if (childConfig) {
|
|
508
|
+
if (verbose) {
|
|
509
|
+
console.error(` 👪 Analyzing children for state: ${className}`);
|
|
510
|
+
}
|
|
511
|
+
stateNode.initial = childConfig.initialState;
|
|
512
|
+
stateNode.states = {};
|
|
513
|
+
for (const childClassName of childConfig.classes) {
|
|
514
|
+
const childClassDeclaration = sourceFile.getClass(childClassName);
|
|
515
|
+
if (childClassDeclaration) {
|
|
516
|
+
const childSymbol = childClassDeclaration.getSymbolOrThrow();
|
|
517
|
+
stateNode.states[childClassName] = analyzeStateNode(childSymbol, verbose);
|
|
518
|
+
} else {
|
|
519
|
+
console.warn(`⚠️ Warning: Child class '${childClassName}' not found.`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return stateNode;
|
|
524
|
+
}
|
|
409
525
|
function extractMachine(config, project, verbose = false) {
|
|
410
526
|
if (verbose) {
|
|
411
527
|
console.error(`
|
|
@@ -416,6 +532,45 @@ function extractMachine(config, project, verbose = false) {
|
|
|
416
532
|
if (!sourceFile) {
|
|
417
533
|
throw new Error(`Source file not found: ${config.input}`);
|
|
418
534
|
}
|
|
535
|
+
if (config.parallel) {
|
|
536
|
+
if (verbose) {
|
|
537
|
+
console.error(` ⏹️ Parallel machine detected. Analyzing regions.`);
|
|
538
|
+
}
|
|
539
|
+
const parallelChart = {
|
|
540
|
+
id: config.id,
|
|
541
|
+
type: "parallel",
|
|
542
|
+
states: {}
|
|
543
|
+
};
|
|
544
|
+
if (config.description) {
|
|
545
|
+
parallelChart.description = config.description;
|
|
546
|
+
}
|
|
547
|
+
for (const region of config.parallel.regions) {
|
|
548
|
+
if (verbose) {
|
|
549
|
+
console.error(` 📍 Analyzing region: ${region.name}`);
|
|
550
|
+
}
|
|
551
|
+
const regionStates = {};
|
|
552
|
+
for (const className of region.classes) {
|
|
553
|
+
const classDeclaration = sourceFile.getClass(className);
|
|
554
|
+
if (classDeclaration) {
|
|
555
|
+
const classSymbol = classDeclaration.getSymbolOrThrow();
|
|
556
|
+
regionStates[className] = analyzeStateNode(classSymbol, verbose);
|
|
557
|
+
} else {
|
|
558
|
+
console.warn(`⚠️ Warning: Class '${className}' not found for region '${region.name}'.`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
parallelChart.states[region.name] = {
|
|
562
|
+
initial: region.initialState,
|
|
563
|
+
states: regionStates
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
if (verbose) {
|
|
567
|
+
console.error(` ✅ Extracted ${config.parallel.regions.length} parallel regions`);
|
|
568
|
+
}
|
|
569
|
+
return parallelChart;
|
|
570
|
+
}
|
|
571
|
+
if (!config.initialState || !config.classes) {
|
|
572
|
+
throw new Error(`Machine config for '${config.id}' must have either 'parallel' or 'initialState'/'classes'.`);
|
|
573
|
+
}
|
|
419
574
|
const fullChart = {
|
|
420
575
|
id: config.id,
|
|
421
576
|
initial: config.initialState,
|
|
@@ -431,7 +586,14 @@ function extractMachine(config, project, verbose = false) {
|
|
|
431
586
|
continue;
|
|
432
587
|
}
|
|
433
588
|
const classSymbol = classDeclaration.getSymbolOrThrow();
|
|
434
|
-
const
|
|
589
|
+
const hasChildren = className === config.initialState && config.children;
|
|
590
|
+
const stateNode = analyzeStateNodeWithNesting(
|
|
591
|
+
className,
|
|
592
|
+
classSymbol,
|
|
593
|
+
sourceFile,
|
|
594
|
+
hasChildren ? config.children : void 0,
|
|
595
|
+
verbose
|
|
596
|
+
);
|
|
435
597
|
fullChart.states[className] = stateNode;
|
|
436
598
|
}
|
|
437
599
|
if (verbose) {
|
|
@@ -539,6 +701,16 @@ function extractStateNode(stateInstance) {
|
|
|
539
701
|
transition.actions = meta.actions.map((a) => a.name);
|
|
540
702
|
}
|
|
541
703
|
stateNode.on[key] = transition;
|
|
704
|
+
} else if (meta.guards && meta.guards.length > 0) {
|
|
705
|
+
const transition = {
|
|
706
|
+
target: "GuardedTransition",
|
|
707
|
+
// Placeholder - actual target determined at runtime
|
|
708
|
+
cond: meta.guards.map((g) => g.name).join(" && ")
|
|
709
|
+
};
|
|
710
|
+
if (meta.description) {
|
|
711
|
+
transition.description = meta.description;
|
|
712
|
+
}
|
|
713
|
+
stateNode.on[key] = transition;
|
|
542
714
|
}
|
|
543
715
|
}
|
|
544
716
|
if (invoke2.length > 0) {
|
|
@@ -797,9 +969,1055 @@ function createMutableMachine(sharedContext, factories, getDiscriminant) {
|
|
|
797
969
|
});
|
|
798
970
|
}
|
|
799
971
|
|
|
972
|
+
// src/higher-order.ts
|
|
973
|
+
function delegateToChild(actionName) {
|
|
974
|
+
return function(...args) {
|
|
975
|
+
const child = this.context.child;
|
|
976
|
+
if (typeof child[actionName] === "function") {
|
|
977
|
+
const newChildState = child[actionName](...args);
|
|
978
|
+
return setContext(this, { ...this.context, child: newChildState });
|
|
979
|
+
}
|
|
980
|
+
return this;
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function toggle(prop) {
|
|
984
|
+
return function() {
|
|
985
|
+
if (typeof this.context[prop] !== "boolean") {
|
|
986
|
+
console.warn(`[toggle primitive] Property '${String(prop)}' is not a boolean. Toggling may have unexpected results.`);
|
|
987
|
+
}
|
|
988
|
+
return setContext(this, {
|
|
989
|
+
...this.context,
|
|
990
|
+
[prop]: !this.context[prop]
|
|
991
|
+
});
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
var IdleMachine = class extends MachineBase {
|
|
995
|
+
constructor(config) {
|
|
996
|
+
super({ status: "idle" });
|
|
997
|
+
this.config = config;
|
|
998
|
+
this.fetch = (params) => new LoadingMachine(this.config, params != null ? params : this.config.initialParams, 1);
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
var LoadingMachine = class extends MachineBase {
|
|
1002
|
+
constructor(config, params, attempts) {
|
|
1003
|
+
super({ status: "loading", abortController: new AbortController(), attempts });
|
|
1004
|
+
this.config = config;
|
|
1005
|
+
this.params = params;
|
|
1006
|
+
this.succeed = (data) => {
|
|
1007
|
+
var _a, _b;
|
|
1008
|
+
(_b = (_a = this.config).onSuccess) == null ? void 0 : _b.call(_a, data);
|
|
1009
|
+
return new SuccessMachine(this.config, { status: "success", data });
|
|
1010
|
+
};
|
|
1011
|
+
this.fail = (error) => {
|
|
1012
|
+
var _a, _b, _c;
|
|
1013
|
+
const maxRetries = (_a = this.config.maxRetries) != null ? _a : 3;
|
|
1014
|
+
if (this.context.attempts < maxRetries) {
|
|
1015
|
+
return new RetryingMachine(this.config, this.params, error, this.context.attempts);
|
|
1016
|
+
}
|
|
1017
|
+
(_c = (_b = this.config).onError) == null ? void 0 : _c.call(_b, error);
|
|
1018
|
+
return new ErrorMachine(this.config, { status: "error", error });
|
|
1019
|
+
};
|
|
1020
|
+
this.cancel = () => {
|
|
1021
|
+
this.context.abortController.abort();
|
|
1022
|
+
return new CanceledMachine(this.config);
|
|
1023
|
+
};
|
|
1024
|
+
this.execute();
|
|
1025
|
+
}
|
|
1026
|
+
async execute() {
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
var RetryingMachine = class extends MachineBase {
|
|
1030
|
+
constructor(config, params, error, attempts) {
|
|
1031
|
+
super({ status: "retrying", error, attempts });
|
|
1032
|
+
this.config = config;
|
|
1033
|
+
this.params = params;
|
|
1034
|
+
// This would be called after a delay.
|
|
1035
|
+
this.retry = (params) => new LoadingMachine(this.config, params != null ? params : this.params, this.context.attempts + 1);
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
var SuccessMachine = class extends MachineBase {
|
|
1039
|
+
constructor(config, context) {
|
|
1040
|
+
super(context);
|
|
1041
|
+
this.config = config;
|
|
1042
|
+
this.refetch = (params) => new LoadingMachine(this.config, params != null ? params : this.config.initialParams, 1);
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
var ErrorMachine = class extends MachineBase {
|
|
1046
|
+
constructor(config, context) {
|
|
1047
|
+
super(context);
|
|
1048
|
+
this.config = config;
|
|
1049
|
+
this.retry = (params) => new LoadingMachine(this.config, params != null ? params : this.config.initialParams, 1);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
var CanceledMachine = class extends MachineBase {
|
|
1053
|
+
constructor(config) {
|
|
1054
|
+
super({ status: "canceled" });
|
|
1055
|
+
this.config = config;
|
|
1056
|
+
this.refetch = (params) => new LoadingMachine(this.config, params != null ? params : this.config.initialParams, 1);
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
function createFetchMachine(config) {
|
|
1060
|
+
return new IdleMachine(config);
|
|
1061
|
+
}
|
|
1062
|
+
function createParallelMachine(m1, m2) {
|
|
1063
|
+
const combinedContext = { ...m1.context, ...m2.context };
|
|
1064
|
+
const transitions1 = { ...m1 };
|
|
1065
|
+
const transitions2 = { ...m2 };
|
|
1066
|
+
delete transitions1.context;
|
|
1067
|
+
delete transitions2.context;
|
|
1068
|
+
const combinedTransitions = {};
|
|
1069
|
+
for (const key in transitions1) {
|
|
1070
|
+
const transitionFn = transitions1[key];
|
|
1071
|
+
combinedTransitions[key] = (...args) => {
|
|
1072
|
+
const nextM1 = transitionFn.apply(m1.context, args);
|
|
1073
|
+
return createParallelMachine(nextM1, m2);
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
for (const key in transitions2) {
|
|
1077
|
+
const transitionFn = transitions2[key];
|
|
1078
|
+
combinedTransitions[key] = (...args) => {
|
|
1079
|
+
const nextM2 = transitionFn.apply(m2.context, args);
|
|
1080
|
+
return createParallelMachine(m1, nextM2);
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
return {
|
|
1084
|
+
context: combinedContext,
|
|
1085
|
+
...combinedTransitions
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// src/middleware.ts
|
|
1090
|
+
var CANCEL = Symbol("CANCEL");
|
|
1091
|
+
function createMiddleware(machine, hooks, options = {}) {
|
|
1092
|
+
const { mode = "auto", exclude = ["context"] } = options;
|
|
1093
|
+
const wrapped = {};
|
|
1094
|
+
for (const prop in machine) {
|
|
1095
|
+
if (!Object.prototype.hasOwnProperty.call(machine, prop)) continue;
|
|
1096
|
+
const value = machine[prop];
|
|
1097
|
+
if (prop === "context") {
|
|
1098
|
+
wrapped.context = value;
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (exclude.includes(prop)) {
|
|
1102
|
+
wrapped[prop] = value;
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
if (typeof value !== "function" || prop.startsWith("_")) {
|
|
1106
|
+
wrapped[prop] = value;
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
wrapped[prop] = createTransitionWrapper(
|
|
1110
|
+
prop,
|
|
1111
|
+
value,
|
|
1112
|
+
machine,
|
|
1113
|
+
hooks,
|
|
1114
|
+
mode
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
return wrapped;
|
|
1118
|
+
}
|
|
1119
|
+
function createTransitionWrapper(transitionName, originalFn, machine, hooks, mode) {
|
|
1120
|
+
return function wrappedTransition(...args) {
|
|
1121
|
+
const context = machine.context;
|
|
1122
|
+
const middlewareCtx = {
|
|
1123
|
+
transitionName,
|
|
1124
|
+
context,
|
|
1125
|
+
args
|
|
1126
|
+
};
|
|
1127
|
+
const executeSyncTransition = () => {
|
|
1128
|
+
try {
|
|
1129
|
+
if (hooks.before) {
|
|
1130
|
+
const beforeResult = hooks.before(middlewareCtx);
|
|
1131
|
+
if (beforeResult === CANCEL) {
|
|
1132
|
+
return machine;
|
|
1133
|
+
}
|
|
1134
|
+
if (beforeResult instanceof Promise) {
|
|
1135
|
+
throw new Error(
|
|
1136
|
+
`Middleware mode is 'sync' but before hook returned Promise for transition: ${transitionName}`
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
const result = originalFn.call(this, ...args);
|
|
1141
|
+
if (result instanceof Promise) {
|
|
1142
|
+
return handleAsyncResult(result, context);
|
|
1143
|
+
}
|
|
1144
|
+
if (hooks.after) {
|
|
1145
|
+
const middlewareResult = {
|
|
1146
|
+
transitionName,
|
|
1147
|
+
prevContext: context,
|
|
1148
|
+
nextContext: result.context,
|
|
1149
|
+
args
|
|
1150
|
+
};
|
|
1151
|
+
const afterResult = hooks.after(middlewareResult);
|
|
1152
|
+
if (afterResult instanceof Promise) {
|
|
1153
|
+
throw new Error(
|
|
1154
|
+
`Middleware mode is 'sync' but after hook returned Promise for transition: ${transitionName}`
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return result;
|
|
1159
|
+
} catch (err) {
|
|
1160
|
+
if (hooks.error) {
|
|
1161
|
+
const middlewareError = {
|
|
1162
|
+
transitionName,
|
|
1163
|
+
context,
|
|
1164
|
+
args,
|
|
1165
|
+
error: err
|
|
1166
|
+
};
|
|
1167
|
+
const errorResult = hooks.error(middlewareError);
|
|
1168
|
+
if (errorResult instanceof Promise) {
|
|
1169
|
+
errorResult.catch(() => {
|
|
1170
|
+
});
|
|
1171
|
+
throw err;
|
|
1172
|
+
}
|
|
1173
|
+
if (errorResult && typeof errorResult === "object" && "context" in errorResult) {
|
|
1174
|
+
return errorResult;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
throw err;
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
const handleAsyncResult = async (resultPromise, ctx) => {
|
|
1181
|
+
try {
|
|
1182
|
+
const result = await resultPromise;
|
|
1183
|
+
if (hooks.after) {
|
|
1184
|
+
const middlewareResult = {
|
|
1185
|
+
transitionName,
|
|
1186
|
+
prevContext: ctx,
|
|
1187
|
+
nextContext: result.context,
|
|
1188
|
+
args
|
|
1189
|
+
};
|
|
1190
|
+
await hooks.after(middlewareResult);
|
|
1191
|
+
}
|
|
1192
|
+
return result;
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
if (hooks.error) {
|
|
1195
|
+
const middlewareError = {
|
|
1196
|
+
transitionName,
|
|
1197
|
+
context: ctx,
|
|
1198
|
+
args,
|
|
1199
|
+
error: err
|
|
1200
|
+
};
|
|
1201
|
+
const errorResult = await hooks.error(middlewareError);
|
|
1202
|
+
if (errorResult && typeof errorResult === "object" && "context" in errorResult) {
|
|
1203
|
+
return errorResult;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
throw err;
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
const executeAsyncTransition = async () => {
|
|
1210
|
+
try {
|
|
1211
|
+
if (hooks.before) {
|
|
1212
|
+
const beforeResult = await hooks.before(middlewareCtx);
|
|
1213
|
+
if (beforeResult === CANCEL) {
|
|
1214
|
+
return machine;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
const result = await originalFn.call(this, ...args);
|
|
1218
|
+
if (hooks.after) {
|
|
1219
|
+
const middlewareResult = {
|
|
1220
|
+
transitionName,
|
|
1221
|
+
prevContext: context,
|
|
1222
|
+
nextContext: result.context,
|
|
1223
|
+
args
|
|
1224
|
+
};
|
|
1225
|
+
await hooks.after(middlewareResult);
|
|
1226
|
+
}
|
|
1227
|
+
return result;
|
|
1228
|
+
} catch (err) {
|
|
1229
|
+
if (hooks.error) {
|
|
1230
|
+
const middlewareError = {
|
|
1231
|
+
transitionName,
|
|
1232
|
+
context,
|
|
1233
|
+
args,
|
|
1234
|
+
error: err
|
|
1235
|
+
};
|
|
1236
|
+
const errorResult = await hooks.error(middlewareError);
|
|
1237
|
+
if (errorResult && typeof errorResult === "object" && "context" in errorResult) {
|
|
1238
|
+
return errorResult;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
throw err;
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
if (mode === "async") {
|
|
1245
|
+
return executeAsyncTransition();
|
|
1246
|
+
} else if (mode === "sync") {
|
|
1247
|
+
return executeSyncTransition();
|
|
1248
|
+
} else {
|
|
1249
|
+
return executeSyncTransition();
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
function withLogging(machine, options = {}) {
|
|
1254
|
+
const {
|
|
1255
|
+
logger = console.log,
|
|
1256
|
+
includeContext = true,
|
|
1257
|
+
includeArgs = true
|
|
1258
|
+
} = options;
|
|
1259
|
+
return createMiddleware(machine, {
|
|
1260
|
+
before: ({ transitionName, args }) => {
|
|
1261
|
+
const argsStr = includeArgs && args.length > 0 ? ` ${JSON.stringify(args)}` : "";
|
|
1262
|
+
logger(`→ ${transitionName}${argsStr}`);
|
|
1263
|
+
},
|
|
1264
|
+
after: ({ transitionName, nextContext }) => {
|
|
1265
|
+
const contextStr = includeContext ? ` ${JSON.stringify(nextContext)}` : "";
|
|
1266
|
+
logger(`✓ ${transitionName}${contextStr}`);
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
function withAnalytics(machine, track, options = {}) {
|
|
1271
|
+
const {
|
|
1272
|
+
eventPrefix = "state_transition",
|
|
1273
|
+
includePrevContext = false,
|
|
1274
|
+
includeArgs = true
|
|
1275
|
+
} = options;
|
|
1276
|
+
return createMiddleware(machine, {
|
|
1277
|
+
after: async ({ transitionName, prevContext, nextContext, args }) => {
|
|
1278
|
+
const properties = {
|
|
1279
|
+
transition: transitionName,
|
|
1280
|
+
to: nextContext
|
|
1281
|
+
};
|
|
1282
|
+
if (includePrevContext) {
|
|
1283
|
+
properties.from = prevContext;
|
|
1284
|
+
}
|
|
1285
|
+
if (includeArgs && args.length > 0) {
|
|
1286
|
+
properties.args = args;
|
|
1287
|
+
}
|
|
1288
|
+
await track(`${eventPrefix}.${transitionName}`, properties);
|
|
1289
|
+
}
|
|
1290
|
+
}, { mode: "async" });
|
|
1291
|
+
}
|
|
1292
|
+
function withValidation(machine, validate, options) {
|
|
1293
|
+
return createMiddleware(machine, {
|
|
1294
|
+
before: (ctx) => {
|
|
1295
|
+
const result = validate(ctx);
|
|
1296
|
+
if (result instanceof Promise) {
|
|
1297
|
+
return result.then((r) => {
|
|
1298
|
+
if (r === false) {
|
|
1299
|
+
throw new Error(`Validation failed for transition: ${ctx.transitionName}`);
|
|
1300
|
+
}
|
|
1301
|
+
return void 0;
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
if (result === false) {
|
|
1305
|
+
throw new Error(`Validation failed for transition: ${ctx.transitionName}`);
|
|
1306
|
+
}
|
|
1307
|
+
return void 0;
|
|
1308
|
+
}
|
|
1309
|
+
}, { mode: "auto", ...options });
|
|
1310
|
+
}
|
|
1311
|
+
function withPermissions(machine, canPerform, options) {
|
|
1312
|
+
return createMiddleware(machine, {
|
|
1313
|
+
before: (ctx) => {
|
|
1314
|
+
const result = canPerform(ctx);
|
|
1315
|
+
if (result instanceof Promise) {
|
|
1316
|
+
return result.then((allowed) => {
|
|
1317
|
+
if (!allowed) {
|
|
1318
|
+
throw new Error(`Unauthorized transition: ${ctx.transitionName}`);
|
|
1319
|
+
}
|
|
1320
|
+
return void 0;
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
if (!result) {
|
|
1324
|
+
throw new Error(`Unauthorized transition: ${ctx.transitionName}`);
|
|
1325
|
+
}
|
|
1326
|
+
return void 0;
|
|
1327
|
+
}
|
|
1328
|
+
}, { mode: "auto", ...options });
|
|
1329
|
+
}
|
|
1330
|
+
function withErrorReporting(machine, captureError, options = {}) {
|
|
1331
|
+
const { includeContext = true, includeArgs = true, mode } = options;
|
|
1332
|
+
return createMiddleware(machine, {
|
|
1333
|
+
error: async ({ transitionName, context, args, error }) => {
|
|
1334
|
+
const errorContext = {
|
|
1335
|
+
transition: transitionName
|
|
1336
|
+
};
|
|
1337
|
+
if (includeContext) {
|
|
1338
|
+
errorContext.context = context;
|
|
1339
|
+
}
|
|
1340
|
+
if (includeArgs && args.length > 0) {
|
|
1341
|
+
errorContext.args = args;
|
|
1342
|
+
}
|
|
1343
|
+
await Promise.resolve(captureError(error, errorContext));
|
|
1344
|
+
}
|
|
1345
|
+
}, { mode });
|
|
1346
|
+
}
|
|
1347
|
+
function withPerformanceMonitoring(machine, onMetric) {
|
|
1348
|
+
const timings = /* @__PURE__ */ new Map();
|
|
1349
|
+
return createMiddleware(machine, {
|
|
1350
|
+
before: ({ transitionName }) => {
|
|
1351
|
+
timings.set(transitionName, performance.now());
|
|
1352
|
+
return void 0;
|
|
1353
|
+
},
|
|
1354
|
+
after: ({ transitionName, nextContext }) => {
|
|
1355
|
+
const startTime = timings.get(transitionName);
|
|
1356
|
+
if (startTime) {
|
|
1357
|
+
const duration = performance.now() - startTime;
|
|
1358
|
+
timings.delete(transitionName);
|
|
1359
|
+
const result = onMetric({ transitionName, duration, context: nextContext });
|
|
1360
|
+
if (result instanceof Promise) {
|
|
1361
|
+
return result;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return void 0;
|
|
1365
|
+
}
|
|
1366
|
+
}, { mode: "auto" });
|
|
1367
|
+
}
|
|
1368
|
+
function withRetry(machine, options = {}) {
|
|
1369
|
+
const {
|
|
1370
|
+
maxRetries = 3,
|
|
1371
|
+
delay = 1e3,
|
|
1372
|
+
backoffMultiplier = 1,
|
|
1373
|
+
shouldRetry = () => true,
|
|
1374
|
+
onRetry
|
|
1375
|
+
} = options;
|
|
1376
|
+
const wrapped = {};
|
|
1377
|
+
for (const prop in machine) {
|
|
1378
|
+
if (!Object.prototype.hasOwnProperty.call(machine, prop)) continue;
|
|
1379
|
+
const value = machine[prop];
|
|
1380
|
+
if (prop === "context" || typeof value !== "function") {
|
|
1381
|
+
wrapped[prop] = value;
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
wrapped[prop] = async function retriableTransition(...args) {
|
|
1385
|
+
let lastError;
|
|
1386
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1387
|
+
try {
|
|
1388
|
+
return await value.call(this, ...args);
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
lastError = error;
|
|
1391
|
+
if (attempt === maxRetries) {
|
|
1392
|
+
break;
|
|
1393
|
+
}
|
|
1394
|
+
if (!shouldRetry(lastError)) {
|
|
1395
|
+
break;
|
|
1396
|
+
}
|
|
1397
|
+
onRetry == null ? void 0 : onRetry(attempt + 1, lastError);
|
|
1398
|
+
const currentDelay = delay * Math.pow(backoffMultiplier, attempt);
|
|
1399
|
+
await new Promise((resolve) => setTimeout(resolve, currentDelay));
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
throw lastError;
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
return wrapped;
|
|
1406
|
+
}
|
|
1407
|
+
function withHistory(machine, options = {}) {
|
|
1408
|
+
const {
|
|
1409
|
+
maxSize,
|
|
1410
|
+
serializer,
|
|
1411
|
+
filter,
|
|
1412
|
+
onEntry,
|
|
1413
|
+
_isRewrap = false
|
|
1414
|
+
} = options;
|
|
1415
|
+
const history = [];
|
|
1416
|
+
let entryId = 0;
|
|
1417
|
+
const instrumentedMachine = createMiddleware(machine, {
|
|
1418
|
+
before: ({ transitionName, args }) => {
|
|
1419
|
+
if (filter && !filter(transitionName, args)) {
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
const entry = {
|
|
1423
|
+
id: `entry-${entryId++}`,
|
|
1424
|
+
transitionName,
|
|
1425
|
+
args: [...args],
|
|
1426
|
+
// Shallow clone args (fast, works with any type)
|
|
1427
|
+
timestamp: Date.now()
|
|
1428
|
+
};
|
|
1429
|
+
if (serializer) {
|
|
1430
|
+
try {
|
|
1431
|
+
entry.serializedArgs = serializer.serialize(args);
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
console.error("Failed to serialize history args:", err);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
history.push(entry);
|
|
1437
|
+
if (maxSize && history.length > maxSize) {
|
|
1438
|
+
history.shift();
|
|
1439
|
+
}
|
|
1440
|
+
onEntry == null ? void 0 : onEntry(entry);
|
|
1441
|
+
}
|
|
1442
|
+
}, { exclude: ["context", "history", "clearHistory"] });
|
|
1443
|
+
if (!_isRewrap) {
|
|
1444
|
+
for (const prop in instrumentedMachine) {
|
|
1445
|
+
if (!Object.prototype.hasOwnProperty.call(instrumentedMachine, prop)) continue;
|
|
1446
|
+
const value = instrumentedMachine[prop];
|
|
1447
|
+
if (typeof value === "function" && !prop.startsWith("_") && prop !== "context" && !["history", "clearHistory"].includes(prop)) {
|
|
1448
|
+
const originalFn = value;
|
|
1449
|
+
instrumentedMachine[prop] = function(...args) {
|
|
1450
|
+
const result = originalFn.apply(this, args);
|
|
1451
|
+
if (result && typeof result === "object" && "context" in result && !("history" in result)) {
|
|
1452
|
+
const rewrappedResult = createMiddleware(result, {
|
|
1453
|
+
before: ({ transitionName, args: transArgs }) => {
|
|
1454
|
+
if (filter && !filter(transitionName, transArgs)) {
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const entry = {
|
|
1458
|
+
id: `entry-${entryId++}`,
|
|
1459
|
+
transitionName,
|
|
1460
|
+
args: [...transArgs],
|
|
1461
|
+
timestamp: Date.now()
|
|
1462
|
+
};
|
|
1463
|
+
if (serializer) {
|
|
1464
|
+
try {
|
|
1465
|
+
entry.serializedArgs = serializer.serialize(transArgs);
|
|
1466
|
+
} catch (err) {
|
|
1467
|
+
console.error("Failed to serialize history args:", err);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
history.push(entry);
|
|
1471
|
+
if (maxSize && history.length > maxSize) {
|
|
1472
|
+
history.shift();
|
|
1473
|
+
}
|
|
1474
|
+
onEntry == null ? void 0 : onEntry(entry);
|
|
1475
|
+
}
|
|
1476
|
+
}, { exclude: ["context", "history", "clearHistory"] });
|
|
1477
|
+
return Object.assign(rewrappedResult, {
|
|
1478
|
+
history,
|
|
1479
|
+
clearHistory: () => {
|
|
1480
|
+
history.length = 0;
|
|
1481
|
+
entryId = 0;
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
return result;
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
return Object.assign(instrumentedMachine, {
|
|
1491
|
+
history,
|
|
1492
|
+
clearHistory: () => {
|
|
1493
|
+
history.length = 0;
|
|
1494
|
+
entryId = 0;
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
function withSnapshot(machine, options = {}) {
|
|
1499
|
+
const {
|
|
1500
|
+
maxSize,
|
|
1501
|
+
serializer,
|
|
1502
|
+
captureSnapshot,
|
|
1503
|
+
onlyIfChanged = false,
|
|
1504
|
+
filter,
|
|
1505
|
+
onSnapshot,
|
|
1506
|
+
_extraExclusions = [],
|
|
1507
|
+
_isRewrap = false
|
|
1508
|
+
} = options;
|
|
1509
|
+
const snapshots = [];
|
|
1510
|
+
let snapshotId = 0;
|
|
1511
|
+
const instrumentedMachine = createMiddleware(machine, {
|
|
1512
|
+
after: ({ transitionName, prevContext, nextContext }) => {
|
|
1513
|
+
if (filter && !filter(transitionName)) {
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
if (onlyIfChanged) {
|
|
1517
|
+
const changed = JSON.stringify(prevContext) !== JSON.stringify(nextContext);
|
|
1518
|
+
if (!changed) {
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
const snapshot = {
|
|
1523
|
+
id: `snapshot-${snapshotId++}`,
|
|
1524
|
+
transitionName,
|
|
1525
|
+
before: { ...prevContext },
|
|
1526
|
+
// Clone
|
|
1527
|
+
after: { ...nextContext },
|
|
1528
|
+
// Clone
|
|
1529
|
+
timestamp: Date.now()
|
|
1530
|
+
};
|
|
1531
|
+
if (serializer) {
|
|
1532
|
+
try {
|
|
1533
|
+
snapshot.serializedBefore = serializer.serialize(prevContext);
|
|
1534
|
+
snapshot.serializedAfter = serializer.serialize(nextContext);
|
|
1535
|
+
} catch (err) {
|
|
1536
|
+
console.error("Failed to serialize snapshot:", err);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (captureSnapshot) {
|
|
1540
|
+
try {
|
|
1541
|
+
snapshot.diff = captureSnapshot(prevContext, nextContext);
|
|
1542
|
+
} catch (err) {
|
|
1543
|
+
console.error("Failed to capture snapshot:", err);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
snapshots.push(snapshot);
|
|
1547
|
+
if (maxSize && snapshots.length > maxSize) {
|
|
1548
|
+
snapshots.shift();
|
|
1549
|
+
}
|
|
1550
|
+
onSnapshot == null ? void 0 : onSnapshot(snapshot);
|
|
1551
|
+
}
|
|
1552
|
+
}, { exclude: ["context", "snapshots", "clearSnapshots", "restoreSnapshot", ..._extraExclusions] });
|
|
1553
|
+
const restoreSnapshot = (context) => {
|
|
1554
|
+
const { context: _, ...transitions } = machine;
|
|
1555
|
+
return { context, ...transitions };
|
|
1556
|
+
};
|
|
1557
|
+
if (!_isRewrap) {
|
|
1558
|
+
for (const prop in instrumentedMachine) {
|
|
1559
|
+
if (!Object.prototype.hasOwnProperty.call(instrumentedMachine, prop)) continue;
|
|
1560
|
+
const value = instrumentedMachine[prop];
|
|
1561
|
+
if (typeof value === "function" && !prop.startsWith("_") && prop !== "context" && !["snapshots", "clearSnapshots", "restoreSnapshot", "history", "clearHistory"].includes(prop)) {
|
|
1562
|
+
const originalWrappedFn = value;
|
|
1563
|
+
instrumentedMachine[prop] = function(...args) {
|
|
1564
|
+
const result = originalWrappedFn.apply(this, args);
|
|
1565
|
+
if (result && typeof result === "object" && "context" in result && !("snapshots" in result)) {
|
|
1566
|
+
for (const transProp in result) {
|
|
1567
|
+
if (!Object.prototype.hasOwnProperty.call(result, transProp)) continue;
|
|
1568
|
+
const transValue = result[transProp];
|
|
1569
|
+
if (typeof transValue === "function" && !transProp.startsWith("_") && transProp !== "context" && !["snapshots", "clearSnapshots", "restoreSnapshot", "history", "clearHistory"].includes(transProp)) {
|
|
1570
|
+
const origTransFn = transValue;
|
|
1571
|
+
result[transProp] = function(...transArgs) {
|
|
1572
|
+
const prevCtx = result.context;
|
|
1573
|
+
const transResult = origTransFn.apply(this, transArgs);
|
|
1574
|
+
if (transResult && typeof transResult === "object" && "context" in transResult) {
|
|
1575
|
+
const nextCtx = transResult.context;
|
|
1576
|
+
if (!(filter && !filter(transProp))) {
|
|
1577
|
+
let shouldRecord = true;
|
|
1578
|
+
if (onlyIfChanged) {
|
|
1579
|
+
const changed = JSON.stringify(prevCtx) !== JSON.stringify(nextCtx);
|
|
1580
|
+
shouldRecord = changed;
|
|
1581
|
+
}
|
|
1582
|
+
if (shouldRecord) {
|
|
1583
|
+
const snapshot = {
|
|
1584
|
+
id: `snapshot-${snapshotId++}`,
|
|
1585
|
+
transitionName: transProp,
|
|
1586
|
+
before: { ...prevCtx },
|
|
1587
|
+
after: { ...nextCtx },
|
|
1588
|
+
timestamp: Date.now()
|
|
1589
|
+
};
|
|
1590
|
+
if (serializer) {
|
|
1591
|
+
try {
|
|
1592
|
+
snapshot.serializedBefore = serializer.serialize(prevCtx);
|
|
1593
|
+
snapshot.serializedAfter = serializer.serialize(nextCtx);
|
|
1594
|
+
} catch (err) {
|
|
1595
|
+
console.error("Failed to serialize snapshot:", err);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if (captureSnapshot) {
|
|
1599
|
+
try {
|
|
1600
|
+
snapshot.diff = captureSnapshot(prevCtx, nextCtx);
|
|
1601
|
+
} catch (err) {
|
|
1602
|
+
console.error("Failed to capture snapshot:", err);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
snapshots.push(snapshot);
|
|
1606
|
+
if (maxSize && snapshots.length > maxSize) {
|
|
1607
|
+
snapshots.shift();
|
|
1608
|
+
}
|
|
1609
|
+
onSnapshot == null ? void 0 : onSnapshot(snapshot);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return transResult;
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
const resultWithTracking = Object.assign(result, {
|
|
1618
|
+
snapshots,
|
|
1619
|
+
clearSnapshots: () => {
|
|
1620
|
+
snapshots.length = 0;
|
|
1621
|
+
snapshotId = 0;
|
|
1622
|
+
},
|
|
1623
|
+
restoreSnapshot
|
|
1624
|
+
});
|
|
1625
|
+
if (machine.history) {
|
|
1626
|
+
resultWithTracking.history = machine.history;
|
|
1627
|
+
resultWithTracking.clearHistory = machine.clearHistory;
|
|
1628
|
+
}
|
|
1629
|
+
return resultWithTracking;
|
|
1630
|
+
}
|
|
1631
|
+
return result;
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
return Object.assign(instrumentedMachine, {
|
|
1637
|
+
snapshots,
|
|
1638
|
+
clearSnapshots: () => {
|
|
1639
|
+
snapshots.length = 0;
|
|
1640
|
+
snapshotId = 0;
|
|
1641
|
+
},
|
|
1642
|
+
restoreSnapshot
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
function withTimeTravel(machine, options = {}) {
|
|
1646
|
+
const { maxSize, serializer, onRecord } = options;
|
|
1647
|
+
const history = [];
|
|
1648
|
+
const snapshots = [];
|
|
1649
|
+
let entryId = 0;
|
|
1650
|
+
let snapshotId = 0;
|
|
1651
|
+
const recordHistory = (transitionName, args) => {
|
|
1652
|
+
const entry = {
|
|
1653
|
+
id: `entry-${entryId++}`,
|
|
1654
|
+
transitionName,
|
|
1655
|
+
args: [...args],
|
|
1656
|
+
timestamp: Date.now()
|
|
1657
|
+
};
|
|
1658
|
+
if (serializer) {
|
|
1659
|
+
try {
|
|
1660
|
+
entry.serializedArgs = serializer.serialize(args);
|
|
1661
|
+
} catch (err) {
|
|
1662
|
+
console.error("Failed to serialize history args:", err);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
history.push(entry);
|
|
1666
|
+
if (maxSize && history.length > maxSize) {
|
|
1667
|
+
history.shift();
|
|
1668
|
+
}
|
|
1669
|
+
onRecord == null ? void 0 : onRecord("history", entry);
|
|
1670
|
+
};
|
|
1671
|
+
const recordSnapshot = (transitionName, prevContext, nextContext) => {
|
|
1672
|
+
const snapshot = {
|
|
1673
|
+
id: `snapshot-${snapshotId++}`,
|
|
1674
|
+
transitionName,
|
|
1675
|
+
before: { ...prevContext },
|
|
1676
|
+
after: { ...nextContext },
|
|
1677
|
+
timestamp: Date.now()
|
|
1678
|
+
};
|
|
1679
|
+
if (serializer) {
|
|
1680
|
+
try {
|
|
1681
|
+
snapshot.serializedBefore = serializer.serialize(prevContext);
|
|
1682
|
+
snapshot.serializedAfter = serializer.serialize(nextContext);
|
|
1683
|
+
} catch (err) {
|
|
1684
|
+
console.error("Failed to serialize snapshot:", err);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
snapshots.push(snapshot);
|
|
1688
|
+
if (maxSize && snapshots.length > maxSize) {
|
|
1689
|
+
snapshots.shift();
|
|
1690
|
+
}
|
|
1691
|
+
onRecord == null ? void 0 : onRecord("snapshot", snapshot);
|
|
1692
|
+
};
|
|
1693
|
+
const restoreSnapshot = (context) => {
|
|
1694
|
+
const { context: _, ...transitions } = machine;
|
|
1695
|
+
return Object.assign({ context }, context, transitions);
|
|
1696
|
+
};
|
|
1697
|
+
const replayFrom = (snapshotIndex = 0) => {
|
|
1698
|
+
if (snapshotIndex < 0 || snapshotIndex >= snapshots.length) {
|
|
1699
|
+
throw new Error(`Invalid snapshot index: ${snapshotIndex}`);
|
|
1700
|
+
}
|
|
1701
|
+
let current = restoreSnapshot(snapshots[snapshotIndex].before);
|
|
1702
|
+
const snapshot = snapshots[snapshotIndex];
|
|
1703
|
+
const historyStartIndex = history.findIndex(
|
|
1704
|
+
(entry) => entry.transitionName === snapshot.transitionName && entry.timestamp === snapshot.timestamp
|
|
1705
|
+
);
|
|
1706
|
+
if (historyStartIndex === -1) {
|
|
1707
|
+
throw new Error("Could not find matching history entry for snapshot");
|
|
1708
|
+
}
|
|
1709
|
+
for (let i = historyStartIndex; i < history.length; i++) {
|
|
1710
|
+
const entry = history[i];
|
|
1711
|
+
const transition = current[entry.transitionName];
|
|
1712
|
+
if (typeof transition === "function") {
|
|
1713
|
+
try {
|
|
1714
|
+
current = transition.apply(current.context, entry.args);
|
|
1715
|
+
} catch (err) {
|
|
1716
|
+
console.error(`Replay failed at step ${i}:`, err);
|
|
1717
|
+
throw err;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
return current;
|
|
1722
|
+
};
|
|
1723
|
+
const wrapMachine = (machine2) => {
|
|
1724
|
+
const wrapped = { ...machine2 };
|
|
1725
|
+
for (const prop in machine2) {
|
|
1726
|
+
if (!Object.prototype.hasOwnProperty.call(machine2, prop)) continue;
|
|
1727
|
+
const value = machine2[prop];
|
|
1728
|
+
if (typeof value === "function" && !prop.startsWith("_") && prop !== "context" && !["history", "snapshots", "clearHistory", "clearSnapshots", "clearTimeTravel", "restoreSnapshot", "replayFrom"].includes(prop)) {
|
|
1729
|
+
wrapped[prop] = function(...args) {
|
|
1730
|
+
recordHistory(prop, args);
|
|
1731
|
+
const prevContext = wrapped.context;
|
|
1732
|
+
const result = value.apply(this, args);
|
|
1733
|
+
if (result && typeof result === "object" && "context" in result) {
|
|
1734
|
+
recordSnapshot(prop, prevContext, result.context);
|
|
1735
|
+
}
|
|
1736
|
+
if (result && typeof result === "object" && "context" in result) {
|
|
1737
|
+
return wrapMachine(result);
|
|
1738
|
+
}
|
|
1739
|
+
return result;
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
return Object.assign(wrapped, {
|
|
1744
|
+
history,
|
|
1745
|
+
snapshots,
|
|
1746
|
+
clearHistory: () => {
|
|
1747
|
+
history.length = 0;
|
|
1748
|
+
entryId = 0;
|
|
1749
|
+
},
|
|
1750
|
+
clearSnapshots: () => {
|
|
1751
|
+
snapshots.length = 0;
|
|
1752
|
+
snapshotId = 0;
|
|
1753
|
+
},
|
|
1754
|
+
clearTimeTravel: () => {
|
|
1755
|
+
history.length = 0;
|
|
1756
|
+
snapshots.length = 0;
|
|
1757
|
+
entryId = 0;
|
|
1758
|
+
snapshotId = 0;
|
|
1759
|
+
},
|
|
1760
|
+
restoreSnapshot,
|
|
1761
|
+
replayFrom
|
|
1762
|
+
});
|
|
1763
|
+
};
|
|
1764
|
+
return wrapMachine(machine);
|
|
1765
|
+
}
|
|
1766
|
+
function compose(machine, ...middlewares) {
|
|
1767
|
+
return middlewares.reduce((acc, middleware) => middleware(acc), machine);
|
|
1768
|
+
}
|
|
1769
|
+
function createCustomMiddleware(hooks, options) {
|
|
1770
|
+
return (machine) => createMiddleware(machine, hooks, options);
|
|
1771
|
+
}
|
|
1772
|
+
function composeTyped(machine, ...middlewares) {
|
|
1773
|
+
return middlewares.reduce((acc, middleware) => middleware(acc), machine);
|
|
1774
|
+
}
|
|
1775
|
+
function chain(machine) {
|
|
1776
|
+
return new MiddlewareChainBuilder(machine);
|
|
1777
|
+
}
|
|
1778
|
+
var MiddlewareChainBuilder = class _MiddlewareChainBuilder {
|
|
1779
|
+
constructor(machine) {
|
|
1780
|
+
this.machine = machine;
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Add a middleware to the composition chain.
|
|
1784
|
+
* @param middleware - The middleware function to add
|
|
1785
|
+
* @returns A new composer with the middleware applied
|
|
1786
|
+
*/
|
|
1787
|
+
with(middleware) {
|
|
1788
|
+
const result = middleware(this.machine);
|
|
1789
|
+
return new _MiddlewareChainBuilder(result);
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Build the final machine with all middlewares applied.
|
|
1793
|
+
*/
|
|
1794
|
+
build() {
|
|
1795
|
+
return this.machine;
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
function withDebugging(machine) {
|
|
1799
|
+
return withTimeTravel(withSnapshot(withHistory(machine)));
|
|
1800
|
+
}
|
|
1801
|
+
function createPipeline(config = {}) {
|
|
1802
|
+
const {
|
|
1803
|
+
continueOnError = false,
|
|
1804
|
+
logErrors = true,
|
|
1805
|
+
onError
|
|
1806
|
+
} = config;
|
|
1807
|
+
return (machine, ...middlewares) => {
|
|
1808
|
+
let currentMachine = machine;
|
|
1809
|
+
const errors = [];
|
|
1810
|
+
for (let i = 0; i < middlewares.length; i++) {
|
|
1811
|
+
const middleware = middlewares[i];
|
|
1812
|
+
try {
|
|
1813
|
+
if ("middleware" in middleware && "when" in middleware) {
|
|
1814
|
+
if (!middleware.when(currentMachine)) {
|
|
1815
|
+
continue;
|
|
1816
|
+
}
|
|
1817
|
+
currentMachine = middleware.middleware(currentMachine);
|
|
1818
|
+
} else {
|
|
1819
|
+
currentMachine = middleware(currentMachine);
|
|
1820
|
+
}
|
|
1821
|
+
} catch (error) {
|
|
1822
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1823
|
+
errors.push({ error: err, middlewareIndex: i });
|
|
1824
|
+
if (logErrors) {
|
|
1825
|
+
console.error(`Middleware pipeline error at index ${i}:`, err);
|
|
1826
|
+
}
|
|
1827
|
+
onError == null ? void 0 : onError(err, `middleware-${i}`);
|
|
1828
|
+
if (!continueOnError) {
|
|
1829
|
+
break;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
return {
|
|
1834
|
+
machine: currentMachine,
|
|
1835
|
+
errors,
|
|
1836
|
+
success: errors.length === 0
|
|
1837
|
+
};
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
function createMiddlewareRegistry() {
|
|
1841
|
+
const registry = /* @__PURE__ */ new Map();
|
|
1842
|
+
return {
|
|
1843
|
+
/**
|
|
1844
|
+
* Register a middleware with a name and optional metadata.
|
|
1845
|
+
*/
|
|
1846
|
+
register(name, middleware, description, priority) {
|
|
1847
|
+
if (registry.has(name)) {
|
|
1848
|
+
throw new Error(`Middleware '${name}' is already registered`);
|
|
1849
|
+
}
|
|
1850
|
+
registry.set(name, { name, middleware, description, priority });
|
|
1851
|
+
return this;
|
|
1852
|
+
},
|
|
1853
|
+
/**
|
|
1854
|
+
* Unregister a middleware by name.
|
|
1855
|
+
*/
|
|
1856
|
+
unregister(name) {
|
|
1857
|
+
return registry.delete(name);
|
|
1858
|
+
},
|
|
1859
|
+
/**
|
|
1860
|
+
* Check if a middleware is registered.
|
|
1861
|
+
*/
|
|
1862
|
+
has(name) {
|
|
1863
|
+
return registry.has(name);
|
|
1864
|
+
},
|
|
1865
|
+
/**
|
|
1866
|
+
* Get a registered middleware by name.
|
|
1867
|
+
*/
|
|
1868
|
+
get(name) {
|
|
1869
|
+
return registry.get(name);
|
|
1870
|
+
},
|
|
1871
|
+
/**
|
|
1872
|
+
* List all registered middlewares.
|
|
1873
|
+
*/
|
|
1874
|
+
list() {
|
|
1875
|
+
return Array.from(registry.values()).sort((a, b) => {
|
|
1876
|
+
var _a, _b;
|
|
1877
|
+
return ((_a = a.priority) != null ? _a : 0) - ((_b = b.priority) != null ? _b : 0);
|
|
1878
|
+
});
|
|
1879
|
+
},
|
|
1880
|
+
/**
|
|
1881
|
+
* Apply a selection of registered middlewares to a machine.
|
|
1882
|
+
* Middlewares are applied in priority order (lowest to highest).
|
|
1883
|
+
*/
|
|
1884
|
+
apply(machine, middlewareNames) {
|
|
1885
|
+
const middlewares = middlewareNames.map((name) => {
|
|
1886
|
+
const entry = registry.get(name);
|
|
1887
|
+
if (!entry) {
|
|
1888
|
+
throw new Error(`Middleware '${name}' is not registered`);
|
|
1889
|
+
}
|
|
1890
|
+
return entry;
|
|
1891
|
+
}).sort((a, b) => {
|
|
1892
|
+
var _a, _b;
|
|
1893
|
+
return ((_a = a.priority) != null ? _a : 0) - ((_b = b.priority) != null ? _b : 0);
|
|
1894
|
+
});
|
|
1895
|
+
return composeTyped(machine, ...middlewares.map((m) => m.middleware));
|
|
1896
|
+
},
|
|
1897
|
+
/**
|
|
1898
|
+
* Apply all registered middlewares to a machine in priority order.
|
|
1899
|
+
*/
|
|
1900
|
+
applyAll(machine) {
|
|
1901
|
+
const middlewares = this.list();
|
|
1902
|
+
return composeTyped(machine, ...middlewares.map((m) => m.middleware));
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
function when(middleware, predicate) {
|
|
1907
|
+
const conditional = function(machine) {
|
|
1908
|
+
return predicate(machine) ? middleware(machine) : machine;
|
|
1909
|
+
};
|
|
1910
|
+
conditional.middleware = middleware;
|
|
1911
|
+
conditional.when = predicate;
|
|
1912
|
+
return conditional;
|
|
1913
|
+
}
|
|
1914
|
+
function inDevelopment(middleware) {
|
|
1915
|
+
return when(middleware, () => {
|
|
1916
|
+
return typeof process !== "undefined" ? true : typeof window !== "undefined" ? !window.location.hostname.includes("production") : false;
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
function whenContext(key, value, middleware) {
|
|
1920
|
+
return when(middleware, (machine) => machine.context[key] === value);
|
|
1921
|
+
}
|
|
1922
|
+
function combine(...middlewares) {
|
|
1923
|
+
return (machine) => composeTyped(machine, ...middlewares);
|
|
1924
|
+
}
|
|
1925
|
+
function branch(branches, fallback) {
|
|
1926
|
+
return (machine) => {
|
|
1927
|
+
for (const [predicate, middleware] of branches) {
|
|
1928
|
+
if (predicate(machine)) {
|
|
1929
|
+
return middleware(machine);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
return fallback ? fallback(machine) : machine;
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
function isMiddlewareFn(value) {
|
|
1936
|
+
return typeof value === "function" && value.length === 1;
|
|
1937
|
+
}
|
|
1938
|
+
function isConditionalMiddleware(value) {
|
|
1939
|
+
return value !== null && "middleware" in value && "when" in value && isMiddlewareFn(value.middleware) && typeof value.when === "function";
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/utils.ts
|
|
1943
|
+
function isState(machine, machineClass) {
|
|
1944
|
+
return machine instanceof machineClass;
|
|
1945
|
+
}
|
|
1946
|
+
function createEvent(type, ...args) {
|
|
1947
|
+
return { type, args };
|
|
1948
|
+
}
|
|
1949
|
+
function mergeContext(machine, partialContext) {
|
|
1950
|
+
return setContext(machine, (ctx) => ({ ...ctx, ...partialContext }));
|
|
1951
|
+
}
|
|
1952
|
+
async function pipeTransitions(initialMachine, ...transitions) {
|
|
1953
|
+
let current = initialMachine;
|
|
1954
|
+
for (const transitionFn of transitions) {
|
|
1955
|
+
current = await transitionFn(current);
|
|
1956
|
+
}
|
|
1957
|
+
return current;
|
|
1958
|
+
}
|
|
1959
|
+
function logState(machine, label) {
|
|
1960
|
+
if (label) {
|
|
1961
|
+
console.log(label, machine.context);
|
|
1962
|
+
} else {
|
|
1963
|
+
console.log(machine.context);
|
|
1964
|
+
}
|
|
1965
|
+
return machine;
|
|
1966
|
+
}
|
|
1967
|
+
function call(fn, context, ...args) {
|
|
1968
|
+
return fn.apply(context, args);
|
|
1969
|
+
}
|
|
1970
|
+
function bindTransitions(machine) {
|
|
1971
|
+
return new Proxy(machine, {
|
|
1972
|
+
get(target, prop) {
|
|
1973
|
+
const value = target[prop];
|
|
1974
|
+
if (typeof value === "function") {
|
|
1975
|
+
return function(...args) {
|
|
1976
|
+
const result = value.apply(target.context, args);
|
|
1977
|
+
if (result && typeof result === "object" && "context" in result) {
|
|
1978
|
+
return bindTransitions(result);
|
|
1979
|
+
}
|
|
1980
|
+
return result;
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
return value;
|
|
1984
|
+
}
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
var BoundMachine = class _BoundMachine {
|
|
1988
|
+
constructor(machine) {
|
|
1989
|
+
this.wrappedMachine = machine;
|
|
1990
|
+
return new Proxy(this, {
|
|
1991
|
+
get: (target, prop) => {
|
|
1992
|
+
if (prop === "wrappedMachine") {
|
|
1993
|
+
return Reflect.get(target, prop);
|
|
1994
|
+
}
|
|
1995
|
+
if (prop === "context") {
|
|
1996
|
+
return this.wrappedMachine.context;
|
|
1997
|
+
}
|
|
1998
|
+
const value = this.wrappedMachine[prop];
|
|
1999
|
+
if (typeof value === "function") {
|
|
2000
|
+
return (...args) => {
|
|
2001
|
+
const result = value.apply(this.wrappedMachine.context, args);
|
|
2002
|
+
if (result && typeof result === "object" && "context" in result) {
|
|
2003
|
+
return new _BoundMachine(result);
|
|
2004
|
+
}
|
|
2005
|
+
return result;
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
return value;
|
|
2009
|
+
}
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
};
|
|
2013
|
+
|
|
800
2014
|
// src/index.ts
|
|
801
2015
|
function createMachine(context, fns) {
|
|
802
|
-
|
|
2016
|
+
const transitions = "context" in fns ? Object.fromEntries(
|
|
2017
|
+
Object.entries(fns).filter(([key]) => key !== "context")
|
|
2018
|
+
) : fns;
|
|
2019
|
+
const machine = Object.assign({ context }, transitions);
|
|
2020
|
+
return machine;
|
|
803
2021
|
}
|
|
804
2022
|
function createAsyncMachine(context, fns) {
|
|
805
2023
|
return Object.assign({ context }, fns);
|
|
@@ -835,6 +2053,17 @@ function extendTransitions(machine, newTransitions) {
|
|
|
835
2053
|
const combinedTransitions = { ...originalTransitions, ...newTransitions };
|
|
836
2054
|
return createMachine(context, combinedTransitions);
|
|
837
2055
|
}
|
|
2056
|
+
function combineFactories(factory1, factory2) {
|
|
2057
|
+
return (...args) => {
|
|
2058
|
+
const machine1 = factory1(...args);
|
|
2059
|
+
const machine2 = factory2();
|
|
2060
|
+
const combinedContext = { ...machine1.context, ...machine2.context };
|
|
2061
|
+
const { context: _, ...transitions1 } = machine1;
|
|
2062
|
+
const { context: __, ...transitions2 } = machine2;
|
|
2063
|
+
const combinedTransitions = { ...transitions1, ...transitions2 };
|
|
2064
|
+
return createMachine(combinedContext, combinedTransitions);
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
838
2067
|
function createMachineBuilder(templateMachine) {
|
|
839
2068
|
const { context, ...transitions } = templateMachine;
|
|
840
2069
|
return (newContext) => {
|
|
@@ -854,15 +2083,32 @@ function hasState(machine, key, value) {
|
|
|
854
2083
|
}
|
|
855
2084
|
function runMachine(initial, onChange) {
|
|
856
2085
|
let current = initial;
|
|
2086
|
+
let activeController = null;
|
|
857
2087
|
async function dispatch(event) {
|
|
2088
|
+
if (activeController) {
|
|
2089
|
+
activeController.abort();
|
|
2090
|
+
activeController = null;
|
|
2091
|
+
}
|
|
858
2092
|
const fn = current[event.type];
|
|
859
2093
|
if (typeof fn !== "function") {
|
|
860
2094
|
throw new Error(`[Machine] Unknown event type '${String(event.type)}' on current state.`);
|
|
861
2095
|
}
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
2096
|
+
const controller = new AbortController();
|
|
2097
|
+
activeController = controller;
|
|
2098
|
+
try {
|
|
2099
|
+
const nextStatePromise = fn.apply(current.context, [...event.args, { signal: controller.signal }]);
|
|
2100
|
+
const nextState = await nextStatePromise;
|
|
2101
|
+
if (controller.signal.aborted) {
|
|
2102
|
+
return current;
|
|
2103
|
+
}
|
|
2104
|
+
current = nextState;
|
|
2105
|
+
onChange == null ? void 0 : onChange(current);
|
|
2106
|
+
return current;
|
|
2107
|
+
} finally {
|
|
2108
|
+
if (activeController === controller) {
|
|
2109
|
+
activeController = null;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
866
2112
|
}
|
|
867
2113
|
return {
|
|
868
2114
|
/** Gets the context of the current state of the machine. */
|
|
@@ -870,7 +2116,14 @@ function runMachine(initial, onChange) {
|
|
|
870
2116
|
return current.context;
|
|
871
2117
|
},
|
|
872
2118
|
/** Dispatches a type-safe event to the machine, triggering a transition. */
|
|
873
|
-
dispatch
|
|
2119
|
+
dispatch,
|
|
2120
|
+
/** Stops any pending async operation and cleans up resources. */
|
|
2121
|
+
stop: () => {
|
|
2122
|
+
if (activeController) {
|
|
2123
|
+
activeController.abort();
|
|
2124
|
+
activeController = null;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
874
2127
|
};
|
|
875
2128
|
}
|
|
876
2129
|
var MachineBase = class {
|