@doeixd/machine 0.0.4 → 0.0.6
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 +952 -13
- package/dist/cjs/development/index.js +691 -0
- package/dist/cjs/development/index.js.map +4 -4
- package/dist/cjs/production/index.js +5 -1
- package/dist/esm/development/index.js +698 -0
- package/dist/esm/development/index.js.map +4 -4
- package/dist/esm/production/index.js +5 -1
- package/dist/types/extract.d.ts +71 -0
- package/dist/types/extract.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/multi.d.ts +838 -0
- package/dist/types/multi.d.ts.map +1 -0
- package/dist/types/primitives.d.ts +202 -0
- package/dist/types/primitives.d.ts.map +1 -0
- package/dist/types/runtime-extract.d.ts +53 -0
- package/dist/types/runtime-extract.d.ts.map +1 -0
- package/package.json +6 -2
- package/src/extract.ts +452 -67
- package/src/index.ts +49 -0
- package/src/multi.ts +1145 -0
- package/src/primitives.ts +135 -0
- package/src/react.ts +349 -28
- package/src/runtime-extract.ts +141 -0
- package/src/solid.ts +8 -8
|
@@ -20,15 +20,34 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
META_KEY: () => META_KEY,
|
|
23
24
|
MachineBase: () => MachineBase,
|
|
25
|
+
MultiMachineBase: () => MultiMachineBase,
|
|
26
|
+
RUNTIME_META: () => RUNTIME_META,
|
|
27
|
+
action: () => action,
|
|
24
28
|
createAsyncMachine: () => createAsyncMachine,
|
|
29
|
+
createEnsemble: () => createEnsemble,
|
|
25
30
|
createFlow: () => createFlow,
|
|
26
31
|
createMachine: () => createMachine,
|
|
27
32
|
createMachineBuilder: () => createMachineBuilder,
|
|
28
33
|
createMachineFactory: () => createMachineFactory,
|
|
34
|
+
createMultiMachine: () => createMultiMachine,
|
|
35
|
+
createMutableMachine: () => createMutableMachine,
|
|
36
|
+
createRunner: () => createRunner,
|
|
37
|
+
describe: () => describe,
|
|
29
38
|
extendTransitions: () => extendTransitions,
|
|
39
|
+
extractFromInstance: () => extractFromInstance,
|
|
40
|
+
extractFunctionMetadata: () => extractFunctionMetadata,
|
|
41
|
+
extractMachine: () => extractMachine,
|
|
42
|
+
extractMachines: () => extractMachines,
|
|
43
|
+
extractStateNode: () => extractStateNode,
|
|
44
|
+
generateChart: () => generateChart,
|
|
45
|
+
generateStatechart: () => generateStatechart,
|
|
46
|
+
guarded: () => guarded,
|
|
30
47
|
hasState: () => hasState,
|
|
48
|
+
invoke: () => invoke,
|
|
31
49
|
matchMachine: () => matchMachine,
|
|
50
|
+
metadata: () => metadata,
|
|
32
51
|
next: () => next,
|
|
33
52
|
overrideTransitions: () => overrideTransitions,
|
|
34
53
|
run: () => run,
|
|
@@ -36,9 +55,12 @@ __export(src_exports, {
|
|
|
36
55
|
runMachine: () => runMachine,
|
|
37
56
|
runSequence: () => runSequence,
|
|
38
57
|
runWithDebug: () => runWithDebug,
|
|
58
|
+
runWithEnsemble: () => runWithEnsemble,
|
|
59
|
+
runWithRunner: () => runWithRunner,
|
|
39
60
|
setContext: () => setContext,
|
|
40
61
|
step: () => step,
|
|
41
62
|
stepAsync: () => stepAsync,
|
|
63
|
+
transitionTo: () => transitionTo,
|
|
42
64
|
yieldMachine: () => yieldMachine
|
|
43
65
|
});
|
|
44
66
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -106,6 +128,675 @@ async function* stepAsync(m) {
|
|
|
106
128
|
return received;
|
|
107
129
|
}
|
|
108
130
|
|
|
131
|
+
// src/primitives.ts
|
|
132
|
+
var META_KEY = Symbol("MachineMeta");
|
|
133
|
+
var RUNTIME_META = Symbol("__machine_runtime_meta__");
|
|
134
|
+
function attachRuntimeMeta(fn, metadata2) {
|
|
135
|
+
const existing = fn[RUNTIME_META] || {};
|
|
136
|
+
const merged = { ...existing, ...metadata2 };
|
|
137
|
+
if (metadata2.guards && existing.guards) {
|
|
138
|
+
merged.guards = [...metadata2.guards, ...existing.guards];
|
|
139
|
+
} else if (metadata2.guards) {
|
|
140
|
+
merged.guards = [...metadata2.guards];
|
|
141
|
+
}
|
|
142
|
+
if (metadata2.actions && existing.actions) {
|
|
143
|
+
merged.actions = [...metadata2.actions, ...existing.actions];
|
|
144
|
+
} else if (metadata2.actions) {
|
|
145
|
+
merged.actions = [...metadata2.actions];
|
|
146
|
+
}
|
|
147
|
+
Object.defineProperty(fn, RUNTIME_META, {
|
|
148
|
+
value: merged,
|
|
149
|
+
enumerable: false,
|
|
150
|
+
writable: false,
|
|
151
|
+
configurable: true
|
|
152
|
+
// CRITICAL: Must be configurable for re-definition
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function transitionTo(_target, implementation) {
|
|
156
|
+
attachRuntimeMeta(implementation, {
|
|
157
|
+
target: _target.name || _target.toString()
|
|
158
|
+
});
|
|
159
|
+
return implementation;
|
|
160
|
+
}
|
|
161
|
+
function describe(_text, transition) {
|
|
162
|
+
attachRuntimeMeta(transition, {
|
|
163
|
+
description: _text
|
|
164
|
+
});
|
|
165
|
+
return transition;
|
|
166
|
+
}
|
|
167
|
+
function guarded(guard, transition) {
|
|
168
|
+
attachRuntimeMeta(transition, {
|
|
169
|
+
guards: [guard]
|
|
170
|
+
});
|
|
171
|
+
return transition;
|
|
172
|
+
}
|
|
173
|
+
function invoke(service, implementation) {
|
|
174
|
+
attachRuntimeMeta(implementation, {
|
|
175
|
+
invoke: {
|
|
176
|
+
src: service.src,
|
|
177
|
+
onDone: service.onDone.name || service.onDone.toString(),
|
|
178
|
+
onError: service.onError.name || service.onError.toString(),
|
|
179
|
+
description: service.description
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return implementation;
|
|
183
|
+
}
|
|
184
|
+
function action(action2, transition) {
|
|
185
|
+
attachRuntimeMeta(transition, {
|
|
186
|
+
actions: [action2]
|
|
187
|
+
});
|
|
188
|
+
return transition;
|
|
189
|
+
}
|
|
190
|
+
function metadata(_meta, value) {
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/extract.ts
|
|
195
|
+
var import_ts_morph = require("ts-morph");
|
|
196
|
+
function resolveClassName(node) {
|
|
197
|
+
if (import_ts_morph.Node.isIdentifier(node)) {
|
|
198
|
+
return node.getText();
|
|
199
|
+
}
|
|
200
|
+
if (import_ts_morph.Node.isTypeOfExpression(node)) {
|
|
201
|
+
return node.getExpression().getText();
|
|
202
|
+
}
|
|
203
|
+
return "unknown";
|
|
204
|
+
}
|
|
205
|
+
function parseObjectLiteral(obj) {
|
|
206
|
+
if (!import_ts_morph.Node.isObjectLiteralExpression(obj)) {
|
|
207
|
+
return {};
|
|
208
|
+
}
|
|
209
|
+
const result = {};
|
|
210
|
+
for (const prop of obj.getProperties()) {
|
|
211
|
+
if (import_ts_morph.Node.isPropertyAssignment(prop)) {
|
|
212
|
+
const name = prop.getName();
|
|
213
|
+
const init = prop.getInitializer();
|
|
214
|
+
if (init) {
|
|
215
|
+
if (import_ts_morph.Node.isStringLiteral(init)) {
|
|
216
|
+
result[name] = init.getLiteralValue();
|
|
217
|
+
} else if (import_ts_morph.Node.isNumericLiteral(init)) {
|
|
218
|
+
result[name] = init.getLiteralValue();
|
|
219
|
+
} else if (init.getText() === "true" || init.getText() === "false") {
|
|
220
|
+
result[name] = init.getText() === "true";
|
|
221
|
+
} else if (import_ts_morph.Node.isIdentifier(init)) {
|
|
222
|
+
result[name] = init.getText();
|
|
223
|
+
} else if (import_ts_morph.Node.isObjectLiteralExpression(init)) {
|
|
224
|
+
result[name] = parseObjectLiteral(init);
|
|
225
|
+
} else if (import_ts_morph.Node.isArrayLiteralExpression(init)) {
|
|
226
|
+
result[name] = init.getElements().map((el) => {
|
|
227
|
+
if (import_ts_morph.Node.isObjectLiteralExpression(el)) {
|
|
228
|
+
return parseObjectLiteral(el);
|
|
229
|
+
}
|
|
230
|
+
return el.getText();
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
function parseInvokeService(obj) {
|
|
239
|
+
if (!import_ts_morph.Node.isObjectLiteralExpression(obj)) {
|
|
240
|
+
return {};
|
|
241
|
+
}
|
|
242
|
+
const service = {};
|
|
243
|
+
for (const prop of obj.getProperties()) {
|
|
244
|
+
if (import_ts_morph.Node.isPropertyAssignment(prop)) {
|
|
245
|
+
const name = prop.getName();
|
|
246
|
+
const init = prop.getInitializer();
|
|
247
|
+
if (!init) continue;
|
|
248
|
+
if (name === "onDone" || name === "onError") {
|
|
249
|
+
service[name] = resolveClassName(init);
|
|
250
|
+
} else if (import_ts_morph.Node.isStringLiteral(init)) {
|
|
251
|
+
service[name] = init.getLiteralValue();
|
|
252
|
+
} else if (import_ts_morph.Node.isIdentifier(init)) {
|
|
253
|
+
service[name] = init.getText();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return service;
|
|
258
|
+
}
|
|
259
|
+
function extractFromCallExpression(call, verbose = false) {
|
|
260
|
+
if (!import_ts_morph.Node.isCallExpression(call)) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
const expression = call.getExpression();
|
|
264
|
+
const fnName = import_ts_morph.Node.isIdentifier(expression) ? expression.getText() : null;
|
|
265
|
+
if (!fnName) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const metadata2 = {};
|
|
269
|
+
const args = call.getArguments();
|
|
270
|
+
switch (fnName) {
|
|
271
|
+
case "transitionTo":
|
|
272
|
+
if (args[0]) {
|
|
273
|
+
metadata2.target = resolveClassName(args[0]);
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
case "describe":
|
|
277
|
+
if (args[0] && import_ts_morph.Node.isStringLiteral(args[0])) {
|
|
278
|
+
metadata2.description = args[0].getLiteralValue();
|
|
279
|
+
}
|
|
280
|
+
if (args[1] && import_ts_morph.Node.isCallExpression(args[1])) {
|
|
281
|
+
const nested = extractFromCallExpression(args[1], verbose);
|
|
282
|
+
if (nested) {
|
|
283
|
+
Object.assign(metadata2, nested);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
case "guarded":
|
|
288
|
+
if (args[0]) {
|
|
289
|
+
const guard = parseObjectLiteral(args[0]);
|
|
290
|
+
if (Object.keys(guard).length > 0) {
|
|
291
|
+
metadata2.guards = [guard];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (args[1] && import_ts_morph.Node.isCallExpression(args[1])) {
|
|
295
|
+
const nested = extractFromCallExpression(args[1], verbose);
|
|
296
|
+
if (nested) {
|
|
297
|
+
Object.assign(metadata2, nested);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
case "invoke":
|
|
302
|
+
if (args[0]) {
|
|
303
|
+
const service = parseInvokeService(args[0]);
|
|
304
|
+
if (Object.keys(service).length > 0) {
|
|
305
|
+
metadata2.invoke = service;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
break;
|
|
309
|
+
case "action":
|
|
310
|
+
if (args[0]) {
|
|
311
|
+
const actionMeta = parseObjectLiteral(args[0]);
|
|
312
|
+
if (Object.keys(actionMeta).length > 0) {
|
|
313
|
+
metadata2.actions = [actionMeta];
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (args[1] && import_ts_morph.Node.isCallExpression(args[1])) {
|
|
317
|
+
const nested = extractFromCallExpression(args[1], verbose);
|
|
318
|
+
if (nested) {
|
|
319
|
+
Object.assign(metadata2, nested);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
default:
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return Object.keys(metadata2).length > 0 ? metadata2 : null;
|
|
327
|
+
}
|
|
328
|
+
function extractMetaFromMember(member, verbose = false) {
|
|
329
|
+
if (!import_ts_morph.Node.isPropertyDeclaration(member)) {
|
|
330
|
+
if (verbose) console.error(` ⚠️ Not a property declaration`);
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
const initializer = member.getInitializer();
|
|
334
|
+
if (!initializer) {
|
|
335
|
+
if (verbose) console.error(` ⚠️ No initializer`);
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
if (!import_ts_morph.Node.isCallExpression(initializer)) {
|
|
339
|
+
if (verbose) console.error(` ⚠️ Initializer is not a call expression`);
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
const metadata2 = extractFromCallExpression(initializer, verbose);
|
|
343
|
+
if (metadata2 && verbose) {
|
|
344
|
+
console.error(` ✅ Extracted metadata:`, JSON.stringify(metadata2, null, 2));
|
|
345
|
+
}
|
|
346
|
+
return metadata2;
|
|
347
|
+
}
|
|
348
|
+
function analyzeStateNode(classSymbol, verbose = false) {
|
|
349
|
+
const chartNode = { on: {} };
|
|
350
|
+
const classDeclaration = classSymbol.getDeclarations()[0];
|
|
351
|
+
if (!classDeclaration || !import_ts_morph.Node.isClassDeclaration(classDeclaration)) {
|
|
352
|
+
if (verbose) {
|
|
353
|
+
console.error(`⚠️ Warning: Could not get class declaration for ${classSymbol.getName()}`);
|
|
354
|
+
}
|
|
355
|
+
return chartNode;
|
|
356
|
+
}
|
|
357
|
+
const className = classSymbol.getName();
|
|
358
|
+
if (verbose) {
|
|
359
|
+
console.error(` Analyzing state: ${className}`);
|
|
360
|
+
}
|
|
361
|
+
for (const member of classDeclaration.getInstanceMembers()) {
|
|
362
|
+
const memberName = member.getName();
|
|
363
|
+
if (verbose) {
|
|
364
|
+
console.error(` Checking member: ${memberName}`);
|
|
365
|
+
}
|
|
366
|
+
const meta = extractMetaFromMember(member, verbose);
|
|
367
|
+
if (!meta) continue;
|
|
368
|
+
if (verbose) {
|
|
369
|
+
console.error(` Found transition: ${memberName}`);
|
|
370
|
+
}
|
|
371
|
+
const { invoke: invoke2, actions, guards, ...onEntry } = meta;
|
|
372
|
+
if (invoke2) {
|
|
373
|
+
if (!chartNode.invoke) chartNode.invoke = [];
|
|
374
|
+
chartNode.invoke.push({
|
|
375
|
+
src: invoke2.src,
|
|
376
|
+
onDone: { target: invoke2.onDone },
|
|
377
|
+
onError: { target: invoke2.onError },
|
|
378
|
+
description: invoke2.description
|
|
379
|
+
});
|
|
380
|
+
if (verbose) {
|
|
381
|
+
console.error(` → Invoke: ${invoke2.src}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (onEntry.target) {
|
|
385
|
+
const transition = { target: onEntry.target };
|
|
386
|
+
if (onEntry.description) {
|
|
387
|
+
transition.description = onEntry.description;
|
|
388
|
+
}
|
|
389
|
+
if (guards) {
|
|
390
|
+
transition.cond = guards.map((g) => g.name).join(" && ");
|
|
391
|
+
if (verbose) {
|
|
392
|
+
console.error(` → Guard: ${transition.cond}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (actions && actions.length > 0) {
|
|
396
|
+
transition.actions = actions.map((a) => a.name);
|
|
397
|
+
if (verbose) {
|
|
398
|
+
console.error(` → Actions: ${transition.actions.join(", ")}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
chartNode.on[memberName] = transition;
|
|
402
|
+
if (verbose) {
|
|
403
|
+
console.error(` → Target: ${onEntry.target}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return chartNode;
|
|
408
|
+
}
|
|
409
|
+
function extractMachine(config, project, verbose = false) {
|
|
410
|
+
if (verbose) {
|
|
411
|
+
console.error(`
|
|
412
|
+
🔍 Analyzing machine: ${config.id}`);
|
|
413
|
+
console.error(` Source: ${config.input}`);
|
|
414
|
+
}
|
|
415
|
+
const sourceFile = project.getSourceFile(config.input);
|
|
416
|
+
if (!sourceFile) {
|
|
417
|
+
throw new Error(`Source file not found: ${config.input}`);
|
|
418
|
+
}
|
|
419
|
+
const fullChart = {
|
|
420
|
+
id: config.id,
|
|
421
|
+
initial: config.initialState,
|
|
422
|
+
states: {}
|
|
423
|
+
};
|
|
424
|
+
if (config.description) {
|
|
425
|
+
fullChart.description = config.description;
|
|
426
|
+
}
|
|
427
|
+
for (const className of config.classes) {
|
|
428
|
+
const classDeclaration = sourceFile.getClass(className);
|
|
429
|
+
if (!classDeclaration) {
|
|
430
|
+
console.warn(`⚠️ Warning: Class '${className}' not found in '${config.input}'. Skipping.`);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
const classSymbol = classDeclaration.getSymbolOrThrow();
|
|
434
|
+
const stateNode = analyzeStateNode(classSymbol, verbose);
|
|
435
|
+
fullChart.states[className] = stateNode;
|
|
436
|
+
}
|
|
437
|
+
if (verbose) {
|
|
438
|
+
console.error(` ✅ Extracted ${config.classes.length} states`);
|
|
439
|
+
}
|
|
440
|
+
return fullChart;
|
|
441
|
+
}
|
|
442
|
+
function extractMachines(config) {
|
|
443
|
+
var _a;
|
|
444
|
+
const verbose = (_a = config.verbose) != null ? _a : false;
|
|
445
|
+
if (verbose) {
|
|
446
|
+
console.error(`
|
|
447
|
+
📊 Starting statechart extraction`);
|
|
448
|
+
console.error(` Machines to extract: ${config.machines.length}`);
|
|
449
|
+
}
|
|
450
|
+
const project = new import_ts_morph.Project();
|
|
451
|
+
project.addSourceFilesAtPaths("src/**/*.ts");
|
|
452
|
+
project.addSourceFilesAtPaths("examples/**/*.ts");
|
|
453
|
+
const results = [];
|
|
454
|
+
for (const machineConfig of config.machines) {
|
|
455
|
+
try {
|
|
456
|
+
const chart = extractMachine(machineConfig, project, verbose);
|
|
457
|
+
results.push(chart);
|
|
458
|
+
} catch (error) {
|
|
459
|
+
console.error(`❌ Error extracting machine '${machineConfig.id}':`, error);
|
|
460
|
+
if (!verbose) {
|
|
461
|
+
console.error(` Run with --verbose for more details`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (verbose) {
|
|
466
|
+
console.error(`
|
|
467
|
+
✅ Extraction complete: ${results.length}/${config.machines.length} machines extracted`);
|
|
468
|
+
}
|
|
469
|
+
return results;
|
|
470
|
+
}
|
|
471
|
+
function generateChart() {
|
|
472
|
+
const config = {
|
|
473
|
+
input: "examples/authMachine.ts",
|
|
474
|
+
classes: [
|
|
475
|
+
"LoggedOutMachine",
|
|
476
|
+
"LoggingInMachine",
|
|
477
|
+
"LoggedInMachine",
|
|
478
|
+
"SessionExpiredMachine",
|
|
479
|
+
"ErrorMachine"
|
|
480
|
+
],
|
|
481
|
+
id: "auth",
|
|
482
|
+
initialState: "LoggedOutMachine",
|
|
483
|
+
description: "Authentication state machine"
|
|
484
|
+
};
|
|
485
|
+
console.error("🔍 Using legacy generateChart function");
|
|
486
|
+
console.error("⚠️ Consider using extractMachines() with a config file instead\n");
|
|
487
|
+
const project = new import_ts_morph.Project();
|
|
488
|
+
project.addSourceFilesAtPaths("src/**/*.ts");
|
|
489
|
+
project.addSourceFilesAtPaths("examples/**/*.ts");
|
|
490
|
+
try {
|
|
491
|
+
const chart = extractMachine(config, project, true);
|
|
492
|
+
console.log(JSON.stringify(chart, null, 2));
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.error(`❌ Error:`, error);
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (require.main === module) {
|
|
499
|
+
generateChart();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/runtime-extract.ts
|
|
503
|
+
function extractFunctionMetadata(fn) {
|
|
504
|
+
if (typeof fn !== "function") {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
const meta = fn[RUNTIME_META];
|
|
508
|
+
return meta || null;
|
|
509
|
+
}
|
|
510
|
+
function extractStateNode(stateInstance) {
|
|
511
|
+
const stateNode = { on: {} };
|
|
512
|
+
const invoke2 = [];
|
|
513
|
+
for (const key in stateInstance) {
|
|
514
|
+
const value = stateInstance[key];
|
|
515
|
+
if (typeof value !== "function") {
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
const meta = extractFunctionMetadata(value);
|
|
519
|
+
if (!meta) {
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (meta.invoke) {
|
|
523
|
+
invoke2.push({
|
|
524
|
+
src: meta.invoke.src,
|
|
525
|
+
onDone: { target: meta.invoke.onDone },
|
|
526
|
+
onError: { target: meta.invoke.onError },
|
|
527
|
+
description: meta.invoke.description
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
if (meta.target) {
|
|
531
|
+
const transition = { target: meta.target };
|
|
532
|
+
if (meta.description) {
|
|
533
|
+
transition.description = meta.description;
|
|
534
|
+
}
|
|
535
|
+
if (meta.guards && meta.guards.length > 0) {
|
|
536
|
+
transition.cond = meta.guards.map((g) => g.name).join(" && ");
|
|
537
|
+
}
|
|
538
|
+
if (meta.actions && meta.actions.length > 0) {
|
|
539
|
+
transition.actions = meta.actions.map((a) => a.name);
|
|
540
|
+
}
|
|
541
|
+
stateNode.on[key] = transition;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (invoke2.length > 0) {
|
|
545
|
+
stateNode.invoke = invoke2;
|
|
546
|
+
}
|
|
547
|
+
return stateNode;
|
|
548
|
+
}
|
|
549
|
+
function generateStatechart(states, config) {
|
|
550
|
+
const chart = {
|
|
551
|
+
id: config.id,
|
|
552
|
+
initial: config.initial,
|
|
553
|
+
states: {}
|
|
554
|
+
};
|
|
555
|
+
if (config.description) {
|
|
556
|
+
chart.description = config.description;
|
|
557
|
+
}
|
|
558
|
+
for (const [stateName, stateInstance] of Object.entries(states)) {
|
|
559
|
+
chart.states[stateName] = extractStateNode(stateInstance);
|
|
560
|
+
}
|
|
561
|
+
return chart;
|
|
562
|
+
}
|
|
563
|
+
function extractFromInstance(machineInstance, config) {
|
|
564
|
+
const stateName = config.stateName || machineInstance.constructor.name || "State";
|
|
565
|
+
return {
|
|
566
|
+
id: config.id,
|
|
567
|
+
initial: stateName,
|
|
568
|
+
states: {
|
|
569
|
+
[stateName]: extractStateNode(machineInstance)
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/multi.ts
|
|
575
|
+
function createRunner(initialMachine, onChange) {
|
|
576
|
+
let currentMachine = initialMachine;
|
|
577
|
+
const setState = (newState) => {
|
|
578
|
+
currentMachine = newState;
|
|
579
|
+
onChange == null ? void 0 : onChange(newState);
|
|
580
|
+
};
|
|
581
|
+
const { context: _initialContext, ...originalTransitions } = initialMachine;
|
|
582
|
+
const actions = new Proxy({}, {
|
|
583
|
+
get(_target, prop) {
|
|
584
|
+
const transition = currentMachine[prop];
|
|
585
|
+
if (typeof transition !== "function") {
|
|
586
|
+
return void 0;
|
|
587
|
+
}
|
|
588
|
+
return (...args) => {
|
|
589
|
+
const nextState = transition.apply(currentMachine.context, args);
|
|
590
|
+
const nextStateWithTransitions = Object.assign(
|
|
591
|
+
{ context: nextState.context },
|
|
592
|
+
originalTransitions
|
|
593
|
+
);
|
|
594
|
+
setState(nextStateWithTransitions);
|
|
595
|
+
return nextStateWithTransitions;
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
return {
|
|
600
|
+
get state() {
|
|
601
|
+
return currentMachine;
|
|
602
|
+
},
|
|
603
|
+
get context() {
|
|
604
|
+
return currentMachine.context;
|
|
605
|
+
},
|
|
606
|
+
actions,
|
|
607
|
+
setState
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
function createEnsemble(store, factories, getDiscriminant) {
|
|
611
|
+
const getCurrentMachine = () => {
|
|
612
|
+
const context = store.getContext();
|
|
613
|
+
const currentStateName = getDiscriminant(context);
|
|
614
|
+
const factory = factories[currentStateName];
|
|
615
|
+
if (!factory) {
|
|
616
|
+
throw new Error(
|
|
617
|
+
`[Ensemble] Invalid state: No factory found for state "${String(currentStateName)}".`
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
return factory(context);
|
|
621
|
+
};
|
|
622
|
+
const actions = new Proxy({}, {
|
|
623
|
+
get(_target, prop) {
|
|
624
|
+
const currentMachine = getCurrentMachine();
|
|
625
|
+
const action2 = currentMachine[prop];
|
|
626
|
+
if (typeof action2 !== "function") {
|
|
627
|
+
throw new Error(
|
|
628
|
+
`[Ensemble] Transition "${prop}" is not valid in the current state.`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
return (...args) => {
|
|
632
|
+
return action2.apply(currentMachine.context, args);
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
return {
|
|
637
|
+
get context() {
|
|
638
|
+
return store.getContext();
|
|
639
|
+
},
|
|
640
|
+
get state() {
|
|
641
|
+
return getCurrentMachine();
|
|
642
|
+
},
|
|
643
|
+
actions
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function runWithRunner(flow, initialMachine) {
|
|
647
|
+
const runner = createRunner(initialMachine);
|
|
648
|
+
const generator = flow(runner);
|
|
649
|
+
let result = generator.next();
|
|
650
|
+
while (!result.done) {
|
|
651
|
+
result = generator.next();
|
|
652
|
+
}
|
|
653
|
+
return result.value;
|
|
654
|
+
}
|
|
655
|
+
function runWithEnsemble(flow, ensemble) {
|
|
656
|
+
const generator = flow(ensemble);
|
|
657
|
+
let result = generator.next();
|
|
658
|
+
while (!result.done) {
|
|
659
|
+
result = generator.next();
|
|
660
|
+
}
|
|
661
|
+
return result.value;
|
|
662
|
+
}
|
|
663
|
+
var MultiMachineBase = class {
|
|
664
|
+
/**
|
|
665
|
+
* @param store - The StateStore that will manage this machine's context.
|
|
666
|
+
*/
|
|
667
|
+
constructor(store) {
|
|
668
|
+
this.store = store;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Read-only access to the current context from the external store.
|
|
672
|
+
* This getter always returns the latest context from the store.
|
|
673
|
+
*
|
|
674
|
+
* @protected
|
|
675
|
+
*
|
|
676
|
+
* @example
|
|
677
|
+
* const currentStatus = this.context.status;
|
|
678
|
+
* const currentData = this.context.data;
|
|
679
|
+
*/
|
|
680
|
+
get context() {
|
|
681
|
+
return this.store.getContext();
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Update the shared context in the external store.
|
|
685
|
+
* Call this method in your transition methods to update the state.
|
|
686
|
+
*
|
|
687
|
+
* @protected
|
|
688
|
+
* @param newContext - The new context object. Should typically be a shallow
|
|
689
|
+
* copy with only the properties you're changing, merged with the current
|
|
690
|
+
* context using spread operators.
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* // In a transition method:
|
|
694
|
+
* this.setContext({ ...this.context, status: 'loading' });
|
|
695
|
+
*
|
|
696
|
+
* @example
|
|
697
|
+
* // Updating nested properties:
|
|
698
|
+
* this.setContext({
|
|
699
|
+
* ...this.context,
|
|
700
|
+
* user: { ...this.context.user, name: 'Alice' }
|
|
701
|
+
* });
|
|
702
|
+
*/
|
|
703
|
+
setContext(newContext) {
|
|
704
|
+
this.store.setContext(newContext);
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
function createMultiMachine(MachineClass, store) {
|
|
708
|
+
const instance = new MachineClass(store);
|
|
709
|
+
return new Proxy({}, {
|
|
710
|
+
get(_target, prop) {
|
|
711
|
+
const context = store.getContext();
|
|
712
|
+
if (prop in context) {
|
|
713
|
+
return context[prop];
|
|
714
|
+
}
|
|
715
|
+
const method = instance[prop];
|
|
716
|
+
if (typeof method === "function") {
|
|
717
|
+
return (...args) => {
|
|
718
|
+
return method.apply(instance, args);
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
return void 0;
|
|
722
|
+
},
|
|
723
|
+
set(_target, prop, value) {
|
|
724
|
+
const context = store.getContext();
|
|
725
|
+
if (prop in context) {
|
|
726
|
+
const newContext = { ...context, [prop]: value };
|
|
727
|
+
store.setContext(newContext);
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
return false;
|
|
731
|
+
},
|
|
732
|
+
has(_target, prop) {
|
|
733
|
+
const context = store.getContext();
|
|
734
|
+
return prop in context || typeof instance[prop] === "function";
|
|
735
|
+
},
|
|
736
|
+
ownKeys(_target) {
|
|
737
|
+
const context = store.getContext();
|
|
738
|
+
const contextKeys = Object.keys(context);
|
|
739
|
+
const methodKeys = Object.getOwnPropertyNames(
|
|
740
|
+
Object.getPrototypeOf(instance)
|
|
741
|
+
).filter((key) => key !== "constructor" && typeof instance[key] === "function");
|
|
742
|
+
return Array.from(/* @__PURE__ */ new Set([...contextKeys, ...methodKeys]));
|
|
743
|
+
},
|
|
744
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
745
|
+
const context = store.getContext();
|
|
746
|
+
if (prop in context || typeof instance[prop] === "function") {
|
|
747
|
+
return {
|
|
748
|
+
value: void 0,
|
|
749
|
+
writable: true,
|
|
750
|
+
enumerable: true,
|
|
751
|
+
configurable: true
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
return void 0;
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
function createMutableMachine(sharedContext, factories, getDiscriminant) {
|
|
759
|
+
const getCurrentMachine = () => {
|
|
760
|
+
const currentStateName = getDiscriminant(sharedContext);
|
|
761
|
+
const factory = factories[currentStateName];
|
|
762
|
+
if (!factory) {
|
|
763
|
+
throw new Error(
|
|
764
|
+
`[MutableMachine] Invalid state: No factory for state "${String(currentStateName)}".`
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
return factory(sharedContext);
|
|
768
|
+
};
|
|
769
|
+
return new Proxy(sharedContext, {
|
|
770
|
+
get(target, prop, _receiver) {
|
|
771
|
+
if (prop in target) {
|
|
772
|
+
return target[prop];
|
|
773
|
+
}
|
|
774
|
+
const currentMachine = getCurrentMachine();
|
|
775
|
+
const transition = currentMachine[prop];
|
|
776
|
+
if (typeof transition === "function") {
|
|
777
|
+
return (...args) => {
|
|
778
|
+
const nextContext = transition.apply(currentMachine.context, args);
|
|
779
|
+
if (typeof nextContext !== "object" || nextContext === null) {
|
|
780
|
+
console.warn(`[MutableMachine] Transition "${String(prop)}" did not return a valid context object. State may be inconsistent.`);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
Object.keys(target).forEach((key) => delete target[key]);
|
|
784
|
+
Object.assign(target, nextContext);
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
return void 0;
|
|
788
|
+
},
|
|
789
|
+
set(target, prop, value, _receiver) {
|
|
790
|
+
target[prop] = value;
|
|
791
|
+
return true;
|
|
792
|
+
},
|
|
793
|
+
has(target, prop) {
|
|
794
|
+
const currentMachine = getCurrentMachine();
|
|
795
|
+
return prop in target || typeof currentMachine[prop] === "function";
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
|
|
109
800
|
// src/index.ts
|
|
110
801
|
function createMachine(context, fns) {
|
|
111
802
|
return Object.assign({ context }, fns);
|