@flowdrop/flowdrop 1.3.0 → 1.5.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.
Files changed (114) hide show
  1. package/README.md +68 -24
  2. package/dist/adapters/WorkflowAdapter.js +2 -22
  3. package/dist/adapters/agentspec/autoLayout.d.ts +51 -5
  4. package/dist/adapters/agentspec/autoLayout.js +120 -23
  5. package/dist/chat/commandClassifier.d.ts +19 -0
  6. package/dist/chat/commandClassifier.js +30 -0
  7. package/dist/chat/index.d.ts +27 -0
  8. package/dist/chat/index.js +32 -0
  9. package/dist/chat/responseParser.d.ts +21 -0
  10. package/dist/chat/responseParser.js +87 -0
  11. package/dist/commands/batch.d.ts +18 -0
  12. package/dist/commands/batch.js +56 -0
  13. package/dist/commands/executor.d.ts +37 -0
  14. package/dist/commands/executor.js +1044 -0
  15. package/dist/commands/index.d.ts +14 -0
  16. package/dist/commands/index.js +17 -0
  17. package/dist/commands/parser.d.ts +16 -0
  18. package/dist/commands/parser.js +278 -0
  19. package/dist/commands/positioner.d.ts +19 -0
  20. package/dist/commands/positioner.js +33 -0
  21. package/dist/commands/storeIntegration.svelte.d.ts +16 -0
  22. package/dist/commands/storeIntegration.svelte.js +67 -0
  23. package/dist/commands/types.d.ts +343 -0
  24. package/dist/commands/types.js +45 -0
  25. package/dist/components/App.svelte +431 -17
  26. package/dist/components/App.svelte.d.ts +10 -0
  27. package/dist/components/CanvasBanner.stories.svelte +6 -2
  28. package/dist/components/CanvasController.svelte +38 -0
  29. package/dist/components/CanvasController.svelte.d.ts +32 -0
  30. package/dist/components/ConfigMappingRow.svelte +130 -0
  31. package/dist/components/ConfigMappingRow.svelte.d.ts +8 -0
  32. package/dist/components/ConfigPanel.svelte +56 -7
  33. package/dist/components/ConfigPanel.svelte.d.ts +2 -0
  34. package/dist/components/FlowDropEdge.svelte +8 -57
  35. package/dist/components/Logo.svelte +14 -14
  36. package/dist/components/LogsSidebar.svelte +5 -5
  37. package/dist/components/Navbar.svelte +58 -10
  38. package/dist/components/Navbar.svelte.d.ts +7 -0
  39. package/dist/components/NodeSidebar.svelte +238 -362
  40. package/dist/components/NodeSwapPicker.svelte +537 -0
  41. package/dist/components/NodeSwapPicker.svelte.d.ts +16 -0
  42. package/dist/components/PortMappingRow.svelte +209 -0
  43. package/dist/components/PortMappingRow.svelte.d.ts +12 -0
  44. package/dist/components/SwapMappingEditor.svelte +550 -0
  45. package/dist/components/SwapMappingEditor.svelte.d.ts +12 -0
  46. package/dist/components/WorkflowEditor.svelte +99 -4
  47. package/dist/components/WorkflowEditor.svelte.d.ts +8 -0
  48. package/dist/components/chat/AIChatPanel.svelte +658 -0
  49. package/dist/components/chat/AIChatPanel.svelte.d.ts +13 -0
  50. package/dist/components/chat/CommandPreview.svelte +184 -0
  51. package/dist/components/chat/CommandPreview.svelte.d.ts +9 -0
  52. package/dist/components/console/CommandConsole.stories.svelte +93 -0
  53. package/dist/components/console/CommandConsole.stories.svelte.d.ts +27 -0
  54. package/dist/components/console/CommandConsole.svelte +259 -0
  55. package/dist/components/console/CommandConsole.svelte.d.ts +11 -0
  56. package/dist/components/console/ConsoleAutocomplete.svelte +139 -0
  57. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +21 -0
  58. package/dist/components/console/ConsoleInput.svelte +712 -0
  59. package/dist/components/console/ConsoleInput.svelte.d.ts +16 -0
  60. package/dist/components/console/ConsoleOutput.svelte +121 -0
  61. package/dist/components/console/ConsoleOutput.svelte.d.ts +11 -0
  62. package/dist/components/console/formatters.d.ts +26 -0
  63. package/dist/components/console/formatters.js +118 -0
  64. package/dist/components/interrupt/index.d.ts +1 -0
  65. package/dist/components/interrupt/index.js +1 -0
  66. package/dist/components/nodes/SimpleNode.stories.svelte +64 -0
  67. package/dist/components/nodes/SimpleNode.svelte +27 -11
  68. package/dist/components/nodes/SquareNode.stories.svelte +45 -0
  69. package/dist/components/nodes/SquareNode.svelte +27 -11
  70. package/dist/components/nodes/WorkflowNode.stories.svelte +63 -0
  71. package/dist/config/endpoints.d.ts +8 -0
  72. package/dist/config/endpoints.js +5 -0
  73. package/dist/core/index.d.ts +5 -0
  74. package/dist/core/index.js +9 -0
  75. package/dist/editor/index.d.ts +3 -1
  76. package/dist/editor/index.js +4 -2
  77. package/dist/helpers/proximityConnect.js +8 -1
  78. package/dist/helpers/workflowEditorHelper.d.ts +3 -53
  79. package/dist/helpers/workflowEditorHelper.js +13 -228
  80. package/dist/playground/index.d.ts +1 -1
  81. package/dist/playground/index.js +1 -1
  82. package/dist/schemas/v1/workflow.schema.json +107 -22
  83. package/dist/services/chatService.d.ts +65 -0
  84. package/dist/services/chatService.js +131 -0
  85. package/dist/services/historyService.d.ts +6 -4
  86. package/dist/services/historyService.js +21 -6
  87. package/dist/skins/slate.js +16 -0
  88. package/dist/stores/interruptStore.svelte.js +6 -1
  89. package/dist/stores/playgroundStore.svelte.d.ts +1 -1
  90. package/dist/stores/playgroundStore.svelte.js +11 -2
  91. package/dist/stores/portCoordinateStore.svelte.d.ts +4 -0
  92. package/dist/stores/portCoordinateStore.svelte.js +20 -26
  93. package/dist/stores/workflowStore.svelte.d.ts +31 -2
  94. package/dist/stores/workflowStore.svelte.js +84 -64
  95. package/dist/stories/EdgeDecorator.svelte +4 -4
  96. package/dist/styles/base.css +48 -0
  97. package/dist/svelte-app.d.ts +7 -1
  98. package/dist/svelte-app.js +4 -1
  99. package/dist/types/chat.d.ts +63 -0
  100. package/dist/types/chat.js +9 -0
  101. package/dist/types/events.d.ts +28 -2
  102. package/dist/types/events.js +1 -0
  103. package/dist/types/index.d.ts +8 -0
  104. package/dist/types/settings.d.ts +6 -0
  105. package/dist/types/settings.js +3 -0
  106. package/dist/utils/edgeStyling.d.ts +42 -0
  107. package/dist/utils/edgeStyling.js +176 -0
  108. package/dist/utils/nodeIds.d.ts +31 -0
  109. package/dist/utils/nodeIds.js +42 -0
  110. package/dist/utils/nodeSwap.d.ts +221 -0
  111. package/dist/utils/nodeSwap.js +686 -0
  112. package/package.json +6 -1
  113. package/dist/helpers/nodeLayoutHelper.d.ts +0 -14
  114. package/dist/helpers/nodeLayoutHelper.js +0 -19
