@flowdrop/flowdrop 1.15.0 → 2.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +508 -0
- package/MIGRATION-2.0.md +629 -0
- package/README.md +23 -23
- package/dist/adapters/WorkflowAdapter.d.ts +1 -1
- package/dist/adapters/WorkflowAdapter.js +14 -8
- package/dist/adapters/agentspec/AgentSpecAdapter.js +7 -7
- package/dist/api/enhanced-client.js +6 -11
- package/dist/chat/batchFeedback.d.ts +39 -0
- package/dist/chat/batchFeedback.js +51 -0
- package/dist/commands/executor.js +15 -1
- package/dist/commands/storeIntegration.svelte.d.ts +4 -1
- package/dist/commands/storeIntegration.svelte.js +26 -21
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/App.svelte +163 -192
- package/dist/components/App.svelte.d.ts +47 -8
- package/dist/components/ConfigForm.svelte +77 -49
- package/dist/components/ConfigModal.svelte +7 -2
- package/dist/components/ConnectionLine.svelte +4 -2
- package/dist/components/Navbar.svelte +61 -1
- package/dist/components/NodeSidebar.svelte +27 -45
- package/dist/components/NodeStatusOverlay.svelte +94 -6
- package/dist/components/NodeSwapPicker.svelte +10 -8
- package/dist/components/PipelineStatus.svelte +22 -68
- package/dist/components/PipelineStatus.svelte.d.ts +3 -0
- package/dist/components/PortCoordinateTracker.svelte +5 -6
- package/dist/components/SchemaForm.stories.svelte +1 -3
- package/dist/components/SchemaForm.svelte +22 -27
- package/dist/components/SchemaForm.svelte.d.ts +0 -8
- package/dist/components/SettingsModal.svelte +8 -3
- package/dist/components/SettingsPanel.svelte +20 -4
- package/dist/components/SwapMappingEditor.svelte +67 -49
- package/dist/components/SwapMappingEditor.svelte.d.ts +0 -2
- package/dist/components/UniversalNode.svelte +9 -7
- package/dist/components/WorkflowEditor.svelte +121 -111
- package/dist/components/WorkflowEditor.svelte.d.ts +21 -10
- package/dist/components/chat/AIChatPanel.svelte +98 -89
- package/dist/components/chat/AIChatPanel.svelte.d.ts +0 -4
- package/dist/components/chat/CommandPreview.svelte +2 -1
- package/dist/components/console/CommandConsole.svelte +7 -5
- package/dist/components/console/ConsoleAutocomplete.svelte +10 -11
- package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +6 -0
- package/dist/components/console/ConsoleInput.svelte +15 -6
- package/dist/components/console/ConsoleOutput.svelte +2 -1
- package/dist/components/form/FormArray.svelte +5 -9
- package/dist/components/form/FormArray.svelte.d.ts +2 -1
- package/dist/components/form/FormAutocomplete.svelte +16 -15
- package/dist/components/form/FormField.svelte +4 -2
- package/dist/components/form/FormFieldLight.svelte +34 -3
- package/dist/components/form/FormFieldLight.svelte.d.ts +12 -0
- package/dist/components/form/FormMarkdownEditor.svelte +9 -4
- package/dist/components/form/FormRangeField.svelte +1 -0
- package/dist/components/form/FormTemplateEditor.svelte +11 -3
- package/dist/components/form/FormToggle.svelte +5 -12
- package/dist/components/form/FormToggle.svelte.d.ts +4 -2
- package/dist/components/form/FormUISchemaRenderer.svelte +3 -1
- package/dist/components/form/templateAutocomplete.js +1 -5
- package/dist/components/form/types.d.ts +1 -14
- package/dist/components/interrupt/FormPrompt.svelte +3 -2
- package/dist/components/interrupt/InterruptBubble.svelte +25 -17
- package/dist/components/interrupt/ReviewPrompt.svelte +10 -3
- package/dist/components/interrupt/TextInputPrompt.svelte +2 -1
- package/dist/components/layouts/MainLayout.svelte +20 -13
- package/dist/components/layouts/MainLayout.svelte.d.ts +4 -0
- package/dist/components/nodes/AtomNode.svelte +17 -5
- package/dist/components/nodes/GatewayNode.svelte +19 -10
- package/dist/components/nodes/IdeaNode.svelte +7 -0
- package/dist/components/nodes/SimpleNode.svelte +11 -6
- package/dist/components/nodes/SquareNode.svelte +15 -8
- package/dist/components/nodes/TerminalNode.svelte +9 -4
- package/dist/components/nodes/ToolNode.svelte +7 -1
- package/dist/components/nodes/WorkflowNode.svelte +16 -7
- package/dist/components/playground/ChatInput.svelte +11 -14
- package/dist/components/playground/ChatPanel.svelte +6 -49
- package/dist/components/playground/ChatPanel.svelte.d.ts +0 -14
- package/dist/components/playground/ControlPanel.svelte +134 -123
- package/dist/components/playground/ControlPanel.svelte.d.ts +3 -0
- package/dist/components/playground/ExecutionLogs.svelte +11 -9
- package/dist/components/playground/InputCollector.svelte +11 -9
- package/dist/components/playground/MessageStream.svelte +17 -23
- package/dist/components/playground/PipelineKanbanView.svelte +69 -8
- package/dist/components/playground/PipelineKanbanView.svelte.d.ts +2 -0
- package/dist/components/playground/PipelinePanel.svelte +31 -8
- package/dist/components/playground/PipelinePanel.svelte.d.ts +2 -0
- package/dist/components/playground/PipelineTableView.svelte +188 -44
- package/dist/components/playground/PipelineTableView.svelte.d.ts +2 -0
- package/dist/components/playground/Playground.svelte +154 -105
- package/dist/components/playground/Playground.svelte.d.ts +5 -0
- package/dist/components/playground/PlaygroundApp.svelte +11 -1
- package/dist/components/playground/PlaygroundApp.svelte.d.ts +6 -0
- package/dist/components/playground/PlaygroundModal.svelte +18 -3
- package/dist/components/playground/PlaygroundModal.svelte.d.ts +6 -0
- package/dist/components/playground/PlaygroundStudio.svelte +40 -32
- package/dist/components/playground/PlaygroundStudio.svelte.d.ts +6 -0
- package/dist/components/playground/SessionManager.svelte +9 -12
- package/dist/components/playground/pipelineViewUtils.svelte.d.ts +30 -1
- package/dist/components/playground/pipelineViewUtils.svelte.js +40 -3
- package/dist/config/endpoints.d.ts +23 -7
- package/dist/config/endpoints.js +30 -10
- package/dist/core/index.d.ts +5 -6
- package/dist/core/index.js +8 -12
- package/dist/display/index.d.ts +6 -3
- package/dist/display/index.js +7 -5
- package/dist/editor/index.d.ts +20 -21
- package/dist/editor/index.js +26 -36
- package/dist/form/code.d.ts +25 -15
- package/dist/form/code.js +44 -41
- package/dist/form/fieldRegistry.d.ts +17 -13
- package/dist/form/fieldRegistry.js +32 -12
- package/dist/form/full.d.ts +19 -14
- package/dist/form/full.js +26 -28
- package/dist/form/index.d.ts +3 -4
- package/dist/form/index.js +6 -5
- package/dist/form/markdown.d.ts +13 -8
- package/dist/form/markdown.js +22 -23
- package/dist/helpers/proximityConnect.d.ts +3 -2
- package/dist/helpers/proximityConnect.js +2 -5
- package/dist/helpers/workflowEditorHelper.d.ts +14 -5
- package/dist/helpers/workflowEditorHelper.js +28 -25
- package/dist/index.d.ts +28 -24
- package/dist/index.js +27 -50
- package/dist/messages/defaults.d.ts +2 -5
- package/dist/messages/defaults.js +3 -6
- package/dist/messages/index.d.ts +0 -1
- package/dist/messages/index.js +0 -1
- package/dist/mocks/app-forms.d.ts +6 -2
- package/dist/mocks/app-forms.js +11 -4
- package/dist/openapi/v1/openapi.yaml +3 -3
- package/dist/playground/index.d.ts +4 -5
- package/dist/playground/index.js +4 -32
- package/dist/playground/mount.d.ts +25 -0
- package/dist/playground/mount.js +50 -20
- package/dist/registry/{BaseRegistry.d.ts → BaseRegistry.svelte.d.ts} +22 -1
- package/dist/registry/{BaseRegistry.js → BaseRegistry.svelte.js} +37 -1
- package/dist/registry/builtinFormats.d.ts +9 -18
- package/dist/registry/builtinFormats.js +9 -39
- package/dist/registry/builtinNodeTypes.d.ts +53 -0
- package/dist/registry/builtinNodeTypes.js +67 -0
- package/dist/registry/builtinNodes.d.ts +2 -64
- package/dist/registry/builtinNodes.js +7 -103
- package/dist/registry/index.d.ts +3 -4
- package/dist/registry/index.js +4 -6
- package/dist/registry/nodeComponentRegistry.d.ts +182 -15
- package/dist/registry/nodeComponentRegistry.js +235 -17
- package/dist/registry/workflowFormatRegistry.d.ts +14 -9
- package/dist/registry/workflowFormatRegistry.js +24 -8
- package/dist/{schema → schemas}/index.d.ts +2 -2
- package/dist/{schema → schemas}/index.js +2 -2
- package/dist/schemas/v1/workflow.schema.json +3 -3
- package/dist/services/agentSpecExecutionService.d.ts +0 -2
- package/dist/services/agentSpecExecutionService.js +0 -3
- package/dist/services/apiVariableService.d.ts +2 -1
- package/dist/services/apiVariableService.js +16 -47
- package/dist/services/autoSaveService.d.ts +7 -0
- package/dist/services/autoSaveService.js +6 -4
- package/dist/services/categoriesApi.js +3 -6
- package/dist/services/chatService.d.ts +9 -4
- package/dist/services/chatService.js +23 -28
- package/dist/services/draftStorage.d.ts +129 -13
- package/dist/services/draftStorage.js +185 -37
- package/dist/services/dynamicSchemaService.d.ts +2 -1
- package/dist/services/dynamicSchemaService.js +5 -22
- package/dist/services/globalSave.d.ts +13 -12
- package/dist/services/globalSave.js +29 -51
- package/dist/services/historyService.d.ts +9 -3
- package/dist/services/historyService.js +9 -3
- package/dist/services/interruptService.d.ts +15 -9
- package/dist/services/interruptService.js +35 -37
- package/dist/services/nodeExecutionService.d.ts +18 -3
- package/dist/services/nodeExecutionService.js +71 -45
- package/dist/services/playgroundService.d.ts +16 -10
- package/dist/services/playgroundService.js +42 -43
- package/dist/services/portConfigApi.js +3 -6
- package/dist/services/settingsService.d.ts +9 -4
- package/dist/services/settingsService.js +23 -12
- package/dist/services/variableService.d.ts +2 -1
- package/dist/services/variableService.js +2 -2
- package/dist/services/workflowStorage.js +6 -6
- package/dist/stores/apiContext.d.ts +56 -0
- package/dist/stores/apiContext.js +80 -0
- package/dist/stores/categoriesStore.svelte.d.ts +28 -23
- package/dist/stores/categoriesStore.svelte.js +69 -64
- package/dist/stores/getInstance.svelte.d.ts +39 -0
- package/dist/stores/getInstance.svelte.js +65 -0
- package/dist/stores/historyStore.svelte.d.ts +77 -93
- package/dist/stores/historyStore.svelte.js +134 -160
- package/dist/stores/instanceContainer.svelte.d.ts +111 -0
- package/dist/stores/instanceContainer.svelte.js +114 -0
- package/dist/stores/interruptStore.svelte.d.ts +112 -82
- package/dist/stores/interruptStore.svelte.js +253 -226
- package/dist/stores/pipelinePanelStore.svelte.d.ts +27 -3
- package/dist/stores/pipelinePanelStore.svelte.js +61 -14
- package/dist/stores/playgroundStore.svelte.d.ts +169 -222
- package/dist/stores/playgroundStore.svelte.js +513 -580
- package/dist/stores/portCoordinateStore.svelte.d.ts +57 -51
- package/dist/stores/portCoordinateStore.svelte.js +109 -98
- package/dist/stores/settingsStore.svelte.d.ts +4 -1
- package/dist/stores/settingsStore.svelte.js +47 -12
- package/dist/stores/workflowStore.svelte.d.ts +178 -213
- package/dist/stores/workflowStore.svelte.js +449 -501
- package/dist/stories/EdgeDecorator.svelte +5 -2
- package/dist/stories/NodeDecorator.svelte +5 -3
- package/dist/svelte-app.d.ts +60 -10
- package/dist/svelte-app.js +159 -54
- package/dist/types/auth.d.ts +9 -51
- package/dist/types/auth.js +4 -54
- package/dist/types/events.d.ts +6 -3
- package/dist/types/index.d.ts +37 -5
- package/dist/types/index.js +0 -1
- package/dist/types/navbar.d.ts +7 -0
- package/dist/types/playground.d.ts +18 -3
- package/dist/types/settings.d.ts +13 -0
- package/dist/types/settings.js +1 -0
- package/dist/utils/colors.d.ts +47 -21
- package/dist/utils/colors.js +69 -68
- package/dist/utils/connections.d.ts +9 -15
- package/dist/utils/connections.js +13 -32
- package/dist/utils/duration.d.ts +13 -0
- package/dist/utils/duration.js +45 -0
- package/dist/utils/edgeStyling.js +9 -5
- package/dist/utils/fetchWithAuth.d.ts +36 -15
- package/dist/utils/fetchWithAuth.js +53 -23
- package/dist/utils/icons.d.ts +5 -2
- package/dist/utils/icons.js +6 -5
- package/dist/utils/nodeSwap.d.ts +6 -2
- package/dist/utils/nodeSwap.js +62 -126
- package/dist/utils/nodeTypes.d.ts +17 -8
- package/dist/utils/nodeTypes.js +27 -20
- package/dist/utils/performanceUtils.js +7 -0
- package/package.json +7 -5
- package/dist/messages/deprecation.d.ts +0 -20
- package/dist/messages/deprecation.js +0 -33
- package/dist/registry/plugin.d.ts +0 -215
- package/dist/registry/plugin.js +0 -249
- package/dist/services/api.d.ts +0 -129
- 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
|
|
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 `${
|
|
123
|
+
return `${prefix}:${workflowId}`;
|
|
30
124
|
}
|
|
31
|
-
return `${
|
|
125
|
+
return `${prefix}:new`;
|
|
32
126
|
}
|
|
33
127
|
/**
|
|
34
|
-
*
|
|
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
|
-
|
|
174
|
+
storage.setItem(storageKey, JSON.stringify(draft));
|
|
51
175
|
return true;
|
|
52
176
|
}
|
|
53
177
|
catch (error) {
|
|
54
|
-
//
|
|
55
|
-
logger.warn('Failed to save draft to
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
217
|
+
storage.removeItem(storageKey);
|
|
92
218
|
}
|
|
93
219
|
catch (error) {
|
|
94
|
-
logger.warn('Failed to delete draft from
|
|
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
|
|
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
|
|
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 (
|
|
139
|
-
|
|
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 (
|
|
273
|
+
if (storage.getItem(key) !== null) {
|
|
146
274
|
keysToRemove.add(key);
|
|
147
275
|
}
|
|
148
276
|
}
|
|
149
277
|
for (const key of keysToRemove) {
|
|
150
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
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
|
|
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)
|