@d34dman/flowdrop 0.0.16 → 0.0.18

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 (37) hide show
  1. package/README.md +106 -0
  2. package/dist/api/enhanced-client.d.ts +3 -3
  3. package/dist/api/enhanced-client.js +57 -57
  4. package/dist/components/FlowDropZone.svelte +4 -5
  5. package/dist/components/FlowDropZone.svelte.d.ts +1 -1
  6. package/dist/components/TerminalNode.svelte +565 -0
  7. package/dist/components/TerminalNode.svelte.d.ts +24 -0
  8. package/dist/components/UniversalNode.svelte +94 -34
  9. package/dist/components/WorkflowEditor.svelte +63 -3
  10. package/dist/config/runtimeConfig.d.ts +2 -2
  11. package/dist/config/runtimeConfig.js +7 -7
  12. package/dist/helpers/workflowEditorHelper.d.ts +44 -4
  13. package/dist/helpers/workflowEditorHelper.js +161 -30
  14. package/dist/index.d.ts +16 -13
  15. package/dist/index.js +19 -8
  16. package/dist/registry/builtinNodes.d.ts +77 -0
  17. package/dist/registry/builtinNodes.js +194 -0
  18. package/dist/registry/index.d.ts +7 -0
  19. package/dist/registry/index.js +10 -0
  20. package/dist/registry/nodeComponentRegistry.d.ts +307 -0
  21. package/dist/registry/nodeComponentRegistry.js +315 -0
  22. package/dist/registry/plugin.d.ts +215 -0
  23. package/dist/registry/plugin.js +249 -0
  24. package/dist/services/draftStorage.d.ts +1 -1
  25. package/dist/services/draftStorage.js +5 -5
  26. package/dist/stores/workflowStore.d.ts +2 -2
  27. package/dist/stores/workflowStore.js +16 -16
  28. package/dist/styles/base.css +15 -0
  29. package/dist/svelte-app.d.ts +6 -6
  30. package/dist/svelte-app.js +25 -25
  31. package/dist/types/auth.d.ts +2 -2
  32. package/dist/types/auth.js +7 -7
  33. package/dist/types/events.d.ts +2 -2
  34. package/dist/types/index.d.ts +38 -3
  35. package/dist/utils/nodeTypes.d.ts +76 -21
  36. package/dist/utils/nodeTypes.js +182 -32
  37. package/package.json +2 -2
@@ -1,24 +1,28 @@
1
1
  <!--
2
2
  Universal Node Component
3
- Renders any node type with automatic status overlay injection
4
- This component can replace individual node components in SvelteFlow
3
+ Renders any node type with automatic status overlay injection.
4
+ This component can replace individual node components in SvelteFlow.
5
+
6
+ Uses the node component registry to resolve which component to render,
7
+ enabling custom node types to be registered and used dynamically.
5
8
  -->
6
9
 
7
10
  <script lang="ts">
8
11
  import type { WorkflowNode } from '../types/index.js';
12
+ import { nodeComponentRegistry } from '../registry/nodeComponentRegistry.js';
13
+ import { resolveBuiltinAlias } from '../registry/builtinNodes.js';
14
+ import NodeStatusOverlay from './NodeStatusOverlay.svelte';
15
+ import { shouldShowNodeStatus } from '../utils/nodeWrapper.js';
16
+ import { resolveComponentName } from '../utils/nodeTypes.js';
17
+
18
+ // Fallback components for when registry is not available
19
+ // These are only used as last-resort fallbacks
9
20
  import WorkflowNodeComponent from './WorkflowNode.svelte';
10
21
  import NotesNode from './NotesNode.svelte';
11
22
  import SimpleNode from './SimpleNode.svelte';
12
23
  import SquareNode from './SquareNode.svelte';
13
24
  import ToolNode from './ToolNode.svelte';
14
25
  import GatewayNode from './GatewayNode.svelte';
15
- import NodeStatusOverlay from './NodeStatusOverlay.svelte';
16
- import {
17
- shouldShowNodeStatus,
18
- getOptimalStatusPosition,
19
- getOptimalStatusSize
20
- } from '../utils/nodeWrapper.js';
21
- import { resolveComponentName } from '../utils/nodeTypes.js';
22
26
 
23
27
  let {
24
28
  data,
@@ -31,26 +35,60 @@
31
35
  selected?: boolean;
32
36
  } = $props();
33
37
 