@@ -6,13 +6,14 @@
6
6
  *
7
7
  * @module types/events
8
8
  */
9
- import type { Workflow, NodeExecutionInfo } from "./index.js";
9
+ import type { Workflow, NodeExecutionInfo, WorkflowNode } from "./index.js";
10
+ import type { SwapEventContext, SwapResult } from "../utils/nodeSwap.js";
10
11
  /**
11
12
  * Types of workflow changes
12
13
  *
13
14
  * Used to identify what kind of change triggered the onWorkflowChange event.
14
15
  */
15
- export type WorkflowChangeType = "node_add" | "node_remove" | "node_move" | "node_config" | "edge_add" | "edge_remove" | "metadata" | "name" | "description";
16
+ export type WorkflowChangeType = "node_add" | "node_remove" | "node_move" | "node_config" | "node_swap" | "edge_add" | "edge_remove" | "metadata" | "name" | "description";
16
17
  /**
17
18
  * High-level event handlers for enterprise integration
18
19
  *
@@ -113,6 +114,22 @@ export interface FlowDropEventHandlers {
113
114
  * @returns true to suppress default error handling, false/void to show default toast
114
115
  */
115
116
  onApiError?: (error: Error, operation: string) => boolean | void;
117
+ /**
118
+ * Called before a node swap is executed.
119
+ * Return false to cancel the swap.
120
+ *
121
+ * @param context - Data-only swap context (oldNode, newMetadata, preview, overrides)
122
+ * @returns false to cancel, true/void to proceed
123
+ */
124
+ onBeforeSwap?: (context: SwapEventContext) => boolean | void | Promise<boolean | void>;
125
+ /**
126
+ * Called after a node swap is successfully executed.
127
+ *
128
+ * @param result - The swap result with updated nodes/edges
129
+ * @param oldNode - The node that was replaced
130
+ * @param newNodeId - The ID of the newly created node
131
+ */
132
+ onAfterSwap?: (result: SwapResult, oldNode: WorkflowNode, newNodeId: string) => void;
116
133
  /**
117
134
  * Called when an Agent Spec execution starts
118
135
  *
@@ -174,6 +191,15 @@ export interface FlowDropFeatures {
174
191
  * @default true
175
192
  */
