@flowdrop/flowdrop 1.15.0 → 2.0.0-beta.1

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 (215) hide show
  1. package/CHANGELOG.md +475 -0
  2. package/MIGRATION-2.0.md +472 -0
  3. package/README.md +23 -23
  4. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  5. package/dist/adapters/WorkflowAdapter.js +14 -8
  6. package/dist/adapters/agentspec/AgentSpecAdapter.js +7 -7
  7. package/dist/chat/batchFeedback.d.ts +39 -0
  8. package/dist/chat/batchFeedback.js +51 -0
  9. package/dist/commands/executor.js +15 -1
  10. package/dist/commands/storeIntegration.svelte.d.ts +4 -1
  11. package/dist/commands/storeIntegration.svelte.js +26 -21
  12. package/dist/commands/types.d.ts +2 -0
  13. package/dist/components/App.svelte +162 -192
  14. package/dist/components/App.svelte.d.ts +47 -8
  15. package/dist/components/ConfigForm.svelte +71 -47
  16. package/dist/components/ConfigModal.svelte +7 -2
  17. package/dist/components/ConnectionLine.svelte +4 -2
  18. package/dist/components/Navbar.svelte +61 -1
  19. package/dist/components/NodeSidebar.svelte +27 -45
  20. package/dist/components/NodeStatusOverlay.svelte +94 -6
  21. package/dist/components/NodeSwapPicker.svelte +10 -8
  22. package/dist/components/PipelineStatus.svelte +16 -67
  23. package/dist/components/PortCoordinateTracker.svelte +5 -6
  24. package/dist/components/SchemaForm.stories.svelte +1 -3
  25. package/dist/components/SchemaForm.svelte +18 -25
  26. package/dist/components/SchemaForm.svelte.d.ts +0 -8
  27. package/dist/components/SettingsModal.svelte +8 -3
  28. package/dist/components/SettingsPanel.svelte +20 -4
  29. package/dist/components/SwapMappingEditor.svelte +67 -49
  30. package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
  31. package/dist/components/UniversalNode.svelte +9 -7
  32. package/dist/components/WorkflowEditor.svelte +118 -111
  33. package/dist/components/WorkflowEditor.svelte.d.ts +18 -10
  34. package/dist/components/chat/AIChatPanel.svelte +93 -89
  35. package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
  36. package/dist/components/chat/CommandPreview.svelte +2 -1
  37. package/dist/components/console/CommandConsole.svelte +7 -5
  38. package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
  39. package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
  40. package/dist/components/console/ConsoleInput.svelte +15 -6
  41. package/dist/components/console/ConsoleOutput.svelte +2 -1
  42. package/dist/components/form/FormArray.svelte +5 -9
  43. package/dist/components/form/FormArray.svelte.d.ts +2 -1
  44. package/dist/components/form/FormAutocomplete.svelte +8 -6
  45. package/dist/components/form/FormField.svelte +4 -2
  46. package/dist/components/form/FormFieldLight.svelte +4 -2
  47. package/dist/components/form/FormMarkdownEditor.svelte +9 -4
  48. package/dist/components/form/FormRangeField.svelte +1 -0
  49. package/dist/components/form/FormTemplateEditor.svelte +11 -3
  50. package/dist/components/form/FormToggle.svelte +5 -12
  51. package/dist/components/form/FormToggle.svelte.d.ts +4 -2
  52. package/dist/components/form/templateAutocomplete.js +1 -5
  53. package/dist/components/form/types.d.ts +1 -14
  54. package/dist/components/interrupt/FormPrompt.svelte +3 -2
  55. package/dist/components/interrupt/InterruptBubble.svelte +16 -17
  56. package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
  57. package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
  58. package/dist/components/layouts/MainLayout.svelte +20 -13
  59. package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
  60. package/dist/components/nodes/AtomNode.svelte +17 -5
  61. package/dist/components/nodes/GatewayNode.svelte +19 -10
  62. package/dist/components/nodes/IdeaNode.svelte +7 -0
  63. package/dist/components/nodes/SimpleNode.svelte +11 -6
  64. package/dist/components/nodes/SquareNode.svelte +15 -8
  65. package/dist/components/nodes/TerminalNode.svelte +9 -4
  66. package/dist/components/nodes/ToolNode.svelte +7 -1
  67. package/dist/components/nodes/WorkflowNode.svelte +16 -7
  68. package/dist/components/playground/ChatInput.svelte +11 -14
  69. package/dist/components/playground/ChatPanel.svelte +6 -49
  70. package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
  71. package/dist/components/playground/ControlPanel.svelte +134 -123
  72. package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
  73. package/dist/components/playground/ExecutionLogs.svelte +11 -9
  74. package/dist/components/playground/InputCollector.svelte +11 -9
  75. package/dist/components/playground/MessageStream.svelte +17 -23
  76. package/dist/components/playground/PipelineKanbanView.svelte +65 -6
  77. package/dist/components/playground/PipelinePanel.svelte +11 -5
  78. package/dist/components/playground/PipelineTableView.svelte +186 -44
  79. package/dist/components/playground/Playground.svelte +90 -92
  80. package/dist/components/playground/Playground.svelte.d.ts +2 -0
  81. package/dist/components/playground/PlaygroundApp.svelte +6 -1
  82. package/dist/components/playground/PlaygroundApp.svelte.d.ts +3 -0
  83. package/dist/components/playground/PlaygroundModal.svelte +13 -3
  84. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -0
  85. package/dist/components/playground/PlaygroundStudio.svelte +34 -32
  86. package/dist/components/playground/PlaygroundStudio.svelte.d.ts +3 -0
  87. package/dist/components/playground/SessionManager.svelte +9 -12
  88. package/dist/components/playground/pipelineViewUtils.svelte.d.ts +28 -0
  89. package/dist/components/playground/pipelineViewUtils.svelte.js +38 -1
  90. package/dist/config/endpoints.d.ts +0 -7
  91. package/dist/config/endpoints.js +2 -10
  92. package/dist/core/index.d.ts +4 -4
  93. package/dist/core/index.js +6 -6
  94. package/dist/display/index.d.ts +0 -2
  95. package/dist/display/index.js +0 -6
  96. package/dist/editor/index.d.ts +19 -20
  97. package/dist/editor/index.js +25 -35
  98. package/dist/form/code.d.ts +25 -15
  99. package/dist/form/code.js +44 -41
  100. package/dist/form/fieldRegistry.d.ts +17 -13
  101. package/dist/form/fieldRegistry.js +32 -12
  102. package/dist/form/full.d.ts +17 -13
  103. package/dist/form/full.js +22 -27
  104. package/dist/form/index.d.ts +3 -3
  105. package/dist/form/index.js +3 -3
  106. package/dist/form/markdown.d.ts +13 -8
  107. package/dist/form/markdown.js +22 -23
  108. package/dist/helpers/proximityConnect.d.ts +3 -2
  109. package/dist/helpers/proximityConnect.js +2 -5
  110. package/dist/helpers/workflowEditorHelper.d.ts +12 -5
  111. package/dist/helpers/workflowEditorHelper.js +27 -25
  112. package/dist/index.d.ts +28 -24
  113. package/dist/index.js +27 -50
  114. package/dist/messages/defaults.d.ts +2 -5
  115. package/dist/messages/defaults.js +3 -6
  116. package/dist/messages/index.d.ts +0 -1
  117. package/dist/messages/index.js +0 -1
  118. package/dist/mocks/app-forms.d.ts +6 -2
  119. package/dist/mocks/app-forms.js +11 -4
  120. package/dist/openapi/v1/openapi.yaml +3 -3
  121. package/dist/playground/index.d.ts +2 -3
  122. package/dist/playground/index.js +2 -30
  123. package/dist/playground/mount.d.ts +15 -0
  124. package/dist/playground/mount.js +46 -20
  125. package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
  126. package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
  127. package/dist/registry/builtinFormats.d.ts +9 -18
  128. package/dist/registry/builtinFormats.js +9 -39
  129. package/dist/registry/builtinNodes.d.ts +0 -25
  130. package/dist/registry/builtinNodes.js +1 -50
  131. package/dist/registry/index.d.ts +3 -4
  132. package/dist/registry/index.js +4 -6
  133. package/dist/registry/nodeComponentRegistry.d.ts +182 -15
  134. package/dist/registry/nodeComponentRegistry.js +235 -17
  135. package/dist/registry/workflowFormatRegistry.d.ts +14 -9
  136. package/dist/registry/workflowFormatRegistry.js +24 -8
  137. package/dist/{schema → schemas}/index.d.ts +2 -2
  138. package/dist/{schema → schemas}/index.js +2 -2
  139. package/dist/schemas/v1/workflow.schema.json +3 -3
  140. package/dist/services/agentSpecExecutionService.js +0 -1
  141. package/dist/services/apiVariableService.d.ts +2 -1
  142. package/dist/services/apiVariableService.js +5 -22
  143. package/dist/services/autoSaveService.d.ts +7 -0
  144. package/dist/services/autoSaveService.js +6 -4
  145. package/dist/services/chatService.d.ts +8 -4
  146. package/dist/services/chatService.js +15 -15
  147. package/dist/services/draftStorage.d.ts +129 -13
  148. package/dist/services/draftStorage.js +185 -37
  149. package/dist/services/dynamicSchemaService.d.ts +2 -1
  150. package/dist/services/dynamicSchemaService.js +5 -22
  151. package/dist/services/globalSave.d.ts +13 -12
  152. package/dist/services/globalSave.js +29 -51
  153. package/dist/services/historyService.d.ts +9 -3
  154. package/dist/services/historyService.js +9 -3
  155. package/dist/services/interruptService.d.ts +14 -9
  156. package/dist/services/interruptService.js +27 -27
  157. package/dist/services/nodeExecutionService.d.ts +18 -3
  158. package/dist/services/nodeExecutionService.js +71 -45
  159. package/dist/services/playgroundService.d.ts +14 -9
  160. package/dist/services/playgroundService.js +31 -30
  161. package/dist/services/variableService.d.ts +2 -1
  162. package/dist/services/variableService.js +2 -2
  163. package/dist/services/workflowStorage.js +6 -6
  164. package/dist/stores/apiContext.d.ts +45 -0
  165. package/dist/stores/apiContext.js +65 -0
  166. package/dist/stores/categoriesStore.svelte.d.ts +28 -23
  167. package/dist/stores/categoriesStore.svelte.js +70 -64
  168. package/dist/stores/getInstance.svelte.d.ts +39 -0
  169. package/dist/stores/getInstance.svelte.js +65 -0
  170. package/dist/stores/historyStore.svelte.d.ts +77 -93
  171. package/dist/stores/historyStore.svelte.js +134 -160
  172. package/dist/stores/instanceContainer.svelte.d.ts +111 -0
  173. package/dist/stores/instanceContainer.svelte.js +114 -0
  174. package/dist/stores/interruptStore.svelte.d.ts +112 -82
  175. package/dist/stores/interruptStore.svelte.js +253 -226
  176. package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
  177. package/dist/stores/pipelinePanelStore.svelte.js +61 -14
  178. package/dist/stores/playgroundStore.svelte.d.ts +169 -222
  179. package/dist/stores/playgroundStore.svelte.js +515 -580
  180. package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
  181. package/dist/stores/portCoordinateStore.svelte.js +109 -98
  182. package/dist/stores/settingsStore.svelte.d.ts +4 -1
  183. package/dist/stores/settingsStore.svelte.js +47 -12
  184. package/dist/stores/workflowStore.svelte.d.ts +178 -213
  185. package/dist/stores/workflowStore.svelte.js +449 -501
  186. package/dist/stories/EdgeDecorator.svelte +5 -2
  187. package/dist/stories/NodeDecorator.svelte +5 -3
  188. package/dist/svelte-app.d.ts +60 -10
  189. package/dist/svelte-app.js +157 -53
  190. package/dist/types/events.d.ts +6 -3
  191. package/dist/types/index.d.ts +33 -3
  192. package/dist/types/navbar.d.ts +7 -0
  193. package/dist/types/playground.d.ts +18 -3
  194. package/dist/types/settings.d.ts +13 -0
  195. package/dist/types/settings.js +1 -0
  196. package/dist/utils/colors.d.ts +47 -21
  197. package/dist/utils/colors.js +69 -68
  198. package/dist/utils/connections.d.ts +9 -15
  199. package/dist/utils/connections.js +13 -32
  200. package/dist/utils/duration.d.ts +13 -0
  201. package/dist/utils/duration.js +45 -0
  202. package/dist/utils/icons.d.ts +5 -2
  203. package/dist/utils/icons.js +6 -5
  204. package/dist/utils/nodeSwap.d.ts +6 -2
  205. package/dist/utils/nodeSwap.js +62 -126
  206. package/dist/utils/nodeTypes.d.ts +17 -8
  207. package/dist/utils/nodeTypes.js +26 -19
  208. package/dist/utils/performanceUtils.js +7 -0
  209. package/package.json +6 -5
  210. package/dist/messages/deprecation.d.ts +0 -20
  211. package/dist/messages/deprecation.js +0 -33
  212. package/dist/registry/plugin.d.ts +0 -215
  213. package/dist/registry/plugin.js +0 -249
  214. package/dist/services/api.d.ts +0 -129
  215. package/dist/services/api.js +0 -217
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Draft Storage Service for FlowDrop
3
3
  *
