@flowdrop/flowdrop 1.15.0 → 2.0.0-beta.2

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 (235) hide show
  1. package/CHANGELOG.md +508 -0
  2. package/MIGRATION-2.0.md +629 -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/api/enhanced-client.js +6 -11
  8. package/dist/chat/batchFeedback.d.ts +39 -0
  9. package/dist/chat/batchFeedback.js +51 -0
  10. package/dist/commands/executor.js +15 -1
  11. package/dist/commands/storeIntegration.svelte.d.ts +4 -1
  12. package/dist/commands/storeIntegration.svelte.js +26 -21
  13. package/dist/commands/types.d.ts +2 -0
  14. package/dist/components/App.svelte +163 -192
  15. package/dist/components/App.svelte.d.ts +47 -8
  16. package/dist/components/ConfigForm.svelte +77 -49
  17. package/dist/components/ConfigModal.svelte +7 -2
  18. package/dist/components/ConnectionLine.svelte +4 -2
  19. package/dist/components/Navbar.svelte +61 -1
  20. package/dist/components/NodeSidebar.svelte +27 -45
  21. package/dist/components/NodeStatusOverlay.svelte +94 -6
  22. package/dist/components/NodeSwapPicker.svelte +10 -8
  23. package/dist/components/PipelineStatus.svelte +22 -68
  24. package/dist/components/PipelineStatus.svelte.d.ts +3 -0
  25. package/dist/components/PortCoordinateTracker.svelte +5 -6
  26. package/dist/components/SchemaForm.stories.svelte +1 -3
  27. package/dist/components/SchemaForm.svelte +22 -27
  28. package/dist/components/SchemaForm.svelte.d.ts +0 -8
  29. package/dist/components/SettingsModal.svelte +8 -3
  30. package/dist/components/SettingsPanel.svelte +20 -4
  31. package/dist/components/SwapMappingEditor.svelte +67 -49
  32. package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
  33. package/dist/components/UniversalNode.svelte +9 -7
  34. package/dist/components/WorkflowEditor.svelte +121 -111
  35. package/dist/components/WorkflowEditor.svelte.d.ts +21 -10
  36. package/dist/components/chat/AIChatPanel.svelte +98 -89
  37. package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
  38. package/dist/components/chat/CommandPreview.svelte +2 -1
  39. package/dist/components/console/CommandConsole.svelte +7 -5
  40. package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
  41. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
  42. package/dist/components/console/ConsoleInput.svelte +15 -6
  43. package/dist/components/console/ConsoleOutput.svelte +2 -1
  44. package/dist/components/form/FormArray.svelte +5 -9
  45. package/dist/components/form/FormArray.svelte.d.ts +2 -1
  46. package/dist/components/form/FormAutocomplete.svelte +16 -15
  47. package/dist/components/form/FormField.svelte +4 -2
  48. package/dist/components/form/FormFieldLight.svelte +34 -3
  49. package/dist/components/form/FormFieldLight.svelte.d.ts +12 -0
  50. package/dist/components/form/FormMarkdownEditor.svelte +9 -4
  51. package/dist/components/form/FormRangeField.svelte +1 -0
  52. package/dist/components/form/FormTemplateEditor.svelte +11 -3
  53. package/dist/components/form/FormToggle.svelte +5 -12
  54. package/dist/components/form/FormToggle.svelte.d.ts +4 -2
  55. package/dist/components/form/FormUISchemaRenderer.svelte +3 -1
  56. package/dist/components/form/templateAutocomplete.js +1 -5
  57. package/dist/components/form/types.d.ts +1 -14
  58. package/dist/components/interrupt/FormPrompt.svelte +3 -2
  59. package/dist/components/interrupt/InterruptBubble.svelte +25 -17
  60. package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
  61. package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
  62. package/dist/components/layouts/MainLayout.svelte +20 -13
  63. package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
  64. package/dist/components/nodes/AtomNode.svelte +17 -5
  65. package/dist/components/nodes/GatewayNode.svelte +19 -10
  66. package/dist/components/nodes/IdeaNode.svelte +7 -0
  67. package/dist/components/nodes/SimpleNode.svelte +11 -6
  68. package/dist/components/nodes/SquareNode.svelte +15 -8
  69. package/dist/components/nodes/TerminalNode.svelte +9 -4
  70. package/dist/components/nodes/ToolNode.svelte +7 -1
  71. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  72. package/dist/components/playground/ChatInput.svelte +11 -14
  73. package/dist/components/playground/ChatPanel.svelte +6 -49
  74. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  75. package/dist/components/playground/ControlPanel.svelte +134 -123
  76. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  77. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  78. package/dist/components/playground/InputCollector.svelte +11 -9
  79. package/dist/components/playground/MessageStream.svelte +17 -23
  80. package/dist/components/playground/PipelineKanbanView.svelte +69 -8
  81. package/dist/components/playground/PipelineKanbanView.svelte.d.ts +2 -0
  82. package/dist/components/playground/PipelinePanel.svelte +31 -8
  83. package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -0
  84. package/dist/components/playground/PipelineTableView.svelte +188 -44
  85. package/dist/components/playground/PipelineTableView.svelte.d.ts +2 -0
  86. package/dist/components/playground/Playground.svelte +154 -105
  87. package/dist/components/playground/Playground.svelte.d.ts +5 -0
  88. package/dist/components/playground/PlaygroundApp.svelte +11 -1
  89. package/dist/components/playground/PlaygroundApp.svelte.d.ts +6 -0
  90. package/dist/components/playground/PlaygroundModal.svelte +18 -3
  91. package/dist/components/playground/PlaygroundModal.svelte.d.ts +6 -0
  92. package/dist/components/playground/PlaygroundStudio.svelte +40 -32
  93. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +6 -0
  94. package/dist/components/playground/SessionManager.svelte +9 -12
  95. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +30 -1
  96. package/dist/components/playground/pipelineViewUtils.svelte.js +40 -3
  97. package/dist/config/endpoints.d.ts +23 -7
  98. package/dist/config/endpoints.js +30 -10
  99. package/dist/core/index.d.ts +5 -6
  100. package/dist/core/index.js +8 -12
  101. package/dist/display/index.d.ts +6 -3
  102. package/dist/display/index.js +7 -5
  103. package/dist/editor/index.d.ts +20 -21
  104. package/dist/editor/index.js +26 -36
  105. package/dist/form/code.d.ts +25 -15
  106. package/dist/form/code.js +44 -41
  107. package/dist/form/fieldRegistry.d.ts +17 -13
  108. package/dist/form/fieldRegistry.js +32 -12
  109. package/dist/form/full.d.ts +19 -14
  110. package/dist/form/full.js +26 -28
  111. package/dist/form/index.d.ts +3 -4
  112. package/dist/form/index.js +6 -5
  113. package/dist/form/markdown.d.ts +13 -8
  114. package/dist/form/markdown.js +22 -23
  115. package/dist/helpers/proximityConnect.d.ts +3 -2
  116. package/dist/helpers/proximityConnect.js +2 -5
  117. package/dist/helpers/workflowEditorHelper.d.ts +14 -5
  118. package/dist/helpers/workflowEditorHelper.js +28 -25
  119. package/dist/index.d.ts +28 -24
  120. package/dist/index.js +27 -50
  121. package/dist/messages/defaults.d.ts +2 -5
  122. package/dist/messages/defaults.js +3 -6
  123. package/dist/messages/index.d.ts +0 -1
  124. package/dist/messages/index.js +0 -1
  125. package/dist/mocks/app-forms.d.ts +6 -2
  126. package/dist/mocks/app-forms.js +11 -4
  127. package/dist/openapi/v1/openapi.yaml +3 -3
  128. package/dist/playground/index.d.ts +4 -5
  129. package/dist/playground/index.js +4 -32
  130. package/dist/playground/mount.d.ts +25 -0
  131. package/dist/playground/mount.js +50 -20
  132. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  133. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  134. package/dist/registry/builtinFormats.d.ts +9 -18
  135. package/dist/registry/builtinFormats.js +9 -39
  136. package/dist/registry/builtinNodeTypes.d.ts +53 -0
  137. package/dist/registry/builtinNodeTypes.js +67 -0
  138. package/dist/registry/builtinNodes.d.ts +2 -64
  139. package/dist/registry/builtinNodes.js +7 -103
  140. package/dist/registry/index.d.ts +3 -4
  141. package/dist/registry/index.js +4 -6
  142. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  143. package/dist/registry/nodeComponentRegistry.js +235 -17
  144. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  145. package/dist/registry/workflowFormatRegistry.js +24 -8
  146. package/dist/{schema → schemas}/index.d.ts +2 -2
  147. package/dist/{schema → schemas}/index.js +2 -2
  148. package/dist/schemas/v1/workflow.schema.json +3 -3
  149. package/dist/services/agentSpecExecutionService.d.ts +0 -2
  150. package/dist/services/agentSpecExecutionService.js +0 -3
  151. package/dist/services/apiVariableService.d.ts +2 -1
  152. package/dist/services/apiVariableService.js +16 -47
  153. package/dist/services/autoSaveService.d.ts +7 -0
  154. package/dist/services/autoSaveService.js +6 -4
  155. package/dist/services/categoriesApi.js +3 -6
  156. package/dist/services/chatService.d.ts +9 -4
  157. package/dist/services/chatService.js +23 -28
  158. package/dist/services/draftStorage.d.ts +129 -13
  159. package/dist/services/draftStorage.js +185 -37
  160. package/dist/services/dynamicSchemaService.d.ts +2 -1
  161. package/dist/services/dynamicSchemaService.js +5 -22
  162. package/dist/services/globalSave.d.ts +13 -12
  163. package/dist/services/globalSave.js +29 -51
  164. package/dist/services/historyService.d.ts +9 -3
  165. package/dist/services/historyService.js +9 -3
  166. package/dist/services/interruptService.d.ts +15 -9
  167. package/dist/services/interruptService.js +35 -37
  168. package/dist/services/nodeExecutionService.d.ts +18 -3
  169. package/dist/services/nodeExecutionService.js +71 -45
  170. package/dist/services/playgroundService.d.ts +16 -10
  171. package/dist/services/playgroundService.js +42 -43
  172. package/dist/services/portConfigApi.js +3 -6
  173. package/dist/services/settingsService.d.ts +9 -4
  174. package/dist/services/settingsService.js +23 -12
  175. package/dist/services/variableService.d.ts +2 -1
  176. package/dist/services/variableService.js +2 -2
  177. package/dist/services/workflowStorage.js +6 -6
  178. package/dist/stores/apiContext.d.ts +56 -0
  179. package/dist/stores/apiContext.js +80 -0
  180. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  181. package/dist/stores/categoriesStore.svelte.js +69 -64
  182. package/dist/stores/getInstance.svelte.d.ts +39 -0
  183. package/dist/stores/getInstance.svelte.js +65 -0
  184. package/dist/stores/historyStore.svelte.d.ts +77 -93
  185. package/dist/stores/historyStore.svelte.js +134 -160
  186. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  187. package/dist/stores/instanceContainer.svelte.js +114 -0
  188. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  189. package/dist/stores/interruptStore.svelte.js +253 -226
  190. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  191. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  192. package/dist/stores/playgroundStore.svelte.d.ts +169 -222
  193. package/dist/stores/playgroundStore.svelte.js +513 -580
  194. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  195. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  196. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  197. package/dist/stores/settingsStore.svelte.js +47 -12
  198. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  199. package/dist/stores/workflowStore.svelte.js +449 -501
  200. package/dist/stories/EdgeDecorator.svelte +5 -2
  201. package/dist/stories/NodeDecorator.svelte +5 -3
  202. package/dist/svelte-app.d.ts +60 -10
  203. package/dist/svelte-app.js +159 -54
  204. package/dist/types/auth.d.ts +9 -51
  205. package/dist/types/auth.js +4 -54
  206. package/dist/types/events.d.ts +6 -3
  207. package/dist/types/index.d.ts +37 -5
  208. package/dist/types/index.js +0 -1
  209. package/dist/types/navbar.d.ts +7 -0
  210. package/dist/types/playground.d.ts +18 -3
  211. package/dist/types/settings.d.ts +13 -0
  212. package/dist/types/settings.js +1 -0
  213. package/dist/utils/colors.d.ts +47 -21
  214. package/dist/utils/colors.js +69 -68
  215. package/dist/utils/connections.d.ts +9 -15
  216. package/dist/utils/connections.js +13 -32
  217. package/dist/utils/duration.d.ts +13 -0
  218. package/dist/utils/duration.js +45 -0
  219. package/dist/utils/edgeStyling.js +9 -5
  220. package/dist/utils/fetchWithAuth.d.ts +36 -15
  221. package/dist/utils/fetchWithAuth.js +53 -23
  222. package/dist/utils/icons.d.ts +5 -2
  223. package/dist/utils/icons.js +6 -5
  224. package/dist/utils/nodeSwap.d.ts +6 -2
  225. package/dist/utils/nodeSwap.js +62 -126
  226. package/dist/utils/nodeTypes.d.ts +17 -8
  227. package/dist/utils/nodeTypes.js +27 -20
  228. package/dist/utils/performanceUtils.js +7 -0
  229. package/package.json +7 -5
  230. package/dist/messages/deprecation.d.ts +0 -20
  231. package/dist/messages/deprecation.js +0 -33
  232. package/dist/registry/plugin.d.ts +0 -215
  233. package/dist/registry/plugin.js +0 -249
  234. package/dist/services/api.d.ts +0 -129
  235. package/dist/services/api.js +0 -217
