@flowdrop/flowdrop 1.15.0 → 2.0.0-beta.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.
Files changed (215) hide show
  1. package/CHANGELOG.md +475 -0
  2. package/MIGRATION-2.0.md +472 -0
  3. package/README.md +23 -23
  4. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  5. package/dist/adapters/WorkflowAdapter.js +14 -8
  6. package/dist/adapters/agentspec/AgentSpecAdapter.js +7 -7
  7. package/dist/chat/batchFeedback.d.ts +39 -0
  8. package/dist/chat/batchFeedback.js +51 -0
  9. package/dist/commands/executor.js +15 -1
  10. package/dist/commands/storeIntegration.svelte.d.ts +4 -1
  11. package/dist/commands/storeIntegration.svelte.js +26 -21
  12. package/dist/commands/types.d.ts +2 -0
  13. package/dist/components/App.svelte +162 -192
  14. package/dist/components/App.svelte.d.ts +47 -8
  15. package/dist/components/ConfigForm.svelte +71 -47
  16. package/dist/components/ConfigModal.svelte +7 -2
  17. package/dist/components/ConnectionLine.svelte +4 -2
  18. package/dist/components/Navbar.svelte +61 -1
  19. package/dist/components/NodeSidebar.svelte +27 -45
  20. package/dist/components/NodeStatusOverlay.svelte +94 -6
  21. package/dist/components/NodeSwapPicker.svelte +10 -8
  22. package/dist/components/PipelineStatus.svelte +16 -67
  23. package/dist/components/PortCoordinateTracker.svelte +5 -6
  24. package/dist/components/SchemaForm.stories.svelte +1 -3
  25. package/dist/components/SchemaForm.svelte +18 -25
  26. package/dist/components/SchemaForm.svelte.d.ts +0 -8
  27. package/dist/components/SettingsModal.svelte +8 -3
  28. package/dist/components/SettingsPanel.svelte +20 -4
  29. package/dist/components/SwapMappingEditor.svelte +67 -49
  30. package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
  31. package/dist/components/UniversalNode.svelte +9 -7
  32. package/dist/components/WorkflowEditor.svelte +118 -111
  33. package/dist/components/WorkflowEditor.svelte.d.ts +18 -10
  34. package/dist/components/chat/AIChatPanel.svelte +93 -89
  35. package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
  36. package/dist/components/chat/CommandPreview.svelte +2 -1
  37. package/dist/components/console/CommandConsole.svelte +7 -5
  38. package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
  39. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
  40. package/dist/components/console/ConsoleInput.svelte +15 -6
  41. package/dist/components/console/ConsoleOutput.svelte +2 -1
  42. package/dist/components/form/FormArray.svelte +5 -9
  43. package/dist/components/form/FormArray.svelte.d.ts +2 -1
  44. package/dist/components/form/FormAutocomplete.svelte +8 -6
  45. package/dist/components/form/FormField.svelte +4 -2
  46. package/dist/components/form/FormFieldLight.svelte +4 -2
  47. package/dist/components/form/FormMarkdownEditor.svelte +9 -4
  48. package/dist/components/form/FormRangeField.svelte +1 -0
  49. package/dist/components/form/FormTemplateEditor.svelte +11 -3
  50. package/dist/components/form/FormToggle.svelte +5 -12
  51. package/dist/components/form/FormToggle.svelte.d.ts +4 -2
  52. package/dist/components/form/templateAutocomplete.js +1 -5
  53. package/dist/components/form/types.d.ts +1 -14
  54. package/dist/components/interrupt/FormPrompt.svelte +3 -2
  55. package/dist/components/interrupt/InterruptBubble.svelte +16 -17
  56. package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
  57. package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
  58. package/dist/components/layouts/MainLayout.svelte +20 -13
  59. package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
  60. package/dist/components/nodes/AtomNode.svelte +17 -5
  61. package/dist/components/nodes/GatewayNode.svelte +19 -10
  62. package/dist/components/nodes/IdeaNode.svelte +7 -0
  63. package/dist/components/nodes/SimpleNode.svelte +11 -6
  64. package/dist/components/nodes/SquareNode.svelte +15 -8
  65. package/dist/components/nodes/TerminalNode.svelte +9 -4
  66. package/dist/components/nodes/ToolNode.svelte +7 -1
  67. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  68. package/dist/components/playground/ChatInput.svelte +11 -14
  69. package/dist/components/playground/ChatPanel.svelte +6 -49
  70. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  71. package/dist/components/playground/ControlPanel.svelte +134 -123
  72. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  73. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  74. package/dist/components/playground/InputCollector.svelte +11 -9
  75. package/dist/components/playground/MessageStream.svelte +17 -23
  76. package/dist/components/playground/PipelineKanbanView.svelte +65 -6
  77. package/dist/components/playground/PipelinePanel.svelte +11 -5
  78. package/dist/components/playground/PipelineTableView.svelte +186 -44
  79. package/dist/components/playground/Playground.svelte +90 -92
  80. package/dist/components/playground/Playground.svelte.d.ts +2 -0
  81. package/dist/components/playground/PlaygroundApp.svelte +6 -1
  82. package/dist/components/playground/PlaygroundApp.svelte.d.ts +3 -0
  83. package/dist/components/playground/PlaygroundModal.svelte +13 -3
  84. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -0
  85. package/dist/components/playground/PlaygroundStudio.svelte +34 -32
  86. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -0
  87. package/dist/components/playground/SessionManager.svelte +9 -12
  88. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +28 -0
  89. package/dist/components/playground/pipelineViewUtils.svelte.js +38 -1
  90. package/dist/config/endpoints.d.ts +0 -7
  91. package/dist/config/endpoints.js +2 -10
  92. package/dist/core/index.d.ts +4 -4
  93. package/dist/core/index.js +6 -6
  94. package/dist/display/index.d.ts +0 -2
  95. package/dist/display/index.js +0 -6
  96. package/dist/editor/index.d.ts +19 -20
  97. package/dist/editor/index.js +25 -35
  98. package/dist/form/code.d.ts +25 -15
  99. package/dist/form/code.js +44 -41
  100. package/dist/form/fieldRegistry.d.ts +17 -13
  101. package/dist/form/fieldRegistry.js +32 -12
  102. package/dist/form/full.d.ts +17 -13
  103. package/dist/form/full.js +22 -27
  104. package/dist/form/index.d.ts +3 -3
  105. package/dist/form/index.js +3 -3
  106. package/dist/form/markdown.d.ts +13 -8
  107. package/dist/form/markdown.js +22 -23
  108. package/dist/helpers/proximityConnect.d.ts +3 -2
  109. package/dist/helpers/proximityConnect.js +2 -5
  110. package/dist/helpers/workflowEditorHelper.d.ts +12 -5
  111. package/dist/helpers/workflowEditorHelper.js +27 -25
  112. package/dist/index.d.ts +28 -24
  113. package/dist/index.js +27 -50
  114. package/dist/messages/defaults.d.ts +2 -5
  115. package/dist/messages/defaults.js +3 -6
  116. package/dist/messages/index.d.ts +0 -1
  117. package/dist/messages/index.js +0 -1
  118. package/dist/mocks/app-forms.d.ts +6 -2
  119. package/dist/mocks/app-forms.js +11 -4
  120. package/dist/openapi/v1/openapi.yaml +3 -3
  121. package/dist/playground/index.d.ts +2 -3
  122. package/dist/playground/index.js +2 -30
  123. package/dist/playground/mount.d.ts +15 -0
  124. package/dist/playground/mount.js +46 -20
  125. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  126. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  127. package/dist/registry/builtinFormats.d.ts +9 -18
  128. package/dist/registry/builtinFormats.js +9 -39
  129. package/dist/registry/builtinNodes.d.ts +0 -25
  130. package/dist/registry/builtinNodes.js +1 -50
  131. package/dist/registry/index.d.ts +3 -4
  132. package/dist/registry/index.js +4 -6
  133. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  134. package/dist/registry/nodeComponentRegistry.js +235 -17
  135. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  136. package/dist/registry/workflowFormatRegistry.js +24 -8
  137. package/dist/{schema → schemas}/index.d.ts +2 -2
  138. package/dist/{schema → schemas}/index.js +2 -2
  139. package/dist/schemas/v1/workflow.schema.json +3 -3
  140. package/dist/services/agentSpecExecutionService.js +0 -1
  141. package/dist/services/apiVariableService.d.ts +2 -1
  142. package/dist/services/apiVariableService.js +5 -22
  143. package/dist/services/autoSaveService.d.ts +7 -0
  144. package/dist/services/autoSaveService.js +6 -4
  145. package/dist/services/chatService.d.ts +8 -4
  146. package/dist/services/chatService.js +15 -15
  147. package/dist/services/draftStorage.d.ts +129 -13
  148. package/dist/services/draftStorage.js +185 -37
  149. package/dist/services/dynamicSchemaService.d.ts +2 -1
  150. package/dist/services/dynamicSchemaService.js +5 -22
  151. package/dist/services/globalSave.d.ts +13 -12
  152. package/dist/services/globalSave.js +29 -51
  153. package/dist/services/historyService.d.ts +9 -3
  154. package/dist/services/historyService.js +9 -3
  155. package/dist/services/interruptService.d.ts +14 -9
  156. package/dist/services/interruptService.js +27 -27
  157. package/dist/services/nodeExecutionService.d.ts +18 -3
  158. package/dist/services/nodeExecutionService.js +71 -45
  159. package/dist/services/playgroundService.d.ts +14 -9
  160. package/dist/services/playgroundService.js +31 -30
  161. package/dist/services/variableService.d.ts +2 -1
  162. package/dist/services/variableService.js +2 -2
  163. package/dist/services/workflowStorage.js +6 -6
  164. package/dist/stores/apiContext.d.ts +45 -0
  165. package/dist/stores/apiContext.js +65 -0
  166. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  167. package/dist/stores/categoriesStore.svelte.js +70 -64
  168. package/dist/stores/getInstance.svelte.d.ts +39 -0
  169. package/dist/stores/getInstance.svelte.js +65 -0
  170. package/dist/stores/historyStore.svelte.d.ts +77 -93
  171. package/dist/stores/historyStore.svelte.js +134 -160
  172. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  173. package/dist/stores/instanceContainer.svelte.js +114 -0
  174. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  175. package/dist/stores/interruptStore.svelte.js +253 -226
  176. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  177. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  178. package/dist/stores/playgroundStore.svelte.d.ts +169 -222
  179. package/dist/stores/playgroundStore.svelte.js +515 -580
  180. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  181. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  182. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  183. package/dist/stores/settingsStore.svelte.js +47 -12
  184. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  185. package/dist/stores/workflowStore.svelte.js +449 -501
  186. package/dist/stories/EdgeDecorator.svelte +5 -2
  187. package/dist/stories/NodeDecorator.svelte +5 -3
  188. package/dist/svelte-app.d.ts +60 -10
  189. package/dist/svelte-app.js +157 -53
  190. package/dist/types/events.d.ts +6 -3
  191. package/dist/types/index.d.ts +33 -3
  192. package/dist/types/navbar.d.ts +7 -0
  193. package/dist/types/playground.d.ts +18 -3
  194. package/dist/types/settings.d.ts +13 -0
  195. package/dist/types/settings.js +1 -0
  196. package/dist/utils/colors.d.ts +47 -21
  197. package/dist/utils/colors.js +69 -68
  198. package/dist/utils/connections.d.ts +9 -15
  199. package/dist/utils/connections.js +13 -32
  200. package/dist/utils/duration.d.ts +13 -0
  201. package/dist/utils/duration.js +45 -0
  202. package/dist/utils/icons.d.ts +5 -2
  203. package/dist/utils/icons.js +6 -5
  204. package/dist/utils/nodeSwap.d.ts +6 -2
  205. package/dist/utils/nodeSwap.js +62 -126
  206. package/dist/utils/nodeTypes.d.ts +17 -8
  207. package/dist/utils/nodeTypes.js +26 -19
  208. package/dist/utils/performanceUtils.js +7 -0
  209. package/package.json +6 -5
  210. package/dist/messages/deprecation.d.ts +0 -20
  211. package/dist/messages/deprecation.js +0 -33
  212. package/dist/registry/plugin.d.ts +0 -215
  213. package/dist/registry/plugin.js +0 -249
  214. package/dist/services/api.d.ts +0 -129
  215. package/dist/services/api.js +0 -217
