@doeixd/machine 0.0.7 → 0.0.9
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 +130 -272
- package/dist/cjs/development/index.js +1121 -18
- package/dist/cjs/development/index.js.map +4 -4
- package/dist/cjs/production/index.js +5 -5
- package/dist/esm/development/index.js +1121 -18
- package/dist/esm/development/index.js.map +4 -4
- package/dist/esm/production/index.js +5 -5
- package/dist/types/extract.d.ts.map +1 -1
- package/dist/types/index.d.ts +64 -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 +205 -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 +111 -6
- package/dist/types/utils.d.ts.map +1 -1
- package/package.json +10 -7
- package/scripts/extract-statechart.ts +351 -0
- package/src/adapters.ts +407 -0
- package/src/extract.ts +60 -20
- package/src/index.ts +201 -8
- package/src/middleware.ts +2325 -0
- package/src/primitives.ts +454 -3
- package/src/runtime-extract.ts +15 -0
- package/src/utils.ts +221 -6
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
ADVANCED_CONFIG_EXAMPLES: () => ADVANCED_CONFIG_EXAMPLES,
|
|
23
24
|
BoundMachine: () => BoundMachine,
|
|
24
25
|
META_KEY: () => META_KEY,
|
|
25
26
|
MachineBase: () => MachineBase,
|
|
@@ -27,8 +28,15 @@ __export(src_exports, {
|
|
|
27
28
|
RUNTIME_META: () => RUNTIME_META,
|
|
28
29
|
action: () => action,
|
|
29
30
|
bindTransitions: () => bindTransitions,
|
|
31
|
+
branch: () => branch,
|
|
30
32
|
call: () => call,
|
|
33
|
+
chain: () => chain,
|
|
34
|
+
combine: () => combine,
|
|
35
|
+
combineFactories: () => combineFactories,
|
|
36
|
+
compose: () => compose,
|
|
37
|
+
composeTyped: () => composeTyped,
|
|
31
38
|
createAsyncMachine: () => createAsyncMachine,
|
|
39
|
+
createCustomMiddleware: () => createCustomMiddleware,
|
|
32
40
|
createEnsemble: () => createEnsemble,
|
|
33
41
|
createEvent: () => createEvent,
|
|
34
42
|
createFetchMachine: () => createFetchMachine,
|
|
@@ -36,9 +44,12 @@ __export(src_exports, {
|
|
|
36
44
|
createMachine: () => createMachine,
|
|
37
45
|
createMachineBuilder: () => createMachineBuilder,
|
|
38
46
|
createMachineFactory: () => createMachineFactory,
|
|
47
|
+
createMiddleware: () => createMiddleware,
|
|
48
|
+
createMiddlewareRegistry: () => createMiddlewareRegistry,
|
|
39
49
|
createMultiMachine: () => createMultiMachine,
|
|
40
50
|
createMutableMachine: () => createMutableMachine,
|
|
41
51
|
createParallelMachine: () => createParallelMachine,
|
|
52
|
+
createPipeline: () => createPipeline,
|
|
42
53
|
createRunner: () => createRunner,
|
|
43
54
|
delegateToChild: () => delegateToChild,
|
|
44
55
|
describe: () => describe,
|
|
@@ -50,9 +61,14 @@ __export(src_exports, {
|
|
|
50
61
|
extractStateNode: () => extractStateNode,
|
|
51
62
|
generateChart: () => generateChart,
|
|
52
63
|
generateStatechart: () => generateStatechart,
|
|
64
|
+
guard: () => guard,
|
|
65
|
+
guardAsync: () => guardAsync,
|
|
53
66
|
guarded: () => guarded,
|
|
54
67
|
hasState: () => hasState,
|
|
68
|
+
inDevelopment: () => inDevelopment,
|
|
55
69
|
invoke: () => invoke,
|
|
70
|
+
isConditionalMiddleware: () => isConditionalMiddleware,
|
|
71
|
+
isMiddlewareFn: () => isMiddlewareFn,
|
|
56
72
|
isState: () => isState,
|
|
57
73
|
logState: () => logState,
|
|
58
74
|
matchMachine: () => matchMachine,
|
|
@@ -73,6 +89,21 @@ __export(src_exports, {
|
|
|
73
89
|
stepAsync: () => stepAsync,
|
|
74
90
|
toggle: () => toggle,
|
|
75
91
|
transitionTo: () => transitionTo,
|
|
92
|
+
when: () => when,
|
|
93
|
+
whenContext: () => whenContext,
|
|
94
|
+
whenGuard: () => whenGuard,
|
|
95
|
+
whenGuardAsync: () => whenGuardAsync,
|
|
96
|
+
withAnalytics: () => withAnalytics,
|
|
97
|
+
withDebugging: () => withDebugging,
|
|
98
|
+
withErrorReporting: () => withErrorReporting,
|
|
99
|
+
withHistory: () => withHistory,
|
|
100
|
+
withLogging: () => withLogging,
|
|
101
|
+
withPerformanceMonitoring: () => withPerformanceMonitoring,
|
|
102
|
+
withPermissions: () => withPermissions,
|
|
103
|
+
withRetry: () => withRetry,
|
|
104
|
+
withSnapshot: () => withSnapshot,
|
|
105
|
+
withTimeTravel: () => withTimeTravel,
|
|
106
|
+
withValidation: () => withValidation,
|
|
76
107
|
yieldMachine: () => yieldMachine
|
|
77
108
|
});
|
|
78
109
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -176,9 +207,9 @@ function describe(_text, transition) {
|
|
|
176
207
|
});
|
|
177
208
|
return transition;
|
|
178
209
|
}
|
|
179
|
-
function guarded(
|
|
210
|
+
function guarded(guard2, transition) {
|
|
180
211
|
attachRuntimeMeta(transition, {
|
|
181
|
-
guards: [
|
|
212
|
+
guards: [guard2]
|
|
182
213
|
});
|
|
183
214
|
return transition;
|
|
184
215
|
}
|
|
@@ -199,6 +230,118 @@ function action(action2, transition) {
|
|
|
199
230
|
});
|
|
200
231
|
return transition;
|
|
201
232
|
}
|
|
233
|
+
function guard(condition, transition, options = {}) {
|
|
234
|
+
const { onFail = "throw", errorMessage, description } = options;
|
|
235
|
+
const fullOptions = { ...options, onFail, errorMessage, description };
|
|
236
|
+
const guardedTransition = function(...args) {
|
|
237
|
+
const isMachine = typeof this === "object" && "context" in this;
|
|
238
|
+
const ctx = isMachine ? this.context : this;
|
|
239
|
+
const conditionResult = condition(ctx, ...args);
|
|
240
|
+
if (conditionResult) {
|
|
241
|
+
const contextForTransition = isMachine ? this.context : this;
|
|
242
|
+
return transition.apply(contextForTransition, args);
|
|
243
|
+
} else {
|
|
244
|
+
if (onFail === "throw") {
|
|
245
|
+
const message = errorMessage || "Guard condition failed";
|
|
246
|
+
throw new Error(message);
|
|
247
|
+
} else if (onFail === "ignore") {
|
|
248
|
+
if (isMachine) {
|
|
249
|
+
return this;
|
|
250
|
+
} else {
|
|
251
|
+
throw new Error('Cannot use "ignore" mode with context-only binding. Use full machine binding or provide fallback.');
|
|
252
|
+
}
|
|
253
|
+
} else if (typeof onFail === "function") {
|
|
254
|
+
if (isMachine) {
|
|
255
|
+
return onFail.apply(this, args);
|
|
256
|
+
} else {
|
|
257
|
+
throw new Error("Cannot use function fallback with context-only binding. Use full machine binding.");
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
return onFail;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
Object.defineProperty(guardedTransition, "__guard", { value: true, enumerable: false });
|
|
265
|
+
Object.defineProperty(guardedTransition, "condition", { value: condition, enumerable: false });
|
|
266
|
+
Object.defineProperty(guardedTransition, "transition", { value: transition, enumerable: false });
|
|
267
|
+
Object.defineProperty(guardedTransition, "options", { value: fullOptions, enumerable: false });
|
|
268
|
+
attachRuntimeMeta(guardedTransition, {
|
|
269
|
+
description: description || "Synchronous guarded transition",
|
|
270
|
+
guards: [{ name: "runtime_guard", description: description || "Synchronous condition check" }]
|
|
271
|
+
});
|
|
272
|
+
return guardedTransition;
|
|
273
|
+
}
|
|
274
|
+
function guardAsync(condition, transition, options = {}) {
|
|
275
|
+
const { onFail = "throw", errorMessage, description } = options;
|
|
276
|
+
const fullOptions = { ...options, onFail, errorMessage, description };
|
|
277
|
+
const guardedTransition = async function(...args) {
|
|
278
|
+
const isMachine = typeof this === "object" && "context" in this;
|
|
279
|
+
const ctx = isMachine ? this.context : this;
|
|
280
|
+
const conditionResult = await Promise.resolve(condition(ctx, ...args));
|
|
281
|
+
if (conditionResult) {
|
|
282
|
+
const contextForTransition = isMachine ? this.context : this;
|
|
283
|
+
return transition.apply(contextForTransition, args);
|
|
284
|
+
} else {
|
|
285
|
+
if (onFail === "throw") {
|
|
286
|
+
const message = errorMessage || "Guard condition failed";
|
|
287
|
+
throw new Error(message);
|
|
288
|
+
} else if (onFail === "ignore") {
|
|
289
|
+
if (isMachine) {
|
|
290
|
+
return this;
|
|
291
|
+
} else {
|
|
292
|
+
throw new Error('Cannot use "ignore" mode with context-only binding. Use full machine binding or provide fallback.');
|
|
293
|
+
}
|
|
294
|
+
} else if (typeof onFail === "function") {
|
|
295
|
+
if (isMachine) {
|
|
296
|
+
return onFail.apply(this, args);
|
|
297
|
+
} else {
|
|
298
|
+
throw new Error("Cannot use function fallback with context-only binding. Use full machine binding.");
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
return onFail;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
Object.defineProperty(guardedTransition, "__guard", { value: true, enumerable: false });
|
|
306
|
+
Object.defineProperty(guardedTransition, "condition", { value: condition, enumerable: false });
|
|
307
|
+
Object.defineProperty(guardedTransition, "transition", { value: transition, enumerable: false });
|
|
308
|
+
Object.defineProperty(guardedTransition, "options", { value: fullOptions, enumerable: false });
|
|
309
|
+
attachRuntimeMeta(guardedTransition, {
|
|
310
|
+
description: description || "Runtime guarded transition",
|
|
311
|
+
guards: [{ name: "runtime_guard", description: description || "Runtime condition check" }]
|
|
312
|
+
});
|
|
313
|
+
return guardedTransition;
|
|
314
|
+
}
|
|
315
|
+
function whenGuard(condition) {
|
|
316
|
+
return {
|
|
317
|
+
/**
|
|
318
|
+
* Define the transition to execute when the condition passes.
|
|
319
|
+
* Returns a guarded transition that can optionally have an else clause.
|
|
320
|
+
*/
|
|
321
|
+
do(transition) {
|
|
322
|
+
const guarded2 = guard(condition, transition);
|
|
323
|
+
guarded2.else = function(fallback) {
|
|
324
|
+
return guard(condition, transition, { onFail: fallback });
|
|
325
|
+
};
|
|
326
|
+
return guarded2;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function whenGuardAsync(condition) {
|
|
331
|
+
return {
|
|
332
|
+
/**
|
|
333
|
+
* Define the transition to execute when the condition passes.
|
|
334
|
+
* Returns a guarded transition that can optionally have an else clause.
|
|
335
|
+
*/
|
|
336
|
+
do(transition) {
|
|
337
|
+
const guarded2 = guardAsync(condition, transition);
|
|
338
|
+
guarded2.else = function(fallback) {
|
|
339
|
+
return guardAsync(condition, transition, { onFail: fallback });
|
|
340
|
+
};
|
|
341
|
+
return guarded2;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
202
345
|
function metadata(_meta, value) {
|
|
203
346
|
return value;
|
|
204
347
|
}
|
|
@@ -298,9 +441,9 @@ function extractFromCallExpression(call2, verbose = false) {
|
|
|
298
441
|
break;
|
|
299
442
|
case "guarded":
|
|
300
443
|
if (args[0]) {
|
|
301
|
-
const
|
|
302
|
-
if (Object.keys(
|
|
303
|
-
metadata2.guards = [
|
|
444
|
+
const guard2 = parseObjectLiteral(args[0]);
|
|
445
|
+
if (Object.keys(guard2).length > 0) {
|
|
446
|
+
metadata2.guards = [guard2];
|
|
304
447
|
}
|
|
305
448
|
}
|
|
306
449
|
if (args[1] && import_ts_morph.Node.isCallExpression(args[1])) {
|
|
@@ -332,6 +475,36 @@ function extractFromCallExpression(call2, verbose = false) {
|
|
|
332
475
|
}
|
|
333
476
|
}
|
|
334
477
|
break;
|
|
478
|
+
case "guard":
|
|
479
|
+
if (args[2]) {
|
|
480
|
+
const options = parseObjectLiteral(args[2]);
|
|
481
|
+
if (options.description) {
|
|
482
|
+
metadata2.description = options.description;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
metadata2.guards = [{ name: "runtime_guard", description: metadata2.description || "Synchronous condition check" }];
|
|
486
|
+
if (args[1] && import_ts_morph.Node.isCallExpression(args[1])) {
|
|
487
|
+
const nested = extractFromCallExpression(args[1], verbose);
|
|
488
|
+
if (nested) {
|
|
489
|
+
Object.assign(metadata2, nested);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
case "guardAsync":
|
|
494
|
+
if (args[2]) {
|
|
495
|
+
const options = parseObjectLiteral(args[2]);
|
|
496
|
+
if (options.description) {
|
|
497
|
+
metadata2.description = options.description;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
metadata2.guards = [{ name: "runtime_guard_async", description: metadata2.description || "Asynchronous condition check" }];
|
|
501
|
+
if (args[1] && import_ts_morph.Node.isCallExpression(args[1])) {
|
|
502
|
+
const nested = extractFromCallExpression(args[1], verbose);
|
|
503
|
+
if (nested) {
|
|
504
|
+
Object.assign(metadata2, nested);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
break;
|
|
335
508
|
default:
|
|
336
509
|
return null;
|
|
337
510
|
}
|
|
@@ -573,6 +746,37 @@ function generateChart() {
|
|
|
573
746
|
process.exit(1);
|
|
574
747
|
}
|
|
575
748
|
}
|
|
749
|
+
var ADVANCED_CONFIG_EXAMPLES = {
|
|
750
|
+
hierarchical: {
|
|
751
|
+
input: "examples/dashboardMachine.ts",
|
|
752
|
+
id: "dashboard",
|
|
753
|
+
classes: ["DashboardMachine", "LoggedOutMachine"],
|
|
754
|
+
initialState: "DashboardMachine",
|
|
755
|
+
children: {
|
|
756
|
+
contextProperty: "child",
|
|
757
|
+
initialState: "ViewingChildMachine",
|
|
758
|
+
classes: ["ViewingChildMachine", "EditingChildMachine"]
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
parallel: {
|
|
762
|
+
input: "examples/editorMachine.ts",
|
|
763
|
+
id: "editor",
|
|
764
|
+
parallel: {
|
|
765
|
+
regions: [
|
|
766
|
+
{
|
|
767
|
+
name: "fontWeight",
|
|
768
|
+
initialState: "NormalWeight",
|
|
769
|
+
classes: ["NormalWeight", "BoldWeight"]
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
name: "textDecoration",
|
|
773
|
+
initialState: "NoDecoration",
|
|
774
|
+
classes: ["NoDecoration", "UnderlineState"]
|
|
775
|
+
}
|
|
776
|
+
]
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
};
|
|
576
780
|
if (require.main === module) {
|
|
577
781
|
generateChart();
|
|
578
782
|
}
|
|
@@ -617,6 +821,16 @@ function extractStateNode(stateInstance) {
|
|
|
617
821
|
transition.actions = meta.actions.map((a) => a.name);
|
|
618
822
|
}
|
|
619
823
|
stateNode.on[key] = transition;
|
|
824
|
+
} else if (meta.guards && meta.guards.length > 0) {
|
|
825
|
+
const transition = {
|
|
826
|
+
target: "GuardedTransition",
|
|
827
|
+
// Placeholder - actual target determined at runtime
|
|
828
|
+
cond: meta.guards.map((g) => g.name).join(" && ")
|
|
829
|
+
};
|
|
830
|
+
if (meta.description) {
|
|
831
|
+
transition.description = meta.description;
|
|
832
|
+
}
|
|
833
|
+
stateNode.on[key] = transition;
|
|
620
834
|
}
|
|
621
835
|
}
|
|
622
836
|
if (invoke2.length > 0) {
|
|
@@ -992,6 +1206,859 @@ function createParallelMachine(m1, m2) {
|
|
|
992
1206
|
};
|
|
993
1207
|
}
|
|
994
1208
|
|
|
1209
|
+
// src/middleware.ts
|
|
1210
|
+
var CANCEL = Symbol("CANCEL");
|
|
1211
|
+
function createMiddleware(machine, hooks, options = {}) {
|
|
1212
|
+
const { mode = "auto", exclude = ["context"] } = options;
|
|
1213
|
+
const wrapped = {};
|
|
1214
|
+
for (const prop in machine) {
|
|
1215
|
+
if (!Object.prototype.hasOwnProperty.call(machine, prop)) continue;
|
|
1216
|
+
const value = machine[prop];
|
|
1217
|
+
if (prop === "context") {
|
|
1218
|
+
wrapped.context = value;
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1221
|
+
if (exclude.includes(prop)) {
|
|
1222
|
+
wrapped[prop] = value;
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
if (typeof value !== "function" || prop.startsWith("_")) {
|
|
1226
|
+
wrapped[prop] = value;
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
wrapped[prop] = createTransitionWrapper(
|
|
1230
|
+
prop,
|
|
1231
|
+
value,
|
|
1232
|
+
machine,
|
|
1233
|
+
hooks,
|
|
1234
|
+
mode
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
return wrapped;
|
|
1238
|
+
}
|
|
1239
|
+
function createTransitionWrapper(transitionName, originalFn, machine, hooks, mode) {
|
|
1240
|
+
return function wrappedTransition(...args) {
|
|
1241
|
+
const context = machine.context;
|
|
1242
|
+
const middlewareCtx = {
|
|
1243
|
+
transitionName,
|
|
1244
|
+
context,
|
|
1245
|
+
args
|
|
1246
|
+
};
|
|
1247
|
+
const executeSyncTransition = () => {
|
|
1248
|
+
try {
|
|
1249
|
+
if (hooks.before) {
|
|
1250
|
+
const beforeResult = hooks.before(middlewareCtx);
|
|
1251
|
+
if (beforeResult === CANCEL) {
|
|
1252
|
+
return machine;
|
|
1253
|
+
}
|
|
1254
|
+
if (beforeResult instanceof Promise) {
|
|
1255
|
+
throw new Error(
|
|
1256
|
+
`Middleware mode is 'sync' but before hook returned Promise for transition: ${transitionName}`
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
const result = originalFn.call(this, ...args);
|
|
1261
|
+
if (result instanceof Promise) {
|
|
1262
|
+
return handleAsyncResult(result, context);
|
|
1263
|
+
}
|
|
1264
|
+
if (hooks.after) {
|
|
1265
|
+
const middlewareResult = {
|
|
1266
|
+
transitionName,
|
|
1267
|
+
prevContext: context,
|
|
1268
|
+
nextContext: result.context,
|
|
1269
|
+
args
|
|
1270
|
+
};
|
|
1271
|
+
const afterResult = hooks.after(middlewareResult);
|
|
1272
|
+
if (afterResult instanceof Promise) {
|
|
1273
|
+
throw new Error(
|
|
1274
|
+
`Middleware mode is 'sync' but after hook returned Promise for transition: ${transitionName}`
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return result;
|
|
1279
|
+
} catch (err) {
|
|
1280
|
+
if (hooks.error) {
|
|
1281
|
+
const middlewareError = {
|
|
1282
|
+
transitionName,
|
|
1283
|
+
context,
|
|
1284
|
+
args,
|
|
1285
|
+
error: err
|
|
1286
|
+
};
|
|
1287
|
+
const errorResult = hooks.error(middlewareError);
|
|
1288
|
+
if (errorResult instanceof Promise) {
|
|
1289
|
+
errorResult.catch(() => {
|
|
1290
|
+
});
|
|
1291
|
+
throw err;
|
|
1292
|
+
}
|
|
1293
|
+
if (errorResult && typeof errorResult === "object" && "context" in errorResult) {
|
|
1294
|
+
return errorResult;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
throw err;
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
const handleAsyncResult = async (resultPromise, ctx) => {
|
|
1301
|
+
try {
|
|
1302
|
+
const result = await resultPromise;
|
|
1303
|
+
if (hooks.after) {
|
|
1304
|
+
const middlewareResult = {
|
|
1305
|
+
transitionName,
|
|
1306
|
+
prevContext: ctx,
|
|
1307
|
+
nextContext: result.context,
|
|
1308
|
+
args
|
|
1309
|
+
};
|
|
1310
|
+
await hooks.after(middlewareResult);
|
|
1311
|
+
}
|
|
1312
|
+
return result;
|
|
1313
|
+
} catch (err) {
|
|
1314
|
+
if (hooks.error) {
|
|
1315
|
+
const middlewareError = {
|
|
1316
|
+
transitionName,
|
|
1317
|
+
context: ctx,
|
|
1318
|
+
args,
|
|
1319
|
+
error: err
|
|
1320
|
+
};
|
|
1321
|
+
const errorResult = await hooks.error(middlewareError);
|
|
1322
|
+
if (errorResult && typeof errorResult === "object" && "context" in errorResult) {
|
|
1323
|
+
return errorResult;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
throw err;
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
const executeAsyncTransition = async () => {
|
|
1330
|
+
try {
|
|
1331
|
+
if (hooks.before) {
|
|
1332
|
+
const beforeResult = await hooks.before(middlewareCtx);
|
|
1333
|
+
if (beforeResult === CANCEL) {
|
|
1334
|
+
return machine;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
const result = await originalFn.call(this, ...args);
|
|
1338
|
+
if (hooks.after) {
|
|
1339
|
+
const middlewareResult = {
|
|
1340
|
+
transitionName,
|
|
1341
|
+
prevContext: context,
|
|
1342
|
+
nextContext: result.context,
|
|
1343
|
+
args
|
|
1344
|
+
};
|
|
1345
|
+
await hooks.after(middlewareResult);
|
|
1346
|
+
}
|
|
1347
|
+
return result;
|
|
1348
|
+
} catch (err) {
|
|
1349
|
+
if (hooks.error) {
|
|
1350
|
+
const middlewareError = {
|
|
1351
|
+
transitionName,
|
|
1352
|
+
context,
|
|
1353
|
+
args,
|
|
1354
|
+
error: err
|
|
1355
|
+
};
|
|
1356
|
+
const errorResult = await hooks.error(middlewareError);
|
|
1357
|
+
if (errorResult && typeof errorResult === "object" && "context" in errorResult) {
|
|
1358
|
+
return errorResult;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
throw err;
|
|
1362
|
+
}
|
|
1363
|
+
};
|
|
1364
|
+
if (mode === "async") {
|
|
1365
|
+
return executeAsyncTransition();
|
|
1366
|
+
} else if (mode === "sync") {
|
|
1367
|
+
return executeSyncTransition();
|
|
1368
|
+
} else {
|
|
1369
|
+
return executeSyncTransition();
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
function withLogging(machine, options = {}) {
|
|
1374
|
+
const {
|
|
1375
|
+
logger = console.log,
|
|
1376
|
+
includeContext = true,
|
|
1377
|
+
includeArgs = true
|
|
1378
|
+
} = options;
|
|
1379
|
+
return createMiddleware(machine, {
|
|
1380
|
+
before: ({ transitionName, args }) => {
|
|
1381
|
+
const argsStr = includeArgs && args.length > 0 ? ` ${JSON.stringify(args)}` : "";
|
|
1382
|
+
logger(`→ ${transitionName}${argsStr}`);
|
|
1383
|
+
},
|
|
1384
|
+
after: ({ transitionName, nextContext }) => {
|
|
1385
|
+
const contextStr = includeContext ? ` ${JSON.stringify(nextContext)}` : "";
|
|
1386
|
+
logger(`✓ ${transitionName}${contextStr}`);
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
function withAnalytics(machine, track, options = {}) {
|
|
1391
|
+
const {
|
|
1392
|
+
eventPrefix = "state_transition",
|
|
1393
|
+
includePrevContext = false,
|
|
1394
|
+
includeArgs = true
|
|
1395
|
+
} = options;
|
|
1396
|
+
return createMiddleware(machine, {
|
|
1397
|
+
after: async ({ transitionName, prevContext, nextContext, args }) => {
|
|
1398
|
+
const properties = {
|
|
1399
|
+
transition: transitionName,
|
|
1400
|
+
to: nextContext
|
|
1401
|
+
};
|
|
1402
|
+
if (includePrevContext) {
|
|
1403
|
+
properties.from = prevContext;
|
|
1404
|
+
}
|
|
1405
|
+
if (includeArgs && args.length > 0) {
|
|
1406
|
+
properties.args = args;
|
|
1407
|
+
}
|
|
1408
|
+
await track(`${eventPrefix}.${transitionName}`, properties);
|
|
1409
|
+
}
|
|
1410
|
+
}, { mode: "async" });
|
|
1411
|
+
}
|
|
1412
|
+
function withValidation(machine, validate, options) {
|
|
1413
|
+
return createMiddleware(machine, {
|
|
1414
|
+
before: (ctx) => {
|
|
1415
|
+
const result = validate(ctx);
|
|
1416
|
+
if (result instanceof Promise) {
|
|
1417
|
+
return result.then((r) => {
|
|
1418
|
+
if (r === false) {
|
|
1419
|
+
throw new Error(`Validation failed for transition: ${ctx.transitionName}`);
|
|
1420
|
+
}
|
|
1421
|
+
return void 0;
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
if (result === false) {
|
|
1425
|
+
throw new Error(`Validation failed for transition: ${ctx.transitionName}`);
|
|
1426
|
+
}
|
|
1427
|
+
return void 0;
|
|
1428
|
+
}
|
|
1429
|
+
}, { mode: "auto", ...options });
|
|
1430
|
+
}
|
|
1431
|
+
function withPermissions(machine, canPerform, options) {
|
|
1432
|
+
return createMiddleware(machine, {
|
|
1433
|
+
before: (ctx) => {
|
|
1434
|
+
const result = canPerform(ctx);
|
|
1435
|
+
if (result instanceof Promise) {
|
|
1436
|
+
return result.then((allowed) => {
|
|
1437
|
+
if (!allowed) {
|
|
1438
|
+
throw new Error(`Unauthorized transition: ${ctx.transitionName}`);
|
|
1439
|
+
}
|
|
1440
|
+
return void 0;
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
if (!result) {
|
|
1444
|
+
throw new Error(`Unauthorized transition: ${ctx.transitionName}`);
|
|
1445
|
+
}
|
|
1446
|
+
return void 0;
|
|
1447
|
+
}
|
|
1448
|
+
}, { mode: "auto", ...options });
|
|
1449
|
+
}
|
|
1450
|
+
function withErrorReporting(machine, captureError, options = {}) {
|
|
1451
|
+
const { includeContext = true, includeArgs = true, mode } = options;
|
|
1452
|
+
return createMiddleware(machine, {
|
|
1453
|
+
error: async ({ transitionName, context, args, error }) => {
|
|
1454
|
+
const errorContext = {
|
|
1455
|
+
transition: transitionName
|
|
1456
|
+
};
|
|
1457
|
+
if (includeContext) {
|
|
1458
|
+
errorContext.context = context;
|
|
1459
|
+
}
|
|
1460
|
+
if (includeArgs && args.length > 0) {
|
|
1461
|
+
errorContext.args = args;
|
|
1462
|
+
}
|
|
1463
|
+
await Promise.resolve(captureError(error, errorContext));
|
|
1464
|
+
}
|
|
1465
|
+
}, { mode });
|
|
1466
|
+
}
|
|
1467
|
+
function withPerformanceMonitoring(machine, onMetric) {
|
|
1468
|
+
const timings = /* @__PURE__ */ new Map();
|
|
1469
|
+
return createMiddleware(machine, {
|
|
1470
|
+
before: ({ transitionName }) => {
|
|
1471
|
+
timings.set(transitionName, performance.now());
|
|
1472
|
+
return void 0;
|
|
1473
|
+
},
|
|
1474
|
+
after: ({ transitionName, nextContext }) => {
|
|
1475
|
+
const startTime = timings.get(transitionName);
|
|
1476
|
+
if (startTime) {
|
|
1477
|
+
const duration = performance.now() - startTime;
|
|
1478
|
+
timings.delete(transitionName);
|
|
1479
|
+
const result = onMetric({ transitionName, duration, context: nextContext });
|
|
1480
|
+
if (result instanceof Promise) {
|
|
1481
|
+
return result;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
return void 0;
|
|
1485
|
+
}
|
|
1486
|
+
}, { mode: "auto" });
|
|
1487
|
+
}
|
|
1488
|
+
function withRetry(machine, options = {}) {
|
|
1489
|
+
const {
|
|
1490
|
+
maxRetries = 3,
|
|
1491
|
+
delay = 1e3,
|
|
1492
|
+
backoffMultiplier = 1,
|
|
1493
|
+
shouldRetry = () => true,
|
|
1494
|
+
onRetry
|
|
1495
|
+
} = options;
|
|
1496
|
+
const wrapped = {};
|
|
1497
|
+
for (const prop in machine) {
|
|
1498
|
+
if (!Object.prototype.hasOwnProperty.call(machine, prop)) continue;
|
|
1499
|
+
const value = machine[prop];
|
|
1500
|
+
if (prop === "context" || typeof value !== "function") {
|
|
1501
|
+
wrapped[prop] = value;
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
wrapped[prop] = async function retriableTransition(...args) {
|
|
1505
|
+
let lastError;
|
|
1506
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1507
|
+
try {
|
|
1508
|
+
return await value.call(this, ...args);
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
lastError = error;
|
|
1511
|
+
if (attempt === maxRetries) {
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
if (!shouldRetry(lastError)) {
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
onRetry == null ? void 0 : onRetry(attempt + 1, lastError);
|
|
1518
|
+
const currentDelay = delay * Math.pow(backoffMultiplier, attempt);
|
|
1519
|
+
await new Promise((resolve) => setTimeout(resolve, currentDelay));
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
throw lastError;
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
return wrapped;
|
|
1526
|
+
}
|
|
1527
|
+
function withHistory(machine, options = {}) {
|
|
1528
|
+
const {
|
|
1529
|
+
maxSize,
|
|
1530
|
+
serializer,
|
|
1531
|
+
filter,
|
|
1532
|
+
onEntry,
|
|
1533
|
+
_isRewrap = false
|
|
1534
|
+
} = options;
|
|
1535
|
+
const history = [];
|
|
1536
|
+
let entryId = 0;
|
|
1537
|
+
const instrumentedMachine = createMiddleware(machine, {
|
|
1538
|
+
before: ({ transitionName, args }) => {
|
|
1539
|
+
if (filter && !filter(transitionName, args)) {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
const entry = {
|
|
1543
|
+
id: `entry-${entryId++}`,
|
|
1544
|
+
transitionName,
|
|
1545
|
+
args: [...args],
|
|
1546
|
+
// Shallow clone args (fast, works with any type)
|
|
1547
|
+
timestamp: Date.now()
|
|
1548
|
+
};
|
|
1549
|
+
if (serializer) {
|
|
1550
|
+
try {
|
|
1551
|
+
entry.serializedArgs = serializer.serialize(args);
|
|
1552
|
+
} catch (err) {
|
|
1553
|
+
console.error("Failed to serialize history args:", err);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
history.push(entry);
|
|
1557
|
+
if (maxSize && history.length > maxSize) {
|
|
1558
|
+
history.shift();
|
|
1559
|
+
}
|
|
1560
|
+
onEntry == null ? void 0 : onEntry(entry);
|
|
1561
|
+
}
|
|
1562
|
+
}, { exclude: ["context", "history", "clearHistory"] });
|
|
1563
|
+
if (!_isRewrap) {
|
|
1564
|
+
for (const prop in instrumentedMachine) {
|
|
1565
|
+
if (!Object.prototype.hasOwnProperty.call(instrumentedMachine, prop)) continue;
|
|
1566
|
+
const value = instrumentedMachine[prop];
|
|
1567
|
+
if (typeof value === "function" && !prop.startsWith("_") && prop !== "context" && !["history", "clearHistory"].includes(prop)) {
|
|
1568
|
+
const originalFn = value;
|
|
1569
|
+
instrumentedMachine[prop] = function(...args) {
|
|
1570
|
+
const result = originalFn.apply(this, args);
|
|
1571
|
+
if (result && typeof result === "object" && "context" in result && !("history" in result)) {
|
|
1572
|
+
const rewrappedResult = createMiddleware(result, {
|
|
1573
|
+
before: ({ transitionName, args: transArgs }) => {
|
|
1574
|
+
if (filter && !filter(transitionName, transArgs)) {
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
const entry = {
|
|
1578
|
+
id: `entry-${entryId++}`,
|
|
1579
|
+
transitionName,
|
|
1580
|
+
args: [...transArgs],
|
|
1581
|
+
timestamp: Date.now()
|
|
1582
|
+
};
|
|
1583
|
+
if (serializer) {
|
|
1584
|
+
try {
|
|
1585
|
+
entry.serializedArgs = serializer.serialize(transArgs);
|
|
1586
|
+
} catch (err) {
|
|
1587
|
+
console.error("Failed to serialize history args:", err);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
history.push(entry);
|
|
1591
|
+
if (maxSize && history.length > maxSize) {
|
|
1592
|
+
history.shift();
|
|
1593
|
+
}
|
|
1594
|
+
onEntry == null ? void 0 : onEntry(entry);
|
|
1595
|
+
}
|
|
1596
|
+
}, { exclude: ["context", "history", "clearHistory"] });
|
|
1597
|
+
return Object.assign(rewrappedResult, {
|
|
1598
|
+
history,
|
|
1599
|
+
clearHistory: () => {
|
|
1600
|
+
history.length = 0;
|
|
1601
|
+
entryId = 0;
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
return result;
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return Object.assign(instrumentedMachine, {
|
|
1611
|
+
history,
|
|
1612
|
+
clearHistory: () => {
|
|
1613
|
+
history.length = 0;
|
|
1614
|
+
entryId = 0;
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
function withSnapshot(machine, options = {}) {
|
|
1619
|
+
const {
|
|
1620
|
+
maxSize,
|
|
1621
|
+
serializer,
|
|
1622
|
+
captureSnapshot,
|
|
1623
|
+
onlyIfChanged = false,
|
|
1624
|
+
filter,
|
|
1625
|
+
onSnapshot,
|
|
1626
|
+
_extraExclusions = [],
|
|
1627
|
+
_isRewrap = false
|
|
1628
|
+
} = options;
|
|
1629
|
+
const snapshots = [];
|
|
1630
|
+
let snapshotId = 0;
|
|
1631
|
+
const instrumentedMachine = createMiddleware(machine, {
|
|
1632
|
+
after: ({ transitionName, prevContext, nextContext }) => {
|
|
1633
|
+
if (filter && !filter(transitionName)) {
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (onlyIfChanged) {
|
|
1637
|
+
const changed = JSON.stringify(prevContext) !== JSON.stringify(nextContext);
|
|
1638
|
+
if (!changed) {
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
const snapshot = {
|
|
1643
|
+
id: `snapshot-${snapshotId++}`,
|
|
1644
|
+
transitionName,
|
|
1645
|
+
before: { ...prevContext },
|
|
1646
|
+
// Clone
|
|
1647
|
+
after: { ...nextContext },
|
|
1648
|
+
// Clone
|
|
1649
|
+
timestamp: Date.now()
|
|
1650
|
+
};
|
|
1651
|
+
if (serializer) {
|
|
1652
|
+
try {
|
|
1653
|
+
snapshot.serializedBefore = serializer.serialize(prevContext);
|
|
1654
|
+
snapshot.serializedAfter = serializer.serialize(nextContext);
|
|
1655
|
+
} catch (err) {
|
|
1656
|
+
console.error("Failed to serialize snapshot:", err);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
if (captureSnapshot) {
|
|
1660
|
+
try {
|
|
1661
|
+
snapshot.diff = captureSnapshot(prevContext, nextContext);
|
|
1662
|
+
} catch (err) {
|
|
1663
|
+
console.error("Failed to capture snapshot:", err);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
snapshots.push(snapshot);
|
|
1667
|
+
if (maxSize && snapshots.length > maxSize) {
|
|
1668
|
+
snapshots.shift();
|
|
1669
|
+
}
|
|
1670
|
+
onSnapshot == null ? void 0 : onSnapshot(snapshot);
|
|
1671
|
+
}
|
|
1672
|
+
}, { exclude: ["context", "snapshots", "clearSnapshots", "restoreSnapshot", ..._extraExclusions] });
|
|
1673
|
+
const restoreSnapshot = (context) => {
|
|
1674
|
+
const { context: _, ...transitions } = machine;
|
|
1675
|
+
return { context, ...transitions };
|
|
1676
|
+
};
|
|
1677
|
+
if (!_isRewrap) {
|
|
1678
|
+
for (const prop in instrumentedMachine) {
|
|
1679
|
+
if (!Object.prototype.hasOwnProperty.call(instrumentedMachine, prop)) continue;
|
|
1680
|
+
const value = instrumentedMachine[prop];
|
|
1681
|
+
if (typeof value === "function" && !prop.startsWith("_") && prop !== "context" && !["snapshots", "clearSnapshots", "restoreSnapshot", "history", "clearHistory"].includes(prop)) {
|
|
1682
|
+
const originalWrappedFn = value;
|
|
1683
|
+
instrumentedMachine[prop] = function(...args) {
|
|
1684
|
+
const result = originalWrappedFn.apply(this, args);
|
|
1685
|
+
if (result && typeof result === "object" && "context" in result && !("snapshots" in result)) {
|
|
1686
|
+
for (const transProp in result) {
|
|
1687
|
+
if (!Object.prototype.hasOwnProperty.call(result, transProp)) continue;
|
|
1688
|
+
const transValue = result[transProp];
|
|
1689
|
+
if (typeof transValue === "function" && !transProp.startsWith("_") && transProp !== "context" && !["snapshots", "clearSnapshots", "restoreSnapshot", "history", "clearHistory"].includes(transProp)) {
|
|
1690
|
+
const origTransFn = transValue;
|
|
1691
|
+
result[transProp] = function(...transArgs) {
|
|
1692
|
+
const prevCtx = result.context;
|
|
1693
|
+
const transResult = origTransFn.apply(this, transArgs);
|
|
1694
|
+
if (transResult && typeof transResult === "object" && "context" in transResult) {
|
|
1695
|
+
const nextCtx = transResult.context;
|
|
1696
|
+
if (!(filter && !filter(transProp))) {
|
|
1697
|
+
let shouldRecord = true;
|
|
1698
|
+
if (onlyIfChanged) {
|
|
1699
|
+
const changed = JSON.stringify(prevCtx) !== JSON.stringify(nextCtx);
|
|
1700
|
+
shouldRecord = changed;
|
|
1701
|
+
}
|
|
1702
|
+
if (shouldRecord) {
|
|
1703
|
+
const snapshot = {
|
|
1704
|
+
id: `snapshot-${snapshotId++}`,
|
|
1705
|
+
transitionName: transProp,
|
|
1706
|
+
before: { ...prevCtx },
|
|
1707
|
+
after: { ...nextCtx },
|
|
1708
|
+
timestamp: Date.now()
|
|
1709
|
+
};
|
|
1710
|
+
if (serializer) {
|
|
1711
|
+
try {
|
|
1712
|
+
snapshot.serializedBefore = serializer.serialize(prevCtx);
|
|
1713
|
+
snapshot.serializedAfter = serializer.serialize(nextCtx);
|
|
1714
|
+
} catch (err) {
|
|
1715
|
+
console.error("Failed to serialize snapshot:", err);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
if (captureSnapshot) {
|
|
1719
|
+
try {
|
|
1720
|
+
snapshot.diff = captureSnapshot(prevCtx, nextCtx);
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
console.error("Failed to capture snapshot:", err);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
snapshots.push(snapshot);
|
|
1726
|
+
if (maxSize && snapshots.length > maxSize) {
|
|
1727
|
+
snapshots.shift();
|
|
1728
|
+
}
|
|
1729
|
+
onSnapshot == null ? void 0 : onSnapshot(snapshot);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
return transResult;
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
const resultWithTracking = Object.assign(result, {
|
|
1738
|
+
snapshots,
|
|
1739
|
+
clearSnapshots: () => {
|
|
1740
|
+
snapshots.length = 0;
|
|
1741
|
+
snapshotId = 0;
|
|
1742
|
+
},
|
|
1743
|
+
restoreSnapshot
|
|
1744
|
+
});
|
|
1745
|
+
if (machine.history) {
|
|
1746
|
+
resultWithTracking.history = machine.history;
|
|
1747
|
+
resultWithTracking.clearHistory = machine.clearHistory;
|
|
1748
|
+
}
|
|
1749
|
+
return resultWithTracking;
|
|
1750
|
+
}
|
|
1751
|
+
return result;
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return Object.assign(instrumentedMachine, {
|
|
1757
|
+
snapshots,
|
|
1758
|
+
clearSnapshots: () => {
|
|
1759
|
+
snapshots.length = 0;
|
|
1760
|
+
snapshotId = 0;
|
|
1761
|
+
},
|
|
1762
|
+
restoreSnapshot
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
function withTimeTravel(machine, options = {}) {
|
|
1766
|
+
const { maxSize, serializer, onRecord } = options;
|
|
1767
|
+
const history = [];
|
|
1768
|
+
const snapshots = [];
|
|
1769
|
+
let entryId = 0;
|
|
1770
|
+
let snapshotId = 0;
|
|
1771
|
+
const recordHistory = (transitionName, args) => {
|
|
1772
|
+
const entry = {
|
|
1773
|
+
id: `entry-${entryId++}`,
|
|
1774
|
+
transitionName,
|
|
1775
|
+
args: [...args],
|
|
1776
|
+
timestamp: Date.now()
|
|
1777
|
+
};
|
|
1778
|
+
if (serializer) {
|
|
1779
|
+
try {
|
|
1780
|
+
entry.serializedArgs = serializer.serialize(args);
|
|
1781
|
+
} catch (err) {
|
|
1782
|
+
console.error("Failed to serialize history args:", err);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
history.push(entry);
|
|
1786
|
+
if (maxSize && history.length > maxSize) {
|
|
1787
|
+
history.shift();
|
|
1788
|
+
}
|
|
1789
|
+
onRecord == null ? void 0 : onRecord("history", entry);
|
|
1790
|
+
};
|
|
1791
|
+
const recordSnapshot = (transitionName, prevContext, nextContext) => {
|
|
1792
|
+
const snapshot = {
|
|
1793
|
+
id: `snapshot-${snapshotId++}`,
|
|
1794
|
+
transitionName,
|
|
1795
|
+
before: { ...prevContext },
|
|
1796
|
+
after: { ...nextContext },
|
|
1797
|
+
timestamp: Date.now()
|
|
1798
|
+
};
|
|
1799
|
+
if (serializer) {
|
|
1800
|
+
try {
|
|
1801
|
+
snapshot.serializedBefore = serializer.serialize(prevContext);
|
|
1802
|
+
snapshot.serializedAfter = serializer.serialize(nextContext);
|
|
1803
|
+
} catch (err) {
|
|
1804
|
+
console.error("Failed to serialize snapshot:", err);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
snapshots.push(snapshot);
|
|
1808
|
+
if (maxSize && snapshots.length > maxSize) {
|
|
1809
|
+
snapshots.shift();
|
|
1810
|
+
}
|
|
1811
|
+
onRecord == null ? void 0 : onRecord("snapshot", snapshot);
|
|
1812
|
+
};
|
|
1813
|
+
const restoreSnapshot = (context) => {
|
|
1814
|
+
const { context: _, ...transitions } = machine;
|
|
1815
|
+
return Object.assign({ context }, context, transitions);
|
|
1816
|
+
};
|
|
1817
|
+
const replayFrom = (snapshotIndex = 0) => {
|
|
1818
|
+
if (snapshotIndex < 0 || snapshotIndex >= snapshots.length) {
|
|
1819
|
+
throw new Error(`Invalid snapshot index: ${snapshotIndex}`);
|
|
1820
|
+
}
|
|
1821
|
+
let current = restoreSnapshot(snapshots[snapshotIndex].before);
|
|
1822
|
+
const snapshot = snapshots[snapshotIndex];
|
|
1823
|
+
const historyStartIndex = history.findIndex(
|
|
1824
|
+
(entry) => entry.transitionName === snapshot.transitionName && entry.timestamp === snapshot.timestamp
|
|
1825
|
+
);
|
|
1826
|
+
if (historyStartIndex === -1) {
|
|
1827
|
+
throw new Error("Could not find matching history entry for snapshot");
|
|
1828
|
+
}
|
|
1829
|
+
for (let i = historyStartIndex; i < history.length; i++) {
|
|
1830
|
+
const entry = history[i];
|
|
1831
|
+
const transition = current[entry.transitionName];
|
|
1832
|
+
if (typeof transition === "function") {
|
|
1833
|
+
try {
|
|
1834
|
+
current = transition.apply(current.context, entry.args);
|
|
1835
|
+
} catch (err) {
|
|
1836
|
+
console.error(`Replay failed at step ${i}:`, err);
|
|
1837
|
+
throw err;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return current;
|
|
1842
|
+
};
|
|
1843
|
+
const wrapMachine = (machine2) => {
|
|
1844
|
+
const wrapped = { ...machine2 };
|
|
1845
|
+
for (const prop in machine2) {
|
|
1846
|
+
if (!Object.prototype.hasOwnProperty.call(machine2, prop)) continue;
|
|
1847
|
+
const value = machine2[prop];
|
|
1848
|
+
if (typeof value === "function" && !prop.startsWith("_") && prop !== "context" && !["history", "snapshots", "clearHistory", "clearSnapshots", "clearTimeTravel", "restoreSnapshot", "replayFrom"].includes(prop)) {
|
|
1849
|
+
wrapped[prop] = function(...args) {
|
|
1850
|
+
recordHistory(prop, args);
|
|
1851
|
+
const prevContext = wrapped.context;
|
|
1852
|
+
const result = value.apply(this, args);
|
|
1853
|
+
if (result && typeof result === "object" && "context" in result) {
|
|
1854
|
+
recordSnapshot(prop, prevContext, result.context);
|
|
1855
|
+
}
|
|
1856
|
+
if (result && typeof result === "object" && "context" in result) {
|
|
1857
|
+
return wrapMachine(result);
|
|
1858
|
+
}
|
|
1859
|
+
return result;
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
return Object.assign(wrapped, {
|
|
1864
|
+
history,
|
|
1865
|
+
snapshots,
|
|
1866
|
+
clearHistory: () => {
|
|
1867
|
+
history.length = 0;
|
|
1868
|
+
entryId = 0;
|
|
1869
|
+
},
|
|
1870
|
+
clearSnapshots: () => {
|
|
1871
|
+
snapshots.length = 0;
|
|
1872
|
+
snapshotId = 0;
|
|
1873
|
+
},
|
|
1874
|
+
clearTimeTravel: () => {
|
|
1875
|
+
history.length = 0;
|
|
1876
|
+
snapshots.length = 0;
|
|
1877
|
+
entryId = 0;
|
|
1878
|
+
snapshotId = 0;
|
|
1879
|
+
},
|
|
1880
|
+
restoreSnapshot,
|
|
1881
|
+
replayFrom
|
|
1882
|
+
});
|
|
1883
|
+
};
|
|
1884
|
+
return wrapMachine(machine);
|
|
1885
|
+
}
|
|
1886
|
+
function compose(machine, ...middlewares) {
|
|
1887
|
+
return middlewares.reduce((acc, middleware) => middleware(acc), machine);
|
|
1888
|
+
}
|
|
1889
|
+
function createCustomMiddleware(hooks, options) {
|
|
1890
|
+
return (machine) => createMiddleware(machine, hooks, options);
|
|
1891
|
+
}
|
|
1892
|
+
function composeTyped(machine, ...middlewares) {
|
|
1893
|
+
return middlewares.reduce((acc, middleware) => middleware(acc), machine);
|
|
1894
|
+
}
|
|
1895
|
+
function chain(machine) {
|
|
1896
|
+
return new MiddlewareChainBuilder(machine);
|
|
1897
|
+
}
|
|
1898
|
+
var MiddlewareChainBuilder = class _MiddlewareChainBuilder {
|
|
1899
|
+
constructor(machine) {
|
|
1900
|
+
this.machine = machine;
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Add a middleware to the composition chain.
|
|
1904
|
+
* @param middleware - The middleware function to add
|
|
1905
|
+
* @returns A new composer with the middleware applied
|
|
1906
|
+
*/
|
|
1907
|
+
with(middleware) {
|
|
1908
|
+
const result = middleware(this.machine);
|
|
1909
|
+
return new _MiddlewareChainBuilder(result);
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Build the final machine with all middlewares applied.
|
|
1913
|
+
*/
|
|
1914
|
+
build() {
|
|
1915
|
+
return this.machine;
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
function withDebugging(machine) {
|
|
1919
|
+
return withTimeTravel(withSnapshot(withHistory(machine)));
|
|
1920
|
+
}
|
|
1921
|
+
function createPipeline(config = {}) {
|
|
1922
|
+
const {
|
|
1923
|
+
continueOnError = false,
|
|
1924
|
+
logErrors = true,
|
|
1925
|
+
onError
|
|
1926
|
+
} = config;
|
|
1927
|
+
return (machine, ...middlewares) => {
|
|
1928
|
+
let currentMachine = machine;
|
|
1929
|
+
const errors = [];
|
|
1930
|
+
for (let i = 0; i < middlewares.length; i++) {
|
|
1931
|
+
const middleware = middlewares[i];
|
|
1932
|
+
try {
|
|
1933
|
+
if ("middleware" in middleware && "when" in middleware) {
|
|
1934
|
+
if (!middleware.when(currentMachine)) {
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
currentMachine = middleware.middleware(currentMachine);
|
|
1938
|
+
} else {
|
|
1939
|
+
currentMachine = middleware(currentMachine);
|
|
1940
|
+
}
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1943
|
+
errors.push({ error: err, middlewareIndex: i });
|
|
1944
|
+
if (logErrors) {
|
|
1945
|
+
console.error(`Middleware pipeline error at index ${i}:`, err);
|
|
1946
|
+
}
|
|
1947
|
+
onError == null ? void 0 : onError(err, `middleware-${i}`);
|
|
1948
|
+
if (!continueOnError) {
|
|
1949
|
+
break;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
return {
|
|
1954
|
+
machine: currentMachine,
|
|
1955
|
+
errors,
|
|
1956
|
+
success: errors.length === 0
|
|
1957
|
+
};
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
function createMiddlewareRegistry() {
|
|
1961
|
+
const registry = /* @__PURE__ */ new Map();
|
|
1962
|
+
return {
|
|
1963
|
+
/**
|
|
1964
|
+
* Register a middleware with a name and optional metadata.
|
|
1965
|
+
*/
|
|
1966
|
+
register(name, middleware, description, priority) {
|
|
1967
|
+
if (registry.has(name)) {
|
|
1968
|
+
throw new Error(`Middleware '${name}' is already registered`);
|
|
1969
|
+
}
|
|
1970
|
+
registry.set(name, { name, middleware, description, priority });
|
|
1971
|
+
return this;
|
|
1972
|
+
},
|
|
1973
|
+
/**
|
|
1974
|
+
* Unregister a middleware by name.
|
|
1975
|
+
*/
|
|
1976
|
+
unregister(name) {
|
|
1977
|
+
return registry.delete(name);
|
|
1978
|
+
},
|
|
1979
|
+
/**
|
|
1980
|
+
* Check if a middleware is registered.
|
|
1981
|
+
*/
|
|
1982
|
+
has(name) {
|
|
1983
|
+
return registry.has(name);
|
|
1984
|
+
},
|
|
1985
|
+
/**
|
|
1986
|
+
* Get a registered middleware by name.
|
|
1987
|
+
*/
|
|
1988
|
+
get(name) {
|
|
1989
|
+
return registry.get(name);
|
|
1990
|
+
},
|
|
1991
|
+
/**
|
|
1992
|
+
* List all registered middlewares.
|
|
1993
|
+
*/
|
|
1994
|
+
list() {
|
|
1995
|
+
return Array.from(registry.values()).sort((a, b) => {
|
|
1996
|
+
var _a, _b;
|
|
1997
|
+
return ((_a = a.priority) != null ? _a : 0) - ((_b = b.priority) != null ? _b : 0);
|
|
1998
|
+
});
|
|
1999
|
+
},
|
|
2000
|
+
/**
|
|
2001
|
+
* Apply a selection of registered middlewares to a machine.
|
|
2002
|
+
* Middlewares are applied in priority order (lowest to highest).
|
|
2003
|
+
*/
|
|
2004
|
+
apply(machine, middlewareNames) {
|
|
2005
|
+
const middlewares = middlewareNames.map((name) => {
|
|
2006
|
+
const entry = registry.get(name);
|
|
2007
|
+
if (!entry) {
|
|
2008
|
+
throw new Error(`Middleware '${name}' is not registered`);
|
|
2009
|
+
}
|
|
2010
|
+
return entry;
|
|
2011
|
+
}).sort((a, b) => {
|
|
2012
|
+
var _a, _b;
|
|
2013
|
+
return ((_a = a.priority) != null ? _a : 0) - ((_b = b.priority) != null ? _b : 0);
|
|
2014
|
+
});
|
|
2015
|
+
return composeTyped(machine, ...middlewares.map((m) => m.middleware));
|
|
2016
|
+
},
|
|
2017
|
+
/**
|
|
2018
|
+
* Apply all registered middlewares to a machine in priority order.
|
|
2019
|
+
*/
|
|
2020
|
+
applyAll(machine) {
|
|
2021
|
+
const middlewares = this.list();
|
|
2022
|
+
return composeTyped(machine, ...middlewares.map((m) => m.middleware));
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
function when(middleware, predicate) {
|
|
2027
|
+
const conditional = function(machine) {
|
|
2028
|
+
return predicate(machine) ? middleware(machine) : machine;
|
|
2029
|
+
};
|
|
2030
|
+
conditional.middleware = middleware;
|
|
2031
|
+
conditional.when = predicate;
|
|
2032
|
+
return conditional;
|
|
2033
|
+
}
|
|
2034
|
+
function inDevelopment(middleware) {
|
|
2035
|
+
return when(middleware, () => {
|
|
2036
|
+
return typeof process !== "undefined" ? true : typeof window !== "undefined" ? !window.location.hostname.includes("production") : false;
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
function whenContext(key, value, middleware) {
|
|
2040
|
+
return when(middleware, (machine) => machine.context[key] === value);
|
|
2041
|
+
}
|
|
2042
|
+
function combine(...middlewares) {
|
|
2043
|
+
return (machine) => composeTyped(machine, ...middlewares);
|
|
2044
|
+
}
|
|
2045
|
+
function branch(branches, fallback) {
|
|
2046
|
+
return (machine) => {
|
|
2047
|
+
for (const [predicate, middleware] of branches) {
|
|
2048
|
+
if (predicate(machine)) {
|
|
2049
|
+
return middleware(machine);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
return fallback ? fallback(machine) : machine;
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
function isMiddlewareFn(value) {
|
|
2056
|
+
return typeof value === "function" && value.length === 1;
|
|
2057
|
+
}
|
|
2058
|
+
function isConditionalMiddleware(value) {
|
|
2059
|
+
return value !== null && "middleware" in value && "when" in value && isMiddlewareFn(value.middleware) && typeof value.when === "function";
|
|
2060
|
+
}
|
|
2061
|
+
|
|
995
2062
|
// src/utils.ts
|
|
996
2063
|
function isState(machine, machineClass) {
|
|
997
2064
|
return machine instanceof machineClass;
|
|
@@ -1042,9 +2109,12 @@ var BoundMachine = class _BoundMachine {
|
|
|
1042
2109
|
this.wrappedMachine = machine;
|
|
1043
2110
|
return new Proxy(this, {
|
|
1044
2111
|
get: (target, prop) => {
|
|
1045
|
-
if (prop === "wrappedMachine"
|
|
2112
|
+
if (prop === "wrappedMachine") {
|
|
1046
2113
|
return Reflect.get(target, prop);
|
|
1047
2114
|
}
|
|
2115
|
+
if (prop === "context") {
|
|
2116
|
+
return this.wrappedMachine.context;
|
|
2117
|
+
}
|
|
1048
2118
|
const value = this.wrappedMachine[prop];
|
|
1049
2119
|
if (typeof value === "function") {
|
|
1050
2120
|
return (...args) => {
|
|
@@ -1059,17 +2129,15 @@ var BoundMachine = class _BoundMachine {
|
|
|
1059
2129
|
}
|
|
1060
2130
|
});
|
|
1061
2131
|
}
|
|
1062
|
-
/**
|
|
1063
|
-
* Access the underlying machine's context directly.
|
|
1064
|
-
*/
|
|
1065
|
-
get context() {
|
|
1066
|
-
return this.wrappedMachine.context;
|
|
1067
|
-
}
|
|
1068
2132
|
};
|
|
1069
2133
|
|
|
1070
2134
|
// src/index.ts
|
|
1071
2135
|
function createMachine(context, fns) {
|
|
1072
|
-
|
|
2136
|
+
const transitions = "context" in fns ? Object.fromEntries(
|
|
2137
|
+
Object.entries(fns).filter(([key]) => key !== "context")
|
|
2138
|
+
) : fns;
|
|
2139
|
+
const machine = Object.assign({ context }, transitions);
|
|
2140
|
+
return machine;
|
|
1073
2141
|
}
|
|
1074
2142
|
function createAsyncMachine(context, fns) {
|
|
1075
2143
|
return Object.assign({ context }, fns);
|
|
@@ -1105,6 +2173,17 @@ function extendTransitions(machine, newTransitions) {
|
|
|
1105
2173
|
const combinedTransitions = { ...originalTransitions, ...newTransitions };
|
|
1106
2174
|
return createMachine(context, combinedTransitions);
|
|
1107
2175
|
}
|
|
2176
|
+
function combineFactories(factory1, factory2) {
|
|
2177
|
+
return (...args) => {
|
|
2178
|
+
const machine1 = factory1(...args);
|
|
2179
|
+
const machine2 = factory2();
|
|
2180
|
+
const combinedContext = { ...machine1.context, ...machine2.context };
|
|
2181
|
+
const { context: _, ...transitions1 } = machine1;
|
|
2182
|
+
const { context: __, ...transitions2 } = machine2;
|
|
2183
|
+
const combinedTransitions = { ...transitions1, ...transitions2 };
|
|
2184
|
+
return createMachine(combinedContext, combinedTransitions);
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
1108
2187
|
function createMachineBuilder(templateMachine) {
|
|
1109
2188
|
const { context, ...transitions } = templateMachine;
|
|
1110
2189
|
return (newContext) => {
|
|
@@ -1124,15 +2203,32 @@ function hasState(machine, key, value) {
|
|
|
1124
2203
|
}
|
|
1125
2204
|
function runMachine(initial, onChange) {
|
|
1126
2205
|
let current = initial;
|
|
2206
|
+
let activeController = null;
|
|
1127
2207
|
async function dispatch(event) {
|
|
2208
|
+
if (activeController) {
|
|
2209
|
+
activeController.abort();
|
|
2210
|
+
activeController = null;
|
|
2211
|
+
}
|
|
1128
2212
|
const fn = current[event.type];
|
|
1129
2213
|
if (typeof fn !== "function") {
|
|
1130
2214
|
throw new Error(`[Machine] Unknown event type '${String(event.type)}' on current state.`);
|
|
1131
2215
|
}
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
2216
|
+
const controller = new AbortController();
|
|
2217
|
+
activeController = controller;
|
|
2218
|
+
try {
|
|
2219
|
+
const nextStatePromise = fn.apply(current.context, [...event.args, { signal: controller.signal }]);
|
|
2220
|
+
const nextState = await nextStatePromise;
|
|
2221
|
+
if (controller.signal.aborted) {
|
|
2222
|
+
return current;
|
|
2223
|
+
}
|
|
2224
|
+
current = nextState;
|
|
2225
|
+
onChange == null ? void 0 : onChange(current);
|
|
2226
|
+
return current;
|
|
2227
|
+
} finally {
|
|
2228
|
+
if (activeController === controller) {
|
|
2229
|
+
activeController = null;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
1136
2232
|
}
|
|
1137
2233
|
return {
|
|
1138
2234
|
/** Gets the context of the current state of the machine. */
|
|
@@ -1140,7 +2236,14 @@ function runMachine(initial, onChange) {
|
|
|
1140
2236
|
return current.context;
|
|
1141
2237
|
},
|
|
1142
2238
|
/** Dispatches a type-safe event to the machine, triggering a transition. */
|
|
1143
|
-
dispatch
|
|
2239
|
+
dispatch,
|
|
2240
|
+
/** Stops any pending async operation and cleans up resources. */
|
|
2241
|
+
stop: () => {
|
|
2242
|
+
if (activeController) {
|
|
2243
|
+
activeController.abort();
|
|
2244
|
+
activeController = null;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
1144
2247
|
};
|
|
1145
2248
|
}
|
|
1146
2249
|
var MachineBase = class {
|