34
- // Determine which node component to render based on node type
35
- // Priority: config.nodeType > metadata.type
36
- // Explicitly track config.nodeType to ensure reactivity
38
+ /**
39
+ * Determine which node component to render based on node type.
40
+ * Priority: config.nodeType > metadata.type
41
+ * Explicitly track config.nodeType to ensure reactivity.
42
+ */
37
43
  let configNodeType = $derived(data.config?.nodeType as string | undefined);
44
+
45
+ /**
46
+ * Resolve the component name from metadata and config.
47
+ * This handles the logic of choosing between config.nodeType and metadata.type.
48
+ */
38
49
  let resolvedComponentName = $derived(
39
50
  data.metadata ? resolveComponentName(data.metadata, configNodeType) : 'workflowNode'
40
51
  );
52
+
53
+ /**
54
+ * Get the node component from the registry.
55
+ * Falls back to built-in components if registry lookup fails.
56
+ */
41
57
  let nodeComponent = $derived(getNodeComponent(resolvedComponentName));
42
58
 
43
- // Get execution info
59
+ /**
60
+ * Get execution info for status overlay
61
+ */
44
62
  let executionInfo = $derived(data.executionInfo);
63
+
64
+ /**
65
+ * Determine if status overlay should be shown.
66
+ * Hide for note nodes as they have their own styling.
67
+ */
45
68
  let shouldShowStatus = $derived(
46
69
  shouldShowNodeStatus(executionInfo) && resolvedComponentName !== 'note'
47
70
  );
48
71
 
49
72
  /**
50
- * Get the appropriate node component based on type
73
+ * Get the node component for the given type.
74
+ * First tries the registry, then falls back to hardcoded components.
75
+ *
76
+ * @param nodeType - The node type identifier
77
+ * @returns The Svelte component to render
51
78
  */
