// Homomorphic mapped type preserves Go-to-Definition in IDEs // -? strips optionality, as clause filters conflicting method names type AccessorChainMap = { [K in keyof M as Exclude]-?: AccessorChain; }; // Check if a type is `any` using the intersection trick type IsAny = 0 extends 1 & T ? true : false; export type AccessorChain = (IsAny extends true ? { [key: string]: any } // Allow any property access for `any` type : [M] extends [object] ? AccessorChainMap // Direct mapping preserves IDE navigation : [NonNullable] extends [object] ? AccessorChainMap> // Fallback for nullable types (e.g. optional properties) : {}) & { toString(): string; valueOf(): M | undefined; nameOf(): string; }; const emptyFn = () => {}; export function createAccessorModelProxy(chain: string = ""): AccessorChain { let lastOp: string | null = null; const proxy = new Proxy(emptyFn, { get: (_, name: string | symbol) => { if (typeof name !== "string") return proxy; switch (name) { case "isAccessorChain": return true; case "toString": case "valueOf": case "nameOf": lastOp = name; return proxy; } let newChain = chain; if (newChain.length > 0) newChain += "."; newChain += name; return createAccessorModelProxy(newChain); }, apply(): string { switch (lastOp) { case "nameOf": const lastDotIndex = chain.lastIndexOf("."); return lastDotIndex > 0 ? chain.substring(lastDotIndex + 1) : chain; default: return chain; } }, }); return proxy as unknown as AccessorChain; } export const createModel = createAccessorModelProxy; export function isAccessorChain(value: unknown): value is AccessorChain { return value != null && !!(value as any).isAccessorChain; }