176
193
  showToasts?: boolean;
194
+ /**
195
+ * Enable node swap functionality
196
+ *
197
+ * When enabled, users can swap a node for a different type
198
+ * while preserving compatible connections and configuration.
199
+ *
200
+ * @default true
201
+ */
202
+ enableNodeSwap?: boolean;
177
203
  }
178
204
  /**
179
205
  * Default feature values
@@ -15,6 +15,7 @@ export const DEFAULT_FEATURES = {
15
15
  autoSaveDraft: true,
16
16
  autoSaveDraftInterval: 30000,
17
17
  showToasts: true,
18
+ enableNodeSwap: true,
18
19
  };
19
20
  /**
20
21
  * Merge user-provided features with defaults
@@ -535,6 +535,14 @@ export interface NodeExtensions {
535
535
  * Allows overriding the node type's configEdit settings for specific instances
536
536
  */
537
537
  configEdit?: ConfigEditOptions;
538
+ /**
539
+ * Set when this node was created by swapping another node.
540
+ * Stores the immediate predecessor only — not a full chain.
541
+ * Sequential swaps (A→B→C) each store only their direct predecessor.
542
+ */
543
+ swap?: {
544
+ previousNodeId: string;
545
+ };
538
546
  /**
539
547
  * Namespaced extension data from 3rd party integrations
540
548
  * Use your package/organization name as the key (e.g., "myapp", "acme:analyzer")
@@ -57,6 +57,12 @@ export interface UISettings {
57
57
  compactMode: boolean;
58
58
  /** Active theme name — overridden by the theme prop when explicitly provided */
59
59
  theme: "default" | "minimal";
60
+ /** Whether the command console panel is open */
61
+ consoleOpen: boolean;
62
+ /** Height of the command console panel in pixels */
63
+ consoleHeight: number;
64
+ /** Active tab in the bottom panel */
65
+ bottomPanelTab: "console" | "chat";
60
66
  }
