@d34dman/flowdrop 0.0.16 → 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.
- package/README.md +64 -0
- package/dist/api/enhanced-client.d.ts +3 -3
- package/dist/api/enhanced-client.js +57 -57
- package/dist/components/FlowDropZone.svelte +4 -5
- package/dist/components/FlowDropZone.svelte.d.ts +1 -1
- package/dist/components/UniversalNode.svelte +94 -34
- package/dist/components/WorkflowEditor.svelte +63 -3
- package/dist/config/runtimeConfig.d.ts +2 -2
- package/dist/config/runtimeConfig.js +7 -7
- package/dist/helpers/workflowEditorHelper.d.ts +44 -4
- package/dist/helpers/workflowEditorHelper.js +161 -30
- package/dist/index.d.ts +16 -13
- package/dist/index.js +19 -8
- package/dist/registry/builtinNodes.d.ts +77 -0
- package/dist/registry/builtinNodes.js +181 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.js +10 -0
- package/dist/registry/nodeComponentRegistry.d.ts +307 -0
- package/dist/registry/nodeComponentRegistry.js +315 -0
- package/dist/registry/plugin.d.ts +215 -0
- package/dist/registry/plugin.js +249 -0
- package/dist/services/draftStorage.d.ts +1 -1
- package/dist/services/draftStorage.js +5 -5
- package/dist/stores/workflowStore.d.ts +2 -2
- package/dist/stores/workflowStore.js +16 -16
- package/dist/styles/base.css +15 -0
- package/dist/svelte-app.d.ts +6 -6
- package/dist/svelte-app.js +25 -25
- package/dist/types/auth.d.ts +2 -2
- package/dist/types/auth.js +7 -7
- package/dist/types/events.d.ts +2 -2
- package/dist/types/index.d.ts +38 -3
- package/dist/utils/nodeTypes.d.ts +76 -21
- package/dist/utils/nodeTypes.js +180 -32
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,6 +14,14 @@ A visual workflow editor component library built with Svelte 5 and @xyflow/svelt
|
|
|
14
14
|
- **TypeScript Support**: Full type definitions included
|
|
15
15
|
- **Docker Ready**: Production-ready Dockerfile and Docker Compose configuration
|
|
16
16
|
|
|
17
|
+
### Enterprise Features (v0.0.16+)
|
|
18
|
+
|
|
19
|
+
- **Pluggable Authentication**: AuthProvider interface for OAuth, JWT, SSO integration
|
|
20
|
+
- **Workflow Lifecycle Events**: Hooks for save, load, change, and unmount events
|
|
21
|
+
- **Dirty State Tracking**: Monitor unsaved changes with reactive stores
|
|
22
|
+
- **Draft Auto-Save**: Automatic localStorage drafts with configurable intervals
|
|
23
|
+
- **Feature Flags**: Customize behavior for security and UX requirements
|
|
24
|
+
|
|
17
25
|
## 📦 Installation
|
|
18
26
|
|
|
19
27
|
```bash
|
|
@@ -117,6 +125,52 @@ const editor = mountWorkflowEditor(container, {
|
|
|
117
125
|
editor.destroy();
|
|
118
126
|
```
|
|
119
127
|
|
|
128
|
+
#### 3. Enterprise Integration (v0.0.16+)
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
import { mountFlowDropApp, createEndpointConfig, CallbackAuthProvider } from '@d34dman/flowdrop';
|
|
132
|
+
|
|
133
|
+
const app = await mountFlowDropApp(container, {
|
|
134
|
+
workflow: myWorkflow,
|
|
135
|
+
endpointConfig: createEndpointConfig('/api/flowdrop'),
|
|
136
|
+
|
|
137
|
+
// Dynamic authentication with token refresh
|
|
138
|
+
authProvider: new CallbackAuthProvider({
|
|
139
|
+
getToken: () => authService.getAccessToken(),
|
|
140
|
+
onUnauthorized: () => authService.refreshToken()
|
|
141
|
+
}),
|
|
142
|
+
|
|
143
|
+
// Workflow lifecycle hooks
|
|
144
|
+
eventHandlers: {
|
|
145
|
+
onDirtyStateChange: (isDirty) => updateSaveButton(isDirty),
|
|
146
|
+
onAfterSave: (workflow) => showSuccess('Saved!'),
|
|
147
|
+
onBeforeUnmount: (workflow, isDirty) => {
|
|
148
|
+
if (isDirty) saveDraft(workflow);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// Feature configuration
|
|
153
|
+
features: {
|
|
154
|
+
autoSaveDraft: true,
|
|
155
|
+
autoSaveDraftInterval: 30000,
|
|
156
|
+
showToasts: true
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Access instance methods
|
|
161
|
+
if (app.isDirty()) {
|
|
162
|
+
await app.save();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Get current workflow data
|
|
166
|
+
const workflow = app.getWorkflow();
|
|
167
|
+
|
|
168
|
+
// Cleanup
|
|
169
|
+
app.destroy();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
See the [Enterprise Integration Guide](./docs/enterprise-integration.md) for React, Vue, Angular, and Drupal examples.
|
|
173
|
+
|
|
120
174
|
#### 3. Integration with Backend Frameworks
|
|
121
175
|
|
|
122
176
|
##### Drupal Example
|
|
@@ -313,6 +367,13 @@ npm run format
|
|
|
313
367
|
- **CHANGELOG.md** - Version history
|
|
314
368
|
- **Storybook** - Component documentation (run `npm run storybook`)
|
|
315
369
|
|
|
370
|
+
### Enterprise Features (v0.0.16+)
|
|
371
|
+
|
|
372
|
+
- **[Enterprise Integration Guide](./docs/enterprise-integration.md)** - Complete integration patterns for React, Vue, Angular, Drupal
|
|
373
|
+
- **[Authentication Guide](./docs/authentication-guide.md)** - OAuth, JWT, SSO, and custom auth providers
|
|
374
|
+
- **[Event Handlers](./docs/event-handlers.md)** - Workflow lifecycle events and hooks
|
|
375
|
+
- **[Features Configuration](./docs/features-configuration.md)** - Feature flags, draft auto-save, and customization
|
|
376
|
+
|
|
316
377
|
## 🤝 Contributing
|
|
317
378
|
|
|
318
379
|
Not accepting contributions until the library stabilizes. Stay tuned.
|
|
@@ -347,6 +408,7 @@ docker-compose up -d
|
|
|
347
408
|
### Environment Variables
|
|
348
409
|
|
|
349
410
|
**Production (Runtime):**
|
|
411
|
+
|
|
350
412
|
- `FLOWDROP_API_BASE_URL` - Backend API URL
|
|
351
413
|
- `FLOWDROP_THEME` - UI theme (light/dark/auto)
|
|
352
414
|
- `FLOWDROP_TIMEOUT` - Request timeout in milliseconds
|
|
@@ -354,6 +416,7 @@ docker-compose up -d
|
|
|
354
416
|
- `FLOWDROP_AUTH_TOKEN` - Authentication token
|
|
355
417
|
|
|
356
418
|
**Development (Build-time):**
|
|
419
|
+
|
|
357
420
|
- `VITE_API_BASE_URL` - Dev API URL (used only during `npm run dev`)
|
|
358
421
|
|
|
359
422
|
### Build for Production
|
|
@@ -371,6 +434,7 @@ node build
|
|
|
371
434
|
```
|
|
372
435
|
|
|
373
436
|
For detailed deployment instructions, see:
|
|
437
|
+
|
|
374
438
|
- [DOCKER.md](./DOCKER.md) - Docker quick start
|
|
375
439
|
|
|
376
440
|
---
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module api/enhanced-client
|
|
7
7
|
*/
|
|
8
|
-
import type { NodeMetadata, Workflow, ExecutionResult } from
|
|
9
|
-
import type { EndpointConfig } from
|
|
10
|
-
import type { AuthProvider } from
|
|
8
|
+
import type { NodeMetadata, Workflow, ExecutionResult } from '../types/index.js';
|
|
9
|
+
import type { EndpointConfig } from '../config/endpoints.js';
|
|
10
|
+
import type { AuthProvider } from '../types/auth.js';
|
|
11
11
|
/**
|
|
12
12
|
* API error with additional context
|
|
13
13
|
*/
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module api/enhanced-client
|
|
7
7
|
*/
|
|
8
|
-
import { buildEndpointUrl, getEndpointMethod, getEndpointHeaders } from
|
|
9
|
-
import { createAuthProviderFromLegacyConfig } from
|
|
8
|
+
import { buildEndpointUrl, getEndpointMethod, getEndpointHeaders } from '../config/endpoints.js';
|
|
9
|
+
import { createAuthProviderFromLegacyConfig } from '../types/auth.js';
|
|
10
10
|
/**
|
|
11
11
|
* API error with additional context
|
|
12
12
|
*/
|
|
@@ -19,7 +19,7 @@ export class ApiError extends Error {
|
|
|
19
19
|
operation;
|
|
20
20
|
constructor(message, status, operation, errorData = {}) {
|
|
21
21
|
super(message);
|
|
22
|
-
this.name =
|
|
22
|
+
this.name = 'ApiError';
|
|
23
23
|
this.status = status;
|
|
24
24
|
this.operation = operation;
|
|
25
25
|
this.errorData = errorData;
|
|
@@ -62,7 +62,7 @@ export class EnhancedFlowDropApiClient {
|
|
|
62
62
|
* @param options - Additional fetch options
|
|
63
63
|
* @param operation - Description of the operation (for error messages)
|
|
64
64
|
*/
|
|
65
|
-
async request(endpointKey, endpointPath, params, options = {}, operation =
|
|
65
|
+
async request(endpointKey, endpointPath, params, options = {}, operation = 'API request') {
|
|
66
66
|
const url = buildEndpointUrl(this.config, endpointPath, params);
|
|
67
67
|
const method = options.method ?? getEndpointMethod(this.config, endpointKey);
|
|
68
68
|
const configHeaders = getEndpointHeaders(this.config, endpointKey);
|
|
@@ -99,14 +99,14 @@ export class EnhancedFlowDropApiClient {
|
|
|
99
99
|
continue; // Retry with new headers
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
throw new ApiError(
|
|
102
|
+
throw new ApiError('Unauthorized', 401, operation, {});
|
|
103
103
|
}
|
|
104
104
|
// Handle 403 Forbidden
|
|
105
105
|
if (response.status === 403) {
|
|
106
106
|
if (this.authProvider.onForbidden) {
|
|
107
107
|
await this.authProvider.onForbidden();
|
|
108
108
|
}
|
|
109
|
-
throw new ApiError(
|
|
109
|
+
throw new ApiError('Forbidden', 403, operation, {});
|
|
110
110
|
}
|
|
111
111
|
// Handle other errors
|
|
112
112
|
if (!response.ok) {
|
|
@@ -133,7 +133,7 @@ export class EnhancedFlowDropApiClient {
|
|
|
133
133
|
}
|
|
134
134
|
// Wait before retry
|
|
135
135
|
const delay = this.config.retry?.delay ?? 1000;
|
|
136
|
-
const backoffDelay = this.config.retry?.backoff ===
|
|
136
|
+
const backoffDelay = this.config.retry?.backoff === 'exponential' ? delay * Math.pow(2, attempt - 1) : delay;
|
|
137
137
|
await new Promise((resolve) => setTimeout(resolve, backoffDelay));
|
|
138
138
|
}
|
|
139
139
|
}
|
|
@@ -166,9 +166,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
166
166
|
* Fetch all available node types
|
|
167
167
|
*/
|
|
168
168
|
async getAvailableNodes() {
|
|
169
|
-
const response = await this.request(
|
|
169
|
+
const response = await this.request('nodes.list', this.config.endpoints.nodes.list, undefined, {}, 'fetch available nodes');
|
|
170
170
|
if (!response.success || !response.data) {
|
|
171
|
-
throw new Error(response.error ??
|
|
171
|
+
throw new Error(response.error ?? 'Failed to fetch available nodes');
|
|
172
172
|
}
|
|
173
173
|
return response.data;
|
|
174
174
|
}
|
|
@@ -176,9 +176,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
176
176
|
* Fetch nodes filtered by category
|
|
177
177
|
*/
|
|
178
178
|
async getNodesByCategory(category) {
|
|
179
|
-
const response = await this.request(
|
|
179
|
+
const response = await this.request('nodes.byCategory', this.config.endpoints.nodes.byCategory, { category }, {}, 'fetch nodes by category');
|
|
180
180
|
if (!response.success || !response.data) {
|
|
181
|
-
throw new Error(response.error ??
|
|
181
|
+
throw new Error(response.error ?? 'Failed to fetch nodes by category');
|
|
182
182
|
}
|
|
183
183
|
return response.data;
|
|
184
184
|
}
|
|
@@ -186,9 +186,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
186
186
|
* Fetch metadata for a specific node type
|
|
187
187
|
*/
|
|
188
188
|
async getNodeMetadata(nodeId) {
|
|
189
|
-
const response = await this.request(
|
|
189
|
+
const response = await this.request('nodes.metadata', this.config.endpoints.nodes.metadata, { id: nodeId }, {}, 'fetch node metadata');
|
|
190
190
|
if (!response.success || !response.data) {
|
|
191
|
-
throw new Error(response.error ??
|
|
191
|
+
throw new Error(response.error ?? 'Failed to fetch node metadata');
|
|
192
192
|
}
|
|
193
193
|
return response.data;
|
|
194
194
|
}
|
|
@@ -199,12 +199,12 @@ export class EnhancedFlowDropApiClient {
|
|
|
199
199
|
* Save a new workflow
|
|
200
200
|
*/
|
|
201
201
|
async saveWorkflow(workflow) {
|
|
202
|
-
const response = await this.request(
|
|
203
|
-
method:
|
|
202
|
+
const response = await this.request('workflows.create', this.config.endpoints.workflows.create, undefined, {
|
|
203
|
+
method: 'POST',
|
|
204
204
|
body: JSON.stringify(workflow)
|
|
205
|
-
},
|
|
205
|
+
}, 'save workflow');
|
|
206
206
|
if (!response.success || !response.data) {
|
|
207
|
-
throw new Error(response.error ??
|
|
207
|
+
throw new Error(response.error ?? 'Failed to save workflow');
|
|
208
208
|
}
|
|
209
209
|
return response.data;
|
|
210
210
|
}
|
|
@@ -212,12 +212,12 @@ export class EnhancedFlowDropApiClient {
|
|
|
212
212
|
* Update an existing workflow
|
|
213
213
|
*/
|
|
214
214
|
async updateWorkflow(workflowId, workflow) {
|
|
215
|
-
const response = await this.request(
|
|
216
|
-
method:
|
|
215
|
+
const response = await this.request('workflows.update', this.config.endpoints.workflows.update, { id: workflowId }, {
|
|
216
|
+
method: 'PUT',
|
|
217
217
|
body: JSON.stringify(workflow)
|
|
218
|
-
},
|
|
218
|
+
}, 'update workflow');
|
|
219
219
|
if (!response.success || !response.data) {
|
|
220
|
-
throw new Error(response.error ??
|
|
220
|
+
throw new Error(response.error ?? 'Failed to update workflow');
|
|
221
221
|
}
|
|
222
222
|
return response.data;
|
|
223
223
|
}
|
|
@@ -225,9 +225,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
225
225
|
* Load a workflow by ID
|
|
226
226
|
*/
|
|
227
227
|
async loadWorkflow(workflowId) {
|
|
228
|
-
const response = await this.request(
|
|
228
|
+
const response = await this.request('workflows.get', this.config.endpoints.workflows.get, { id: workflowId }, {}, 'load workflow');
|
|
229
229
|
if (!response.success || !response.data) {
|
|
230
|
-
throw new Error(response.error ??
|
|
230
|
+
throw new Error(response.error ?? 'Failed to load workflow');
|
|
231
231
|
}
|
|
232
232
|
return response.data;
|
|
233
233
|
}
|
|
@@ -235,9 +235,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
235
235
|
* List all workflows
|
|
236
236
|
*/
|
|
237
237
|
async listWorkflows() {
|
|
238
|
-
const response = await this.request(
|
|
238
|
+
const response = await this.request('workflows.list', this.config.endpoints.workflows.list, undefined, {}, 'list workflows');
|
|
239
239
|
if (!response.success || !response.data) {
|
|
240
|
-
throw new Error(response.error ??
|
|
240
|
+
throw new Error(response.error ?? 'Failed to list workflows');
|
|
241
241
|
}
|
|
242
242
|
return response.data;
|
|
243
243
|
}
|
|
@@ -245,21 +245,21 @@ export class EnhancedFlowDropApiClient {
|
|
|
245
245
|
* Delete a workflow
|
|
246
246
|
*/
|
|
247
247
|
async deleteWorkflow(workflowId) {
|
|
248
|
-
const response = await this.request(
|
|
248
|
+
const response = await this.request('workflows.delete', this.config.endpoints.workflows.delete, { id: workflowId }, { method: 'DELETE' }, 'delete workflow');
|
|
249
249
|
if (!response.success) {
|
|
250
|
-
throw new Error(response.error ??
|
|
250
|
+
throw new Error(response.error ?? 'Failed to delete workflow');
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
/**
|
|
254
254
|
* Validate a workflow
|
|
255
255
|
*/
|
|
256
256
|
async validateWorkflow(workflow) {
|
|
257
|
-
const response = await this.request(
|
|
258
|
-
method:
|
|
257
|
+
const response = await this.request('workflows.validate', this.config.endpoints.workflows.validate, undefined, {
|
|
258
|
+
method: 'POST',
|
|
259
259
|
body: JSON.stringify(workflow)
|
|
260
|
-
},
|
|
260
|
+
}, 'validate workflow');
|
|
261
261
|
if (!response.success || !response.data) {
|
|
262
|
-
throw new Error(response.error ??
|
|
262
|
+
throw new Error(response.error ?? 'Failed to validate workflow');
|
|
263
263
|
}
|
|
264
264
|
return response.data;
|
|
265
265
|
}
|
|
@@ -267,9 +267,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
267
267
|
* Export a workflow as JSON string
|
|
268
268
|
*/
|
|
269
269
|
async exportWorkflow(workflowId) {
|
|
270
|
-
const response = await this.request(
|
|
270
|
+
const response = await this.request('workflows.export', this.config.endpoints.workflows.export, { id: workflowId }, {}, 'export workflow');
|
|
271
271
|
if (!response.success || !response.data) {
|
|
272
|
-
throw new Error(response.error ??
|
|
272
|
+
throw new Error(response.error ?? 'Failed to export workflow');
|
|
273
273
|
}
|
|
274
274
|
return response.data;
|
|
275
275
|
}
|
|
@@ -277,12 +277,12 @@ export class EnhancedFlowDropApiClient {
|
|
|
277
277
|
* Import a workflow from JSON
|
|
278
278
|
*/
|
|
279
279
|
async importWorkflow(workflowJson) {
|
|
280
|
-
const response = await this.request(
|
|
281
|
-
method:
|
|
280
|
+
const response = await this.request('workflows.import', this.config.endpoints.workflows.import, undefined, {
|
|
281
|
+
method: 'POST',
|
|
282
282
|
body: JSON.stringify({ workflow: workflowJson })
|
|
283
|
-
},
|
|
283
|
+
}, 'import workflow');
|
|
284
284
|
if (!response.success || !response.data) {
|
|
285
|
-
throw new Error(response.error ??
|
|
285
|
+
throw new Error(response.error ?? 'Failed to import workflow');
|
|
286
286
|
}
|
|
287
287
|
return response.data;
|
|
288
288
|
}
|
|
@@ -293,12 +293,12 @@ export class EnhancedFlowDropApiClient {
|
|
|
293
293
|
* Execute a workflow
|
|
294
294
|
*/
|
|
295
295
|
async executeWorkflow(workflowId, inputs) {
|
|
296
|
-
const response = await this.request(
|
|
297
|
-
method:
|
|
296
|
+
const response = await this.request('executions.execute', this.config.endpoints.executions.execute, { id: workflowId }, {
|
|
297
|
+
method: 'POST',
|
|
298
298
|
body: JSON.stringify({ inputs })
|
|
299
|
-
},
|
|
299
|
+
}, 'execute workflow');
|
|
300
300
|
if (!response.success || !response.data) {
|
|
301
|
-
throw new Error(response.error ??
|
|
301
|
+
throw new Error(response.error ?? 'Failed to execute workflow');
|
|
302
302
|
}
|
|
303
303
|
return response.data;
|
|
304
304
|
}
|
|
@@ -306,9 +306,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
306
306
|
* Get execution status
|
|
307
307
|
*/
|
|
308
308
|
async getExecutionStatus(executionId) {
|
|
309
|
-
const response = await this.request(
|
|
309
|
+
const response = await this.request('executions.status', this.config.endpoints.executions.status, { id: executionId }, {}, 'get execution status');
|
|
310
310
|
if (!response.success || !response.data) {
|
|
311
|
-
throw new Error(response.error ??
|
|
311
|
+
throw new Error(response.error ?? 'Failed to get execution status');
|
|
312
312
|
}
|
|
313
313
|
return response.data;
|
|
314
314
|
}
|
|
@@ -316,18 +316,18 @@ export class EnhancedFlowDropApiClient {
|
|
|
316
316
|
* Cancel a running execution
|
|
317
317
|
*/
|
|
318
318
|
async cancelExecution(executionId) {
|
|
319
|
-
const response = await this.request(
|
|
319
|
+
const response = await this.request('executions.cancel', this.config.endpoints.executions.cancel, { id: executionId }, { method: 'POST' }, 'cancel execution');
|
|
320
320
|
if (!response.success) {
|
|
321
|
-
throw new Error(response.error ??
|
|
321
|
+
throw new Error(response.error ?? 'Failed to cancel execution');
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
/**
|
|
325
325
|
* Get execution logs
|
|
326
326
|
*/
|
|
327
327
|
async getExecutionLogs(executionId) {
|
|
328
|
-
const response = await this.request(
|
|
328
|
+
const response = await this.request('executions.logs', this.config.endpoints.executions.logs, { id: executionId }, {}, 'get execution logs');
|
|
329
329
|
if (!response.success || !response.data) {
|
|
330
|
-
throw new Error(response.error ??
|
|
330
|
+
throw new Error(response.error ?? 'Failed to get execution logs');
|
|
331
331
|
}
|
|
332
332
|
return response.data;
|
|
333
333
|
}
|
|
@@ -338,9 +338,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
338
338
|
* List available templates
|
|
339
339
|
*/
|
|
340
340
|
async listTemplates() {
|
|
341
|
-
const response = await this.request(
|
|
341
|
+
const response = await this.request('templates.list', this.config.endpoints.templates.list, undefined, {}, 'list templates');
|
|
342
342
|
if (!response.success || !response.data) {
|
|
343
|
-
throw new Error(response.error ??
|
|
343
|
+
throw new Error(response.error ?? 'Failed to list templates');
|
|
344
344
|
}
|
|
345
345
|
return response.data;
|
|
346
346
|
}
|
|
@@ -348,9 +348,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
348
348
|
* Get a template by ID
|
|
349
349
|
*/
|
|
350
350
|
async getTemplate(templateId) {
|
|
351
|
-
const response = await this.request(
|
|
351
|
+
const response = await this.request('templates.get', this.config.endpoints.templates.get, { id: templateId }, {}, 'get template');
|
|
352
352
|
if (!response.success || !response.data) {
|
|
353
|
-
throw new Error(response.error ??
|
|
353
|
+
throw new Error(response.error ?? 'Failed to get template');
|
|
354
354
|
}
|
|
355
355
|
return response.data;
|
|
356
356
|
}
|
|
@@ -361,9 +361,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
361
361
|
* Get system health status
|
|
362
362
|
*/
|
|
363
363
|
async getSystemHealth() {
|
|
364
|
-
const response = await this.request(
|
|
364
|
+
const response = await this.request('system.health', this.config.endpoints.system.health, undefined, {}, 'get system health');
|
|
365
365
|
if (!response.success || !response.data) {
|
|
366
|
-
throw new Error(response.error ??
|
|
366
|
+
throw new Error(response.error ?? 'Failed to get system health');
|
|
367
367
|
}
|
|
368
368
|
return response.data;
|
|
369
369
|
}
|
|
@@ -371,9 +371,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
371
371
|
* Get system configuration
|
|
372
372
|
*/
|
|
373
373
|
async getSystemConfig() {
|
|
374
|
-
const response = await this.request(
|
|
374
|
+
const response = await this.request('system.config', this.config.endpoints.system.config, undefined, {}, 'get system config');
|
|
375
375
|
if (!response.success || !response.data) {
|
|
376
|
-
throw new Error(response.error ??
|
|
376
|
+
throw new Error(response.error ?? 'Failed to get system config');
|
|
377
377
|
}
|
|
378
378
|
return response.data;
|
|
379
379
|
}
|
|
@@ -381,9 +381,9 @@ export class EnhancedFlowDropApiClient {
|
|
|
381
381
|
* Get system version information
|
|
382
382
|
*/
|
|
383
383
|
async getSystemVersion() {
|
|
384
|
-
const response = await this.request(
|
|
384
|
+
const response = await this.request('system.version', this.config.endpoints.system.version, undefined, {}, 'get system version');
|
|
385
385
|
if (!response.success || !response.data) {
|
|
386
|
-
throw new Error(response.error ??
|
|
386
|
+
throw new Error(response.error ?? 'Failed to get system version');
|
|
387
387
|
}
|
|
388
388
|
return response.data;
|
|
389
389
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
-->
|
|
6
6
|
|
|
7
7
|
<script lang="ts">
|
|
8
|
-
import { useSvelteFlow } from
|
|
9
|
-
import type { Snippet } from
|
|
8
|
+
import { useSvelteFlow } from '@xyflow/svelte';
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
12
|
ondrop: (nodeTypeData: string, position: { x: number; y: number }) => void;
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
function handleDragOver(e: DragEvent): void {
|
|
25
25
|
e.preventDefault();
|
|
26
26
|
if (e.dataTransfer) {
|
|
27
|
-
e.dataTransfer.dropEffect =
|
|
27
|
+
e.dataTransfer.dropEffect = 'copy';
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
e.preventDefault();
|
|
36
36
|
|
|
37
37
|
// Get the data from the drag event
|
|
38
|
-
const nodeTypeData = e.dataTransfer?.getData(
|
|
38
|
+
const nodeTypeData = e.dataTransfer?.getData('application/json');
|
|
39
39
|
if (nodeTypeData) {
|
|
40
40
|
// Convert screen coordinates to flow coordinates (accounts for zoom and pan)
|
|
41
41
|
const position = screenToFlowPosition({
|
|
@@ -65,4 +65,3 @@
|
|
|
65
65
|
height: 100%;
|
|
66
66
|
}
|
|
67
67
|
</style>
|
|
68
|
-
|
|
@@ -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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
164
|
+
nodeId={data.nodeId ?? 'unknown'}
|
|
105
165
|
{executionInfo}
|
|
106
166
|
position={getStatusPosition()}
|
|
107
167
|
size={getStatusSize()}
|