@statelyai/sdk 0.5.1 → 0.6.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/README.md +207 -83
- package/dist/api.d.mts +20 -0
- package/dist/api.mjs +56 -0
- package/dist/assetStorage.d.mts +73 -0
- package/dist/assetStorage.mjs +99 -0
- package/dist/cli.d.mts +100 -2
- package/dist/cli.mjs +584 -13
- package/dist/embed.d.mts +29 -4
- package/dist/embed.mjs +65 -9
- package/dist/graph.d.mts +1 -1
- package/dist/graph.mjs +28 -11
- package/dist/{graphToXStateTS-CtecEESq.mjs → graphToXStateTS-Gzh0ZqbN.mjs} +166 -25
- package/dist/index.d.mts +33 -9
- package/dist/index.mjs +5 -3
- package/dist/{inspect-ttRIjoCu.d.mts → inspect-BMIJcsFh.d.mts} +1 -1
- package/dist/inspect.d.mts +2 -2
- package/dist/inspect.mjs +1 -1
- package/dist/{protocol-BPuwbNCz.d.mts → protocol-CDoCcaIP.d.mts} +70 -5
- package/dist/studio.d.mts +112 -2
- package/dist/studio.mjs +73 -11
- package/dist/sync.d.mts +19 -3
- package/dist/sync.mjs +128 -6
- package/dist/{transport-DoCHBLTu.mjs → transport-C0eTgNNu.mjs} +14 -0
- package/package.json +26 -14
- package/schemas/statelyai.schema.json +128 -0
- package/dist/studio-D2uQhrvX.d.mts +0 -54
- /package/dist/{graph-BfezxFKJ.d.mts → graph-DpBGHZwl.d.mts} +0 -0
package/dist/embed.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as EmbedMode, c as ExportFormatMap, f as
|
|
1
|
+
import { a as EmbedMode, c as ExportFormatMap, d as MachineSourceLocations, f as ProjectEmbedMachine, i as EmbedEventName, l as InitOptions, m as UploadResult, n as EmbedEventHandler, o as ExportCallOptions, r as EmbedEventMap, s as ExportFormat, t as CommentsConfig, u as MachineInitOptions } from "./protocol-CDoCcaIP.mjs";
|
|
2
|
+
import { AssetUploadAdapter } from "./assetStorage.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/embed.d.ts
|
|
4
5
|
interface AssetConfig {
|
|
@@ -7,9 +8,15 @@ interface AssetConfig {
|
|
|
7
8
|
* Receives a real File object (reconstructed from serialized data).
|
|
8
9
|
* Throwing or rejecting will show an error toast in the embed.
|
|
9
10
|
*/
|
|
10
|
-
onUploadRequest
|
|
11
|
+
onUploadRequest?: (file: File, context: {
|
|
11
12
|
stateNodeId: string;
|
|
12
13
|
}) => Promise<UploadResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Storage adapter used by the embed upload bridge. This keeps the editor
|
|
16
|
+
* protocol storage-neutral while allowing integrations to plug in S3, R2,
|
|
17
|
+
* Supabase, or any other backing store.
|
|
18
|
+
*/
|
|
19
|
+
adapter?: AssetUploadAdapter;
|
|
13
20
|
/**
|
|
14
21
|
* Accepted MIME types. Supports wildcards (e.g. 'image/*').
|
|
15
22
|
* @default ['image/*']
|
|
@@ -21,10 +28,24 @@ interface AssetConfig {
|
|
|
21
28
|
*/
|
|
22
29
|
maxFileSize?: number;
|
|
23
30
|
}
|
|
24
|
-
interface StatelyEmbedOptions {
|
|
31
|
+
interface StatelyEmbedOptions extends Partial<MachineInitOptions> {
|
|
25
32
|
baseUrl: string;
|
|
26
33
|
apiKey?: string;
|
|
27
34
|
origin?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Optional iframe to attach to immediately. Equivalent to calling
|
|
37
|
+
* `embed.attach(iframe)` after construction.
|
|
38
|
+
*/
|
|
39
|
+
iframe?: HTMLIFrameElement;
|
|
40
|
+
/**
|
|
41
|
+
* Project mode: pass machines instead of `machine`. If set, an init is
|
|
42
|
+
* auto-sent on attach using these machines.
|
|
43
|
+
*/
|
|
44
|
+
machines?: ProjectEmbedMachine[];
|
|
45
|
+
/** Project mode only — initially selected machine id. */
|
|
46
|
+
currentMachineId?: string;
|
|
47
|
+
/** Project mode only — initial comments keyed by machine id. */
|
|
48
|
+
commentsByMachineId?: Record<string, CommentsConfig>;
|
|
28
49
|
/** Asset upload configuration. If omitted, files are stored as base64 data URLs. */
|
|
29
50
|
assets?: AssetConfig;
|
|
30
51
|
onReady?: () => void;
|
|
@@ -45,6 +66,10 @@ interface StatelyEmbed {
|
|
|
45
66
|
init(options: InitOptions): void;
|
|
46
67
|
/** Update the machine (shorthand for update message). */
|
|
47
68
|
updateMachine(machine: unknown, format?: string, sourceLocations?: MachineSourceLocations): void;
|
|
69
|
+
/** Update the project machine list without re-creating the iframe. */
|
|
70
|
+
updateProject(machines: ProjectEmbedMachine[], currentMachineId?: string, commentsByMachineId?: Record<string, CommentsConfig>): void;
|
|
71
|
+
/** Select the active machine in project mode. */
|
|
72
|
+
selectMachine(machineId: string): void;
|
|
48
73
|
/** Change the embed mode. */
|
|
49
74
|
setMode(mode: EmbedMode): void;
|
|
50
75
|
/** Change the embed theme. */
|
|
@@ -64,4 +89,4 @@ interface StatelyEmbed {
|
|
|
64
89
|
}
|
|
65
90
|
declare function createStatelyEmbed(options: StatelyEmbedOptions): StatelyEmbed;
|
|
66
91
|
//#endregion
|
|
67
|
-
export { AssetConfig, type CommentsConfig, type EmbedEventHandler, type EmbedEventMap, type EmbedEventName, type EmbedMode, type ExportCallOptions, type ExportFormat, type ExportFormatMap, type InitOptions, type MachineSourceLocations, StatelyEmbed, StatelyEmbedOptions, type UploadResult, createStatelyEmbed };
|
|
92
|
+
export { AssetConfig, type CommentsConfig, type EmbedEventHandler, type EmbedEventMap, type EmbedEventName, type EmbedMode, type ExportCallOptions, type ExportFormat, type ExportFormatMap, type InitOptions, type MachineInitOptions, type MachineSourceLocations, type ProjectEmbedMachine, StatelyEmbed, StatelyEmbedOptions, type UploadResult, createStatelyEmbed };
|
package/dist/embed.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as toInitMessage, i as createPendingExportManager, r as createEventRegistry, t as createPostMessageTransport } from "./transport-
|
|
1
|
+
import { a as toInitMessage, i as createPendingExportManager, r as createEventRegistry, t as createPostMessageTransport } from "./transport-C0eTgNNu.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/embed.ts
|
|
4
4
|
function buildEmbedUrl(options) {
|
|
@@ -7,6 +7,32 @@ function buildEmbedUrl(options) {
|
|
|
7
7
|
if (options.apiKey) url.searchParams.set("api_key", options.apiKey);
|
|
8
8
|
return url.toString();
|
|
9
9
|
}
|
|
10
|
+
function extractInitOptions(options) {
|
|
11
|
+
if (options.machines) return {
|
|
12
|
+
machines: options.machines,
|
|
13
|
+
currentMachineId: options.currentMachineId,
|
|
14
|
+
mode: options.mode,
|
|
15
|
+
theme: options.theme,
|
|
16
|
+
readOnly: options.readOnly,
|
|
17
|
+
depth: options.depth,
|
|
18
|
+
panels: options.panels,
|
|
19
|
+
commentsByMachineId: options.commentsByMachineId,
|
|
20
|
+
unsavedIndicator: options.unsavedIndicator
|
|
21
|
+
};
|
|
22
|
+
if (options.machine !== void 0) return {
|
|
23
|
+
machine: options.machine,
|
|
24
|
+
format: options.format,
|
|
25
|
+
mode: options.mode,
|
|
26
|
+
theme: options.theme,
|
|
27
|
+
readOnly: options.readOnly,
|
|
28
|
+
depth: options.depth,
|
|
29
|
+
panels: options.panels,
|
|
30
|
+
comments: options.comments,
|
|
31
|
+
sourceLocations: options.sourceLocations,
|
|
32
|
+
unsavedIndicator: options.unsavedIndicator
|
|
33
|
+
};
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
10
36
|
function createStatelyEmbed(options) {
|
|
11
37
|
const embedUrl = buildEmbedUrl(options);
|
|
12
38
|
const targetOrigin = options.origin ?? "*";
|
|
@@ -17,6 +43,13 @@ function createStatelyEmbed(options) {
|
|
|
17
43
|
const pendingMessages = [];
|
|
18
44
|
const events = createEventRegistry();
|
|
19
45
|
const exportManager = createPendingExportManager((message) => send(message));
|
|
46
|
+
const assetUploadAdapter = options.assets?.adapter;
|
|
47
|
+
const uploadAsset = options.assets?.onUploadRequest ? (file, context) => options.assets.onUploadRequest(file, context) : assetUploadAdapter ? (file, context) => assetUploadAdapter.upload({
|
|
48
|
+
file,
|
|
49
|
+
...context
|
|
50
|
+
}) : void 0;
|
|
51
|
+
const assetAccept = options.assets?.accept ?? assetUploadAdapter?.accept;
|
|
52
|
+
const assetMaxFileSize = options.assets?.maxFileSize ?? assetUploadAdapter?.maxFileSize;
|
|
20
53
|
function send(msg) {
|
|
21
54
|
if (!transport?.ready) {
|
|
22
55
|
pendingMessages.push(msg);
|
|
@@ -37,11 +70,11 @@ function createStatelyEmbed(options) {
|
|
|
37
70
|
case "@statelyai.ready": {
|
|
38
71
|
const ready = data;
|
|
39
72
|
flush();
|
|
40
|
-
if (
|
|
73
|
+
if (uploadAsset) send({
|
|
41
74
|
type: "@statelyai.uploadCapabilities",
|
|
42
75
|
enabled: true,
|
|
43
|
-
accept:
|
|
44
|
-
maxFileSize:
|
|
76
|
+
accept: assetAccept,
|
|
77
|
+
maxFileSize: assetMaxFileSize
|
|
45
78
|
});
|
|
46
79
|
options.onReady?.();
|
|
47
80
|
events.emit("ready", { version: ready.version });
|
|
@@ -52,6 +85,7 @@ function createStatelyEmbed(options) {
|
|
|
52
85
|
options.onLoaded?.(loaded.graph);
|
|
53
86
|
events.emit("loaded", {
|
|
54
87
|
graph: loaded.graph,
|
|
88
|
+
machineId: loaded.machineId,
|
|
55
89
|
sourceLocations: loaded.sourceLocations
|
|
56
90
|
});
|
|
57
91
|
break;
|
|
@@ -62,6 +96,7 @@ function createStatelyEmbed(options) {
|
|
|
62
96
|
events.emit("change", {
|
|
63
97
|
graph: change.graph,
|
|
64
98
|
machineConfig: change.machineConfig,
|
|
99
|
+
machineId: change.machineId,
|
|
65
100
|
patches: change.patches,
|
|
66
101
|
sourceLocations: change.sourceLocations
|
|
67
102
|
});
|
|
@@ -73,12 +108,16 @@ function createStatelyEmbed(options) {
|
|
|
73
108
|
events.emit("save", {
|
|
74
109
|
graph: save.graph,
|
|
75
110
|
machineConfig: save.machineConfig,
|
|
111
|
+
machineId: save.machineId,
|
|
76
112
|
patches: save.patches,
|
|
77
113
|
validations: save.validations,
|
|
78
114
|
sourceLocations: save.sourceLocations
|
|
79
115
|
});
|
|
80
116
|
break;
|
|
81
117
|
}
|
|
118
|
+
case "@statelyai.project.machineSelected":
|
|
119
|
+
events.emit("machineSelected", { machineId: String(data.machineId) });
|
|
120
|
+
break;
|
|
82
121
|
case "@statelyai.retrieved": {
|
|
83
122
|
const retrieved = data;
|
|
84
123
|
exportManager.resolve(retrieved.requestId, retrieved.data);
|
|
@@ -97,7 +136,7 @@ function createStatelyEmbed(options) {
|
|
|
97
136
|
}
|
|
98
137
|
case "@statelyai.uploadRequest": {
|
|
99
138
|
const req = data;
|
|
100
|
-
if (!
|
|
139
|
+
if (!uploadAsset) {
|
|
101
140
|
send({
|
|
102
141
|
type: "@statelyai.error",
|
|
103
142
|
requestId: req.requestId,
|
|
@@ -106,7 +145,7 @@ function createStatelyEmbed(options) {
|
|
|
106
145
|
});
|
|
107
146
|
break;
|
|
108
147
|
}
|
|
109
|
-
const maxSize =
|
|
148
|
+
const maxSize = assetMaxFileSize ?? 10485760;
|
|
110
149
|
if (req.file.size > maxSize) {
|
|
111
150
|
send({
|
|
112
151
|
type: "@statelyai.error",
|
|
@@ -119,8 +158,7 @@ function createStatelyEmbed(options) {
|
|
|
119
158
|
const binary = atob(req.file.data);
|
|
120
159
|
const bytes = new Uint8Array(binary.length);
|
|
121
160
|
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
122
|
-
|
|
123
|
-
options.assets.onUploadRequest(file, { stateNodeId: req.stateNodeId }).then((result) => {
|
|
161
|
+
uploadAsset(new File([bytes], req.file.name, { type: req.file.mimeType }), { stateNodeId: req.stateNodeId }).then((result) => {
|
|
124
162
|
send({
|
|
125
163
|
type: "@statelyai.uploadResponse",
|
|
126
164
|
requestId: req.requestId,
|
|
@@ -151,7 +189,7 @@ function createStatelyEmbed(options) {
|
|
|
151
189
|
nextTransport.onMessage(handleMessage);
|
|
152
190
|
nextTransport.onReady(flush);
|
|
153
191
|
}
|
|
154
|
-
|
|
192
|
+
const embed = {
|
|
155
193
|
attach(el) {
|
|
156
194
|
if (destroyed) return;
|
|
157
195
|
const currentSrc = el.getAttribute("src");
|
|
@@ -190,6 +228,20 @@ function createStatelyEmbed(options) {
|
|
|
190
228
|
sourceLocations
|
|
191
229
|
});
|
|
192
230
|
},
|
|
231
|
+
updateProject(machines, currentMachineId, commentsByMachineId) {
|
|
232
|
+
send({
|
|
233
|
+
type: "@statelyai.project.update",
|
|
234
|
+
machines,
|
|
235
|
+
currentMachineId,
|
|
236
|
+
commentsByMachineId
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
selectMachine(machineId) {
|
|
240
|
+
send({
|
|
241
|
+
type: "@statelyai.project.selectMachine",
|
|
242
|
+
machineId
|
|
243
|
+
});
|
|
244
|
+
},
|
|
193
245
|
setMode(mode) {
|
|
194
246
|
send({
|
|
195
247
|
type: "@statelyai.setMode",
|
|
@@ -236,6 +288,10 @@ function createStatelyEmbed(options) {
|
|
|
236
288
|
iframe = null;
|
|
237
289
|
}
|
|
238
290
|
};
|
|
291
|
+
if (options.iframe) embed.attach(options.iframe);
|
|
292
|
+
const autoInit = extractInitOptions(options);
|
|
293
|
+
if (autoInit) embed.init(autoInit);
|
|
294
|
+
return embed;
|
|
239
295
|
}
|
|
240
296
|
|
|
241
297
|
//#endregion
|
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-
|
|
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-DpBGHZwl.mjs";
|
|
2
2
|
export { StatelyAction, StatelyActorImplementation, StatelyEdgeData, StatelyGraph, StatelyGraphData, StatelyGuard, StatelyImplementation, StatelyInvoke, StatelyNodeData, StatelyTagImplementation, StudioAction, StudioEdge, StudioEventTypeData, StudioMachine, StudioNode, fromStudioMachine, studioMachineConverter, toStudioMachine };
|
package/dist/graph.mjs
CHANGED
|
@@ -2,6 +2,13 @@ import { createFormatConverter, createGraph } from "@statelyai/graph";
|
|
|
2
2
|
|
|
3
3
|
//#region src/graph.ts
|
|
4
4
|
const EXPR_ACTION_TYPE = "xstate.expr";
|
|
5
|
+
function stripMarkdownLinks(code) {
|
|
6
|
+
return code.replace(/\[([^\]\n]+)\]\(([^)\n]+)\)/g, "$1");
|
|
7
|
+
}
|
|
8
|
+
function stripExportDefault(code) {
|
|
9
|
+
const normalized = stripMarkdownLinks(code).trim();
|
|
10
|
+
return normalized.match(/^export\s+default\s+(.+)/s)?.[1]?.trim() ?? normalized;
|
|
11
|
+
}
|
|
5
12
|
function toJsonObject(value) {
|
|
6
13
|
return value;
|
|
7
14
|
}
|
|
@@ -126,7 +133,7 @@ function toStudioAction(action) {
|
|
|
126
133
|
}
|
|
127
134
|
};
|
|
128
135
|
}
|
|
129
|
-
function fromStudioAction(action) {
|
|
136
|
+
function fromStudioAction(action, actionImplementations) {
|
|
130
137
|
if (action.kind === "inline") return {
|
|
131
138
|
type: EXPR_ACTION_TYPE,
|
|
132
139
|
params: {
|
|
@@ -134,6 +141,16 @@ function fromStudioAction(action) {
|
|
|
134
141
|
lang: "ts"
|
|
135
142
|
}
|
|
136
143
|
};
|
|
144
|
+
if (action.action.type.startsWith("inline:") && actionImplementations?.[action.action.type]?.code) {
|
|
145
|
+
const implementation = actionImplementations[action.action.type];
|
|
146
|
+
return {
|
|
147
|
+
type: EXPR_ACTION_TYPE,
|
|
148
|
+
params: {
|
|
149
|
+
code: stripExportDefault(implementation.code),
|
|
150
|
+
lang: "ts"
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
137
154
|
if ("params" in action.action && action.action.params) return {
|
|
138
155
|
type: action.action.type,
|
|
139
156
|
params: toUnknownRecord(action.action.params)
|
|
@@ -204,7 +221,7 @@ function toStudioNode(graph, nodeId, pathIds, parentPath) {
|
|
|
204
221
|
nodes: childNodes
|
|
205
222
|
};
|
|
206
223
|
}
|
|
207
|
-
function flattenStudioNodes(studioNode, parentId, nodes, pathIdToNodeId) {
|
|
224
|
+
function flattenStudioNodes(studioNode, parentId, nodes, pathIdToNodeId, actionImplementations) {
|
|
208
225
|
const nodeId = studioNode.id;
|
|
209
226
|
pathIdToNodeId.set(studioNode.id, nodeId);
|
|
210
227
|
const initialId = studioNode.data.initial ? `${studioNode.id}.${studioNode.data.initial}` : void 0;
|
|
@@ -227,8 +244,8 @@ function flattenStudioNodes(studioNode, parentId, nodes, pathIdToNodeId) {
|
|
|
227
244
|
...normalizeStudioNodeType(studioNode.data.type) ? { type: normalizeStudioNodeType(studioNode.data.type) } : {},
|
|
228
245
|
...studioNode.data.history ? { history: studioNode.data.history } : {},
|
|
229
246
|
...initialId ? { initialId } : {},
|
|
230
|
-
entry: studioNode.data.entry.map(fromStudioAction),
|
|
231
|
-
exit: studioNode.data.exit.map(fromStudioAction),
|
|
247
|
+
entry: studioNode.data.entry.map((action) => fromStudioAction(action, actionImplementations)),
|
|
248
|
+
exit: studioNode.data.exit.map((action) => fromStudioAction(action, actionImplementations)),
|
|
232
249
|
invokes: studioNode.data.invoke.map((invoke) => ({
|
|
233
250
|
src: invoke.src,
|
|
234
251
|
id: invoke.id,
|
|
@@ -240,7 +257,7 @@ function flattenStudioNodes(studioNode, parentId, nodes, pathIdToNodeId) {
|
|
|
240
257
|
...studioNode.data.metaEntries ? { meta: Object.fromEntries(studioNode.data.metaEntries) } : {}
|
|
241
258
|
}
|
|
242
259
|
});
|
|
243
|
-
studioNode.nodes.forEach((child) => flattenStudioNodes(child, nodeId, nodes, pathIdToNodeId));
|
|
260
|
+
studioNode.nodes.forEach((child) => flattenStudioNodes(child, nodeId, nodes, pathIdToNodeId, actionImplementations));
|
|
244
261
|
}
|
|
245
262
|
function toStudioMachine(graph) {
|
|
246
263
|
const rootNode = graph.nodes.find((node) => node.parentId == null);
|
|
@@ -298,7 +315,7 @@ function toStudioMachine(graph) {
|
|
|
298
315
|
function fromStudioMachine(studioMachine) {
|
|
299
316
|
const nodes = [];
|
|
300
317
|
const pathIdToNodeId = /* @__PURE__ */ new Map();
|
|
301
|
-
flattenStudioNodes(studioMachine.rootNode, null, nodes, pathIdToNodeId);
|
|
318
|
+
flattenStudioNodes(studioMachine.rootNode, null, nodes, pathIdToNodeId, studioMachine.implementations?.actions);
|
|
302
319
|
const edges = studioMachine.edges.map((edge) => ({
|
|
303
320
|
type: "edge",
|
|
304
321
|
id: edge.id,
|
|
@@ -319,9 +336,9 @@ function fromStudioMachine(studioMachine) {
|
|
|
319
336
|
...edge.data.guard ? { guard: {
|
|
320
337
|
type: edge.data.guard.type,
|
|
321
338
|
params: toUnknownRecord(edge.data.guard.params),
|
|
322
|
-
...edge.data.guard.kind === "inline" ? { code: edge.data.guard.type } : {}
|
|
339
|
+
...edge.data.guard.kind === "inline" ? { code: stripExportDefault(studioMachine.implementations?.guards?.[edge.data.guard.type]?.code ?? edge.data.guard.type) } : {}
|
|
323
340
|
} } : {},
|
|
324
|
-
actions: edge.data.actions.map(fromStudioAction),
|
|
341
|
+
actions: edge.data.actions.map((action) => fromStudioAction(action, studioMachine.implementations?.actions)),
|
|
325
342
|
...edge.data.description ? { description: edge.data.description } : {},
|
|
326
343
|
...edge.data.color ? { color: edge.data.color } : {},
|
|
327
344
|
...edge.data.metaEntries ? { meta: Object.fromEntries(edge.data.metaEntries) } : {}
|
|
@@ -337,9 +354,9 @@ function fromStudioMachine(studioMachine) {
|
|
|
337
354
|
data: {
|
|
338
355
|
...studioMachine.schemas ? { schemas: studioMachine.schemas } : {},
|
|
339
356
|
...studioMachine.implementations ? { implementations: {
|
|
340
|
-
actions: Object.
|
|
341
|
-
guards: Object.
|
|
342
|
-
actors: Object.
|
|
357
|
+
actions: Object.entries(studioMachine.implementations.actions).filter(([key]) => !key.startsWith("inline:")).map(([, source]) => fromActionSource(source)),
|
|
358
|
+
guards: Object.entries(studioMachine.implementations.guards).filter(([key]) => !key.startsWith("inline:")).map(([, guard]) => fromGuardSource(guard)),
|
|
359
|
+
actors: Object.entries(studioMachine.implementations.actors).filter(([key]) => !key.startsWith("inline:") && !/:invocation\[\d+\]$/.test(key)).map(([, actor]) => fromActorSource(actor)),
|
|
343
360
|
delays: [],
|
|
344
361
|
tags: []
|
|
345
362
|
} } : {}
|
|
@@ -1,4 +1,104 @@
|
|
|
1
|
-
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
//#region src/statelyPragma.ts
|
|
4
|
+
function findStatelyPragmaAttachments(sourceText, fileName = "machine.ts") {
|
|
5
|
+
const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(fileName));
|
|
6
|
+
const attachments = [];
|
|
7
|
+
const visit = (node) => {
|
|
8
|
+
if (ts.isCallExpression(node) && isCreateMachineExpression(node.expression)) {
|
|
9
|
+
const attachNode = findAttachNode(node);
|
|
10
|
+
if (attachNode) attachments.push({
|
|
11
|
+
machineStart: getMachineExpressionStart(node.expression, sourceFile),
|
|
12
|
+
attachStart: attachNode.getStart(sourceFile),
|
|
13
|
+
pragma: findAttachedStatelyPragma(sourceText, attachNode)
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
ts.forEachChild(node, visit);
|
|
17
|
+
};
|
|
18
|
+
visit(sourceFile);
|
|
19
|
+
return attachments;
|
|
20
|
+
}
|
|
21
|
+
function getStatelyPragma(sourceText, fileName = "machine.ts", machineIndex = 0) {
|
|
22
|
+
return findStatelyPragmaAttachments(sourceText, fileName)[machineIndex]?.pragma;
|
|
23
|
+
}
|
|
24
|
+
function upsertStatelyPragma(sourceText, id, options = {}) {
|
|
25
|
+
const attachment = findStatelyPragmaAttachments(sourceText, options.fileName ?? "machine.ts")[options.machineIndex ?? 0];
|
|
26
|
+
if (!attachment) return sourceText;
|
|
27
|
+
const lineEnding = options.lineEnding ?? detectLineEnding(sourceText);
|
|
28
|
+
const canonicalComment = `// @statelyai id=${id}`;
|
|
29
|
+
if (attachment.pragma) return sourceText.slice(0, attachment.pragma.start) + canonicalComment + sourceText.slice(attachment.pragma.end);
|
|
30
|
+
const insertText = `${getIndentationAtOffset(sourceText, attachment.attachStart)}${canonicalComment}${lineEnding}`;
|
|
31
|
+
return sourceText.slice(0, attachment.attachStart) + insertText + sourceText.slice(attachment.attachStart);
|
|
32
|
+
}
|
|
33
|
+
function findAttachedStatelyPragma(sourceText, node) {
|
|
34
|
+
const comments = ts.getLeadingCommentRanges(sourceText, node.getFullStart()) ?? [];
|
|
35
|
+
for (let index = comments.length - 1; index >= 0; index -= 1) {
|
|
36
|
+
const comment = comments[index];
|
|
37
|
+
const parsed = parseStatelyPragma(sourceText, comment.pos, comment.end);
|
|
38
|
+
if (parsed) return parsed;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function parseStatelyPragma(sourceText, start, end) {
|
|
42
|
+
const match = normalizeCommentContent(sourceText.slice(start, end)).match(/^@statelyai(?:\s+(.*))?$/s);
|
|
43
|
+
if (!match) return;
|
|
44
|
+
const fields = parseFields(match[1] ?? "");
|
|
45
|
+
return {
|
|
46
|
+
tag: "@statelyai",
|
|
47
|
+
id: fields.id,
|
|
48
|
+
fields,
|
|
49
|
+
start,
|
|
50
|
+
end
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function normalizeCommentContent(rawComment) {
|
|
54
|
+
if (rawComment.startsWith("//")) return rawComment.slice(2).trim();
|
|
55
|
+
if (rawComment.startsWith("/*")) return rawComment.slice(2, rawComment.endsWith("*/") ? -2 : void 0).split(/\r?\n/).map((line) => line.replace(/^\s*\*\s?/, "")).join(" ").trim();
|
|
56
|
+
return rawComment.trim();
|
|
57
|
+
}
|
|
58
|
+
function parseFields(input) {
|
|
59
|
+
const fields = {};
|
|
60
|
+
for (const token of input.split(/\s+/)) {
|
|
61
|
+
if (!token) continue;
|
|
62
|
+
const equalsIndex = token.indexOf("=");
|
|
63
|
+
if (equalsIndex <= 0) continue;
|
|
64
|
+
const key = token.slice(0, equalsIndex);
|
|
65
|
+
const value = token.slice(equalsIndex + 1);
|
|
66
|
+
if (!key || !value) continue;
|
|
67
|
+
fields[key] = value;
|
|
68
|
+
}
|
|
69
|
+
return fields;
|
|
70
|
+
}
|
|
71
|
+
function findAttachNode(node) {
|
|
72
|
+
let current = node;
|
|
73
|
+
while (current?.parent) {
|
|
74
|
+
if (ts.isVariableStatement(current.parent) || ts.isExpressionStatement(current.parent) || ts.isExportAssignment(current.parent)) return current.parent;
|
|
75
|
+
current = current.parent;
|
|
76
|
+
}
|
|
77
|
+
return current;
|
|
78
|
+
}
|
|
79
|
+
function isCreateMachineExpression(expression) {
|
|
80
|
+
return ts.isIdentifier(expression) && expression.text === "createMachine" || ts.isPropertyAccessExpression(expression) && expression.name.text === "createMachine";
|
|
81
|
+
}
|
|
82
|
+
function getMachineExpressionStart(expression, sourceFile) {
|
|
83
|
+
if (ts.isPropertyAccessExpression(expression)) return expression.name.getStart(sourceFile);
|
|
84
|
+
return expression.getStart(sourceFile);
|
|
85
|
+
}
|
|
86
|
+
function getIndentationAtOffset(sourceText, offset) {
|
|
87
|
+
const lineStart = sourceText.lastIndexOf("\n", Math.max(0, offset - 1)) + 1;
|
|
88
|
+
return sourceText.slice(lineStart, offset).match(/^[ \t]*/)?.[0] ?? "";
|
|
89
|
+
}
|
|
90
|
+
function detectLineEnding(sourceText) {
|
|
91
|
+
return sourceText.includes("\r\n") ? "\r\n" : "\n";
|
|
92
|
+
}
|
|
93
|
+
function getScriptKind(fileName) {
|
|
94
|
+
if (fileName.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
95
|
+
if (fileName.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
96
|
+
if (fileName.endsWith(".js")) return ts.ScriptKind.JS;
|
|
97
|
+
return ts.ScriptKind.TS;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region ../graph-tools/src/serializeJS.ts
|
|
2
102
|
/**
|
|
3
103
|
* Serializes JavaScript values as JS source code (not JSON).
|
|
4
104
|
* - Unquoted keys for valid identifiers
|
|
@@ -67,33 +167,19 @@ function indentRawCode(code, indent) {
|
|
|
67
167
|
}
|
|
68
168
|
|
|
69
169
|
//#endregion
|
|
70
|
-
//#region src/textUtils.ts
|
|
71
|
-
/**
|
|
72
|
-
* Pure string utilities used by codegen.
|
|
73
|
-
*/
|
|
74
|
-
/**
|
|
75
|
-
* Removes common leading whitespace from all non-empty lines.
|
|
76
|
-
*/
|
|
77
|
-
function dedent(text) {
|
|
78
|
-
const lines = text.split("\n");
|
|
79
|
-
const nonEmptyLines = lines.filter((l) => l.trim().length > 0);
|
|
80
|
-
if (nonEmptyLines.length === 0) return text;
|
|
81
|
-
const minIndent = Math.min(...nonEmptyLines.map((l) => l.match(/^(\s*)/)[1].length));
|
|
82
|
-
if (minIndent === 0) return text;
|
|
83
|
-
return lines.map((l) => l.trim().length > 0 ? l.slice(minIndent) : l).join("\n");
|
|
84
|
-
}
|
|
170
|
+
//#region ../graph-tools/src/textUtils.ts
|
|
85
171
|
/**
|
|
86
172
|
* Strips `export default` wrapper from an expression string.
|
|
87
173
|
* E.g. `"export default assign({ ... })"` → `"assign({ ... })"`
|
|
88
174
|
*/
|
|
89
|
-
function stripExportDefault(code) {
|
|
175
|
+
function stripExportDefault$1(code) {
|
|
90
176
|
const trimmed = code.trim();
|
|
91
177
|
if (trimmed.startsWith("export default ")) return trimmed.slice(15).replace(/;$/, "");
|
|
92
178
|
return trimmed;
|
|
93
179
|
}
|
|
94
180
|
|
|
95
181
|
//#endregion
|
|
96
|
-
//#region src/graphToMachineConfig.ts
|
|
182
|
+
//#region ../graph-tools/src/graphToMachineConfig.ts
|
|
97
183
|
function isAutoGeneratedId(id) {
|
|
98
184
|
return !!id && id.startsWith("$auto-");
|
|
99
185
|
}
|
|
@@ -217,7 +303,7 @@ function serializeActionItem(action) {
|
|
|
217
303
|
return raw(`assign({ ${entries.map(([k, v]) => `${JSON.stringify(k)}: ${formatAssignValue(v)}`).join(", ")} })`);
|
|
218
304
|
}
|
|
219
305
|
default:
|
|
220
|
-
if (exprCode) return raw(stripExportDefault(exprCode));
|
|
306
|
+
if (exprCode) return raw(stripExportDefault$1(exprCode));
|
|
221
307
|
if (!params || Object.keys(params).length === 0) return { type };
|
|
222
308
|
return {
|
|
223
309
|
type,
|
|
@@ -282,9 +368,9 @@ function graphToMachineConfig(graph, options = {}) {
|
|
|
282
368
|
const type = edge.data.eventType ?? "";
|
|
283
369
|
const transitionMeta = showMeta ? buildMeta(edge.data.meta, edge.data.color) : void 0;
|
|
284
370
|
const transitionObject = {
|
|
285
|
-
target: `${resolvedTarget}`,
|
|
371
|
+
target: edge.data.transitionType === "targetless" ? void 0 : `${resolvedTarget}`,
|
|
286
372
|
...edge.data.transitionType === "reenter" ? { reenter: true } : void 0,
|
|
287
|
-
guard: edge.data.guard ? edge.data.guard.code ? raw(stripExportDefault(edge.data.guard.code)) : edge.data.guard : void 0,
|
|
373
|
+
guard: edge.data.guard ? edge.data.guard.code ? raw(stripExportDefault$1(edge.data.guard.code)) : edge.data.guard : void 0,
|
|
288
374
|
actions: edge.data.actions?.length ? edge.data.actions.map(serializeActionItem) : void 0,
|
|
289
375
|
description: edge.data.description ?? void 0,
|
|
290
376
|
...transitionMeta ? { meta: transitionMeta } : void 0
|
|
@@ -420,12 +506,38 @@ function eventsSchemaToTSType(events) {
|
|
|
420
506
|
}).join("\n | ");
|
|
421
507
|
}
|
|
422
508
|
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/textUtils.ts
|
|
511
|
+
/**
|
|
512
|
+
* Pure string utilities used by codegen.
|
|
513
|
+
*/
|
|
514
|
+
/**
|
|
515
|
+
* Removes common leading whitespace from all non-empty lines.
|
|
516
|
+
*/
|
|
517
|
+
function dedent(text) {
|
|
518
|
+
const lines = text.split("\n");
|
|
519
|
+
const nonEmptyLines = lines.filter((l) => l.trim().length > 0);
|
|
520
|
+
if (nonEmptyLines.length === 0) return text;
|
|
521
|
+
const minIndent = Math.min(...nonEmptyLines.map((l) => l.match(/^(\s*)/)[1].length));
|
|
522
|
+
if (minIndent === 0) return text;
|
|
523
|
+
return lines.map((l) => l.trim().length > 0 ? l.slice(minIndent) : l).join("\n");
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Strips `export default` wrapper from an expression string.
|
|
527
|
+
* E.g. `"export default assign({ ... })"` → `"assign({ ... })"`
|
|
528
|
+
*/
|
|
529
|
+
function stripExportDefault(code) {
|
|
530
|
+
const trimmed = code.trim();
|
|
531
|
+
if (trimmed.startsWith("export default ")) return trimmed.slice(15).replace(/;$/, "");
|
|
532
|
+
return trimmed;
|
|
533
|
+
}
|
|
534
|
+
|
|
423
535
|
//#endregion
|
|
424
536
|
//#region src/graphToXStateTS.ts
|
|
425
537
|
function graphToXStateTS(graph, options = {}) {
|
|
426
538
|
const { exportStyle = "named", ...configOptions } = options;
|
|
427
539
|
const schemas = graph.data.schemas;
|
|
428
|
-
const impls = graph.data.implementations;
|
|
540
|
+
const impls = filterInlineImplementations(graph.data.implementations);
|
|
429
541
|
const hasSchemas = !!(schemas && (schemas.context || schemas.events || schemas.input || schemas.output));
|
|
430
542
|
const hasActions = !!impls?.actions.length;
|
|
431
543
|
const hasGuards = !!impls?.guards.length;
|
|
@@ -459,6 +571,15 @@ function graphToXStateTS(graph, options = {}) {
|
|
|
459
571
|
}
|
|
460
572
|
return lines.join("\n") + "\n";
|
|
461
573
|
}
|
|
574
|
+
function filterInlineImplementations(impls) {
|
|
575
|
+
if (!impls) return impls;
|
|
576
|
+
return {
|
|
577
|
+
...impls,
|
|
578
|
+
actions: impls.actions.filter((action) => !action.name.startsWith("inline:")),
|
|
579
|
+
guards: impls.guards.filter((guard) => !guard.name.startsWith("inline:")),
|
|
580
|
+
delays: impls.delays.filter((delay) => !delay.name.startsWith("inline:"))
|
|
581
|
+
};
|
|
582
|
+
}
|
|
462
583
|
/** Map from xstate action type to the import name */
|
|
463
584
|
const BUILTIN_ACTION_IMPORTS = {
|
|
464
585
|
"xstate.raise": "raise",
|
|
@@ -536,6 +657,11 @@ function hasSchemaProperties(schema) {
|
|
|
536
657
|
function buildActionsBlock(actions) {
|
|
537
658
|
const block = {};
|
|
538
659
|
for (const implementation of actions) if (implementation.code?.body) {
|
|
660
|
+
const exportedExpression = getExportDefaultExpression(implementation.code.body);
|
|
661
|
+
if (exportedExpression) {
|
|
662
|
+
block[implementation.name] = raw(exportedExpression);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
539
665
|
const params = hasSchemaProperties(implementation.paramsSchema) ? `, params: ${jsonSchemaToTSType(implementation.paramsSchema)}` : "";
|
|
540
666
|
block[implementation.name] = raw(`function ({ context, event }${params ? params : ""}) {\n ${dedent(implementation.code.body)}\n}`);
|
|
541
667
|
} else block[implementation.name] = raw(`function ({ context, event }) {\n // TODO: implement ${implementation.name}\n}`);
|
|
@@ -544,6 +670,11 @@ function buildActionsBlock(actions) {
|
|
|
544
670
|
function buildGuardsBlock(guards) {
|
|
545
671
|
const block = {};
|
|
546
672
|
for (const guard of guards) if (guard.code?.body) {
|
|
673
|
+
const exportedExpression = getExportDefaultExpression(guard.code.body);
|
|
674
|
+
if (exportedExpression) {
|
|
675
|
+
block[guard.name] = raw(exportedExpression);
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
547
678
|
const params = hasSchemaProperties(guard.paramsSchema) ? `, params: ${jsonSchemaToTSType(guard.paramsSchema)}` : "";
|
|
548
679
|
block[guard.name] = raw(`function ({ context, event }${params ? params : ""}) {\n ${dedent(guard.code.body)}\n}`);
|
|
549
680
|
} else block[guard.name] = raw(`function ({ context, event }) {\n // TODO: implement ${guard.name}\n return false;\n}`);
|
|
@@ -559,10 +690,20 @@ function buildActorsBlock(actors) {
|
|
|
559
690
|
}
|
|
560
691
|
function buildDelaysBlock(delays) {
|
|
561
692
|
const block = {};
|
|
562
|
-
for (const delay of delays) if (delay.code?.body)
|
|
563
|
-
|
|
693
|
+
for (const delay of delays) if (delay.code?.body) {
|
|
694
|
+
const exportedExpression = getExportDefaultExpression(delay.code.body);
|
|
695
|
+
if (exportedExpression) {
|
|
696
|
+
block[delay.name] = raw(exportedExpression);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
block[delay.name] = raw(`function ({ context, event }) {\n ${dedent(delay.code.body)}\n}`);
|
|
700
|
+
} else block[delay.name] = raw(`function () {\n // TODO: implement ${delay.name}\n return 1000;\n}`);
|
|
564
701
|
return block;
|
|
565
702
|
}
|
|
703
|
+
function getExportDefaultExpression(code) {
|
|
704
|
+
const stripped = stripExportDefault(code);
|
|
705
|
+
return stripped === code.trim() ? void 0 : stripped;
|
|
706
|
+
}
|
|
566
707
|
|
|
567
708
|
//#endregion
|
|
568
|
-
export { graphToMachineConfig as a, serializeJS as c, jsonSchemaToTSType as i, contextSchemaToTSType as n, RawCode as o, eventsSchemaToTSType as r, raw as s, graphToXStateTS as t };
|
|
709
|
+
export { graphToMachineConfig as a, serializeJS as c, upsertStatelyPragma as d, jsonSchemaToTSType as i, findStatelyPragmaAttachments as l, contextSchemaToTSType as n, RawCode as o, eventsSchemaToTSType as r, raw as s, graphToXStateTS as t, getStatelyPragma as u };
|