@d34dman/flowdrop 0.0.46 → 0.0.48

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 (34) hide show
  1. package/dist/components/App.svelte +10 -0
  2. package/dist/components/App.svelte.d.ts +2 -0
  3. package/dist/components/ConfigForm.svelte +72 -6
  4. package/dist/components/ConfigForm.svelte.d.ts +13 -1
  5. package/dist/components/SchemaForm.svelte +4 -2
  6. package/dist/components/SettingsPanel.svelte +5 -2
  7. package/dist/components/WorkflowEditor.svelte +26 -0
  8. package/dist/components/WorkflowEditor.svelte.d.ts +1 -0
  9. package/dist/components/form/FormAutocomplete.svelte +7 -4
  10. package/dist/components/form/FormField.svelte +42 -13
  11. package/dist/components/form/FormField.svelte.d.ts +11 -0
  12. package/dist/components/form/FormFieldLight.svelte +16 -13
  13. package/dist/components/form/FormTemplateEditor.svelte +343 -21
  14. package/dist/components/form/FormTemplateEditor.svelte.d.ts +22 -2
  15. package/dist/components/form/index.d.ts +1 -0
  16. package/dist/components/form/index.js +2 -0
  17. package/dist/components/form/templateAutocomplete.d.ts +29 -0
  18. package/dist/components/form/templateAutocomplete.js +253 -0
  19. package/dist/components/form/types.d.ts +29 -3
  20. package/dist/components/nodes/TerminalNode.svelte +27 -15
  21. package/dist/registry/nodeComponentRegistry.d.ts +9 -9
  22. package/dist/registry/nodeComponentRegistry.js +10 -10
  23. package/dist/services/apiVariableService.d.ts +116 -0
  24. package/dist/services/apiVariableService.js +338 -0
  25. package/dist/services/variableService.d.ts +141 -0
  26. package/dist/services/variableService.js +462 -0
  27. package/dist/svelte-app.d.ts +2 -0
  28. package/dist/svelte-app.js +2 -1
  29. package/dist/types/index.d.ts +257 -0
  30. package/dist/utils/handlePositioning.d.ts +31 -0
  31. package/dist/utils/handlePositioning.js +35 -0
  32. package/dist/utils/nodeTypes.d.ts +15 -10
  33. package/dist/utils/nodeTypes.js +24 -22
  34. package/package.json +7 -3
@@ -69,6 +69,11 @@ export interface NodePort {
69
69
  required?: boolean;
70
70
  description?: string;
71
71
  defaultValue?: unknown;
72
+ /**
73
+ * Optional JSON Schema describing the structure of data on this port.
74
+ * Used for template variable autocomplete to drill into nested properties.
75
+ */
76
+ schema?: OutputSchema | InputSchema;
72
77
  }
