@statelyai/sdk 0.28.0 → 0.29.0
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 +6 -1
- package/dist/{editor-sync-types-CTKArS8n.d.mts → editor-sync-types-DB1N6QUK.d.mts} +0 -1
- package/dist/{editorHostSession-shrjfosC.mjs → editorHostSession-DNpXPNAc.mjs} +0 -1
- package/dist/{editorHostSession-CWAuoMtC.d.mts → editorHostSession-vSt07IJV.d.mts} +1 -1
- package/dist/editorHostSession.d.mts +1 -1
- package/dist/editorHostSession.mjs +1 -1
- package/dist/embed.d.mts +2 -2
- package/dist/embed.mjs +1 -1
- package/dist/{graph-BwHnhzpC.d.mts → graph-5X1Ilppq.d.mts} +1 -1
- package/dist/graph.d.mts +1 -1
- package/dist/{index-D2I1qPsf.d.mts → index-DNDfA-YC.d.mts} +38 -6
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +6 -6
- package/dist/{inspect-DSDnyzZ5.mjs → inspect-DYFQzEsh.mjs} +1 -1
- package/dist/inspect.mjs +2 -2
- package/dist/patchTypes.d.mts +1 -1
- package/dist/protocol.d.mts +5 -5
- package/dist/{statelyPragma-ByF0-L-N.mjs → statelyPragma-Cg1KzBZr.mjs} +938 -49
- package/dist/{studio-CNkxMZyz.mjs → studio-DpCW5V4a.mjs} +61 -0
- package/dist/{studio-B9-1Ynrx.d.mts → studio-QQRLjF5V.d.mts} +55 -1
- package/dist/studio.d.mts +1 -1
- package/dist/studio.mjs +1 -1
- package/dist/sync.d.mts +2 -2
- package/dist/sync.mjs +2 -2
- package/dist/{version-D_SqbrSy.mjs → version-BJPr6idb.mjs} +1 -1
- package/package.json +3 -3
|
@@ -3487,10 +3487,16 @@ function singleOrArrayRecord(record) {
|
|
|
3487
3487
|
* Remove empty arrays/objects/strings, unwrap single-element arrays.
|
|
3488
3488
|
* Matches studio's simplifyAttributes behavior.
|
|
3489
3489
|
*/
|
|
3490
|
+
/**
|
|
3491
|
+
* Keys whose empty object value is authored, not incidental: `context: {}`
|
|
3492
|
+
* declares that the machine has context, so dropping it loses information.
|
|
3493
|
+
*/
|
|
3494
|
+
const KEEP_WHEN_EMPTY = new Set(["context"]);
|
|
3490
3495
|
function simplifyAttributes(obj) {
|
|
3491
3496
|
for (const key of Object.keys(obj)) {
|
|
3492
3497
|
const value = obj[key];
|
|
3493
3498
|
if (value === void 0 || value === null) delete obj[key];
|
|
3499
|
+
else if (KEEP_WHEN_EMPTY.has(key)) continue;
|
|
3494
3500
|
else if (Array.isArray(value)) {
|
|
3495
3501
|
if (value.length === 0) delete obj[key];
|
|
3496
3502
|
else if (value.length === 1) obj[key] = value[0];
|
|
@@ -3535,7 +3541,7 @@ function deepSimplify(config) {
|
|
|
3535
3541
|
return config;
|
|
3536
3542
|
}
|
|
3537
3543
|
function buildMeta(meta, color) {
|
|
3538
|
-
if (isCodeExpression$
|
|
3544
|
+
if (isCodeExpression$4(meta)) return meta;
|
|
3539
3545
|
const result = {};
|
|
3540
3546
|
if (meta) {
|
|
3541
3547
|
for (const [k, v] of Object.entries(meta)) if (v !== void 0 && v !== null) result[k] = v;
|
|
@@ -3543,7 +3549,7 @@ function buildMeta(meta, color) {
|
|
|
3543
3549
|
if (color) result["@statelyai.color"] = color;
|
|
3544
3550
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
3545
3551
|
}
|
|
3546
|
-
function isCodeExpression$
|
|
3552
|
+
function isCodeExpression$4(value) {
|
|
3547
3553
|
return Boolean(value && typeof value === "object" && !Array.isArray(value) && value["@type"] === "code" && typeof value.expr === "string");
|
|
3548
3554
|
}
|
|
3549
3555
|
/** Format event param — supports { type: string } (new) and plain string (legacy) */
|
|
@@ -3554,7 +3560,7 @@ function formatEventParam(event) {
|
|
|
3554
3560
|
}
|
|
3555
3561
|
function formatStringParam(value) {
|
|
3556
3562
|
if (typeof value === "string") return `'${value}'`;
|
|
3557
|
-
return isCodeExpression$
|
|
3563
|
+
return isCodeExpression$4(value) ? serializeJS(value) : "undefined";
|
|
3558
3564
|
}
|
|
3559
3565
|
function formatInlineObject(value) {
|
|
3560
3566
|
return `{ ${Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => `${formatObjectKey(key)}: ${serializeJS(item)}`).join(", ")} }`;
|
|
@@ -3589,7 +3595,11 @@ const builtInActionParamSchemas = {
|
|
|
3589
3595
|
}),
|
|
3590
3596
|
"xstate.stopChild": object({ actorRef: string().optional() }),
|
|
3591
3597
|
"xstate.log": object({ label: string().optional() }),
|
|
3592
|
-
"xstate.assign": object({ assignment: record(string(), unknown()).optional() }).catchall(unknown())
|
|
3598
|
+
"xstate.assign": object({ assignment: record(string(), unknown()).optional() }).catchall(unknown()),
|
|
3599
|
+
"xstate.choose": object({ branches: array(object({
|
|
3600
|
+
guard: unknown().optional(),
|
|
3601
|
+
actions: array(unknown()).optional()
|
|
3602
|
+
})).optional() })
|
|
3593
3603
|
};
|
|
3594
3604
|
const XSTATE_BUILT_IN_ACTIONS = {
|
|
3595
3605
|
"xstate.enqueue": {
|
|
@@ -3685,6 +3695,16 @@ const XSTATE_BUILT_IN_ACTIONS = {
|
|
|
3685
3695
|
return `log(${(params?.label ? `'${params.label}'` : void 0) ?? ""})`;
|
|
3686
3696
|
}
|
|
3687
3697
|
},
|
|
3698
|
+
"xstate.choose": {
|
|
3699
|
+
package: "xstate",
|
|
3700
|
+
version: "^5",
|
|
3701
|
+
import: "enqueueActions",
|
|
3702
|
+
importKind: "named",
|
|
3703
|
+
paramsSchema: builtInActionParamSchemas["xstate.choose"],
|
|
3704
|
+
emit(params) {
|
|
3705
|
+
return emitChooseBranches(params?.branches ?? []);
|
|
3706
|
+
}
|
|
3707
|
+
},
|
|
3688
3708
|
"xstate.assign": {
|
|
3689
3709
|
package: "xstate",
|
|
3690
3710
|
version: "^5",
|
|
@@ -3692,7 +3712,7 @@ const XSTATE_BUILT_IN_ACTIONS = {
|
|
|
3692
3712
|
importKind: "named",
|
|
3693
3713
|
paramsSchema: builtInActionParamSchemas["xstate.assign"],
|
|
3694
3714
|
emit(params) {
|
|
3695
|
-
if (isCodeExpression$
|
|
3715
|
+
if (isCodeExpression$4(params?.assignment)) return `assign(${serializeJS(params.assignment)})`;
|
|
3696
3716
|
const assignments = params?.assignment && typeof params.assignment === "object" && !Array.isArray(params.assignment) ? params.assignment : params;
|
|
3697
3717
|
const entries = Object.entries(assignments ?? {}).filter(([k, v]) => k && v !== void 0);
|
|
3698
3718
|
if (entries.length === 0) return "assign({})";
|
|
@@ -3703,6 +3723,35 @@ const XSTATE_BUILT_IN_ACTIONS = {
|
|
|
3703
3723
|
function isBuiltInActionType(type) {
|
|
3704
3724
|
return type in XSTATE_BUILT_IN_ACTIONS;
|
|
3705
3725
|
}
|
|
3726
|
+
/** Lower `xstate.choose` branches to an `enqueueActions` if/else-if chain. */
|
|
3727
|
+
function emitChooseBranches(branches) {
|
|
3728
|
+
const lines = [];
|
|
3729
|
+
const indent = (depth) => " ".repeat(depth);
|
|
3730
|
+
const serializeEnqueued = (action, depth) => {
|
|
3731
|
+
if (action && typeof action === "object" && !Array.isArray(action) && Object.keys(action).length === 1 && typeof action.type === "string") return serializeJS(action.type);
|
|
3732
|
+
return serializeJS(action, depth * 2);
|
|
3733
|
+
};
|
|
3734
|
+
const body = (actions, depth) => (actions ?? []).map((action) => `${indent(depth)}enqueue(${serializeEnqueued(action, depth)});`);
|
|
3735
|
+
let closed = false;
|
|
3736
|
+
let opened = false;
|
|
3737
|
+
for (const branch of branches) {
|
|
3738
|
+
if (closed) break;
|
|
3739
|
+
if (branch.guard === void 0) {
|
|
3740
|
+
if (opened) {
|
|
3741
|
+
lines.push(` } else {`);
|
|
3742
|
+
lines.push(...body(branch.actions, 2));
|
|
3743
|
+
lines.push(" }");
|
|
3744
|
+
} else lines.push(...body(branch.actions, 1));
|
|
3745
|
+
closed = true;
|
|
3746
|
+
continue;
|
|
3747
|
+
}
|
|
3748
|
+
lines.push(` ${opened ? "} else if" : "if"} (check(${serializeJS(branch.guard)})) {`);
|
|
3749
|
+
lines.push(...body(branch.actions, 2));
|
|
3750
|
+
opened = true;
|
|
3751
|
+
}
|
|
3752
|
+
if (opened && !closed) lines.push(" }");
|
|
3753
|
+
return `enqueueActions(({ check, enqueue }) => {\n${lines.join("\n")}\n})`;
|
|
3754
|
+
}
|
|
3706
3755
|
function emitBuiltInAction(type, params) {
|
|
3707
3756
|
return XSTATE_BUILT_IN_ACTIONS[type].emit(params);
|
|
3708
3757
|
}
|
|
@@ -3743,8 +3792,12 @@ function serializeGuard(guard, asTypeRef) {
|
|
|
3743
3792
|
* Converts a built-in xstate ActionItem to a code expression directive,
|
|
3744
3793
|
* or returns a plain { type, params } object for user-defined actions.
|
|
3745
3794
|
*/
|
|
3746
|
-
function serializeActionItem(action) {
|
|
3795
|
+
function serializeActionItem(action, keepChooseStructured = false) {
|
|
3747
3796
|
const { type, params } = action;
|
|
3797
|
+
if (keepChooseStructured && type === "xstate.choose") return {
|
|
3798
|
+
type,
|
|
3799
|
+
params
|
|
3800
|
+
};
|
|
3748
3801
|
if (type === "xstate.enqueue" && typeof params?.code === "string") return inlineCodeExpression$1(params.code);
|
|
3749
3802
|
const exprCode = type === "xstate.expr" && typeof params?.code === "string" ? params.code : void 0;
|
|
3750
3803
|
if (isBuiltInActionType(type)) {
|
|
@@ -3770,7 +3823,8 @@ function tagToString(tag) {
|
|
|
3770
3823
|
}
|
|
3771
3824
|
function graphToMachineConfig(graph, options = {}) {
|
|
3772
3825
|
const v6 = isXStateV6Profile(graph.data.profile);
|
|
3773
|
-
const { showDescriptions = true, showMeta = true, inlineGuardsAsTypeRef = false } = options;
|
|
3826
|
+
const { showDescriptions = true, showMeta = true, inlineGuardsAsTypeRef = false, keepChooseStructured = false } = options;
|
|
3827
|
+
const serializeAction = (action) => serializeActionItem(action, keepChooseStructured);
|
|
3774
3828
|
const config = {};
|
|
3775
3829
|
function getNodeConfig(node) {
|
|
3776
3830
|
const nodeConfig = node.parentId ? {} : config;
|
|
@@ -3790,11 +3844,11 @@ function graphToMachineConfig(graph, options = {}) {
|
|
|
3790
3844
|
target: initialNode.data.key,
|
|
3791
3845
|
input: node.data.initialInput
|
|
3792
3846
|
} : initialNode.data.key : void 0,
|
|
3793
|
-
target: nodeType === "history" && initialNode ? initialNode
|
|
3847
|
+
target: nodeType === "history" && initialNode ? resolveTransitionTarget(graph, node, initialNode) : void 0,
|
|
3794
3848
|
...node.data.timeout !== void 0 ? { timeout: node.data.timeout } : void 0,
|
|
3795
3849
|
...node.data.route !== void 0 ? { route: node.data.route } : void 0,
|
|
3796
|
-
...node.data.entry?.length ? { entry: node.data.entry.map(
|
|
3797
|
-
...node.data.exit?.length ? { exit: node.data.exit.map(
|
|
3850
|
+
...node.data.entry?.length ? { entry: node.data.entry.map(serializeAction) } : void 0,
|
|
3851
|
+
...node.data.exit?.length ? { exit: node.data.exit.map(serializeAction) } : void 0,
|
|
3798
3852
|
...node.data.invokes?.length ? { invoke: node.data.invokes.map((inv) => ({
|
|
3799
3853
|
src: inv.srcExpression ?? inv.src,
|
|
3800
3854
|
id: inv.invocationId ?? inv.id,
|
|
@@ -3833,7 +3887,7 @@ function graphToMachineConfig(graph, options = {}) {
|
|
|
3833
3887
|
guard: edge.data.guard ? serializeGuard(edge.data.guard, inlineGuardsAsTypeRef) : void 0,
|
|
3834
3888
|
...edge.data.context !== void 0 ? { context: edge.data.context } : void 0,
|
|
3835
3889
|
...edge.data.input !== void 0 ? { input: edge.data.input } : void 0,
|
|
3836
|
-
actions: edge.data.actions?.length ? edge.data.actions.map(
|
|
3890
|
+
actions: edge.data.actions?.length ? edge.data.actions.map(serializeAction) : void 0,
|
|
3837
3891
|
description: edge.data.description ?? void 0,
|
|
3838
3892
|
...transitionMeta ? { meta: transitionMeta } : void 0
|
|
3839
3893
|
};
|
|
@@ -4212,7 +4266,8 @@ function isShadowedWithinExpression(node, expression) {
|
|
|
4212
4266
|
}
|
|
4213
4267
|
return false;
|
|
4214
4268
|
}
|
|
4215
|
-
function replacementText(node, expression) {
|
|
4269
|
+
function replacementText(node, expression, nested = true) {
|
|
4270
|
+
if (!nested) return expression;
|
|
4216
4271
|
if (ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node) return `${node.text}: (${expression})`;
|
|
4217
4272
|
return `(${expression})`;
|
|
4218
4273
|
}
|
|
@@ -4254,7 +4309,7 @@ function resolvedExpressionText(expression, bindings, resolving = /* @__PURE__ *
|
|
|
4254
4309
|
replacements.push({
|
|
4255
4310
|
start: node.getStart(source) - start,
|
|
4256
4311
|
end: node.getEnd() - start,
|
|
4257
|
-
text: replacementText(node, resolvedExpressionText(binding, bindings, nextResolving))
|
|
4312
|
+
text: replacementText(node, resolvedExpressionText(binding, bindings, nextResolving), node !== expression)
|
|
4258
4313
|
});
|
|
4259
4314
|
return;
|
|
4260
4315
|
}
|
|
@@ -4359,6 +4414,15 @@ function schemasObjectToValue(node) {
|
|
|
4359
4414
|
}
|
|
4360
4415
|
function propertyContext(parent, key) {
|
|
4361
4416
|
const inherited = parent.imports ? { imports: parent.imports } : {};
|
|
4417
|
+
if (parent.statesMap) return {
|
|
4418
|
+
...inherited,
|
|
4419
|
+
v6: parent.v6
|
|
4420
|
+
};
|
|
4421
|
+
if (key === "states") return {
|
|
4422
|
+
...inherited,
|
|
4423
|
+
v6: parent.v6,
|
|
4424
|
+
statesMap: true
|
|
4425
|
+
};
|
|
4362
4426
|
if (parent.machineValue && (key === "actors" || key === "actorSources")) return {
|
|
4363
4427
|
...inherited,
|
|
4364
4428
|
v6: parent.v6,
|
|
@@ -4497,9 +4561,40 @@ function hashString(value) {
|
|
|
4497
4561
|
function inlineGuardCode(params, condition) {
|
|
4498
4562
|
return `${params} => ${condition.trimStart().startsWith("{") ? `(${condition})` : condition}`;
|
|
4499
4563
|
}
|
|
4564
|
+
function namedReferenceName(node, objectName) {
|
|
4565
|
+
if (!node) return null;
|
|
4566
|
+
const target = unwrapExpression(node);
|
|
4567
|
+
if (ts.isElementAccessExpression(target) && ts.isIdentifier(target.expression) && target.expression.text === objectName && ts.isStringLiteralLike(target.argumentExpression)) return target.argumentExpression.text;
|
|
4568
|
+
if (ts.isPropertyAccessExpression(target) && ts.isIdentifier(target.expression) && target.expression.text === objectName) return target.name.text;
|
|
4569
|
+
return null;
|
|
4570
|
+
}
|
|
4571
|
+
function namedReferenceParams(node, bindings) {
|
|
4572
|
+
if (!node) return {};
|
|
4573
|
+
const target = unwrapExpression(node);
|
|
4574
|
+
if (!ts.isObjectLiteralExpression(target) && !ts.isArrayLiteralExpression(target)) return {};
|
|
4575
|
+
const value = expressionToValue(target, bindings, { effectArgumentValue: true }, 0);
|
|
4576
|
+
return value && typeof value === "object" ? value : {};
|
|
4577
|
+
}
|
|
4578
|
+
function namedGuardFromCondition(text) {
|
|
4579
|
+
const [statement] = ts.createSourceFile("guard.ts", text, ts.ScriptTarget.Latest, true).statements;
|
|
4580
|
+
if (!statement || !ts.isExpressionStatement(statement)) return void 0;
|
|
4581
|
+
const call = unwrapExpression(statement.expression);
|
|
4582
|
+
if (!ts.isCallExpression(call)) return void 0;
|
|
4583
|
+
const name = namedReferenceName(call.expression, "guards");
|
|
4584
|
+
if (!name) return void 0;
|
|
4585
|
+
return {
|
|
4586
|
+
type: name,
|
|
4587
|
+
params: namedReferenceParams(call.arguments[1], /* @__PURE__ */ new Map())
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4500
4590
|
function guardFromConditions(conds, params) {
|
|
4501
4591
|
const simplified = simplifyConditions(conds);
|
|
4502
4592
|
if (simplified.length === 0) return void 0;
|
|
4593
|
+
const only = simplified.length === 1 ? simplified[0] : void 0;
|
|
4594
|
+
if (only) {
|
|
4595
|
+
const named = namedGuardFromCondition(conditionText(only));
|
|
4596
|
+
if (named) return named;
|
|
4597
|
+
}
|
|
4503
4598
|
const code = inlineGuardCode(params, simplified.map(conditionText).join(" && "));
|
|
4504
4599
|
return {
|
|
4505
4600
|
type: `inline:${hashString(code)}`,
|
|
@@ -4599,13 +4694,20 @@ function expressionStatementAction(statement, enqName, bindings) {
|
|
|
4599
4694
|
const expression = unwrapExpression(statement.expression);
|
|
4600
4695
|
if (!ts.isCallExpression(expression)) return null;
|
|
4601
4696
|
const callee = expression.expression;
|
|
4602
|
-
if (ts.isIdentifier(callee) && callee.text === enqName)
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
}
|
|
4608
|
-
|
|
4697
|
+
if (ts.isIdentifier(callee) && callee.text === enqName) {
|
|
4698
|
+
const actionName = namedReferenceName(expression.arguments[0], "actions");
|
|
4699
|
+
if (actionName) return {
|
|
4700
|
+
type: actionName,
|
|
4701
|
+
params: namedReferenceParams(expression.arguments[1], bindings)
|
|
4702
|
+
};
|
|
4703
|
+
return {
|
|
4704
|
+
type: "xstate.expr",
|
|
4705
|
+
params: {
|
|
4706
|
+
code: resolvedExpressionText(expression, bindings),
|
|
4707
|
+
lang: "ts"
|
|
4708
|
+
}
|
|
4709
|
+
};
|
|
4710
|
+
}
|
|
4609
4711
|
if (!ts.isPropertyAccessExpression(callee)) return null;
|
|
4610
4712
|
if (!ts.isIdentifier(callee.expression) || callee.expression.text !== enqName) return null;
|
|
4611
4713
|
const method = callee.name.text;
|
|
@@ -4813,6 +4915,79 @@ function normalizeFunctionTransition(fn, bindings, depth) {
|
|
|
4813
4915
|
if (transitions.length > 1) return transitions;
|
|
4814
4916
|
return fallbackFunctionTransition(fn);
|
|
4815
4917
|
}
|
|
4918
|
+
/**
|
|
4919
|
+
* XState v5 has no `choose()`, so `xstate.choose` is exported as the canonical
|
|
4920
|
+
* `enqueueActions(({ check, enqueue }) => { if (check(g)) { enqueue(a); } ... })`
|
|
4921
|
+
* if/else-if chain. Read exactly that shape back into structured branches so
|
|
4922
|
+
* the action round-trips; anything else stays an opaque inline action.
|
|
4923
|
+
*/
|
|
4924
|
+
function chooseActionFromEnqueueActions(node, bindings) {
|
|
4925
|
+
if (!ts.isIdentifier(node.expression) || node.expression.text !== "enqueueActions" || node.arguments.length !== 1) return null;
|
|
4926
|
+
const fn = unwrapExpression(node.arguments[0]);
|
|
4927
|
+
if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) return null;
|
|
4928
|
+
if (fn.parameters.length !== 1) return null;
|
|
4929
|
+
const pattern = fn.parameters[0].name;
|
|
4930
|
+
if (!ts.isObjectBindingPattern(pattern)) return null;
|
|
4931
|
+
const bound = /* @__PURE__ */ new Map();
|
|
4932
|
+
for (const element of pattern.elements) {
|
|
4933
|
+
if (element.dotDotDotToken || !ts.isIdentifier(element.name)) return null;
|
|
4934
|
+
const key = element.propertyName ? propName(element.propertyName) : element.name.text;
|
|
4935
|
+
if (!key) return null;
|
|
4936
|
+
bound.set(key, element.name.text);
|
|
4937
|
+
}
|
|
4938
|
+
const checkName = bound.get("check");
|
|
4939
|
+
const enqueueName = bound.get("enqueue");
|
|
4940
|
+
if (!checkName || !enqueueName || bound.size !== 2) return null;
|
|
4941
|
+
const body = fn.body;
|
|
4942
|
+
if (!body || !ts.isBlock(body)) return null;
|
|
4943
|
+
const enqueuedAction = (statement) => {
|
|
4944
|
+
if (!ts.isExpressionStatement(statement)) return null;
|
|
4945
|
+
const call = unwrapExpression(statement.expression);
|
|
4946
|
+
if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression) || call.expression.text !== enqueueName || call.arguments.length !== 1) return null;
|
|
4947
|
+
const value = expressionToValue(call.arguments[0], bindings, { effectArgumentValue: true }, 0);
|
|
4948
|
+
return typeof value === "string" ? { type: value } : value;
|
|
4949
|
+
};
|
|
4950
|
+
const blockActions = (statements) => {
|
|
4951
|
+
const actions = [];
|
|
4952
|
+
for (const statement of statements) {
|
|
4953
|
+
const action = enqueuedAction(statement);
|
|
4954
|
+
if (action === null) return null;
|
|
4955
|
+
actions.push(action);
|
|
4956
|
+
}
|
|
4957
|
+
return actions;
|
|
4958
|
+
};
|
|
4959
|
+
const branches = [];
|
|
4960
|
+
const readChain = (statement) => {
|
|
4961
|
+
const condition = unwrapExpression(statement.expression);
|
|
4962
|
+
if (!ts.isCallExpression(condition) || !ts.isIdentifier(condition.expression) || condition.expression.text !== checkName || condition.arguments.length !== 1) return false;
|
|
4963
|
+
if (!ts.isBlock(statement.thenStatement)) return false;
|
|
4964
|
+
const actions = blockActions(statement.thenStatement.statements);
|
|
4965
|
+
if (!actions) return false;
|
|
4966
|
+
branches.push({
|
|
4967
|
+
guard: expressionToValue(condition.arguments[0], bindings, { effectArgumentValue: true }, 0),
|
|
4968
|
+
actions
|
|
4969
|
+
});
|
|
4970
|
+
const alternate = statement.elseStatement;
|
|
4971
|
+
if (!alternate) return true;
|
|
4972
|
+
if (ts.isIfStatement(alternate)) return readChain(alternate);
|
|
4973
|
+
if (!ts.isBlock(alternate)) return false;
|
|
4974
|
+
const elseActions = blockActions(alternate.statements);
|
|
4975
|
+
if (!elseActions) return false;
|
|
4976
|
+
branches.push({ actions: elseActions });
|
|
4977
|
+
return true;
|
|
4978
|
+
};
|
|
4979
|
+
if (body.statements.length === 1 && ts.isIfStatement(body.statements[0])) {
|
|
4980
|
+
if (!readChain(body.statements[0])) return null;
|
|
4981
|
+
} else {
|
|
4982
|
+
const actions = blockActions(body.statements);
|
|
4983
|
+
if (!actions || actions.length === 0) return null;
|
|
4984
|
+
branches.push({ actions });
|
|
4985
|
+
}
|
|
4986
|
+
return {
|
|
4987
|
+
type: "xstate.choose",
|
|
4988
|
+
params: { branches }
|
|
4989
|
+
};
|
|
4990
|
+
}
|
|
4816
4991
|
function inlineAction(fn) {
|
|
4817
4992
|
return {
|
|
4818
4993
|
type: "xstate.expr",
|
|
@@ -4859,7 +5034,34 @@ function codeImportsForExpression(expression, imports) {
|
|
|
4859
5034
|
visit(file);
|
|
4860
5035
|
return found.size ? [...found.values()] : void 0;
|
|
4861
5036
|
}
|
|
4862
|
-
|
|
5037
|
+
/**
|
|
5038
|
+
* `entry: ({ actions }, enq) => { enq(actions['NAME'], params); ... }` is the
|
|
5039
|
+
* shape the exporter writes for a plain list of *named* actions, so it is
|
|
5040
|
+
* decomposed back into those actions instead of surviving as one opaque
|
|
5041
|
+
* `xstate.enqueue` code action. Direct `enq.raise(...)` / `enq(() => ...)`
|
|
5042
|
+
* bodies are deliberately left alone: they are authored code, not a list.
|
|
5043
|
+
*/
|
|
5044
|
+
function enqueuedNamedActions(fn, bindings) {
|
|
5045
|
+
const body = fn.body;
|
|
5046
|
+
if (!body || !ts.isBlock(body) || body.statements.length === 0) return null;
|
|
5047
|
+
const enqName = enqueueParamName(fn);
|
|
5048
|
+
const actions = [];
|
|
5049
|
+
for (const statement of body.statements) {
|
|
5050
|
+
if (!ts.isExpressionStatement(statement)) return null;
|
|
5051
|
+
const expression = unwrapExpression(statement.expression);
|
|
5052
|
+
if (!ts.isCallExpression(expression) || !ts.isIdentifier(expression.expression) || expression.expression.text !== enqName) return null;
|
|
5053
|
+
const name = namedReferenceName(expression.arguments[0], "actions");
|
|
5054
|
+
if (!name) return null;
|
|
5055
|
+
actions.push({
|
|
5056
|
+
type: name,
|
|
5057
|
+
params: namedReferenceParams(expression.arguments[1], bindings)
|
|
5058
|
+
});
|
|
5059
|
+
}
|
|
5060
|
+
return actions;
|
|
5061
|
+
}
|
|
5062
|
+
function normalizeFunctionAction(fn, bindings) {
|
|
5063
|
+
const actions = enqueuedNamedActions(fn, bindings);
|
|
5064
|
+
if (actions) return actions.length === 1 ? actions[0] : actions;
|
|
4863
5065
|
return enqueueAction(fn);
|
|
4864
5066
|
}
|
|
4865
5067
|
function objectLiteralToValue(node, bindings, context, depth, overrides = /* @__PURE__ */ new Map()) {
|
|
@@ -4934,6 +5136,7 @@ function expressionToValue(expression, bindings, context = {}, depth = 0) {
|
|
|
4934
5136
|
if (context.machineContextValue && (ts.isArrowFunction(node) || ts.isFunctionExpression(node))) return inlineCodeExpression(node.getText());
|
|
4935
5137
|
if (context.targetValue) {
|
|
4936
5138
|
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
5139
|
+
if (ts.isArrayLiteralExpression(node) && node.elements.length > 0 && node.elements.every((element) => ts.isStringLiteral(element) || ts.isNoSubstitutionTemplateLiteral(element))) return node.elements.map((element) => element.text);
|
|
4937
5140
|
if (ts.isIdentifier(node)) {
|
|
4938
5141
|
const binding = bindings.get(node.text);
|
|
4939
5142
|
if (binding && !ts.isFunctionDeclaration(binding)) return expressionToValue(binding, bindings, context, depth + 1);
|
|
@@ -4956,11 +5159,11 @@ function expressionToValue(expression, bindings, context = {}, depth = 0) {
|
|
|
4956
5159
|
return inlineCodeExpression(node.getText());
|
|
4957
5160
|
}
|
|
4958
5161
|
if (context.timeoutValue && (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isCallExpression(node))) return inlineCodeExpression(node.getText());
|
|
4959
|
-
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return
|
|
4960
|
-
if (ts.isNumericLiteral(node)) return
|
|
4961
|
-
if (node.kind === ts.SyntaxKind.TrueKeyword) return
|
|
4962
|
-
if (node.kind === ts.SyntaxKind.FalseKeyword) return
|
|
4963
|
-
if (node.kind === ts.SyntaxKind.NullKeyword) return
|
|
5162
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
5163
|
+
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
5164
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
5165
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
5166
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
4964
5167
|
if (context.effectArgumentValue && !ts.isObjectLiteralExpression(node) && !ts.isArrayLiteralExpression(node)) {
|
|
4965
5168
|
if (ts.isIdentifier(node) && node.text === "undefined") return void 0;
|
|
4966
5169
|
return inlineCodeExpression(resolvedExpressionText(node, bindings));
|
|
@@ -5020,7 +5223,11 @@ function expressionToValue(expression, bindings, context = {}, depth = 0) {
|
|
|
5020
5223
|
const configArg = node.arguments.length > 1 ? node.arguments[1] : node.arguments[0];
|
|
5021
5224
|
return configArg ? expressionToValue(configArg, bindings, { v6: context.v6 }, depth + 1) : {};
|
|
5022
5225
|
}
|
|
5023
|
-
if (context.actionValue)
|
|
5226
|
+
if (context.actionValue) {
|
|
5227
|
+
const choose = chooseActionFromEnqueueActions(node, bindings);
|
|
5228
|
+
if (choose) return choose;
|
|
5229
|
+
return inlineAction(node);
|
|
5230
|
+
}
|
|
5024
5231
|
const callee = node.expression.getText();
|
|
5025
5232
|
const type = callee.includes(".") ? callee.split(".").pop() : callee;
|
|
5026
5233
|
return { type: type === "assign" ? "xstate.assign" : type };
|
|
@@ -5499,7 +5706,7 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
5499
5706
|
const inferredXStateVersion = isXStateV6ProfileRef(graph.data.profile ?? "xstate-v5") ? 6 : 5;
|
|
5500
5707
|
const { exportStyle = "named", targetLanguage = "typescript", xstateVersion = inferredXStateVersion, xstateV6SchemaFormat = "types", ...configOptions } = options;
|
|
5501
5708
|
const schemas = graph.data.schemas;
|
|
5502
|
-
const impls = filterInlineImplementations(graph.data.implementations);
|
|
5709
|
+
const impls = filterInlineImplementations$1(graph.data.implementations);
|
|
5503
5710
|
const supportsTypeSyntax = targetLanguage === "typescript";
|
|
5504
5711
|
const hasSchemas = !!(schemas && (schemas.context || schemas.events || schemas.input || schemas.output));
|
|
5505
5712
|
const hasStateSchemas = graph.nodes.some((node) => node.data.schemas);
|
|
@@ -5511,8 +5718,8 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
5511
5718
|
const builtInActions = getUsedBuiltInActions(graph, xstateVersion);
|
|
5512
5719
|
const actorSourceImports = getActorSourceImports(graph);
|
|
5513
5720
|
const implementationImports = getImplementationImports(impls);
|
|
5514
|
-
const codeImports = getCodeExpressionImports(graph);
|
|
5515
|
-
const imports = buildImports({
|
|
5721
|
+
const codeImports = getCodeExpressionImports$1(graph);
|
|
5722
|
+
const imports = buildImports$1({
|
|
5516
5723
|
hasSetup,
|
|
5517
5724
|
hasActors,
|
|
5518
5725
|
actors: impls?.actors ?? [],
|
|
@@ -5524,7 +5731,10 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
5524
5731
|
usesTypes: xstateVersion === 6 && supportsTypeSyntax && (hasSchemas || hasStateSchemas) && xstateV6SchemaFormat === "types",
|
|
5525
5732
|
usesZod: xstateVersion === 6 && (hasSchemas || hasStateSchemas) && xstateV6SchemaFormat === "zod"
|
|
5526
5733
|
});
|
|
5527
|
-
const machineConfig = xstateVersion === 6 ? toV6MachineConfig(graphToMachineConfig(graph,
|
|
5734
|
+
const machineConfig = xstateVersion === 6 ? toV6MachineConfig(graphToMachineConfig(graph, {
|
|
5735
|
+
...configOptions,
|
|
5736
|
+
keepChooseStructured: true
|
|
5737
|
+
})) : toV5MachineConfig(graphToMachineConfig(graph, configOptions));
|
|
5528
5738
|
if (xstateVersion === 6) stripStateSchemas(machineConfig);
|
|
5529
5739
|
let machineExpr;
|
|
5530
5740
|
if (hasSetup) {
|
|
@@ -5601,7 +5811,7 @@ function buildV6StateSchemasBlock(schemas, options) {
|
|
|
5601
5811
|
}
|
|
5602
5812
|
return Object.keys(result).length ? result : void 0;
|
|
5603
5813
|
}
|
|
5604
|
-
function filterInlineImplementations(impls) {
|
|
5814
|
+
function filterInlineImplementations$1(impls) {
|
|
5605
5815
|
if (!impls) return impls;
|
|
5606
5816
|
return {
|
|
5607
5817
|
...impls,
|
|
@@ -5619,7 +5829,8 @@ const BUILTIN_ACTION_IMPORTS = {
|
|
|
5619
5829
|
"xstate.spawnChild": "spawnChild",
|
|
5620
5830
|
"xstate.stopChild": "stopChild",
|
|
5621
5831
|
"xstate.log": "log",
|
|
5622
|
-
"xstate.assign": "assign"
|
|
5832
|
+
"xstate.assign": "assign",
|
|
5833
|
+
"xstate.choose": "enqueueActions"
|
|
5623
5834
|
};
|
|
5624
5835
|
/** Check inline action/entry/exit code for references to xstate builtins */
|
|
5625
5836
|
function scanInlineCodeForBuiltins(code, used) {
|
|
@@ -5661,6 +5872,10 @@ function mapV6MachineConfig(value, context = {}) {
|
|
|
5661
5872
|
if (!value || typeof value !== "object") return value;
|
|
5662
5873
|
const result = {};
|
|
5663
5874
|
for (const [key, item] of Object.entries(value)) {
|
|
5875
|
+
if (key === "states") {
|
|
5876
|
+
result[key] = mapV6StatesMap(item);
|
|
5877
|
+
continue;
|
|
5878
|
+
}
|
|
5664
5879
|
if (key === "entry" || key === "exit" || key === "actions") {
|
|
5665
5880
|
result[key] = toV6ActionField(Array.isArray(item) ? item : item === void 0 ? [] : [item]);
|
|
5666
5881
|
continue;
|
|
@@ -5677,6 +5892,13 @@ function mapV6MachineConfig(value, context = {}) {
|
|
|
5677
5892
|
}
|
|
5678
5893
|
return result;
|
|
5679
5894
|
}
|
|
5895
|
+
/** Walks a `states` map, treating every entry as a state node. */
|
|
5896
|
+
function mapV6StatesMap(value) {
|
|
5897
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
5898
|
+
const result = {};
|
|
5899
|
+
for (const [key, item] of Object.entries(value)) result[key] = mapV6MachineConfig(item);
|
|
5900
|
+
return result;
|
|
5901
|
+
}
|
|
5680
5902
|
function mapV6TransitionMap(value) {
|
|
5681
5903
|
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
5682
5904
|
const result = {};
|
|
@@ -5863,6 +6085,7 @@ function buildV6TransitionFunction(transitions) {
|
|
|
5863
6085
|
});
|
|
5864
6086
|
}
|
|
5865
6087
|
if (branches.length === 0) return;
|
|
6088
|
+
if (branches.slice(0, -1).some((branch) => branch.condition === "true")) return;
|
|
5866
6089
|
const generated = branches.flatMap((branch) => [branch.condition, ...branch.body]).join("\n");
|
|
5867
6090
|
let renderedBranches = branches;
|
|
5868
6091
|
let merged = mergeV6FirstParam(authoredFirstParam, inferV6ArgBindings(generated));
|
|
@@ -5904,19 +6127,21 @@ function buildV6ActionFunction(actions, transition) {
|
|
|
5904
6127
|
return raw(`(${bindings.length ? `{ ${bindings.join(", ")} }` : "_"}, enq) => {\n${[...statements.map((statement) => ` ${formatV6Statement(statement)}`), ...returnValue ? [` return ${returnValue};`] : []].join("\n")}\n}`);
|
|
5905
6128
|
}
|
|
5906
6129
|
function formatV6Statement(statement) {
|
|
5907
|
-
|
|
6130
|
+
const trimmed = statement.trim();
|
|
6131
|
+
return trimmed.endsWith("{") || trimmed.startsWith("}") || trimmed.endsWith(";") ? statement : `${statement};`;
|
|
5908
6132
|
}
|
|
5909
6133
|
function renderV6Effects(effects, finalContext) {
|
|
5910
|
-
if (!(finalContext !== void 0 || effects.some((effect) => effect.contextValue))) return { statements: effects.flatMap((effect) => effect.statements ?? []) };
|
|
6134
|
+
if (!(finalContext !== void 0 || effects.some((effect) => effect.contextValue || effect.mutatesContext))) return { statements: effects.flatMap((effect) => effect.statements ?? []) };
|
|
5911
6135
|
const statements = ["let nextContext = context"];
|
|
5912
|
-
const inCurrentContext = (
|
|
5913
|
-
|
|
6136
|
+
const inCurrentContext = (body) => {
|
|
6137
|
+
if (body.length === 0) return;
|
|
6138
|
+
statements.push("{", " const context = nextContext", ...body.map((line) => ` ${line}`), "}");
|
|
5914
6139
|
};
|
|
5915
6140
|
for (const effect of effects) {
|
|
5916
|
-
|
|
5917
|
-
if (effect.contextValue) inCurrentContext(`nextContext = { ...nextContext, ...(${effect.contextValue}) }`);
|
|
6141
|
+
inCurrentContext(effect.statements ?? []);
|
|
6142
|
+
if (effect.contextValue) inCurrentContext([`nextContext = { ...nextContext, ...(${effect.contextValue}) }`]);
|
|
5918
6143
|
}
|
|
5919
|
-
if (finalContext !== void 0) inCurrentContext(`nextContext = { ...nextContext, ...(${finalContext}) }`);
|
|
6144
|
+
if (finalContext !== void 0) inCurrentContext([`nextContext = { ...nextContext, ...(${finalContext}) }`]);
|
|
5920
6145
|
return {
|
|
5921
6146
|
statements,
|
|
5922
6147
|
contextValue: "nextContext"
|
|
@@ -5946,6 +6171,10 @@ function mapMachineActionFields(value, mapActions) {
|
|
|
5946
6171
|
if (!value || typeof value !== "object") return value;
|
|
5947
6172
|
const result = {};
|
|
5948
6173
|
for (const [key, item] of Object.entries(value)) {
|
|
6174
|
+
if (key === "states" && item && typeof item === "object" && !Array.isArray(item)) {
|
|
6175
|
+
result[key] = Object.fromEntries(Object.entries(item).map(([stateKey, state]) => [stateKey, mapMachineActionFields(state, mapActions)]));
|
|
6176
|
+
continue;
|
|
6177
|
+
}
|
|
5949
6178
|
if (key === "entry" || key === "exit" || key === "actions") {
|
|
5950
6179
|
const wasArray = Array.isArray(item);
|
|
5951
6180
|
result[key] = mapActions(wasArray ? item : item === void 0 ? [] : [item], wasArray);
|
|
@@ -5984,6 +6213,7 @@ function actionToV6Effect(action) {
|
|
|
5984
6213
|
const type = record.type;
|
|
5985
6214
|
if (typeof type !== "string") return null;
|
|
5986
6215
|
const params = record.params && typeof record.params === "object" && !Array.isArray(record.params) ? record.params : {};
|
|
6216
|
+
const rawParams = record.params;
|
|
5987
6217
|
if (type === "xstate.raise") {
|
|
5988
6218
|
const options = actionOptions(params, ["event"]);
|
|
5989
6219
|
return { statements: [`enq.raise(${serializeJS(params.event)}${options})`] };
|
|
@@ -6000,8 +6230,49 @@ function actionToV6Effect(action) {
|
|
|
6000
6230
|
return { statements: [`enq.spawn(${serializeJS(params.src)}${options})`] };
|
|
6001
6231
|
}
|
|
6002
6232
|
if (type === "xstate.stopChild") return { statements: [`enq.stop(${serializeJS(params.actorRef)})`] };
|
|
6003
|
-
if (type === "xstate.
|
|
6004
|
-
|
|
6233
|
+
if (type === "xstate.choose") {
|
|
6234
|
+
const branches = Array.isArray(params.branches) ? params.branches : [];
|
|
6235
|
+
const statements = [];
|
|
6236
|
+
let mutatesContext = false;
|
|
6237
|
+
let opened = false;
|
|
6238
|
+
for (const raw of branches) {
|
|
6239
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
6240
|
+
const branch = raw;
|
|
6241
|
+
const effects = (Array.isArray(branch.actions) ? branch.actions : []).map(actionToV6Effect);
|
|
6242
|
+
if (effects.some((effect) => !effect)) return null;
|
|
6243
|
+
const body = effects.flatMap((effect) => {
|
|
6244
|
+
const lines = [...effect.statements ?? []];
|
|
6245
|
+
if (effect.mutatesContext) mutatesContext = true;
|
|
6246
|
+
if (effect.contextValue) {
|
|
6247
|
+
mutatesContext = true;
|
|
6248
|
+
lines.push(`nextContext = { ...nextContext, ...(${effect.contextValue}) }`);
|
|
6249
|
+
}
|
|
6250
|
+
return lines;
|
|
6251
|
+
});
|
|
6252
|
+
if (branch.guard === void 0) {
|
|
6253
|
+
if (opened) {
|
|
6254
|
+
statements.push("} else {");
|
|
6255
|
+
statements.push(...body.map((line) => ` ${line}`));
|
|
6256
|
+
statements.push("}");
|
|
6257
|
+
} else statements.push(...body);
|
|
6258
|
+
opened = false;
|
|
6259
|
+
break;
|
|
6260
|
+
}
|
|
6261
|
+
const guard = guardCondition(typeof branch.guard === "string" ? { type: branch.guard } : branch.guard);
|
|
6262
|
+
if (!guard) return null;
|
|
6263
|
+
statements.push(`${opened ? "} else if" : "if"} (${guard.condition}) {`);
|
|
6264
|
+
statements.push(...body.map((line) => ` ${line}`));
|
|
6265
|
+
opened = true;
|
|
6266
|
+
}
|
|
6267
|
+
if (opened) statements.push("}");
|
|
6268
|
+
if (statements.length === 0) return null;
|
|
6269
|
+
return mutatesContext ? {
|
|
6270
|
+
statements,
|
|
6271
|
+
mutatesContext
|
|
6272
|
+
} : { statements };
|
|
6273
|
+
}
|
|
6274
|
+
if (type === "xstate.assign") return { contextValue: serializeJS(unwrapTemplateExpressions(params.assignment ?? params)) };
|
|
6275
|
+
if (!type.startsWith("xstate.")) return { statements: [`enq(${/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(type) ? `actions.${type}` : `actions[${serializeJS(type)}]`}${(Array.isArray(rawParams) ? rawParams.length > 0 : Object.keys(params).length > 0) ? `, ${serializeJS(rawParams)}` : ""})`] };
|
|
6005
6276
|
return null;
|
|
6006
6277
|
}
|
|
6007
6278
|
function getV6AssignmentArgs(code, includeAll = false) {
|
|
@@ -6061,7 +6332,7 @@ function actionOptions(params, consumedKeys) {
|
|
|
6061
6332
|
const rest = Object.fromEntries(Object.entries(params).filter(([key, value]) => !consumedKeys.includes(key) && value !== void 0));
|
|
6062
6333
|
return Object.keys(rest).length ? `, ${serializeJS(rest)}` : "";
|
|
6063
6334
|
}
|
|
6064
|
-
function buildImports({ hasSetup, hasActors, actors, builtInActions, actorSourceImports, implementationImports, codeImports, xstateVersion, usesTypes, usesZod }) {
|
|
6335
|
+
function buildImports$1({ hasSetup, hasActors, actors, builtInActions, actorSourceImports, implementationImports, codeImports, xstateVersion, usesTypes, usesZod }) {
|
|
6065
6336
|
const xstateImports = [];
|
|
6066
6337
|
if (hasSetup) xstateImports.push("setup");
|
|
6067
6338
|
else xstateImports.push("createMachine");
|
|
@@ -6089,7 +6360,7 @@ function buildImports({ hasSetup, hasActors, actors, builtInActions, actorSource
|
|
|
6089
6360
|
if (usesZod && !externalImports.get("zod")?.has("z")) imports.push("import { z } from 'zod';");
|
|
6090
6361
|
return imports.join("\n");
|
|
6091
6362
|
}
|
|
6092
|
-
function getCodeExpressionImports(graph) {
|
|
6363
|
+
function getCodeExpressionImports$1(graph) {
|
|
6093
6364
|
const imports = /* @__PURE__ */ new Map();
|
|
6094
6365
|
function visit(value) {
|
|
6095
6366
|
if (!value || typeof value !== "object") return;
|
|
@@ -6238,7 +6509,7 @@ function hasSchemaProperties(schema) {
|
|
|
6238
6509
|
function buildActionsBlock(actions, options) {
|
|
6239
6510
|
const block = {};
|
|
6240
6511
|
for (const implementation of actions) if (implementation.code?.body) {
|
|
6241
|
-
const inlineExpression = getInlineExpression(implementation.code.body);
|
|
6512
|
+
const inlineExpression = getInlineExpression$1(implementation.code.body);
|
|
6242
6513
|
if (inlineExpression) {
|
|
6243
6514
|
block[implementation.name] = raw(inlineExpression);
|
|
6244
6515
|
continue;
|
|
@@ -6251,7 +6522,7 @@ function buildActionsBlock(actions, options) {
|
|
|
6251
6522
|
function buildGuardsBlock(guards, options) {
|
|
6252
6523
|
const block = {};
|
|
6253
6524
|
for (const guard of guards) if (guard.code?.body) {
|
|
6254
|
-
const inlineExpression = getInlineExpression(guard.code.body);
|
|
6525
|
+
const inlineExpression = getInlineExpression$1(guard.code.body);
|
|
6255
6526
|
if (inlineExpression) {
|
|
6256
6527
|
block[guard.name] = raw(inlineExpression);
|
|
6257
6528
|
continue;
|
|
@@ -6289,7 +6560,7 @@ function getActorLogicExpression(code) {
|
|
|
6289
6560
|
function buildDelaysBlock(delays) {
|
|
6290
6561
|
const block = {};
|
|
6291
6562
|
for (const delay of delays) if (delay.code?.body) {
|
|
6292
|
-
const inlineExpression = getInlineExpression(delay.code.body);
|
|
6563
|
+
const inlineExpression = getInlineExpression$1(delay.code.body);
|
|
6293
6564
|
if (inlineExpression) {
|
|
6294
6565
|
block[delay.name] = raw(inlineExpression);
|
|
6295
6566
|
continue;
|
|
@@ -6312,7 +6583,7 @@ function buildDelaysBlock(delays) {
|
|
|
6312
6583
|
* on import) → the expression itself.
|
|
6313
6584
|
* Returns undefined for a plain statement body, which still needs wrapping.
|
|
6314
6585
|
*/
|
|
6315
|
-
function getInlineExpression(code) {
|
|
6586
|
+
function getInlineExpression$1(code) {
|
|
6316
6587
|
const stripped = stripExportDefault(code);
|
|
6317
6588
|
if (stripped !== code.trim()) return stripped;
|
|
6318
6589
|
return isCompleteFunctionExpression(stripped) || isCompleteXStateHelperExpression(stripped) ? stripped : void 0;
|
|
@@ -6348,6 +6619,624 @@ function isCompleteFunctionExpression(code) {
|
|
|
6348
6619
|
if (!expression) return false;
|
|
6349
6620
|
return ts.isFunctionExpression(expression) || ts.isArrowFunction(expression);
|
|
6350
6621
|
}
|
|
6622
|
+
/**
|
|
6623
|
+
* Escapes text that is interpolated into a `/* ... *\/` marker so it cannot
|
|
6624
|
+
* close the comment early.
|
|
6625
|
+
*/
|
|
6626
|
+
function escapeMarkerText(text) {
|
|
6627
|
+
return text.replace(/\*\//g, "*\\/");
|
|
6628
|
+
}
|
|
6629
|
+
/** Builds a `TODO: v5-only` block comment around untrusted text. */
|
|
6630
|
+
function v5OnlyMarker(detail) {
|
|
6631
|
+
return `/* TODO: v5-only (${escapeMarkerText(detail)}); no XState v4 equivalent */`;
|
|
6632
|
+
}
|
|
6633
|
+
/**
|
|
6634
|
+
* Emits XState **v4** (deprecated) TypeScript for a graph.
|
|
6635
|
+
*
|
|
6636
|
+
* The v4 surface differs from v5 in ways this emitter downgrades:
|
|
6637
|
+
* `createMachine(config, options)` instead of `setup().createMachine()`,
|
|
6638
|
+
* `cond` instead of `guard`, `services` instead of `actors`, `internal: false`
|
|
6639
|
+
* instead of `reenter: true`, `done.invoke.X` / `error.platform.X` /
|
|
6640
|
+
* `done.state.X` event names, and positional `(context, event)` implementation
|
|
6641
|
+
* arguments (enabled by `predictableActionArguments`).
|
|
6642
|
+
*
|
|
6643
|
+
* Concepts with no v4 equivalent (`emit`, `spawnChild`, `enqueueActions`,
|
|
6644
|
+
* actor-logic creators such as `fromPromise`) are emitted verbatim with a
|
|
6645
|
+
* `// TODO: v5-only` marker rather than silently dropped.
|
|
6646
|
+
*/
|
|
6647
|
+
function graphToXStateV4TS(graph, options = {}) {
|
|
6648
|
+
const { exportStyle = "named", targetLanguage = "typescript", ...configOptions } = options;
|
|
6649
|
+
const supportsTypeSyntax = targetLanguage === "typescript";
|
|
6650
|
+
const schemas = graph.data.schemas;
|
|
6651
|
+
const impls = filterInlineImplementations(graph.data.implementations);
|
|
6652
|
+
const rawConfig = graphToMachineConfig(graph, configOptions);
|
|
6653
|
+
const services = {};
|
|
6654
|
+
const config = transformState(rawConfig, { services });
|
|
6655
|
+
const machineConfig = {};
|
|
6656
|
+
if (config.id !== void 0) machineConfig.id = config.id;
|
|
6657
|
+
machineConfig.predictableActionArguments = true;
|
|
6658
|
+
machineConfig.preserveActionOrder = true;
|
|
6659
|
+
const schemaBlock = supportsTypeSyntax ? buildSchemaBlock(schemas) : void 0;
|
|
6660
|
+
if (schemaBlock) machineConfig.schema = schemaBlock;
|
|
6661
|
+
for (const [key, value] of Object.entries(config)) {
|
|
6662
|
+
if (key === "id") continue;
|
|
6663
|
+
machineConfig[key] = value;
|
|
6664
|
+
}
|
|
6665
|
+
const machineOptions = buildMachineOptions(impls, services);
|
|
6666
|
+
const configStr = serializeJS(machineConfig, 0);
|
|
6667
|
+
const optionsStr = Object.keys(machineOptions).length ? serializeJS(machineOptions, 0) : void 0;
|
|
6668
|
+
const machineExpr = optionsStr ? `createMachine(${configStr}, ${optionsStr})` : `createMachine(${configStr})`;
|
|
6669
|
+
const lines = [
|
|
6670
|
+
buildImports(machineExpr, getCodeExpressionImports(graph)),
|
|
6671
|
+
"",
|
|
6672
|
+
`const machine = ${machineExpr};`
|
|
6673
|
+
];
|
|
6674
|
+
if (exportStyle === "named") lines.push("", "export { machine };");
|
|
6675
|
+
else if (exportStyle === "default") lines.push("", "export default machine;");
|
|
6676
|
+
return lines.join("\n") + "\n";
|
|
6677
|
+
}
|
|
6678
|
+
/** Keys emitted by the shared config builder that v4 has no place for. */
|
|
6679
|
+
const DROPPED_STATE_KEYS = new Set([
|
|
6680
|
+
"schemas",
|
|
6681
|
+
"timeout",
|
|
6682
|
+
"route",
|
|
6683
|
+
"onTimeout",
|
|
6684
|
+
"onSnapshot",
|
|
6685
|
+
"version",
|
|
6686
|
+
"options"
|
|
6687
|
+
]);
|
|
6688
|
+
function transformState(state, ctx, sourceKey) {
|
|
6689
|
+
const result = {};
|
|
6690
|
+
let choiceBranches;
|
|
6691
|
+
for (const [key, value] of Object.entries(state)) {
|
|
6692
|
+
if (value === void 0 || DROPPED_STATE_KEYS.has(key)) continue;
|
|
6693
|
+
switch (key) {
|
|
6694
|
+
case "type":
|
|
6695
|
+
if (value !== "choice") result.type = value;
|
|
6696
|
+
break;
|
|
6697
|
+
case "choice":
|
|
6698
|
+
choiceBranches = transformTransitionValue(value, sourceKey);
|
|
6699
|
+
break;
|
|
6700
|
+
case "initial":
|
|
6701
|
+
result.initial = value && typeof value === "object" && !Array.isArray(value) ? value.target : value;
|
|
6702
|
+
break;
|
|
6703
|
+
case "output":
|
|
6704
|
+
result.data = transformCodeValues(value);
|
|
6705
|
+
break;
|
|
6706
|
+
case "entry":
|
|
6707
|
+
case "exit":
|
|
6708
|
+
result[key] = transformActions(value);
|
|
6709
|
+
break;
|
|
6710
|
+
case "invoke":
|
|
6711
|
+
result.invoke = transformInvoke(value, ctx, sourceKey);
|
|
6712
|
+
break;
|
|
6713
|
+
case "on":
|
|
6714
|
+
case "after":
|
|
6715
|
+
result[key] = transformTransitionMap(value, key === "on", sourceKey);
|
|
6716
|
+
break;
|
|
6717
|
+
case "always":
|
|
6718
|
+
case "onDone":
|
|
6719
|
+
case "onError":
|
|
6720
|
+
result[key] = transformTransitionValue(value, sourceKey);
|
|
6721
|
+
break;
|
|
6722
|
+
case "states": {
|
|
6723
|
+
const states = value;
|
|
6724
|
+
result.states = Object.fromEntries(Object.entries(states).map(([stateKey, child]) => [stateKey, transformState(child, ctx, stateKey)]));
|
|
6725
|
+
break;
|
|
6726
|
+
}
|
|
6727
|
+
default: result[key] = value;
|
|
6728
|
+
}
|
|
6729
|
+
}
|
|
6730
|
+
if (choiceBranches !== void 0) {
|
|
6731
|
+
const existing = result.always === void 0 ? [] : Array.isArray(result.always) ? result.always : [result.always];
|
|
6732
|
+
const branches = Array.isArray(choiceBranches) ? choiceBranches : [choiceBranches];
|
|
6733
|
+
result.always = [...existing, ...branches];
|
|
6734
|
+
}
|
|
6735
|
+
return result;
|
|
6736
|
+
}
|
|
6737
|
+
/** Rewrites embedded code expressions of a plain data value for v4. */
|
|
6738
|
+
function transformCodeValues(value) {
|
|
6739
|
+
if (isCodeExpression$3(value)) return raw(toV4Code(value.expr));
|
|
6740
|
+
if (Array.isArray(value)) return value.map(transformCodeValues);
|
|
6741
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, transformCodeValues(item)]));
|
|
6742
|
+
return value;
|
|
6743
|
+
}
|
|
6744
|
+
/** v5 event names → their v4 spellings. */
|
|
6745
|
+
function toV4EventName(eventName) {
|
|
6746
|
+
if (eventName.startsWith("xstate.done.actor.")) return `done.invoke.${eventName.slice(18)}`;
|
|
6747
|
+
if (eventName.startsWith("xstate.error.actor.")) return `error.platform.${eventName.slice(19)}`;
|
|
6748
|
+
if (eventName.startsWith("xstate.done.state.")) return `done.state.${eventName.slice(18)}`;
|
|
6749
|
+
return eventName;
|
|
6750
|
+
}
|
|
6751
|
+
function transformTransitionMap(value, renameEvents, sourceKey) {
|
|
6752
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
6753
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [renameEvents ? toV4EventName(key) : key, transformTransitionValue(item, sourceKey)]));
|
|
6754
|
+
}
|
|
6755
|
+
/**
|
|
6756
|
+
* True when a resolved v4 target points at the source state itself or one of
|
|
6757
|
+
* its descendants — the only targets for which `internal` changes behaviour.
|
|
6758
|
+
* Targets resolve against the source's parent, so a descendant of the source
|
|
6759
|
+
* is spelled `<sourceKey>.<child>` (or `.<child>` for root transitions).
|
|
6760
|
+
*/
|
|
6761
|
+
function targetsSelfOrDescendant(target, sourceKey) {
|
|
6762
|
+
if (typeof target !== "string" || !target) return false;
|
|
6763
|
+
if (sourceKey === void 0) return target.startsWith(".");
|
|
6764
|
+
return target === sourceKey || target.startsWith(`${sourceKey}.`);
|
|
6765
|
+
}
|
|
6766
|
+
/** Renders a v6 `matches` guard as readable text for the TODO marker. */
|
|
6767
|
+
function describeMatches(matches) {
|
|
6768
|
+
if (typeof matches === "string") return matches;
|
|
6769
|
+
if (isCodeExpression$3(matches)) return matches.expr;
|
|
6770
|
+
try {
|
|
6771
|
+
return serializeJS(matches, 0).replace(/\s+/g, " ");
|
|
6772
|
+
} catch {
|
|
6773
|
+
return String(matches);
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
function transformTransitionValue(value, sourceKey) {
|
|
6777
|
+
if (Array.isArray(value)) return value.map((item) => transformTransitionValue(item, sourceKey));
|
|
6778
|
+
if (!value || typeof value !== "object") return value;
|
|
6779
|
+
if (isCodeExpression$3(value)) return raw(toV4Code(value.expr));
|
|
6780
|
+
const transition = value;
|
|
6781
|
+
const result = {};
|
|
6782
|
+
let reenter;
|
|
6783
|
+
let contextUpdate;
|
|
6784
|
+
let matches;
|
|
6785
|
+
for (const [key, item] of Object.entries(transition)) {
|
|
6786
|
+
if (item === void 0) continue;
|
|
6787
|
+
switch (key) {
|
|
6788
|
+
case "guard":
|
|
6789
|
+
result.cond = transformGuard(item);
|
|
6790
|
+
break;
|
|
6791
|
+
case "reenter":
|
|
6792
|
+
reenter = item;
|
|
6793
|
+
break;
|
|
6794
|
+
case "actions":
|
|
6795
|
+
result.actions = transformActions(item);
|
|
6796
|
+
break;
|
|
6797
|
+
case "context":
|
|
6798
|
+
contextUpdate = item;
|
|
6799
|
+
break;
|
|
6800
|
+
case "matches":
|
|
6801
|
+
matches = item;
|
|
6802
|
+
break;
|
|
6803
|
+
case "input": break;
|
|
6804
|
+
default: result[key] = item;
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
if (reenter === true) result.internal = false;
|
|
6808
|
+
else if (targetsSelfOrDescendant(result.target, sourceKey)) result.internal = true;
|
|
6809
|
+
if (matches !== void 0) {
|
|
6810
|
+
const marker = v5OnlyMarker(`matches guard: ${describeMatches(matches)}`);
|
|
6811
|
+
result.cond = result.cond === void 0 ? raw(`undefined ${marker}`) : raw(`${serializeJS(result.cond, 0)} ${marker}`);
|
|
6812
|
+
}
|
|
6813
|
+
if (contextUpdate !== void 0) {
|
|
6814
|
+
const assignAction = raw(`assign(${serializeJS(transformCodeValues(contextUpdate), 0)})`);
|
|
6815
|
+
result.actions = [...result.actions === void 0 ? [] : Array.isArray(result.actions) ? result.actions : [result.actions], assignAction];
|
|
6816
|
+
}
|
|
6817
|
+
return result;
|
|
6818
|
+
}
|
|
6819
|
+
/** v4 reads parameters off the action/cond object itself, so they are spread. */
|
|
6820
|
+
function toV4ParameterizedObject(value) {
|
|
6821
|
+
const { type, params } = value;
|
|
6822
|
+
if (typeof type !== "string") return value;
|
|
6823
|
+
if (!params || typeof params !== "object") return type;
|
|
6824
|
+
const v4Params = transformCodeValues(params);
|
|
6825
|
+
const name = "type" in v4Params ? raw(`'${type}' ${v5OnlyMarker(`param named \`type\` collides with the ${JSON.stringify(type)} name`)}`) : type;
|
|
6826
|
+
return {
|
|
6827
|
+
...v4Params,
|
|
6828
|
+
type: name
|
|
6829
|
+
};
|
|
6830
|
+
}
|
|
6831
|
+
function transformGuard(guard) {
|
|
6832
|
+
if (isCodeExpression$3(guard)) return raw(toV4Code(guard.expr));
|
|
6833
|
+
if (guard && typeof guard === "object" && !Array.isArray(guard)) return toV4ParameterizedObject(guard);
|
|
6834
|
+
return guard;
|
|
6835
|
+
}
|
|
6836
|
+
function transformActions(value) {
|
|
6837
|
+
const wasArray = Array.isArray(value);
|
|
6838
|
+
const mapped = (wasArray ? value : [value]).map(transformAction);
|
|
6839
|
+
return wasArray ? mapped : mapped[0];
|
|
6840
|
+
}
|
|
6841
|
+
function transformAction(action) {
|
|
6842
|
+
if (isCodeExpression$3(action)) return raw(toV4Code(action.expr));
|
|
6843
|
+
if (action && typeof action === "object" && !Array.isArray(action)) return toV4ParameterizedObject(action);
|
|
6844
|
+
return action;
|
|
6845
|
+
}
|
|
6846
|
+
function transformInvoke(value, ctx, sourceKey) {
|
|
6847
|
+
if (Array.isArray(value)) return value.map((item) => transformInvoke(item, ctx, sourceKey));
|
|
6848
|
+
if (!value || typeof value !== "object") return value;
|
|
6849
|
+
const invoke = value;
|
|
6850
|
+
const result = {};
|
|
6851
|
+
for (const [key, item] of Object.entries(invoke)) {
|
|
6852
|
+
if (item === void 0) continue;
|
|
6853
|
+
switch (key) {
|
|
6854
|
+
case "src":
|
|
6855
|
+
if (isCodeExpression$3(item)) {
|
|
6856
|
+
const name = typeof invoke.id === "string" && invoke.id ? invoke.id : `service${Object.keys(ctx.services).length + 1}`;
|
|
6857
|
+
ctx.services[name] = raw(toV4Code(item.expr));
|
|
6858
|
+
result.src = name;
|
|
6859
|
+
} else result.src = item;
|
|
6860
|
+
break;
|
|
6861
|
+
case "input":
|
|
6862
|
+
result.data = transformCodeValues(item);
|
|
6863
|
+
break;
|
|
6864
|
+
case "onDone":
|
|
6865
|
+
case "onError":
|
|
6866
|
+
result[key] = transformTransitionValue(item, sourceKey);
|
|
6867
|
+
break;
|
|
6868
|
+
case "output":
|
|
6869
|
+
case "timeout":
|
|
6870
|
+
case "registryKey":
|
|
6871
|
+
case "onSnapshot":
|
|
6872
|
+
case "onTimeout": break;
|
|
6873
|
+
default: result[key] = item;
|
|
6874
|
+
}
|
|
6875
|
+
}
|
|
6876
|
+
return result;
|
|
6877
|
+
}
|
|
6878
|
+
/** v5 helpers that exist in v4 under a different name. */
|
|
6879
|
+
const RENAMED_HELPERS = { stopChild: "stop" };
|
|
6880
|
+
/**
|
|
6881
|
+
* v4 helpers that are not root exports of `xstate@4`; they live on the
|
|
6882
|
+
* `actions` namespace export.
|
|
6883
|
+
*/
|
|
6884
|
+
const V4_ACTIONS_NAMESPACE_HELPERS = ["respond", "escalate"];
|
|
6885
|
+
/** v5-only helpers with no v4 equivalent. */
|
|
6886
|
+
const V5_ONLY_HELPERS = [
|
|
6887
|
+
"emit",
|
|
6888
|
+
"spawnChild",
|
|
6889
|
+
"enqueueActions",
|
|
6890
|
+
"fromPromise",
|
|
6891
|
+
"fromCallback",
|
|
6892
|
+
"fromObservable",
|
|
6893
|
+
"fromEventObservable",
|
|
6894
|
+
"fromTransition",
|
|
6895
|
+
"createAsyncLogic"
|
|
6896
|
+
];
|
|
6897
|
+
/**
|
|
6898
|
+
* Bindings the emitter re-binds itself, so dropping them from a destructured
|
|
6899
|
+
* parameter list is not a loss the user has to act on.
|
|
6900
|
+
*/
|
|
6901
|
+
const REBOUND_BINDINGS = new Set(["params", "input"]);
|
|
6902
|
+
/** Bindings v4 passes positionally, so they survive the rewrite. */
|
|
6903
|
+
const V4_BOUND_BINDINGS = new Set(["context", "event"]);
|
|
6904
|
+
/**
|
|
6905
|
+
* Names that mark a destructured parameter list as a v5 implementation
|
|
6906
|
+
* signature rather than an object literal argument.
|
|
6907
|
+
*/
|
|
6908
|
+
const V5_DESTRUCTURED_BINDINGS = new Set([
|
|
6909
|
+
"context",
|
|
6910
|
+
"event",
|
|
6911
|
+
"params",
|
|
6912
|
+
"input",
|
|
6913
|
+
"self",
|
|
6914
|
+
"system",
|
|
6915
|
+
"enqueue",
|
|
6916
|
+
"spawn",
|
|
6917
|
+
"emit",
|
|
6918
|
+
"check",
|
|
6919
|
+
"signal"
|
|
6920
|
+
]);
|
|
6921
|
+
/** Parses a snippet permissively so positions can be read off the AST. */
|
|
6922
|
+
function parseSnippet(source) {
|
|
6923
|
+
return ts.createSourceFile("snippet.tsx", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
6924
|
+
}
|
|
6925
|
+
/** True when the snippet parsed without syntax errors. */
|
|
6926
|
+
function parsedCleanly(sourceFile) {
|
|
6927
|
+
const diagnostics = sourceFile.parseDiagnostics;
|
|
6928
|
+
return !diagnostics || diagnostics.length === 0;
|
|
6929
|
+
}
|
|
6930
|
+
/**
|
|
6931
|
+
* The positional v4 parameter list for a v5 destructured binding list, or
|
|
6932
|
+
* `undefined` when the bindings are not a v5 implementation signature.
|
|
6933
|
+
*/
|
|
6934
|
+
function v4ParameterList(names) {
|
|
6935
|
+
if (!names.length) return void 0;
|
|
6936
|
+
if (!names.some((name) => V5_DESTRUCTURED_BINDINGS.has(name))) return;
|
|
6937
|
+
if (names.includes("context")) return names.includes("event") ? "context, event" : "context";
|
|
6938
|
+
return names.includes("event") ? "_, event" : "";
|
|
6939
|
+
}
|
|
6940
|
+
/**
|
|
6941
|
+
* Rewrites v5 destructured *parameter* lists (`({ context, event }) => ...`)
|
|
6942
|
+
* to positional v4 ones. Only parameter positions of functions and arrows are
|
|
6943
|
+
* touched, so a shorthand object *argument* such as `send({ context, event })`
|
|
6944
|
+
* is left alone. Bindings v4 cannot supply are collected into `dropped`.
|
|
6945
|
+
*/
|
|
6946
|
+
function rewriteDestructuredParameters(code, dropped) {
|
|
6947
|
+
const sourceFile = parseSnippet(code);
|
|
6948
|
+
const edits = [];
|
|
6949
|
+
const visit = (node) => {
|
|
6950
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
|
|
6951
|
+
const [parameter] = node.parameters;
|
|
6952
|
+
if (parameter && node.parameters.length === 1 && !parameter.type && !parameter.initializer && !parameter.dotDotDotToken && ts.isObjectBindingPattern(parameter.name)) {
|
|
6953
|
+
const elements = parameter.name.elements;
|
|
6954
|
+
if (elements.every((element) => !element.propertyName && !element.initializer && !element.dotDotDotToken && ts.isIdentifier(element.name))) {
|
|
6955
|
+
const names = elements.map((element) => element.name.getText());
|
|
6956
|
+
const replacement = v4ParameterList(names);
|
|
6957
|
+
if (replacement !== void 0) {
|
|
6958
|
+
for (const name of names) if (!V4_BOUND_BINDINGS.has(name) && !REBOUND_BINDINGS.has(name)) dropped.add(name);
|
|
6959
|
+
edits.push({
|
|
6960
|
+
start: parameter.getStart(sourceFile),
|
|
6961
|
+
end: parameter.getEnd(),
|
|
6962
|
+
text: replacement
|
|
6963
|
+
});
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
ts.forEachChild(node, visit);
|
|
6969
|
+
};
|
|
6970
|
+
ts.forEachChild(sourceFile, visit);
|
|
6971
|
+
let result = code;
|
|
6972
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
6973
|
+
return result;
|
|
6974
|
+
}
|
|
6975
|
+
/**
|
|
6976
|
+
* Rewrites a v5 implementation expression for v4: positional implementation
|
|
6977
|
+
* arguments, renamed helpers, and a marker on anything v4 cannot express.
|
|
6978
|
+
*/
|
|
6979
|
+
function toV4Code(code) {
|
|
6980
|
+
const dropped = /* @__PURE__ */ new Set();
|
|
6981
|
+
let result = rewriteDestructuredParameters(stripExportDefault(code), dropped);
|
|
6982
|
+
for (const [from, to] of Object.entries(RENAMED_HELPERS)) result = result.replace(new RegExp(`\\b${from}\\(`, "g"), `${to}(`);
|
|
6983
|
+
for (const helper of V4_ACTIONS_NAMESPACE_HELPERS) result = result.replace(new RegExp(`(?<!\\.)\\b${helper}\\(`, "g"), `actions.${helper}(`);
|
|
6984
|
+
const unsupported = [...V5_ONLY_HELPERS.filter((helper) => new RegExp(`\\b${helper}\\s*\\(`).test(result)), ...dropped];
|
|
6985
|
+
if (unsupported.length) result = `${result} ${v5OnlyMarker(unsupported.join(", "))}`;
|
|
6986
|
+
return result;
|
|
6987
|
+
}
|
|
6988
|
+
/**
|
|
6989
|
+
* True when a body is a single expression, so `return` can be prefixed. Parsed
|
|
6990
|
+
* as `(body)`: a statement such as `if (x) run()` fails to parse that way, and
|
|
6991
|
+
* an object literal such as `{ a: 1 }` parses as an expression rather than a
|
|
6992
|
+
* block.
|
|
6993
|
+
*/
|
|
6994
|
+
function isBareExpression(body) {
|
|
6995
|
+
const trimmed = body.trim();
|
|
6996
|
+
if (!trimmed) return false;
|
|
6997
|
+
const sourceFile = parseSnippet(`(${trimmed})`);
|
|
6998
|
+
if (!parsedCleanly(sourceFile)) return false;
|
|
6999
|
+
if (sourceFile.statements.length !== 1) return false;
|
|
7000
|
+
const statement = sourceFile.statements[0];
|
|
7001
|
+
return !!statement && ts.isExpressionStatement(statement) && ts.isParenthesizedExpression(statement.expression);
|
|
7002
|
+
}
|
|
7003
|
+
/** True when an implementation body reads a re-bound v5 binding. */
|
|
7004
|
+
function referencesBinding(body, name) {
|
|
7005
|
+
return new RegExp(`\\b${name}\\b`).test(body);
|
|
7006
|
+
}
|
|
7007
|
+
/**
|
|
7008
|
+
* True when `name` is referenced free in a block body: it is read somewhere
|
|
7009
|
+
* that no enclosing declaration inside the body binds. Declarations tracked
|
|
7010
|
+
* are variable declarations, function/arrow/catch parameters, function and
|
|
7011
|
+
* class names, so a nested `(params) => params.id` shadows without suppressing
|
|
7012
|
+
* an outer read.
|
|
7013
|
+
*/
|
|
7014
|
+
function referencesFreeBinding(body, name) {
|
|
7015
|
+
const sourceFile = parseSnippet(body);
|
|
7016
|
+
const scopes = [/* @__PURE__ */ new Set()];
|
|
7017
|
+
let found = false;
|
|
7018
|
+
const declare = (bindingName) => {
|
|
7019
|
+
if (ts.isIdentifier(bindingName)) {
|
|
7020
|
+
scopes[scopes.length - 1]?.add(bindingName.text);
|
|
7021
|
+
return;
|
|
7022
|
+
}
|
|
7023
|
+
for (const element of bindingName.elements) if (ts.isBindingElement(element)) declare(element.name);
|
|
7024
|
+
};
|
|
7025
|
+
const isDeclared = () => scopes.some((scope) => scope.has(name));
|
|
7026
|
+
/** True when an identifier node is a value reference, not a name position. */
|
|
7027
|
+
const isReference = (node) => {
|
|
7028
|
+
const parent = node.parent;
|
|
7029
|
+
if (!parent) return true;
|
|
7030
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === node) return false;
|
|
7031
|
+
if (ts.isQualifiedName(parent) && parent.right === node) return false;
|
|
7032
|
+
if ((ts.isPropertyAssignment(parent) || ts.isMethodDeclaration(parent) || ts.isPropertyDeclaration(parent) || ts.isEnumMember(parent)) && parent.name === node) return false;
|
|
7033
|
+
if (ts.isBindingElement(parent) && parent.propertyName === node) return false;
|
|
7034
|
+
if (ts.isJsxAttribute(parent) && parent.name === node) return false;
|
|
7035
|
+
if (ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent)) return false;
|
|
7036
|
+
if ((ts.isVariableDeclaration(parent) || ts.isParameter(parent) || ts.isFunctionDeclaration(parent) || ts.isFunctionExpression(parent) || ts.isClassDeclaration(parent) || ts.isClassExpression(parent) || ts.isBindingElement(parent)) && parent.name === node) return false;
|
|
7037
|
+
if (ts.isLabeledStatement(parent) && parent.label === node) return false;
|
|
7038
|
+
return true;
|
|
7039
|
+
};
|
|
7040
|
+
const withScope = (run) => {
|
|
7041
|
+
scopes.push(/* @__PURE__ */ new Set());
|
|
7042
|
+
run();
|
|
7043
|
+
scopes.pop();
|
|
7044
|
+
};
|
|
7045
|
+
const visit = (node) => {
|
|
7046
|
+
if (found) return;
|
|
7047
|
+
if (ts.isIdentifier(node)) {
|
|
7048
|
+
if (node.text === name && isReference(node) && !isDeclared()) found = true;
|
|
7049
|
+
return;
|
|
7050
|
+
}
|
|
7051
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node) || ts.isConstructorDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node)) {
|
|
7052
|
+
if (ts.isFunctionDeclaration(node) && node.name) declare(node.name);
|
|
7053
|
+
withScope(() => {
|
|
7054
|
+
for (const parameter of node.parameters) declare(parameter.name);
|
|
7055
|
+
if (ts.isFunctionExpression(node) && node.name) declare(node.name);
|
|
7056
|
+
for (const parameter of node.parameters) if (parameter.initializer) visit(parameter.initializer);
|
|
7057
|
+
if (node.body) visit(node.body);
|
|
7058
|
+
});
|
|
7059
|
+
return;
|
|
7060
|
+
}
|
|
7061
|
+
if (ts.isBlock(node) || ts.isCaseBlock(node)) {
|
|
7062
|
+
withScope(() => ts.forEachChild(node, visit));
|
|
7063
|
+
return;
|
|
7064
|
+
}
|
|
7065
|
+
if (ts.isCatchClause(node)) {
|
|
7066
|
+
withScope(() => {
|
|
7067
|
+
if (node.variableDeclaration) declare(node.variableDeclaration.name);
|
|
7068
|
+
visit(node.block);
|
|
7069
|
+
});
|
|
7070
|
+
return;
|
|
7071
|
+
}
|
|
7072
|
+
if (ts.isVariableDeclaration(node)) {
|
|
7073
|
+
if (node.initializer) visit(node.initializer);
|
|
7074
|
+
declare(node.name);
|
|
7075
|
+
return;
|
|
7076
|
+
}
|
|
7077
|
+
if ((ts.isClassDeclaration(node) || ts.isClassExpression(node)) && node.name) declare(node.name);
|
|
7078
|
+
ts.forEachChild(node, visit);
|
|
7079
|
+
};
|
|
7080
|
+
ts.forEachChild(sourceFile, visit);
|
|
7081
|
+
return found;
|
|
7082
|
+
}
|
|
7083
|
+
/**
|
|
7084
|
+
* True when the emitter should re-bind `name` around a block body: the body
|
|
7085
|
+
* reads it free and does not declare it itself.
|
|
7086
|
+
*/
|
|
7087
|
+
function needsRebind(body, name) {
|
|
7088
|
+
return referencesFreeBinding(body, name);
|
|
7089
|
+
}
|
|
7090
|
+
function filterInlineImplementations(impls) {
|
|
7091
|
+
if (!impls) return impls;
|
|
7092
|
+
return {
|
|
7093
|
+
...impls,
|
|
7094
|
+
actions: impls.actions.filter((action) => !action.name.startsWith("inline:")),
|
|
7095
|
+
guards: impls.guards.filter((guard) => !guard.name.startsWith("inline:")),
|
|
7096
|
+
delays: impls.delays.filter((delay) => !delay.name.startsWith("inline:"))
|
|
7097
|
+
};
|
|
7098
|
+
}
|
|
7099
|
+
function buildMachineOptions(impls, liftedServices) {
|
|
7100
|
+
const result = {};
|
|
7101
|
+
if (impls?.actions.length) result.actions = Object.fromEntries(impls.actions.map((action) => [action.name, raw(implementationFunction(action, "// TODO: implement"))]));
|
|
7102
|
+
if (impls?.guards.length) result.guards = Object.fromEntries(impls.guards.map((guard) => [guard.name, raw(implementationFunction(guard, "// TODO: implement\n return false;", "cond"))]));
|
|
7103
|
+
const services = { ...liftedServices };
|
|
7104
|
+
for (const actor of impls?.actors ?? []) services[actor.name] = raw(serviceImplementation(actor));
|
|
7105
|
+
if (Object.keys(services).length) result.services = services;
|
|
7106
|
+
if (impls?.delays.length) result.delays = Object.fromEntries(impls.delays.map((delay) => [delay.name, raw(implementationFunction(delay, "// TODO: implement\n return 1000;", "delay"))]));
|
|
7107
|
+
return result;
|
|
7108
|
+
}
|
|
7109
|
+
/**
|
|
7110
|
+
* Emits a v4 implementation. v4 calls actions/guards with
|
|
7111
|
+
* `(context, event, meta)`, where `meta.action` (actions, delays) and
|
|
7112
|
+
* `meta.cond` (guards) carry the action/cond object that parameters were
|
|
7113
|
+
* spread onto, so `params` is re-bound from there when the body reads it.
|
|
7114
|
+
*/
|
|
7115
|
+
function implementationFunction(implementation, todo, kind = "action") {
|
|
7116
|
+
const body = implementation.code?.body;
|
|
7117
|
+
if (!body) return `function (context, event) {\n ${todo}\n}`;
|
|
7118
|
+
const inline = getInlineExpression(body);
|
|
7119
|
+
const canRebind = kind !== "delay";
|
|
7120
|
+
const metaKey = kind === "cond" ? "cond" : "action";
|
|
7121
|
+
const readsParams = inline ? referencesBinding(body, "params") : needsRebind(body, "params");
|
|
7122
|
+
const usesParams = readsParams && canRebind;
|
|
7123
|
+
const marker = readsParams && !canRebind ? ` ${PARAMS_IN_DELAY_MARKER}` : "";
|
|
7124
|
+
if (inline) {
|
|
7125
|
+
const converted = toV4Code(inline);
|
|
7126
|
+
if (!usesParams) return `${converted}${marker}`;
|
|
7127
|
+
return `function (context, event, meta) {\n const params = meta.${metaKey};\n return (${converted})(context, event);\n}`;
|
|
7128
|
+
}
|
|
7129
|
+
const code = toV4Code(dedent(body));
|
|
7130
|
+
const statement = isBareExpression(code) ? `return ${code};` : code;
|
|
7131
|
+
if (!usesParams) return `function (context, event) {\n ${statement}\n}${marker}`;
|
|
7132
|
+
return `function (context, event, meta) {\n const params = meta.${metaKey};\n ${statement}\n}`;
|
|
7133
|
+
}
|
|
7134
|
+
/** Marks a delay body that reads `params`, which v4 cannot supply. */
|
|
7135
|
+
const PARAMS_IN_DELAY_MARKER = "/* TODO: v5-only (params in delay); no XState v4 equivalent */";
|
|
7136
|
+
/**
|
|
7137
|
+
* Emits a v4 service. v4 invoke creators are called with
|
|
7138
|
+
* `(context, event, { data, src, meta })`, so a body reading v5 `input` gets it
|
|
7139
|
+
* re-bound from `meta.data` (what `invoke.data` resolves to).
|
|
7140
|
+
*/
|
|
7141
|
+
function serviceImplementation(actor) {
|
|
7142
|
+
const body = actor.code?.body;
|
|
7143
|
+
if (!body) return `async function (context, event) {\n // TODO: implement ${actor.name}\n}`;
|
|
7144
|
+
const inline = getInlineExpression(body);
|
|
7145
|
+
const usesInput = inline ? referencesBinding(body, "input") : needsRebind(body, "input");
|
|
7146
|
+
if (inline) {
|
|
7147
|
+
if (!usesInput) return toV4Code(inline);
|
|
7148
|
+
return `async function (context, event, meta) {\n return (${stripExportDefault(inline)})({ context, event, input: meta.data });\n}`;
|
|
7149
|
+
}
|
|
7150
|
+
const code = toV4Code(dedent(body));
|
|
7151
|
+
const statement = isBareExpression(code) ? `return ${code};` : code;
|
|
7152
|
+
if (!usesInput) return `async function (context, event) {\n ${statement}\n}`;
|
|
7153
|
+
return `async function (context, event, meta) {\n const input = meta.data;\n ${statement}\n}`;
|
|
7154
|
+
}
|
|
7155
|
+
/** A body that is already a complete expression is emitted verbatim. */
|
|
7156
|
+
function getInlineExpression(code) {
|
|
7157
|
+
const stripped = stripExportDefault(code);
|
|
7158
|
+
if (stripped !== code.trim()) return stripped;
|
|
7159
|
+
return /^\s*(async\s+)?(function\b|\(|[A-Za-z_$][\w$]*\s*=>)/.test(stripped) && !/;\s*\n/.test(stripped) ? stripped : void 0;
|
|
7160
|
+
}
|
|
7161
|
+
function buildSchemaBlock(schemas) {
|
|
7162
|
+
if (!schemas) return void 0;
|
|
7163
|
+
const result = {};
|
|
7164
|
+
const contextType = contextSchemaToTSType(schemas.context);
|
|
7165
|
+
if (contextType) result.context = raw(`{} as ${contextType}`);
|
|
7166
|
+
const eventsType = eventsSchemaToTSType(schemas.events);
|
|
7167
|
+
if (eventsType) result.events = raw(`{} as\n | ${eventsType}`);
|
|
7168
|
+
return Object.keys(result).length ? result : void 0;
|
|
7169
|
+
}
|
|
7170
|
+
/** Named exports of `xstate` v4 that generated code may reference. */
|
|
7171
|
+
const V4_XSTATE_HELPERS = [
|
|
7172
|
+
"assign",
|
|
7173
|
+
"raise",
|
|
7174
|
+
"send",
|
|
7175
|
+
"sendTo",
|
|
7176
|
+
"sendParent",
|
|
7177
|
+
"sendUpdate",
|
|
7178
|
+
"forwardTo",
|
|
7179
|
+
"log",
|
|
7180
|
+
"cancel",
|
|
7181
|
+
"stop",
|
|
7182
|
+
"choose",
|
|
7183
|
+
"pure",
|
|
7184
|
+
"spawn",
|
|
7185
|
+
"actions",
|
|
7186
|
+
"doneInvoke",
|
|
7187
|
+
"interpret"
|
|
7188
|
+
];
|
|
7189
|
+
function buildImports(machineExpr, codeImports) {
|
|
7190
|
+
const xstateImports = new Set(["createMachine"]);
|
|
7191
|
+
for (const helper of V4_XSTATE_HELPERS) if (new RegExp(`\\b${helper}\\s*\\(`).test(machineExpr)) xstateImports.add(helper);
|
|
7192
|
+
if (/\bactions\s*\./.test(machineExpr)) xstateImports.add("actions");
|
|
7193
|
+
const externalImports = /* @__PURE__ */ new Map();
|
|
7194
|
+
for (const codeImport of codeImports) {
|
|
7195
|
+
if ((codeImport.importKind ?? "named") !== "named") continue;
|
|
7196
|
+
const specifier = codeImport.local && codeImport.local !== codeImport.import ? `${codeImport.import} as ${codeImport.local}` : codeImport.import;
|
|
7197
|
+
if (codeImport.package === "xstate") {
|
|
7198
|
+
if (V4_ACTIONS_NAMESPACE_HELPERS.includes(codeImport.import)) xstateImports.add("actions");
|
|
7199
|
+
else if (V4_XSTATE_HELPERS.includes(codeImport.import)) xstateImports.add(specifier);
|
|
7200
|
+
continue;
|
|
7201
|
+
}
|
|
7202
|
+
const names = externalImports.get(codeImport.package) ?? /* @__PURE__ */ new Set();
|
|
7203
|
+
names.add(specifier);
|
|
7204
|
+
externalImports.set(codeImport.package, names);
|
|
7205
|
+
}
|
|
7206
|
+
const imports = [`import { ${[...xstateImports].join(", ")} } from 'xstate';`];
|
|
7207
|
+
for (const [packageName, names] of externalImports) imports.push(`import { ${[...names].sort().join(", ")} } from '${packageName}';`);
|
|
7208
|
+
return imports.join("\n");
|
|
7209
|
+
}
|
|
7210
|
+
function getCodeExpressionImports(graph) {
|
|
7211
|
+
const imports = /* @__PURE__ */ new Map();
|
|
7212
|
+
function visit(value) {
|
|
7213
|
+
if (!value || typeof value !== "object") return;
|
|
7214
|
+
if (Array.isArray(value)) {
|
|
7215
|
+
value.forEach(visit);
|
|
7216
|
+
return;
|
|
7217
|
+
}
|
|
7218
|
+
const record = value;
|
|
7219
|
+
if (Array.isArray(record.imports)) for (const item of record.imports) {
|
|
7220
|
+
if (!item || typeof item !== "object") continue;
|
|
7221
|
+
const candidate = item;
|
|
7222
|
+
if (typeof candidate.package !== "string" || typeof candidate.import !== "string") continue;
|
|
7223
|
+
const key = `${candidate.package}:${candidate.import}:${candidate.local ?? ""}:${candidate.importKind ?? "named"}`;
|
|
7224
|
+
imports.set(key, {
|
|
7225
|
+
package: candidate.package,
|
|
7226
|
+
import: candidate.import,
|
|
7227
|
+
local: candidate.local,
|
|
7228
|
+
importKind: candidate.importKind,
|
|
7229
|
+
version: candidate.version
|
|
7230
|
+
});
|
|
7231
|
+
}
|
|
7232
|
+
Object.values(record).forEach(visit);
|
|
7233
|
+
}
|
|
7234
|
+
visit(graph);
|
|
7235
|
+
return [...imports.values()];
|
|
7236
|
+
}
|
|
7237
|
+
function isCodeExpression$3(value) {
|
|
7238
|
+
return !!value && typeof value === "object" && value["@type"] === "code" && typeof value.expr === "string";
|
|
7239
|
+
}
|
|
6351
7240
|
function getXStateFSMCompatibilityIssues(graph) {
|
|
6352
7241
|
const root = graph.nodes.find((node) => node.parentId == null);
|
|
6353
7242
|
if (!root) return ["missing root state"];
|
|
@@ -6831,4 +7720,4 @@ function getScriptKind(fileName) {
|
|
|
6831
7720
|
}
|
|
6832
7721
|
|
|
6833
7722
|
//#endregion
|
|
6834
|
-
export {
|
|
7723
|
+
export { jsonSchemaToTSType as _, RawCode as a, serializeJS as b, detectXStateSourceProfile as c, getXStateFSMCompatibilityIssues as d, graphChangeToPatchesResult as f, graphToXStateV4TS as g, graphToXStateTS as h, INLINE_EXPRESSION_DIRECTIVE_FIELDS as i, diffGraphsSemantically as l, graphToXStateFSM as m, getStatelyPragma as n, alignCompareGraphIds as o, graphToMachineConfig as p, upsertStatelyPragma as r, contextSchemaToTSType as s, findStatelyPragmaAttachments as t, eventsSchemaToTSType as u, normalizeMachineProfile as v, xstateSourceToConfig as x, raw as y };
|