From 35eb0286e5726116853de7399005d1c8be03250b Mon Sep 17 00:00:00 2001 From: Alexander Brown <448862+DrJKL@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:44:52 -0800 Subject: [PATCH] refactor: move workflow loading state from bootstrapStore to workflowStore - Add useAsyncState in workflowStore for syncWorkflows with loading state - Add loadWorkflows action that guards against double-loading - Simplify bootstrapStore by delegating workflow loading to workflowStore - Update test mocks for workflowStore Amp-Thread-ID: https://ampcode.com/threads/T-019bfcce-cce0-70b0-abc6-742669ac86e3 Co-authored-by: Amp --- .../management/stores/workflowStore.ts | 93 ++++++++++++------- src/stores/bootstrapStore.test.ts | 22 +---- src/stores/bootstrapStore.ts | 45 +-------- 3 files changed, 65 insertions(+), 95 deletions(-) diff --git a/src/platform/workflow/management/stores/workflowStore.ts b/src/platform/workflow/management/stores/workflowStore.ts index 7a3df1942..ceaaedcaa 100644 --- a/src/platform/workflow/management/stores/workflowStore.ts +++ b/src/platform/workflow/management/stores/workflowStore.ts @@ -1,4 +1,5 @@ import _ from 'es-toolkit/compat' +import { useAsyncState } from '@vueuse/core' import { defineStore } from 'pinia' import { computed, markRaw, ref, shallowRef, watch } from 'vue' import type { Raw } from 'vue' @@ -500,48 +501,67 @@ export const useWorkflowStore = defineStore('workflow', () => { workflow.isPersisted && !workflow.path.startsWith('subgraphs/') ) ) - const syncWorkflows = async (dir: string = '') => { - await syncEntities( - dir ? 'workflows/' + dir : 'workflows', - workflowLookup.value, - (file) => - new ComfyWorkflow({ - path: file.path, - modified: file.modified, - size: file.size - }), - (existingWorkflow, file) => { - const isActiveWorkflow = - activeWorkflow.value?.path === existingWorkflow.path - const nextLastModified = Math.max( - existingWorkflow.lastModified, - file.modified - ) + const { + isReady: isSyncReady, + isLoading: isSyncLoading, + execute: executeSyncWorkflows + } = useAsyncState( + async (dir: string = '') => { + await syncEntities( + dir ? 'workflows/' + dir : 'workflows', + workflowLookup.value, + (file) => + new ComfyWorkflow({ + path: file.path, + modified: file.modified, + size: file.size + }), + (existingWorkflow, file) => { + const isActiveWorkflow = + activeWorkflow.value?.path === existingWorkflow.path - const isMetadataUnchanged = - nextLastModified === existingWorkflow.lastModified && - file.size === existingWorkflow.size + const nextLastModified = Math.max( + existingWorkflow.lastModified, + file.modified + ) - if (!isMetadataUnchanged) { - existingWorkflow.lastModified = nextLastModified - existingWorkflow.size = file.size - } + const isMetadataUnchanged = + nextLastModified === existingWorkflow.lastModified && + file.size === existingWorkflow.size - // Never unload the active workflow - it may contain unsaved in-memory edits. - if (isActiveWorkflow) { - return - } + if (!isMetadataUnchanged) { + existingWorkflow.lastModified = nextLastModified + existingWorkflow.size = file.size + } - // If nothing changed, keep any loaded content cached. - if (isMetadataUnchanged) { - return - } + // Never unload the active workflow - it may contain unsaved in-memory edits. + if (isActiveWorkflow) { + return + } - existingWorkflow.unload() - }, - /* exclude */ (workflow) => workflow.isTemporary - ) + // If nothing changed, keep any loaded content cached. + if (isMetadataUnchanged) { + return + } + + existingWorkflow.unload() + }, + /* exclude */ (workflow) => workflow.isTemporary + ) + }, + undefined, + { immediate: false } + ) + + async function syncWorkflows(dir: string = '') { + return executeSyncWorkflows(0, dir) + } + + async function loadWorkflows() { + if (!isSyncReady.value && !isSyncLoading.value) { + return syncWorkflows() + } } const bookmarkStore = useWorkflowBookmarkStore() @@ -849,6 +869,7 @@ export const useWorkflowStore = defineStore('workflow', () => { modifiedWorkflows, getWorkflowByPath, syncWorkflows, + loadWorkflows, isSubgraphActive, activeSubgraph, diff --git a/src/stores/bootstrapStore.test.ts b/src/stores/bootstrapStore.test.ts index 3013dfbc8..f0249e65f 100644 --- a/src/stores/bootstrapStore.test.ts +++ b/src/stores/bootstrapStore.test.ts @@ -33,11 +33,10 @@ vi.mock('@/platform/settings/settingStore', () => ({ })) })) -vi.mock('@/stores/workspaceStore', () => ({ - useWorkspaceStore: vi.fn(() => ({ - workflow: { - syncWorkflows: vi.fn().mockResolvedValue(undefined) - } +vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({ + useWorkflowStore: vi.fn(() => ({ + loadWorkflows: vi.fn(), + syncWorkflows: vi.fn().mockResolvedValue(undefined) })) })) @@ -53,23 +52,10 @@ describe('bootstrapStore', () => { it('initializes with all flags false', () => { const settingStore = useSettingStore() - expect(store.isNodeDefsReady).toBe(false) expect(settingStore.isReady).toBe(false) expect(store.isI18nReady).toBe(false) }) - it('starts early bootstrap (node defs)', async () => { - const { api } = await import('@/scripts/api') - - store.startEarlyBootstrap() - - await vi.waitFor(() => { - expect(store.isNodeDefsReady).toBe(true) - }) - - expect(api.getNodeDefs).toHaveBeenCalled() - }) - it('starts store bootstrap (settings, i18n)', async () => { const settingStore = useSettingStore() void store.startStoreBootstrap() diff --git a/src/stores/bootstrapStore.ts b/src/stores/bootstrapStore.ts index 43c9531eb..ae875c1b8 100644 --- a/src/stores/bootstrapStore.ts +++ b/src/stores/bootstrapStore.ts @@ -2,26 +2,13 @@ import { useAsyncState } from '@vueuse/core' import { defineStore } from 'pinia' import { useSettingStore } from '@/platform/settings/settingStore' -import type { ComfyNodeDef } from '@/schemas/nodeDefSchema' +import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' import { api } from '@/scripts/api' import { useUserStore } from '@/stores/userStore' export const useBootstrapStore = defineStore('bootstrap', () => { const settingStore = useSettingStore() - - const { - state: nodeDefs, - isReady: isNodeDefsReady, - error: nodeDefsError, - execute: fetchNodeDefs - } = useAsyncState>( - async () => { - const defs = await api.getNodeDefs() - return defs - }, - {}, - { immediate: false } - ) + const workflowStore = useWorkflowStore() const { isReady: isI18nReady, @@ -37,28 +24,7 @@ export const useBootstrapStore = defineStore('bootstrap', () => { { immediate: false } ) - const { - isReady: isWorkflowsReady, - isLoading: isWorkflowsLoading, - execute: executeSyncWorkflows - } = useAsyncState( - async () => { - const { useWorkspaceStore } = await import('@/stores/workspaceStore') - await useWorkspaceStore().workflow.syncWorkflows() - }, - undefined, - { immediate: false } - ) - - function syncWorkflows() { - if (!isWorkflowsReady.value && !isWorkflowsLoading.value) { - void executeSyncWorkflows() - } - } - - function startEarlyBootstrap() { - void fetchNodeDefs() - } + function startEarlyBootstrap() {} async function startStoreBootstrap() { // Defer settings and workflows if multi-user login is required @@ -71,14 +37,11 @@ export const useBootstrapStore = defineStore('bootstrap', () => { if (!userStore.needsLogin) { await settingStore.load() - syncWorkflows() + await workflowStore.loadWorkflows() } } return { - nodeDefs, - isNodeDefsReady, - nodeDefsError, isI18nReady, i18nError, startEarlyBootstrap,