@d34dman/flowdrop 0.0.15 → 0.0.16

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.
@@ -1,27 +1,53 @@
1
1
  /**
2
2
  * Svelte App Wrapper for Framework Integration
3
- * Provides mount/unmount functions for integrating FlowDrop into any web application
4
- * Particularly useful for integration with vanilla JS, Drupal, WordPress, or other frameworks
3
+ *
4
+ * Provides mount/unmount functions for integrating FlowDrop into any web application.
5
+ * Particularly useful for integration with vanilla JS, Drupal, WordPress, or other frameworks.
6
+ *
7
+ * @module svelte-app
5
8
  */
6
- import { mount } from 'svelte';
7
- import WorkflowEditor from './components/WorkflowEditor.svelte';
8
- import App from './components/App.svelte';
9
- import { initializePortCompatibility } from './utils/connections.js';
10
- import { DEFAULT_PORT_CONFIG } from './config/defaultPortConfig.js';
11
- import { fetchPortConfig } from './services/portConfigApi.js';
9
+ import { mount, unmount } from "svelte";
10
+ import WorkflowEditor from "./components/WorkflowEditor.svelte";
11
+ import App from "./components/App.svelte";
12
+ import { initializePortCompatibility } from "./utils/connections.js";
13
+ import { DEFAULT_PORT_CONFIG } from "./config/defaultPortConfig.js";
14
+ import { fetchPortConfig } from "./services/portConfigApi.js";
15
+ import { isDirty, markAsSaved, getWorkflow as getWorkflowFromStore, setOnDirtyStateChange, setOnWorkflowChange } from "./stores/workflowStore.js";
16
+ import { DraftAutoSaveManager, getDraftStorageKey } from "./services/draftStorage.js";
17
+ import { mergeFeatures } from "./types/events.js";
12
18
  /**
13
19
  * Mount the full FlowDrop App with navbar, sidebars, and workflow editor
14
- * Use this for a complete workflow editing experience with all UI components
20
+ *
21
+ * Use this for a complete workflow editing experience with all UI components.
22
+ *
15
23
  * @param container - DOM element to mount the app into
16
24
  * @param options - Configuration options for the app
25
+ * @returns Promise resolving to a MountedFlowDropApp instance
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const app = await mountFlowDropApp(document.getElementById("editor"), {
30
+ * workflow: myWorkflow,
31
+ * endpointConfig: createEndpointConfig("/api/flowdrop"),
32
+ * authProvider: new CallbackAuthProvider({
33
+ * getToken: () => authService.getAccessToken()
34
+ * }),
35
+ * eventHandlers: {
36
+ * onDirtyStateChange: (isDirty) => updateSaveButton(isDirty),
37
+ * onAfterSave: () => showSuccess("Saved!")
38
+ * }
39
+ * });
40
+ * ```
17
41
  */
