@statelyai/sdk 0.3.0 → 0.3.1

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/dist/embed.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as ExportCallOptions, c as InitOptions, i as EmbedMode, n as EmbedEventMap, o as ExportFormat, r as EmbedEventName, s as ExportFormatMap, t as EmbedEventHandler } from "./protocol-BC-_s3if.mjs";
1
+ import { a as ExportCallOptions, c as InitOptions, i as EmbedMode, n as EmbedEventMap, o as ExportFormat, r as EmbedEventName, s as ExportFormatMap, t as EmbedEventHandler } from "./protocol-DTIQmjHH.mjs";
2
2
 
3
3
  //#region src/embed.d.ts
4
4
  interface StatelyEmbedOptions {
package/dist/graph.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as studioMachineConverter, a as StatelyGraphData, c as StatelyInvoke, d as StudioAction, f as StudioEdge, g as fromStudioMachine, h as StudioNode, i as StatelyGraph, l as StatelyNodeData, m as StudioMachine, n as StatelyActorImplementation, o as StatelyGuard, p as StudioEventTypeData, r as StatelyEdgeData, s as StatelyImplementation, t as StatelyAction, u as StatelyTagImplementation, v as toStudioMachine } from "./graph-C-7ZK_nK.mjs";
1
+ import { _ as studioMachineConverter, a as StatelyGraphData, c as StatelyInvoke, d as StudioAction, f as StudioEdge, g as fromStudioMachine, h as StudioNode, i as StatelyGraph, l as StatelyNodeData, m as StudioMachine, n as StatelyActorImplementation, o as StatelyGuard, p as StudioEventTypeData, r as StatelyEdgeData, s as StatelyImplementation, t as StatelyAction, u as StatelyTagImplementation, v as toStudioMachine } from "./graph-BTUkUCsl.mjs";
2
2
  export { StatelyAction, StatelyActorImplementation, StatelyEdgeData, StatelyGraph, StatelyGraphData, StatelyGuard, StatelyImplementation, StatelyInvoke, StatelyNodeData, StatelyTagImplementation, StudioAction, StudioEdge, StudioEventTypeData, StudioMachine, StudioNode, fromStudioMachine, studioMachineConverter, toStudioMachine };
package/dist/index.d.mts CHANGED
@@ -1,9 +1,9 @@
1
- import { a as ExportCallOptions, c as InitOptions, i as EmbedMode, l as ProtocolMessage, n as EmbedEventMap, o as ExportFormat, r as EmbedEventName, s as ExportFormatMap, t as EmbedEventHandler } from "./protocol-BC-_s3if.mjs";
1
+ import { a as ExportCallOptions, c as InitOptions, i as EmbedMode, l as ProtocolMessage, n as EmbedEventMap, o as ExportFormat, r as EmbedEventName, s as ExportFormatMap, t as EmbedEventHandler } from "./protocol-DTIQmjHH.mjs";
2
2
  import { StatelyEmbed, StatelyEmbedOptions, createStatelyEmbed } from "./embed.mjs";
3
- import { C as EventTypeData, S as DigraphNodeConfig, _ as studioMachineConverter, a as StatelyGraphData, b as DigraphConfig, c as StatelyInvoke, d as StudioAction, f as StudioEdge, g as fromStudioMachine, h as StudioNode, i as StatelyGraph, l as StatelyNodeData, m as StudioMachine, o as StatelyGuard, r as StatelyEdgeData, t as StatelyAction, v as toStudioMachine, w as StateNodeJSONData, x as DigraphEdgeConfig, y as DigraphAction } from "./graph-C-7ZK_nK.mjs";
3
+ import { C as EventTypeData, S as DigraphNodeConfig, _ as studioMachineConverter, a as StatelyGraphData, b as DigraphConfig, c as StatelyInvoke, d as StudioAction, f as StudioEdge, g as fromStudioMachine, h as StudioNode, i as StatelyGraph, l as StatelyNodeData, m as StudioMachine, o as StatelyGuard, r as StatelyEdgeData, t as StatelyAction, v as toStudioMachine, w as StateNodeJSONData, x as DigraphEdgeConfig, y as DigraphAction } from "./graph-BTUkUCsl.mjs";
4
4
  import { CreateInspectorOptions, InspectOptions, Inspector, createStatelyInspector } from "./inspect.mjs";
5
5
  import { ExtractMachinesResponse, ExtractedMachine, GetMachineOptions, ProjectData, ProjectMachine, StudioApiError, StudioClient, StudioClientOptions, VerifyApiKeyResponse, createStatelyClient } from "./studio.mjs";
6
- import { PlanSyncOptions, PullSyncResult, ResolvedSyncInput, SyncInputFormat, SyncPlan, SyncPlanSummary, planSync, pullSync } from "./sync.mjs";
6
+ import { PlanSyncOptions, PullSyncResult, ResolvedSyncInput, SyncInputFormat, SyncPlan, SyncPlanSummary } from "./sync.mjs";
7
7
 
8
8
  //#region src/transport.d.ts
