export type Extended = (A extends Function ? A : B) & { [K in keyof (A & B)]: K extends keyof B ? B[K] : K extends keyof A ? A[K] : unknown; }; /** * Creates an object extension * @param parent any extensible object or function * @param extensions optional objects providing additional properties * @returns `A & B` * ### How are properties and methods resolved? * - calling the extent directly invokes the parent if the parent */ export function extend( parent: A, ...extensions: B[] ): Extended { const callable = [parent, ...extensions, () => extent].reduce( (left, right) => { return typeof left === 'function' ? left : right; } ); const { extent: caller } = { extent(this: any) { return (callable as Function).apply(this || extent, arguments); }, }; const proxy = new Proxy(parent, { get(_target, name) { const key = name as keyof A & keyof B; const ext = extensions.find((ext) => ext?.[key]) ?? parent; const prop = ext?.[key]; return typeof prop === 'function' ? prop.bind(ext) : prop; }, }); const extent = Object.setPrototypeOf(caller, proxy) as Extended; return extent; } export default extend; Object.defineProperties(extend, { default: { get: () => extend }, extend: { get: () => extend }, });