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 <amp@ampcode.com>
This commit is contained in:
Alexander Brown
2026-01-26 16:44:52 -08:00
parent c171227d28
commit 35eb0286e5
3 changed files with 65 additions and 95 deletions

View File

@@ -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,

View File

@@ -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()

View File

@@ -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<Record<string, ComfyNodeDef>>(
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,