9
9
  interface Transport {
@@ -35,4 +35,4 @@ interface WebSocketTransportOptions {
35
35
  }
36
36
  declare function createWebSocketTransport(options: WebSocketTransportOptions): Transport;
37
37
  //#endregion
38
- export { type CreateInspectorOptions, type DigraphAction, type DigraphConfig, type DigraphEdgeConfig, type DigraphNodeConfig, type EmbedEventHandler, type EmbedEventMap, type EmbedEventName, type EmbedMode, type EventTypeData, type ExportCallOptions, type ExportFormat, type ExportFormatMap, type ExtractMachinesResponse, type ExtractedMachine, type GetMachineOptions, type InitOptions, type InspectOptions, type Inspector, type PlanSyncOptions, type ProjectData, type ProjectMachine, type PullSyncResult, type ResolvedSyncInput, type StateNodeJSONData, type StatelyAction, type StatelyEdgeData, type StatelyEmbed, type StatelyEmbedOptions, type StatelyGraph, type StatelyGraphData, type StatelyGuard, type StatelyInvoke, type StatelyNodeData, type StudioAction, StudioApiError, type StudioClient, type StudioClientOptions, type StudioEdge, type StudioMachine, type StudioNode, type SyncInputFormat, type SyncPlan, type SyncPlanSummary, type Transport, type VerifyApiKeyResponse, createPostMessageTransport, createStatelyClient, createStatelyEmbed, createStatelyInspector, createWebSocketTransport, fromStudioMachine, planSync, pullSync, studioMachineConverter, toStudioMachine };
38
+ export { type CreateInspectorOptions, type DigraphAction, type DigraphConfig, type DigraphEdgeConfig, type DigraphNodeConfig, type EmbedEventHandler, type EmbedEventMap, type EmbedEventName, type EmbedMode, type EventTypeData, type ExportCallOptions, type ExportFormat, type ExportFormatMap, type ExtractMachinesResponse, type ExtractedMachine, type GetMachineOptions, type InitOptions, type InspectOptions, type Inspector, type PlanSyncOptions, type ProjectData, type ProjectMachine, type PullSyncResult, type ResolvedSyncInput, type StateNodeJSONData, type StatelyAction, type StatelyEdgeData, type StatelyEmbed, type StatelyEmbedOptions, type StatelyGraph, type StatelyGraphData, type StatelyGuard, type StatelyInvoke, type StatelyNodeData, type StudioAction, StudioApiError, type StudioClient, type StudioClientOptions, type StudioEdge, type StudioMachine, type StudioNode, type SyncInputFormat, type SyncPlan, type SyncPlanSummary, type Transport, type VerifyApiKeyResponse, createPostMessageTransport, createStatelyClient, createStatelyEmbed, createStatelyInspector, createWebSocketTransport, fromStudioMachine, studioMachineConverter, toStudioMachine };
package/dist/index.mjs CHANGED
@@ -3,6 +3,5 @@ import { createStatelyEmbed } from "./embed.mjs";
3
3
  import { createStatelyInspector } from "./inspect.mjs";
4
4
  import { StudioApiError, createStatelyClient } from "./studio.mjs";
5
5
  import { fromStudioMachine, studioMachineConverter, toStudioMachine } from "./graph.mjs";
6
- import { n as pullSync, t as planSync } from "./sync-CzEOizjx.mjs";
7
6
 
8
- export { StudioApiError, createPostMessageTransport, createStatelyClient, createStatelyEmbed, createStatelyInspector, createWebSocketTransport, fromStudioMachine, planSync, pullSync, studioMachineConverter, toStudioMachine };
7
+ export { StudioApiError, createPostMessageTransport, createStatelyClient, createStatelyEmbed, createStatelyInspector, createWebSocketTransport, fromStudioMachine, studioMachineConverter, toStudioMachine };
@@ -1,4 +1,4 @@
1
- import { a as ExportCallOptions, c as InitOptions, i as EmbedMode, n as EmbedEventMap, o as ExportFormat, r as EmbedEventName, s as ExportFormatMap, t as EmbedEventHandler } from "./protocol-BC-_s3if.mjs";
1
+ import { a as ExportCallOptions, c as InitOptions, i as EmbedMode, n as EmbedEventMap, o as ExportFormat, r as EmbedEventName, s as ExportFormatMap, t as EmbedEventHandler } from "./protocol-DTIQmjHH.mjs";
2
2
 
3
3
  //#region src/inspect.d.ts
4
4
  interface CreateInspectorOptions {
package/dist/sync.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as StatelyGraph } from "./graph-C-7ZK_nK.mjs";
1
+ import { i as StatelyGraph } from "./graph-BTUkUCsl.mjs";
2
2
  import { StudioClient } from "./studio.mjs";
3
3
  import { GraphDiff } from "@statelyai/graph";
4
4
 
package/dist/sync.mjs CHANGED
@@ -1,5 +1,558 @@
1
- import "./studio.mjs";
2
- import "./graph.mjs";
3
- import { n as pullSync, t as planSync } from "./sync-CzEOizjx.mjs";
1
+ import { createStatelyClient } from "./studio.mjs";
2
+ import { fromStudioMachine, toStudioMachine } from "./graph.mjs";
3
+ import { getDiff, isEmptyDiff } from "@statelyai/graph";
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
4
6
 
7
+ //#region src/serializeJS.ts
8
+ const VALID_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
9
+ var RawCode = class {
10
+ constructor(code) {
11
+ this.code = code;
12
+ }
13
+ };
14
+ function raw(code) {
15
+ return new RawCode(code);
16
+ }
17
+ function serializeJS(value, indent = 0, step = 2) {
18
+ if (value instanceof RawCode) return value.code;
19
+ if (value === void 0) return "undefined";
20
+ if (value === null) return "null";
21
+ if (typeof value === "string") return `'${escapeString(value)}'`;
22
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
23
+ if (Array.isArray(value)) return serializeArray(value, indent, step);
24
+ if (typeof value === "object") return serializeObject(value, indent, step);
25
+ return String(value);
26
+ }
27
+ function escapeString(value) {
28
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
29
+ }
30
+ function serializeArray(array, indent, step) {
31
+ const values = array.filter((item) => item !== void 0);
32
+ if (values.length === 0) return "[]";
33
+ const innerIndent = indent + step;
34
+ const pad = " ".repeat(innerIndent);
35
+ const closePad = " ".repeat(indent);
36
+ return `[\n${values.map((item) => `${pad}${serializeJS(item, innerIndent, step)}`).join(",\n")},\n${closePad}]`;
37
+ }
38
+ function serializeObject(object, indent, step) {
39
+ const entries = Object.entries(object).filter(([, value]) => value !== void 0);
40
+ if (entries.length === 0) return "{}";
41
+ const innerIndent = indent + step;
42
+ const pad = " ".repeat(innerIndent);
43
+ const closePad = " ".repeat(indent);
44
+ return `{\n${entries.map(([key, value]) => {
45
+ const serializedKey = VALID_IDENT.test(key) ? key : `'${escapeString(key)}'`;
46
+ const serializedValue = serializeJS(value, innerIndent, step);
47
+ if (value instanceof RawCode && serializedValue.includes("\n")) return `${pad}${serializedKey}: ${indentRawCode(serializedValue, innerIndent)}`;
48
+ return `${pad}${serializedKey}: ${serializedValue}`;
49
+ }).join(",\n")},\n${closePad}}`;
50
+ }
51
+ function indentRawCode(code, indent) {
52
+ const lines = code.split("\n");
53
+ if (lines.length <= 1) return code;
54
+ const pad = " ".repeat(indent);
55
+ return [lines[0], ...lines.slice(1).map((line) => line.trim() === "" ? "" : `${pad}${line}`)].join("\n");
56
+ }
57
+
58
+ //#endregion
59
+ //#region src/graphToMachineConfig.ts
60
+ function singleOrArray(items) {
61
+ return items.length === 1 ? items[0] : items;
62
+ }
63
+ function singleOrArrayRecord(record) {
64
+ const next = {};
65
+ for (const [key, value] of Object.entries(record)) next[key] = singleOrArray(value);
66
+ return next;
67
+ }
68
+ function simplifyAttributes(value) {
69
+ for (const key of Object.keys(value)) {
70
+ const item = value[key];
71
+ if (item === void 0 || item === null) {
72
+ delete value[key];
73
+ continue;
74
+ }
75
+ if (Array.isArray(item)) {
76
+ if (item.length === 0) delete value[key];
77
+ else if (item.length === 1) value[key] = item[0];
78
+ continue;
79
+ }
80
+ if (typeof item === "object" && Object.keys(item).length === 0) {
81
+ delete value[key];
82
+ continue;
83
+ }
84
+ if (typeof item === "string" && item === "") delete value[key];
85
+ }
86
+ return value;
87
+ }
88
+ function deepSimplify(config) {
89
+ for (const mapKey of ["on", "after"]) {
90
+ const value = config[mapKey];
91
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
92
+ for (const transition of Object.values(value)) if (transition && typeof transition === "object" && !Array.isArray(transition)) simplifyAttributes(transition);
93
+ else if (Array.isArray(transition)) {
94
+ for (const item of transition) if (item && typeof item === "object") simplifyAttributes(item);
95
+ }
96
+ }
97
+ if (config.always && typeof config.always === "object") if (Array.isArray(config.always)) {
98
+ for (const item of config.always) if (item && typeof item === "object") simplifyAttributes(item);
99
+ } else simplifyAttributes(config.always);
100
+ simplifyAttributes(config);
101
+ if (config.states && typeof config.states === "object") for (const state of Object.values(config.states)) deepSimplify(state);
102
+ return config;
103
+ }
104
+ function serializeActionItem(action) {
105
+ const { type, params } = action;
106
+ switch (type) {
107
+ case "xstate.raise": return raw(`raise(${serializeJSValue(params?.event ?? { type: "unknown" })})`);
108
+ case "xstate.sendTo": return raw(`sendTo(${serializeJSValue(params?.to ?? "unknown")}, ${serializeJSValue(params?.event ?? { type: "unknown" })})`);
109
+ case "xstate.cancel": return raw(`cancel(${serializeJSValue(params?.sendId ?? "unknown")})`);
110
+ case "xstate.log": return raw(`log(${serializeJSValue(params?.label ?? "")})`);
111
+ default:
112
+ if (!params || Object.keys(params).length === 0) return { type };
113
+ return {
114
+ type,
115
+ params
116
+ };
117
+ }
118
+ }
119
+ function serializeJSValue(value) {
120
+ if (typeof value === "string") return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
121
+ if (value && typeof value === "object") return rawObject(value);
122
+ return String(value);
123
+ }
124
+ function rawObject(value) {
125
+ return `{ ${Object.entries(value).map(([key, item]) => `${key}: ${serializeJSValue(item)}`).join(", ")} }`;
126
+ }
127
+ function graphToMachineConfig(graph, options = {}) {
128
+ const { showDescriptions = true } = options;
129
+ function getNodeConfig(nodeId) {
130
+ const node = graph.nodes.find((candidate) => candidate.id === nodeId);
131
+ if (!node) throw new Error(`Node not found: ${nodeId}`);
132
+ const config = {};
133
+ const initialNode = node.data.initialId ? graph.nodes.find((candidate) => candidate.id === node.data.initialId) : null;
134
+ Object.assign(config, {
135
+ id: node.parentId == null ? graph.id : node.id.endsWith(`.${node.data.key}`) ? void 0 : node.id,
136
+ ...node.data.type ? { type: node.data.type } : {},
137
+ ...initialNode ? { initial: initialNode.data.key } : {},
138
+ ...node.data.entry?.length ? { entry: node.data.entry.map(serializeActionItem) } : {},
139
+ ...node.data.exit?.length ? { exit: node.data.exit.map(serializeActionItem) } : {},
140
+ ...node.data.invokes?.length ? { invoke: node.data.invokes.map((invoke) => ({
141
+ src: invoke.src,
142
+ id: invoke.id,
143
+ ...invoke.input ? { input: invoke.input } : {}
144
+ })) } : {},
145
+ ...node.data.tags?.length ? { tags: node.data.tags } : {},
146
+ ...showDescriptions && node.data.description ? { description: node.data.description } : {},
147
+ ...node.data.history ? { history: node.data.history } : {}
148
+ });
149
+ const childNodes = graph.nodes.filter((candidate) => candidate.parentId === node.id);
150
+ const edges = graph.edges.filter((edge) => edge.sourceId === node.id);
151
+ if (edges.length > 0) {
152
+ const on = {};
153
+ const after = {};
154
+ const always = [];
155
+ for (const edge of edges) {
156
+ const targetNode = graph.nodes.find((candidate) => candidate.id === edge.targetId);
157
+ const target = targetNode && targetNode.parentId === node.parentId ? targetNode.data.key : targetNode ? `#${targetNode.id}` : void 0;
158
+ const transition = {
159
+ ...target ? { target } : {},
160
+ ...edge.data.transitionType === "reenter" ? { reenter: true } : {},
161
+ ...edge.data.guard ? { guard: edge.data.guard } : {},
162
+ ...edge.data.actions?.length ? { actions: edge.data.actions.map(serializeActionItem) } : {},
163
+ ...edge.data.description ? { description: edge.data.description } : {}
164
+ };
165
+ if (edge.data.eventType === "") {
166
+ always.push(transition);
167
+ continue;
168
+ }
169
+ if (edge.data.eventType.startsWith("xstate.after.")) {
170
+ const delay = edge.data.eventType.slice(13).split(".")[0];
171
+ after[delay] ??= [];
172
+ after[delay].push(transition);
173
+ continue;
174
+ }
175
+ on[edge.data.eventType] ??= [];
176
+ on[edge.data.eventType].push(transition);
177
+ }
178
+ if (Object.keys(on).length > 0) config.on = singleOrArrayRecord(on);
179
+ if (Object.keys(after).length > 0) config.after = singleOrArrayRecord(after);
180
+ if (always.length > 0) config.always = singleOrArray(always);
181
+ }
182
+ if (childNodes.length > 0) config.states = Object.fromEntries(childNodes.map((child) => [child.data.key, getNodeConfig(child.id)]));
183
+ return config;
184
+ }
185
+ const rootNode = graph.nodes.find((candidate) => candidate.parentId == null);
186
+ if (!rootNode) throw new Error("No root node found");
187
+ return deepSimplify(getNodeConfig(rootNode.id));
188
+ }
189
+
190
+ //#endregion
191
+ //#region src/jsonSchemaToTSType.ts
192
+ function jsonSchemaToTSType(schema) {
193
+ if (!schema) return "unknown";
194
+ if ("const" in schema) {
195
+ const value = schema.const;
196
+ return typeof value === "string" ? `'${value}'` : String(value);
197
+ }
198
+ if (schema.type === "array") return `Array<${jsonSchemaToTSType(schema.items)}>`;
199
+ if (schema.type === "object") {
200
+ const props = schema.properties;
201
+ if (!props || Object.keys(props).length === 0) return "Record<string, unknown>";
202
+ return `{ ${Object.entries(props).map(([key, value]) => `${key}: ${jsonSchemaToTSType(value)}`).join("; ")} }`;
203
+ }
204
+ switch (schema.type) {
205
+ case "string": return "string";
206
+ case "number": return "number";
207
+ case "boolean": return "boolean";
208
+ case "null": return "null";
209
+ default: return "unknown";
210
+ }
211
+ }
212
+ function contextSchemaToTSType(context) {
213
+ if (!context || Object.keys(context).length === 0) return null;
214
+ return `{ ${Object.entries(context).map(([key, schema]) => `${key}: ${jsonSchemaToTSType(schema)}`).join("; ")} }`;
215
+ }
216
+ function eventsSchemaToTSType(events) {
217
+ if (!events || Object.keys(events).length === 0) return null;
218
+ return Object.entries(events).map(([eventType, schema]) => {
219
+ const props = schema.properties;
220
+ const extraProps = Object.entries(props ?? {}).filter(([key]) => key !== "type").map(([key, value]) => `${key}?: ${jsonSchemaToTSType(value)}`);
221
+ if (extraProps.length === 0) return `{ type: '${eventType}' }`;
222
+ return `{ type: '${eventType}'; ${extraProps.join("; ")} }`;
223
+ }).join("\n | ");
224
+ }
225
+
226
+ //#endregion
227
+ //#region src/graphToXStateTS.ts
228
+ const BUILTIN_ACTION_IMPORTS = {
229
+ "xstate.raise": "raise",
230
+ "xstate.sendTo": "sendTo",
231
+ "xstate.cancel": "cancel",
232
+ "xstate.log": "log"
233
+ };
234
+ function graphToXStateTS(graph, options = {}) {
235
+ const { exportStyle = "named" } = options;
236
+ const schemas = graph.data.schemas;
237
+ const impls = graph.data.implementations;
238
+ const hasSchemas = Boolean(schemas && (schemas.context || schemas.events || schemas.input || schemas.output));
239
+ const hasActions = Boolean(impls?.actions.length);
240
+ const hasGuards = Boolean(impls?.guards.length);
241
+ const hasActors = Boolean(impls?.actors.length);
242
+ const hasDelays = Boolean(impls?.delays.length);
243
+ const hasSetup = hasSchemas || hasActions || hasGuards || hasActors || hasDelays;
244
+ const imports = new Set([hasSetup ? "setup" : "createMachine"]);
245
+ for (const node of graph.nodes) for (const action of [...node.data.entry ?? [], ...node.data.exit ?? []]) {
246
+ const builtInImport = BUILTIN_ACTION_IMPORTS[action.type];
247
+ if (builtInImport) imports.add(builtInImport);
248
+ }
249
+ for (const edge of graph.edges) for (const action of edge.data.actions ?? []) {
250
+ const builtInImport = BUILTIN_ACTION_IMPORTS[action.type];
251
+ if (builtInImport) imports.add(builtInImport);
252
+ }
253
+ const machineConfig = graphToMachineConfig(graph);
254
+ if (hasActors) imports.add("fromPromise");
255
+ const machineExpr = hasSetup ? `setup(${serializeJS(buildSetupObject(graph), 0)}).createMachine(${serializeJS(machineConfig, 0)})` : `createMachine(${serializeJS(machineConfig, 0)})`;
256
+ const lines = [
257
+ `import { ${Array.from(imports).join(", ")} } from 'xstate';`,
258
+ "",
259
+ `const machine = ${machineExpr};`
260
+ ];
261
+ if (exportStyle === "named") lines.push("", "export { machine };");
262
+ else if (exportStyle === "default") lines.push("", "export default machine;");
263
+ return `${lines.join("\n")}\n`;
264
+ }
265
+ function buildSetupObject(graph) {
266
+ const setup = {};
267
+ const schemas = graph.data.schemas;
268
+ const impls = graph.data.implementations;
269
+ const types = buildTypesBlock(schemas);
270
+ if (types) setup.types = types;
271
+ if (impls?.actions.length) setup.actions = buildActionsBlock(impls.actions);
272
+ if (impls?.guards.length) setup.guards = buildGuardsBlock(impls.guards);
273
+ if (impls?.actors.length) setup.actors = buildActorsBlock(impls.actors);
274
+ if (impls?.delays.length) setup.delays = buildDelaysBlock(impls.delays);
275
+ return setup;
276
+ }
277
+ function buildTypesBlock(schemas) {
278
+ if (!schemas) return;
279
+ const types = {};
280
+ const contextType = contextSchemaToTSType(schemas.context);
281
+ if (contextType) types.context = raw(`{} as ${contextType}`);
282
+ const eventsType = eventsSchemaToTSType(schemas.events);
283
+ if (eventsType) types.events = raw(`{} as\n | ${eventsType}`);
284
+ if (schemas.input) types.input = raw(`{} as ${jsonSchemaToTSType(schemas.input)}`);
285
+ if (schemas.output) types.output = raw(`{} as ${jsonSchemaToTSType(schemas.output)}`);
286
+ return Object.keys(types).length > 0 ? types : void 0;
287
+ }
288
+ function buildActionsBlock(actions) {
289
+ return Object.fromEntries(actions.map((action) => [action.name, (() => {
290
+ const paramsType = action.paramsSchema ? `: ${jsonSchemaToTSType(action.paramsSchema)}` : "";
291
+ return raw(action.code?.body ? `function ({ context, event }, params${paramsType}) {\n ${action.code.body}\n}` : `function ({ context, event }) {\n // TODO: implement ${action.name}\n}`);
292
+ })()]));
293
+ }
294
+ function buildGuardsBlock(guards) {
295
+ return Object.fromEntries(guards.map((guard) => [guard.name, (() => {
296
+ const paramsType = guard.paramsSchema ? `: ${jsonSchemaToTSType(guard.paramsSchema)}` : "";
297
+ return raw(guard.code?.body ? `function ({ context, event }, params${paramsType}) {\n ${guard.code.body}\n}` : `function ({ context, event }) {\n // TODO: implement ${guard.name}\n return false;\n}`);
298
+ })()]));
299
+ }
300
+ function buildActorsBlock(actors) {
301
+ return Object.fromEntries(actors.map((actor) => [actor.name, (() => {
302
+ const inputType = actor.inputSchema ? `: { input: ${jsonSchemaToTSType(actor.inputSchema)} }` : "";
303
+ return raw(actor.code?.body ? `fromPromise(async ({ input }${inputType}) => {\n ${actor.code.body}\n})` : `fromPromise(async ({ input }) => {\n // TODO: implement ${actor.name}\n})`);
304
+ })()]));
305
+ }
306
+ function buildDelaysBlock(delays) {
307
+ return Object.fromEntries(delays.map((delay) => [delay.name, raw(delay.code?.body ? `function ({ context, event }) {\n ${delay.code.body}\n}` : `function () {\n // TODO: implement ${delay.name}\n return 1000;\n}`)]));
308
+ }
309
+
310
+ //#endregion
311
+ //#region src/sync.ts
312
+ function isUrl(value) {
313
+ try {
314
+ const url = new URL(value);
315
+ return url.protocol === "http:" || url.protocol === "https:";
316
+ } catch {
317
+ return false;
318
+ }
319
+ }
320
+ async function fileExists(filePath) {
321
+ try {
322
+ await fs.access(filePath);
323
+ return true;
324
+ } catch {
325
+ return false;
326
+ }
327
+ }
328
+ function normalizeActions(value) {
329
+ if (value == null) return [];
330
+ return (Array.isArray(value) ? value : [value]).map((item) => {
331
+ if (typeof item === "string") return { type: item };
332
+ if (typeof item === "object" && item !== null && "type" in item) {
333
+ const type = item.type;
334
+ const params = "params" in item && typeof item.params === "object" && item.params ? item.params : void 0;
335
+ return {
336
+ type,
337
+ ...params ? { params } : {}
338
+ };
339
+ }
340
+ return { type: String(item) };
341
+ });
342
+ }
343
+ function normalizeTags(value) {
344
+ if (!value) return [];
345
+ if (Array.isArray(value)) return value.map((tag) => String(tag));
346
+ return [String(value)];
347
+ }
348
+ function normalizeInvoke(value) {
349
+ if (!value) return [];
350
+ return (Array.isArray(value) ? value : [value]).map((invoke, index) => ({
351
+ src: invoke.src,
352
+ id: invoke.id ?? `invoke[${index}]`,
353
+ ...invoke.input ? { input: invoke.input } : {}
354
+ }));
355
+ }
356
+ function resolveTargetId(sourceParentPath, target) {
357
+ if (!target) return;
358
+ if (target.startsWith("#")) {
359
+ const stripped = target.slice(1);
360
+ const machineIndex = stripped.indexOf(".");
361
+ if (machineIndex >= 0) return `(machine).${stripped.slice(machineIndex + 1)}`;
362
+ return `(machine).${stripped}`;
363
+ }
364
+ if (target.startsWith("(machine)")) return target;
365
+ if (target.includes(".")) return `(machine).${target}`;
366
+ return `${sourceParentPath}.${target}`;
367
+ }
368
+ function appendTransitionEdges(edges, sourceId, sourceParentPath, eventType, transition, edgeCounts) {
369
+ const transitions = Array.isArray(transition) ? transition : [transition];
370
+ for (const item of transitions) {
371
+ const normalized = typeof item === "string" ? { target: item } : item;
372
+ const targetId = resolveTargetId(sourceParentPath, Array.isArray(normalized.target) ? normalized.target[0] : normalized.target);
373
+ const groupKey = `${sourceId}:${eventType}`;
374
+ const index = edgeCounts.get(groupKey) ?? 0;
375
+ edgeCounts.set(groupKey, index + 1);
376
+ edges.push({
377
+ type: "edge",
378
+ id: `${sourceId}#${eventType}[${index}]`,
379
+ sourceId,
380
+ targetId: targetId ?? sourceId,
381
+ label: eventType,
382
+ data: {
383
+ eventType,
384
+ transitionType: normalized.reenter === true ? "reenter" : normalized.internal === true || !targetId ? "targetless" : "normal",
385
+ ...normalized.guard ? { guard: typeof normalized.guard === "string" ? { type: normalized.guard } : {
386
+ type: normalized.guard.type,
387
+ ...normalized.guard.params ? { params: normalized.guard.params } : {}
388
+ } } : {},
389
+ actions: normalizeActions(normalized.actions),
390
+ ...normalized.description ? { description: normalized.description } : {}
391
+ }
392
+ });
393
+ }
394
+ }
395
+ function fromXStateConfig(config) {
396
+ const nodes = [];
397
+ const edges = [];
398
+ const edgeCounts = /* @__PURE__ */ new Map();
399
+ function visitNode(key, nodeConfig, parentId, parentPath) {
400
+ const nodeId = parentPath ? `${parentPath}.${key}` : "(machine)";
401
+ const currentPath = parentPath ? nodeId : "(machine)";
402
+ const initialId = nodeConfig.initial ? `${currentPath}.${nodeConfig.initial}` : void 0;
403
+ nodes.push({
404
+ type: "node",
405
+ id: nodeId,
406
+ parentId,
407
+ label: key,
408
+ ...initialId ? { initialNodeId: initialId } : {},
409
+ data: {
410
+ key,
411
+ ...nodeConfig.type ? { type: nodeConfig.type } : {},
412
+ ...nodeConfig.history ? { history: nodeConfig.history } : {},
413
+ ...initialId ? { initialId } : {},
414
+ entry: normalizeActions(nodeConfig.entry),
415
+ exit: normalizeActions(nodeConfig.exit),
416
+ invokes: normalizeInvoke(nodeConfig.invoke),
417
+ tags: normalizeTags(nodeConfig.tags),
418
+ ...nodeConfig.description ? { description: nodeConfig.description } : {}
419
+ }
420
+ });
421
+ for (const [eventType, transition] of Object.entries(nodeConfig.on ?? {})) appendTransitionEdges(edges, nodeId, parentPath ?? "(machine)", eventType, transition, edgeCounts);
422
+ if (nodeConfig.always) appendTransitionEdges(edges, nodeId, parentPath ?? "(machine)", "", nodeConfig.always, edgeCounts);
423
+ for (const [childKey, childConfig] of Object.entries(nodeConfig.states ?? {})) visitNode(childKey, childConfig, nodeId, currentPath);
424
+ }
425
+ visitNode("(machine)", config, null, null);
426
+ return {
427
+ id: config.id ?? "machine",
428
+ nodes,
429
+ edges,
430
+ data: {}
431
+ };
432
+ }
433
+ async function resolveLocalFile(locator, options) {
434
+ const filePath = path.resolve(options.cwd ?? process.cwd(), locator);
435
+ const contents = await fs.readFile(filePath, "utf8");
436
+ const extension = path.extname(filePath).toLowerCase();
437
+ if (extension === ".ts" || extension === ".tsx" || extension === ".js" || extension === ".jsx") {
438
+ const config = (await (options.client ?? createStatelyClient({
439
+ apiKey: options.apiKey,
440
+ baseUrl: options.baseUrl,
441
+ fetch: options.fetch
442
+ })).code.extractMachines(contents)).machines[0]?.config;
443
+ if (!config) throw new Error(`No machines extracted from ${filePath}`);
444
+ return {
445
+ kind: "local-file",
446
+ locator: filePath,
447
+ format: "xstate",
448
+ graph: fromXStateConfig(config)
449
+ };
450
+ }
451
+ const parsed = JSON.parse(contents);
452
+ if ("rootNode" in parsed && "edges" in parsed) return {
453
+ kind: "local-file",
454
+ locator: filePath,
455
+ format: "digraph",
456
+ graph: fromStudioMachine(parsed)
457
+ };
458
+ if ("nodes" in parsed && "edges" in parsed) return {
459
+ kind: "local-file",
460
+ locator: filePath,
461
+ format: "graph",
462
+ graph: parsed
463
+ };
464
+ if ("states" in parsed) return {
465
+ kind: "local-file",
466
+ locator: filePath,
467
+ format: "xstate",
468
+ graph: fromXStateConfig(parsed)
469
+ };
470
+ throw new Error(`Unsupported local sync input: ${filePath}`);
471
+ }
472
+ function inferWritableTargetFormat(filePath) {
473
+ if (filePath.endsWith(".ts") || filePath.endsWith(".tsx") || filePath.endsWith(".js") || filePath.endsWith(".jsx")) return "xstate";
474
+ if (filePath.endsWith(".digraph.json")) return "digraph";
475
+ if (filePath.endsWith(".graph.json")) return "graph";
476
+ return null;
477
+ }
478
+ function serializeGraph(graph, format) {
479
+ switch (format) {
480
+ case "digraph": return `${JSON.stringify(toStudioMachine(graph), null, 2)}\n`;
481
+ case "graph": return `${JSON.stringify(graph, null, 2)}\n`;
482
+ case "xstate": return graphToXStateTS(graph);
483
+ default: {
484
+ const exhaustive = format;
485
+ throw new Error(`Unsupported sync output format: ${exhaustive}`);
486
+ }
487
+ }
488
+ }
489
+ async function resolveRemoteMachine(machineId, baseUrl, kind, locator, options) {
490
+ return {
491
+ kind,
492
+ locator,
493
+ format: "digraph",
494
+ graph: fromStudioMachine(await (options.client ?? createStatelyClient({
495
+ apiKey: options.apiKey,
496
+ baseUrl,
497
+ fetch: options.fetch
498
+ })).machines.get(machineId))
499
+ };
500
+ }
501
+ async function resolveSyncInput(locator, options) {
502
+ if (await fileExists(path.resolve(options.cwd ?? process.cwd(), locator))) return resolveLocalFile(locator, options);
503
+ if (isUrl(locator)) {
504
+ const url = new URL(locator);
505
+ const machineId = url.pathname.split("/").filter(Boolean).at(-1);
506
+ if (!machineId) throw new Error(`Could not resolve machine ID from URL: ${locator}`);
507
+ return resolveRemoteMachine(machineId, url.origin, "studio-url", locator, options);
508
+ }
509
+ return resolveRemoteMachine(locator, options.baseUrl, "studio-machine-id", locator, options);
510
+ }
511
+ function summarizeDiff(diff) {
512
+ const nodeChanges = diff.nodes.added.length + diff.nodes.removed.length + diff.nodes.updated.length;
513
+ const edgeChanges = diff.edges.added.length + diff.edges.removed.length + diff.edges.updated.length;
514
+ return {
515
+ hasChanges: !isEmptyDiff(diff),
516
+ nodeChanges,
517
+ edgeChanges
518
+ };
519
+ }
520
+ async function planSync(options) {
521
+ const source = await resolveSyncInput(options.source, options);
522
+ const target = await resolveSyncInput(options.target, options);
523
+ const diff = getDiff(source.graph, target.graph);
524
+ return {
525
+ source,
526
+ target,
527
+ diff,
528
+ summary: summarizeDiff(diff),
529
+ warnings: []
530
+ };
531
+ }
532
+ async function pullSync(options) {
533
+ const source = await resolveSyncInput(options.source, options);
534
+ const outputPath = path.resolve(options.cwd ?? process.cwd(), options.target);
535
+ const fallbackFormat = inferWritableTargetFormat(outputPath);
536
+ let targetFormat = fallbackFormat;
537
+ if (await fileExists(outputPath)) try {
538
+ targetFormat = (await resolveLocalFile(options.target, options)).format;
539
+ } catch (error) {
540
+ if (!fallbackFormat) throw error;
541
+ }
542
+ if (!targetFormat) throw new Error(`Could not infer a writable target format from ${outputPath}. Use an existing digraph/graph file or a .digraph.json/.graph.json target.`);
543
+ const serialized = serializeGraph(source.graph, targetFormat);
544
+ await fs.writeFile(outputPath, serialized, "utf8");
545
+ return {
546
+ source,
547
+ target: {
548
+ kind: "local-file",
549
+ locator: outputPath,
550
+ format: targetFormat,
551
+ graph: source.graph
552
+ },
553
+ outputPath
554
+ };
555
+ }
556
+
557
+ //#endregion
5
558
  export { planSync, pullSync };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist"
@@ -1,558 +0,0 @@
1
- import { createStatelyClient } from "./studio.mjs";
2
- import { fromStudioMachine, toStudioMachine } from "./graph.mjs";
3
- import { getDiff, isEmptyDiff } from "@statelyai/graph";
4
- import fs from "node:fs/promises";
5
- import path from "node:path";
6
-
7
- //#region src/serializeJS.ts
8
- const VALID_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
9
- var RawCode = class {
10
- constructor(code) {
11
- this.code = code;
12
- }
13
- };
14
- function raw(code) {
15
- return new RawCode(code);
16
- }
17
- function serializeJS(value, indent = 0, step = 2) {
18
- if (value instanceof RawCode) return value.code;
19
- if (value === void 0) return "undefined";
20
- if (value === null) return "null";
21
- if (typeof value === "string") return `'${escapeString(value)}'`;
22
- if (typeof value === "number" || typeof value === "boolean") return String(value);
23
- if (Array.isArray(value)) return serializeArray(value, indent, step);
24
- if (typeof value === "object") return serializeObject(value, indent, step);
25
- return String(value);
26
- }
27
- function escapeString(value) {
28
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
29
- }
30
- function serializeArray(array, indent, step) {
31
- const values = array.filter((item) => item !== void 0);
32
- if (values.length === 0) return "[]";
33
- const innerIndent = indent + step;
34
- const pad = " ".repeat(innerIndent);
35
- const closePad = " ".repeat(indent);
36
- return `[\n${values.map((item) => `${pad}${serializeJS(item, innerIndent, step)}`).join(",\n")},\n${closePad}]`;
37
- }
38
- function serializeObject(object, indent, step) {
39
- const entries = Object.entries(object).filter(([, value]) => value !== void 0);
40
- if (entries.length === 0) return "{}";
41
- const innerIndent = indent + step;
42
- const pad = " ".repeat(innerIndent);
43
- const closePad = " ".repeat(indent);
44
- return `{\n${entries.map(([key, value]) => {
45
- const serializedKey = VALID_IDENT.test(key) ? key : `'${escapeString(key)}'`;
46
- const serializedValue = serializeJS(value, innerIndent, step);
47
- if (value instanceof RawCode && serializedValue.includes("\n")) return `${pad}${serializedKey}: ${indentRawCode(serializedValue, innerIndent)}`;
48
- return `${pad}${serializedKey}: ${serializedValue}`;
49
- }).join(",\n")},\n${closePad}}`;
50
- }
51
- function indentRawCode(code, indent) {
52
- const lines = code.split("\n");
53
- if (lines.length <= 1) return code;
54
- const pad = " ".repeat(indent);
55
- return [lines[0], ...lines.slice(1).map((line) => line.trim() === "" ? "" : `${pad}${line}`)].join("\n");
56
- }
57
-
58
- //#endregion
59
- //#region src/graphToMachineConfig.ts
60
- function singleOrArray(items) {
61
- return items.length === 1 ? items[0] : items;
62
- }
63
- function singleOrArrayRecord(record) {
64
- const next = {};
65
- for (const [key, value] of Object.entries(record)) next[key] = singleOrArray(value);
66
- return next;
67
- }
68
- function simplifyAttributes(value) {
69
- for (const key of Object.keys(value)) {
70
- const item = value[key];
71
- if (item === void 0 || item === null) {
72
- delete value[key];
73
- continue;
74
- }
75
- if (Array.isArray(item)) {
76
- if (item.length === 0) delete value[key];
77
- else if (item.length === 1) value[key] = item[0];
78
- continue;
79
- }
80
- if (typeof item === "object" && Object.keys(item).length === 0) {
81
- delete value[key];
82
- continue;
83
- }
84
- if (typeof item === "string" && item === "") delete value[key];
85
- }
86
- return value;
87
- }
88
- function deepSimplify(config) {
89
- for (const mapKey of ["on", "after"]) {
90
- const value = config[mapKey];
91
- if (!value || typeof value !== "object" || Array.isArray(value)) continue;
92
- for (const transition of Object.values(value)) if (transition && typeof transition === "object" && !Array.isArray(transition)) simplifyAttributes(transition);
93
- else if (Array.isArray(transition)) {
94
- for (const item of transition) if (item && typeof item === "object") simplifyAttributes(item);
95
- }
96
- }
97
- if (config.always && typeof config.always === "object") if (Array.isArray(config.always)) {
98
- for (const item of config.always) if (item && typeof item === "object") simplifyAttributes(item);
99
- } else simplifyAttributes(config.always);
100
- simplifyAttributes(config);
101
- if (config.states && typeof config.states === "object") for (const state of Object.values(config.states)) deepSimplify(state);
102
- return config;
103
- }
104
- function serializeActionItem(action) {
105
- const { type, params } = action;
106
- switch (type) {
107
- case "xstate.raise": return raw(`raise(${serializeJSValue(params?.event ?? { type: "unknown" })})`);
108
- case "xstate.sendTo": return raw(`sendTo(${serializeJSValue(params?.to ?? "unknown")}, ${serializeJSValue(params?.event ?? { type: "unknown" })})`);
109
- case "xstate.cancel": return raw(`cancel(${serializeJSValue(params?.sendId ?? "unknown")})`);
110
- case "xstate.log": return raw(`log(${serializeJSValue(params?.label ?? "")})`);
111
- default:
112
- if (!params || Object.keys(params).length === 0) return { type };
113
- return {
114
- type,
115
- params
116
- };
117
- }
118
- }
119
- function serializeJSValue(value) {
120
- if (typeof value === "string") return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
121
- if (value && typeof value === "object") return rawObject(value);
122
- return String(value);
123
- }
124
- function rawObject(value) {
125
- return `{ ${Object.entries(value).map(([key, item]) => `${key}: ${serializeJSValue(item)}`).join(", ")} }`;
126
- }
127
- function graphToMachineConfig(graph, options = {}) {
128
- const { showDescriptions = true } = options;
129
- function getNodeConfig(nodeId) {
130
- const node = graph.nodes.find((candidate) => candidate.id === nodeId);
131
- if (!node) throw new Error(`Node not found: ${nodeId}`);
132
- const config = {};
133
- const initialNode = node.data.initialId ? graph.nodes.find((candidate) => candidate.id === node.data.initialId) : null;
134
- Object.assign(config, {
135
- id: node.parentId == null ? graph.id : node.id.endsWith(`.${node.data.key}`) ? void 0 : node.id,
136
- ...node.data.type ? { type: node.data.type } : {},
137
- ...initialNode ? { initial: initialNode.data.key } : {},
138
- ...node.data.entry?.length ? { entry: node.data.entry.map(serializeActionItem) } : {},
139
- ...node.data.exit?.length ? { exit: node.data.exit.map(serializeActionItem) } : {},
140
- ...node.data.invokes?.length ? { invoke: node.data.invokes.map((invoke) => ({
141
- src: invoke.src,
142
- id: invoke.id,
143
- ...invoke.input ? { input: invoke.input } : {}
144
- })) } : {},
145
- ...node.data.tags?.length ? { tags: node.data.tags } : {},
146
- ...showDescriptions && node.data.description ? { description: node.data.description } : {},
147
- ...node.data.history ? { history: node.data.history } : {}
148
- });
149
- const childNodes = graph.nodes.filter((candidate) => candidate.parentId === node.id);
150
- const edges = graph.edges.filter((edge) => edge.sourceId === node.id);
151
- if (edges.length > 0) {
152
- const on = {};
153
- const after = {};
154
- const always = [];
155
- for (const edge of edges) {
156
- const targetNode = graph.nodes.find((candidate) => candidate.id === edge.targetId);
157
- const target = targetNode && targetNode.parentId === node.parentId ? targetNode.data.key : targetNode ? `#${targetNode.id}` : void 0;
158
- const transition = {
159
- ...target ? { target } : {},
160
- ...edge.data.transitionType === "reenter" ? { reenter: true } : {},
161
- ...edge.data.guard ? { guard: edge.data.guard } : {},
162
- ...edge.data.actions?.length ? { actions: edge.data.actions.map(serializeActionItem) } : {},
163
- ...edge.data.description ? { description: edge.data.description } : {}
164
- };
165
- if (edge.data.eventType === "") {
166
- always.push(transition);
167
- continue;
168
- }
169
- if (edge.data.eventType.startsWith("xstate.after.")) {
170
- const delay = edge.data.eventType.slice(13).split(".")[0];
171
- after[delay] ??= [];
172
- after[delay].push(transition);
173
- continue;
174
- }
175
- on[edge.data.eventType] ??= [];
176
- on[edge.data.eventType].push(transition);
177
- }
178
- if (Object.keys(on).length > 0) config.on = singleOrArrayRecord(on);
179
- if (Object.keys(after).length > 0) config.after = singleOrArrayRecord(after);
180
- if (always.length > 0) config.always = singleOrArray(always);
181
- }
182
- if (childNodes.length > 0) config.states = Object.fromEntries(childNodes.map((child) => [child.data.key, getNodeConfig(child.id)]));
183
- return config;
184
- }
185
- const rootNode = graph.nodes.find((candidate) => candidate.parentId == null);
186
- if (!rootNode) throw new Error("No root node found");
187
- return deepSimplify(getNodeConfig(rootNode.id));
188
- }
189
-
190
- //#endregion
191
- //#region src/jsonSchemaToTSType.ts
192
- function jsonSchemaToTSType(schema) {
193
- if (!schema) return "unknown";
194
- if ("const" in schema) {
195
- const value = schema.const;
196
- return typeof value === "string" ? `'${value}'` : String(value);
197
- }
198
- if (schema.type === "array") return `Array<${jsonSchemaToTSType(schema.items)}>`;
199
- if (schema.type === "object") {
200
- const props = schema.properties;
201
- if (!props || Object.keys(props).length === 0) return "Record<string, unknown>";
202
- return `{ ${Object.entries(props).map(([key, value]) => `${key}: ${jsonSchemaToTSType(value)}`).join("; ")} }`;
203
- }
204
- switch (schema.type) {
205
- case "string": return "string";
206
- case "number": return "number";
207
- case "boolean": return "boolean";
208
- case "null": return "null";
209
- default: return "unknown";
210
- }
211
- }
212
- function contextSchemaToTSType(context) {
213
- if (!context || Object.keys(context).length === 0) return null;
214
- return `{ ${Object.entries(context).map(([key, schema]) => `${key}: ${jsonSchemaToTSType(schema)}`).join("; ")} }`;
215
- }
216
- function eventsSchemaToTSType(events) {
217
- if (!events || Object.keys(events).length === 0) return null;
218
- return Object.entries(events).map(([eventType, schema]) => {
219
- const props = schema.properties;
220
- const extraProps = Object.entries(props ?? {}).filter(([key]) => key !== "type").map(([key, value]) => `${key}?: ${jsonSchemaToTSType(value)}`);
221
- if (extraProps.length === 0) return `{ type: '${eventType}' }`;
222
- return `{ type: '${eventType}'; ${extraProps.join("; ")} }`;
223
- }).join("\n | ");
224
- }
225
-
226
- //#endregion
227
- //#region src/graphToXStateTS.ts
228
- const BUILTIN_ACTION_IMPORTS = {
229
- "xstate.raise": "raise",
230
- "xstate.sendTo": "sendTo",
231
- "xstate.cancel": "cancel",
232
- "xstate.log": "log"
233
- };
234
- function graphToXStateTS(graph, options = {}) {
235
- const { exportStyle = "named" } = options;
236
- const schemas = graph.data.schemas;
237
- const impls = graph.data.implementations;
238
- const hasSchemas = Boolean(schemas && (schemas.context || schemas.events || schemas.input || schemas.output));
239
- const hasActions = Boolean(impls?.actions.length);
240
- const hasGuards = Boolean(impls?.guards.length);
241
- const hasActors = Boolean(impls?.actors.length);
242
- const hasDelays = Boolean(impls?.delays.length);
243
- const hasSetup = hasSchemas || hasActions || hasGuards || hasActors || hasDelays;
244
- const imports = new Set([hasSetup ? "setup" : "createMachine"]);
245
- for (const node of graph.nodes) for (const action of [...node.data.entry ?? [], ...node.data.exit ?? []]) {
246
- const builtInImport = BUILTIN_ACTION_IMPORTS[action.type];
247
- if (builtInImport) imports.add(builtInImport);
248
- }
249
- for (const edge of graph.edges) for (const action of edge.data.actions ?? []) {
250
- const builtInImport = BUILTIN_ACTION_IMPORTS[action.type];
251
- if (builtInImport) imports.add(builtInImport);
252
- }
253
- const machineConfig = graphToMachineConfig(graph);
254
- if (hasActors) imports.add("fromPromise");
255
- const machineExpr = hasSetup ? `setup(${serializeJS(buildSetupObject(graph), 0)}).createMachine(${serializeJS(machineConfig, 0)})` : `createMachine(${serializeJS(machineConfig, 0)})`;
256
- const lines = [
257
- `import { ${Array.from(imports).join(", ")} } from 'xstate';`,
258
- "",
259
- `const machine = ${machineExpr};`
260
- ];
261
- if (exportStyle === "named") lines.push("", "export { machine };");
262
- else if (exportStyle === "default") lines.push("", "export default machine;");
263
- return `${lines.join("\n")}\n`;
264
- }
265
- function buildSetupObject(graph) {
266
- const setup = {};
267
- const schemas = graph.data.schemas;
268
- const impls = graph.data.implementations;
269
- const types = buildTypesBlock(schemas);
270
- if (types) setup.types = types;
271
- if (impls?.actions.length) setup.actions = buildActionsBlock(impls.actions);
272
- if (impls?.guards.length) setup.guards = buildGuardsBlock(impls.guards);
273
- if (impls?.actors.length) setup.actors = buildActorsBlock(impls.actors);
274
- if (impls?.delays.length) setup.delays = buildDelaysBlock(impls.delays);
275
- return setup;
276
- }
277
- function buildTypesBlock(schemas) {
278
- if (!schemas) return;
279
- const types = {};
280
- const contextType = contextSchemaToTSType(schemas.context);
281
- if (contextType) types.context = raw(`{} as ${contextType}`);
282
- const eventsType = eventsSchemaToTSType(schemas.events);
283
- if (eventsType) types.events = raw(`{} as\n | ${eventsType}`);
284
- if (schemas.input) types.input = raw(`{} as ${jsonSchemaToTSType(schemas.input)}`);
285
- if (schemas.output) types.output = raw(`{} as ${jsonSchemaToTSType(schemas.output)}`);
286
- return Object.keys(types).length > 0 ? types : void 0;
287
- }
288
- function buildActionsBlock(actions) {
289
- return Object.fromEntries(actions.map((action) => [action.name, (() => {
290
- const paramsType = action.paramsSchema ? `: ${jsonSchemaToTSType(action.paramsSchema)}` : "";
291
- return raw(action.code?.body ? `function ({ context, event }, params${paramsType}) {\n ${action.code.body}\n}` : `function ({ context, event }) {\n // TODO: implement ${action.name}\n}`);
292
- })()]));
293
- }
294
- function buildGuardsBlock(guards) {
295
- return Object.fromEntries(guards.map((guard) => [guard.name, (() => {
296
- const paramsType = guard.paramsSchema ? `: ${jsonSchemaToTSType(guard.paramsSchema)}` : "";
297
- return raw(guard.code?.body ? `function ({ context, event }, params${paramsType}) {\n ${guard.code.body}\n}` : `function ({ context, event }) {\n // TODO: implement ${guard.name}\n return false;\n}`);
298
- })()]));
299
- }
300
- function buildActorsBlock(actors) {
301
- return Object.fromEntries(actors.map((actor) => [actor.name, (() => {
302
- const inputType = actor.inputSchema ? `: { input: ${jsonSchemaToTSType(actor.inputSchema)} }` : "";
303
- return raw(actor.code?.body ? `fromPromise(async ({ input }${inputType}) => {\n ${actor.code.body}\n})` : `fromPromise(async ({ input }) => {\n // TODO: implement ${actor.name}\n})`);
304
- })()]));
305
- }
306
- function buildDelaysBlock(delays) {
307
- return Object.fromEntries(delays.map((delay) => [delay.name, raw(delay.code?.body ? `function ({ context, event }) {\n ${delay.code.body}\n}` : `function () {\n // TODO: implement ${delay.name}\n return 1000;\n}`)]));
308
- }
309
-
310
- //#endregion
311
- //#region src/sync.ts
312
- function isUrl(value) {
313
- try {
314
- const url = new URL(value);
315
- return url.protocol === "http:" || url.protocol === "https:";
316
- } catch {
317
- return false;
318
- }
319
- }
320
- async function fileExists(filePath) {
321
- try {
322
- await fs.access(filePath);
323
- return true;
324
- } catch {
325
- return false;
326
- }
327
- }
328
- function normalizeActions(value) {
329
- if (value == null) return [];
330
- return (Array.isArray(value) ? value : [value]).map((item) => {
331
- if (typeof item === "string") return { type: item };
332
- if (typeof item === "object" && item !== null && "type" in item) {
333
- const type = item.type;
334
- const params = "params" in item && typeof item.params === "object" && item.params ? item.params : void 0;
335
- return {
336
- type,
337
- ...params ? { params } : {}
338
- };
339
- }
340
- return { type: String(item) };
341
- });
342
- }
343
- function normalizeTags(value) {
344
- if (!value) return [];
345
- if (Array.isArray(value)) return value.map((tag) => String(tag));
346
- return [String(value)];
347
- }
348
- function normalizeInvoke(value) {
349
- if (!value) return [];
350
- return (Array.isArray(value) ? value : [value]).map((invoke, index) => ({
351
- src: invoke.src,
352
- id: invoke.id ?? `invoke[${index}]`,
353
- ...invoke.input ? { input: invoke.input } : {}
354
- }));
355
- }
356
- function resolveTargetId(sourceParentPath, target) {
357
- if (!target) return;
358
- if (target.startsWith("#")) {
359
- const stripped = target.slice(1);
360
- const machineIndex = stripped.indexOf(".");
361
- if (machineIndex >= 0) return `(machine).${stripped.slice(machineIndex + 1)}`;
362
- return `(machine).${stripped}`;
363
- }
364
- if (target.startsWith("(machine)")) return target;
365
- if (target.includes(".")) return `(machine).${target}`;
366
- return `${sourceParentPath}.${target}`;
367
- }
368
- function appendTransitionEdges(edges, sourceId, sourceParentPath, eventType, transition, edgeCounts) {
369
- const transitions = Array.isArray(transition) ? transition : [transition];
370
- for (const item of transitions) {
371
- const normalized = typeof item === "string" ? { target: item } : item;
372
- const targetId = resolveTargetId(sourceParentPath, Array.isArray(normalized.target) ? normalized.target[0] : normalized.target);
373
- const groupKey = `${sourceId}:${eventType}`;
374
- const index = edgeCounts.get(groupKey) ?? 0;
375
- edgeCounts.set(groupKey, index + 1);
376
- edges.push({
377
- type: "edge",
378
- id: `${sourceId}#${eventType}[${index}]`,
379
- sourceId,
380
- targetId: targetId ?? sourceId,
381
- label: eventType,
382
- data: {
383
- eventType,
384
- transitionType: normalized.reenter === true ? "reenter" : normalized.internal === true || !targetId ? "targetless" : "normal",
385
- ...normalized.guard ? { guard: typeof normalized.guard === "string" ? { type: normalized.guard } : {
386
- type: normalized.guard.type,
387
- ...normalized.guard.params ? { params: normalized.guard.params } : {}
388
- } } : {},
389
- actions: normalizeActions(normalized.actions),
390
- ...normalized.description ? { description: normalized.description } : {}
391
- }
392
- });
393
- }
394
- }
395
- function fromXStateConfig(config) {
396
- const nodes = [];
397
- const edges = [];
398
- const edgeCounts = /* @__PURE__ */ new Map();
399
- function visitNode(key, nodeConfig, parentId, parentPath) {
400
- const nodeId = parentPath ? `${parentPath}.${key}` : "(machine)";
401
- const currentPath = parentPath ? nodeId : "(machine)";
402
- const initialId = nodeConfig.initial ? `${currentPath}.${nodeConfig.initial}` : void 0;
403
- nodes.push({
404
- type: "node",
405
- id: nodeId,
406
- parentId,
407
- label: key,
408
- ...initialId ? { initialNodeId: initialId } : {},
409
- data: {
410
- key,
411
- ...nodeConfig.type ? { type: nodeConfig.type } : {},
412
- ...nodeConfig.history ? { history: nodeConfig.history } : {},
413
- ...initialId ? { initialId } : {},
414
- entry: normalizeActions(nodeConfig.entry),
415
- exit: normalizeActions(nodeConfig.exit),
416
- invokes: normalizeInvoke(nodeConfig.invoke),
417
- tags: normalizeTags(nodeConfig.tags),
418
- ...nodeConfig.description ? { description: nodeConfig.description } : {}
419
- }
420
- });
421
- for (const [eventType, transition] of Object.entries(nodeConfig.on ?? {})) appendTransitionEdges(edges, nodeId, parentPath ?? "(machine)", eventType, transition, edgeCounts);
422
- if (nodeConfig.always) appendTransitionEdges(edges, nodeId, parentPath ?? "(machine)", "", nodeConfig.always, edgeCounts);
423
- for (const [childKey, childConfig] of Object.entries(nodeConfig.states ?? {})) visitNode(childKey, childConfig, nodeId, currentPath);
424
- }
425
- visitNode("(machine)", config, null, null);
426
- return {
427
- id: config.id ?? "machine",
428
- nodes,
429
- edges,
430
- data: {}
431
- };
432
- }
433
- async function resolveLocalFile(locator, options) {
434
- const filePath = path.resolve(options.cwd ?? process.cwd(), locator);
435
- const contents = await fs.readFile(filePath, "utf8");
436
- const extension = path.extname(filePath).toLowerCase();
437
- if (extension === ".ts" || extension === ".tsx" || extension === ".js" || extension === ".jsx") {
438
- const config = (await (options.client ?? createStatelyClient({
439
- apiKey: options.apiKey,
440
- baseUrl: options.baseUrl,
441
- fetch: options.fetch
442
- })).code.extractMachines(contents)).machines[0]?.config;
443
- if (!config) throw new Error(`No machines extracted from ${filePath}`);
444
- return {
445
- kind: "local-file",
446
- locator: filePath,
447
- format: "xstate",
448
- graph: fromXStateConfig(config)
449
- };
450
- }
451
- const parsed = JSON.parse(contents);
452
- if ("rootNode" in parsed && "edges" in parsed) return {
453
- kind: "local-file",
454
- locator: filePath,
455
- format: "digraph",
456
- graph: fromStudioMachine(parsed)
457
- };
458
- if ("nodes" in parsed && "edges" in parsed) return {
459
- kind: "local-file",
460
- locator: filePath,
461
- format: "graph",
462
- graph: parsed
463
- };
464
- if ("states" in parsed) return {
465
- kind: "local-file",
466
- locator: filePath,
467
- format: "xstate",
468
- graph: fromXStateConfig(parsed)
469
- };
470
- throw new Error(`Unsupported local sync input: ${filePath}`);
471
- }
472
- function inferWritableTargetFormat(filePath) {
473
- if (filePath.endsWith(".ts") || filePath.endsWith(".tsx") || filePath.endsWith(".js") || filePath.endsWith(".jsx")) return "xstate";
474
- if (filePath.endsWith(".digraph.json")) return "digraph";
475
- if (filePath.endsWith(".graph.json")) return "graph";
476
- return null;
477
- }
478
- function serializeGraph(graph, format) {
479
- switch (format) {
480
- case "digraph": return `${JSON.stringify(toStudioMachine(graph), null, 2)}\n`;
481
- case "graph": return `${JSON.stringify(graph, null, 2)}\n`;
482
- case "xstate": return graphToXStateTS(graph);
483
- default: {
484
- const exhaustive = format;
485
- throw new Error(`Unsupported sync output format: ${exhaustive}`);
486
- }
487
- }
488
- }
489
- async function resolveRemoteMachine(machineId, baseUrl, kind, locator, options) {
490
- return {
491
- kind,
492
- locator,
493
- format: "digraph",
494
- graph: fromStudioMachine(await (options.client ?? createStatelyClient({
495
- apiKey: options.apiKey,
496
- baseUrl,
497
- fetch: options.fetch
498
- })).machines.get(machineId))
499
- };
500
- }
501
- async function resolveSyncInput(locator, options) {
502
- if (await fileExists(path.resolve(options.cwd ?? process.cwd(), locator))) return resolveLocalFile(locator, options);
503
- if (isUrl(locator)) {
504
- const url = new URL(locator);
505
- const machineId = url.pathname.split("/").filter(Boolean).at(-1);
506
- if (!machineId) throw new Error(`Could not resolve machine ID from URL: ${locator}`);
507
- return resolveRemoteMachine(machineId, url.origin, "studio-url", locator, options);
508
- }
509
- return resolveRemoteMachine(locator, options.baseUrl, "studio-machine-id", locator, options);
510
- }
511
- function summarizeDiff(diff) {
512
- const nodeChanges = diff.nodes.added.length + diff.nodes.removed.length + diff.nodes.updated.length;
513
- const edgeChanges = diff.edges.added.length + diff.edges.removed.length + diff.edges.updated.length;
514
- return {
515
- hasChanges: !isEmptyDiff(diff),
516
- nodeChanges,
517
- edgeChanges
518
- };
519
- }
520
- async function planSync(options) {
521
- const source = await resolveSyncInput(options.source, options);
522
- const target = await resolveSyncInput(options.target, options);
523
- const diff = getDiff(source.graph, target.graph);
524
- return {
525
- source,
526
- target,
527
- diff,
528
- summary: summarizeDiff(diff),
529
- warnings: []
530
- };
531
- }
532
- async function pullSync(options) {
533
- const source = await resolveSyncInput(options.source, options);
534
- const outputPath = path.resolve(options.cwd ?? process.cwd(), options.target);
535
- const fallbackFormat = inferWritableTargetFormat(outputPath);
536
- let targetFormat = fallbackFormat;
537
- if (await fileExists(outputPath)) try {
538
- targetFormat = (await resolveLocalFile(options.target, options)).format;
539
- } catch (error) {
540
- if (!fallbackFormat) throw error;
541
- }
542
- if (!targetFormat) throw new Error(`Could not infer a writable target format from ${outputPath}. Use an existing digraph/graph file or a .digraph.json/.graph.json target.`);
543
- const serialized = serializeGraph(source.graph, targetFormat);
544
- await fs.writeFile(outputPath, serialized, "utf8");
545
- return {
546
- source,
547
- target: {
548
- kind: "local-file",
549
- locator: outputPath,
550
- format: targetFormat,
551
- graph: source.graph
552
- },
553
- outputPath
554
- };
555
- }
556
-
557
- //#endregion
558
- export { pullSync as n, planSync as t };