4
- * Handles saving and loading workflow drafts to/from localStorage.
4
+ * Handles saving and loading workflow drafts to/from browser storage
5
+ * (localStorage by default, configurable via {@link setDraftStorage}).
5
6
  * Provides interval-based auto-save functionality.
6
7
  *
7
8
  * @module services/draftStorage
@@ -11,6 +12,96 @@ import { logger } from '../utils/logger.js';
11
12
  * Default storage key prefix
12
13
  */
13
14
  const STORAGE_KEY_PREFIX = 'flowdrop:draft';
15
+ /**
16
+ * Wrap a Web Storage object (localStorage/sessionStorage) as a DraftStorageAdapter.
17
+ * The storage global is resolved lazily so SSR and test environments that
18
+ * replace the globals keep working.
19
+ */
20
+ function createWebStorageAdapter(getStorage) {
21
+ return {
22
+ getItem: (key) => getStorage().getItem(key),
23
+ setItem: (key, value) => getStorage().setItem(key, value),
24
+ removeItem: (key) => getStorage().removeItem(key),
25
+ keys: () => {
26
+ const storage = getStorage();
27
+ const keys = [];
28
+ for (let i = 0; i < storage.length; i++) {
29
+ const key = storage.key(i);
30
+ if (key !== null) {
31
+ keys.push(key);
32
+ }
33
+ }
34
+ return keys;
35
+ }
36
+ };
37
+ }
38
+ const WEB_STORAGE_ADAPTERS = {
39
+ local: createWebStorageAdapter(() => localStorage),
40
+ session: createWebStorageAdapter(() => sessionStorage)
41
+ };
42
+ /**
43
+ * Module-level default adapter (localStorage by default).
44
+ *
45
+ * Used by the standalone helpers (`saveDraft`, `clearAllDrafts`, ...) when no
46
+ * explicit adapter is passed, and as the fallback for managers constructed
47
+ * without a `storage` option. Per-instance code (each `DraftAutoSaveManager`,
48
+ * each mounted app) captures its own resolved adapter instead, so multiple
49
+ * FlowDrop instances with different backends do not interfere.
50
+ */
51
+ let activeAdapter = WEB_STORAGE_ADAPTERS.local;
52
+ /**
53
+ * Resolve a {@link DraftStorageOption} to a concrete adapter.
54
+ *
55
+ * `'local'` / `'session'` map to the built-in Web Storage adapters, a custom
56
+ * adapter is returned as-is, and `undefined` resolves to the current
57
+ * module-level default (see {@link setDraftStorage}).
58
+ */
59
+ export function resolveDraftStorage(option) {
60
+ if (option === undefined) {
61
+ return activeAdapter;
62
+ }
63
+ return typeof option === 'string' ? WEB_STORAGE_ADAPTERS[option] : option;
64
+ }
65
+ /**
66
+ * Configure the module-level default for where workflow drafts are persisted.
67
+ *
68
+ * - `'local'` (default): `localStorage` — drafts survive reloads and browser
69
+ * restarts, and remain stored on the device even after the tab is closed,
70
+ * until saved or cleared.
71
+ * - `'session'`: `sessionStorage` — drafts are scoped to the current tab and
72
+ * removed when it closes. Note this also means drafts do not survive a
73
+ * crash-and-reopen.
74
+ * - A custom {@link DraftStorageAdapter} for anything else.
75
+ *
76
+ * This sets the default used by the standalone helpers and by managers
77
+ * constructed without an explicit `storage` option. Mounted apps capture
78
+ * their own adapter at mount time, so calling this does not retarget
79
+ * already-running instances. With multiple mounts the most recent mount's
80
+ * backend wins *for the standalone helpers only*.
81
+ *
82
+ * Security note: neither built-in backend protects against same-origin
83
+ * script access (XSS) — both are readable by any script on the page. If
84
+ * workflows may contain secrets in node configs, disable drafts via
85
+ * `features.autoSaveDraft: false` or keep them off-disk with a custom
86
+ * in-memory adapter.
87
+ */
88
+ export function setDraftStorage(option) {
89
+ activeAdapter = resolveDraftStorage(option);
90
+ }
91
+ /**
92
+ * Get the current module-level default draft storage adapter
93
+ */
94
+ export function getDraftStorage() {
95
+ return activeAdapter;
96
+ }
97
+ /**
98
+ * Storage prefix of the page-default instance (`flowdrop:draft:default`).
99
+ *
100
+ * Every instance — including the default — uses an id-scoped prefix since
101
+ * v2.0. Standalone helper callers that don't pass a prefix get this one, so
102
+ * they keep addressing the default editor's drafts.
103
+ */
104
+ export const DEFAULT_INSTANCE_DRAFT_PREFIX = `${STORAGE_KEY_PREFIX}:default`;
14
105
  /**
15
106
  * Generate a storage key for a workflow
16
107
  *
@@ -19,25 +110,58 @@ const STORAGE_KEY_PREFIX = 'flowdrop:draft';
19
110
  *
20
111
  * @param workflowId - The workflow ID (optional)
21
112
  * @param customKey - Custom storage key provided by enterprise (optional)
113
+ * @param prefix - Key namespace; pass a FlowDrop instance's `storagePrefix`
114
+ * to scope drafts per instance. Defaults to the page-default instance's
115
+ * prefix.
22
116
  * @returns The storage key to use
23
117
  */
