@d34dman/flowdrop 0.0.47 → 0.0.49
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.
- package/dist/components/App.svelte +11 -0
- package/dist/components/App.svelte.d.ts +2 -0
- package/dist/components/ConfigForm.svelte +31 -5
- package/dist/components/ConfigForm.svelte.d.ts +3 -1
- package/dist/components/NodeSidebar.svelte +1 -0
- package/dist/components/SchemaForm.svelte +4 -2
- package/dist/components/SettingsPanel.svelte +3 -3
- package/dist/components/form/FormAutocomplete.svelte +9 -4
- package/dist/components/form/FormCodeEditor.svelte +17 -15
- package/dist/components/form/FormField.svelte +32 -4
- package/dist/components/form/FormField.svelte.d.ts +11 -0
- package/dist/components/form/FormFieldLight.svelte +0 -1
- package/dist/components/form/FormTemplateEditor.svelte +300 -79
- package/dist/components/form/FormTemplateEditor.svelte.d.ts +11 -7
- package/dist/components/form/index.d.ts +1 -1
- package/dist/components/form/index.js +1 -1
- package/dist/components/form/templateAutocomplete.d.ts +2 -11
- package/dist/components/form/templateAutocomplete.js +49 -104
- package/dist/components/form/types.d.ts +0 -6
- package/dist/components/nodes/TerminalNode.svelte +27 -15
- package/dist/components/nodes/ToolNode.svelte +4 -6
- package/dist/services/apiVariableService.d.ts +116 -0
- package/dist/services/apiVariableService.js +338 -0
- package/dist/services/globalSave.js +6 -0
- package/dist/services/variableService.d.ts +50 -9
- package/dist/services/variableService.js +139 -44
- package/dist/svelte-app.d.ts +5 -0
- package/dist/svelte-app.js +7 -1
- package/dist/types/index.d.ts +138 -1
- package/dist/types/settings.js +4 -4
- package/dist/utils/colors.js +1 -0
- package/dist/utils/handlePositioning.d.ts +31 -0
- package/dist/utils/handlePositioning.js +35 -0
- package/dist/utils/icons.js +1 -0
- package/dist/utils/nodeTypes.js +3 -3
- package/package.json +8 -4
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Variable Service
|
|
3
|
+
* Handles fetching template variable schemas from REST endpoints at runtime.
|
|
4
|
+
* Enables dynamic variable suggestions from backend APIs for template editors.
|
|
5
|
+
*
|
|
6
|
+
* @module services/apiVariableService
|
|
7
|
+
*/
|
|
8
|
+
import { getEndpointConfig } from './api.js';
|
|
9
|
+
/**
|
|
10
|
+
* Variable schema cache with TTL support
|
|
11
|
+
* Key format: `variables:{workflowId}:{nodeId}`
|
|
12
|
+
*/
|
|
13
|
+
const variableCache = new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Default cache TTL in milliseconds (5 minutes)
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_VARIABLE_CACHE_TTL = 5 * 60 * 1000;
|
|
18
|
+
/**
|
|
19
|
+
* Replaces {workflowId} and {nodeId} placeholders in URL template.
|
|
20
|
+
*
|
|
21
|
+
* @param template - The URL template string
|
|
22
|
+
* @param context - The variable context with workflowId and nodeId
|
|
23
|
+
* @returns The resolved URL with placeholders replaced
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const url = "/api/variables/{workflowId}/{nodeId}";
|
|
28
|
+
* const context = { workflowId: "wf-123", nodeId: "node-456" };
|
|
29
|
+
* resolveEndpointUrl(url, context);
|
|
30
|
+
* // Returns "/api/variables/wf-123/node-456"
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function resolveEndpointUrl(template, context) {
|
|
34
|
+
let resolved = template;
|
|
35
|
+
// Replace {workflowId}
|
|
36
|
+
if (context.workflowId) {
|
|
37
|
+
resolved = resolved.replace(/\{workflowId\}/g, encodeURIComponent(context.workflowId));
|
|
38
|
+
}
|
|
39
|
+
// Replace {nodeId}
|
|
40
|
+
resolved = resolved.replace(/\{nodeId\}/g, encodeURIComponent(context.nodeId));
|
|
41
|
+
return resolved;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generates a cache key for a variable schema based on the context.
|
|
45
|
+
*
|
|
46
|
+
* @param workflowId - The workflow ID (optional)
|
|
47
|
+
* @param nodeId - The node instance ID
|
|
48
|
+
* @returns A unique cache key string
|
|
49
|
+
*/
|
|
50
|
+
function generateVariableCacheKey(workflowId, nodeId) {
|
|
51
|
+
return `variables:${workflowId || 'unknown'}:${nodeId}`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Checks if a cached variable schema is still valid (not expired).
|
|
55
|
+
*
|
|
56
|
+
* @param entry - The cache entry to check
|
|
57
|
+
* @param ttl - Time-to-live in milliseconds
|
|
58
|
+
* @returns True if the cache entry is still valid
|
|
59
|
+
*/
|
|
60
|
+
function isCacheValid(entry, ttl = DEFAULT_VARIABLE_CACHE_TTL) {
|
|
61
|
+
return Date.now() - entry.cachedAt < ttl;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolves template variables in request body.
|
|
65
|
+
* Recursively processes nested objects and arrays.
|
|
66
|
+
*
|
|
67
|
+
* @param body - The request body object
|
|
68
|
+
* @param context - The variable context
|
|
69
|
+
* @returns The body with template variables resolved
|
|
70
|
+
*/
|
|
71
|
+
function resolveBodyTemplates(body, context) {
|
|
72
|
+
const resolved = {};
|
|
73
|
+
for (const [key, value] of Object.entries(body)) {
|
|
74
|
+
if (typeof value === 'string') {
|
|
75
|
+
// Resolve template variables in string values
|
|
76
|
+
let resolvedValue = value;
|
|
77
|
+
if (context.workflowId) {
|
|
78
|
+
resolvedValue = resolvedValue.replace(/\{workflowId\}/g, encodeURIComponent(context.workflowId));
|
|
79
|
+
}
|
|
80
|
+
resolvedValue = resolvedValue.replace(/\{nodeId\}/g, encodeURIComponent(context.nodeId));
|
|
81
|
+
resolved[key] = resolvedValue;
|
|
82
|
+
}
|
|
83
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
84
|
+
// Recursively resolve nested objects
|
|
85
|
+
resolved[key] = resolveBodyTemplates(value, context);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Pass through other values as-is
|
|
89
|
+
resolved[key] = value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Fetches variable schema from a backend API endpoint.
|
|
96
|
+
*
|
|
97
|
+
* @param workflowId - The workflow ID (optional)
|
|
98
|
+
* @param nodeId - The node instance ID
|
|
99
|
+
* @param config - The API variables configuration
|
|
100
|
+
* @param authProvider - Optional auth provider for auth headers
|
|
101
|
+
* @returns A promise that resolves to the variable schema result
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const config: ApiVariablesConfig = {
|
|
106
|
+
* endpoint: {
|
|
107
|
+
* url: "/api/variables/{workflowId}/{nodeId}",
|
|
108
|
+
* method: "GET"
|
|
109
|
+
* },
|
|
110
|
+
* cacheTtl: 300000
|
|
111
|
+
* };
|
|
112
|
+
*
|
|
113
|
+
* const result = await fetchVariableSchema("wf-123", "node-456", config);
|
|
114
|
+
* if (result.success && result.schema) {
|
|
115
|
+
* // Use the fetched variable schema
|
|
116
|
+
* }
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export async function fetchVariableSchema(workflowId, nodeId, config, authProvider) {
|
|
120
|
+
const endpoint = config.endpoint;
|
|
121
|
+
const context = { workflowId, nodeId };
|
|
122
|
+
// Generate cache key
|
|
123
|
+
const cacheKey = generateVariableCacheKey(workflowId, nodeId);
|
|
124
|
+
// Check cache first (if caching is enabled)
|
|
125
|
+
if (endpoint.cacheEnabled !== false) {
|
|
126
|
+
const cached = variableCache.get(cacheKey);
|
|
127
|
+
const ttl = config.cacheTtl ?? DEFAULT_VARIABLE_CACHE_TTL;
|
|
128
|
+
if (cached && isCacheValid(cached, ttl)) {
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
schema: cached.schema,
|
|
132
|
+
fromCache: true
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Resolve the URL with template variables
|
|
137
|
+
let url = resolveEndpointUrl(endpoint.url, context);
|
|
138
|
+
// If URL is relative, prepend base URL from endpoint config
|
|
139
|
+
if (url.startsWith('/')) {
|
|
140
|
+
const currentConfig = getEndpointConfig();
|
|
141
|
+
if (currentConfig?.baseUrl) {
|
|
142
|
+
const baseUrl = currentConfig.baseUrl.replace(/\/$/, '');
|
|
143
|
+
url = `${baseUrl}${url}`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Prepare request options
|
|
147
|
+
const method = endpoint.method ?? 'GET';
|
|
148
|
+
const timeout = endpoint.timeout ?? 30000;
|
|
149
|
+
const headers = {
|
|
150
|
+
Accept: 'application/json',
|
|
151
|
+
'Content-Type': 'application/json',
|
|
152
|
+
...endpoint.headers
|
|
153
|
+
};
|
|
154
|
+
// Add auth headers from AuthProvider if available
|
|
155
|
+
if (authProvider) {
|
|
156
|
+
try {
|
|
157
|
+
const authHeaders = await authProvider.getAuthHeaders();
|
|
158
|
+
Object.assign(headers, authHeaders);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
console.warn('Failed to get auth headers:', error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Add auth headers from endpoint config as fallback
|
|
165
|
+
const currentConfig = getEndpointConfig();
|
|
166
|
+
if (currentConfig?.auth) {
|
|
167
|
+
if (currentConfig.auth.type === 'bearer' && currentConfig.auth.token) {
|
|
168
|
+
headers['Authorization'] = headers['Authorization'] ?? `Bearer ${currentConfig.auth.token}`;
|
|
169
|
+
}
|
|
170
|
+
else if (currentConfig.auth.type === 'api_key' && currentConfig.auth.apiKey) {
|
|
171
|
+
headers['X-API-Key'] = headers['X-API-Key'] ?? currentConfig.auth.apiKey;
|
|
172
|
+
}
|
|
173
|
+
else if (currentConfig.auth.type === 'custom' && currentConfig.auth.headers) {
|
|
174
|
+
Object.assign(headers, currentConfig.auth.headers);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Prepare fetch options
|
|
178
|
+
const fetchOptions = {
|
|
179
|
+
method,
|
|
180
|
+
headers,
|
|
181
|
+
signal: AbortSignal.timeout(timeout)
|
|
182
|
+
};
|
|
183
|
+
// Add body for non-GET requests
|
|
184
|
+
if (method !== 'GET' && endpoint.body) {
|
|
185
|
+
const resolvedBody = resolveBodyTemplates(endpoint.body, context);
|
|
186
|
+
fetchOptions.body = JSON.stringify(resolvedBody);
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const response = await fetch(url, fetchOptions);
|
|
190
|
+
// Handle 404 as "no variables available"
|
|
191
|
+
if (response.status === 404) {
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
schema: { variables: {} },
|
|
195
|
+
fromCache: false
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
// Handle authentication errors
|
|
200
|
+
if (response.status === 401 || response.status === 403) {
|
|
201
|
+
if (authProvider?.onUnauthorized) {
|
|
202
|
+
const refreshed = await authProvider.onUnauthorized();
|
|
203
|
+
if (refreshed) {
|
|
204
|
+
// Retry with refreshed auth
|
|
205
|
+
return fetchVariableSchema(workflowId, nodeId, config, authProvider);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
error: 'Authentication failed'
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const errorText = await response.text();
|
|
214
|
+
return {
|
|
215
|
+
success: false,
|
|
216
|
+
error: `HTTP ${response.status}: ${errorText || response.statusText}`
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const data = await response.json();
|
|
220
|
+
// The response could be:
|
|
221
|
+
// 1. Direct VariableSchema: { variables: {...} }
|
|
222
|
+
// 2. Wrapped in { data: { variables: {...} } }
|
|
223
|
+
// 3. Wrapped in { schema: { variables: {...} } }
|
|
224
|
+
// 4. Wrapped in { success: true, data: { variables: {...} } }
|
|
225
|
+
let schema;
|
|
226
|
+
if (data.variables && typeof data.variables === 'object') {
|
|
227
|
+
// Direct VariableSchema
|
|
228
|
+
schema = data;
|
|
229
|
+
}
|
|
230
|
+
else if (data.data?.variables && typeof data.data.variables === 'object') {
|
|
231
|
+
// Wrapped in { data: ... }
|
|
232
|
+
schema = data.data;
|
|
233
|
+
}
|
|
234
|
+
else if (data.schema?.variables && typeof data.schema.variables === 'object') {
|
|
235
|
+
// Wrapped in { schema: ... }
|
|
236
|
+
schema = data.schema;
|
|
237
|
+
}
|
|
238
|
+
else if (data.success && data.data?.variables) {
|
|
239
|
+
// Wrapped in { success: true, data: ... }
|
|
240
|
+
schema = data.data;
|
|
241
|
+
}
|
|
242
|
+
if (!schema) {
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
error: 'Invalid variable schema format received from endpoint'
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// Cache the schema (if caching is enabled)
|
|
249
|
+
if (endpoint.cacheEnabled !== false) {
|
|
250
|
+
variableCache.set(cacheKey, {
|
|
251
|
+
schema,
|
|
252
|
+
cachedAt: Date.now(),
|
|
253
|
+
cacheKey
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
success: true,
|
|
258
|
+
schema,
|
|
259
|
+
fromCache: false
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
// Handle specific error types
|
|
264
|
+
if (error instanceof Error) {
|
|
265
|
+
if (error.name === 'AbortError' || error.name === 'TimeoutError') {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
error: `Request timed out after ${timeout}ms`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
success: false,
|
|
273
|
+
error: error.message
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
error: 'Unknown error occurred while fetching variable schema'
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Clears the variable schema cache.
|
|
284
|
+
* Can optionally clear only entries matching a specific pattern.
|
|
285
|
+
*
|
|
286
|
+
* @param pattern - Optional pattern to match cache keys (e.g., workflow ID or node ID)
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```typescript
|
|
290
|
+
* // Clear all cache
|
|
291
|
+
* clearVariableCache();
|
|
292
|
+
*
|
|
293
|
+
* // Clear cache for a specific workflow
|
|
294
|
+
* clearVariableCache("wf-123");
|
|
295
|
+
*
|
|
296
|
+
* // Clear cache for a specific node
|
|
297
|
+
* clearVariableCache("node-456");
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
export function clearVariableCache(pattern) {
|
|
301
|
+
if (!pattern) {
|
|
302
|
+
variableCache.clear();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
// Clear matching entries
|
|
306
|
+
for (const key of variableCache.keys()) {
|
|
307
|
+
if (key.includes(pattern)) {
|
|
308
|
+
variableCache.delete(key);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Invalidates a specific variable cache entry.
|
|
314
|
+
*
|
|
315
|
+
* @param workflowId - The workflow ID (optional)
|
|
316
|
+
* @param nodeId - The node instance ID
|
|
317
|
+
*/
|
|
318
|
+
export function invalidateVariableCache(workflowId, nodeId) {
|
|
319
|
+
const cacheKey = generateVariableCacheKey(workflowId, nodeId);
|
|
320
|
+
variableCache.delete(cacheKey);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Gets the current cache size (number of entries).
|
|
324
|
+
*
|
|
325
|
+
* @returns The number of cached variable schemas
|
|
326
|
+
*/
|
|
327
|
+
export function getVariableCacheSize() {
|
|
328
|
+
return variableCache.size;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Gets all cache keys currently stored.
|
|
332
|
+
* Useful for debugging and monitoring.
|
|
333
|
+
*
|
|
334
|
+
* @returns Array of cache keys
|
|
335
|
+
*/
|
|
336
|
+
export function getVariableCacheKeys() {
|
|
337
|
+
return Array.from(variableCache.keys());
|
|
338
|
+
}
|
|
@@ -62,6 +62,12 @@ async function ensureApiConfiguration() {
|
|
|
62
62
|
* Uses the current workflow from the global store
|
|
63
63
|
*/
|
|
64
64
|
export async function globalSaveWorkflow() {
|
|
65
|
+
// Flush any pending form changes by blurring the active element.
|
|
66
|
+
// This ensures focusout handlers (like ConfigForm's handleFormBlur)
|
|
67
|
+
// sync local state to the global store before we read it.
|
|
68
|
+
if (typeof document !== 'undefined' && document.activeElement instanceof HTMLElement) {
|
|
69
|
+
document.activeElement.blur();
|
|
70
|
+
}
|
|
65
71
|
let loadingToast;
|
|
66
72
|
try {
|
|
67
73
|
// Show loading toast
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module services/variableService
|
|
7
7
|
*/
|
|
8
|
-
import type { WorkflowNode, WorkflowEdge, VariableSchema, TemplateVariable } from
|
|
8
|
+
import type { WorkflowNode, WorkflowEdge, VariableSchema, TemplateVariable, TemplateVariablesConfig, AuthProvider } from '../types/index.js';
|
|
9
9
|
/**
|
|
10
10
|
* Options for deriving available variables.
|
|
11
11
|
*/
|
|
@@ -46,14 +46,6 @@ export interface GetAvailableVariablesOptions {
|
|
|
46
46
|
* ```
|
|
47
47
|
*/
|
|
48
48
|
export declare function getAvailableVariables(node: WorkflowNode, nodes: WorkflowNode[], edges: WorkflowEdge[], options?: GetAvailableVariablesOptions): VariableSchema;
|
|
49
|
-
/**
|
|
50
|
-
* Converts simple variable hints (string array) to a VariableSchema.
|
|
51
|
-
* Used for backward compatibility with the old variableHints format.
|
|
52
|
-
*
|
|
53
|
-
* @param hints - Array of variable names
|
|
54
|
-
* @returns A VariableSchema with basic string variables
|
|
55
|
-
*/
|
|
56
|
-
export declare function hintsToVariableSchema(hints: string[]): VariableSchema;
|
|
57
49
|
/**
|
|
58
50
|
* Gets the child variables for a given path in the variable schema.
|
|
59
51
|
* Used for drilling down into nested objects and arrays.
|
|
@@ -98,3 +90,52 @@ export declare function isArrayVariable(schema: VariableSchema, path: string): b
|
|
|
98
90
|
* @returns True if the variable has children that can be drilled into
|
|
99
91
|
*/
|
|
100
92
|
export declare function hasChildren(schema: VariableSchema, path: string): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Merges two variable schemas together.
|
|
95
|
+
* Variables from the primary schema take precedence over the secondary schema.
|
|
96
|
+
*
|
|
97
|
+
* @param primary - The primary variable schema (takes precedence)
|
|
98
|
+
* @param secondary - The secondary variable schema
|
|
99
|
+
* @returns A merged VariableSchema
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const apiSchema = { variables: { apiKey: {...}, endpoint: {...} } };
|
|
104
|
+
* const staticSchema = { variables: { env: {...}, config: {...} } };
|
|
105
|
+
* const merged = mergeVariableSchemas(apiSchema, staticSchema);
|
|
106
|
+
* // Result: { variables: { apiKey, endpoint, env, config } }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export declare function mergeVariableSchemas(primary: VariableSchema, secondary: VariableSchema): VariableSchema;
|
|
110
|
+
/**
|
|
111
|
+
* Gets variable schema using the appropriate mode (API, schema-based, or hybrid).
|
|
112
|
+
* This is the main orchestration function that determines how to fetch variables
|
|
113
|
+
* based on the configuration.
|
|
114
|
+
*
|
|
115
|
+
* @param node - The current node being configured
|
|
116
|
+
* @param nodes - All nodes in the workflow
|
|
117
|
+
* @param edges - All edges in the workflow
|
|
118
|
+
* @param config - Template variables configuration
|
|
119
|
+
* @param workflowId - Optional workflow ID for API context
|
|
120
|
+
* @param authProvider - Optional auth provider for API requests
|
|
121
|
+
* @returns A promise that resolves to the variable schema
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* // Schema-based mode (existing behavior)
|
|
126
|
+
* const config = { ports: ["data"], schema: {...} };
|
|
127
|
+
* const schema = await getVariableSchema(node, nodes, edges, config);
|
|
128
|
+
*
|
|
129
|
+
* // API mode
|
|
130
|
+
* const config = { api: { endpoint: { url: "/api/variables/{workflowId}/{nodeId}" } } };
|
|
131
|
+
* const schema = await getVariableSchema(node, nodes, edges, config, workflowId, authProvider);
|
|
132
|
+
*
|
|
133
|
+
* // Hybrid mode (API + static schema)
|
|
134
|
+
* const config = {
|
|
135
|
+
* schema: {...},
|
|
136
|
+
* api: { endpoint: {...}, mergeWithSchema: true }
|
|
137
|
+
* };
|
|
138
|
+
* const schema = await getVariableSchema(node, nodes, edges, config, workflowId, authProvider);
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export declare function getVariableSchema(node: WorkflowNode, nodes: WorkflowNode[], edges: WorkflowEdge[], config: TemplateVariablesConfig, workflowId?: string, authProvider?: AuthProvider): Promise<VariableSchema>;
|
|
@@ -13,22 +13,22 @@
|
|
|
13
13
|
*/
|
|
14
14
|
function toTemplateVariableType(schemaType) {
|
|
15
15
|
switch (schemaType) {
|
|
16
|
-
case
|
|
17
|
-
return
|
|
18
|
-
case
|
|
19
|
-
return
|
|
20
|
-
case
|
|
21
|
-
return
|
|
22
|
-
case
|
|
23
|
-
return
|
|
24
|
-
case
|
|
25
|
-
return
|
|
26
|
-
case
|
|
27
|
-
return
|
|
28
|
-
case
|
|
29
|
-
return
|
|
16
|
+
case 'string':
|
|
17
|
+
return 'string';
|
|
18
|
+
case 'number':
|
|
19
|
+
return 'number';
|
|
20
|
+
case 'integer':
|
|
21
|
+
return 'integer';
|
|
22
|
+
case 'boolean':
|
|
23
|
+
return 'boolean';
|
|
24
|
+
case 'array':
|
|
25
|
+
return 'array';
|
|
26
|
+
case 'object':
|
|
27
|
+
return 'object';
|
|
28
|
+
case 'float':
|
|
29
|
+
return 'float';
|
|
30
30
|
default:
|
|
31
|
-
return
|
|
31
|
+
return 'mixed';
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
@@ -51,15 +51,15 @@ function propertyToTemplateVariable(name, property, sourcePort, sourceNode) {
|
|
|
51
51
|
sourceNode
|
|
52
52
|
};
|
|
53
53
|
// Handle nested object properties
|
|
54
|
-
if (property.type ===
|
|
54
|
+
if (property.type === 'object' && property.properties) {
|
|
55
55
|
variable.properties = {};
|
|
56
56
|
for (const [propName, propValue] of Object.entries(property.properties)) {
|
|
57
57
|
variable.properties[propName] = propertyToTemplateVariable(propName, propValue, sourcePort, sourceNode);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
// Handle array items
|
|
61
|
-
if (property.type ===
|
|
62
|
-
variable.items = propertyToTemplateVariable(
|
|
61
|
+
if (property.type === 'array' && property.items) {
|
|
62
|
+
variable.items = propertyToTemplateVariable('item', property.items, sourcePort, sourceNode);
|
|
63
63
|
}
|
|
64
64
|
return variable;
|
|
65
65
|
}
|
|
@@ -78,7 +78,7 @@ function portToTemplateVariable(port, sourceNode) {
|
|
|
78
78
|
name: port.id,
|
|
79
79
|
label: port.name,
|
|
80
80
|
description: port.description,
|
|
81
|
-
type:
|
|
81
|
+
type: 'object',
|
|
82
82
|
sourcePort: port.id,
|
|
83
83
|
sourceNode,
|
|
84
84
|
properties: {}
|
|
@@ -186,12 +186,12 @@ export function getAvailableVariables(node, nodes, edges, options) {
|
|
|
186
186
|
for (const connection of connections) {
|
|
187
187
|
const { sourceNode, sourcePort, targetPort } = connection;
|
|
188
188
|
// Skip trigger ports - they don't carry data
|
|
189
|
-
if (sourcePort?.dataType ===
|
|
189
|
+
if (sourcePort?.dataType === 'trigger')
|
|
190
190
|
continue;
|
|
191
|
-
if (targetPort?.dataType ===
|
|
191
|
+
if (targetPort?.dataType === 'trigger')
|
|
192
192
|
continue;
|
|
193
193
|
// Get the target port ID for filtering
|
|
194
|
-
const targetPortId = targetPort?.id ?? sourcePort?.id ??
|
|
194
|
+
const targetPortId = targetPort?.id ?? sourcePort?.id ?? 'data';
|
|
195
195
|
// Filter by target port IDs if specified
|
|
196
196
|
if (targetPortIds !== undefined) {
|
|
197
197
|
if (!targetPortIds.includes(targetPortId))
|
|
@@ -212,7 +212,7 @@ export function getAvailableVariables(node, nodes, edges, options) {
|
|
|
212
212
|
}
|
|
213
213
|
else {
|
|
214
214
|
// No schema or includePortName is true - use port name as the variable
|
|
215
|
-
const variableName = includePortName ? targetPortId :
|
|
215
|
+
const variableName = includePortName ? targetPortId : targetPortId;
|
|
216
216
|
// Skip if we already have a variable with this name
|
|
217
217
|
if (variables[variableName])
|
|
218
218
|
continue;
|
|
@@ -224,23 +224,6 @@ export function getAvailableVariables(node, nodes, edges, options) {
|
|
|
224
224
|
}
|
|
225
225
|
return { variables };
|
|
226
226
|
}
|
|
227
|
-
/**
|
|
228
|
-
* Converts simple variable hints (string array) to a VariableSchema.
|
|
229
|
-
* Used for backward compatibility with the old variableHints format.
|
|
230
|
-
*
|
|
231
|
-
* @param hints - Array of variable names
|
|
232
|
-
* @returns A VariableSchema with basic string variables
|
|
233
|
-
*/
|
|
234
|
-
export function hintsToVariableSchema(hints) {
|
|
235
|
-
const variables = {};
|
|
236
|
-
for (const hint of hints) {
|
|
237
|
-
variables[hint] = {
|
|
238
|
-
name: hint,
|
|
239
|
-
type: "mixed"
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
return { variables };
|
|
243
|
-
}
|
|
244
227
|
/**
|
|
245
228
|
* Gets the child variables for a given path in the variable schema.
|
|
246
229
|
* Used for drilling down into nested objects and arrays.
|
|
@@ -261,7 +244,7 @@ export function hintsToVariableSchema(hints) {
|
|
|
261
244
|
* ```
|
|
262
245
|
*/
|
|
263
246
|
export function getChildVariables(schema, path) {
|
|
264
|
-
const parts = path.split(
|
|
247
|
+
const parts = path.split('.');
|
|
265
248
|
let current;
|
|
266
249
|
// Navigate to the target variable
|
|
267
250
|
for (let i = 0; i < parts.length; i++) {
|
|
@@ -312,7 +295,7 @@ export function getArrayIndexSuggestions(maxIndex = 2) {
|
|
|
312
295
|
suggestions.push(`${i}]`);
|
|
313
296
|
}
|
|
314
297
|
// Add wildcard for "all items"
|
|
315
|
-
suggestions.push(
|
|
298
|
+
suggestions.push('*]');
|
|
316
299
|
return suggestions;
|
|
317
300
|
}
|
|
318
301
|
/**
|
|
@@ -323,7 +306,7 @@ export function getArrayIndexSuggestions(maxIndex = 2) {
|
|
|
323
306
|
* @returns True if the variable is an array type
|
|
324
307
|
*/
|
|
325
308
|
export function isArrayVariable(schema, path) {
|
|
326
|
-
const parts = path.split(
|
|
309
|
+
const parts = path.split('.');
|
|
327
310
|
let current;
|
|
328
311
|
for (let i = 0; i < parts.length; i++) {
|
|
329
312
|
const part = parts[i];
|
|
@@ -352,7 +335,7 @@ export function isArrayVariable(schema, path) {
|
|
|
352
335
|
return false;
|
|
353
336
|
}
|
|
354
337
|
}
|
|
355
|
-
return current?.type ===
|
|
338
|
+
return current?.type === 'array';
|
|
356
339
|
}
|
|
357
340
|
/**
|
|
358
341
|
* Checks if a variable at the given path has child properties.
|
|
@@ -365,3 +348,115 @@ export function hasChildren(schema, path) {
|
|
|
365
348
|
const children = getChildVariables(schema, path);
|
|
366
349
|
return children.length > 0;
|
|
367
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Merges two variable schemas together.
|
|
353
|
+
* Variables from the primary schema take precedence over the secondary schema.
|
|
354
|
+
*
|
|
355
|
+
* @param primary - The primary variable schema (takes precedence)
|
|
356
|
+
* @param secondary - The secondary variable schema
|
|
357
|
+
* @returns A merged VariableSchema
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```typescript
|
|
361
|
+
* const apiSchema = { variables: { apiKey: {...}, endpoint: {...} } };
|
|
362
|
+
* const staticSchema = { variables: { env: {...}, config: {...} } };
|
|
363
|
+
* const merged = mergeVariableSchemas(apiSchema, staticSchema);
|
|
364
|
+
* // Result: { variables: { apiKey, endpoint, env, config } }
|
|
365
|
+
* ```
|
|
366
|
+
*/
|
|
367
|
+
export function mergeVariableSchemas(primary, secondary) {
|
|
368
|
+
// Create a shallow copy of secondary variables
|
|
369
|
+
const mergedVariables = { ...secondary.variables };
|
|
370
|
+
// Overlay primary variables (they take precedence)
|
|
371
|
+
for (const [key, value] of Object.entries(primary.variables)) {
|
|
372
|
+
mergedVariables[key] = value;
|
|
373
|
+
}
|
|
374
|
+
return { variables: mergedVariables };
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Gets variable schema using the appropriate mode (API, schema-based, or hybrid).
|
|
378
|
+
* This is the main orchestration function that determines how to fetch variables
|
|
379
|
+
* based on the configuration.
|
|
380
|
+
*
|
|
381
|
+
* @param node - The current node being configured
|
|
382
|
+
* @param nodes - All nodes in the workflow
|
|
383
|
+
* @param edges - All edges in the workflow
|
|
384
|
+
* @param config - Template variables configuration
|
|
385
|
+
* @param workflowId - Optional workflow ID for API context
|
|
386
|
+
* @param authProvider - Optional auth provider for API requests
|
|
387
|
+
* @returns A promise that resolves to the variable schema
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* ```typescript
|
|
391
|
+
* // Schema-based mode (existing behavior)
|
|
392
|
+
* const config = { ports: ["data"], schema: {...} };
|
|
393
|
+
* const schema = await getVariableSchema(node, nodes, edges, config);
|
|
394
|
+
*
|
|
395
|
+
* // API mode
|
|
396
|
+
* const config = { api: { endpoint: { url: "/api/variables/{workflowId}/{nodeId}" } } };
|
|
397
|
+
* const schema = await getVariableSchema(node, nodes, edges, config, workflowId, authProvider);
|
|
398
|
+
*
|
|
399
|
+
* // Hybrid mode (API + static schema)
|
|
400
|
+
* const config = {
|
|
401
|
+
* schema: {...},
|
|
402
|
+
* api: { endpoint: {...}, mergeWithSchema: true }
|
|
403
|
+
* };
|
|
404
|
+
* const schema = await getVariableSchema(node, nodes, edges, config, workflowId, authProvider);
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
export async function getVariableSchema(node, nodes, edges, config, workflowId, authProvider) {
|
|
408
|
+
let resultSchema = { variables: {} };
|
|
409
|
+
// Try API mode first (if configured)
|
|
410
|
+
if (config.api) {
|
|
411
|
+
try {
|
|
412
|
+
// Import API variable service dynamically to avoid circular dependencies
|
|
413
|
+
const { fetchVariableSchema } = await import('./apiVariableService.js');
|
|
414
|
+
const apiResult = await fetchVariableSchema(workflowId, node.id, config.api, authProvider);
|
|
415
|
+
if (apiResult.success && apiResult.schema) {
|
|
416
|
+
resultSchema = apiResult.schema;
|
|
417
|
+
// Merge with static schema if configured
|
|
418
|
+
if (config.api.mergeWithSchema !== false && config.schema) {
|
|
419
|
+
resultSchema = mergeVariableSchemas(resultSchema, config.schema);
|
|
420
|
+
}
|
|
421
|
+
// Merge with port-derived variables if configured
|
|
422
|
+
if (config.api.mergeWithPorts) {
|
|
423
|
+
const portSchema = getAvailableVariables(node, nodes, edges, {
|
|
424
|
+
targetPortIds: config.ports,
|
|
425
|
+
includePortName: config.includePortName
|
|
426
|
+
});
|
|
427
|
+
resultSchema = mergeVariableSchemas(resultSchema, portSchema);
|
|
428
|
+
}
|
|
429
|
+
return resultSchema;
|
|
430
|
+
}
|
|
431
|
+
else if (!config.api.fallbackOnError) {
|
|
432
|
+
// API failed and fallback is disabled - return empty schema
|
|
433
|
+
console.error('Failed to fetch variables from API:', apiResult.error);
|
|
434
|
+
return { variables: {} };
|
|
435
|
+
}
|
|
436
|
+
// If fallback is enabled (default), continue to schema-based mode below
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
console.error('Error fetching variables from API:', error);
|
|
440
|
+
// If fallback is disabled, return empty schema
|
|
441
|
+
if (config.api.fallbackOnError === false) {
|
|
442
|
+
return { variables: {} };
|
|
443
|
+
}
|
|
444
|
+
// Otherwise, continue to schema-based mode below
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Schema-based mode (existing behavior)
|
|
448
|
+
// This is the fallback when API mode is not configured or fails
|
|
449
|
+
// Start with port-derived variables (if ports are configured or no API mode)
|
|
450
|
+
if (config.ports !== undefined || !config.api) {
|
|
451
|
+
const portSchema = getAvailableVariables(node, nodes, edges, {
|
|
452
|
+
targetPortIds: config.ports,
|
|
453
|
+
includePortName: config.includePortName
|
|
454
|
+
});
|
|
455
|
+
resultSchema = portSchema;
|
|
456
|
+
}
|
|
457
|
+
// Merge with static schema (if configured)
|
|
458
|
+
if (config.schema) {
|
|
459
|
+
resultSchema = mergeVariableSchemas(config.schema, resultSchema);
|
|
460
|
+
}
|
|
461
|
+
return resultSchema;
|
|
462
|
+
}
|