@@ -1,34 +1,64 @@
1
1
  /**
2
- * Fetch authentication utilities
2
+ * Authenticated fetch
3
3
  *
4
- * Shared logic for building HTTP headers with authentication.
5
- * Used by components that make authenticated API requests (e.g., FormAutocomplete).
4
+ * The single fetch path for FlowDrop's per-instance services. It builds request
5
+ * headers (static endpoint headers + the {@link AuthProvider}'s headers + any
6
+ * caller headers) and uniformly applies the auth lifecycle: on `401` it invokes
7
+ * `onUnauthorized()` and — if that reports a successful refresh — retries the
8
+ * request once with freshly fetched headers; on `403` it invokes
9
+ * `onForbidden()`. Callers receive the raw {@link Response} and keep their own
10
+ * status/parse handling.
11
+ *
12
+ * Routing every service through this helper means a configured `AuthProvider`
13
+ * authenticates *and* refreshes consistently — matching the behaviour the typed
14
+ * workflow/node API gets from {@link EnhancedFlowDropApiClient}.
6
15
  *
7
16
  * @module utils/fetchWithAuth
8
17
  */
18
+ import { getEndpointHeaders } from '../config/endpoints.js';
19
+ const DEFAULT_HEADERS = {
20
+ Accept: 'application/json',
21
+ 'Content-Type': 'application/json'
22
+ };
9
23
  /**
10
- * Build fetch headers with optional authentication
11
- *
12
- * Constructs standard JSON request headers and merges in authentication
13
- * headers from the provided AuthProvider, if available.
14
- *
15
- * @param authProvider - Optional auth provider to get headers from
16
- * @returns Promise resolving to a complete headers object
24
+ * Build the merged header set: base headers < auth headers < per-request headers.
17
25
  *
18
- * @example
19
- * ```typescript
20
- * const headers = await buildFetchHeaders(authProvider);
21
- * const response = await fetch(url, { headers });
22
- * ```
26
+ * Re-invoked for the retry so a refreshed token is picked up.
23
27
  */
