import { useStore } from 'zustand'; import { createStore, type StoreApi } from 'zustand/vanilla'; import { configureDataService, getCodingPlugins, inspectDataService, removeDataServiceCollection, removeDataServiceProject, resetDataService, setCodingPluginEnabled, type CodingPluginProject, } from '@/lib/coding-plugins'; import type { DataServiceInstanceState } from '../../shared/data-service'; type Dependencies = { list: typeof getCodingPlugins; setEnabled: typeof setCodingPluginEnabled; inspectDataService: typeof inspectDataService; configureDataService: typeof configureDataService; resetDataService: typeof resetDataService; removeCollection: typeof removeDataServiceCollection; removeProject: typeof removeDataServiceProject; }; export type CodingPluginsState = { projectId: string | null; projection: CodingPluginProject | null; dataService: DataServiceInstanceState | null; loadState: 'idle' | 'loading' | 'ready' | 'error'; error: string | null; pending: Record; load(projectId: string): Promise; setEnabled(projectId: string, pluginId: string, enabled: boolean): Promise; configure(collections: string[]): Promise; reset(): Promise; removeCollection(collection: string): Promise; removeProject(): Promise; }; export function createCodingPluginsStore(overrides: Partial = {}): StoreApi { const deps: Dependencies = { list: getCodingPlugins, setEnabled: setCodingPluginEnabled, inspectDataService, configureDataService, resetDataService, removeCollection: removeDataServiceCollection, removeProject: removeDataServiceProject, ...overrides, }; const flights = new Map>(); const loadFlightGenerations = new Map(); const pendingCounts = new Map(); const projectEpochs = new Map(); let loadGeneration = 0; let latestLoadProjectId: string | null = null; const projectEpoch = (projectId: string): number => projectEpochs.get(projectId) ?? 0; const advanceProjectEpoch = (projectId: string): void => { projectEpochs.set(projectId, projectEpoch(projectId) + 1); }; const isCurrentProject = (projectId: string | null): projectId is string => ( projectId !== null && store.getState().projectId === projectId && (latestLoadProjectId === null || latestLoadProjectId === projectId) ); const operation = ( key: string, run: () => Promise, pendingKey = key, ): Promise => { const existing = flights.get(key); if (existing) return existing; const pendingCount = (pendingCounts.get(pendingKey) ?? 0) + 1; pendingCounts.set(pendingKey, pendingCount); let flight: Promise; flight = run().finally(() => { if (flights.get(key) === flight) { flights.delete(key); loadFlightGenerations.delete(key); } const remaining = (pendingCounts.get(pendingKey) ?? 1) - 1; if (remaining > 0) { pendingCounts.set(pendingKey, remaining); } else { pendingCounts.delete(pendingKey); const pending = { ...store.getState().pending }; delete pending[pendingKey]; store.setState({ pending }); } }); flights.set(key, flight); if (pendingCount === 1) { store.setState((state) => ({ pending: { ...state.pending, [pendingKey]: true } })); } return flight; }; const store = createStore((set, get) => ({ projectId: null, projection: null, dataService: null, loadState: 'idle', error: null, pending: {}, load(projectId) { latestLoadProjectId = projectId; const epoch = projectEpoch(projectId); const pendingKey = `load:${projectId}`; const key = `${pendingKey}:${epoch}`; const existing = flights.get(key); if (existing) { loadFlightGenerations.set(key, ++loadGeneration); return existing; } loadFlightGenerations.set(key, ++loadGeneration); return operation(key, async () => { set({ loadState: 'loading', error: null }); try { const projection = await deps.list(projectId); let dataService = get().projectId === projectId ? get().dataService : null; const item = projection.items.find(({ settingsSurface }) => settingsSurface === 'data-service'); if (item?.enabled && item.backend.status === 'ready') { const result = await deps.inspectDataService(); if (result.success && result.data) dataService = result.data; } if (loadFlightGenerations.get(key) === loadGeneration && epoch === projectEpoch(projectId)) { set({ projectId, projection, dataService, loadState: 'ready', error: null }); } } catch (error) { if (loadFlightGenerations.get(key) === loadGeneration && epoch === projectEpoch(projectId)) { set({ loadState: 'error', error: error instanceof Error ? error.message : String(error) }); } throw error; } }, pendingKey); }, setEnabled(projectId, pluginId, enabled) { return operation(`enabled:${projectId}:${pluginId}`, async () => { const projection = await deps.setEnabled(projectId, pluginId, enabled); if (!isCurrentProject(projectId)) return; let dataService = get().dataService; const item = projection.items.find(({ id }) => id === pluginId); if (!enabled) { dataService = null; } else if (item?.settingsSurface === 'data-service' && item.backend.status === 'ready') { const inspected = await deps.inspectDataService(); if (!isCurrentProject(projectId)) return; if (inspected.success && inspected.data) dataService = inspected.data; } advanceProjectEpoch(projectId); set({ projectId, projection, dataService, loadState: 'ready', error: null, }); }); }, configure(collections) { const projectId = get().projectId; return operation('data-service:configure', async () => { const result = await deps.configureDataService(collections); if (!result.success || !result.data) throw new Error(result.error || '开发数据空间创建失败'); if (!isCurrentProject(projectId)) return; advanceProjectEpoch(projectId); set({ dataService: result.data, loadState: 'ready', error: null }); await get().load(projectId); }); }, reset() { const projectId = get().projectId; return operation('data-service:reset', async () => { const result = await deps.resetDataService(); if (!result.success || !result.data) throw new Error(result.error || '开发数据重置失败'); if (!isCurrentProject(projectId)) return; advanceProjectEpoch(projectId); set({ dataService: result.data, loadState: 'ready' }); }); }, removeCollection(collection) { const projectId = get().projectId; return operation(`data-service:collection:${collection}`, async () => { const result = await deps.removeCollection(collection); if (!result.success) throw new Error(result.error || '移除 collection 失败'); if (!isCurrentProject(projectId)) return; const inspected = await deps.inspectDataService(); if (!isCurrentProject(projectId)) return; advanceProjectEpoch(projectId); if (inspected.success && inspected.data) { set({ dataService: inspected.data, loadState: 'ready' }); } else { await get().load(projectId); } }); }, removeProject() { const projectId = get().projectId; return operation('data-service:remove-project', async () => { const result = await deps.removeProject(); if (!result.success) throw new Error(result.error || '删除开发数据空间失败'); if (!isCurrentProject(projectId)) return; advanceProjectEpoch(projectId); set({ dataService: null, loadState: 'ready' }); await get().load(projectId); }); }, })); return store; } export const codingPluginsStore = createCodingPluginsStore(); export function useCodingPluginsStore(selector: (state: CodingPluginsState) => T): T { return useStore(codingPluginsStore, selector); }