import { ReadonlySignal, Signal, computed, signal, } from "@preact-signals/unified-signals"; import type { IfNever, Opaque, ReadonlyKeysOf, SetReadonly } from "type-fest"; import { AnyRecord } from "../hooks"; import { FlatStoreSetter, setterOfFlatStore } from "./setter"; const __storeState = Symbol("store-state"); const handler: ProxyHandler = { get(target, key, self) { if (key === __storeState) { return target[key]; } const storageState = target[__storeState]; if (key in storageState) { return storageState[key]?.value; } const prop = Object.getOwnPropertyDescriptor(target, key); if (prop?.get) { storageState[key] = computed(prop.get?.bind(self)); delete target[key]; return storageState[key]?.value; } if (typeof target[key] === "function") { return target[key]; } storageState[key] = signal(target[key]); delete target[key]; return storageState[key].value; }, set(target, key, value) { if (typeof target[key] === "function") { target[key] = value; return true; } const storageState = target[__storeState]; if (key in target) { delete target[key]; } if (!storageState[key]) { storageState[key] = signal(value); return true; } storageState[key].value = value; return true; }, deleteProperty(target, key) { const storage = key in target ? target : key in target[__storeState] ? target[__storeState] : null; if (!storage) { return false; } delete storage[key]; return true; }, has(target, key) { return key in target || key in target[__storeState]; }, ownKeys(target) { return [...Object.keys(target), ...Object.keys(target[__storeState])]; }, getOwnPropertyDescriptor() { return { enumerable: true, configurable: true, }; }, }; export type FlatStore> = Opaque< T, "@preact-signals/utils;flatStore" >; export type ReadonlyFlatStore> = Readonly< FlatStore >; /** * * @param initialState this value will be **mutated** and proxied * @returns */ export const flatStore = >( initialState: T ): FlatStore => { // @ts-expect-error if (initialState[__storeState]) { return initialState as FlatStore; } // @ts-expect-error initialState[__storeState] = {} as Record>; return new Proxy(initialState, handler); }; type FlatStoreOfSignals = >( initialState: T ) => FlatStore>; export type FlatStoreOfSignalsBody> = SetReadonly< { [TKey in keyof T]: T[TKey] extends ReadonlySignal ? TValue : T[TKey]; }, ReadonlySignalsKeys >; export type ReadonlySignalsKeys = keyof { [TKey in keyof T as IfNever< ReadonlyKeysOf, never, T[TKey] extends ReadonlySignal ? TKey : never >]: T[TKey]; }; /** * * @param initialState this value will be **mutated** and proxied * @returns */ export const flatStoreOfSignals: FlatStoreOfSignals = < T extends Record, >( initialState: T ): FlatStore> => { const signalValues = {} as Record>; for (const key in initialState) { const valueDescriptor = Object.getOwnPropertyDescriptor(initialState, key); if (valueDescriptor?.get) { continue; } const value = initialState[key]; // @ts-expect-error fuck you typescript, i wanna write OCaml if (value && typeof value === "object" && value instanceof Signal) { signalValues[key] = initialState[key]; delete initialState[key]; continue; } } const self = new Proxy( Object.assign(initialState, { [__storeState]: signalValues }), handler ) as FlatStore>; return self; }; export const createFlatStore = /** #__PURE__ */ >( initialState: T ) => { const store = flatStore(initialState); return [store, setterOfFlatStore(store)] as const; }; export const createFlatStoreOfSignals = /** #__PURE__ */ < T extends Record, >( initialState: T ): readonly [ FlatStore>, FlatStoreSetter>, ] => { const store = flatStoreOfSignals(initialState); return [store, setterOfFlatStore(store)]; };