@d34dman/flowdrop 0.0.14 → 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.
@@ -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
+ }
@@ -1,9 +1,60 @@
1
- import type { Workflow, WorkflowNode, WorkflowEdge } from '../types';
1
+ /**
2
+ * Workflow Store for FlowDrop
3
+ *
4
+ * Provides global state management for workflows with dirty state tracking.
5
+ *
6
+ * @module stores/workflowStore
7
+ */
8
+ import type { Workflow, WorkflowNode, WorkflowEdge } from "../types";
9
+ import type { WorkflowChangeType } from "../types/events.js";
10
+ /** Global workflow state */
2
11
  export declare const workflowStore: import("svelte/store").Writable<Workflow>;
12
+ /**
13
+ * Store for tracking if there are unsaved changes
14
+ *
15
+ * This is set to true whenever the workflow changes after initialization.
16
+ * It can be reset to false by calling markAsSaved().
17
+ */
18
+ export declare const isDirtyStore: import("svelte/store").Writable<boolean>;
19
+ /**
20
+ * Set the dirty state change callback
21
+ *
22
+ * @param callback - Function to call when dirty state changes
23
+ */
24
+ export declare function setOnDirtyStateChange(callback: ((isDirty: boolean) => void) | null): void;
25
+ /**
26
+ * Set the workflow change callback
27
+ *
28
+ * @param callback - Function to call when workflow changes
29
+ */
30
+ export declare function setOnWorkflowChange(callback: ((workflow: Workflow, changeType: WorkflowChangeType) => void) | null): void;
31
+ /**
32
+ * Mark the current workflow state as saved
33
+ *
34
+ * Clears the dirty state by updating the saved snapshot.
35
+ */
36
+ export declare function markAsSaved(): void;
37
+ /**
38
+ * Check if there are unsaved changes
39
+ *
40
+ * @returns true if there are unsaved changes
41
+ */
42
+ export declare function isDirty(): boolean;
43
+ /**
44
+ * Get the current workflow
45
+ *
46
+ * @returns The current workflow or null
47
+ */
48
+ export declare function getWorkflow(): Workflow | null;
49
+ /** Derived store for workflow ID */
3
50
  export declare const workflowId: import("svelte/store").Readable<string>;
51
+ /** Derived store for workflow name */
4
52
  export declare const workflowName: import("svelte/store").Readable<string>;
53
+ /** Derived store for workflow nodes */
5
54
  export declare const workflowNodes: import("svelte/store").Readable<WorkflowNode[]>;
55
+ /** Derived store for workflow edges */
6
56
  export declare const workflowEdges: import("svelte/store").Readable<WorkflowEdge[]>;
57
+ /** Derived store for workflow metadata */
7
58
  export declare const workflowMetadata: import("svelte/store").Readable<{
8
59
  version: string;
9
60
  createdAt: string;
@@ -13,19 +64,68 @@ export declare const workflowMetadata: import("svelte/store").Readable<{
13
64
  versionId?: string;
14
65
  updateNumber?: number;
15
66
  }>;
67
+ /**
68
+ * Actions for updating the workflow
69
+ *
70
+ * All actions that modify the workflow will trigger dirty state updates
71
+ * and emit change events.
72
+ */
16
73
  export declare const workflowActions: {
74
+ /**
75
+ * Initialize workflow (from load or new)
76
+ *
77
+ * This sets the initial saved snapshot and clears dirty state.
78
+ */
17
79
  initialize: (workflow: Workflow) => void;
80
+ /**
81
+ * Update the entire workflow
82
+ */
18
83
  updateWorkflow: (workflow: Workflow) => void;
84
+ /**
85
+ * Update nodes
86
+ */
19
87
  updateNodes: (nodes: WorkflowNode[]) => void;
88
+ /**
89
+ * Update edges
90
+ */
20
91
  updateEdges: (edges: WorkflowEdge[]) => void;
92
+ /**
93
+ * Update workflow name
94
+ */
21
95
  updateName: (name: string) => void;
96
+ /**
97
+ * Add a node
98
+ */
22
99
  addNode: (node: WorkflowNode) => void;
100
+ /**
101
+ * Remove a node
102
+ */
23
103
  removeNode: (nodeId: string) => void;
104
+ /**
105
+ * Add an edge
106
+ */
24
107
  addEdge: (edge: WorkflowEdge) => void;
108
+ /**
109
+ * Remove an edge
110
+ */
25
111
  removeEdge: (edgeId: string) => void;
112
+ /**
113
+ * Update a specific node
114
+ */
26
115
  updateNode: (nodeId: string, updates: Partial<WorkflowNode>) => void;
116
+ /**
117
+ * Clear the workflow
118
+ */
27
119
  clear: () => void;
120
+ /**
121
+ * Update workflow metadata
122
+ */
28
123
  updateMetadata: (metadata: Partial<Workflow["metadata"]>) => void;
124
+ /**
125
+ * Batch update nodes and edges
126
+ *
127
+ * Useful for complex operations that update multiple things at once.
128
+ */
29
129
  batchUpdate: (updates: {
30
130
  nodes?: WorkflowNode[];
31
131
  edges?: WorkflowEdge[];
@@ -34,11 +134,13 @@ export declare const workflowActions: {
34
134
  metadata?: Partial<Workflow["metadata"]>;
35
135
  }) => void;
36
136
  };
137
+ /** Derived store for workflow changes (useful for triggering saves) */
37
138
  export declare const workflowChanged: import("svelte/store").Readable<{
38
139
  nodes: WorkflowNode[];
39
140
  edges: WorkflowEdge[];
40
141
  name: string;
41
142
  }>;
143
+ /** Derived store for workflow validation */
42
144
  export declare const workflowValidation: import("svelte/store").Readable<{
43
145
  hasNodes: boolean;
44
146
  hasEdges: boolean;
@@ -46,6 +148,7 @@ export declare const workflowValidation: import("svelte/store").Readable<{
46
148
  edgeCount: number;
47
149
  isValid: boolean;
48
150
  }>;
151
+ /** Derived store for workflow metadata changes */
49
152
  export declare const workflowMetadataChanged: import("svelte/store").Readable<{
50
153
  createdAt: string;
51
154
  updatedAt: string;