24
- export function getDraftStorageKey(workflowId, customKey) {
118
+ export function getDraftStorageKey(workflowId, customKey, prefix = DEFAULT_INSTANCE_DRAFT_PREFIX) {
25
119
  if (customKey) {
26
120
  return customKey;
27
121
  }
28
122
  if (workflowId) {
29
- return `${STORAGE_KEY_PREFIX}:${workflowId}`;
123
+ return `${prefix}:${workflowId}`;
30
124
  }
31
- return `${STORAGE_KEY_PREFIX}:new`;
125
+ return `${prefix}:new`;
32
126
  }
33
127
  /**
34
- * Save a workflow draft to localStorage
128
+ * One-time migration of a 1.x draft key to its 2.0 scoped equivalent.
129
+ *
130
+ * In 1.x the page-default instance stored drafts under the bare
131
+ * `flowdrop:draft:<workflowId>` key; since 2.0 it uses
132
+ * `flowdrop:draft:default:<workflowId>`. When the scoped key is empty and the
133
+ * legacy key holds a draft, the draft is moved (copied, then the legacy key
134
+ * removed) so users upgrading mid-edit don't lose work.
135
+ *
136
+ * @param legacyKey - The 1.x bare-prefix key
137
+ * @param scopedKey - The 2.0 instance-scoped key
138
+ * @param storage - Adapter to migrate within (defaults to the module-level default)
139
+ */
140
+ export function migrateLegacyDraftKey(legacyKey, scopedKey, storage = activeAdapter) {
141
+ if (legacyKey === scopedKey)
142
+ return;
143
+ try {
144
+ if (storage.getItem(scopedKey) !== null)
145
+ return;
146
+ const legacy = storage.getItem(legacyKey);
147
+ if (legacy === null)
148
+ return;
149
+ storage.setItem(scopedKey, legacy);
150
+ storage.removeItem(legacyKey);
151
+ }
152
+ catch (error) {
153
+ logger.warn('Failed to migrate legacy draft key:', error);
154
+ }
155
+ }
156
+ /**
157
+ * Save a workflow draft to draft storage
35
158
  *
36
159
  * @param workflow - The workflow to save
37
160
  * @param storageKey - The storage key to use
161
+ * @param storage - Adapter to write to (defaults to the module-level default)
38
162
  * @returns true if saved successfully, false otherwise
39
163
  */
40
- export function saveDraft(workflow, storageKey) {
164
+ export function saveDraft(workflow, storageKey, storage = activeAdapter) {
41
165
  try {
42
166
  const draft = {
43
167
  workflow,
@@ -47,62 +171,65 @@ export function saveDraft(workflow, storageKey) {
47
171
  workflowName: workflow.name
48
172
  }
49
173
  };
50
- localStorage.setItem(storageKey, JSON.stringify(draft));
174
+ storage.setItem(storageKey, JSON.stringify(draft));
51
175
  return true;
52
176
  }
53
177
  catch (error) {
54
- // localStorage might be full or disabled
55
- logger.warn('Failed to save draft to localStorage:', error);
178
+ // Storage might be full or disabled
179
+ logger.warn('Failed to save draft to storage:', error);
56
180
  return false;
57
181
  }
58
182
  }
59
183
  /**
60
- * Load a workflow draft from localStorage
184
+ * Load a workflow draft from draft storage
61
185
  *
62
186
  * @param storageKey - The storage key to load from
187
+ * @param storage - Adapter to read from (defaults to the module-level default)
63
188
  * @returns The stored draft, or null if not found
64
189
  */
65
- export function loadDraft(storageKey) {
190
+ export function loadDraft(storageKey, storage = activeAdapter) {
66
191
  try {
67
- const stored = localStorage.getItem(storageKey);
192
+ const stored = storage.getItem(storageKey);
68
193
  if (!stored) {
69
194
  return null;
70
195
  }
71
196
  const draft = JSON.parse(stored);
72
197
  // Validate the draft structure
73
198
  if (!draft.workflow || !draft.metadata) {
74
- logger.warn('Invalid draft structure in localStorage');
199
+ logger.warn('Invalid draft structure in storage');
75
200
  return null;
76
201
  }
77
202
  return draft;
78
203
  }
79
204
  catch (error) {
80
- logger.warn('Failed to load draft from localStorage:', error);
205
+ logger.warn('Failed to load draft from storage:', error);
81
206
  return null;
82
207
  }
83
208
  }
84
209
  /**
85
- * Delete a workflow draft from localStorage
210
+ * Delete a workflow draft from draft storage
86
211
  *
87
212
  * @param storageKey - The storage key to delete
213
+ * @param storage - Adapter to delete from (defaults to the module-level default)
88
214
  */
89
- export function deleteDraft(storageKey) {
215
+ export function deleteDraft(storageKey, storage = activeAdapter) {
90
216
  try {
91
- localStorage.removeItem(storageKey);
217
+ storage.removeItem(storageKey);
92
218
  }
93
219
  catch (error) {
94
- logger.warn('Failed to delete draft from localStorage:', error);
220
+ logger.warn('Failed to delete draft from storage:', error);
95
221
  }
96
222
  }
97
223
  /**
98
224
  * Check if a draft exists for a given storage key
99
225
  *
100
226
  * @param storageKey - The storage key to check
227
+ * @param storage - Adapter to check (defaults to the module-level default)
101
228
  * @returns true if a draft exists
102
229
  */
103
- export function hasDraft(storageKey) {
230
+ export function hasDraft(storageKey, storage = activeAdapter) {
104
231
  try {
105
- return localStorage.getItem(storageKey) !== null;
232
+ return storage.getItem(storageKey) !== null;
106
233
  }
107
234
  catch {
108
235
  return false;
@@ -114,14 +241,15 @@ export function hasDraft(storageKey) {
114
241
  * Useful for displaying draft information without parsing the entire workflow.
115
242
  *
116
243
  * @param storageKey - The storage key to check
244
+ * @param storage - Adapter to read from (defaults to the module-level default)
117
245
  * @returns Draft metadata, or null if not found
118
246
  */
119
- export function getDraftMetadata(storageKey) {
120
- const draft = loadDraft(storageKey);
247
+ export function getDraftMetadata(storageKey, storage = activeAdapter) {
248
+ const draft = loadDraft(storageKey, storage);
121
249
  return draft?.metadata ?? null;
122
250
  }
123
251
  /**
124
- * Clear all FlowDrop drafts from localStorage
252
+ * Clear all FlowDrop drafts from draft storage
125
253
  *
126
254
  * Removes every key beginning with `flowdrop:draft:`. Intended to be called
127
255
  * from a host application's logout handler so workflow drafts do not persist
@@ -130,29 +258,29 @@ export function getDraftMetadata(storageKey) {
130
258
  * @param extraKeys - Additional explicit keys to remove. Pass any custom
131
259
  * `draftStorageKey` values configured at mount time so they are cleared
132
260
  * alongside the default-prefixed keys.
261
+ * @param storage - Adapter to clear (defaults to the module-level default)
133
262
  * @returns The number of entries removed.
134
263
  */
135
- export function clearAllDrafts(extraKeys = []) {
264
+ export function clearAllDrafts(extraKeys = [], storage = activeAdapter) {
136
265
  try {
137
266
  const keysToRemove = new Set();
138
- for (let i = 0; i < localStorage.length; i++) {
139
- const key = localStorage.key(i);
140
- if (key && key.startsWith(`${STORAGE_KEY_PREFIX}:`)) {
267
+ for (const key of storage.keys()) {
268
+ if (key.startsWith(`${STORAGE_KEY_PREFIX}:`)) {
141
269
  keysToRemove.add(key);
142
270
  }
143
271
  }
144
272
  for (const key of extraKeys) {
145
- if (localStorage.getItem(key) !== null) {
273
+ if (storage.getItem(key) !== null) {
146
274
  keysToRemove.add(key);
147
275
  }
148
276
  }
149
277
  for (const key of keysToRemove) {
150
- localStorage.removeItem(key);
278
+ storage.removeItem(key);
151
279
  }
152
280
  return keysToRemove.size;
153
281
  }
154
282
  catch (error) {
155
- logger.warn('Failed to clear drafts from localStorage:', error);
283
+ logger.warn('Failed to clear drafts from storage:', error);
156
284
  return 0;
157
285
  }
158
286
  }
@@ -175,6 +303,17 @@ export class DraftAutoSaveManager {
175
303
  getWorkflow;
176
304
  /** Function to check if workflow is dirty */
177
305
  isDirty;
306
+ /**
307
+ * Runtime gate for draft persistence (e.g. a user-facing opt-out setting).
308
+ * Checked on every save, so it can change after construction.
309
+ */
310
+ isPersistenceAllowed;
311
+ /**
312
+ * Storage adapter this instance writes to.
313
+ * Captured at construction, so a later `setDraftStorage()` call (e.g. from
314
+ * another FlowDrop mount on the same page) cannot retarget this manager.
315
+ */
316
+ storage;
178
317
  /** Last saved workflow hash (for change detection) */
179
318
  lastSavedHash = null;
180
319
  /**
@@ -188,6 +327,8 @@ export class DraftAutoSaveManager {
188
327
  this.enabled = options.enabled;
189
328
  this.getWorkflow = options.getWorkflow;
190
329
  this.isDirty = options.isDirty;
330
+ this.isPersistenceAllowed = options.isPersistenceAllowed ?? (() => true);
331
+ this.storage = resolveDraftStorage(options.storage);
191
332
  }
192
333
  /**
193
334
  * Start auto-save interval
@@ -217,7 +358,7 @@ export class DraftAutoSaveManager {
217
358
  * @returns true if a draft was saved
218
359
  */
219
360
  saveIfDirty() {
220
- if (!this.enabled) {
361
+ if (!this.enabled || !this.isPersistenceAllowed()) {
221
362
  return false;
222
363
  }
223
364
  const workflow = this.getWorkflow();
@@ -233,7 +374,7 @@ export class DraftAutoSaveManager {
233
374
  if (currentHash === this.lastSavedHash) {
234
375
  return false;
235
376
  }
236
- const saved = saveDraft(workflow, this.storageKey);
377
+ const saved = saveDraft(workflow, this.storageKey, this.storage);
237
378
  if (saved) {
238
379
  this.lastSavedHash = currentHash;
239
380
  }
@@ -247,11 +388,14 @@ export class DraftAutoSaveManager {
247
388
  * @returns true if saved successfully
248
389
  */
249
390
  forceSave() {
391
+ if (!this.isPersistenceAllowed()) {
392
+ return false;
393
+ }
250
394
  const workflow = this.getWorkflow();
251
395
  if (!workflow) {
252
396
  return false;
253
397
  }
254
- const saved = saveDraft(workflow, this.storageKey);
398
+ const saved = saveDraft(workflow, this.storageKey, this.storage);
255
399
  if (saved) {
256
400
  this.lastSavedHash = this.hashWorkflow(workflow);
257
401
  }
@@ -261,7 +405,7 @@ export class DraftAutoSaveManager {
261
405
  * Clear the draft from storage
262
406
  */
263
407
  clearDraft() {
264
- deleteDraft(this.storageKey);
408
+ deleteDraft(this.storageKey, this.storage);
265
409
  this.lastSavedHash = null;
266
410
  }
267
411
  /**
@@ -284,10 +428,14 @@ export class DraftAutoSaveManager {
284
428
  */
285
429
  updateStorageKey(newKey) {
286
430
  // If there's an existing draft with the old key, migrate it
287
- const existingDraft = loadDraft(this.storageKey);
431
+ const existingDraft = loadDraft(this.storageKey, this.storage);
288
432
  if (existingDraft && this.storageKey !== newKey) {
289
- deleteDraft(this.storageKey);
290
- saveDraft(existingDraft.workflow, newKey);
433
+ deleteDraft(this.storageKey, this.storage);
434
+ // Migration is still a write — respect the user's opt-out. The old
435
+ // draft is deleted either way, which is in line with opting out.
436
+ if (this.isPersistenceAllowed()) {
437
+ saveDraft(existingDraft.workflow, newKey, this.storage);
438
+ }
291
439
  }
292
440
  this.storageKey = newKey;
293
441
  }
@@ -6,6 +6,7 @@
6
6
  * @module services/dynamicSchemaService
7
7
  */
8
8
  import type { ConfigSchema, DynamicSchemaEndpoint, ExternalEditLink, ConfigEditOptions, WorkflowNode } from '../types/index.js';
9
+ import type { EndpointConfig } from '../config/endpoints.js';
9
10
  /**
10
11
  * Result of a dynamic schema fetch operation
11
12
  */
@@ -41,7 +42,7 @@ export interface DynamicSchemaResult {
41
42
  * }
42
43
  * ```
43
44
  */
44
- export declare function fetchDynamicSchema(endpoint: DynamicSchemaEndpoint, node: WorkflowNode, workflowId?: string): Promise<DynamicSchemaResult>;
45
+ export declare function fetchDynamicSchema(endpointConfig: EndpointConfig | null, endpoint: DynamicSchemaEndpoint, node: WorkflowNode, workflowId?: string): Promise<DynamicSchemaResult>;
45
46
  /**
46
47
  * Resolves an external edit link URL with template variables.
47
48
  *
@@ -5,7 +5,6 @@
5
5
  *
6
6
  * @module services/dynamicSchemaService
7
7
  */
8
- import { getEndpointConfig } from './api.js';
9
8
  import { DEFAULT_CACHE_TTL_MS } from '../config/constants.js';
10
9
  /**
11
10
  * Schema cache with TTL support
@@ -137,7 +136,7 @@ function isCacheValid(entry, ttl = DEFAULT_CACHE_TTL) {
137
136
  * }
138
137
  * ```
139
138
  */
140
- export async function fetchDynamicSchema(endpoint, node, workflowId) {
139
+ export async function fetchDynamicSchema(endpointConfig, endpoint, node, workflowId) {
141
140
  // Build the context from the node
142
141
  const context = {
143
142
  id: node.id,
@@ -163,13 +162,10 @@ export async function fetchDynamicSchema(endpoint, node, workflowId) {
163
162
  // Resolve the URL with template variables
164
163
  let url = resolveTemplate(endpoint.url, endpoint.parameterMapping, context);
165
164
  // If URL is relative, try to prepend base URL from endpoint config
166
- if (url.startsWith('/')) {
167
- const currentConfig = getEndpointConfig();
168
- if (currentConfig?.baseUrl) {
169
- // Remove trailing slash from base URL and leading slash from relative URL
170
- const baseUrl = currentConfig.baseUrl.replace(/\/$/, '');
171
- url = `${baseUrl}${url}`;
172
- }
165
+ if (url.startsWith('/') && endpointConfig?.baseUrl) {
166
+ // Remove trailing slash from base URL and leading slash from relative URL
167
+ const baseUrl = endpointConfig.baseUrl.replace(/\/$/, '');
168
+ url = `${baseUrl}${url}`;
173
169
  }
174
170
  // Prepare request options
175
171
  const method = endpoint.method ?? 'GET';
@@ -179,19 +175,6 @@ export async function fetchDynamicSchema(endpoint, node, workflowId) {
179
175
  'Content-Type': 'application/json',
180
176
  ...endpoint.headers
181
177
  };
182
- // Add auth headers from endpoint config if available
183
- const currentConfig = getEndpointConfig();
184
- if (currentConfig?.auth) {
185
- if (currentConfig.auth.type === 'bearer' && currentConfig.auth.token) {
186
- headers['Authorization'] = `Bearer ${currentConfig.auth.token}`;
187
- }
188
- else if (currentConfig.auth.type === 'api_key' && currentConfig.auth.apiKey) {
189
- headers['X-API-Key'] = currentConfig.auth.apiKey;
190
- }
191
- else if (currentConfig.auth.type === 'custom' && currentConfig.auth.headers) {
192
- Object.assign(headers, currentConfig.auth.headers);
193
- }
194
- }
195
178
  // Prepare fetch options
196
179
  const fetchOptions = {
197
180
  method,
@@ -7,24 +7,15 @@
7
7
  * reimplementing the logic, ensuring "blur active element" flushing and metadata
8
8
  * construction happen in exactly one place.
9
9
  */
10
+ import { type FlowDropInstance } from '../stores/instanceContainer.svelte.js';
10
11
  import type { Workflow } from '../types/index.js';
11
12
  import type { FlowDropEventHandlers, FlowDropFeatures } from '../types/events.js';
12
- /**
13
- * Minimal interface for the enhanced API client used when an authProvider is present.
14
- * Matches the surface of EnhancedFlowDropApiClient that save needs.
15
- */
16
- export interface SaveApiClient {
17
- saveWorkflow(workflow: Workflow): Promise<Workflow>;
18
- updateWorkflow(id: string, workflow: Workflow): Promise<Workflow>;
19
- }
20
13
  /**
21
14
  * Options accepted by globalSaveWorkflow().
22
15
  * All fields are optional — omitting them falls back to the basic behaviour
23
- * (no event handlers, always show toasts, legacy workflowApi).
16
+ * (no event handlers, always show toasts).
24
17
  */
25
18
  export interface GlobalSaveOptions {
26
- /** Enhanced API client with authProvider support. Falls back to legacy workflowApi when absent. */
27
- apiClient?: SaveApiClient | null;
28
19
  /** Event handler hooks (onBeforeSave, onAfterSave, onSaveError, onApiError). */
29
20
  eventHandlers?: FlowDropEventHandlers;
30
21
  /** Feature flags (showToasts). Defaults to DEFAULT_FEATURES. */
@@ -41,6 +32,11 @@ export interface GlobalSaveOptions {
41
32
  * Use this to update draft storage keys or other ID-dependent state.
42
33
  */
43
34
  onSaved?: (savedWorkflow: Workflow) => void;
35
+ /**
36
+ * The FlowDrop instance whose workflow should be saved.
37
+ * Defaults to the page-default instance when omitted.
38
+ */
39
+ instance?: FlowDropInstance;
44
40
  }
45
41
  /**
46
42
  * Options accepted by globalExportWorkflow().
@@ -48,6 +44,11 @@ export interface GlobalSaveOptions {
48
44
  export interface GlobalExportOptions {
49
45
  /** Feature flags (showToasts). Defaults to DEFAULT_FEATURES. */
50
46
  features?: Partial<FlowDropFeatures>;
47
+ /**
48
+ * The FlowDrop instance whose workflow should be exported.
49
+ * Defaults to the page-default instance when omitted.
50
+ */
51
+ instance?: FlowDropInstance;
51
52
  }
52
53
  /**
53
54
  * Save the current workflow to the backend.
@@ -59,7 +60,7 @@ export interface GlobalExportOptions {
59
60
  * 1. Flush pending form changes (blur active element + tick)
60
61
  * 2. Optionally call onBeforeSave — return false cancels the save
61
62
  * 3. Build the canonical Workflow object (preserving metadata, format, etc.)
62
- * 4. Persist via enhanced apiClient or legacy workflowApi
63
+ * 4. Persist via the instance's API client (fd.api.client)
63
64
  * 5. Update the store if the server assigned a new ID
64
65
  * 6. Call onMarkAsSaved / onAfterSave hooks
65
66
  * 7. Show toast notifications (respecting features.showToasts)