24
- export async function buildFetchHeaders(authProvider) {
25
- const headers = {
26
- Accept: 'application/json',
27
- 'Content-Type': 'application/json'
28
- };
29
- if (authProvider) {
30
- const authHeaders = await authProvider.getAuthHeaders();
31
- Object.assign(headers, authHeaders);
28
+ async function buildHeaders(init, opts) {
29
+ const headers = opts.config && opts.endpointKey
30
+ ? getEndpointHeaders(opts.config, opts.endpointKey)
31
+ : { ...DEFAULT_HEADERS, ...opts.baseHeaders };
32
+ if (opts.authProvider) {
33
+ Object.assign(headers, await opts.authProvider.getAuthHeaders());
34
+ }
35
+ if (init.headers) {
36
+ Object.assign(headers, init.headers);
32
37
  }
33
38
  return headers;
34
39
  }
40
+ /**
41
+ * Perform an authenticated fetch with consistent 401/403 handling.
42
+ *
43
+ * @param url - The fully-resolved request URL
44
+ * @param init - Standard fetch options (method, body, signal, headers, …)
45
+ * @param opts - Auth provider and header source
46
+ * @returns The raw {@link Response}; callers handle `.ok`/parsing themselves
47
+ */
48
+ export async function authenticatedFetch(url, init = {}, opts = {}) {
49
+ const headers = await buildHeaders(init, opts);
50
+ let response = await fetch(url, { ...init, headers });
51
+ // 401 → let the provider refresh, then retry once with fresh headers.
52
+ if (response.status === 401 && opts.authProvider?.onUnauthorized) {
53
+ const refreshed = await opts.authProvider.onUnauthorized();
54
+ if (refreshed) {
55
+ const retryHeaders = await buildHeaders(init, opts);
56
+ response = await fetch(url, { ...init, headers: retryHeaders });
57
+ }
58
+ }
59
+ // 403 → notify the provider (e.g. surface a permission error).
60
+ if (response.status === 403 && opts.authProvider?.onForbidden) {
61
+ await opts.authProvider.onForbidden();
62
+ }
63
+ return response;
64
+ }
@@ -3,6 +3,7 @@
3
3
  * Ensures consistent icon usage across all components
4
4
  */
5
5
  import type { NodeCategory } from '../types/index.js';
6
+ import type { CategoriesStore } from '../stores/categoriesStore.svelte.js';
6
7
  /**
7
8
  * Default icons for different contexts
8
9
  */
@@ -72,19 +73,21 @@ export declare const DEFAULT_ICONS: {
72
73
  export declare const CATEGORY_ICONS: Record<string, string>;
73
74
  /**
74
75
  * Get the appropriate icon for a node
76
+ * @param categories - The instance's categories store (e.g. `fd.categories`)
75
77
  * @param nodeIcon - The node's specific icon
76
78
  * @param category - The node's category
77
79
  * @returns The icon to use
78
80
  */
79
- export declare function getNodeIcon(nodeIcon?: string, category?: NodeCategory): string;
81
+ export declare function getNodeIcon(categories: CategoriesStore, nodeIcon?: string, category?: NodeCategory): string;
80
82
  /**
81
83
  * Get the appropriate icon for a category.
82
84
  * Checks the categories store first (which includes API overrides),
83
85
  * then falls back to the static CATEGORY_ICONS map, then to the default.
86
+ * @param categories - The instance's categories store (e.g. `fd.categories`)
84
87
  * @param category - The category
85
88
  * @returns The icon to use
86
89
  */
87
- export declare function getCategoryIcon(category: NodeCategory): string;
90
+ export declare function getCategoryIcon(categories: CategoriesStore, category: NodeCategory): string;
88
91
  /**
89
92
  * Get a default icon by key
90
93
  * @param key - The icon key from DEFAULT_ICONS
@@ -2,7 +2,6 @@
2
2
  * Centralized icon management for FlowDrop
3
3
  * Ensures consistent icon usage across all components
4
4
  */
5
- import { getCategoryIcon as getCategoryIconFromStore } from '../stores/categoriesStore.svelte.js';
6
5
  /**
7
6
  * Default icons for different contexts
8
7
  */
@@ -103,18 +102,19 @@ export const CATEGORY_ICONS = {
103
102
  };
104
103
  /**
105
104
  * Get the appropriate icon for a node
105
+ * @param categories - The instance's categories store (e.g. `fd.categories`)
106
106
  * @param nodeIcon - The node's specific icon
107
107
  * @param category - The node's category
108
108
  * @returns The icon to use
109
109
  */
110
- export function getNodeIcon(nodeIcon, category) {
110
+ export function getNodeIcon(categories, nodeIcon, category) {
111
111
  // If node has a specific icon, use it
112
112
  if (nodeIcon) {
113
113
  return nodeIcon;
114
114
  }
115
115
  // If category is provided, use category icon from store (which includes API overrides)
116
116
  if (category) {
117
- return getCategoryIconFromStore(category);
117
+ return categories.getIcon(category);
118
118
  }
119
119
  // Fallback to default node icon
120
120
  return DEFAULT_ICONS.NODE;
@@ -123,11 +123,12 @@ export function getNodeIcon(nodeIcon, category) {
123
123
  * Get the appropriate icon for a category.
124
124
  * Checks the categories store first (which includes API overrides),
125
125
  * then falls back to the static CATEGORY_ICONS map, then to the default.
126
+ * @param categories - The instance's categories store (e.g. `fd.categories`)
126
127
  * @param category - The category
127
128
  * @returns The icon to use
128
129
  */
129
- export function getCategoryIcon(category) {
130
- return getCategoryIconFromStore(category);
130
+ export function getCategoryIcon(categories, category) {
131
+ return categories.getIcon(category);
131
132
  }
132
133
  /**
133
134
  * Get a default icon by key
@@ -161,6 +161,10 @@ export declare function mapConfig(oldConfig: ConfigValues, newConfigSchema: Conf
161
161
  *
162
162
  * This does NOT mutate anything — it returns a preview that can be displayed
163
163
  * to the user for confirmation before executing the swap.
164
+ *
165
+ * Thin wrapper over {@link computeSwapPreviewWithOptions} — with no strategies
166
+ * or overrides the cascade reduces to the built-in exact matching, so both
167
+ * entry points share one implementation.
164
168
  */
165
169
  export declare function computeSwapPreview(oldNode: WorkflowNode, newMetadata: NodeMetadata, edges: WorkflowEdge[], allNodes: WorkflowNode[], checker?: PortCompatibilityChecker | null): SwapPreview;
166
170
  /**
@@ -183,7 +187,7 @@ export declare function getVersionUpgrade(currentMetadata: NodeMetadata, allNode
183
187
  *
184
188
  * Resolution order:
185
189
  * 1. Check strategies — first canHandle() match wins for mapPorts()/mapConfig()
186
- * 2. Fall through to built-in 3-pass for ports not covered by strategy
190
+ * 2. Fall through to built-in exact matching (ID, then name) for ports not covered by strategy
187
191
  * 3. Apply portOverrides on top (highest priority — user's manual overrides)
188
192
  * 4. Same cascade for config
189
193
  */
@@ -199,7 +203,7 @@ export declare function computeInteractiveState(oldNode: WorkflowNode, newMetada
199
203
  * Convert user-edited InteractiveSwapState back into a SwapPreview
200
204
  * for executeSwap(). Pure function, no side effects.
201
205
  */
202
- export declare function buildSwapPreviewFromState(state: InteractiveSwapState, allEdges: WorkflowEdge[]): SwapPreview;
206
+ export declare function buildSwapPreviewFromState(state: InteractiveSwapState, _allEdges: WorkflowEdge[]): SwapPreview;
203
207
  /**
204
208
  * Headless one-shot swap with full validation.
205
209
  *
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module utils/nodeSwap
8
8
  */
9
- import { buildHandleId, extractPortId, extractDirection } from './handleIds.js';
9
+ import { buildHandleId, extractPortId } from './handleIds.js';
10
10
  import { generateNodeId } from './nodeIds.js';
11
11
  /** Error class for swap validation failures. */
12
12
  export class SwapValidationError extends Error {
@@ -86,16 +86,17 @@ export function mapConfig(oldConfig, newConfigSchema, newDefaults = {}) {
86
86
  }
87
87
  return { config, carriedOver, reset };
88
88
  }
89
- // =========================================================================
90
- // Port matching
91
- // =========================================================================
92
89
  /**
93
90
  * Find the best matching port on the new node for a given old port.
94
91
  *
95
- * Three-pass strategy:
92
+ * Two-pass strategy — exact matches only:
96
93
  * 1. Exact port ID match with compatible dataType
97
- * 2. Port name match (case-insensitive) with compatible dataType
98
- * 3. First available port with compatible dataType
94
+ * 2. Exact port name match (case-insensitive) with compatible dataType
95
+ *
96
+ * There is deliberately no fuzzy "first compatible dataType" fallback:
97
+ * it silently rewired edges to the wrong port (e.g. a `text` edge landing
98
+ * on `assistant_message`). If neither ID nor name matches, the edge is
99
+ * dropped and reported instead.
99
100
  */
100
101
  function findMatchingPort(oldPort, newPorts, usedPortIds, checker) {
101
102
  const available = newPorts.filter((p) => p.type === oldPort.type && !usedPortIds.has(p.id));
@@ -116,11 +117,7 @@ function findMatchingPort(oldPort, newPorts, usedPortIds, checker) {
116
117
  // Pass 2: name match (case-insensitive)
117
118
  const oldNameLower = oldPort.name.toLowerCase();
118
119
  const nameMatch = available.find((p) => p.name.toLowerCase() === oldNameLower && isCompatible(oldPort, p));
119
- if (nameMatch)
120
- return nameMatch;
121
- // Pass 3: first compatible dataType
122
- const typeMatch = available.find((p) => isCompatible(oldPort, p));
123
- return typeMatch ?? null;
120
+ return nameMatch ?? null;
124
121
  }
125
122
  /**
126
123
  * Resolve the port metadata for an edge endpoint on a given node.
@@ -142,65 +139,13 @@ function resolvePort(node, handleId, direction) {
142
139
  *
143
140
  * This does NOT mutate anything — it returns a preview that can be displayed
144
141
  * to the user for confirmation before executing the swap.
142
+ *
143
+ * Thin wrapper over {@link computeSwapPreviewWithOptions} — with no strategies
144
+ * or overrides the cascade reduces to the built-in exact matching, so both
145
+ * entry points share one implementation.
145
146
  */
146
147
  export function computeSwapPreview(oldNode, newMetadata, edges, allNodes, checker = null) {
147
- const oldNodeId = oldNode.id;
148
- const newNodeId = generateNodeId(newMetadata.id, allNodes);
149
- // Collect all edges connected to the old node
150
- const connectedEdges = edges.filter((e) => e.source === oldNodeId || e.target === oldNodeId);
151
- // Track which ports on the new node have been claimed
152
- const usedInputPortIds = new Set();
153
- const usedOutputPortIds = new Set();
154
- const keptEdges = [];
155
- const droppedEdges = [];
156
- for (const edge of connectedEdges) {
157
- const isSource = edge.source === oldNodeId;
158
- const direction = isSource ? 'output' : 'input';
159
- const handleId = isSource ? edge.sourceHandle : edge.targetHandle;
160
- const usedPorts = isSource ? usedOutputPortIds : usedInputPortIds;
161
- // Resolve the old port
162
- const oldPort = resolvePort(oldNode, handleId, direction);
163
- if (!oldPort) {
164
- droppedEdges.push({
165
- edge,
166
- reason: `Port not found on original node`
167
- });
168
- continue;
169
- }
170
- // Find matching port on new node
171
- const newPorts = direction === 'input' ? newMetadata.inputs : newMetadata.outputs;
172
- const match = findMatchingPort(oldPort, newPorts, usedPorts, checker);
173
- if (!match) {
174
- droppedEdges.push({
175
- edge,
176
- reason: `No compatible ${direction} port found on "${newMetadata.name}"`
177
- });
178
- continue;
179
- }
180
- usedPorts.add(match.id);
181
- // Build the rewritten edge
182
- const newHandleId = buildHandleId(newNodeId, direction, match.id);
183
- const newEdge = { ...edge };
184
- if (isSource) {
185
- newEdge.source = newNodeId;
186
- newEdge.sourceHandle = newHandleId;
187
- }
188
- else {
189
- newEdge.target = newNodeId;
190
- newEdge.targetHandle = newHandleId;
191
- }
192
- keptEdges.push({ edge, newEdge });
193
- }
194
- // Config mapping preview
195
- const { carriedOver, reset } = mapConfig(oldNode.data.config, newMetadata.configSchema, newMetadata.config);
196
- return {
197
- keptEdges,
198
- droppedEdges,
199
- hasDataLoss: droppedEdges.length > 0,
200
- newNodeId,
201
- configCarriedOver: carriedOver,
202
- configReset: reset
203
- };
148
+ return computeSwapPreviewWithOptions(oldNode, newMetadata, edges, allNodes, { checker });
204
149
  }
205
150
  // =========================================================================
206
151
  // Swap execution
@@ -231,6 +176,9 @@ export function executeSwap(oldNode, newMetadata, preview, allNodes, allEdges) {
231
176
  label: newMetadata.name,
232
177
  config: mappedConfig,
233
178
  metadata: newMetadata,
179
+ // Node components derive their handle IDs from data.nodeId — without it
180
+ // every edge anchored to this node silently disappears from the canvas.
181
+ nodeId: newNodeId,
234
182
  extensions
235
183
  }
236
184
  };
@@ -298,7 +246,7 @@ function classifyMatch(oldPort, matchedPort) {
298
246
  *
299
247
  * Resolution order:
300
248
  * 1. Check strategies — first canHandle() match wins for mapPorts()/mapConfig()
301
- * 2. Fall through to built-in 3-pass for ports not covered by strategy
249
+ * 2. Fall through to built-in exact matching (ID, then name) for ports not covered by strategy
302
250
  * 3. Apply portOverrides on top (highest priority — user's manual overrides)
303
251
  * 4. Same cascade for config
304
252
  */
@@ -332,84 +280,73 @@ export function computeSwapPreviewWithOptions(oldNode, newMetadata, edges, allNo
332
280
  for (const override of options.portOverrides ?? []) {
333
281
  portOverrideLookup.set(`${override.direction}:${override.oldPortId}`, override.newPortId);
334
282
  }
335
- // Track used ports
283
+ // Track ports on the new node claimed by a DIFFERENT old port
336
284
  const usedInputPortIds = new Set();
337
285
  const usedOutputPortIds = new Set();
338
- const keptEdges = [];
339
- const droppedEdges = [];
340
- for (const edge of connectedEdges) {
341
- const isSource = edge.source === oldNodeId;
342
- const direction = isSource ? 'output' : 'input';
343
- const handleId = isSource ? edge.sourceHandle : edge.targetHandle;
344
- const usedPorts = isSource ? usedOutputPortIds : usedInputPortIds;
345
- const oldPort = resolvePort(oldNode, handleId, direction);
346
- if (!oldPort) {
347
- droppedEdges.push({ edge, reason: 'Port not found on original node' });
348
- continue;
349
- }
286
+ // Memoized old-port → new-port resolutions so every edge anchored on the
287
+ // same old port maps to the same new port (multi-connection ports, fan-outs).
288
+ const portResolutions = new Map();
289
+ /** Resolve a new port for an old port through the priority cascade. */
290
+ const resolveNewPort = (oldPort, direction, usedPorts) => {
291
+ const newPorts = direction === 'input' ? newMetadata.inputs : newMetadata.outputs;
350
292
  // Priority 1: Manual port override
351
293
  const overrideKey = `${direction}:${oldPort.id}`;
352
294
  if (portOverrideLookup.has(overrideKey)) {
353
295
  const overrideNewPortId = portOverrideLookup.get(overrideKey);
354
296
  if (overrideNewPortId === null) {
355
- droppedEdges.push({ edge, reason: 'Manually dropped' });
356
- continue;
297
+ return { port: null, reason: 'Manually dropped' };
357
298
  }
358
- const newPorts = direction === 'input' ? newMetadata.inputs : newMetadata.outputs;
359
299
  const overridePort = newPorts.find((p) => p.id === overrideNewPortId);
360
300
  if (overridePort) {
361
- usedPorts.add(overridePort.id);
362
- const newHandleId = buildHandleId(newNodeId, direction, overridePort.id);
363
- const newEdge = { ...edge };
364
- if (isSource) {
365
- newEdge.source = newNodeId;
366
- newEdge.sourceHandle = newHandleId;
367
- }
368
- else {
369
- newEdge.target = newNodeId;
370
- newEdge.targetHandle = newHandleId;
371
- }
372
- keptEdges.push({ edge, newEdge });
373
- continue;
301
+ return { port: overridePort };
374
302
  }
375
303
  }
376
304
  // Priority 2: Strategy port mapping
377
305
  if (strategyPortMap && oldPort.id in strategyPortMap) {
378
306
  const strategyNewPortId = strategyPortMap[oldPort.id];
379
307
  if (strategyNewPortId === null) {
380
- droppedEdges.push({ edge, reason: 'Dropped by strategy' });
381
- continue;
308
+ return { port: null, reason: 'Dropped by strategy' };
382
309
  }
383
- const newPorts = direction === 'input' ? newMetadata.inputs : newMetadata.outputs;
384
310
  const strategyPort = newPorts.find((p) => p.id === strategyNewPortId);
385
311
  if (strategyPort && !usedPorts.has(strategyPort.id)) {
386
- usedPorts.add(strategyPort.id);
387
- const newHandleId = buildHandleId(newNodeId, direction, strategyPort.id);
388
- const newEdge = { ...edge };
389
- if (isSource) {
390
- newEdge.source = newNodeId;
391
- newEdge.sourceHandle = newHandleId;
392
- }
393
- else {
394
- newEdge.target = newNodeId;
395
- newEdge.targetHandle = newHandleId;
396
- }
397
- keptEdges.push({ edge, newEdge });
398
- continue;
312
+ return { port: strategyPort };
399
313
  }
400
314
  }
401
- // Priority 3: Built-in 3-pass matching
402
- const newPorts = direction === 'input' ? newMetadata.inputs : newMetadata.outputs;
315
+ // Priority 3: Built-in exact matching (ID, then name)
403
316
  const match = findMatchingPort(oldPort, newPorts, usedPorts, checker);
404
317
  if (!match) {
405
- droppedEdges.push({
406
- edge,
407
- reason: `No compatible ${direction} port found on "${newMetadata.name}"`
408
- });
318
+ return {
319
+ port: null,
320
+ reason: `No matching ${direction} port for "${oldPort.id}" on "${newMetadata.name}"`
321
+ };
322
+ }
323
+ return { port: match };
324
+ };
325
+ const keptEdges = [];
326
+ const droppedEdges = [];
327
+ for (const edge of connectedEdges) {
328
+ const isSource = edge.source === oldNodeId;
329
+ const direction = isSource ? 'output' : 'input';
330
+ const handleId = isSource ? edge.sourceHandle : edge.targetHandle;
331
+ const usedPorts = isSource ? usedOutputPortIds : usedInputPortIds;
332
+ const oldPort = resolvePort(oldNode, handleId, direction);
333
+ if (!oldPort) {
334
+ droppedEdges.push({ edge, reason: 'Port not found on original node' });
409
335
  continue;
410
336
  }
411
- usedPorts.add(match.id);
412
- const newHandleId = buildHandleId(newNodeId, direction, match.id);
337
+ const matchKey = `${direction}:${oldPort.id}`;
338
+ let resolved = portResolutions.get(matchKey);
339
+ if (!resolved) {
340
+ resolved = resolveNewPort(oldPort, direction, usedPorts);
341
+ portResolutions.set(matchKey, resolved);
342
+ if (resolved.port)
343
+ usedPorts.add(resolved.port.id);
344
+ }
345
+ if (!resolved.port) {
346
+ droppedEdges.push({ edge, reason: resolved.reason });
347
+ continue;
348
+ }
349
+ const newHandleId = buildHandleId(newNodeId, direction, resolved.port.id);
413
350
  const newEdge = { ...edge };
414
351
  if (isSource) {
415
352
  newEdge.source = newNodeId;
@@ -422,7 +359,7 @@ export function computeSwapPreviewWithOptions(oldNode, newMetadata, edges, allNo
422
359
  keptEdges.push({ edge, newEdge });
423
360
  }
424
361
  // Config mapping — apply strategy then overrides
425
- const { config: baseConfig, carriedOver, reset } = mapConfig(oldNode.data.config, newMetadata.configSchema, newMetadata.config);
362
+ const { carriedOver, reset } = mapConfig(oldNode.data.config, newMetadata.configSchema, newMetadata.config);
426
363
  // Apply strategy config overrides
427
364
  if (strategyConfigMap) {
428
365
  for (const [key, mapping] of Object.entries(strategyConfigMap)) {
@@ -482,7 +419,6 @@ export function computeSwapPreviewWithOptions(oldNode, newMetadata, edges, allNo
482
419
  * with match quality annotations and isFlat flags.
483
420
  */
484
421
  export function computeInteractiveState(oldNode, newMetadata, edges, allNodes, options = {}) {
485
- const checker = options.checker ?? null;
486
422
  const oldNodeId = oldNode.id;
487
423
  const newNodeId = generateNodeId(newMetadata.id, allNodes);
488
424
  const connectedEdges = edges.filter((e) => e.source === oldNodeId || e.target === oldNodeId);
@@ -567,7 +503,7 @@ function isPrimitive(value) {
567
503
  * Convert user-edited InteractiveSwapState back into a SwapPreview
568
504
  * for executeSwap(). Pure function, no side effects.
569
505
  */
570
- export function buildSwapPreviewFromState(state, allEdges) {
506
+ export function buildSwapPreviewFromState(state, _allEdges) {
571
507
  const keptEdges = [];
572
508
  const droppedEdges = [];
573
509
  for (const mapping of state.portMappings) {
@@ -10,14 +10,16 @@
10
10
  * Works with both built-in types and custom registered types.
11
11
  */
12
12
  import type { NodeType, NodeMetadata } from '../types/index.js';
13
+ import type { NodeComponentRegistry } from '../registry/nodeComponentRegistry.js';
13
14
  /**
14
15
  * Gets the SvelteFlow component name for a given NodeType.
15
16
  * Uses the node component registry to resolve types.
16
17
  *
18
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
17
19
  * @param nodeType - The node type identifier
18
20
  * @returns The component name to use
19
21
  */
20
- export declare function getComponentNameForNodeType(nodeType: NodeType | string): string;
22
+ export declare function getComponentNameForNodeType(registry: NodeComponentRegistry, nodeType: NodeType | string): string;
21
23
  /**
22
24
  * Gets the available node types for a given NodeMetadata.
23
25
  * Priority: supportedTypes > type > "default"
@@ -43,28 +45,31 @@ export declare function getPrimaryNodeType(metadata: NodeMetadata): NodeType | s
43
45
  * 3. First supportedType
44
46
  * 4. "default"
45
47
  *
48
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
46
49
  * @param metadata - The node metadata
47
50
  * @param configNodeType - Optional type from user config
48
51
  * @returns The resolved node type
49
52
  */
50
- export declare function resolveNodeType(metadata: NodeMetadata, configNodeType?: string): NodeType | string;
53
+ export declare function resolveNodeType(registry: NodeComponentRegistry, metadata: NodeMetadata, configNodeType?: string): NodeType | string;
51
54
  /**
52
55
  * Gets the SvelteFlow component name for resolved node type.
53
56
  * This is the main function used by UniversalNode to determine which component to render.
54
57
  *
58
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
55
59
  * @param metadata - The node metadata
56
60
  * @param configNodeType - Optional type from user config
57
61
  * @returns The component name to use
58
62
  */
59
- export declare function resolveComponentName(metadata: NodeMetadata, configNodeType?: string): string;
63
+ export declare function resolveComponentName(registry: NodeComponentRegistry, metadata: NodeMetadata, configNodeType?: string): string;
60
64
  /**
61
65
  * Validates if a node type is supported by the given metadata.
62
66
  *
67
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
63
68
  * @param metadata - The node metadata
64
69
  * @param nodeType - The type to check
65
70
  * @returns true if the type is supported
66
71
  */
67
- export declare function isNodeTypeSupported(metadata: NodeMetadata, nodeType: NodeType | string): boolean;
72
+ export declare function isNodeTypeSupported(registry: NodeComponentRegistry, metadata: NodeMetadata, nodeType: NodeType | string): boolean;
68
73
  /**
69
74
  * Gets oneOf options for node type configuration.
70
75
  * Used in config schemas to show available options with labels.
@@ -73,11 +78,12 @@ export declare function isNodeTypeSupported(metadata: NodeMetadata, nodeType: No
73
78
  * - Types specified in metadata.supportedTypes
74
79
  * - Registered custom types (optionally filtered)
75
80
  *
81
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
76
82
  * @param metadata - The node metadata
77
83
  * @param includeCustomTypes - Whether to include registered custom types
78
84
  * @returns Array of oneOf items with const (type value) and title (display name)
79
85
  */
80
- export declare function getNodeTypeOneOfOptions(metadata: NodeMetadata, includeCustomTypes?: boolean): Array<{
86
+ export declare function getNodeTypeOneOfOptions(registry: NodeComponentRegistry, metadata: NodeMetadata, includeCustomTypes?: boolean): Array<{
81
87
  const: string;
82
88
  title: string;
83
89
  }>;
@@ -88,11 +94,12 @@ export declare function getNodeTypeOneOfOptions(metadata: NodeMetadata, includeC
88
94
  * Uses JSON Schema `oneOf` pattern with `const`/`title` for labeled options,
89
95
  * which is the standard approach supported by form components.
90
96
  *
97
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
91
98
  * @param metadata - The node metadata
92
99
  * @param defaultType - Optional default type override
93
100
  * @returns Config schema property object with oneOf for labeled options
94
101
  */
95
- export declare function createNodeTypeConfigProperty(metadata: NodeMetadata, defaultType?: NodeType | string): {
102
+ export declare function createNodeTypeConfigProperty(registry: NodeComponentRegistry, metadata: NodeMetadata, defaultType?: NodeType | string): {
96
103
  type: "string";
97
104
  title: string;
98
105
  description: string;
@@ -105,13 +112,15 @@ export declare function createNodeTypeConfigProperty(metadata: NodeMetadata, def
105
112
  /**
106
113
  * Check if a type string represents a valid registered or built-in type.
107
114
  *
115
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
108
116
  * @param type - The type to check
109
117
  * @returns true if the type is valid
110
118
  */
111
- export declare function isValidNodeType(type: string): boolean;
119
+ export declare function isValidNodeType(registry: NodeComponentRegistry, type: string): boolean;
112
120
  /**
113
121
  * Get all available node types (built-in + registered).
114
122
  *
123
+ * @param registry - The instance's node component registry (e.g. `fd.nodes`)
115
124
  * @returns Array of all valid node type identifiers
116
125
  */
117
- export declare function getAllNodeTypes(): string[];
126
+ export declare function getAllNodeTypes(registry: NodeComponentRegistry): string[];