import { atom } from "jotai"; import { selectAtom } from "jotai/utils"; /** Model map: each key is a model type name, each value is the model shape (must have id). */ type ModelMap = Record; /** The store data shape derived from the model map. */ type StoreData = { [K in keyof M]: Record; }; export type ModelChange = { type: "upsert" } | { type: "delete" }; function emptyStore(keys: (keyof M)[]): StoreData { const data = {} as StoreData; for (const k of keys) { data[k] = {} as Record; } return data; } export function createModelStore(modelTypes: (keyof M & string)[]) { const dataAtom = atom>(emptyStore(modelTypes)); /** Apply a single upsert or delete to the store. */ function applyChange( prev: StoreData, modelType: K, model: M[K], change: ModelChange, ): StoreData { if (change.type === "upsert") { return { ...prev, [modelType]: { ...prev[modelType], [model.id]: model }, }; } else { const bucket = { ...prev[modelType] }; delete bucket[model.id]; return { ...prev, [modelType]: bucket }; } } /** Atom that selects all models of a given type as an array. */ function listAtom(modelType: K) { return selectAtom( dataAtom, (data) => Object.values(data[modelType] ?? {}) as M[K][], shallowEqual, ); } /** Atom that selects all models of a given type, sorted by a field. */ function orderedListAtom( modelType: K, field: keyof M[K], order: "asc" | "desc", ) { return selectAtom( dataAtom, (data) => { const vals = Object.values(data[modelType] ?? {}) as M[K][]; return vals.sort((a, b) => { const n = a[field] > b[field] ? 1 : -1; return order === "desc" ? -n : n; }); }, shallowEqual, ); } /** Replace all models of a given type. Used for initial hydration. */ function replaceAll( prev: StoreData, modelType: K, models: M[K][], ): StoreData { const bucket = {} as Record; for (const m of models) { bucket[m.id] = m; } return { ...prev, [modelType]: bucket }; } return { dataAtom, applyChange, replaceAll, listAtom, orderedListAtom }; } function shallowEqual(a: T[], b: T[]): boolean { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; }