@d34dman/flowdrop 0.0.15 → 0.0.17

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 (40) hide show
  1. package/README.md +64 -1
  2. package/dist/api/enhanced-client.d.ts +119 -3
  3. package/dist/api/enhanced-client.js +233 -54
  4. package/dist/components/App.svelte +145 -33
  5. package/dist/components/App.svelte.d.ts +27 -1
  6. package/dist/components/FlowDropZone.svelte +4 -5
  7. package/dist/components/FlowDropZone.svelte.d.ts +1 -1
  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/data/samples.js +9 -9
  13. package/dist/examples/adapter-usage.js +1 -1
  14. package/dist/helpers/workflowEditorHelper.d.ts +44 -4
  15. package/dist/helpers/workflowEditorHelper.js +161 -30
  16. package/dist/index.d.ts +12 -2
  17. package/dist/index.js +20 -1
  18. package/dist/registry/builtinNodes.d.ts +77 -0
  19. package/dist/registry/builtinNodes.js +181 -0
  20. package/dist/registry/index.d.ts +7 -0
  21. package/dist/registry/index.js +10 -0
  22. package/dist/registry/nodeComponentRegistry.d.ts +307 -0
  23. package/dist/registry/nodeComponentRegistry.js +315 -0
  24. package/dist/registry/plugin.d.ts +215 -0
  25. package/dist/registry/plugin.js +249 -0
  26. package/dist/services/draftStorage.d.ts +171 -0
  27. package/dist/services/draftStorage.js +298 -0
  28. package/dist/stores/workflowStore.d.ts +103 -0
  29. package/dist/stores/workflowStore.js +249 -29
  30. package/dist/styles/base.css +15 -0
  31. package/dist/svelte-app.d.ts +110 -28
  32. package/dist/svelte-app.js +150 -27
  33. package/dist/types/auth.d.ts +278 -0
  34. package/dist/types/auth.js +244 -0
  35. package/dist/types/events.d.ts +163 -0
  36. package/dist/types/events.js +30 -0
  37. package/dist/types/index.d.ts +38 -3
  38. package/dist/utils/nodeTypes.d.ts +76 -21
  39. package/dist/utils/nodeTypes.js +180 -32
  40. package/package.json +1 -2
@@ -1,24 +1,80 @@
1
1
  /**
2
2
  * Enhanced API Client for FlowDrop
3
- * Uses configurable endpoints for all API actions
3
+ *
4
+ * Uses configurable endpoints and supports pluggable authentication providers.
5
+ *
6
+ * @module api/enhanced-client
4
7
  */
5
8
  import { buildEndpointUrl, getEndpointMethod, getEndpointHeaders } from '../config/endpoints.js';
9
+ import { createAuthProviderFromLegacyConfig } from '../types/auth.js';
10
+ /**
11
+ * API error with additional context
12
+ */
13
+ export class ApiError extends Error {
14
+ /** HTTP status code */
15
+ status;
16
+ /** Original error data from API */
17
+ errorData;
18
+ /** Operation that was being performed */
19
+ operation;
20
+ constructor(message, status, operation, errorData = {}) {
21
+ super(message);
22
+ this.name = 'ApiError';
23
+ this.status = status;
24
+ this.operation = operation;
25
+ this.errorData = errorData;
26
+ }
27
+ }
6
28
  /**
7
29
  * Enhanced HTTP API client for FlowDrop with configurable endpoints
30
+ *
31
+ * Supports pluggable authentication via AuthProvider interface.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * // With AuthProvider
36
+ * const client = new EnhancedFlowDropApiClient(config, authProvider);
37
+ *
38
+ * // Backward compatible (uses config.auth)
39
+ * const client = new EnhancedFlowDropApiClient(config);
40
+ * ```
8
41
  */