52
79
  function getNodeComponent(nodeType: string) {
53
- switch (nodeType) {
80
+ // Resolve any aliases (e.g., "default" -> "workflowNode")
81
+ const resolvedType = resolveBuiltinAlias(nodeType);
82
+
83
+ // Try registry first
84
+ const registeredComponent = nodeComponentRegistry.getComponent(resolvedType);
85
+ if (registeredComponent) {
86
+ return registeredComponent;
87
+ }
88
+
89
+ // Fallback to hardcoded switch for backwards compatibility
90
+ // This ensures the component works even if registry fails to initialize
91
+ switch (resolvedType) {
54
92
  case 'note':
55
93
  return NotesNode;
56
94
  case 'simple':
@@ -68,40 +106,62 @@
68
106
  }
69
107
 
70
108
  /**
71
- * Get optimal status position for this node type
109
+ * Get optimal status position for this node type.
110
+ * Uses registry if available, otherwise falls back to defaults.
72
111
  */
73
112
  function getStatusPosition(): 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' {
74
- return getOptimalStatusPosition(resolvedComponentName);
113
+ // Try registry first
114
+ const position = nodeComponentRegistry.getStatusPosition(resolvedComponentName);
115
+ if (position) {
116
+ return position;
117
+ }
118
+
119
+ // Fallback based on node type
120
+ switch (resolvedComponentName) {
121
+ case 'tool':
122
+ return 'top-left';
123
+ case 'note':
124
+ return 'bottom-right';
125
+ case 'simple':
126
+ case 'square':
127
+ default:
128
+ return 'top-right';
129
+ }
75
130
  }
76
131
 
77
132
  /**
78
- * Get optimal status size for this node type
133
+ * Get optimal status size for this node type.
134
+ * Uses registry if available, otherwise falls back to defaults.
79
135
  */
80
136
  function getStatusSize(): 'sm' | 'md' | 'lg' {
81
- return getOptimalStatusSize(resolvedComponentName);
137
+ // Try registry first
138
+ const size = nodeComponentRegistry.getStatusSize(resolvedComponentName);
139
+ if (size) {
140
+ return size;
141
+ }
142
+
143
+ // Fallback based on node type
144
+ switch (resolvedComponentName) {
145
+ case 'tool':
146
+ case 'note':
147
+ case 'square':
148
+ return 'sm';
149
+ case 'simple':
150
+ default:
151
+ return 'md';
152
+ }
82
153
  }
83
154
  </script>
84
155
 
85
156
  <div class="universal-node">
86
- <!-- Render the appropriate node component -->
87
- {#if nodeComponent === WorkflowNodeComponent}
88
- <WorkflowNodeComponent {data} {selected} />
89
- {:else if nodeComponent === NotesNode}
90
- <NotesNode {data} {selected} />
91
- {:else if nodeComponent === SimpleNode}
92
- <SimpleNode {data} {selected} />
93
- {:else if nodeComponent === SquareNode}
94
- <SquareNode {data} {selected} />
95
- {:else if nodeComponent === ToolNode}
96
- <ToolNode {data} {selected} />
97
- {:else if nodeComponent === GatewayNode}
98
- <GatewayNode {data} {selected} />
99
- {/if}
157
+ <!-- Render the node component dynamically -->
158
+ <!-- svelte-ignore binding_property_non_reactive -->
159
+ <svelte:component this={nodeComponent} {data} {selected} />
100
160
 
101
161
  <!-- Status overlay - only show if there's meaningful status information -->
102
162
  {#if shouldShowStatus}
103
163
  <NodeStatusOverlay
104
- nodeId={data.nodeId || 'unknown'}
164
+ nodeId={data.nodeId ?? 'unknown'}
105
165
  {executionInfo}
106
166
  position={getStatusPosition()}
107
167
  size={getStatusSize()}
@@ -80,14 +80,21 @@
80
80
 
81
81
  $effect(() => {
82
82
  if (currentWorkflow) {
83
- flowNodes = currentWorkflow.nodes.map((node) => ({
83
+ const nodesWithCallbacks = currentWorkflow.nodes.map((node) => ({
84
84
  ...node,
85
85
  data: {
86
86
  ...node.data,
87
87
  onConfigOpen: props.openConfigSidebar
88
88
  }
89
89
  }));
90
- flowEdges = currentWorkflow.edges;
90
+ flowNodes = nodesWithCallbacks;
91
+
92
+ // Apply edge styling based on source port data type when loading workflow
93
+ const styledEdges = EdgeStylingHelper.updateEdgeStyles(
94
+ currentWorkflow.edges,
95
+ nodesWithCallbacks
96
+ );
97
+ flowEdges = styledEdges;
91
98
 
92
99
  // Only load execution info if we have a pipelineId (pipeline status mode)
93
100
  // and if the workflow or pipeline has changed
@@ -523,7 +530,60 @@
523
530
  pointer-events: all;
524
531
  cursor: crosshair;
525
532
  }
533
+
534
+ /**
535
+ * Edge Styling Based on Source Port Data Type
536
+ * Uses CSS tokens from base.css for consistent theming
537
+ * - Trigger edges: solid dark line (control flow)
538
+ * - Tool edges: dashed amber line (tool connections)
539
+ * - Data edges: normal gray line (data flow)
540
+ */
541
+
542
+ /* Trigger Edge: Solid dark line for control flow */
543
+ :global(.flowdrop--edge--trigger path.svelte-flow__edge-path) {
544
+ stroke: var(--flowdrop-edge-trigger-color);
545
+ stroke-width: var(--flowdrop-edge-trigger-width);
546
+ }
547
+
548
+ :global(.flowdrop--edge--trigger:hover path.svelte-flow__edge-path) {
549
+ stroke: var(--flowdrop-edge-trigger-color-hover);
550
+ stroke-width: var(--flowdrop-edge-trigger-width-hover);
551
+ }
552
+
553
+ :global(.flowdrop--edge--trigger.selected path.svelte-flow__edge-path) {
554
+ stroke: var(--flowdrop-edge-trigger-color-selected);
555
+ stroke-width: var(--flowdrop-edge-trigger-width-hover);
556
+ }
557
+
558
+ /* Tool Edge: Dashed amber line for tool connections */
526
559
  :global(.flowdrop--edge--tool path.svelte-flow__edge-path) {
527
- stroke-dasharray: 5 5;
560
+ stroke: var(--flowdrop-edge-tool-color);
561
+ stroke-dasharray: 5 3;
562
+ }
563
+
564
+ :global(.flowdrop--edge--tool:hover path.svelte-flow__edge-path) {
565
+ stroke: var(--flowdrop-edge-tool-color-hover);
566
+ stroke-width: 2;
567
+ }
568
+
569
+ :global(.flowdrop--edge--tool.selected path.svelte-flow__edge-path) {
570
+ stroke: var(--flowdrop-edge-tool-color-selected);
571
+ stroke-dasharray: 5 3;
572
+ stroke-width: 2;
573
+ }
574
+
575
+ /* Data Edge: Normal gray line for data flow (default) */
576
+ :global(.flowdrop--edge--data path.svelte-flow__edge-path) {
577
+ stroke: var(--flowdrop-edge-data-color);
578
+ }
579
+
580
+ :global(.flowdrop--edge--data:hover path.svelte-flow__edge-path) {
581
+ stroke: var(--flowdrop-edge-data-color-hover);
582
+ stroke-width: 2;
583
+ }
584
+
585
+ :global(.flowdrop--edge--data.selected path.svelte-flow__edge-path) {
586
+ stroke: var(--flowdrop-edge-data-color-selected);
587
+ stroke-width: 2;
528
588
  }
529
589
  </style>
@@ -9,11 +9,11 @@ export interface RuntimeConfig {
9
9
  /** Base URL for the FlowDrop API */
10
10
  apiBaseUrl: string;
11
11
  /** Theme preference */
12
- theme: "light" | "dark" | "auto";
12
+ theme: 'light' | 'dark' | 'auto';
13
13
  /** Request timeout in milliseconds */
14
14
  timeout: number;
15
15
  /** Authentication type */
16
- authType: "none" | "bearer" | "api_key" | "custom";
16
+ authType: 'none' | 'bearer' | 'api_key' | 'custom';
17
17
  /** Authentication token */
18
18
  authToken?: string;
19
19
  /** Application version */
@@ -24,7 +24,7 @@ export async function fetchRuntimeConfig(force = false) {
24
24
  return cachedConfig;
25
25
  }
26
26
  try {
27
- const response = await fetch("/api/config");
27
+ const response = await fetch('/api/config');
28
28
  if (!response.ok) {
29
29
  throw new Error(`Failed to fetch runtime config: ${response.statusText}`);
30
30
  }
@@ -35,15 +35,15 @@ export async function fetchRuntimeConfig(force = false) {
35
35
  return config;
36
36
  }
37
37
  catch (error) {
38
- console.error("Failed to fetch runtime configuration:", error);
38
+ console.error('Failed to fetch runtime configuration:', error);
39
39
  // Return default configuration if fetch fails
40
40
  const defaultConfig = {
41
- apiBaseUrl: "/api/flowdrop",
42
- theme: "auto",
41
+ apiBaseUrl: '/api/flowdrop',
42
+ theme: 'auto',
43
43
  timeout: 30000,
44
- authType: "none",
45
- version: "1.0.0",
46
- environment: "production",
44
+ authType: 'none',
45
+ version: '1.0.0',
46
+ environment: 'production'
47
47
  };
48
48
  // Cache the default config to avoid repeated failed requests
49
49
  if (!cachedConfig) {
@@ -11,13 +11,53 @@ import type { EndpointConfig } from '../config/endpoints.js';
11
11
  */
12
12
  export declare function generateNodeId(nodeTypeId: string, existingNodes: WorkflowNodeType[]): string;
13
13
  /**
14
- * Edge styling configuration
14
+ * Edge category type for styling purposes
15
+ * - trigger: For control flow connections (dataType: "trigger")
16
+ * - tool: Dashed amber line for tool connections (dataType: "tool")
17
+ * - data: Normal gray line for all other data connections
18
+ */
19
+ export type EdgeCategory = 'trigger' | 'tool' | 'data';
20
+ /**
21
+ * Edge styling configuration based on source port data type
15
22
  */
16
23
  export declare class EdgeStylingHelper {
17
24
  /**
18
- * Apply custom styling to connection edges based on rules:
19
- * - Dashed lines for connections to tool nodes
20
- * - Arrow markers pointing towards input ports
25
+ * Extract the port ID from a handle ID
26
+ * Supports two formats:
27
+ * 1. New format: "${nodeId}-output-${portId}" or "${nodeId}-input-${portId}"
28
+ * 2. Legacy format: just the portId (e.g., "text", "trigger")
29
+ * @param handleId - The handle ID string (e.g., "sample-node.1-output-trigger" or "trigger")
30
+ * @returns The port ID (e.g., "trigger") or the handleId itself for legacy format
31
+ */
32
+ static extractPortIdFromHandle(handleId: string | undefined): string | null;
33
+ /**
34
+ * Check if a port ID matches a dynamic branch in a Gateway node
35
+ * Gateway nodes store branches in config.branches array
36
+ * @param node - The workflow node to check
37
+ * @param portId - The port ID to look up
38
+ * @returns true if the portId matches a gateway branch
39
+ */
40
+ static isGatewayBranch(node: WorkflowNodeType, portId: string): boolean;
41
+ /**
42
+ * Get the data type of a port from a node's metadata
43
+ * Also handles dynamic ports like Gateway branches
44
+ * @param node - The workflow node containing the port
45
+ * @param portId - The port ID to look up
46
+ * @param portType - Whether to look in "inputs" or "outputs"
47
+ * @returns The port's dataType or null if not found
48
+ */
49
+ static getPortDataType(node: WorkflowNodeType, portId: string, portType: 'input' | 'output'): string | null;
50
+ /**
51
+ * Determine the edge category based on source port data type
52
+ * @param sourcePortDataType - The data type of the source output port
53
+ * @returns The edge category for styling
54
+ */
55
+ static getEdgeCategory(sourcePortDataType: string | null): EdgeCategory;
56
+ /**
57
+ * Apply custom styling to connection edges based on source port data type:
58
+ * - Trigger ports: Solid black line with arrow
59
+ * - Tool ports: Dashed amber line with arrow
60
+ * - Data ports: Normal gray line with arrow
21
61
  */
22
62
  static applyConnectionStyling(edge: WorkflowEdge, sourceNode: WorkflowNodeType, targetNode: WorkflowNodeType): void;
23
63
  /**
@@ -30,44 +30,166 @@ export function generateNodeId(nodeTypeId, existingNodes) {
30
30
  return `${nodeTypeId}.${nextNumber}`;
31
31
  }
32
32
  /**
33
- * Edge styling configuration
33
+ * Edge styling configuration based on source port data type
34
34
  */
35
35
  export class EdgeStylingHelper {
36
36
  /**
37
- * Apply custom styling to connection edges based on rules:
38
- * - Dashed lines for connections to tool nodes
39
- * - Arrow markers pointing towards input ports
37
+ * Extract the port ID from a handle ID
38
+ * Supports two formats:
39
+ * 1. New format: "${nodeId}-output-${portId}" or "${nodeId}-input-${portId}"
40
+ * 2. Legacy format: just the portId (e.g., "text", "trigger")
41
+ * @param handleId - The handle ID string (e.g., "sample-node.1-output-trigger" or "trigger")
42
+ * @returns The port ID (e.g., "trigger") or the handleId itself for legacy format
40
43
  */
41
- static applyConnectionStyling(edge, sourceNode, targetNode) {
42
- // Rule 1: Dashed lines for tool nodes
43
- // A node is a tool node when it uses the ToolNode component,
44
- // which happens when sourceNode.type === "tool"
45
- const isToolNode = sourceNode.type === 'tool';
46
- // Use inline styles for dashed lines (more reliable than CSS classes)
47
- if (isToolNode) {
48
- edge.style = 'stroke-dasharray: 0 4 0; stroke: amber !important;';
49
- edge.class = 'flowdrop--edge--tool';
44
+ static extractPortIdFromHandle(handleId) {
45
+ if (!handleId) {
46
+ return null;
47
+ }
48
+ // Try new format: "${nodeId}-output-${portId}" or "${nodeId}-input-${portId}"
49
+ // We need to find the last occurrence of "-output-" or "-input-" and get what follows
50
+ const outputMatch = handleId.lastIndexOf('-output-');
51
+ const inputMatch = handleId.lastIndexOf('-input-');
52
+ if (outputMatch !== -1) {
53
+ return handleId.substring(outputMatch + '-output-'.length);
54
+ }
55
+ if (inputMatch !== -1) {
56
+ return handleId.substring(inputMatch + '-input-'.length);
57
+ }
58
+ // Legacy format: the handleId IS the port ID
59
+ return handleId;
60
+ }
61
+ /**
62
+ * Check if a port ID matches a dynamic branch in a Gateway node
63
+ * Gateway nodes store branches in config.branches array
64
+ * @param node - The workflow node to check
65
+ * @param portId - The port ID to look up
66
+ * @returns true if the portId matches a gateway branch
67
+ */
68
+ static isGatewayBranch(node, portId) {
69
+ // Check if this is a gateway node with dynamic branches
70
+ const nodeType = node.data?.metadata?.type || node.type;
71
+ if (nodeType !== 'gateway') {
72
+ return false;
73
+ }
74
+ // Check if the portId matches a branch name in config.branches
75
+ const branches = node.data?.config?.branches;
76
+ if (!branches || !Array.isArray(branches)) {
77
+ return false;
78
+ }
79
+ return branches.some((branch) => branch.name === portId);
80
+ }
81
+ /**
82
+ * Get the data type of a port from a node's metadata
83
+ * Also handles dynamic ports like Gateway branches
84
+ * @param node - The workflow node containing the port
85
+ * @param portId - The port ID to look up
86
+ * @param portType - Whether to look in "inputs" or "outputs"
87
+ * @returns The port's dataType or null if not found
88
+ */
89
+ static getPortDataType(node, portId, portType) {
90
+ // First, check static ports in metadata
91
+ const ports = portType === 'output' ? node.data?.metadata?.outputs : node.data?.metadata?.inputs;
92
+ if (ports && Array.isArray(ports)) {
93
+ const port = ports.find((p) => p.id === portId);
94
+ if (port?.dataType) {
95
+ return port.dataType;
96
+ }
97
+ }
98
+ // For output ports, also check dynamic Gateway branches
99
+ // Gateway branches are always trigger type (control flow)
100
+ if (portType === 'output' && this.isGatewayBranch(node, portId)) {
101
+ return 'trigger';
102
+ }
103
+ return null;
104
+ }
105
+ /**
106
+ * Determine the edge category based on source port data type
107
+ * @param sourcePortDataType - The data type of the source output port
108
+ * @returns The edge category for styling
109
+ */
110
+ static getEdgeCategory(sourcePortDataType) {
111
+ if (sourcePortDataType === 'trigger') {
112
+ return 'trigger';
113
+ }
114
+ if (sourcePortDataType === 'tool') {
115
+ return 'tool';
50
116
  }
51
- else {
52
- edge.style = 'stroke: grey;';
117
+ // All other data types (string, number, boolean, array, etc.) are "data" edges
118
+ return 'data';
119
+ }
120
+ /**
121
+ * Apply custom styling to connection edges based on source port data type:
122
+ * - Trigger ports: Solid black line with arrow
123
+ * - Tool ports: Dashed amber line with arrow
124
+ * - Data ports: Normal gray line with arrow
125
+ */
126
+ static applyConnectionStyling(edge, sourceNode, targetNode) {
127
+ // Extract port ID from sourceHandle
128
+ const sourcePortId = this.extractPortIdFromHandle(edge.sourceHandle);
129
+ // Get the source port's data type
130
+ const sourcePortDataType = sourcePortId
131
+ ? this.getPortDataType(sourceNode, sourcePortId, 'output')
132
+ : null;
133
+ // Determine edge category based on source port data type
134
+ const edgeCategory = this.getEdgeCategory(sourcePortDataType);
135
+ // Edge color constants (matching CSS tokens in base.css)
136
+ const EDGE_COLORS = {
137
+ trigger: '#111827', // --color-ref-gray-900
138
+ tool: '#f59e0b', // --color-ref-amber-500
139
+ data: '#9ca3af' // --color-ref-gray-400
140
+ };
141
+ // Apply styling based on edge category
142
+ // CSS classes handle styling via tokens; inline styles are fallback
143
+ switch (edgeCategory) {
144
+ case 'trigger':
145
+ // Trigger edges: solid dark line for control flow
146
+ edge.style =
147
+ 'stroke: var(--flowdrop-edge-trigger-color); stroke-width: var(--flowdrop-edge-trigger-width);';
148
+ edge.class = 'flowdrop--edge--trigger';
149
+ edge.markerEnd = {
150
+ type: MarkerType.ArrowClosed,
151
+ width: 16,
152
+ height: 16,
153
+ color: EDGE_COLORS.trigger
154
+ };
155
+ break;
156
+ case 'tool':
157
+ // Tool edges: dashed amber line
158
+ edge.style = 'stroke: var(--flowdrop-edge-tool-color); stroke-dasharray: 5 3;';
159
+ edge.class = 'flowdrop--edge--tool';
160
+ edge.markerEnd = {
161
+ type: MarkerType.ArrowClosed,
162
+ width: 16,
163
+ height: 16,
164
+ color: EDGE_COLORS.tool
165
+ };
166
+ break;
167
+ case 'data':
168
+ default:
169
+ // Data edges: normal gray line
170
+ edge.style = 'stroke: var(--flowdrop-edge-data-color);';
171
+ edge.class = 'flowdrop--edge--data';
172
+ edge.markerEnd = {
173
+ type: MarkerType.ArrowClosed,
174
+ width: 16,
175
+ height: 16,
176
+ color: EDGE_COLORS.data
177
+ };
178
+ break;
53
179
  }
54
- // Store metadata in edge data for debugging
180
+ // Store metadata in edge data for API and persistence
55
181
  edge.data = {
56
182
  ...edge.data,
57
- isToolConnection: isToolNode,
183
+ metadata: {
184
+ ...(edge.data?.metadata || {}),
185
+ edgeType: edgeCategory,
186
+ sourcePortDataType: sourcePortDataType || undefined
187
+ },
188
+ // Keep legacy fields for backward compatibility
189
+ isToolConnection: edgeCategory === 'tool',
58
190
  targetNodeType: targetNode.type,
59
191
  targetCategory: targetNode.data.metadata.category
60
192
  };
61
- // Rule 2: Always add arrow pointing towards input port
62
- // This replaces the default arrows we removed
63
- if (!isToolNode) {
64
- edge.markerEnd = {
65
- type: MarkerType.ArrowClosed,
66
- width: 16,
67
- height: 16,
68
- color: 'grey'
69
- };
70
- }
71
193
  }
72
194
  /**
73
195
  * Update existing edges with custom styling rules
@@ -77,11 +199,20 @@ export class EdgeStylingHelper {
77
199
  // Find source and target nodes
78
200
  const sourceNode = nodes.find((node) => node.id === edge.source);
79
201
  const targetNode = nodes.find((node) => node.id === edge.target);
202
+ // Create a copy of the edge
203
+ const updatedEdge = { ...edge };
80
204
  if (!sourceNode || !targetNode) {
81
- return edge;
205
+ // Set default edgeType even when nodes are not found
206
+ updatedEdge.data = {
207
+ ...updatedEdge.data,
208
+ metadata: {
209
+ ...(updatedEdge.data?.metadata || {}),
210
+ edgeType: 'data'
211
+ }
212
+ };
213
+ return updatedEdge;
82
214
  }
83
- // Create a copy of the edge and apply styling
84
- const updatedEdge = { ...edge };
215
+ // Apply full styling when nodes are available
85
216
  this.applyConnectionStyling(updatedEdge, sourceNode, targetNode);
86
217
  return updatedEdge;
87
218
  });
package/dist/index.d.ts CHANGED
@@ -2,13 +2,14 @@
2
2
  * FlowDrop - Visual Workflow Editor Library
3
3
  * A Svelte 5 component library built on @xyflow/svelte for creating node-based workflow editors
4
4
  */
5
- import "./styles/base.css";
6
- export type { NodeCategory, NodeDataType, NodePort, NodeMetadata, NodeConfig, WorkflowNode, WorkflowEdge, Workflow, ApiResponse, NodesResponse, WorkflowResponse, WorkflowsResponse, ExecutionStatus, ExecutionResult, FlowDropConfig, WorkflowEvents } from "./types/index.js";
7
- export type { WorkflowEditorConfig, EditorFeatures, UIConfig, APIConfig, ExecutionConfig, StorageConfig, NodeType, WorkflowData, ExecutionResult as EditorExecutionResult, EditorState } from "./types/config.js";
8
- export type { AuthProvider, StaticAuthConfig, CallbackAuthConfig } from "./types/auth.js";
9
- export { StaticAuthProvider, CallbackAuthProvider, NoAuthProvider, createAuthProviderFromLegacyConfig } from "./types/auth.js";
10
- export type { WorkflowChangeType, FlowDropEventHandlers, FlowDropFeatures } from "./types/events.js";
11
- export { DEFAULT_FEATURES, mergeFeatures } from "./types/events.js";
5
+ import './styles/base.css';
6
+ import './registry/builtinNodes.js';
7
+ export type { NodeCategory, NodeDataType, NodePort, NodeMetadata, NodeConfig, WorkflowNode, WorkflowEdge, Workflow, ApiResponse, NodesResponse, WorkflowResponse, WorkflowsResponse, ExecutionStatus, ExecutionResult, FlowDropConfig, WorkflowEvents, BuiltinNodeType } from './types/index.js';
8
+ export type { WorkflowEditorConfig, EditorFeatures, UIConfig, APIConfig, ExecutionConfig, StorageConfig, NodeType, WorkflowData, ExecutionResult as EditorExecutionResult, EditorState } from './types/config.js';
9
+ export type { AuthProvider, StaticAuthConfig, CallbackAuthConfig } from './types/auth.js';
10
+ export { StaticAuthProvider, CallbackAuthProvider, NoAuthProvider, createAuthProviderFromLegacyConfig } from './types/auth.js';
11
+ export type { WorkflowChangeType, FlowDropEventHandlers, FlowDropFeatures } from './types/events.js';
12
+ export { DEFAULT_FEATURES, mergeFeatures } from './types/events.js';
12
13
  export { FlowDropApiClient } from './api/client.js';
13
14
  export { EnhancedFlowDropApiClient } from './api/enhanced-client.js';
14
15
  export { default as WorkflowEditor } from './components/WorkflowEditor.svelte';
@@ -43,16 +44,18 @@ export * from './utils/nodeTypes.js';
43
44
  export { getStatusColor, getStatusIcon, getStatusLabel, getStatusBackgroundColor, getStatusTextColor, createDefaultExecutionInfo, updateExecutionStart, updateExecutionComplete, updateExecutionFailed, resetExecutionInfo, formatExecutionDuration, formatLastExecuted } from './utils/nodeStatus.js';
44
45
  export { createNodeWrapperConfig, shouldShowNodeStatus, getOptimalStatusPosition, getOptimalStatusSize, DEFAULT_NODE_STATUS_CONFIG } from './utils/nodeWrapper.js';
45
46
  export type { NodeStatusConfig } from './utils/nodeWrapper.js';
47
+ export { nodeComponentRegistry, createNamespacedType, parseNamespacedType, BUILTIN_NODE_COMPONENTS, BUILTIN_NODE_TYPES, FLOWDROP_SOURCE, registerBuiltinNodes, areBuiltinsRegistered, isBuiltinType, getBuiltinTypes, resolveBuiltinAlias, registerFlowDropPlugin, unregisterFlowDropPlugin, registerCustomNode, createPlugin, isValidNamespace, getRegisteredPlugins, getPluginNodeCount } from './registry/index.js';
48
+ export type { NodeComponentProps, NodeComponentRegistration, NodeComponentCategory, StatusPosition, StatusSize, NodeRegistrationFilter, FlowDropPluginConfig, PluginNodeDefinition, PluginRegistrationResult } from './registry/index.js';
46
49
  export * from './services/api.js';
47
50
  export { showSuccess, showError, showWarning, showInfo, showLoading, dismissToast, dismissAllToasts, showPromise, showConfirmation, apiToasts, workflowToasts, pipelineToasts } from './services/toastService.js';
48
51
  export type { ToastType, ToastOptions } from './services/toastService.js';
49
52
  export { NodeExecutionService, nodeExecutionService } from './services/nodeExecutionService.js';
50
53
  export { saveWorkflow, updateWorkflow, getWorkflow, getWorkflows, deleteWorkflow, getWorkflowCount, initializeSampleWorkflows } from './services/workflowStorage.js';
51
54
  export { globalSaveWorkflow, globalExportWorkflow, initializeGlobalSave } from './services/globalSave.js';
52
- export { fetchPortConfig, validatePortConfig } from "./services/portConfigApi.js";
53
- export { getDraftStorageKey, saveDraft, loadDraft, deleteDraft, hasDraft, getDraftMetadata, DraftAutoSaveManager } from "./services/draftStorage.js";
55
+ export { fetchPortConfig, validatePortConfig } from './services/portConfigApi.js';
56
+ export { getDraftStorageKey, saveDraft, loadDraft, deleteDraft, hasDraft, getDraftMetadata, DraftAutoSaveManager } from './services/draftStorage.js';
54
57
  export { EdgeStylingHelper, NodeOperationsHelper, WorkflowOperationsHelper, ConfigurationHelper } from './helpers/workflowEditorHelper.js';
55
- export { workflowStore, workflowActions, workflowId, workflowName, workflowNodes, workflowEdges, workflowMetadata, workflowChanged, workflowValidation, workflowMetadataChanged, isDirtyStore, isDirty, markAsSaved, getWorkflow as getWorkflowFromStore, setOnDirtyStateChange, setOnWorkflowChange } from "./stores/workflowStore.js";
58
+ export { workflowStore, workflowActions, workflowId, workflowName, workflowNodes, workflowEdges, workflowMetadata, workflowChanged, workflowValidation, workflowMetadataChanged, isDirtyStore, isDirty, markAsSaved, getWorkflow as getWorkflowFromStore, setOnDirtyStateChange, setOnWorkflowChange } from './stores/workflowStore.js';
56
59
  export * from './config/endpoints.js';
57
60
  export { defaultApiConfig, getEndpointUrl } from './config/apiConfig.js';
58
61
  export type { ApiConfig } from './config/apiConfig.js';
@@ -60,6 +63,6 @@ export { DEFAULT_PORT_CONFIG } from './config/defaultPortConfig.js';
60
63
  export * from './config/runtimeConfig.js';
61
64
  export * from './adapters/WorkflowAdapter.js';
62
65
  export * from './clients/ApiClient.js';
63
- export { mountWorkflowEditor, unmountWorkflowEditor, mountFlowDropApp, unmountFlowDropApp } from "./svelte-app.js";
64
- export type { FlowDropMountOptions, MountedFlowDropApp, NavbarAction } from "./svelte-app.js";
65
- export { ApiError } from "./api/enhanced-client.js";
66
+ export { mountWorkflowEditor, unmountWorkflowEditor, mountFlowDropApp, unmountFlowDropApp } from './svelte-app.js';
67
+ export type { FlowDropMountOptions, MountedFlowDropApp, NavbarAction } from './svelte-app.js';
68
+ export { ApiError } from './api/enhanced-client.js';