73
78
  /**
74
79
  * Dynamic port configuration for user-defined inputs/outputs
@@ -612,6 +617,258 @@ export interface OutputProperty extends BaseProperty {
612
617
  export interface OutputSchema extends BaseSchema {
613
618
  properties: Record<string, OutputProperty>;
614
619
  }
620
+ /**
621
+ * Primitive types for template variables
622
+ */
623
+ export type TemplateVariableType = 'string' | 'number' | 'boolean' | 'array' | 'object' | 'integer' | 'mixed' | 'float';
624
+ /**
625
+ * Represents a variable available for template interpolation.
626
+ * Used by the template editor for autocomplete suggestions.
627
+ *
628
+ * Supports hierarchical drilling:
629
+ * - Objects have `properties` for dot notation (e.g., `user.name`)
630
+ * - Arrays have `items` for index access (e.g., `items[0].name`)
631
+ *
632
+ * @example
633
+ * ```typescript
634
+ * const userVariable: TemplateVariable = {
635
+ * name: "user",
636
+ * label: "User Data",
637
+ * type: "object",
638
+ * properties: {
639
+ * name: { name: "name", type: "string", label: "User Name" },
640
+ * email: { name: "email", type: "string", label: "Email Address" },
641
+ * address: {
642
+ * name: "address",
643
+ * type: "object",
644
+ * label: "Address",
645
+ * properties: {
646
+ * city: { name: "city", type: "string", label: "City" },
647
+ * country: { name: "country", type: "string", label: "Country" }
648
+ * }
649
+ * }
650
+ * }
651
+ * };
652
+ * ```
653
+ */
654
+ export interface TemplateVariable {
655
+ /** Variable name (used in template as {{ name }}) */
656
+ name: string;
657
+ /** Display label for the variable in autocomplete dropdown */
658
+ label?: string;
659
+ /** Description shown in autocomplete tooltip */
660
+ description?: string;
661
+ /** Data type of the variable */
662
+ type: TemplateVariableType;
663
+ /** For objects: child properties accessible via dot notation */
664
+ properties?: Record<string, TemplateVariable>;
665
+ /** For arrays: schema of array items accessible via index notation */
666
+ items?: TemplateVariable;
667
+ /** Source port ID this variable comes from */
668
+ sourcePort?: string;
669
+ /** Source node ID */
670
+ sourceNode?: string;
671
+ }
672
+ /**
673
+ * Schema passed to template editor for autocomplete functionality.
674
+ * Contains all available variables derived from connected upstream nodes.
675
+ *
676
+ * @example
677
+ * ```typescript
678
+ * const variableSchema: VariableSchema = {
679
+ * variables: {
680
+ * user: { name: "user", type: "object", properties: { ... } },
681
+ * items: { name: "items", type: "array", items: { ... } },
682
+ * config: { name: "config", type: "object", properties: { ... } }
683
+ * }
684
+ * };
685
+ * ```
686
+ */
687
+ export interface VariableSchema {
688
+ /** Map of available variables keyed by variable name */
689
+ variables: Record<string, TemplateVariable>;
690
+ }
691
+ /**
692
+ * Configuration for template variable autocomplete.
693
+ * Used in template fields to control which variables are available
694
+ * and how they are derived.
695
+ *
696
+ * Supports three modes:
697
+ * 1. **Schema-only** (existing): Variables from static schema and/or upstream ports
698
+ * 2. **API-only** (new): Variables fetched from backend endpoint
699
+ * 3. **Hybrid** (new): Merge API variables with static schema/ports
700
+ *
701
+ * @example
702
+ * ```json
703
+ * {
704
+ * "type": "string",
705
+ * "format": "template",
706
+ * "variables": {
707
+ * "ports": ["data", "context"],
708
+ * "includePortName": true
709
+ * }
710
+ * }
711
+ * ```
712
+ *
713
+ * @example API Mode
714
+ * ```json
715
+ * {
716
+ * "type": "string",
717
+ * "format": "template",
718
+ * "variables": {
719
+ * "api": {
720
+ * "endpoint": {
721
+ * "url": "/api/variables/{workflowId}/{nodeId}"
722
+ * }
723
+ * }
724
+ * }
725
+ * }
726
+ * ```
727
+ */
728
+ export interface TemplateVariablesConfig {
729
+ /**
730
+ * Specifies which input port IDs should provide variables for autocomplete.
731
+ * Only connections to these ports will provide variables.
732
+ *
733
+ * - If not specified, all input ports with connections are used.
734
+ * - If specified as an empty array, no variables will be derived from ports.
735
+ */
736
+ ports?: string[];
737
+ /**
738
+ * Pre-defined variable schema to use instead of (or in addition to) deriving from ports.
739
+ * Useful for providing static variables or overriding derived ones.
740
+ */
741
+ schema?: VariableSchema;
742
+ /**
743
+ * Whether to include the port name as a prefix for variables.
744
+ * When true, variables are named like `data.user` instead of just `user`.
745
+ * Useful when multiple ports might have overlapping variable names.
746
+ * @default false
747
+ */
748
+ includePortName?: boolean;
749
+ /**
750
+ * Whether to show available variables as clickable hints below the editor.
751
+ * @default true
752
+ */
753
+ showHints?: boolean;
754
+ /**
755
+ * API mode configuration for fetching variables from backend endpoint.
756
+ * When configured, variables will be fetched from the specified endpoint
757
+ * and can be merged with static schema and/or port-derived variables.
758
+ */
759
+ api?: ApiVariablesConfig;
760
+ }
761
+ /**
762
+ * Configuration for API-based variable fetching.
763
+ * Enables dynamic variable suggestions from backend endpoints.
764
+ *
765
+ * @example
766
+ * ```typescript
767
+ * const apiConfig: ApiVariablesConfig = {
768
+ * endpoint: {
769
+ * url: "/api/variables/{workflowId}/{nodeId}",
770
+ * method: "GET"
771
+ * },
772
+ * cacheTtl: 300000,
773
+ * mergeWithSchema: true,
774
+ * fallbackOnError: true
775
+ * };
776
+ * ```
777
+ */
778
+ export interface ApiVariablesConfig {
779
+ /**
780
+ * Endpoint configuration for fetching variable schema
781
+ */
782
+ endpoint: ApiVariablesEndpoint;
783
+ /**
784
+ * Cache TTL in milliseconds.
785
+ * Variables are cached to prevent excessive API calls during editing.
786
+ * @default 300000 (5 minutes)
787
+ */
788
+ cacheTtl?: number;
789
+ /**
790
+ * Whether to merge API variables with static schema.
791
+ * When true, variables from both API and schema are combined.
792
+ * @default true
793
+ */
794
+ mergeWithSchema?: boolean;
795
+ /**
796
+ * Whether to merge API variables with port-derived variables.
797
+ * When true, variables from both API and ports are combined.
798
+ * @default false
799
+ */
800
+ mergeWithPorts?: boolean;
801
+ /**
802
+ * Whether to fallback to schema/ports on API error.
803
+ * When true, gracefully degrades to static variables if API fails.
804
+ * When false, shows error message to user.
805
+ * @default true
806
+ */
807
+ fallbackOnError?: boolean;
808
+ }
809
+ /**
810
+ * Endpoint configuration for fetching variable schemas from backend API.
811
+ * Supports template variables in URL (e.g., {workflowId}, {nodeId})
812
+ * which are resolved at runtime from node context.
813
+ *
814
+ * @example GET Request
815
+ * ```typescript
816
+ * const endpoint: ApiVariablesEndpoint = {
817
+ * url: "/api/variables/{workflowId}/{nodeId}",
818
+ * method: "GET"
819
+ * };
820
+ * ```
821
+ *
822
+ * @example POST Request with Body
823
+ * ```typescript
824
+ * const endpoint: ApiVariablesEndpoint = {
825
+ * url: "/api/variables",
826
+ * method: "POST",
827
+ * body: {
828
+ * workflowId: "{workflowId}",
829
+ * nodeId: "{nodeId}"
830
+ * }
831
+ * };
832
+ * ```
833
+ */
834
+ export interface ApiVariablesEndpoint {
835
+ /**
836
+ * URL to fetch variables from.
837
+ * Supports template placeholders:
838
+ * - `{workflowId}` - Resolved from workflow ID
839
+ * - `{nodeId}` - Resolved from node instance ID
840
+ *
841
+ * @example "/api/variables/{workflowId}/{nodeId}"
842
+ * @example "https://api.example.com/variables?workflow={workflowId}&node={nodeId}"
843
+ */
844
+ url: string;
845
+ /**
846
+ * HTTP method for the request.
847
+ * @default "GET"
848
+ */
849
+ method?: HttpMethod;
850
+ /**
851
+ * Custom headers to include in the request.
852
+ * Note: Authentication headers are automatically added via AuthProvider.
853
+ */
854
+ headers?: Record<string, string>;
855
+ /**
856
+ * Request body for POST/PUT/PATCH methods.
857
+ * Supports template variables like the URL.
858
+ */
859
+ body?: Record<string, unknown>;
860
+ /**
861
+ * Request timeout in milliseconds.
862
+ * @default 30000 (30 seconds)
863
+ */
864
+ timeout?: number;
865
+ /**
866
+ * Whether to cache the fetched schema.
867
+ * When false, schema is fetched on every editor load.
868
+ * @default true
869
+ */
870
+ cacheEnabled?: boolean;
871
+ }
615
872
  /**
616
873
  * Union type for all schema types
617
874
  */
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Utility functions for calculating handle positions on nodes
3
+ */
4
+ export interface HandlePosition {
5
+ left: number;
6
+ top: number;
7
+ }
8
+ /**
9
+ * Calculate handle position along a circle arc using cos/sin
10
+ *
11
+ * Distributes handles evenly along an arc on the left or right side of a circle.
12
+ * For N handles, they are positioned at angles calculated as:
13
+ * angle = centerAngle - arcSpan/2 + arcSpan * (index + 1) / (count + 1)
14
+ *
15
+ * @param index - The index of the handle (0-based)
16
+ * @param count - Total number of handles on this side
17
+ * @param side - 'left' for inputs, 'right' for outputs
18
+ * @param radius - The radius of the circle (default: 40px for 80px diameter)
19
+ * @param arcSpan - The arc span in radians (default: 5π/6 = 150°)
20
+ * @returns Object with left and top pixel values relative to the circle's bounding box
21
+ *
22
+ * @example
23
+ * // Single handle on left side - positioned at center (180°)
24
+ * getCircleHandlePosition(0, 1, 'left') // { left: 0, top: 36 }
25
+ *
26
+ * @example
27
+ * // Two handles on left side - positioned at 150° and 210°
28
+ * getCircleHandlePosition(0, 2, 'left') // { left: ~4.8, top: ~18 }
29
+ * getCircleHandlePosition(1, 2, 'left') // { left: ~4.8, top: ~54 }
30
+ */
31
+ export declare function getCircleHandlePosition(index: number, count: number, side: 'left' | 'right', radius?: number, arcSpan?: number): HandlePosition;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Utility functions for calculating handle positions on nodes
3
+ */
4
+ /**
5
+ * Calculate handle position along a circle arc using cos/sin
6
+ *
7
+ * Distributes handles evenly along an arc on the left or right side of a circle.
8
+ * For N handles, they are positioned at angles calculated as:
9
+ * angle = centerAngle - arcSpan/2 + arcSpan * (index + 1) / (count + 1)
10
+ *
11
+ * @param index - The index of the handle (0-based)
12
+ * @param count - Total number of handles on this side
13
+ * @param side - 'left' for inputs, 'right' for outputs
14
+ * @param radius - The radius of the circle (default: 40px for 80px diameter)
15
+ * @param arcSpan - The arc span in radians (default: 5π/6 = 150°)
16
+ * @returns Object with left and top pixel values relative to the circle's bounding box
17
+ *
18
+ * @example
19
+ * // Single handle on left side - positioned at center (180°)
20
+ * getCircleHandlePosition(0, 1, 'left') // { left: 0, top: 36 }
21
+ *
22
+ * @example
23
+ * // Two handles on left side - positioned at 150° and 210°
24
+ * getCircleHandlePosition(0, 2, 'left') // { left: ~4.8, top: ~18 }
25
+ * getCircleHandlePosition(1, 2, 'left') // { left: ~4.8, top: ~54 }
26
+ */
27
+ export function getCircleHandlePosition(index, count, side, radius = 40, arcSpan = (Math.PI * 5) / 6) {
28
+ const centerAngle = side === 'left' ? Math.PI : 0; // 180° for left, 0° for right
29
+ const angle = centerAngle - arcSpan / 2 + (arcSpan * (index + 1)) / (count + 1);
30
+ const centerOffset = radius; // center of the circle (assuming square bounding box)
31
+ return {
32
+ left: centerOffset + radius * Math.cos(angle),
33
+ top: centerOffset + radius * Math.sin(angle)
34
+ };
35
+ }
@@ -66,8 +66,8 @@ export declare function resolveComponentName(metadata: NodeMetadata, configNodeT
66
66
  */
67
67
  export declare function isNodeTypeSupported(metadata: NodeMetadata, nodeType: NodeType | string): boolean;
68
68
  /**
69
- * Gets enum options for node type configuration.
70
- * Used in config schemas to show available options.
69
+ * Gets oneOf options for node type configuration.
70
+ * Used in config schemas to show available options with labels.
71
71
  *
72
72
  * This function combines:
73
73
  * - Types specified in metadata.supportedTypes
@@ -75,27 +75,32 @@ export declare function isNodeTypeSupported(metadata: NodeMetadata, nodeType: No
75
75
  *
76
76
  * @param metadata - The node metadata
77
77
  * @param includeCustomTypes - Whether to include registered custom types
78
- * @returns Object with enum values and display names
78
+ * @returns Array of oneOf items with const (type value) and title (display name)
79
79
  */
80
- export declare function getNodeTypeEnumOptions(metadata: NodeMetadata, includeCustomTypes?: boolean): {
81
- enum: string[];
82
- enumNames: string[];
83
- };
80
+ export declare function getNodeTypeOneOfOptions(metadata: NodeMetadata, includeCustomTypes?: boolean): Array<{
81
+ const: string;
82
+ title: string;
83
+ }>;
84
84
  /**
85
85
  * Creates a nodeType config property that respects supportedTypes.
86
86
  * This replaces hardcoded enum values in config schemas.
87
87
  *
88
+ * Uses JSON Schema `oneOf` pattern with `const`/`title` for labeled options,
89
+ * which is the standard approach supported by form components.
90
+ *
88
91
  * @param metadata - The node metadata
89
92
  * @param defaultType - Optional default type override
90
- * @returns Config schema property object
93
+ * @returns Config schema property object with oneOf for labeled options
91
94
  */
92
95
  export declare function createNodeTypeConfigProperty(metadata: NodeMetadata, defaultType?: NodeType | string): {
93
96
  type: "string";
94
97
  title: string;
95
98
  description: string;
96
99
  default: string;
97
- enum: string[];
98
- enumNames: string[];
100
+ oneOf: {
101
+ const: string;
102
+ title: string;
103
+ }[];
99
104
  };
100
105
  /**
101
106
  * Check if a type string represents a valid registered or built-in type.
@@ -136,8 +136,8 @@ export function isNodeTypeSupported(metadata, nodeType) {
136
136
  return false;
137
137
  }
138
138
  /**
139
- * Gets enum options for node type configuration.
140
- * Used in config schemas to show available options.
139
+ * Gets oneOf options for node type configuration.
140
+ * Used in config schemas to show available options with labels.
141
141
  *
142
142
  * This function combines:
143
143
  * - Types specified in metadata.supportedTypes
@@ -145,58 +145,60 @@ export function isNodeTypeSupported(metadata, nodeType) {
145
145
  *
146
146
  * @param metadata - The node metadata
147
147
  * @param includeCustomTypes - Whether to include registered custom types
148
- * @returns Object with enum values and display names
148
+ * @returns Array of oneOf items with const (type value) and title (display name)
149
149
  */
150
- export function getNodeTypeEnumOptions(metadata, includeCustomTypes = false) {
150
+ export function getNodeTypeOneOfOptions(metadata, includeCustomTypes = false) {
151
151
  const availableTypes = getAvailableNodeTypes(metadata);
152
- // Build enum values and names
153
- const enumValues = [];
154
- const enumNames = [];
152
+ const options = [];
153
+ const includedTypes = new Set();
155
154
  for (const type of availableTypes) {
156
- enumValues.push(type);
155
+ includedTypes.add(type);
157
156
  // Get display name from registry or fallback to built-in names
158
157
  const registration = nodeComponentRegistry.get(type);
158
+ let title;
159
159
  if (registration) {
160
- enumNames.push(registration.displayName);
160
+ title = registration.displayName;
161
161
  }
162
162
  else if (type in TYPE_DISPLAY_NAMES) {
163
- enumNames.push(TYPE_DISPLAY_NAMES[type]);
163
+ title = TYPE_DISPLAY_NAMES[type];
164
164
  }
165
165
  else {
166
166
  // Format unknown type nicely
167
- enumNames.push(formatTypeName(type));
167
+ title = formatTypeName(type);
168
168
  }
169
+ options.push({ const: type, title });
169
170
  }
170
171
  // Optionally include all registered custom types
171
172
  if (includeCustomTypes) {
172
173
  const registrations = nodeComponentRegistry.filter({
173
- predicate: (reg) => !isBuiltinType(reg.type) && !enumValues.includes(reg.type)
174
+ predicate: (reg) => !isBuiltinType(reg.type) && !includedTypes.has(reg.type)
174
175
  });
175
176
  for (const reg of registrations) {
176
- enumValues.push(reg.type);
177
- enumNames.push(reg.displayName);
177
+ options.push({ const: reg.type, title: reg.displayName });
178
178
  }
179
179
  }
180
- return { enum: enumValues, enumNames };
180
+ return options;
181
181
  }
182
182
  /**
183
183
  * Creates a nodeType config property that respects supportedTypes.
184
184
  * This replaces hardcoded enum values in config schemas.
185
185
  *
186
+ * Uses JSON Schema `oneOf` pattern with `const`/`title` for labeled options,
187
+ * which is the standard approach supported by form components.
188
+ *
186
189
  * @param metadata - The node metadata
187
190
  * @param defaultType - Optional default type override
188
- * @returns Config schema property object
191
+ * @returns Config schema property object with oneOf for labeled options
189
192
  */
190
193
  export function createNodeTypeConfigProperty(metadata, defaultType) {
191
- const { enum: enumValues, enumNames } = getNodeTypeEnumOptions(metadata);
194
+ const oneOf = getNodeTypeOneOfOptions(metadata);
192
195
  const primaryType = defaultType ?? getPrimaryNodeType(metadata);
193
196
  return {
194
- type: 'string',
195
- title: 'Node Type',
196
- description: 'Choose the visual representation for this node',
197
+ type: "string",
198
+ title: "Node Type",
199
+ description: "Choose the visual representation for this node",
197
200
  default: primaryType,
198
- enum: enumValues,
199
- enumNames
201
+ oneOf
200
202
  };
201
203
  }
202
204
  /**
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@d34dman/flowdrop",
3
3
  "license": "MIT",
4
4
  "private": false,
5
- "version": "0.0.46",
5
+ "version": "0.0.48",
6
6
  "scripts": {
7
7
  "dev": "vite dev",
8
8
  "build": "vite build && npm run prepack",
@@ -12,7 +12,7 @@
12
12
  "watch:build:production": "npm-watch build:production",
13
13
  "watch:build": "npm-watch build",
14
14
  "preview": "vite preview",
15
- "prepare": "svelte-kit sync || echo ''",
15
+ "prepare": "svelte-kit sync || echo '' && husky",
16
16
  "prepack": "svelte-kit sync && svelte-package && publint",
17
17
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
18
18
  "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
@@ -30,6 +30,7 @@
30
30
  "build-storybook": "storybook build",
31
31
  "api:lint": "redocly lint api/openapi.yaml --config api/redocly.yaml",
32
32
  "api:bundle": "redocly bundle api/openapi.yaml -o api/bundled.yaml --config api/redocly.yaml",
33
+ "api:watch": "nodemon --watch api --ext yaml --ignore api/bundled.yaml --exec 'npm run api:bundle'",
33
34
  "api:preview": "redocly preview-docs api/openapi.yaml --config api/redocly.yaml",
34
35
  "api:docs": "redocly build-docs api/bundled.yaml -o api-docs/index.html"
35
36
  },
@@ -172,8 +173,10 @@
172
173
  "eslint-plugin-svelte": "^3.0.0",
173
174
  "globals": "^16.0.0",
174
175
  "happy-dom": "^20.0.11",
176
+ "husky": "^9.1.7",
175
177
  "msw": "^2.12.7",
176
178
  "npm-watch": "^0.13.0",
179
+ "picomatch": "^4.0.3",
177
180
  "playwright": "^1.53.0",
178
181
  "prettier": "^3.4.2",
179
182
  "prettier-plugin-svelte": "^3.3.3",
@@ -187,7 +190,8 @@
187
190
  "vite": "^6.2.6",
188
191
  "vite-plugin-devtools-json": "^0.2.1",
189
192
  "vitest": "^3.2.3",
190
- "vitest-browser-svelte": "^0.1.0"
193
+ "vitest-browser-svelte": "^0.1.0",
194
+ "yaml": "^2.8.2"
191
195
  },
192
196
  "overrides": {
193
197
  "@sveltejs/kit": {