9
42
  export class EnhancedFlowDropApiClient {
10
43
  config;
11
- constructor(config) {
44
+ authProvider;
45
+ /**
46
+ * Create a new EnhancedFlowDropApiClient
47
+ *
48
+ * @param config - Endpoint configuration
49
+ * @param authProvider - Optional authentication provider (if not provided, uses config.auth)
50
+ */
51
+ constructor(config, authProvider) {
12
52
  this.config = config;
53
+ // Use provided AuthProvider or create one from legacy config
54
+ this.authProvider = authProvider ?? createAuthProviderFromLegacyConfig(config.auth);
13
55
  }
14
56
  /**
15
- * Make HTTP request with error handling and retry logic
57
+ * Make HTTP request with error handling, retry logic, and auth support
58
+ *
59
+ * @param endpointKey - Key identifying the endpoint (for method/header lookup)
60
+ * @param endpointPath - The endpoint path template
61
+ * @param params - URL parameters to substitute
62
+ * @param options - Additional fetch options
63
+ * @param operation - Description of the operation (for error messages)
16
64
  */
17
- async request(endpointKey, endpointPath, params, options = {}) {
65
+ async request(endpointKey, endpointPath, params, options = {}, operation = 'API request') {
18
66
  const url = buildEndpointUrl(this.config, endpointPath, params);
19
- const method = getEndpointMethod(this.config, endpointKey);
20
- const headers = getEndpointHeaders(this.config, endpointKey);
21
- const config = {
67
+ const method = options.method ?? getEndpointMethod(this.config, endpointKey);
68
+ const configHeaders = getEndpointHeaders(this.config, endpointKey);
69
+ // Get auth headers from provider
70
+ const authHeaders = await this.authProvider.getAuthHeaders();
71
+ // Merge headers: config headers < auth headers < request-specific headers
72
+ const headers = {
73
+ ...configHeaders,
74
+ ...authHeaders,
75
+ ...options.headers
76
+ };
77
+ const fetchConfig = {
22
78
  method,
23
79
  headers,
24
80
  ...options
@@ -27,184 +83,307 @@ export class EnhancedFlowDropApiClient {
27
83
  const maxAttempts = this.config.retry?.enabled ? this.config.retry.maxAttempts : 1;
28
84
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
29
85
  try {
30
- const response = await fetch(url, config);
86
+ const response = await fetch(url, fetchConfig);
87
+ // Handle 401 Unauthorized
88
+ if (response.status === 401) {
89
+ if (this.authProvider.onUnauthorized) {
90
+ const refreshed = await this.authProvider.onUnauthorized();
91
+ if (refreshed && attempt < maxAttempts) {
92
+ // Get new auth headers and retry
93
+ const newAuthHeaders = await this.authProvider.getAuthHeaders();
94
+ fetchConfig.headers = {
95
+ ...configHeaders,
96
+ ...newAuthHeaders,
97
+ ...options.headers
98
+ };
99
+ continue; // Retry with new headers
100
+ }
101
+ }
102
+ throw new ApiError('Unauthorized', 401, operation, {});
103
+ }
104
+ // Handle 403 Forbidden
105
+ if (response.status === 403) {
106
+ if (this.authProvider.onForbidden) {
107
+ await this.authProvider.onForbidden();
108
+ }
109
+ throw new ApiError('Forbidden', 403, operation, {});
110
+ }
111
+ // Handle other errors
31
112
  if (!response.ok) {
32
113
  const errorData = await response.json().catch(() => ({}));
33
- throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`);
114
+ throw new ApiError(errorData.error ?? `HTTP ${response.status}: ${response.statusText}`, response.status, operation, errorData);
34
115
  }
35
116
  const data = await response.json();
36
117
  return data;
37
118
  }
38
119
  catch (error) {
39
- lastError = error instanceof Error ? error : new Error(String(error));
40
- // Don't retry on last attempt
41
- if (attempt === maxAttempts) {
42
- console.error(`API request failed after ${maxAttempts} attempts:`, lastError);
120
+ // If it's already an ApiError, preserve it
121
+ if (error instanceof ApiError) {
122
+ lastError = error;
123
+ }
124
+ else {
125
+ lastError = error instanceof Error ? error : new Error(String(error));
126
+ }
127
+ // Don't retry on auth errors (401, 403) or last attempt
128
+ if ((lastError instanceof ApiError &&
129
+ (lastError.status === 401 || lastError.status === 403)) ||
130
+ attempt === maxAttempts) {
131
+ console.error(`API request failed after ${attempt} attempts:`, lastError);
43
132
  throw lastError;
44
133
  }
45
134
  // Wait before retry
46
- const delay = this.config.retry?.delay || 1000;
135
+ const delay = this.config.retry?.delay ?? 1000;
47
136
  const backoffDelay = this.config.retry?.backoff === 'exponential' ? delay * Math.pow(2, attempt - 1) : delay;
48
137
  await new Promise((resolve) => setTimeout(resolve, backoffDelay));
49
138
  }
50
139
  }
51
140
  throw lastError;
52
141
  }
142
+ /**
143
+ * Update the auth provider
144
+ *
145
+ * Useful for updating auth after token refresh in parent application.
146
+ * Note: Per specification, this should rarely be needed as auth is typically
147
+ * set at mount time and requires remount to change.
148
+ *
149
+ * @param authProvider - New authentication provider
150
+ */
151
+ setAuthProvider(authProvider) {
152
+ this.authProvider = authProvider;
153
+ }
154
+ /**
155
+ * Get current auth provider
156
+ *
157
+ * @returns The current AuthProvider instance
158
+ */
159
+ getAuthProvider() {
160
+ return this.authProvider;
161
+ }
162
+ // =========================================================================
53
163
  // Node API Methods
164
+ // =========================================================================
165
+ /**
166
+ * Fetch all available node types
167
+ */
54
168
  async getAvailableNodes() {
55
- const response = await this.request('nodes.list', this.config.endpoints.nodes.list);
169
+ const response = await this.request('nodes.list', this.config.endpoints.nodes.list, undefined, {}, 'fetch available nodes');
56
170
  if (!response.success || !response.data) {
57
- throw new Error(response.error || 'Failed to fetch available nodes');
171
+ throw new Error(response.error ?? 'Failed to fetch available nodes');
58
172
  }
59
173
  return response.data;
60
174
  }
175
+ /**
176
+ * Fetch nodes filtered by category
177
+ */
61
178
  async getNodesByCategory(category) {
62
- const response = await this.request('nodes.byCategory', this.config.endpoints.nodes.byCategory, { category });
179
+ const response = await this.request('nodes.byCategory', this.config.endpoints.nodes.byCategory, { category }, {}, 'fetch nodes by category');
63
180
  if (!response.success || !response.data) {
64
- throw new Error(response.error || 'Failed to fetch nodes by category');
181
+ throw new Error(response.error ?? 'Failed to fetch nodes by category');
65
182
  }
66
183
  return response.data;
67
184
  }
185
+ /**
186
+ * Fetch metadata for a specific node type
187
+ */
68
188
  async getNodeMetadata(nodeId) {
69
- const response = await this.request('nodes.metadata', this.config.endpoints.nodes.metadata, { id: nodeId });
189
+ const response = await this.request('nodes.metadata', this.config.endpoints.nodes.metadata, { id: nodeId }, {}, 'fetch node metadata');
70
190
  if (!response.success || !response.data) {
71
- throw new Error(response.error || 'Failed to fetch node metadata');
191
+ throw new Error(response.error ?? 'Failed to fetch node metadata');
72
192
  }
73
193
  return response.data;
74
194
  }
195
+ // =========================================================================
75
196
  // Workflow API Methods
197
+ // =========================================================================
198
+ /**
199
+ * Save a new workflow
200
+ */
76
201
  async saveWorkflow(workflow) {
77
202
  const response = await this.request('workflows.create', this.config.endpoints.workflows.create, undefined, {
78
203
  method: 'POST',
79
204
  body: JSON.stringify(workflow)
80
- });
205
+ }, 'save workflow');
81
206
  if (!response.success || !response.data) {
82
- throw new Error(response.error || 'Failed to save workflow');
207
+ throw new Error(response.error ?? 'Failed to save workflow');
83
208
  }
84
209
  return response.data;
85
210
  }
211
+ /**
212
+ * Update an existing workflow
213
+ */
86
214
  async updateWorkflow(workflowId, workflow) {
87
215
  const response = await this.request('workflows.update', this.config.endpoints.workflows.update, { id: workflowId }, {
88
216
  method: 'PUT',
89
217
  body: JSON.stringify(workflow)
90
- });
218
+ }, 'update workflow');
91
219
  if (!response.success || !response.data) {
92
- throw new Error(response.error || 'Failed to update workflow');
220
+ throw new Error(response.error ?? 'Failed to update workflow');
93
221
  }
94
222
  return response.data;
95
223
  }
224
+ /**
225
+ * Load a workflow by ID
226
+ */
96
227
  async loadWorkflow(workflowId) {
97
- const response = await this.request('workflows.get', this.config.endpoints.workflows.get, { id: workflowId });
228
+ const response = await this.request('workflows.get', this.config.endpoints.workflows.get, { id: workflowId }, {}, 'load workflow');
98
229
  if (!response.success || !response.data) {
99
- throw new Error(response.error || 'Failed to load workflow');
230
+ throw new Error(response.error ?? 'Failed to load workflow');
100
231
  }
101
232
  return response.data;
102
233
  }
234
+ /**
235
+ * List all workflows
236
+ */
103
237
  async listWorkflows() {
104
- const response = await this.request('workflows.list', this.config.endpoints.workflows.list);
238
+ const response = await this.request('workflows.list', this.config.endpoints.workflows.list, undefined, {}, 'list workflows');
105
239
  if (!response.success || !response.data) {
106
- throw new Error(response.error || 'Failed to list workflows');
240
+ throw new Error(response.error ?? 'Failed to list workflows');
107
241
  }
108
242
  return response.data;
109
243
  }
244
+ /**
245
+ * Delete a workflow
246
+ */
110
247
  async deleteWorkflow(workflowId) {
111
- const response = await this.request('workflows.delete', this.config.endpoints.workflows.delete, { id: workflowId }, { method: 'DELETE' });
248
+ const response = await this.request('workflows.delete', this.config.endpoints.workflows.delete, { id: workflowId }, { method: 'DELETE' }, 'delete workflow');
112
249
  if (!response.success) {
113
- throw new Error(response.error || 'Failed to delete workflow');
250
+ throw new Error(response.error ?? 'Failed to delete workflow');
114
251
  }
115
252
  }
253
+ /**
254
+ * Validate a workflow
255
+ */
116
256
  async validateWorkflow(workflow) {
117
257
  const response = await this.request('workflows.validate', this.config.endpoints.workflows.validate, undefined, {
118
258
  method: 'POST',
119
259
  body: JSON.stringify(workflow)
120
- });
260
+ }, 'validate workflow');
121
261
  if (!response.success || !response.data) {
122
- throw new Error(response.error || 'Failed to validate workflow');
262
+ throw new Error(response.error ?? 'Failed to validate workflow');
123
263
  }
124
264
  return response.data;
125
265
  }
266
+ /**
267
+ * Export a workflow as JSON string
268
+ */
126
269
  async exportWorkflow(workflowId) {
127
- const response = await this.request('workflows.export', this.config.endpoints.workflows.export, { id: workflowId });
270
+ const response = await this.request('workflows.export', this.config.endpoints.workflows.export, { id: workflowId }, {}, 'export workflow');
128
271
  if (!response.success || !response.data) {
129
- throw new Error(response.error || 'Failed to export workflow');
272
+ throw new Error(response.error ?? 'Failed to export workflow');
130
273
  }
131
274
  return response.data;
132
275
  }
276
+ /**
277
+ * Import a workflow from JSON
278
+ */
133
279
  async importWorkflow(workflowJson) {
134
280
  const response = await this.request('workflows.import', this.config.endpoints.workflows.import, undefined, {
135
281
  method: 'POST',
136
282
  body: JSON.stringify({ workflow: workflowJson })
137
- });
283
+ }, 'import workflow');
138
284
  if (!response.success || !response.data) {
139
- throw new Error(response.error || 'Failed to import workflow');
285
+ throw new Error(response.error ?? 'Failed to import workflow');
140
286
  }
141
287
  return response.data;
142
288
  }
289
+ // =========================================================================
143
290
  // Execution API Methods
291
+ // =========================================================================
292
+ /**
293
+ * Execute a workflow
294
+ */
144
295
  async executeWorkflow(workflowId, inputs) {
145
296
  const response = await this.request('executions.execute', this.config.endpoints.executions.execute, { id: workflowId }, {
146
297
  method: 'POST',
147
298
  body: JSON.stringify({ inputs })
148
- });
299
+ }, 'execute workflow');
149
300
  if (!response.success || !response.data) {
150
- throw new Error(response.error || 'Failed to execute workflow');
301
+ throw new Error(response.error ?? 'Failed to execute workflow');
151
302
  }
152
303
  return response.data;
153
304
  }
305
+ /**
306
+ * Get execution status
307
+ */
154
308
  async getExecutionStatus(executionId) {
155
- const response = await this.request('executions.status', this.config.endpoints.executions.status, { id: executionId });
309
+ const response = await this.request('executions.status', this.config.endpoints.executions.status, { id: executionId }, {}, 'get execution status');
156
310
  if (!response.success || !response.data) {
157
- throw new Error(response.error || 'Failed to get execution status');
311
+ throw new Error(response.error ?? 'Failed to get execution status');
158
312
  }
159
313
  return response.data;
160
314
  }
315
+ /**
316
+ * Cancel a running execution
317
+ */
161
318
  async cancelExecution(executionId) {
162
- const response = await this.request('executions.cancel', this.config.endpoints.executions.cancel, { id: executionId }, { method: 'POST' });
319
+ const response = await this.request('executions.cancel', this.config.endpoints.executions.cancel, { id: executionId }, { method: 'POST' }, 'cancel execution');
163
320
  if (!response.success) {
164
- throw new Error(response.error || 'Failed to cancel execution');
321
+ throw new Error(response.error ?? 'Failed to cancel execution');
165
322
  }
166
323
  }
324
+ /**
325
+ * Get execution logs
326
+ */
167
327
  async getExecutionLogs(executionId) {
168
- const response = await this.request('executions.logs', this.config.endpoints.executions.logs, { id: executionId });
328
+ const response = await this.request('executions.logs', this.config.endpoints.executions.logs, { id: executionId }, {}, 'get execution logs');
169
329
  if (!response.success || !response.data) {
170
- throw new Error(response.error || 'Failed to get execution logs');
330
+ throw new Error(response.error ?? 'Failed to get execution logs');
171
331
  }
172
332
  return response.data;
173
333
  }
334
+ // =========================================================================
174
335
  // Template API Methods
336
+ // =========================================================================
337
+ /**
338
+ * List available templates
339
+ */
175
340
  async listTemplates() {
176
- const response = await this.request('templates.list', this.config.endpoints.templates.list);
341
+ const response = await this.request('templates.list', this.config.endpoints.templates.list, undefined, {}, 'list templates');
177
342
  if (!response.success || !response.data) {
178
- throw new Error(response.error || 'Failed to list templates');
343
+ throw new Error(response.error ?? 'Failed to list templates');
179
344
  }
180
345
  return response.data;
181
346
  }
347
+ /**
348
+ * Get a template by ID
349
+ */
182
350
  async getTemplate(templateId) {
183
- const response = await this.request('templates.get', this.config.endpoints.templates.get, { id: templateId });
351
+ const response = await this.request('templates.get', this.config.endpoints.templates.get, { id: templateId }, {}, 'get template');
184
352
  if (!response.success || !response.data) {
185
- throw new Error(response.error || 'Failed to get template');
353
+ throw new Error(response.error ?? 'Failed to get template');
186
354
  }
187
355
  return response.data;
188
356
  }
357
+ // =========================================================================
189
358
  // System API Methods
359
+ // =========================================================================
360
+ /**
361
+ * Get system health status
362
+ */
190
363
  async getSystemHealth() {
191
- const response = await this.request('system.health', this.config.endpoints.system.health);
364
+ const response = await this.request('system.health', this.config.endpoints.system.health, undefined, {}, 'get system health');
192
365
  if (!response.success || !response.data) {
193
- throw new Error(response.error || 'Failed to get system health');
366
+ throw new Error(response.error ?? 'Failed to get system health');
194
367
  }
195
368
  return response.data;
196
369
  }
370
+ /**
371
+ * Get system configuration
372
+ */
197
373
  async getSystemConfig() {
198
- const response = await this.request('system.config', this.config.endpoints.system.config);
374
+ const response = await this.request('system.config', this.config.endpoints.system.config, undefined, {}, 'get system config');
199
375
  if (!response.success || !response.data) {
200
- throw new Error(response.error || 'Failed to get system config');
376
+ throw new Error(response.error ?? 'Failed to get system config');
201
377
  }
202
378
  return response.data;
203
379
  }
380
+ /**
381
+ * Get system version information
382
+ */
204
383
  async getSystemVersion() {
205
- const response = await this.request('system.version', this.config.endpoints.system.version);
384
+ const response = await this.request('system.version', this.config.endpoints.system.version, undefined, {}, 'get system version');
206
385
  if (!response.success || !response.data) {
207
- throw new Error(response.error || 'Failed to get system version');
386
+ throw new Error(response.error ?? 'Failed to get system version');
208
387
  }
209
388
  return response.data;
210
389
  }