61
67
  /**
62
68
  * Application behavior and automation settings
@@ -66,6 +66,9 @@ export const DEFAULT_UI_SETTINGS = {
66
66
  sidebarCollapsed: false,
67
67
  compactMode: false,
68
68
  theme: "default",
69
+ consoleOpen: false,
70
+ consoleHeight: 300,
71
+ bottomPanelTab: "console",
69
72
  };
70
73
  /**
71
74
  * Default behavior settings
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Edge Styling Utility
3
+ *
4
+ * Standalone functions for determining edge categories and applying
5
+ * visual styling to workflow edges based on source port data types.
6
+ * Used by both the visual editor and the command DSL system.
7
+ */
8
+ import type { WorkflowNode as WorkflowNodeType, WorkflowEdge, EdgeCategory } from "../types/index.js";
9
+ /**
10
+ * Check if a port ID matches a dynamic branch in a Gateway node.
11
+ * Gateway nodes store branches in config.branches array.
12
+ */
13
+ export declare function isGatewayBranch(node: WorkflowNodeType, portId: string): boolean;
14
+ /**
15
+ * Get the data type of a port from a node's metadata.
16
+ * Also handles dynamic ports like Gateway branches.
17
+ */
18
+ export declare function getPortDataType(node: WorkflowNodeType, portId: string, portType: "input" | "output"): string | null;
19
+ /**
20
+ * Determine the edge category based on source port data type.
21
+ * Note: This does not check for loopback edges — use getEdgeCategoryWithLoopback() for that.
22
+ */
23
+ export declare function getEdgeCategory(sourcePortDataType: string | null): EdgeCategory;
24
+ /**
25
+ * Determine the full edge category including loopback detection.
26
+ * Loopback edges take precedence over source port data type.
27
+ */
28
+ export declare function getEdgeCategoryWithLoopback(edge: WorkflowEdge, sourcePortDataType: string | null): EdgeCategory;
29
+ /**
30
+ * Apply custom styling to a connection edge based on its source/target nodes.
31
+ *
32
+ * Sets:
33
+ * - edge.data.metadata.edgeType (trigger/tool/loopback/data)
34
+ * - edge.style, edge.class, edge.markerEnd based on category
35
+ * - edge.data.targetNodeType and edge.data.targetCategory
36
+ */
37
+ export declare function applyConnectionStyling(edge: WorkflowEdge, sourceNode: WorkflowNodeType, targetNode: WorkflowNodeType): void;
38
+ /**
39
+ * Update existing edges with custom styling rules.
40
+ * Batch operation that applies styling to all edges using a node map for O(1) lookup.
41
+ */
42
+ export declare function updateEdgeStyles(edges: WorkflowEdge[], nodes: WorkflowNodeType[]): WorkflowEdge[];
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Edge Styling Utility
3
+ *
4
+ * Standalone functions for determining edge categories and applying
5
+ * visual styling to workflow edges based on source port data types.
6
+ * Used by both the visual editor and the command DSL system.
7
+ */
8
+ import { MarkerType } from "@xyflow/svelte";
9
+ import { extractPortId } from "../utils/handleIds.js";
10
+ import { isLoopbackEdge } from "../utils/connections.js";
11
+ import { EDGE_MARKER_SIZES } from "../config/constants.js";
12
+ /**
13
+ * Check if a port ID matches a dynamic branch in a Gateway node.
14
+ * Gateway nodes store branches in config.branches array.
15
+ */
16
+ export function isGatewayBranch(node, portId) {
17
+ const nodeType = node.data?.metadata?.type || node.type;
18
+ if (nodeType !== "gateway") {
19
+ return false;
20
+ }
21
+ const branches = node.data?.config?.branches;
22
+ if (!branches || !Array.isArray(branches)) {
23
+ return false;
24
+ }
25
+ return branches.some((branch) => branch.name === portId);
26
+ }
27
+ /**
28
+ * Get the data type of a port from a node's metadata.
29
+ * Also handles dynamic ports like Gateway branches.
30
+ */
31
+ export function getPortDataType(node, portId, portType) {
32
+ // First, check static ports in metadata
33
+ const ports = portType === "output"
34
+ ? node.data?.metadata?.outputs
35
+ : node.data?.metadata?.inputs;
36
+ if (ports && Array.isArray(ports)) {
37
+ const port = ports.find((p) => p.id === portId);
38
+ if (port?.dataType) {
39
+ return port.dataType;
40
+ }
41
+ }
42
+ // Check dynamic ports from config (dynamicInputs/dynamicOutputs)
43
+ const dynamicKey = portType === "output" ? "dynamicOutputs" : "dynamicInputs";
44
+ const dynamicPorts = node.data?.config?.[dynamicKey];
45
+ if (dynamicPorts && Array.isArray(dynamicPorts)) {
46
+ const dynamicPort = dynamicPorts.find((p) => p.name === portId);
47
+ if (dynamicPort?.dataType) {
48
+ return dynamicPort.dataType;
49
+ }
50
+ }
51
+ // For output ports, also check dynamic Gateway branches
52
+ // Gateway branches are always trigger type (control flow)
53
+ if (portType === "output" && isGatewayBranch(node, portId)) {
54
+ return "trigger";
55
+ }
56
+ return null;
57
+ }
58
+ /**
59
+ * Determine the edge category based on source port data type.
60
+ * Note: This does not check for loopback edges — use getEdgeCategoryWithLoopback() for that.
61
+ */
62
+ export function getEdgeCategory(sourcePortDataType) {
63
+ if (sourcePortDataType === "trigger") {
64
+ return "trigger";
65
+ }
66
+ if (sourcePortDataType === "tool") {
67
+ return "tool";
68
+ }
69
+ return "data";
70
+ }
71
+ /**
72
+ * Determine the full edge category including loopback detection.
73
+ * Loopback edges take precedence over source port data type.
74
+ */
75
+ export function getEdgeCategoryWithLoopback(edge, sourcePortDataType) {
76
+ if (isLoopbackEdge(edge)) {
77
+ return "loopback";
78
+ }
79
+ return getEdgeCategory(sourcePortDataType);
80
+ }
81
+ /**
82
+ * Apply custom styling to a connection edge based on its source/target nodes.
83
+ *
84
+ * Sets:
85
+ * - edge.data.metadata.edgeType (trigger/tool/loopback/data)
86
+ * - edge.style, edge.class, edge.markerEnd based on category
87
+ * - edge.data.targetNodeType and edge.data.targetCategory
88
+ */
89
+ export function applyConnectionStyling(edge, sourceNode, targetNode) {
90
+ // Extract port ID from sourceHandle
91
+ const sourcePortId = extractPortId(edge.sourceHandle);
92
+ // Get the source port's data type
93
+ const sourcePortDataType = sourcePortId
94
+ ? getPortDataType(sourceNode, sourcePortId, "output")
95
+ : null;
96
+ // Determine edge category (loopback takes precedence)
97
+ const edgeCategory = getEdgeCategoryWithLoopback(edge, sourcePortDataType);
98
+ // Apply styling based on edge category
99
+ // Marker colors use CSS custom properties so they respond to theme changes automatically
100
+ switch (edgeCategory) {
101
+ case "loopback":
102
+ edge.style =
103
+ "stroke: var(--fd-edge-loopback); stroke-dasharray: var(--fd-edge-loopback-dasharray); stroke-width: var(--fd-edge-loopback-width); opacity: var(--fd-edge-loopback-opacity);";
104
+ edge.class = "flowdrop--edge--loopback";
105
+ edge.markerEnd = {
106
+ type: MarkerType.ArrowClosed,
107
+ ...EDGE_MARKER_SIZES.loopback,
108
+ color: "var(--fd-edge-loopback)",
109
+ };
110
+ break;
111
+ case "trigger":
112
+ edge.style =
113
+ "stroke: var(--fd-edge-trigger); stroke-width: var(--fd-edge-trigger-width);";
114
+ edge.class = "flowdrop--edge--trigger";
115
+ edge.markerEnd = {
116
+ type: MarkerType.ArrowClosed,
117
+ ...EDGE_MARKER_SIZES.trigger,
118
+ color: "var(--fd-edge-trigger)",
119
+ };
120
+ break;
121
+ case "tool":
122
+ edge.style = "stroke: var(--fd-edge-tool); stroke-dasharray: 5 3;";
123
+ edge.class = "flowdrop--edge--tool";
124
+ edge.markerEnd = {
125
+ type: MarkerType.ArrowClosed,
126
+ ...EDGE_MARKER_SIZES.tool,
127
+ color: "var(--fd-edge-tool)",
128
+ };
129
+ break;
130
+ case "data":
131
+ default:
132
+ edge.style = "stroke: var(--fd-edge-data);";
133
+ edge.class = "flowdrop--edge--data";
134
+ edge.markerEnd = {
135
+ type: MarkerType.ArrowClosed,
136
+ ...EDGE_MARKER_SIZES.data,
137
+ color: "var(--fd-edge-data)",
138
+ };
139
+ break;
140
+ }
141
+ // Store metadata in edge data for API and persistence
142
+ edge.data = {
143
+ ...edge.data,
144
+ metadata: {
145
+ ...(edge.data?.metadata || {}),
146
+ edgeType: edgeCategory,
147
+ sourcePortDataType: sourcePortDataType ?? undefined,
148
+ },
149
+ targetNodeType: targetNode.type,
150
+ targetCategory: targetNode.data.metadata.category,
151
+ };
152
+ }
153
+ /**
154
+ * Update existing edges with custom styling rules.
155
+ * Batch operation that applies styling to all edges using a node map for O(1) lookup.
156
+ */
157
+ export function updateEdgeStyles(edges, nodes) {
158
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
159
+ return edges.map((edge) => {
160
+ const sourceNode = nodeMap.get(edge.source);
161
+ const targetNode = nodeMap.get(edge.target);
162
+ const updatedEdge = { ...edge };
163
+ if (!sourceNode || !targetNode) {
164
+ updatedEdge.data = {
165
+ ...updatedEdge.data,
166
+ metadata: {
167
+ ...(updatedEdge.data?.metadata || {}),
168
+ edgeType: "data",
169
+ },
170
+ };
171
+ return updatedEdge;
172
+ }
173
+ applyConnectionStyling(updatedEdge, sourceNode, targetNode);
174
+ return updatedEdge;
175
+ });
176
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Shared node ID generation and config defaults utilities.
3
+ * Used by both the visual editor and the workflow adapter to ensure
4
+ * consistent behavior across all code paths.
5
+ */
6
+ import type { ConfigSchema } from "../types/index.js";
7
+ /**
8
+ * Minimal node shape required for ID generation.
9
+ * Both WorkflowNode and StandardNode satisfy this interface.
10
+ */
11
+ interface NodeWithMetadata {
12
+ id: string;
13
+ data?: {
14
+ metadata?: {
15
+ id?: string;
16
+ };
17
+ };
18
+ }
19
+ /**
20
+ * Generate a unique node ID based on node type and existing nodes.
21
+ * Format: <node_type>.<number>
22
+ * Example: boolean_gateway.1, calculator.2
23
+ */
24
+ export declare function generateNodeId(nodeTypeId: string, existingNodes: NodeWithMetadata[]): string;
25
+ /**
26
+ * Extract default config values from a node's configSchema.
27
+ * Iterates configSchema.properties and returns an object with each property's
28
+ * default value (if defined).
29
+ */
30
+ export declare function extractConfigDefaults(configSchema?: ConfigSchema): Record<string, unknown>;
31
+ export {};
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Shared node ID generation and config defaults utilities.
3
+ * Used by both the visual editor and the workflow adapter to ensure
4
+ * consistent behavior across all code paths.
5
+ */
6
+ /**
7
+ * Generate a unique node ID based on node type and existing nodes.
8
+ * Format: <node_type>.<number>
9
+ * Example: boolean_gateway.1, calculator.2
10
+ */
11
+ export function generateNodeId(nodeTypeId, existingNodes) {
12
+ // Count how many nodes of this type already exist
13
+ const existingNodeIds = existingNodes
14
+ .filter((node) => node.data?.metadata?.id === nodeTypeId)
15
+ .map((node) => node.id);
16
+ // Extract the numbers from existing IDs with the same prefix
17
+ const existingNumbers = existingNodeIds
18
+ .map((id) => {
19
+ const match = id.match(new RegExp(`^${nodeTypeId}\\.(\\d+)$`));
20
+ return match ? parseInt(match[1], 10) : 0;
21
+ })
22
+ .filter((num) => num > 0);
23
+ // Find the next available number (highest + 1)
24
+ const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1;
25
+ return `${nodeTypeId}.${nextNumber}`;
26
+ }
27
+ /**
28
+ * Extract default config values from a node's configSchema.
29
+ * Iterates configSchema.properties and returns an object with each property's
30
+ * default value (if defined).
31
+ */
32
+ export function extractConfigDefaults(configSchema) {
33
+ const config = {};
34
+ if (!configSchema?.properties)
35
+ return config;
36
+ for (const [key, prop] of Object.entries(configSchema.properties)) {
37
+ if (prop && typeof prop === "object" && "default" in prop) {
38
+ config[key] = prop.default;
39
+ }
40
+ }
41
+ return config;
42
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Node Swap utilities for FlowDrop
3
+ *
4
+ * Provides logic for swapping a workflow node with a different node type
5
+ * while intelligently remapping compatible port connections.
6
+ *
7
+ * @module utils/nodeSwap
8
+ */
9
+ import type { WorkflowNode, WorkflowEdge, NodeMetadata, NodePort, ConfigSchema, ConfigValues } from "../types/index.js";
10
+ import type { WorkflowValidationResult } from "./validation.js";
11
+ import type { PortCompatibilityChecker } from "./connections.js";
12
+ /**
13
+ * Describes how a single port was remapped during a swap.
14
+ */
15
+ export interface PortMapping {
16
+ oldHandleId: string;
17
+ newHandleId: string;
18
+ oldPortId: string;
19
+ newPortId: string;
20
+ direction: "input" | "output";
21
+ }
22
+ /**
23
+ * An edge that could not be remapped and will be dropped.
24
+ */
25
+ export interface DroppedEdge {
26
+ edge: WorkflowEdge;
27
+ reason: string;
28
+ }
29
+ /**
30
+ * Preview of what a node swap will do before it is executed.
31
+ */
32
+ export interface SwapPreview {
33
+ /** Edges that will be preserved with their rewritten versions */
34
+ keptEdges: Array<{
35
+ edge: WorkflowEdge;
36
+ newEdge: WorkflowEdge;
37
+ }>;
38
+ /** Edges that will be removed */
39
+ droppedEdges: DroppedEdge[];
40
+ /** True if any connected edges will be lost */
41
+ hasDataLoss: boolean;
42
+ /** The new node ID that will be generated */
43
+ newNodeId: string;
44
+ /** Config keys carried over from the old node */
45
+ configCarriedOver: string[];
46
+ /** Config keys reset to defaults on the new node */
47
+ configReset: string[];
48
+ }
49
+ /**
50
+ * Result of executing a node swap.
51
+ */
52
+ export interface SwapResult {
53
+ updatedNodes: WorkflowNode[];
54
+ updatedEdges: WorkflowEdge[];
55
+ }
56
+ /** Quality annotation for how a port was matched. */
57
+ export type MatchQuality = "id" | "name" | "type" | "manual" | "unmapped";
58
+ /** Manual override for a single port mapping. */
59
+ export interface PortMappingOverride {
60
+ oldPortId: string;
61
+ newPortId: string | null;
62
+ direction: "input" | "output";
63
+ }
64
+ /** Manual override for a single config mapping. */
65
+ export interface ConfigMappingOverride {
66
+ key: string;
67
+ action: "carry" | "reset" | "set";
68
+ value?: unknown;
69
+ }
70
+ /** Options bag for advanced swap functions. */
71
+ export interface SwapOptions {
72
+ checker?: PortCompatibilityChecker | null;
73
+ portOverrides?: PortMappingOverride[];
74
+ configOverrides?: ConfigMappingOverride[];
75
+ strategies?: SwapStrategy[];
76
+ }
77
+ /** Pluggable strategy passed per-call, not registered globally. */
78
+ export interface SwapStrategy {
79
+ readonly id: string;
80
+ readonly name: string;
81
+ canHandle(ctx: SwapStrategyContext): boolean;
82
+ mapPorts?(ctx: SwapStrategyContext): Record<string, string | null> | undefined;
83
+ mapConfig?(ctx: SwapStrategyContext): Record<string, {
84
+ action: "carry" | "reset" | "set";
85
+ value?: unknown;
86
+ }> | undefined;
87
+ }
88
+ /** Context passed to swap strategies. */
89
+ export interface SwapStrategyContext {
90
+ oldNode: WorkflowNode;
91
+ newMetadata: NodeMetadata;
92
+ edges: WorkflowEdge[];
93
+ allNodes: WorkflowNode[];
94
+ checker: PortCompatibilityChecker | null;
95
+ }
96
+ /** Stable, data-only event context for swap hooks. */
97
+ export interface SwapEventContext {
98
+ oldNode: WorkflowNode;
99
+ newMetadata: NodeMetadata;
100
+ preview: SwapPreview;
101
+ portOverrides: PortMappingOverride[];
102
+ configOverrides: ConfigMappingOverride[];
103
+ }
104
+ /** Error class for swap validation failures. */
105
+ export declare class SwapValidationError extends Error {
106
+ constructor(message: string);
107
+ }
108
+ /** Editable port mapping for the interactive mapping editor. */
109
+ export interface EditablePortMapping {
110
+ oldPort: NodePort;
111
+ edge: WorkflowEdge;
112
+ direction: "input" | "output";
113
+ selectedNewPortId: string | null;
114
+ matchQuality: MatchQuality;
115
+ autoSuggestedPortId: string | null;
116
+ isOverridden: boolean;
117
+ }
118
+ /** Editable config mapping for the interactive mapping editor. */
119
+ export interface EditableConfigMapping {
120
+ key: string;
121
+ title: string;
122
+ oldValue: unknown;
123
+ newDefault: unknown;
124
+ carryOver: boolean;
125
+ autoCarryOver: boolean;
126
+ /** false for nested objects/arrays — shown as read-only, always reset */
127
+ isFlat: boolean;
128
+ }
129
+ /** Full interactive swap state for the mapping editor. */
130
+ export interface InteractiveSwapState {
131
+ oldNode: WorkflowNode;
132
+ newMetadata: NodeMetadata;
133
+ newNodeId: string;
134
+ portMappings: EditablePortMapping[];
135
+ configMappings: EditableConfigMapping[];
136
+ availableNewInputs: NodePort[];
137
+ availableNewOutputs: NodePort[];
138
+ }
139
+ /**
140
+ * Compare two semver-like version strings.
141
+ * Returns positive if a > b, negative if a < b, 0 if equal.
142
+ *
143
+ * Handles pre-release tags: "2.0.0-beta" < "2.0.0"
144
+ */
145
+ export declare function compareSemver(a: string, b: string): number;
146
+ /**
147
+ * Map config values from an old node to a new node's schema.
148
+ *
149
+ * - Keys present in both old config and new schema: carry over the old value
150
+ * - Keys only in the new schema: use the schema default or newDefaults
151
+ * - Keys only in the old config: discarded
152
+ * - Dynamic port keys (dynamicInputs, dynamicOutputs, branches): never carried over
153
+ */
154
+ export declare function mapConfig(oldConfig: ConfigValues, newConfigSchema: ConfigSchema | undefined, newDefaults?: Record<string, unknown>): {
155
+ config: ConfigValues;
156
+ carriedOver: string[];
157
+ reset: string[];
158
+ };
159
+ /**
160
+ * Compute a preview of what will happen when swapping oldNode with newMetadata.
161
+ *
162
+ * This does NOT mutate anything — it returns a preview that can be displayed
163
+ * to the user for confirmation before executing the swap.
164
+ */
165
+ export declare function computeSwapPreview(oldNode: WorkflowNode, newMetadata: NodeMetadata, edges: WorkflowEdge[], allNodes: WorkflowNode[], checker?: PortCompatibilityChecker | null): SwapPreview;
166
+ /**
167
+ * Execute a node swap using a previously computed preview.
168
+ *
169
+ * Returns new nodes and edges arrays ready for `workflowActions.batchUpdate()`.
170
+ */
171
+ export declare function executeSwap(oldNode: WorkflowNode, newMetadata: NodeMetadata, preview: SwapPreview, allNodes: WorkflowNode[], allEdges: WorkflowEdge[]): SwapResult;
172
+ /**
173
+ * Check if a newer version of the same node type is available.
174
+ *
175
+ * Compares the node's embedded metadata.version against the same-ID entry
176
+ * in the available nodes list (API returns only the latest version).
177
+ *
178
+ * @returns The newer NodeMetadata if an upgrade is available, null otherwise
179
+ */
180
+ export declare function getVersionUpgrade(currentMetadata: NodeMetadata, allNodeTypes: NodeMetadata[]): NodeMetadata | null;
181
+ /**
182
+ * Compute a swap preview with full options support (strategies, overrides).
183
+ *
184
+ * Resolution order:
185
+ * 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
187
+ * 3. Apply portOverrides on top (highest priority — user's manual overrides)
188
+ * 4. Same cascade for config
189
+ */
190
+ export declare function computeSwapPreviewWithOptions(oldNode: WorkflowNode, newMetadata: NodeMetadata, edges: WorkflowEdge[], allNodes: WorkflowNode[], options: SwapOptions): SwapPreview;
191
+ /**
192
+ * Compute interactive state for the mapping editor UI.
193
+ *
194
+ * Returns EditablePortMapping and EditableConfigMapping entries
195
+ * with match quality annotations and isFlat flags.
196
+ */
197
+ export declare function computeInteractiveState(oldNode: WorkflowNode, newMetadata: NodeMetadata, edges: WorkflowEdge[], allNodes: WorkflowNode[], options?: SwapOptions): InteractiveSwapState;
198
+ /**
199
+ * Convert user-edited InteractiveSwapState back into a SwapPreview
200
+ * for executeSwap(). Pure function, no side effects.
201
+ */
202
+ export declare function buildSwapPreviewFromState(state: InteractiveSwapState, allEdges: WorkflowEdge[]): SwapPreview;
203
+ /**
204
+ * Headless one-shot swap with full validation.
205
+ *
206
+ * Guardrails:
207
+ * - Validates oldNode.id exists in allNodes
208
+ * - Validates format compatibility if newMetadata.formats is set
209
+ * - Computes preview → executes → validates → returns result
210
+ * - Throws SwapValidationError on invalid input
211
+ */
212
+ export declare function performSwap(oldNode: WorkflowNode, newMetadata: NodeMetadata, allNodes: WorkflowNode[], allEdges: WorkflowEdge[], options?: SwapOptions): SwapResult;
213
+ /**
214
+ * Validate a swap result for structural integrity.
215
+ *
216
+ * Checks:
217
+ * - No dangling edge references (every edge source/target exists in nodes)
218
+ * - No duplicate node IDs
219
+ * - No duplicate edge IDs
220
+ */
221
+ export declare function validateSwapResult(result: SwapResult): WorkflowValidationResult;