proxy-vir
Version:
An easier Proxy.
205 lines (204 loc) • 8.06 kB
JavaScript
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,
};
}