UNPKG

proxy-vir

Version:
205 lines (204 loc) 8.06 kB
import { check } from '@augment-vir/assert'; import { getObjectTypedKeys, } from '@augment-vir/common'; import { createPrioritizedProperties } from './prioritized-properties.js'; /** * Create an instance of {@link WrappedMultiTargetProxy} which can be used to merge multiple targets * together or override proxy handler methods. * * @category Main * @example * * ```ts * import {createWrappedMultiTargetProxy} from 'proxy-vir'; * * // something you imported from a 3rd party library that you want to wrap * const importedThing = { * doThingA() {}, * }; * * const thingWrapper = createWrappedMultiTargetProxy({ * initialTarget: importedThing, * }); * * // add a new override * thingWrapper.proxyModifier.addOverrideTarget({ * doThingA() {}, * }); * ``` */ export function createWrappedMultiTargetProxy(options) { /** This target will always be used first. */ const primaryTarget = options?.isCallable ? () => { } : {}; const deletedProperties = new Set(); let prototype = Object.getPrototypeOf(primaryTarget); let isExtensible = !options?.isNotExtensible; const proxyOverrides = createPrioritizedProperties(); const targetProperties = createPrioritizedProperties({ initialList: [primaryTarget], /** This is set to 1 so that the primaryTarget above is never overridden. */ overrideEntryPoint: 1, updateCallback(combinedObject) { Object.setPrototypeOf(combinedObject, prototype); deletedProperties.forEach((property) => { delete combinedObject[property]; }); if (!isExtensible && Object.isExtensible(combinedObject)) { Object.preventExtensions(combinedObject); } }, }); const functionHandlers = options?.isCallable ? { apply() { if (proxyOverrides.combinedProperties.apply) { // eslint-disable-next-line @typescript-eslint/unbound-method return proxyOverrides.combinedProperties.apply; } const firstCallableTarget = targetProperties .getCurrentList() .find((target) => typeof target === 'function') ?? primaryTarget; return firstCallableTarget(); }, } : {}; const createdProxy = new Proxy(targetProperties.combinedProperties, { ...functionHandlers, defineProperty(combinedTargets, property, attributes) { if (proxyOverrides.combinedProperties.defineProperty) { return proxyOverrides.combinedProperties.defineProperty(combinedTargets, property, attributes); } if (!isExtensible) { return false; } deletedProperties.delete(property); Object.defineProperty(primaryTarget, property, attributes); targetProperties.forceUpdate(); return true; }, deleteProperty(combinedTargets, property) { if (proxyOverrides.combinedProperties.deleteProperty) { return proxyOverrides.combinedProperties.deleteProperty(combinedTargets, property); } if (!isExtensible) { return false; } deletedProperties.add(property); return true; }, get(combinedTargets, property, receiver) { if (proxyOverrides.combinedProperties.get) { return proxyOverrides.combinedProperties.get(combinedTargets, property, receiver); } if (deletedProperties.has(property)) { return undefined; } return combinedTargets[property]; }, getOwnPropertyDescriptor(combinedTargets, property) { if (proxyOverrides.combinedProperties.getOwnPropertyDescriptor) { return proxyOverrides.combinedProperties.getOwnPropertyDescriptor(combinedTargets, property); } if (deletedProperties.has(property)) { return undefined; } return Object.getOwnPropertyDescriptor(combinedTargets, property); }, getPrototypeOf(combinedTargets) { if (proxyOverrides.combinedProperties.getPrototypeOf) { return proxyOverrides.combinedProperties.getPrototypeOf(combinedTargets); } return Object.getPrototypeOf(combinedTargets); }, has(combinedTargets, property) { if (proxyOverrides.combinedProperties.has) { return proxyOverrides.combinedProperties.has(combinedTargets, property); } if (deletedProperties.has(property)) { return false; } return check.hasKey(combinedTargets, property); }, isExtensible(combinedTargets) { if (proxyOverrides.combinedProperties.isExtensible) { return proxyOverrides.combinedProperties.isExtensible(combinedTargets); } return isExtensible; }, ownKeys(combinedTargets) { if (proxyOverrides.combinedProperties.ownKeys) { return proxyOverrides.combinedProperties.ownKeys(combinedTargets); } return Array.from(getObjectTypedKeys(combinedTargets)) .map((key) => { return typeof key === 'number' ? String(key) : key; }) .filter((property) => !deletedProperties.has(property)); }, preventExtensions(combinedTargets) { if (proxyOverrides.combinedProperties.preventExtensions) { return proxyOverrides.combinedProperties.preventExtensions(combinedTargets); } isExtensible = false; targetProperties.forceUpdate(); return true; }, set(combinedTargets, property, newValue, receiver) { if (proxyOverrides.combinedProperties.set) { return proxyOverrides.combinedProperties.set(combinedTargets, property, newValue, receiver); } if (!isExtensible) { return false; } deletedProperties.delete(property); primaryTarget[property] = newValue; targetProperties.forceUpdate(); return true; }, setPrototypeOf(combinedTargets, newPrototype) { if (proxyOverrides.combinedProperties.setPrototypeOf) { return proxyOverrides.combinedProperties.setPrototypeOf(combinedTargets, newPrototype); } if (!isExtensible) { return false; } prototype = newPrototype; targetProperties.forceUpdate(); return true; }, }); const proxyModifier = { addFallbackTarget(target) { targetProperties.addFallback(target); }, addOverrideTarget(target) { targetProperties.addOverride(target); }, removeTarget(target) { return targetProperties.removeEntry(target); }, addProxyHandlerOverride(handlerOverride) { proxyOverrides.addOverride(handlerOverride); }, addProxyHandlerFallback(handlerOverride) { proxyOverrides.addFallback(handlerOverride); }, removeProxyOverride(handlerOverride) { return proxyOverrides.removeEntry(handlerOverride); }, getAllTargets() { return targetProperties.getCurrentList(); }, }; if (options && 'initialTarget' in options && options.initialTarget) { proxyModifier.addFallbackTarget(options.initialTarget); } else if (options && 'initialTargets' in options && options.initialTargets) { options.initialTargets.forEach((initialTarget) => { proxyModifier.addOverrideTarget(initialTarget); }); } return { proxy: createdProxy, proxyModifier, }; }