feat(coding): add model tools and device packages
This commit is contained in:
96
src/stores/device-packages.ts
Normal file
96
src/stores/device-packages.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { createStore, type StoreApi } from 'zustand';
|
||||
import { useStore } from 'zustand';
|
||||
import {
|
||||
readDevicePackages,
|
||||
setDevicePackageEnabled,
|
||||
uninstallDevicePackage,
|
||||
type DevicePackageIndexV1,
|
||||
} from '@/lib/device-packages';
|
||||
|
||||
type LoadState = 'idle' | 'loading' | 'ready' | 'error';
|
||||
|
||||
export interface DevicePackageDependencies {
|
||||
read(): Promise<DevicePackageIndexV1>;
|
||||
setEnabled(packageId: string, enabled: boolean): Promise<DevicePackageIndexV1>;
|
||||
uninstall(packageId: string): Promise<DevicePackageIndexV1>;
|
||||
}
|
||||
|
||||
export interface DevicePackageState {
|
||||
index: DevicePackageIndexV1 | null;
|
||||
state: LoadState;
|
||||
error: string | null;
|
||||
pending: Record<string, true>;
|
||||
load(): Promise<void>;
|
||||
setEnabled(packageId: string, enabled: boolean): Promise<void>;
|
||||
uninstall(packageId: string): Promise<void>;
|
||||
}
|
||||
|
||||
const defaults: DevicePackageDependencies = {
|
||||
read: readDevicePackages,
|
||||
setEnabled: setDevicePackageEnabled,
|
||||
uninstall: uninstallDevicePackage,
|
||||
};
|
||||
|
||||
function message(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
export function createDevicePackageStore(
|
||||
overrides: Partial<DevicePackageDependencies> = {},
|
||||
): StoreApi<DevicePackageState> {
|
||||
const dependencies = { ...defaults, ...overrides };
|
||||
let projectionIntent = 0;
|
||||
return createStore<DevicePackageState>((set) => {
|
||||
const mutate = async (
|
||||
key: string,
|
||||
operation: () => Promise<DevicePackageIndexV1>,
|
||||
): Promise<void> => {
|
||||
const intent = ++projectionIntent;
|
||||
set((current) => ({ pending: { ...current.pending, [key]: true }, error: null }));
|
||||
try {
|
||||
const index = await operation();
|
||||
if (intent === projectionIntent) set({ index, state: 'ready', error: null });
|
||||
} catch (error) {
|
||||
if (intent === projectionIntent) set({ error: message(error) });
|
||||
throw error;
|
||||
} finally {
|
||||
set((current) => {
|
||||
const pending = { ...current.pending };
|
||||
delete pending[key];
|
||||
return { pending };
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
index: null,
|
||||
state: 'idle',
|
||||
error: null,
|
||||
pending: {},
|
||||
async load() {
|
||||
const intent = ++projectionIntent;
|
||||
set({ state: 'loading', error: null });
|
||||
try {
|
||||
const index = await dependencies.read();
|
||||
if (intent === projectionIntent) set({ index, state: 'ready', error: null });
|
||||
} catch (error) {
|
||||
if (intent === projectionIntent) set({ state: 'error', error: message(error) });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
setEnabled: async (packageId, enabled) => await mutate(
|
||||
`enabled:${packageId}`,
|
||||
async () => await dependencies.setEnabled(packageId, enabled),
|
||||
),
|
||||
uninstall: async (packageId) => await mutate(
|
||||
`uninstall:${packageId}`,
|
||||
async () => await dependencies.uninstall(packageId),
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const devicePackageStore = createDevicePackageStore();
|
||||
|
||||
export function useDevicePackageStore<T>(selector: (state: DevicePackageState) => T): T {
|
||||
return useStore(devicePackageStore, selector);
|
||||
}
|
||||
Reference in New Issue
Block a user