@@ -8,7 +8,17 @@
8
8
  * - External libraries to contribute node types via plugins
9
9
  * - Runtime switching between different node visualizations
10
10
  */
11
- import { BaseRegistry } from './BaseRegistry.js';
11
+ import { BaseRegistry } from './BaseRegistry.svelte.js';
12
+ /**
13
+ * Check if a namespace is valid.
14
+ * Must be lowercase alphanumeric with optional hyphens.
15
+ *
16
+ * @param namespace - The namespace to validate
17
+ * @returns true if valid
18
+ */
19
+ export function isValidNamespace(namespace) {
20
+ return /^[a-z][a-z0-9-]*$/.test(namespace);
21
+ }
12
22
  /**
13
23
  * Central registry for node component types.
14
24
  * Allows built-in and third-party components to be registered and resolved.
@@ -18,7 +28,7 @@ import { BaseRegistry } from './BaseRegistry.js';
18
28
  * @example
19
29
  * ```typescript
20
30
  * // Register a custom node
21
- * nodeComponentRegistry.register({
31
+ * fd.nodes.register({
22
32
  * type: "myCustomNode",
23
33
  * displayName: "My Custom Node",
24
34
  * component: MyCustomNodeComponent,
@@ -27,14 +37,37 @@ import { BaseRegistry } from './BaseRegistry.js';
27
37
  * });
28
38
  *
29
39
  * // Get a component
30
- * const component = nodeComponentRegistry.getComponent("myCustomNode");
40
+ * const component = fd.nodes.getComponent("myCustomNode");
31
41
  * ```
32
42
  */
