import type { Project } from './types'; import { getProject, listProjects } from './client'; import { ApiError } from './api'; let projects: Project[] = $state([]); // ids that failed to fetch because the project is not visible to this session // (404 not found / 403 not owner). Remembered so the sidebar can show an honest // 不可见项目 state instead of an endless 加载中… retry loop. Cleared on logout / // session switch so cross-user stale names or failure marks never leak. // $state map so derived views (sidebar) re-react when a mark is added/cleared. let invisibleIds: Record = $state({}); let inflight = new Map>(); export interface ProjectsStore { get all(): Project[]; set(items: Project[]): void; /** fetch + cache a project the sidebar/derived views cannot see yet (deep link) */ ensure(id: string): Promise; bump(p: Project): void; /** reactive lookup: reads only the $state projects array */ get(id: string): Project | undefined; load(): Promise; /** drop all cached projects + failure marks (must be called on logout so a * future session on the same browser cannot see the previous user's names) */ clear(): void; } function upsert(p: Project) { const idx = projects.findIndex((x) => x.id === p.id); if (idx >= 0) projects[idx] = p; else projects = [...projects, p]; } export const projectsStore: ProjectsStore = { get all() { return projects; }, set(items: Project[]) { projects = items; }, get(id: string) { return projects.find((p) => p.id === id); }, bump(p: Project) { upsert(p); }, async ensure(id: string) { const known = projects.find((p) => p.id === id); if (known) return known; if (invisibleIds[id]) return null; const pending = inflight.get(id); if (pending) return pending; const fetchPromise = (async () => { try { const p = await getProject(id); upsert(p); return p; } catch (e) { if (e instanceof ApiError && (e.status === 403 || e.status === 404)) { invisibleIds = { ...invisibleIds, [id]: true }; return null; } throw e; } finally { inflight.delete(id); } })(); inflight.set(id, fetchPromise); return fetchPromise; }, async load() { const { items } = await listProjects(); projects = items; return items; }, clear() { projects = []; invisibleIds = {}; inflight.clear(); } }; /** reactive derived read for components */ export function projectOf(id: string): Project | undefined { return projectsStore.get(id); } /** true when a previous ensure() proved the id is not visible for this session */ export function projectInvisible(id: string): boolean { return !!invisibleIds[id]; }