@statelyai/sdk 0.20.3 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,6 +37,7 @@ embed.init({
37
37
  format: 'xstate',
38
38
  mode: 'editing',
39
39
  theme: 'dark',
40
+ settings: { canvas: { viewMode: 'list' } },
40
41
  documents: [
41
42
  { path: 'README.md', content: '# How this workflow works' },
42
43
  { path: 'src/machine.ts', content: machineSource },
@@ -44,6 +45,16 @@ embed.init({
44
45
  });
45
46
  ```
46
47
 
48
+ Use `viewMode: 'list'` on `createStatelyEmbed` to add `?view=list` to the
49
+ iframe URL, or subscribe to `viewModeChanged` when the user switches views.
50
+
51
+ Custom profiles and formats can be supplied when the embed is created. Their
52
+ serializable manifests are sent to the iframe; detector, importer, exporter,
53
+ and validator implementations remain in the host and are called asynchronously.
54
+ RPC methods and payloads are validated before host code runs, and malformed
55
+ graphs returned by custom importers are rejected.
56
+ Use `defaultProfile` and `defaultFormat` to choose the initial custom pair.
57
+
47
58
  Restrict an embed by passing a resolved access policy:
48
59
 
49
60
  <!-- restricted MachineInitOptions from src/protocol.ts -->
@@ -1,5 +1,5 @@
1
- import { t as EDITOR_PROTOCOL_VERSION } from "./editorHostProtocol-D_h4y3rX.mjs";
2
- import { c as object, f as unknown, i as custom, l as record, n as array, o as literal, r as boolean, s as number, t as _enum, u as string } from "./schemas-b9wG-zYl.mjs";
1
+ import { c as object, f as unknown, i as custom, l as record, n as array, o as literal, r as boolean, s as number, t as _enum, u as string } from "./openapi-BTDjYWXd.mjs";
2
+ import { t as EDITOR_PROTOCOL_VERSION } from "./editorHostProtocol-Bko7wge8.mjs";
3
3
 
4
4
  //#region ../api-contract/dist/editor-sync.mjs
5
5
  const EDITOR_SYNC_PROTOCOL_VERSION = 1;
@@ -1,4 +1,5 @@
1
- import { t as EDITOR_PROTOCOL_VERSION } from "./editorHostProtocol-D_h4y3rX.mjs";
2
- import { n as createRemoteEditorSyncBackend, r as fetchEditorSyncAccess, t as EditorHostSession } from "./editorHostSession-CCTwvUOB.mjs";
1
+ import "./openapi-BTDjYWXd.mjs";
2
+ import { t as EDITOR_PROTOCOL_VERSION } from "./editorHostProtocol-Bko7wge8.mjs";
3
+ import { n as createRemoteEditorSyncBackend, r as fetchEditorSyncAccess, t as EditorHostSession } from "./editorHostSession-HRuIaZ2G.mjs";
3
4
 
4
5
  export { EDITOR_PROTOCOL_VERSION, EditorHostSession, createRemoteEditorSyncBackend, fetchEditorSyncAccess };
package/dist/embed.d.mts CHANGED
@@ -1,5 +1,6 @@
1
- import { i as StatelyGraph } from "./graph-yDK-MM3G.mjs";
2
- import { CapabilityDeniedEvent, CommentsConfig, EditorCapabilities, EmbedDocument, EmbedDocumentLanguage, EmbedEventHandler, EmbedEventMap, EmbedEventName, EmbedMode, ExportCallOptions, ExportFormat, ExportFormatMap, InitOptions, MachineInitOptions, MachineSourceLocations, ProjectEmbedMachine, UploadResult } from "./protocol.mjs";
1
+ import { i as ProfileManifest } from "./index-Bakx1tkW.mjs";
2
+ import { i as StatelyGraph } from "./graph-DYjfFztq.mjs";
3
+ import { CapabilityDeniedEvent, CommentsConfig, EditorCapabilities, EmbedDocument, EmbedDocumentLanguage, EmbedEventHandler, EmbedEventMap, EmbedEventName, EmbedExtensionsManifest, EmbedMode, ExportCallOptions, ExportFormat, ExportFormatMap, InitOptions, MachineInitOptions, MachineSourceLocations, ProjectEmbedMachine, UploadResult } from "./protocol.mjs";
3
4
  import { AssetUploadAdapter } from "./assetStorage.mjs";
4
5
 
5
6
  //#region src/embed.d.ts
@@ -29,9 +30,50 @@ interface AssetConfig {
29
30
  */
30
31
  maxFileSize?: number;
31
32
  }
33
+ type EmbedEntity = {
34
+ data?: Record<string, unknown>;
35
+ };
36
+ type EmbedGraph = EmbedEntity & {
37
+ nodes?: EmbedEntity[];
38
+ edges?: EmbedEntity[];
39
+ };
40
+ type EmbedFormatContext = {
41
+ profile: EmbedProfileDefinition;
42
+ resolveGraphData(graph: EmbedGraph): Record<string, unknown>;
43
+ resolveNodeData(node: EmbedEntity): Record<string, unknown>;
44
+ resolveEdgeData(edge: EmbedEntity): Record<string, unknown>;
45
+ };
46
+ type EmbedProfileDefinition = Omit<ProfileManifest, 'validation'> & {
47
+ validation?: Array<{
48
+ name: string;
49
+ description: string;
50
+ validate?(context: {
51
+ graph: EmbedGraph;
52
+ profile: ProfileManifest;
53
+ revision?: number;
54
+ resolveGraphData(graph: EmbedGraph): Record<string, unknown>;
55
+ resolveNodeData(node: EmbedEntity): Record<string, unknown>;
56
+ resolveEdgeData(edge: EmbedEntity): Record<string, unknown>;
57
+ }): unknown | Promise<unknown>;
58
+ }>;
59
+ };
60
+ type EmbedFormatDefinition = {
61
+ id: string;
62
+ name: string;
63
+ file?: {
64
+ extension?: string;
65
+ mediaType?: string;
66
+ };
67
+ editorLanguage?: string;
68
+ detect?(source: string): boolean | number | Promise<boolean | number>;
69
+ import?(source: string, context: EmbedFormatContext): unknown | Promise<unknown>;
70
+ export?(graph: EmbedGraph, context: EmbedFormatContext): unknown | Promise<unknown>;
71
+ };
32
72
  interface StatelyEmbedOptions extends Partial<MachineInitOptions> {
33
73
  baseUrl: string;
34
74
  apiKey?: string;
75
+ /** Initial canvas view. Also reflected in the iframe URL as `?view=`. */
76
+ viewMode?: 'graph' | 'list';
35
77
  origin?: string;
36
78
  /**
37
79
  * Optional iframe to attach to immediately. Equivalent to calling
@@ -49,10 +91,15 @@ interface StatelyEmbedOptions extends Partial<MachineInitOptions> {
49
91
  commentsByMachineId?: Record<string, CommentsConfig>;
50
92
  /** Asset upload configuration. If omitted, files are stored as base64 data URLs. */
51
93
  assets?: AssetConfig;
94
+ profiles?: readonly EmbedProfileDefinition[];
95
+ formats?: readonly EmbedFormatDefinition[];
96
+ defaultProfile?: string;
97
+ defaultFormat?: string;
52
98
  onReady?: () => void;
53
99
  onLoaded?: (graph: StatelyGraph) => void;
54
100
  onChange?: (graph: StatelyGraph, machineConfig: unknown) => void;
55
101
  onSave?: (graph: StatelyGraph, machineConfig: unknown) => void;
102
+ onViewModeChanged?: (viewMode: 'graph' | 'list') => void;
56
103
  onError?: (error: {
57
104
  code: string;
58
105
  message: string;
@@ -91,4 +138,4 @@ interface StatelyEmbed {
91
138
  }
92
139
  declare function createStatelyEmbed(options: StatelyEmbedOptions): StatelyEmbed;
93
140
  //#endregion
94
- export { AssetConfig, type CapabilityDeniedEvent, type CommentsConfig, type EditorCapabilities, type EmbedDocument, type EmbedDocumentLanguage, 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 };
141
+ export { AssetConfig, type CapabilityDeniedEvent, type CommentsConfig, type EditorCapabilities, type EmbedDocument, type EmbedDocumentLanguage, EmbedEntity, type EmbedEventHandler, type EmbedEventMap, type EmbedEventName, type EmbedExtensionsManifest, EmbedFormatContext, EmbedFormatDefinition, EmbedGraph, type EmbedMode, EmbedProfileDefinition, 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,13 +1,74 @@
1
- import { a as createEventRegistry, o as createPendingExportManager, r as createPostMessageTransport, s as toInitMessage, t as SDK_VERSION } from "./version-DoNMw8g6.mjs";
2
- import { t as EDITOR_PROTOCOL_VERSION } from "./editorHostProtocol-D_h4y3rX.mjs";
1
+ import { a as createEventRegistry, o as createPendingExportManager, r as createPostMessageTransport, s as toInitMessage, t as SDK_VERSION } from "./version-3cKMfysA.mjs";
2
+ import { t as EDITOR_PROTOCOL_VERSION } from "./editorHostProtocol-Bko7wge8.mjs";
3
3
 
4
4
  //#region src/embed.ts
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function isEmbedEntity(value) {
9
+ return isRecord(value) && isRecord(value.data);
10
+ }
11
+ function isEmbedGraph(value) {
12
+ return isRecord(value) && isRecord(value.data) && Array.isArray(value.nodes) && value.nodes.every(isEmbedEntity) && Array.isArray(value.edges) && value.edges.every(isEmbedEntity);
13
+ }
14
+ function assertExtensionCall(value) {
15
+ if (!isRecord(value)) throw new Error("Invalid extension request.");
16
+ if (typeof value.requestId !== "string" || typeof value.extensionId !== "string" || ![
17
+ "detect",
18
+ "import",
19
+ "export",
20
+ "validate"
21
+ ].includes(String(value.method)) || !isRecord(value.payload)) throw new Error("Invalid extension request.");
22
+ const payload = value.payload;
23
+ if ((value.method === "detect" || value.method === "import") && typeof payload.source !== "string") throw new Error("Invalid extension source payload.");
24
+ if ((value.method === "import" || value.method === "export") && typeof payload.profileId !== "string") throw new Error("Invalid extension profile payload.");
25
+ if ((value.method === "export" || value.method === "validate") && !isEmbedGraph(payload.graph)) throw new Error("Invalid extension graph payload.");
26
+ if (value.method === "validate" && payload.revision !== void 0 && typeof payload.revision !== "number") throw new Error("Invalid extension revision payload.");
27
+ }
5
28
  function buildEmbedUrl(options) {
6
29
  const base = options.baseUrl.replace(/\/+$/, "") + "/embed";
7
30
  const url = new URL(base);
8
31
  if (options.apiKey) url.searchParams.set("api_key", options.apiKey);
32
+ if (options.viewMode) url.searchParams.set("view", options.viewMode);
9
33
  return url.toString();
10
34
  }
35
+ function serializeExtensions(options) {
36
+ if (!options.profiles?.length && !options.formats?.length) return options.extensions;
37
+ return {
38
+ profiles: (options.profiles ?? []).map((profile) => ({
39
+ profile: profile.profile,
40
+ name: profile.name,
41
+ description: profile.description,
42
+ namespace: profile.namespace,
43
+ extends: profile.extends,
44
+ formats: profile.formats,
45
+ defaultFormat: profile.defaultFormat,
46
+ graph: profile.graph,
47
+ authoring: profile.authoring,
48
+ operations: profile.operations,
49
+ restrictFormatsToListed: profile.restrictFormatsToListed,
50
+ features: profile.features,
51
+ implementations: profile.implementations,
52
+ rules: profile.rules,
53
+ editorDefaults: profile.editorDefaults,
54
+ validation: profile.validation?.map(({ name, description }) => ({
55
+ name,
56
+ description
57
+ }))
58
+ })),
59
+ formats: (options.formats ?? []).map((format) => ({
60
+ id: format.id,
61
+ name: format.name,
62
+ file: format.file,
63
+ editorLanguage: format.editorLanguage,
64
+ detect: !!format.detect,
65
+ import: !!format.import,
66
+ export: !!format.export
67
+ })),
68
+ defaultProfile: options.defaultProfile,
69
+ defaultFormat: options.defaultFormat
70
+ };
71
+ }
11
72
  function extractInitOptions(options) {
12
73
  if (options.machines) return {
13
74
  machines: options.machines,
@@ -20,12 +81,14 @@ function extractInitOptions(options) {
20
81
  capabilities: options.capabilities,
21
82
  panels: options.panels,
22
83
  commentsByMachineId: options.commentsByMachineId,
84
+ settings: options.settings,
23
85
  unsavedIndicator: options.unsavedIndicator
24
86
  };
25
87
  if (options.machine !== void 0) return {
26
88
  machine: options.machine,
27
89
  documents: options.documents,
28
90
  format: options.format,
91
+ extensions: serializeExtensions(options),
29
92
  mode: options.mode,
30
93
  theme: options.theme,
31
94
  readOnly: options.readOnly,
@@ -35,13 +98,14 @@ function extractInitOptions(options) {
35
98
  panels: options.panels,
36
99
  comments: options.comments,
37
100
  sourceLocations: options.sourceLocations,
101
+ settings: options.settings,
38
102
  unsavedIndicator: options.unsavedIndicator
39
103
  };
40
104
  return null;
41
105
  }
42
106
  function createStatelyEmbed(options) {
43
107
  const embedUrl = buildEmbedUrl(options);
44
- const targetOrigin = options.origin ?? "*";
108
+ const targetOrigin = options.origin ?? new URL(embedUrl).origin;
45
109
  let iframe = null;
46
110
  let transport = null;
47
111
  let ownedIframe = false;
@@ -49,6 +113,9 @@ function createStatelyEmbed(options) {
49
113
  let editorProtocolCompatible;
50
114
  const pendingMessages = [];
51
115
  const events = createEventRegistry();
116
+ const profiles = new Map((options.profiles ?? []).map((profile) => [profile.profile, profile]));
117
+ const formats = new Map((options.formats ?? []).map((format) => [format.id, format]));
118
+ const bundledExtensions = serializeExtensions(options);
52
119
  const exportManager = createPendingExportManager((message) => send(message));
53
120
  const assetUploadAdapter = options.assets?.adapter;
54
121
  const uploadAsset = options.assets?.onUploadRequest ? (file, context) => options.assets.onUploadRequest(file, context) : assetUploadAdapter ? (file, context) => assetUploadAdapter.upload({
@@ -72,9 +139,73 @@ function createStatelyEmbed(options) {
72
139
  transport.send(msg);
73
140
  }
74
141
  }
142
+ function formatContext(profile) {
143
+ const namespace = profile.namespace ?? /^(@[^/]+)\//.exec(profile.profile)?.[1];
144
+ if (!namespace) throw new Error(`Profile ${profile.profile} has no namespace.`);
145
+ const resolve = (data) => Object.fromEntries(Object.entries(data).filter(([key]) => key.startsWith(`${namespace}.`)).map(([key, value]) => [key.slice(namespace.length + 1), value]));
146
+ return {
147
+ profile,
148
+ resolveGraphData: (graph) => resolve(graph.data ?? {}),
149
+ resolveNodeData: (node) => resolve(node.data ?? {}),
150
+ resolveEdgeData: (edge) => resolve(edge.data ?? {})
151
+ };
152
+ }
153
+ async function handleExtensionCall(input) {
154
+ const requestId = isRecord(input) && typeof input.requestId === "string" ? input.requestId : void 0;
155
+ try {
156
+ assertExtensionCall(input);
157
+ const call = input;
158
+ const payload = call.payload;
159
+ let result;
160
+ if (call.method === "validate") {
161
+ const profile = profiles.get(call.extensionId);
162
+ if (!profile) throw new Error(`Unknown profile ${call.extensionId}.`);
163
+ const context = formatContext(profile);
164
+ result = (await Promise.all((profile.validation ?? []).filter((validator) => validator.validate).map((validator) => validator.validate({
165
+ graph: payload.graph,
166
+ revision: typeof payload.revision === "number" ? payload.revision : void 0,
167
+ ...context
168
+ })))).flat();
169
+ } else {
170
+ const format = formats.get(call.extensionId);
171
+ if (!format) throw new Error(`Unknown format ${call.extensionId}.`);
172
+ if (call.method === "detect") {
173
+ if (!format.detect) throw new Error("Format has no detector.");
174
+ result = await format.detect(String(payload.source ?? ""));
175
+ } else {
176
+ const profile = profiles.get(String(payload.profileId ?? ""));
177
+ if (!profile) throw new Error(`Unknown profile ${payload.profileId}.`);
178
+ const context = formatContext(profile);
179
+ if (call.method === "import") {
180
+ if (!format.import) throw new Error("Format has no importer.");
181
+ result = await format.import(payload.source, context);
182
+ if (!isEmbedGraph(result)) throw new Error("Format importer returned an invalid graph.");
183
+ } else {
184
+ if (!format.export) throw new Error("Format has no exporter.");
185
+ result = await format.export(payload.graph, context);
186
+ }
187
+ }
188
+ }
189
+ send({
190
+ type: "@statelyai.extension.result",
191
+ requestId: call.requestId,
192
+ result
193
+ });
194
+ } catch (error) {
195
+ if (!requestId) return;
196
+ send({
197
+ type: "@statelyai.extension.result",
198
+ requestId,
199
+ error: error instanceof Error ? error.message : String(error)
200
+ });
201
+ }
202
+ }
75
203
  function handleMessage(data) {
76
204
  if (destroyed) return;
77
205
  switch (data.type) {
206
+ case "@statelyai.extension.call":
207
+ handleExtensionCall(data);
208
+ break;
78
209
  case "@statelyai.ready": {
79
210
  const ready = data;
80
211
  if (ready.protocolVersion !== void 0) {
@@ -147,6 +278,15 @@ function createStatelyEmbed(options) {
147
278
  case "@statelyai.project.machineSelected":
148
279
  events.emit("machineSelected", { machineId: String(data.machineId) });
149
280
  break;
281
+ case "@statelyai.viewModeChanged": {
282
+ const viewMode = data;
283
+ options.onViewModeChanged?.(viewMode.viewMode);
284
+ events.emit("viewModeChanged", {
285
+ viewMode: viewMode.viewMode,
286
+ machineId: viewMode.machineId
287
+ });
288
+ break;
289
+ }
150
290
  case "@statelyai.retrieved": {
151
291
  const retrieved = data;
152
292
  exportManager.resolve(retrieved.requestId, retrieved.data);
@@ -257,7 +397,10 @@ function createStatelyEmbed(options) {
257
397
  return el;
258
398
  },
259
399
  init(opts) {
260
- send(toInitMessage(opts));
400
+ send(toInitMessage("machine" in opts ? {
401
+ ...opts,
402
+ extensions: opts.extensions ?? bundledExtensions
403
+ } : opts));
261
404
  },
262
405
  updateMachine(machine, format, sourceLocations) {
263
406
  send({
@@ -1,4 +1,4 @@
1
- import { r as CodeExpression } from "./index-BAkQThnl.mjs";
1
+ import { t as CodeExpression } from "./index-Bakx1tkW.mjs";
2
2
  import * as _statelyai_graph0 from "@statelyai/graph";
3
3
  import { Graph } from "@statelyai/graph";
4
4
 
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-yDK-MM3G.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-DYjfFztq.mjs";
2
2
  export { StatelyAction, StatelyActorImplementation, StatelyEdgeData, StatelyGraph, StatelyGraphData, StatelyGuard, StatelyImplementation, StatelyInvoke, StatelyNodeData, StatelyTagImplementation, StudioAction, StudioEdge, StudioEventTypeData, StudioMachine, StudioNode, fromStudioMachine, studioMachineConverter, toStudioMachine };