18
42
  export async function mountFlowDropApp(container, options = {}) {
19
- const { workflow, endpointConfig, portConfig, height = '100vh', width = '100%', showNavbar = false, disableSidebar, lockWorkflow, readOnly, nodeStatuses, pipelineId, navbarTitle, navbarActions } = options;
43
+ const { workflow, nodes, endpointConfig, portConfig, height = "100vh", width = "100%", showNavbar = false, disableSidebar, lockWorkflow, readOnly, nodeStatuses, pipelineId, navbarTitle, navbarActions, authProvider, eventHandlers, features: userFeatures, draftStorageKey: customDraftKey } = options;
44
+ // Merge features with defaults
45
+ const features = mergeFeatures(userFeatures);
20
46
  // Create endpoint configuration
21
47
  let config;
22
48
  if (endpointConfig) {
23
49
  // Merge with default configuration to ensure all required endpoints are present
24
- const { defaultEndpointConfig } = await import('./config/endpoints.js');
50
+ const { defaultEndpointConfig } = await import("./config/endpoints.js");
25
51
  config = {
26
52
  ...defaultEndpointConfig,
27
53
  ...endpointConfig,
@@ -33,7 +59,7 @@ export async function mountFlowDropApp(container, options = {}) {
33
59
  }
34
60
  else {
35
61
  // Use default configuration if none provided
36
- const { defaultEndpointConfig } = await import('./config/endpoints.js');
62
+ const { defaultEndpointConfig } = await import("./config/endpoints.js");
37
63
  config = defaultEndpointConfig;
38
64
  }
39
65
  // Initialize port configuration
@@ -44,7 +70,7 @@ export async function mountFlowDropApp(container, options = {}) {
44
70
  finalPortConfig = await fetchPortConfig(config);
45
71
  }
46
72
  catch (error) {
47
- console.warn('Failed to fetch port config from API, using default:', error);
73
+ console.warn("Failed to fetch port config from API, using default:", error);
48
74
  finalPortConfig = DEFAULT_PORT_CONFIG;
49
75
  }
50
76
  }
@@ -52,11 +78,19 @@ export async function mountFlowDropApp(container, options = {}) {
52
78
  finalPortConfig = DEFAULT_PORT_CONFIG;
53
79
  }
54
80
  initializePortCompatibility(finalPortConfig);
81
+ // Set up event handler callbacks in the store
82
+ if (eventHandlers?.onDirtyStateChange) {
83
+ setOnDirtyStateChange(eventHandlers.onDirtyStateChange);
84
+ }
85
+ if (eventHandlers?.onWorkflowChange) {
86
+ setOnWorkflowChange(eventHandlers.onWorkflowChange);
87
+ }
55
88
  // Create the Svelte App component with configuration
56
- const app = mount(App, {
89
+ const svelteApp = mount(App, {
57
90
  target: container,
58
91
  props: {
59
92
  workflow,
93
+ nodes,
60
94
  height,
61
95
  width,
62
96
  showNavbar,
@@ -67,31 +101,91 @@ export async function mountFlowDropApp(container, options = {}) {
67
101
  pipelineId,
68
102
  navbarTitle,
69
103
  navbarActions,
70
- endpointConfig: config
104
+ endpointConfig: config,
105
+ authProvider,
106
+ eventHandlers,
107
+ features
71
108
  }
72
109
  });
73
- // Expose save and export functionality
74
- app.save = async () => {
75
- if (typeof window !== 'undefined' && window.flowdropSave) {
76
- await window.flowdropSave();
77
- }
78
- else {
79
- console.warn('⚠️ Save functionality not available');
80
- }
110
+ // Set up draft auto-save manager
111
+ let draftManager = null;
112
+ if (features.autoSaveDraft) {
113
+ const storageKey = getDraftStorageKey(workflow?.id, customDraftKey);
114
+ draftManager = new DraftAutoSaveManager({
115
+ storageKey,
116
+ interval: features.autoSaveDraftInterval,
117
+ enabled: features.autoSaveDraft,
118
+ getWorkflow: getWorkflowFromStore,
119
+ isDirty
120
+ });
121
+ draftManager.start();
122
+ }
123
+ // Store state for cleanup
124
+ const state = {
125
+ svelteApp,
126
+ draftManager,
127
+ eventHandlers: eventHandlers ?? null
81
128
  };
82
- app.export = () => {
83
- if (typeof window !== 'undefined' && window.flowdropExport) {
84
- window.flowdropExport();
85
- }
86
- else {
87
- console.warn('⚠️ Export functionality not available');
129
+ // Create the mounted app interface
130
+ const mountedApp = {
131
+ destroy: () => {
132
+ // Call onBeforeUnmount if provided
133
+ if (state.eventHandlers?.onBeforeUnmount) {
134
+ const currentWorkflow = getWorkflowFromStore();
135
+ if (currentWorkflow) {
136
+ state.eventHandlers.onBeforeUnmount(currentWorkflow, isDirty());
137
+ }
138
+ }
139
+ // Stop draft manager
140
+ if (state.draftManager) {
141
+ // Save one final draft if dirty
142
+ if (isDirty()) {
143
+ state.draftManager.forceSave();
144
+ }
145
+ state.draftManager.stop();
146
+ }
147
+ // Clear event callbacks
148
+ setOnDirtyStateChange(null);
149
+ setOnWorkflowChange(null);
150
+ // Unmount Svelte app
151
+ unmount(state.svelteApp);
152
+ },
153
+ isDirty: () => isDirty(),
154
+ markAsSaved: () => {
155
+ markAsSaved();
156
+ // Also update draft manager
157
+ if (state.draftManager) {
158
+ state.draftManager.markAsSaved();
159
+ }
160
+ },
161
+ getWorkflow: () => getWorkflowFromStore(),
162
+ save: async () => {
163
+ if (typeof window !== "undefined" && window.flowdropSave) {
164
+ await window.flowdropSave();
165
+ }
166
+ else {
167
+ console.warn("⚠️ Save functionality not available");
168
+ }
169
+ },
170
+ export: () => {
171
+ if (typeof window !== "undefined" && window.flowdropExport) {
172
+ window.flowdropExport();
173
+ }
174
+ else {
175
+ console.warn("⚠️ Export functionality not available");
176
+ }
88
177
  }
89
178
  };
90
- return app;
179
+ return mountedApp;
91
180
  }
92
181
  /**
93
182
  * Mount the WorkflowEditor component in a container
94
- * Simpler alternative to mountFlowDropApp - only mounts the editor without navbar
183
+ *
184
+ * Simpler alternative to mountFlowDropApp - only mounts the editor without navbar.
185
+ *
186
+ * @param container - DOM element to mount the editor into
187
+ * @param options - Configuration options
188
+ * @returns Promise resolving to a MountedFlowDropApp instance
95
189
  */
96
190
  export async function mountWorkflowEditor(container, options = {}) {
97
191
  const { nodes = [], endpointConfig, portConfig } = options;
@@ -99,7 +193,7 @@ export async function mountWorkflowEditor(container, options = {}) {
99
193
  let config;
100
194
  if (endpointConfig) {
101
195
  // Merge with default configuration to ensure all required endpoints are present
102
- const { defaultEndpointConfig } = await import('./config/endpoints.js');
196
+ const { defaultEndpointConfig } = await import("./config/endpoints.js");
103
197
  config = {
104
198
  ...defaultEndpointConfig,
105
199
  ...endpointConfig,
@@ -111,7 +205,7 @@ export async function mountWorkflowEditor(container, options = {}) {
111
205
  }
112
206
  else {
113
207
  // Use default configuration if none provided
114
- const { defaultEndpointConfig } = await import('./config/endpoints.js');
208
+ const { defaultEndpointConfig } = await import("./config/endpoints.js");
115
209
  config = defaultEndpointConfig;
116
210
  }
117
211
  // Initialize port configuration
@@ -122,7 +216,7 @@ export async function mountWorkflowEditor(container, options = {}) {
122
216
  finalPortConfig = await fetchPortConfig(config);
123
217
  }
124
218
  catch (error) {
125
- console.warn('Failed to fetch port config from API, using default:', error);
219
+ console.warn("Failed to fetch port config from API, using default:", error);
126
220
  finalPortConfig = DEFAULT_PORT_CONFIG;
127
221
  }
128
222
  }
@@ -131,25 +225,54 @@ export async function mountWorkflowEditor(container, options = {}) {
131
225
  }
132
226
  initializePortCompatibility(finalPortConfig);
133
227
  // Create the Svelte component
134
- const app = mount(WorkflowEditor, {
228
+ const svelteApp = mount(WorkflowEditor, {
135
229
  target: container,
136
230
  props: {
137
231
  nodes,
138
232
  endpointConfig: config
139
233
  }
140
234
  });
141
- return app;
235
+ // Create the mounted app interface (simpler version)
236
+ const mountedApp = {
237
+ destroy: () => {
238
+ unmount(svelteApp);
239
+ },
240
+ isDirty: () => isDirty(),
241
+ markAsSaved: () => markAsSaved(),
242
+ getWorkflow: () => getWorkflowFromStore(),
243
+ save: async () => {
244
+ if (typeof window !== "undefined" && window.flowdropSave) {
245
+ await window.flowdropSave();
246
+ }
247
+ else {
248
+ console.warn("⚠️ Save functionality not available");
249
+ }
250
+ },
251
+ export: () => {
252
+ if (typeof window !== "undefined" && window.flowdropExport) {
253
+ window.flowdropExport();
254
+ }
255
+ else {
256
+ console.warn("⚠️ Export functionality not available");
257
+ }
258
+ }
259
+ };
260
+ return mountedApp;
142
261
  }
143
262
  /**
144
- * Unmount a Svelte app (works for both App and WorkflowEditor)
263
+ * Unmount a FlowDrop app
264
+ *
265
+ * @param app - The mounted app to unmount
145
266
  */
146
267
  export function unmountFlowDropApp(app) {
147
- if (app && typeof app.destroy === 'function') {
268
+ if (app && typeof app.destroy === "function") {
148
269
  app.destroy();
149
270
  }
150
271
  }
151
272
  /**
152
- * Unmount a Svelte app (alias for backward compatibility)
273
+ * Unmount a FlowDrop app (alias for backward compatibility)
274
+ *
275
+ * @param app - The mounted app to unmount
153
276
  */
154
277
  export function unmountWorkflowEditor(app) {
155
278
  unmountFlowDropApp(app);
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Authentication Provider Types for FlowDrop
3
+ *
4
+ * Provides interfaces and implementations for authentication in FlowDrop.
5
+ * AuthProvider is passed at mount time and cannot be changed without remounting.
6
+ *
7
+ * @module types/auth
8
+ */
9
+ /**
10
+ * Authentication provider interface
11
+ *
12
+ * Defines the contract for authentication providers that can be passed to FlowDrop.
13
+ * Implementations handle token retrieval, authentication state, and error handling.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const authProvider: AuthProvider = {
18
+ * getAuthHeaders: async () => ({ Authorization: "Bearer token123" }),
19
+ * isAuthenticated: () => true
20
+ * };
21
+ * ```
22
+ */
23
+ export interface AuthProvider {
24
+ /**
25
+ * Get current authentication headers
26
+ *
27
+ * Called before every API request to retrieve current auth headers.
28
+ * Should return fresh headers each time (e.g., after token refresh).
29
+ *
30
+ * @returns Promise resolving to a record of header name-value pairs
31
+ */
32
+ getAuthHeaders(): Promise<Record<string, string>>;
33
+ /**
34
+ * Check if currently authenticated
35
+ *
36
+ * Used to determine if API requests should be attempted.
37
+ * Should return synchronously for performance.
38
+ *
39
+ * @returns true if authenticated, false otherwise
40
+ */
41
+ isAuthenticated(): boolean;
42
+ /**
43
+ * Called when API returns 401 Unauthorized
44
+ *
45
+ * Allows the provider to attempt token refresh or re-authentication.
46
+ * If this returns true, the failed request will be retried with new headers.
47
+ *
48
+ * @returns Promise resolving to true if auth was refreshed and request should retry
49
+ */
50
+ onUnauthorized?(): Promise<boolean>;
51
+ /**
52
+ * Called when API returns 403 Forbidden
53
+ *
54
+ * Allows the provider to handle permission errors (e.g., show error UI).
55
+ * Unlike 401, this typically means the user is authenticated but lacks permission.
56
+ */
57
+ onForbidden?(): Promise<void>;
58
+ }
59
+ /**
60
+ * Configuration for static authentication
61
+ *
62
+ * Used to configure StaticAuthProvider with different auth types.
63
+ */
64
+ export interface StaticAuthConfig {
65
+ /** Authentication type */
66
+ type: "none" | "bearer" | "api_key" | "custom";
67
+ /** Bearer token (used when type is "bearer") */
68
+ token?: string;
69
+ /** API key (used when type is "api_key") */
70
+ apiKey?: string;
71
+ /** Custom headers (used when type is "custom") */
72
+ headers?: Record<string, string>;
73
+ }
74
+ /**
75
+ * Static authentication provider
76
+ *
77
+ * Provides authentication using static credentials configured at instantiation.
78
+ * Suitable for simple use cases where tokens don't change during the session.
79
+ * Also used internally for backward compatibility with existing endpointConfig.auth.
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // Bearer token authentication
84
+ * const authProvider = new StaticAuthProvider({
85
+ * type: "bearer",
86
+ * token: "your-jwt-token"
87
+ * });
88
+ *
89
+ * // API key authentication
90
+ * const authProvider = new StaticAuthProvider({
91
+ * type: "api_key",
92
+ * apiKey: "your-api-key"
93
+ * });
94
+ *
95
+ * // Custom headers
96
+ * const authProvider = new StaticAuthProvider({
97
+ * type: "custom",
98
+ * headers: {
99
+ * "X-Custom-Auth": "value",
100
+ * "X-Tenant-ID": "tenant123"
101
+ * }
102
+ * });
103
+ * ```
104
+ */
105
+ export declare class StaticAuthProvider implements AuthProvider {
106
+ /** Cached authentication headers */
107
+ private headers;
108
+ /**
109
+ * Create a new StaticAuthProvider
110
+ *
111
+ * @param config - Static authentication configuration
112
+ */
113
+ constructor(config: StaticAuthConfig);
114
+ /**
115
+ * Get authentication headers
116
+ *
117
+ * Returns the statically configured headers.
118
+ *
119
+ * @returns Promise resolving to authentication headers
120
+ */
121
+ getAuthHeaders(): Promise<Record<string, string>>;
122
+ /**
123
+ * Check if authenticated
124
+ *
125
+ * Returns true if any auth headers are configured.
126
+ *
127
+ * @returns true if headers are configured
128
+ */
129
+ isAuthenticated(): boolean;
130
+ /**
131
+ * Handle unauthorized response
132
+ *
133
+ * Static provider cannot refresh tokens, so always returns false.
134
+ *
135
+ * @returns Promise resolving to false (cannot refresh)
136
+ */
137
+ onUnauthorized(): Promise<boolean>;
138
+ /**
139
+ * Handle forbidden response
140
+ *
141
+ * Static provider has no special handling for 403.
142
+ */
143
+ onForbidden(): Promise<void>;
144
+ }
145
+ /**
146
+ * Configuration for callback-based authentication
147
+ *
148
+ * Used to configure CallbackAuthProvider with dynamic token retrieval.
149
+ */
150
+ export interface CallbackAuthConfig {
151
+ /**
152
+ * Function to get the current access token
153
+ *
154
+ * Called before each API request to get a fresh token.
155
+ * Should return null if not authenticated.
156
+ */
157
+ getToken: () => Promise<string | null>;
158
+ /**
159
+ * Optional callback when 401 Unauthorized is received
160
+ *
161
+ * Can be used to trigger token refresh.
162
+ *
163
+ * @returns Promise resolving to true if token was refreshed successfully
164
+ */
165
+ onUnauthorized?: () => Promise<boolean>;
166
+ /**
167
+ * Optional callback when 403 Forbidden is received
168
+ *
169
+ * Can be used to show permission error UI.
170
+ */
171
+ onForbidden?: () => Promise<void>;
172
+ }
173
+ /**
174
+ * Callback-based authentication provider
175
+ *
176
+ * Provides authentication using callback functions for dynamic token retrieval.
177
+ * Ideal for enterprise integrations where the parent application manages auth.
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * const authProvider = new CallbackAuthProvider({
182
+ * getToken: async () => {
183
+ * return authService.getAccessToken();
184
+ * },
185
+ * onUnauthorized: async () => {
186
+ * const refreshed = await authService.refreshToken();
187
+ * return refreshed;
188
+ * },
189
+ * onForbidden: async () => {
190
+ * showError("You don't have permission to access this resource");
191
+ * }
192
+ * });
193
+ * ```
194
+ */
195
+ export declare class CallbackAuthProvider implements AuthProvider {
196
+ /** Function to get the current token */
197
+ private getToken;
198
+ /** Optional unauthorized callback */
199
+ private onUnauthorizedCallback?;
200
+ /** Optional forbidden callback */
201
+ private onForbiddenCallback?;
202
+ /**
203
+ * Create a new CallbackAuthProvider
204
+ *
205
+ * @param config - Callback authentication configuration
206
+ */
207
+ constructor(config: CallbackAuthConfig);
208
+ /**
209
+ * Get authentication headers
210
+ *
211
+ * Calls the getToken callback to retrieve the current token.
212
+ *
213
+ * @returns Promise resolving to authentication headers
214
+ */
215
+ getAuthHeaders(): Promise<Record<string, string>>;
216
+ /**
217
+ * Check if authenticated
218
+ *
219
+ * For callback-based auth, we assume authenticated if getToken exists.
220
+ * The actual token validity is checked when making requests.
221
+ *
222
+ * @returns true (assumes authenticated, actual check happens on request)
223
+ */
224
+ isAuthenticated(): boolean;
225
+ /**
226
+ * Handle unauthorized response
227
+ *
228
+ * Calls the onUnauthorized callback if provided.
229
+ *
230
+ * @returns Promise resolving to true if auth was refreshed
231
+ */
232
+ onUnauthorized(): Promise<boolean>;
233
+ /**
234
+ * Handle forbidden response
235
+ *
236
+ * Calls the onForbidden callback if provided.
237
+ */
238
+ onForbidden(): Promise<void>;
239
+ }
240
+ /**
241
+ * No-op authentication provider
242
+ *
243
+ * Used when no authentication is required.
244
+ * Provides empty headers and always returns not authenticated.
245
+ */
246
+ export declare class NoAuthProvider implements AuthProvider {
247
+ /**
248
+ * Get authentication headers
249
+ *
250
+ * Returns empty headers (no auth).
251
+ *
252
+ * @returns Promise resolving to empty object
253
+ */
254
+ getAuthHeaders(): Promise<Record<string, string>>;
255
+ /**
256
+ * Check if authenticated
257
+ *
258
+ * Always returns false (no auth configured).
259
+ *
260
+ * @returns false
261
+ */
262
+ isAuthenticated(): boolean;
263
+ }
264
+ /**
265
+ * Create an AuthProvider from legacy endpointConfig.auth configuration
266
+ *
267
+ * Used internally for backward compatibility with existing code that uses
268
+ * the old auth configuration format in EndpointConfig.
269
+ *
270
+ * @param authConfig - Legacy auth configuration from EndpointConfig
271
+ * @returns AuthProvider instance
272
+ */
273
+ export declare function createAuthProviderFromLegacyConfig(authConfig?: {
274
+ type: "none" | "bearer" | "api_key" | "custom";
275
+ token?: string;
276
+ apiKey?: string;
277
+ headers?: Record<string, string>;
278
+ }): AuthProvider;