feat: add project plugin center

This commit is contained in:
2026-08-27 18:36:45 +08:00
parent b6d9e6156f
commit cb1fd2629c
15 changed files with 982 additions and 1 deletions

View File

@@ -0,0 +1,124 @@
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<string, true>;
load(projectId: string): Promise<void>;
setEnabled(projectId: string, pluginId: string, enabled: boolean): Promise<void>;
configure(collections: string[]): Promise<void>;
reset(): Promise<void>;
removeCollection(collection: string): Promise<void>;
removeProject(): Promise<void>;
};
export function createCodingPluginsStore(overrides: Partial<Dependencies> = {}): StoreApi<CodingPluginsState> {
const deps: Dependencies = {
list: getCodingPlugins, setEnabled: setCodingPluginEnabled, inspectDataService,
configureDataService, resetDataService, removeCollection: removeDataServiceCollection,
removeProject: removeDataServiceProject, ...overrides,
};
const flights = new Map<string, Promise<void>>();
const operation = (key: string, run: () => Promise<void>): Promise<void> => {
const existing = flights.get(key); if (existing) return existing;
let flight: Promise<void>;
flight = run().finally(() => {
flights.delete(key);
const pending = { ...store.getState().pending }; delete pending[key]; store.setState({ pending });
});
flights.set(key, flight); store.setState((state) => ({ pending: { ...state.pending, [key]: true } }));
return flight;
};
const store = createStore<CodingPluginsState>((set, get) => ({
projectId: null, projection: null, dataService: null, loadState: 'idle', error: null, pending: {},
load(projectId) {
return operation(`load:${projectId}`, 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;
}
set({ projectId, projection, dataService, loadState: 'ready' });
} catch (error) {
set({ loadState: 'error', error: error instanceof Error ? error.message : String(error) });
throw error;
}
});
},
setEnabled(projectId, pluginId, enabled) {
return operation(`enabled:${projectId}:${pluginId}`, async () => {
const projection = await deps.setEnabled(projectId, pluginId, enabled);
set({
projectId, projection, error: null,
...(!enabled || get().projectId !== projectId ? { dataService: null } : {}),
});
});
},
configure(collections) {
return operation('data-service:configure', async () => {
const result = await deps.configureDataService(collections);
if (!result.success || !result.data) throw new Error(result.error || '开发数据空间创建失败');
set({ dataService: result.data, error: null });
if (get().projectId) await get().load(get().projectId as string);
});
},
reset() {
return operation('data-service:reset', async () => {
const result = await deps.resetDataService();
if (!result.success || !result.data) throw new Error(result.error || '开发数据重置失败');
set({ dataService: result.data });
});
},
removeCollection(collection) {
return operation(`data-service:collection:${collection}`, async () => {
const result = await deps.removeCollection(collection);
if (!result.success) throw new Error(result.error || '移除 collection 失败');
const inspected = await deps.inspectDataService();
if (inspected.success && inspected.data) set({ dataService: inspected.data });
});
},
removeProject() {
return operation('data-service:remove-project', async () => {
const result = await deps.removeProject();
if (!result.success) throw new Error(result.error || '删除开发数据空间失败');
set({ dataService: null });
if (get().projectId) await get().load(get().projectId as string);
});
},
}));
return store;
}
export const codingPluginsStore = createCodingPluginsStore();
export function useCodingPluginsStore<T>(selector: (state: CodingPluginsState) => T): T {
return useStore(codingPluginsStore, selector);
}