33
- class NodeComponentRegistry extends BaseRegistry {
43
+ export class NodeComponentRegistry extends BaseRegistry {
34
44
  /** Default type to use when requested type is not found */
35
45
  defaultType = 'workflowNode';
36
46
  /** Initial default type, restored on clear() */
37
47
  static INITIAL_DEFAULT_TYPE = 'workflowNode';
48
+ /**
49
+ * @param seed - Optional initial registrations and default type. When
50
+ * omitted the registry starts empty; instances created via
51
+ * `createFlowDropInstance` pass the built-in node components (see
52
+ * `builtinNodes.ts`).
53
+ */
54
+ constructor(seed) {
55
+ super();
56
+ if (seed?.registrations) {
57
+ this.registerAll(seed.registrations, true);
58
+ }
59
+ if (seed?.defaultType) {
60
+ this.defaultType = seed.defaultType;
61
+ }
62
+ }
63
+ /**
64
+ * Clear all registrations and reset default type.
65
+ */
66
+ clear() {
67
+ super.clear();
68
+ this.defaultType = NodeComponentRegistry.INITIAL_DEFAULT_TYPE;
69
+ this.touch(); // defaultType changed — invalidate getComponent/getDefaultType reads
70
+ }
38
71
  /**
39
72
  * Register a node component type.
40
73
  *
@@ -44,7 +77,7 @@ class NodeComponentRegistry extends BaseRegistry {
44
77
  *
45
78
  * @example
46
79
  * ```typescript
47
- * nodeComponentRegistry.register({
80
+ * fd.nodes.register({
48
81
  * type: "fancy",
49
82
  * displayName: "Fancy Node",
50
83
  * component: FancyNode,
@@ -52,19 +85,13 @@ class NodeComponentRegistry extends BaseRegistry {
52
85
  * });
53
86
  * ```
54
87
  */
55
- /**
56
- * Clear all registrations and reset default type.
57
- */
58
- clear() {
59
- super.clear();
60
- this.defaultType = NodeComponentRegistry.INITIAL_DEFAULT_TYPE;
61
- }
62
88
  register(registration, overwrite = false) {
63
89
  if (this.items.has(registration.type) && !overwrite) {
64
90
  throw new Error(`Node type "${registration.type}" is already registered. ` +
65
91
  `Use overwrite: true to replace it, or use a namespaced type like "mylib:${registration.type}".`);
66
92
  }
67
93
  this.items.set(registration.type, registration);
94
+ this.touch();
68
95
  this.notifyListeners();
69
96
  }
70
97
  /**
@@ -86,6 +113,7 @@ class NodeComponentRegistry extends BaseRegistry {
86
113
  * @returns The component if found, or the default component
87
114
  */
88
115
  getComponent(type) {
116
+ this.trackVersion(); // reactive dependency (reads items + defaultType directly)
89
117
  const registration = this.items.get(type) ?? this.items.get(this.defaultType);
90
118
  return registration?.component;
91
119
  }
@@ -96,6 +124,7 @@ class NodeComponentRegistry extends BaseRegistry {
96
124
  * @returns The metadata if found, undefined otherwise
97
125
  */
98
126
  getMetadata(type) {
127
+ this.trackVersion(); // reactive dependency (reads items directly)
99
128
  const reg = this.items.get(type);
100
129
  if (!reg)
101
130
  return undefined;
@@ -119,10 +148,10 @@ class NodeComponentRegistry extends BaseRegistry {
119
148
  * @example
120
149
  * ```typescript
121
150
  * // Get all visual nodes
122
- * const visualNodes = nodeComponentRegistry.filter({ category: "visual" });
151
+ * const visualNodes = fd.nodes.filter({ category: "visual" });
123
152
  *
124
153
  * // Get nodes from a specific library
125
- * const libNodes = nodeComponentRegistry.filter({ source: "mylib" });
154
+ * const libNodes = fd.nodes.filter({ source: "mylib" });
126
155
  * ```
127
156
  */
128
157
  filter(filter) {
@@ -168,6 +197,7 @@ class NodeComponentRegistry extends BaseRegistry {
168
197
  throw new Error(`Cannot set default to unregistered type: ${type}`);
169
198
  }
170
199
  this.defaultType = type;
200
+ this.touch(); // defaultType changed — invalidate getComponent/getDefaultType reads
171
201
  }
172
202
  /**
173
203
  * Get the current default type.
@@ -175,6 +205,7 @@ class NodeComponentRegistry extends BaseRegistry {
175
205
  * @returns The default type identifier
176
206
  */
177
207
  getDefaultType() {
208
+ this.trackVersion(); // reactive dependency (reads defaultType directly)
178
209
  return this.defaultType;
179
210
  }
180
211
  /**
@@ -186,7 +217,7 @@ class NodeComponentRegistry extends BaseRegistry {
186
217
  *
187
218
  * @example
188
219
  * ```typescript
189
- * const oneOf = nodeComponentRegistry.getOneOfOptions();
220
+ * const oneOf = fd.nodes.getOneOfOptions();
190
221
  * // Use in configSchema: { type: "string", oneOf }
191
222
  * ```
192
223
  */
@@ -204,6 +235,7 @@ class NodeComponentRegistry extends BaseRegistry {
204
235
  * @returns The status position, or default "top-right"
205
236
  */
206
237
  getStatusPosition(type) {
238
+ this.trackVersion(); // reactive dependency (reads items directly)
207
239
  return this.items.get(type)?.statusPosition ?? 'top-right';
208
240
  }
209
241
  /**
@@ -213,11 +245,197 @@ class NodeComponentRegistry extends BaseRegistry {
213
245
  * @returns The status size, or default "md"
214
246
  */
215
247
  getStatusSize(type) {
248
+ this.trackVersion(); // reactive dependency (reads items directly)
216
249
  return this.items.get(type)?.statusSize ?? 'md';
217
250
  }
251
+ // ==========================================================================
252
+ // Plugin system
253
+ // ==========================================================================
254
+ /**
255
+ * Register a single custom node without a full plugin.
256
+ * Useful for project-specific custom nodes.
257
+ *
258
+ * @param type - Type identifier (can be namespaced or plain)
259
+ * @param displayName - Display name for UI
260
+ * @param component - Svelte component
261
+ * @param options - Additional options
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * fd.nodes.registerCustom("myproject:special", "Special Node", MyNode, {
266
+ * icon: "mdi:star",
267
+ * description: "A special node for my project"
268
+ * });
269
+ * ```
270
+ */
271
+ registerCustom(type, displayName, component, options = {}) {
272
+ this.register({
273
+ type,
274
+ displayName,
275
+ component,
276
+ description: options.description,
277
+ icon: options.icon,
278
+ category: options.category ?? 'custom',
279
+ source: options.source ?? 'custom',
280
+ statusPosition: options.statusPosition,
281
+ statusSize: options.statusSize
282
+ });
283
+ }
284
+ /**
285
+ * Register a FlowDrop plugin with custom node components.
286
+ * All node types are automatically namespaced with the plugin namespace.
287
+ *
288
+ * @param config - Plugin configuration with namespace and node definitions
289
+ * @returns Result object with registered types and any errors
290
+ */
291
+ registerPlugin(config) {
292
+ const result = {
293
+ success: true,
294
+ namespace: config.namespace,
295
+ registeredTypes: [],
296
+ errors: []
297
+ };
298
+ // Validate namespace
299
+ if (!isValidNamespace(config.namespace)) {
300
+ result.success = false;
301
+ result.errors.push(`Invalid namespace "${config.namespace}". ` +
302
+ `Namespace must be lowercase alphanumeric with optional hyphens.`);
303
+ return result;
304
+ }
305
+ // Register each node
306
+ for (const nodeDef of config.nodes) {
307
+ try {
308
+ const namespacedType = createNamespacedType(config.namespace, nodeDef.type);
309
+ const registration = {
310
+ type: namespacedType,
311
+ displayName: nodeDef.displayName,
312
+ description: nodeDef.description,
313
+ component: nodeDef.component,
314
+ icon: nodeDef.icon,
315
+ category: nodeDef.category ?? 'custom',
316
+ source: config.namespace,
317
+ statusPosition: nodeDef.statusPosition,
318
+ statusSize: nodeDef.statusSize
319
+ };
320
+ this.register(registration);
321
+ result.registeredTypes.push(namespacedType);
322
+ }
323
+ catch (error) {
324
+ result.success = false;
325
+ const errorMessage = error instanceof Error ? error.message : String(error);
326
+ result.errors.push(`Failed to register ${config.namespace}:${nodeDef.type}: ${errorMessage}`);
327
+ }
328
+ }
329
+ return result;
330
+ }
331
+ /**
332
+ * Unregister all nodes from a plugin by namespace.
333
+ *
334
+ * @param namespace - The plugin namespace to unregister
335
+ * @returns Array of unregistered type identifiers
336
+ */
337
+ unregisterPlugin(namespace) {
338
+ const unregistered = [];
339
+ const types = this.getTypes();
340
+ for (const type of types) {
341
+ if (type.startsWith(`${namespace}:`)) {
342
+ if (this.unregister(type)) {
343
+ unregistered.push(type);
344
+ }
345
+ }
346
+ }
347
+ return unregistered;
348
+ }
349
+ /**
350
+ * Get all registered plugins (unique namespaces).
351
+ *
352
+ * @returns Array of namespace strings
353
+ */
354
+ getRegisteredPlugins() {
355
+ const sources = new Set();
356
+ for (const reg of this.getAll()) {
357
+ if (reg.source && reg.source !== 'flowdrop') {
358
+ sources.add(reg.source);
359
+ }
360
+ }
361
+ return Array.from(sources);
362
+ }
363
+ /**
364
+ * Get the count of nodes registered by a plugin.
365
+ *
366
+ * @param namespace - The plugin namespace
367
+ * @returns Number of nodes registered by this plugin
368
+ */
369
+ getPluginNodeCount(namespace) {
370
+ return this.getBySource(namespace).length;
371
+ }
372
+ }
373
+ /**
374
+ * Create a plugin builder for a fluent API experience.
375
+ *
376
+ * @param namespace - Plugin namespace
377
+ * @param name - Plugin name
378
+ * @returns Plugin builder with chainable methods
379
+ *
380
+ * @example
381
+ * ```typescript
382
+ * import { createPlugin } from "@flowdrop/flowdrop/editor";
383
+ *
384
+ * createPlugin("awesome", "Awesome Nodes")
385
+ * .version("1.0.0")
386
+ * .node("fancy", "Fancy Node", FancyNode)
387
+ * .node("glow", "Glowing Node", GlowNode, { icon: "mdi:lightbulb" })
388
+ * .register(fd.nodes);
389
+ * ```
390
+ */
391
+ export function createPlugin(namespace, name) {
392
+ const config = {
393
+ namespace,
394
+ name,
395
+ nodes: []
396
+ };
397
+ const builder = {
398
+ /**
399
+ * Set plugin version
400
+ */
401
+ version(v) {
402
+ config.version = v;
403
+ return builder;
404
+ },
405
+ /**
406
+ * Set plugin description
407
+ */
408
+ description(desc) {
409
+ config.description = desc;
410
+ return builder;
411
+ },
412
+ /**
413
+ * Add a node to the plugin
414
+ */
415
+ node(type, displayName, component, options = {}) {
416
+ config.nodes.push({
417
+ type,
418
+ displayName,
419
+ component,
420
+ ...options
421
+ });
422
+ return builder;
423
+ },
424
+ /**
425
+ * Register the plugin into a node component registry (e.g. `fd.nodes`).
426
+ */
427
+ register(registry) {
428
+ return registry.registerPlugin(config);
429
+ },
430
+ /**
431
+ * Get the config without registering (for testing/inspection)
432
+ */
433
+ getConfig() {
434
+ return { ...config };
435
+ }
436
+ };
437
+ return builder;
218
438
  }
219
- /** Singleton instance of the node component registry */
220
- export const nodeComponentRegistry = new NodeComponentRegistry();
221
439
  /**
222
440
  * Helper function to create a namespaced type identifier.
223
441
  * Use this to avoid conflicts when registering custom nodes.
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import type { StandardWorkflow } from '../adapters/WorkflowAdapter.js';
15
15
  import type { NodeMetadata, WorkflowFormat } from '../types/index.js';
16
- import { BaseRegistry } from './BaseRegistry.js';
16
+ import { BaseRegistry } from './BaseRegistry.svelte.js';
17
17
  /**
18
18
  * Validation result returned by format adapters.
19
19
  */
@@ -60,13 +60,15 @@ export interface WorkflowFormatAdapter {
60
60
  validate?(workflow: StandardWorkflow): FormatValidationResult;
61
61
  }
62
62
  /**
63
- * Central registry for workflow format adapters.
64
- * Singleton — extends BaseRegistry for shared mechanics.
63
+ * Per-instance registry for workflow format adapters.
64
+ * Extends BaseRegistry for shared mechanics; seeded with the built-in
65
+ * adapters at construction (see `createFlowDropInstance`). Reach it via
66
+ * `fd.formats`, or supply adapters through the `formatAdapters` mount option.
65
67
  *
66
68
  * @example
67
69
  * ```typescript
68
70
  * // Register a custom format
69
- * workflowFormatRegistry.register({
71
+ * fd.formats.register({
70
72
  * id: 'n8n',
71
73
  * name: 'n8n Workflow',
72
74
  * export: (workflow) => JSON.stringify(convertToN8n(workflow)),
@@ -74,10 +76,16 @@ export interface WorkflowFormatAdapter {
74
76
  * });
75
77
  *
76
78
  * // Get an adapter
77
- * const adapter = workflowFormatRegistry.get('n8n');
79
+ * const adapter = fd.formats.get('n8n');
78
80
  * ```
79
81
  */
80
- declare class WorkflowFormatRegistry extends BaseRegistry<string, WorkflowFormatAdapter> {
82
+ export declare class WorkflowFormatRegistry extends BaseRegistry<string, WorkflowFormatAdapter> {
83
+ /**
84
+ * @param seed - Optional initial format adapters. When omitted the registry
85
+ * starts empty; instances created via `createFlowDropInstance` pass the
86
+ * built-in flowdrop + agentspec adapters (see `builtinFormats.ts`).
87
+ */
88
+ constructor(seed?: WorkflowFormatAdapter[]);
81
89
  /**
82
90
  * Register a workflow format adapter.
83
91
  *
@@ -117,6 +125,3 @@ declare class WorkflowFormatRegistry extends BaseRegistry<string, WorkflowFormat
117
125
  title: string;
118
126
  }>;
119
127
  }
120
- /** Singleton instance of the workflow format registry */
121
- export declare const workflowFormatRegistry: WorkflowFormatRegistry;
122
- export {};
@@ -11,15 +11,17 @@
11
11
  *
12
12
  * Extends BaseRegistry for shared mechanics (subscribe, onClear, etc.).
13
13
  */
14
- import { BaseRegistry } from './BaseRegistry.js';
14
+ import { BaseRegistry } from './BaseRegistry.svelte.js';
15
15
  /**
16
- * Central registry for workflow format adapters.
17
- * Singleton — extends BaseRegistry for shared mechanics.
16
+ * Per-instance registry for workflow format adapters.
17
+ * Extends BaseRegistry for shared mechanics; seeded with the built-in
18
+ * adapters at construction (see `createFlowDropInstance`). Reach it via
19
+ * `fd.formats`, or supply adapters through the `formatAdapters` mount option.
18
20
  *
19
21
  * @example
20
22
  * ```typescript
21
23
  * // Register a custom format
22
- * workflowFormatRegistry.register({
24
+ * fd.formats.register({
23
25
  * id: 'n8n',
24
26
  * name: 'n8n Workflow',
25
27
  * export: (workflow) => JSON.stringify(convertToN8n(workflow)),
@@ -27,10 +29,23 @@ import { BaseRegistry } from './BaseRegistry.js';
27
29
  * });
28
30
  *
29
31
  * // Get an adapter
30
- * const adapter = workflowFormatRegistry.get('n8n');
32
+ * const adapter = fd.formats.get('n8n');
31
33
  * ```
32
34
  */
33
- class WorkflowFormatRegistry extends BaseRegistry {
35
+ export class WorkflowFormatRegistry extends BaseRegistry {
36
+ /**
37
+ * @param seed - Optional initial format adapters. When omitted the registry
38
+ * starts empty; instances created via `createFlowDropInstance` pass the
39
+ * built-in flowdrop + agentspec adapters (see `builtinFormats.ts`).
40
+ */
41
+ constructor(seed) {
42
+ super();
43
+ if (seed) {
44
+ for (const adapter of seed) {
45
+ this.register(adapter, true);
46
+ }
47
+ }
48
+ }
34
49
  /**
35
50
  * Register a workflow format adapter.
36
51
  *
@@ -44,6 +59,7 @@ class WorkflowFormatRegistry extends BaseRegistry {
44
59
  `Use overwrite: true to replace it.`);
45
60
  }
46
61
  this.items.set(adapter.id, adapter);
62
+ this.touch();
47
63
  this.notifyListeners();
48
64
  }
49
65
  /**
@@ -61,6 +77,7 @@ class WorkflowFormatRegistry extends BaseRegistry {
61
77
  * @returns Array of NodeMetadata from all format adapters
62
78
  */
63
79
  getAllFormatNodes() {
80
+ this.trackVersion(); // reactive dependency (reads items directly)
64
81
  const allNodes = [];
65
82
  for (const adapter of this.items.values()) {
66
83
  if (adapter.nodes && adapter.nodes.length > 0) {
@@ -76,6 +93,7 @@ class WorkflowFormatRegistry extends BaseRegistry {
76
93
  * @returns Array of NodeMetadata for the format, or empty array
77
94
  */
78
95
  getFormatNodes(formatId) {
96
+ this.trackVersion(); // reactive dependency (reads items directly)
79
97
  const adapter = this.items.get(formatId);
80
98
  return adapter?.nodes ?? [];
81
99
  }
@@ -92,5 +110,3 @@ class WorkflowFormatRegistry extends BaseRegistry {
92
110
  }));
93
111
  }
94
112
  }
95
- /** Singleton instance of the workflow format registry */
96
- export const workflowFormatRegistry = new WorkflowFormatRegistry();
@@ -15,9 +15,9 @@
15
15
  * const valid = validate(myWorkflow);
16
16
  * ```
17
17
  *
18
- * @module schema
18
+ * @module schemas
19
19
  */
20
- import workflowSchema from '../schemas/v1/workflow.schema.json';
20
+ import workflowSchema from './v1/workflow.schema.json';
21
21
  /** Current workflow schema format version */
22
22
  export declare const WORKFLOW_SCHEMA_VERSION = "1.0.0";
23
23
  export { workflowSchema };
@@ -15,9 +15,9 @@
15
15
  * const valid = validate(myWorkflow);
16
16
  * ```
17
17
  *
18
- * @module schema
18
+ * @module schemas
19
19
  */
20
- import workflowSchema from '../schemas/v1/workflow.schema.json';
20
+ import workflowSchema from './v1/workflow.schema.json';
21
21
  /** Current workflow schema format version */
22
22
  export const WORKFLOW_SCHEMA_VERSION = '1.0.0';
23
23
  export { workflowSchema };
@@ -1053,9 +1053,9 @@
1053
1053
  "WorkflowMetadata": {
1054
1054
  "type": "object",
1055
1055
  "properties": {
1056
- "version": {
1056
+ "schemaVersion": {
1057
1057
  "type": "string",
1058
- "description": "Workflow version"
1058
+ "description": "Workflow schema format version — identifies the document format, not the workflow's own revision."
1059
1059
  },
1060
1060
  "createdAt": {
1061
1061
  "type": "string",
@@ -1093,7 +1093,7 @@
1093
1093
  }
1094
1094
  },
1095
1095
  "required": [
1096
- "version",
1096
+ "schemaVersion",
1097
1097
  "createdAt",
1098
1098
  "updatedAt"
1099
1099
  ]
@@ -227,7 +227,6 @@ export class AgentSpecExecutionService {
227
227
  /** Get the config, throwing if not configured */
228
228
  getConfig() {
229
229
  this.ensureConfigured();
230
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
231
230
  return this.config;
232
231
  }
233
232
  startPolling(executionId, nameToNodeId, onNodeUpdate, onComplete, onError, intervalMs = 2000) {
@@ -6,6 +6,7 @@
6
6
  * @module services/apiVariableService
7
7
  */
8
8
  import type { VariableSchema, ApiVariablesConfig, AuthProvider } from '../types/index.js';
9
+ import type { EndpointConfig } from '../config/endpoints.js';
9
10
  /**
10
11
  * Context for variable API requests
11
12
  */
@@ -71,7 +72,7 @@ export declare function resolveEndpointUrl(template: string, context: VariableCo
71
72
  * }
72
73
  * ```
73
74
  */
74
- export declare function fetchVariableSchema(workflowId: string | undefined, nodeId: string, config: ApiVariablesConfig, authProvider?: AuthProvider): Promise<ApiVariableResult>;
75
+ export declare function fetchVariableSchema(endpointConfig: EndpointConfig | null, workflowId: string | undefined, nodeId: string, config: ApiVariablesConfig, authProvider?: AuthProvider): Promise<ApiVariableResult>;
75
76
  /**
76
77
  * Clears the variable schema cache.
77
78
  * Can optionally clear only entries matching a specific pattern.
@@ -5,7 +5,6 @@
5
5
  *
6
6
  * @module services/apiVariableService
7
7
  */
8
- import { getEndpointConfig } from './api.js';
9
8
  import { logger } from '../utils/logger.js';
10
9
  import { DEFAULT_CACHE_TTL_MS } from '../config/constants.js';
11
10
  /**
@@ -116,7 +115,7 @@ function resolveBodyTemplates(body, context) {
116
115
  * }
117
116
  * ```
118
117
  */
119
- export async function fetchVariableSchema(workflowId, nodeId, config, authProvider) {
118
+ export async function fetchVariableSchema(endpointConfig, workflowId, nodeId, config, authProvider) {
120
119
  const endpoint = config.endpoint;
121
120
  const context = { workflowId, nodeId };
122
121
  // Generate cache key
@@ -136,12 +135,9 @@ export async function fetchVariableSchema(workflowId, nodeId, config, authProvid
136
135
  // Resolve the URL with template variables
137
136
  let url = resolveEndpointUrl(endpoint.url, context);
138
137
  // If URL is relative, prepend base URL from endpoint config
139
- if (url.startsWith('/')) {
140
- const currentConfig = getEndpointConfig();
141
- if (currentConfig?.baseUrl) {
142
- const baseUrl = currentConfig.baseUrl.replace(/\/$/, '');
143
- url = `${baseUrl}${url}`;
144
- }
138
+ if (url.startsWith('/') && endpointConfig?.baseUrl) {
139
+ const baseUrl = endpointConfig.baseUrl.replace(/\/$/, '');
140
+ url = `${baseUrl}${url}`;
145
141
  }
146
142
  // Prepare request options
147
143
  const method = endpoint.method ?? 'GET';
@@ -161,19 +157,6 @@ export async function fetchVariableSchema(workflowId, nodeId, config, authProvid
161
157
  logger.warn('Failed to get auth headers:', error);
162
158
  }
163
159
  }
164
- // Add auth headers from endpoint config as fallback
165
- const currentConfig = getEndpointConfig();
166
- if (currentConfig?.auth) {
167
- if (currentConfig.auth.type === 'bearer' && currentConfig.auth.token) {
168
- headers['Authorization'] = headers['Authorization'] ?? `Bearer ${currentConfig.auth.token}`;
169
- }
170
- else if (currentConfig.auth.type === 'api_key' && currentConfig.auth.apiKey) {
171
- headers['X-API-Key'] = headers['X-API-Key'] ?? currentConfig.auth.apiKey;
172
- }
173
- else if (currentConfig.auth.type === 'custom' && currentConfig.auth.headers) {
174
- Object.assign(headers, currentConfig.auth.headers);
175
- }
176
- }
177
160
  // Prepare fetch options
178
161
  const fetchOptions = {
179
162
  method,
@@ -202,7 +185,7 @@ export async function fetchVariableSchema(workflowId, nodeId, config, authProvid
202
185
  const refreshed = await authProvider.onUnauthorized();
203
186
  if (refreshed) {
204
187
  // Retry with refreshed auth
205
- return fetchVariableSchema(workflowId, nodeId, config, authProvider);
188
+ return fetchVariableSchema(endpointConfig, workflowId, nodeId, config, authProvider);
206
189
  }
207
190
  }
208
191
  return {
@@ -23,6 +23,12 @@ interface AutoSaveOptions {
23
23
  * Optional callback for successful saves
24
24
  */
25
25
  onSuccess?: () => void;
26
+ /**
27
+ * Dirty-state probe for the owning FlowDrop instance, e.g.
28
+ * `() => fd.workflow.isDirty`. Defaults to the page-default
29
+ * instance's dirty state when omitted.
30
+ */
31
+ isDirty?: () => boolean;
26
32
  }
27
33
  /**
28
34
  * Initialize auto-save functionality based on user settings
@@ -68,6 +74,7 @@ export declare class AutoSaveManager {
68
74
  private onSave;
69
75
  private onError?;
70
76
  private onSuccess?;
77
+ private isDirtyProbe;
71
78
  /**
72
79
  * Create a new AutoSaveManager
73
80
  *