export type WatchStateCallback = (value: T, old: T) => void export type WatchState = (callback: WatchStateCallback, options?: { immediate: boolean }) => (() => void) export type State = { value: T | undefined, watch: WatchState } export type WatchTriggerCallback = (value: T) => void export type WatchTrigger = (callback: WatchTriggerCallback) => (() => void) export type Trigger = { watch: WatchTrigger } export type DeepProxy = { [P in keyof T]: T[P] extends State ? T[P] : T[P] extends Trigger ? T[P] : DeepProxy } export function createDeepProxy(initial: Map = new Map()) { const keyValue = new Map(initial) const listeners = new Map | WatchTriggerCallback>>() function makeHandler(path: string): ProxyHandler { return { get(target, p, receiver) { if (p === 'value') return keyValue.get(path) if (p === 'watch') return ((callback: WatchStateCallback, options?: { immediate: boolean }) => { if (listeners.has(path)) listeners.get(path)!.add(callback) else listeners.set(path, new Set([callback])) if (options?.immediate) callback(keyValue.get(path), keyValue.get(path)) return () => { listeners.get(path)!.delete(callback) if (listeners.get(path)!.size === 0) listeners.delete(path) } }) as WatchState const nextPath = path == '' ? p.toString() : `${path}.${p.toString()}` return new Proxy({}, makeHandler(nextPath)) }, } } const notSet = Symbol('notSet') function update(path: string, value: any, old: any = notSet) { const oldValue = old == notSet ? keyValue.get(path) : old keyValue.set(path, value) const callbacks = listeners.get(path) if (!callbacks) return for (const iterator of callbacks) { (iterator as WatchStateCallback)(value, oldValue) } } function trigger(path: string, value: any) { const callbacks = listeners.get(path) if (!callbacks) return for (const iterator of callbacks) { (iterator as WatchTriggerCallback)(value) } } function resetup(initial: Map = new Map()) { for (const [key, value] of keyValue.entries()) { if (!initial.has(key)) { update(key, undefined) } else { update(key, initial.get(key)) } } for (const [key, value] of initial.entries()) { if (!keyValue.has(key)) { update(key, value) } } } return { proxy: new Proxy(keyValue as any, makeHandler('')) as DeepProxy, update, trigger, resetup } }