@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
@@ -0,0 +1,249 @@
1
+ /**
2
+ * FlowDrop Plugin System
3
+ * Provides APIs for external libraries to register custom node components.
4
+ *
5
+ * This enables a plugin ecosystem where:
6
+ * - Third-party libraries can provide custom node types
7
+ * - Custom nodes are namespaced to avoid conflicts
8
+ * - Registration is simplified with a clean API
9
+ */
10
+ import { nodeComponentRegistry, createNamespacedType } from './nodeComponentRegistry.js';
11
+ /**
12
+ * Register a FlowDrop plugin with custom node components.
13
+ * All node types are automatically namespaced with the plugin namespace.
14
+ *
15
+ * @param config - Plugin configuration with namespace and node definitions
16
+ * @returns Result object with registered types and any errors
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { registerFlowDropPlugin } from "@flowdrop/lib";
21
+ * import FancyNode from "./FancyNode.svelte";
22
+ * import GlowNode from "./GlowNode.svelte";
23
+ *
24
+ * const result = registerFlowDropPlugin({
25
+ * namespace: "awesome",
26
+ * name: "Awesome Nodes",
27
+ * version: "1.0.0",
28
+ * nodes: [
29
+ * {
30
+ * type: "fancy",
31
+ * displayName: "Fancy Node",
32
+ * component: FancyNode,
33
+ * icon: "mdi:sparkles"
34
+ * },
35
+ * {
36
+ * type: "glow",
37
+ * displayName: "Glowing Node",
38
+ * component: GlowNode,
39
+ * icon: "mdi:lightbulb"
40
+ * }
41
+ * ]
42
+ * });
43
+ *
44
+ * // Result:
45
+ * // {
46
+ * // success: true,
47
+ * // namespace: "awesome",
48
+ * // registeredTypes: ["awesome:fancy", "awesome:glow"],
49
+ * // errors: []
50
+ * // }
51
+ * ```
52
+ */
53
+ export function registerFlowDropPlugin(config) {
54
+ const result = {
55
+ success: true,
56
+ namespace: config.namespace,
57
+ registeredTypes: [],
58
+ errors: []
59
+ };
60
+ // Validate namespace
61
+ if (!isValidNamespace(config.namespace)) {
62
+ result.success = false;
63
+ result.errors.push(`Invalid namespace "${config.namespace}". ` +
64
+ `Namespace must be lowercase alphanumeric with optional hyphens.`);
65
+ return result;
66
+ }
67
+ // Register each node
68
+ for (const nodeDef of config.nodes) {
69
+ try {
70
+ const namespacedType = createNamespacedType(config.namespace, nodeDef.type);
71
+ const registration = {
72
+ type: namespacedType,
73
+ displayName: nodeDef.displayName,
74
+ description: nodeDef.description,
75
+ component: nodeDef.component,
76
+ icon: nodeDef.icon,
77
+ category: nodeDef.category ?? 'custom',
78
+ source: config.namespace,
79
+ statusPosition: nodeDef.statusPosition,
80
+ statusSize: nodeDef.statusSize
81
+ };
82
+ nodeComponentRegistry.register(registration);
83
+ result.registeredTypes.push(namespacedType);
84
+ }
85
+ catch (error) {
86
+ result.success = false;
87
+ const errorMessage = error instanceof Error ? error.message : String(error);
88
+ result.errors.push(`Failed to register ${config.namespace}:${nodeDef.type}: ${errorMessage}`);
89
+ }
90
+ }
91
+ return result;
92
+ }
93
+ /**
94
+ * Unregister all nodes from a plugin by namespace.
95
+ *
96
+ * @param namespace - The plugin namespace to unregister
97
+ * @returns Array of unregistered type identifiers
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const removed = unregisterFlowDropPlugin("awesome");
102
+ * // Returns ["awesome:fancy", "awesome:glow"]
103
+ * ```
104
+ */
105
+ export function unregisterFlowDropPlugin(namespace) {
106
+ const unregistered = [];
107
+ const types = nodeComponentRegistry.getTypes();
108
+ for (const type of types) {
109
+ if (type.startsWith(`${namespace}:`)) {
110
+ if (nodeComponentRegistry.unregister(type)) {
111
+ unregistered.push(type);
112
+ }
113
+ }
114
+ }
115
+ return unregistered;
116
+ }
117
+ /**
118
+ * Check if a namespace is valid.
119
+ * Must be lowercase alphanumeric with optional hyphens.
120
+ *
121
+ * @param namespace - The namespace to validate
122
+ * @returns true if valid
123
+ */
124
+ export function isValidNamespace(namespace) {
125
+ return /^[a-z][a-z0-9-]*$/.test(namespace);
126
+ }
127
+ /**
128
+ * Get all registered plugins (unique namespaces).
129
+ *
130
+ * @returns Array of namespace strings
131
+ */
132
+ export function getRegisteredPlugins() {
133
+ const sources = new Set();
134
+ const registrations = nodeComponentRegistry.getAll();
135
+ for (const reg of registrations) {
136
+ if (reg.source && reg.source !== 'flowdrop') {
137
+ sources.add(reg.source);
138
+ }
139
+ }
140
+ return Array.from(sources);
141
+ }
142
+ /**
143
+ * Get the count of nodes registered by a plugin.
144
+ *
145
+ * @param namespace - The plugin namespace
146
+ * @returns Number of nodes registered by this plugin
147
+ */
148
+ export function getPluginNodeCount(namespace) {
149
+ return nodeComponentRegistry.getBySource(namespace).length;
150
+ }
151
+ /**
152
+ * Register a single custom node without a full plugin.
153
+ * Useful for project-specific custom nodes.
154
+ *
155
+ * @param type - Type identifier (can be namespaced or plain)
156
+ * @param displayName - Display name for UI
157
+ * @param component - Svelte component
158
+ * @param options - Additional options
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * import { registerCustomNode } from "@flowdrop/lib";
163
+ * import MyNode from "./MyNode.svelte";
164
+ *
165
+ * registerCustomNode("myproject:special", "Special Node", MyNode, {
166
+ * icon: "mdi:star",
167
+ * description: "A special node for my project"
168
+ * });
169
+ * ```
170
+ */
171
+ export function registerCustomNode(type, displayName, component, options = {}) {
172
+ nodeComponentRegistry.register({
173
+ type,
174
+ displayName,
175
+ component,
176
+ description: options.description,
177
+ icon: options.icon,
178
+ category: options.category ?? 'custom',
179
+ source: options.source ?? 'custom',
180
+ statusPosition: options.statusPosition,
181
+ statusSize: options.statusSize
182
+ });
183
+ }
184
+ /**
185
+ * Create a plugin builder for a fluent API experience.
186
+ *
187
+ * @param namespace - Plugin namespace
188
+ * @param name - Plugin name
189
+ * @returns Plugin builder with chainable methods
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * import { createPlugin } from "@flowdrop/lib";
194
+ *
195
+ * createPlugin("awesome", "Awesome Nodes")
196
+ * .version("1.0.0")
197
+ * .node("fancy", "Fancy Node", FancyNode)
198
+ * .node("glow", "Glowing Node", GlowNode, { icon: "mdi:lightbulb" })
199
+ * .register();
200
+ * ```
201
+ */
202
+ export function createPlugin(namespace, name) {
203
+ const config = {
204
+ namespace,
205
+ name,
206
+ nodes: []
207
+ };
208
+ const builder = {
209
+ /**
210
+ * Set plugin version
211
+ */
212
+ version(v) {
213
+ config.version = v;
214
+ return builder;
215
+ },
216
+ /**
217
+ * Set plugin description
218
+ */
219
+ description(desc) {
220
+ config.description = desc;
221
+ return builder;
222
+ },
223
+ /**
224
+ * Add a node to the plugin
225
+ */
226
+ node(type, displayName, component, options = {}) {
227
+ config.nodes.push({
228
+ type,
229
+ displayName,
230
+ component,
231
+ ...options
232
+ });
233
+ return builder;
234
+ },
235
+ /**
236
+ * Register the plugin
237
+ */
238
+ register() {
239
+ return registerFlowDropPlugin(config);
240
+ },
241
+ /**
242
+ * Get the config without registering (for testing/inspection)
243
+ */
244
+ getConfig() {
245
+ return { ...config };
246
+ }
247
+ };
248
+ return builder;
249
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Draft Storage Service for FlowDrop
3
+ *
4
+ * Handles saving and loading workflow drafts to/from localStorage.
5
+ * Provides interval-based auto-save functionality.
6
+ *
7
+ * @module services/draftStorage
8
+ */
9
+ import type { Workflow } from '../types/index.js';
10
+ /**
11
+ * Draft metadata stored alongside the workflow
12
+ */
13
+ interface DraftMetadata {
14
+ /** Timestamp when the draft was saved */
15
+ savedAt: string;
16
+ /** Workflow ID (if available) */
17
+ workflowId?: string;
18
+ /** Workflow name */
19
+ workflowName?: string;
20
+ }
21
+ /**
22
+ * Complete draft data stored in localStorage
23
+ */
24
+ interface StoredDraft {
25
+ /** The workflow data */
26
+ workflow: Workflow;
27
+ /** Draft metadata */
28
+ metadata: DraftMetadata;
29
+ }
30
+ /**
31
+ * Generate a storage key for a workflow
32
+ *
33
+ * If a custom key is provided, use it directly.
34
+ * Otherwise, generate based on workflow ID or use "new" for unsaved workflows.
35
+ *
36
+ * @param workflowId - The workflow ID (optional)
37
+ * @param customKey - Custom storage key provided by enterprise (optional)
38
+ * @returns The storage key to use
39
+ */
40
+ export declare function getDraftStorageKey(workflowId?: string, customKey?: string): string;
41
+ /**
42
+ * Save a workflow draft to localStorage
43
+ *
44
+ * @param workflow - The workflow to save
45
+ * @param storageKey - The storage key to use
46
+ * @returns true if saved successfully, false otherwise
47
+ */
48
+ export declare function saveDraft(workflow: Workflow, storageKey: string): boolean;
49
+ /**
50
+ * Load a workflow draft from localStorage
51
+ *
52
+ * @param storageKey - The storage key to load from
53
+ * @returns The stored draft, or null if not found
54
+ */
55
+ export declare function loadDraft(storageKey: string): StoredDraft | null;
56
+ /**
57
+ * Delete a workflow draft from localStorage
58
+ *
59
+ * @param storageKey - The storage key to delete
60
+ */
61
+ export declare function deleteDraft(storageKey: string): void;
62
+ /**
63
+ * Check if a draft exists for a given storage key
64
+ *
65
+ * @param storageKey - The storage key to check
66
+ * @returns true if a draft exists
67
+ */
68
+ export declare function hasDraft(storageKey: string): boolean;
69
+ /**
70
+ * Get draft metadata without loading the full workflow
71
+ *
72
+ * Useful for displaying draft information without parsing the entire workflow.
73
+ *
74
+ * @param storageKey - The storage key to check
75
+ * @returns Draft metadata, or null if not found
76
+ */
77
+ export declare function getDraftMetadata(storageKey: string): DraftMetadata | null;
78
+ /**
79
+ * Draft auto-save manager
80
+ *
81
+ * Handles interval-based auto-saving of workflow drafts.
82
+ * Should be instantiated per FlowDrop instance.
83
+ */
84
+ export declare class DraftAutoSaveManager {
85
+ /** Interval timer ID */
86
+ private intervalId;
87
+ /** Storage key for drafts */
88
+ private storageKey;
89
+ /** Auto-save interval in milliseconds */
90
+ private interval;
91
+ /** Whether auto-save is enabled */
92
+ private enabled;
93
+ /** Function to get current workflow */
94
+ private getWorkflow;
95
+ /** Function to check if workflow is dirty */
96
+ private isDirty;
97
+ /** Last saved workflow hash (for change detection) */
98
+ private lastSavedHash;
99
+ /**
100
+ * Create a new DraftAutoSaveManager
101
+ *
102
+ * @param options - Configuration options
103
+ */
104
+ constructor(options: {
105
+ storageKey: string;
106
+ interval: number;
107
+ enabled: boolean;
108
+ getWorkflow: () => Workflow | null;
109
+ isDirty: () => boolean;
110
+ });
111
+ /**
112
+ * Start auto-save interval
113
+ *
114
+ * Will save drafts at the configured interval if there are unsaved changes.
115
+ */
116
+ start(): void;
117
+ /**
118
+ * Stop auto-save interval
119
+ */
120
+ stop(): void;
121
+ /**
122
+ * Save draft if there are unsaved changes
123
+ *
124
+ * @returns true if a draft was saved
125
+ */
126
+ saveIfDirty(): boolean;
127
+ /**
128
+ * Force save the current workflow as a draft
129
+ *
130
+ * Saves regardless of dirty state.
131
+ *
132
+ * @returns true if saved successfully
133
+ */
134
+ forceSave(): boolean;
135
+ /**
136
+ * Clear the draft from storage
137
+ */
138
+ clearDraft(): void;
139
+ /**
140
+ * Mark the current state as saved
141
+ *
142
+ * Updates the hash so the next saveIfDirty won't save unless there are new changes.
143
+ */
144
+ markAsSaved(): void;
145
+ /**
146
+ * Update the storage key
147
+ *
148
+ * Useful when the workflow ID changes (e.g., after first save).
149
+ *
150
+ * @param newKey - The new storage key
151
+ */
152
+ updateStorageKey(newKey: string): void;
153
+ /**
154
+ * Simple hash function for change detection
155
+ *
156
+ * Not cryptographically secure, just for detecting changes.
157
+ *
158
+ * @param workflow - The workflow to hash
159
+ * @returns A simple hash string
160
+ */
161
+ private hashWorkflow;
162
+ /**
163
+ * Check if auto-save is currently running
164
+ */
165
+ isRunning(): boolean;
166
+ /**
167
+ * Get the current storage key
168
+ */
169
+ getStorageKey(): string;
170
+ }
171
+ export {};
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Draft Storage Service for FlowDrop
3
+ *
4
+ * Handles saving and loading workflow drafts to/from localStorage.
5
+ * Provides interval-based auto-save functionality.
6
+ *
7
+ * @module services/draftStorage
8
+ */
9
+ /**
10
+ * Default storage key prefix
11
+ */
12
+ const STORAGE_KEY_PREFIX = 'flowdrop:draft';
13
+ /**
14
+ * Generate a storage key for a workflow
15
+ *
16
+ * If a custom key is provided, use it directly.
17
+ * Otherwise, generate based on workflow ID or use "new" for unsaved workflows.
18
+ *
19
+ * @param workflowId - The workflow ID (optional)
20
+ * @param customKey - Custom storage key provided by enterprise (optional)
21
+ * @returns The storage key to use
22
+ */
23
+ export function getDraftStorageKey(workflowId, customKey) {
24
+ if (customKey) {
25
+ return customKey;
26
+ }
27
+ if (workflowId) {
28
+ return `${STORAGE_KEY_PREFIX}:${workflowId}`;
29
+ }
30
+ return `${STORAGE_KEY_PREFIX}:new`;
31
+ }
32
+ /**
33
+ * Save a workflow draft to localStorage
34
+ *
35
+ * @param workflow - The workflow to save
36
+ * @param storageKey - The storage key to use
37
+ * @returns true if saved successfully, false otherwise
38
+ */
39
+ export function saveDraft(workflow, storageKey) {
40
+ try {
41
+ const draft = {
42
+ workflow,
43
+ metadata: {
44
+ savedAt: new Date().toISOString(),
45
+ workflowId: workflow.id,
46
+ workflowName: workflow.name
47
+ }
48
+ };
49
+ localStorage.setItem(storageKey, JSON.stringify(draft));
50
+ return true;
51
+ }
52
+ catch (error) {
53
+ // localStorage might be full or disabled
54
+ console.warn('Failed to save draft to localStorage:', error);
55
+ return false;
56
+ }
57
+ }
58
+ /**
59
+ * Load a workflow draft from localStorage
60
+ *
61
+ * @param storageKey - The storage key to load from
62
+ * @returns The stored draft, or null if not found
63
+ */
64
+ export function loadDraft(storageKey) {
65
+ try {
66
+ const stored = localStorage.getItem(storageKey);
67
+ if (!stored) {
68
+ return null;
69
+ }
70
+ const draft = JSON.parse(stored);
71
+ // Validate the draft structure
72
+ if (!draft.workflow || !draft.metadata) {
73
+ console.warn('Invalid draft structure in localStorage');
74
+ return null;
75
+ }
76
+ return draft;
77
+ }
78
+ catch (error) {
79
+ console.warn('Failed to load draft from localStorage:', error);
80
+ return null;
81
+ }
82
+ }
83
+ /**
84
+ * Delete a workflow draft from localStorage
85
+ *
86
+ * @param storageKey - The storage key to delete
87
+ */
88
+ export function deleteDraft(storageKey) {
89
+ try {
90
+ localStorage.removeItem(storageKey);
91
+ }
92
+ catch (error) {
93
+ console.warn('Failed to delete draft from localStorage:', error);
94
+ }
95
+ }
96
+ /**
97
+ * Check if a draft exists for a given storage key
98
+ *
99
+ * @param storageKey - The storage key to check
100
+ * @returns true if a draft exists
101
+ */
102
+ export function hasDraft(storageKey) {
103
+ try {
104
+ return localStorage.getItem(storageKey) !== null;
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ }
110
+ /**
111
+ * Get draft metadata without loading the full workflow
112
+ *
113
+ * Useful for displaying draft information without parsing the entire workflow.
114
+ *
115
+ * @param storageKey - The storage key to check
116
+ * @returns Draft metadata, or null if not found
117
+ */
118
+ export function getDraftMetadata(storageKey) {
119
+ const draft = loadDraft(storageKey);
120
+ return draft?.metadata ?? null;
121
+ }
122
+ /**
123
+ * Draft auto-save manager
124
+ *
125
+ * Handles interval-based auto-saving of workflow drafts.
126
+ * Should be instantiated per FlowDrop instance.
127
+ */
128
+ export class DraftAutoSaveManager {
129
+ /** Interval timer ID */
130
+ intervalId = null;
131
+ /** Storage key for drafts */
132
+ storageKey;
133
+ /** Auto-save interval in milliseconds */
134
+ interval;
135
+ /** Whether auto-save is enabled */
136
+ enabled;
137
+ /** Function to get current workflow */
138
+ getWorkflow;
139
+ /** Function to check if workflow is dirty */
140
+ isDirty;
141
+ /** Last saved workflow hash (for change detection) */
142
+ lastSavedHash = null;
143
+ /**
144
+ * Create a new DraftAutoSaveManager
145
+ *
146
+ * @param options - Configuration options
147
+ */
148
+ constructor(options) {
149
+ this.storageKey = options.storageKey;
150
+ this.interval = options.interval;
151
+ this.enabled = options.enabled;
152
+ this.getWorkflow = options.getWorkflow;
153
+ this.isDirty = options.isDirty;
154
+ }
155
+ /**
156
+ * Start auto-save interval
157
+ *
158
+ * Will save drafts at the configured interval if there are unsaved changes.
159
+ */
160
+ start() {
161
+ if (!this.enabled || this.intervalId) {
162
+ return;
163
+ }
164
+ this.intervalId = setInterval(() => {
165
+ this.saveIfDirty();
166
+ }, this.interval);
167
+ }
168
+ /**
169
+ * Stop auto-save interval
170
+ */
171
+ stop() {
172
+ if (this.intervalId) {
173
+ clearInterval(this.intervalId);
174
+ this.intervalId = null;
175
+ }
176
+ }
177
+ /**
178
+ * Save draft if there are unsaved changes
179
+ *
180
+ * @returns true if a draft was saved
181
+ */
182
+ saveIfDirty() {
183
+ if (!this.enabled) {
184
+ return false;
185
+ }
186
+ const workflow = this.getWorkflow();
187
+ if (!workflow) {
188
+ return false;
189
+ }
190
+ // Only save if dirty
191
+ if (!this.isDirty()) {
192
+ return false;
193
+ }
194
+ // Check if workflow has actually changed since last save
195
+ const currentHash = this.hashWorkflow(workflow);
196
+ if (currentHash === this.lastSavedHash) {
197
+ return false;
198
+ }
199
+ const saved = saveDraft(workflow, this.storageKey);
200
+ if (saved) {
201
+ this.lastSavedHash = currentHash;
202
+ }
203
+ return saved;
204
+ }
205
+ /**
206
+ * Force save the current workflow as a draft
207
+ *
208
+ * Saves regardless of dirty state.
209
+ *
210
+ * @returns true if saved successfully
211
+ */
212
+ forceSave() {
213
+ const workflow = this.getWorkflow();
214
+ if (!workflow) {
215
+ return false;
216
+ }
217
+ const saved = saveDraft(workflow, this.storageKey);
218
+ if (saved) {
219
+ this.lastSavedHash = this.hashWorkflow(workflow);
220
+ }
221
+ return saved;
222
+ }
223
+ /**
224
+ * Clear the draft from storage
225
+ */
226
+ clearDraft() {
227
+ deleteDraft(this.storageKey);
228
+ this.lastSavedHash = null;
229
+ }
230
+ /**
231
+ * Mark the current state as saved
232
+ *
233
+ * Updates the hash so the next saveIfDirty won't save unless there are new changes.
234
+ */
235
+ markAsSaved() {
236
+ const workflow = this.getWorkflow();
237
+ if (workflow) {
238
+ this.lastSavedHash = this.hashWorkflow(workflow);
239
+ }
240
+ }
241
+ /**
242
+ * Update the storage key
243
+ *
244
+ * Useful when the workflow ID changes (e.g., after first save).
245
+ *
246
+ * @param newKey - The new storage key
247
+ */
248
+ updateStorageKey(newKey) {
249
+ // If there's an existing draft with the old key, migrate it
250
+ const existingDraft = loadDraft(this.storageKey);
251
+ if (existingDraft && this.storageKey !== newKey) {
252
+ deleteDraft(this.storageKey);
253
+ saveDraft(existingDraft.workflow, newKey);
254
+ }
255
+ this.storageKey = newKey;
256
+ }
257
+ /**
258
+ * Simple hash function for change detection
259
+ *
260
+ * Not cryptographically secure, just for detecting changes.
261
+ *
262
+ * @param workflow - The workflow to hash
263
+ * @returns A simple hash string
264
+ */
265
+ hashWorkflow(workflow) {
266
+ // Use a simple stringification for change detection
267
+ // We only need nodes, edges, name, and description for change detection
268
+ const toHash = {
269
+ name: workflow.name,
270
+ description: workflow.description,
271
+ nodes: workflow.nodes.map((n) => ({
272
+ id: n.id,
273
+ position: n.position,
274
+ data: n.data
275
+ })),
276
+ edges: workflow.edges.map((e) => ({
277
+ id: e.id,
278
+ source: e.source,
279
+ target: e.target,
280
+ sourceHandle: e.sourceHandle,
281
+ targetHandle: e.targetHandle
282
+ }))
283
+ };
284
+ return JSON.stringify(toHash);
285
+ }
286
+ /**
287
+ * Check if auto-save is currently running
288
+ */
289
+ isRunning() {
290
+ return this.intervalId !== null;
291
+ }
292
+ /**
293
+ * Get the current storage key
294
+ */
295
+ getStorageKey() {
296
+ return this.storageKey;
297
+ }
298
+ }