import { computed } from "./computed"; import { signal, Signal } from "./signal" const proxyToSignals = new WeakMap(); const objToProxy = new WeakMap(); const arrayToArrayOfSignals = new WeakMap(); const ignore = new WeakSet(); const objToIterable = new WeakMap(); const rg = /^\$/; const descriptor = Object.getOwnPropertyDescriptor; let peeking = false; export const deepSignal = (obj: T): DeepSignal => { if (!shouldProxy(obj)) throw new Error("This object can't be observed."); if (!objToProxy.has(obj)) objToProxy.set(obj, createProxy(obj, objectHandlers) as DeepSignal); return objToProxy.get(obj); }; export const peek = < T extends DeepSignalObject, K extends keyof RevertDeepSignalObject >( obj: T, key: K ): RevertDeepSignal[K]> => { peeking = true; const value = obj[key]; try { peeking = false; } catch (e) { } return value as RevertDeepSignal[K]>; }; const isShallow = Symbol("shallow"); export function shallow(obj: T): Shallow { ignore.add(obj); return obj as Shallow; } const createProxy = (target: object, handlers: ProxyHandler) => { const proxy = new Proxy(target, handlers); ignore.add(proxy); return proxy; }; const throwOnMutation = () => { throw new Error("Don't mutate the signals directly."); }; const get = (isArrayOfSignals: boolean) => (target: object, fullKey: string, receiver: object): unknown => { if (peeking) return Reflect.get(target, fullKey, receiver); let returnSignal = isArrayOfSignals || fullKey[0] === "$"; if (!isArrayOfSignals && returnSignal && Array.isArray(target)) { if (fullKey === "$") { if (!arrayToArrayOfSignals.has(target)) arrayToArrayOfSignals.set(target, createProxy(target, arrayHandlers)); return arrayToArrayOfSignals.get(target); } returnSignal = fullKey === "$length"; } if (!proxyToSignals.has(receiver)) proxyToSignals.set(receiver, new Map()); const signals = proxyToSignals.get(receiver); const key = returnSignal ? fullKey.replace(rg, "") : fullKey; if ( !signals.has(key) && typeof descriptor(target, key)?.get === "function" ) { signals.set( key, computed(() => Reflect.get(target, key, receiver)) ); } else { let value = Reflect.get(target, key, receiver); if (returnSignal && typeof value === "function") return; if (typeof key === "symbol" && wellKnownSymbols.has(key)) return value; if (!signals.has(key)) { if (shouldProxy(value)) { if (!objToProxy.has(value)) objToProxy.set(value, createProxy(value, objectHandlers)); value = objToProxy.get(value); } signals.set(key, signal(value)); } } return returnSignal ? signals.get(key) : signals.get(key).get(); }; const objectHandlers = { get: get(false), set(target: object, fullKey: string, val: any, receiver: object): boolean { if (typeof descriptor(target, fullKey)?.set === "function") return Reflect.set(target, fullKey, val, receiver); if (!proxyToSignals.has(receiver)) proxyToSignals.set(receiver, new Map()); const signals = proxyToSignals.get(receiver); if (fullKey[0] === "$") { if (!(val instanceof Signal)) throwOnMutation(); const key = fullKey.replace(rg, ""); signals.set(key, val); return Reflect.set(target, key, val.peek(), receiver); } else { let internal = val; if (shouldProxy(val)) { if (!objToProxy.has(val)) objToProxy.set(val, createProxy(val, objectHandlers)); internal = objToProxy.get(val); } const isNew = !(fullKey in target); const result = Reflect.set(target, fullKey, val, receiver); if (!signals.has(fullKey)) signals.set(fullKey, signal(internal)); else signals.get(fullKey).set(internal); if (isNew && objToIterable.has(target)) objToIterable.get(target).value++; if (Array.isArray(target) && signals.has("length")) signals.get("length").set(target.length); return result; } }, deleteProperty(target: object, key: string): boolean { if (key[0] === "$") throwOnMutation(); const signals = proxyToSignals.get(objToProxy.get(target)); const result = Reflect.deleteProperty(target, key); if (signals && signals.has(key)) signals.get(key).value = undefined; objToIterable.has(target) && objToIterable.get(target).value++; return result; }, ownKeys(target: object): (string | symbol)[] { if (!objToIterable.has(target)) objToIterable.set(target, signal(0)); (objToIterable as any)._ = objToIterable.get(target).get(); return Reflect.ownKeys(target); }, }; const arrayHandlers = { get: get(true), set: throwOnMutation, deleteProperty: throwOnMutation, }; const wellKnownSymbols = new Set( Object.getOwnPropertyNames(Symbol) .map(key => Symbol[key as WellKnownSymbols]) .filter(value => typeof value === "symbol") ); const supported = new Set([Object, Array]); const shouldProxy = (val: any): boolean => { if (typeof val !== "object" || val === null) return false; return supported.has(val.constructor) && !ignore.has(val); }; /** TYPES **/ export type DeepSignal = T extends Function ? T : T extends { [isShallow]: true } ? T : T extends Array ? DeepSignalArray : T extends object ? DeepSignalObject : T; type DeepSignalObject = { [P in keyof T & string as `$${P}`]?: T[P] extends Function ? never : Signal; } & { [P in keyof T]: DeepSignal; }; /** @ts-expect-error **/ interface DeepArray extends Array { map: ( callbackfn: ( value: DeepSignal, index: number, array: DeepSignalArray ) => U, thisArg?: any ) => U[]; forEach: ( callbackfn: ( value: DeepSignal, index: number, array: DeepSignalArray ) => void, thisArg?: any ) => void; concat(...items: ConcatArray[]): DeepSignalArray; concat(...items: (T | ConcatArray)[]): DeepSignalArray; reverse(): DeepSignalArray; shift(): DeepSignal | undefined; slice(start?: number, end?: number): DeepSignalArray; splice(start: number, deleteCount?: number): DeepSignalArray; splice( start: number, deleteCount: number, ...items: T[] ): DeepSignalArray; filter( predicate: ( value: DeepSignal, index: number, array: DeepSignalArray ) => value is DeepSignal, thisArg?: any ): DeepSignalArray; filter( predicate: ( value: DeepSignal, index: number, array: DeepSignalArray ) => unknown, thisArg?: any ): DeepSignalArray; reduce( callbackfn: ( previousValue: DeepSignal, currentValue: DeepSignal, currentIndex: number, array: DeepSignalArray ) => T ): DeepSignal; reduce( callbackfn: ( previousValue: DeepSignal, currentValue: DeepSignal, currentIndex: number, array: DeepSignalArray ) => DeepSignal, initialValue: T ): DeepSignal; reduce( callbackfn: ( previousValue: U, currentValue: DeepSignal, currentIndex: number, array: DeepSignalArray ) => U, initialValue: U ): U; reduceRight( callbackfn: ( previousValue: DeepSignal, currentValue: DeepSignal, currentIndex: number, array: DeepSignalArray ) => T ): DeepSignal; reduceRight( callbackfn: ( previousValue: DeepSignal, currentValue: DeepSignal, currentIndex: number, array: DeepSignalArray ) => DeepSignal, initialValue: T ): DeepSignal; reduceRight( callbackfn: ( previousValue: U, currentValue: DeepSignal, currentIndex: number, array: DeepSignalArray ) => U, initialValue: U ): U; } type ArrayType = T extends Array ? I : T; type DeepSignalArray = DeepArray> & { [key: number]: DeepSignal>; $?: { [key: number]: Signal> }; $length?: Signal; }; export type Shallow = T & { [isShallow]: true }; export declare const useDeepSignal: (obj: T) => DeepSignal; // @ts-ignore type FilterSignals = K extends `$${infer P}` ? never : K; type RevertDeepSignalObject = Pick>; type RevertDeepSignalArray = Omit; export type RevertDeepSignal = T extends Array ? RevertDeepSignalArray : T extends object ? RevertDeepSignalObject : T; type WellKnownSymbols = | "asyncIterator" | "hasInstance" | "isConcatSpreadable" | "iterator" | "match" | "matchAll" | "replace" | "search" | "species" | "split" | "toPrimitive" | "toStringTag" | "unscopables";