observable-membrane
Advanced tools
Comparing version 0.25.0 to 0.26.0
@@ -1,3 +0,3 @@ | ||
<a name="0.25.0"></a> | ||
# 0.25.0 (2018-07-18) | ||
<a name="0.26.0"></a> | ||
# 0.26.0 (2018-11-19) | ||
@@ -7,2 +7,5 @@ | ||
* bundle generation for cjs ([78b420a](https://github.com/salesforce/observable-membrane/commit/78b420a)) | ||
* entry point ([4231ecb](https://github.com/salesforce/observable-membrane/commit/4231ecb)) | ||
* issue [#20](https://github.com/salesforce/observable-membrane/issues/20) - supporting access to value via descriptor ([#22](https://github.com/salesforce/observable-membrane/issues/22)) ([4bd4751](https://github.com/salesforce/observable-membrane/commit/4bd4751)) | ||
* linting ([ac2d448](https://github.com/salesforce/observable-membrane/commit/ac2d448)) | ||
@@ -17,2 +20,7 @@ * simplify build ([13bd2ee](https://github.com/salesforce/observable-membrane/commit/13bd2ee)) | ||
### Performance Improvements | ||
* fix for small perf regression associated to defineProperties(). ([9c54ec0](https://github.com/salesforce/observable-membrane/commit/9c54ec0)) | ||
@@ -7,5 +7,4 @@ /** | ||
const { isArray } = Array; | ||
const { getPrototypeOf, create: ObjectCreate, defineProperty: ObjectDefineProperty, defineProperties: ObjectDefineProperties, isExtensible, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, preventExtensions, } = Object; | ||
const { getPrototypeOf, create: ObjectCreate, defineProperty: ObjectDefineProperty, defineProperties: ObjectDefineProperties, isExtensible, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, preventExtensions, hasOwnProperty, } = Object; | ||
const { push: ArrayPush, concat: ArrayConcat, map: ArrayMap, } = Array.prototype; | ||
const ObjectDotPrototype = Object.prototype; | ||
const OtS = {}.toString; | ||
@@ -26,2 +25,5 @@ function toString(obj) { | ||
} | ||
function isFunction(obj) { | ||
return typeof obj === 'function'; | ||
} | ||
const TargetSlot = Symbol(); | ||
@@ -35,13 +37,2 @@ // TODO: we are using a funky and leaky abstraction here to try to identify if | ||
: (replicaOrAny) => (replicaOrAny && replicaOrAny[TargetSlot]) || replicaOrAny; | ||
function isObservable(value) { | ||
// intentionally checking for null and undefined | ||
if (value == null) { | ||
return false; | ||
} | ||
if (isArray(value)) { | ||
return true; | ||
} | ||
const proto = getPrototypeOf(value); | ||
return (proto === ObjectDotPrototype || proto === null || getPrototypeOf(proto) === null); | ||
} | ||
function isObject(obj) { | ||
@@ -51,6 +42,9 @@ return typeof obj === 'object'; | ||
function wrapValue(membrane, value) { | ||
return membrane.valueIsObservable(value) ? membrane.getProxy(value) : value; | ||
} | ||
// Unwrap property descriptors | ||
// We only need to unwrap if value is specified | ||
function unwrapDescriptor(descriptor) { | ||
if ('value' in descriptor) { | ||
if (hasOwnProperty.call(descriptor, 'value')) { | ||
descriptor.value = unwrap(descriptor.value); | ||
@@ -60,8 +54,2 @@ } | ||
} | ||
function wrapDescriptor(membrane, descriptor) { | ||
if ('value' in descriptor) { | ||
descriptor.value = isObservable(descriptor.value) ? membrane.getProxy(descriptor.value) : descriptor.value; | ||
} | ||
return descriptor; | ||
} | ||
function lockShadowTarget(membrane, shadowTarget, originalTarget) { | ||
@@ -71,3 +59,3 @@ const targetKeys = ArrayConcat.call(getOwnPropertyNames(originalTarget), getOwnPropertySymbols(originalTarget)); | ||
let descriptor = getOwnPropertyDescriptor(originalTarget, key); | ||
// We do not need to wrap the descriptor if not configurable | ||
// We do not need to wrap the descriptor if configurable | ||
// Because we can deal with wrapping it when user goes through | ||
@@ -78,3 +66,3 @@ // Get own property descriptor. There is also a chance that this descriptor | ||
if (!descriptor.configurable) { | ||
descriptor = wrapDescriptor(membrane, descriptor); | ||
descriptor = wrapDescriptor(membrane, descriptor, wrapValue); | ||
} | ||
@@ -86,9 +74,5 @@ ObjectDefineProperty(shadowTarget, key, descriptor); | ||
class ReactiveProxyHandler { | ||
constructor(membrane, value, options) { | ||
constructor(membrane, value) { | ||
this.originalTarget = value; | ||
this.membrane = membrane; | ||
if (!isUndefined(options)) { | ||
this.valueMutated = options.valueMutated; | ||
this.valueObserved = options.valueObserved; | ||
} | ||
} | ||
@@ -101,16 +85,12 @@ get(shadowTarget, key) { | ||
const value = originalTarget[key]; | ||
const { valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { valueObserved } = membrane; | ||
valueObserved(originalTarget, key); | ||
return membrane.getProxy(value); | ||
} | ||
set(shadowTarget, key, value) { | ||
const { originalTarget, valueMutated } = this; | ||
const { originalTarget, membrane: { valueMutated } } = this; | ||
const oldValue = originalTarget[key]; | ||
if (oldValue !== value) { | ||
originalTarget[key] = value; | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
} | ||
@@ -122,5 +102,3 @@ else if (key === 'length' && isArray(originalTarget)) { | ||
// to support this use case. | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
} | ||
@@ -130,7 +108,5 @@ return true; | ||
deleteProperty(shadowTarget, key) { | ||
const { originalTarget, valueMutated } = this; | ||
const { originalTarget, membrane: { valueMutated } } = this; | ||
delete originalTarget[key]; | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
return true; | ||
@@ -145,6 +121,4 @@ } | ||
has(shadowTarget, key) { | ||
const { originalTarget, valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { originalTarget, membrane: { valueObserved } } = this; | ||
valueObserved(originalTarget, key); | ||
return key in originalTarget; | ||
@@ -178,7 +152,6 @@ } | ||
getOwnPropertyDescriptor(shadowTarget, key) { | ||
const { originalTarget, membrane, valueObserved } = this; | ||
const { originalTarget, membrane } = this; | ||
const { valueObserved } = this.membrane; | ||
// keys looked up via hasOwnProperty need to be reactive | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
valueObserved(originalTarget, key); | ||
let desc = getOwnPropertyDescriptor(originalTarget, key); | ||
@@ -189,3 +162,10 @@ if (isUndefined(desc)) { | ||
const shadowDescriptor = getOwnPropertyDescriptor(shadowTarget, key); | ||
if (!desc.configurable && !shadowDescriptor) { | ||
if (!isUndefined(shadowDescriptor)) { | ||
return shadowDescriptor; | ||
} | ||
// Note: by accessing the descriptor, the key is marked as observed | ||
// but access to the value, setter or getter (if available) cannot observe | ||
// mutations, just like regular methods, in which case we just do nothing. | ||
desc = wrapDescriptor(membrane, desc, wrapValue); | ||
if (!desc.configurable) { | ||
// If descriptor from original target is not configurable, | ||
@@ -196,6 +176,5 @@ // We must copy the wrapped descriptor over to the shadow target. | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor#Invariants | ||
desc = wrapDescriptor(membrane, desc); | ||
ObjectDefineProperty(shadowTarget, key, desc); | ||
} | ||
return shadowDescriptor || desc; | ||
return desc; | ||
} | ||
@@ -209,3 +188,4 @@ preventExtensions(shadowTarget) { | ||
defineProperty(shadowTarget, key, descriptor) { | ||
const { originalTarget, membrane, valueMutated } = this; | ||
const { originalTarget, membrane } = this; | ||
const { valueMutated } = membrane; | ||
const { configurable } = descriptor; | ||
@@ -219,3 +199,3 @@ // We have to check for value in descriptor | ||
// value is present. This eliminates getter and setter descriptors | ||
if ('writable' in descriptor && !('value' in descriptor)) { | ||
if (hasOwnProperty.call(descriptor, 'writable') && !hasOwnProperty.call(descriptor, 'value')) { | ||
const originalDescriptor = getOwnPropertyDescriptor(originalTarget, key); | ||
@@ -226,7 +206,5 @@ descriptor.value = originalDescriptor.value; | ||
if (configurable === false) { | ||
ObjectDefineProperty(shadowTarget, key, wrapDescriptor(membrane, descriptor)); | ||
ObjectDefineProperty(shadowTarget, key, wrapDescriptor(membrane, descriptor, wrapValue)); | ||
} | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
return true; | ||
@@ -236,15 +214,9 @@ } | ||
function wrapDescriptor$1(membrane, descriptor) { | ||
if ('value' in descriptor) { | ||
descriptor.value = isObservable(descriptor.value) ? membrane.getReadOnlyProxy(descriptor.value) : descriptor.value; | ||
} | ||
return descriptor; | ||
function wrapReadOnlyValue(membrane, value) { | ||
return membrane.valueIsObservable(value) ? membrane.getReadOnlyProxy(value) : value; | ||
} | ||
class ReadOnlyHandler { | ||
constructor(membrane, value, options) { | ||
constructor(membrane, value) { | ||
this.originalTarget = value; | ||
this.membrane = membrane; | ||
if (!isUndefined(options)) { | ||
this.valueObserved = options.valueObserved; | ||
} | ||
} | ||
@@ -257,6 +229,4 @@ get(shadowTarget, key) { | ||
const value = originalTarget[key]; | ||
const { valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { valueObserved } = membrane; | ||
valueObserved(originalTarget, key); | ||
return membrane.getReadOnlyProxy(value); | ||
@@ -285,7 +255,4 @@ } | ||
has(shadowTarget, key) { | ||
const { originalTarget } = this; | ||
const { valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { originalTarget, membrane: { valueObserved } } = this; | ||
valueObserved(originalTarget, key); | ||
return key in originalTarget; | ||
@@ -304,7 +271,6 @@ } | ||
getOwnPropertyDescriptor(shadowTarget, key) { | ||
const { originalTarget, membrane, valueObserved } = this; | ||
const { originalTarget, membrane } = this; | ||
const { valueObserved } = membrane; | ||
// keys looked up via hasOwnProperty need to be reactive | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
valueObserved(originalTarget, key); | ||
let desc = getOwnPropertyDescriptor(originalTarget, key); | ||
@@ -315,3 +281,13 @@ if (isUndefined(desc)) { | ||
const shadowDescriptor = getOwnPropertyDescriptor(shadowTarget, key); | ||
if (!desc.configurable && !shadowDescriptor) { | ||
if (!isUndefined(shadowDescriptor)) { | ||
return shadowDescriptor; | ||
} | ||
// Note: by accessing the descriptor, the key is marked as observed | ||
// but access to the value or getter (if available) cannot be observed, | ||
// just like regular methods, in which case we just do nothing. | ||
desc = wrapDescriptor(membrane, desc, wrapReadOnlyValue); | ||
if (hasOwnProperty.call(desc, 'set')) { | ||
desc.set = undefined; // readOnly membrane does not allow setters | ||
} | ||
if (!desc.configurable) { | ||
// If descriptor from original target is not configurable, | ||
@@ -322,6 +298,5 @@ // We must copy the wrapped descriptor over to the shadow target. | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor#Invariants | ||
desc = wrapDescriptor$1(membrane, desc); | ||
ObjectDefineProperty(shadowTarget, key, desc); | ||
} | ||
return shadowDescriptor || desc; | ||
return desc; | ||
} | ||
@@ -417,15 +392,64 @@ preventExtensions(shadowTarget) { | ||
} | ||
const ObjectDotPrototype = Object.prototype; | ||
function defaultValueIsObservable(value) { | ||
// intentionally checking for null and undefined | ||
if (value == null) { | ||
return false; | ||
} | ||
if (isArray(value)) { | ||
return true; | ||
} | ||
const proto = getPrototypeOf(value); | ||
return (proto === ObjectDotPrototype || proto === null || getPrototypeOf(proto) === null); | ||
} | ||
const defaultValueObserved = (obj, key) => { | ||
/* do nothing */ | ||
}; | ||
const defaultValueMutated = (obj, key) => { | ||
/* do nothing */ | ||
}; | ||
const defaultValueDistortion = (value) => value; | ||
function wrapDescriptor(membrane, descriptor, getValue) { | ||
const { set, get } = descriptor; | ||
if (hasOwnProperty.call(descriptor, 'value')) { | ||
descriptor.value = getValue(membrane, descriptor.value); | ||
} | ||
else { | ||
if (!isUndefined(get)) { | ||
descriptor.get = function () { | ||
// invoking the original getter with the original target | ||
return getValue(membrane, get.call(unwrap(this))); | ||
}; | ||
} | ||
if (!isUndefined(set)) { | ||
descriptor.set = function (value) { | ||
// At this point we don't have a clear indication of whether | ||
// or not a valid mutation will occur, we don't have the key, | ||
// and we are not sure why and how they are invoking this setter. | ||
// Nevertheless we preserve the original semantics by invoking the | ||
// original setter with the original target and the unwrapped value | ||
set.call(unwrap(this), membrane.unwrapProxy(value)); | ||
}; | ||
} | ||
} | ||
return descriptor; | ||
} | ||
class ReactiveMembrane { | ||
constructor(options) { | ||
this.valueDistortion = defaultValueDistortion; | ||
this.valueMutated = defaultValueMutated; | ||
this.valueObserved = defaultValueObserved; | ||
this.valueIsObservable = defaultValueIsObservable; | ||
this.objectGraph = new WeakMap(); | ||
if (!isUndefined(options)) { | ||
this.valueDistortion = options.valueDistortion; | ||
this.valueMutated = options.valueMutated; | ||
this.valueObserved = options.valueObserved; | ||
const { valueDistortion, valueMutated, valueObserved, valueIsObservable } = options; | ||
this.valueDistortion = isFunction(valueDistortion) ? valueDistortion : defaultValueDistortion; | ||
this.valueMutated = isFunction(valueMutated) ? valueMutated : defaultValueMutated; | ||
this.valueObserved = isFunction(valueObserved) ? valueObserved : defaultValueObserved; | ||
this.valueIsObservable = isFunction(valueIsObservable) ? valueIsObservable : defaultValueIsObservable; | ||
} | ||
} | ||
getProxy(value) { | ||
const { valueDistortion } = this; | ||
const distorted = isUndefined(valueDistortion) ? value : valueDistortion(value); | ||
if (isObservable(distorted)) { | ||
const distorted = this.valueDistortion(value); | ||
if (this.valueIsObservable(distorted)) { | ||
const o = this.getReactiveState(distorted); | ||
@@ -439,5 +463,4 @@ // when trying to extract the writable version of a readonly | ||
getReadOnlyProxy(value) { | ||
const { valueDistortion } = this; | ||
const distorted = isUndefined(valueDistortion) ? value : valueDistortion(value); | ||
if (isObservable(distorted)) { | ||
const distorted = this.valueDistortion(value); | ||
if (this.valueIsObservable(distorted)) { | ||
return this.getReactiveState(distorted).readOnly; | ||
@@ -451,4 +474,3 @@ } | ||
getReactiveState(value) { | ||
const membrane = this; | ||
const { objectGraph, valueMutated, valueObserved, } = membrane; | ||
const { objectGraph, } = this; | ||
value = unwrap(value); | ||
@@ -459,29 +481,19 @@ let reactiveState = objectGraph.get(value); | ||
} | ||
reactiveState = ObjectDefineProperties(ObjectCreate(null), { | ||
reactive: { | ||
get() { | ||
const reactiveHandler = new ReactiveProxyHandler(membrane, value, { | ||
valueMutated, | ||
valueObserved, | ||
}); | ||
// caching the reactive proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), reactiveHandler); | ||
ObjectDefineProperty(this, 'reactive', { value: proxy }); | ||
return proxy; | ||
}, | ||
configurable: true, | ||
const membrane = this; | ||
reactiveState = { | ||
get reactive() { | ||
const reactiveHandler = new ReactiveProxyHandler(membrane, value); | ||
// caching the reactive proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), reactiveHandler); | ||
ObjectDefineProperty(this, 'reactive', { value: proxy }); | ||
return proxy; | ||
}, | ||
readOnly: { | ||
get() { | ||
const readOnlyHandler = new ReadOnlyHandler(membrane, value, { | ||
valueObserved, | ||
}); | ||
// caching the readOnly proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), readOnlyHandler); | ||
ObjectDefineProperty(this, 'readOnly', { value: proxy }); | ||
return proxy; | ||
}, | ||
configurable: true, | ||
get readOnly() { | ||
const readOnlyHandler = new ReadOnlyHandler(membrane, value); | ||
// caching the readOnly proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), readOnlyHandler); | ||
ObjectDefineProperty(this, 'readOnly', { value: proxy }); | ||
return proxy; | ||
} | ||
}); | ||
}; | ||
objectGraph.set(value, reactiveState); | ||
@@ -488,0 +500,0 @@ return reactiveState; |
@@ -5,5 +5,4 @@ /** | ||
const { isArray } = Array; | ||
const { getPrototypeOf, create: ObjectCreate, defineProperty: ObjectDefineProperty, defineProperties: ObjectDefineProperties, isExtensible, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, preventExtensions, } = Object; | ||
const { getPrototypeOf, create: ObjectCreate, defineProperty: ObjectDefineProperty, defineProperties: ObjectDefineProperties, isExtensible, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, preventExtensions, hasOwnProperty, } = Object; | ||
const { push: ArrayPush, concat: ArrayConcat, map: ArrayMap, } = Array.prototype; | ||
const ObjectDotPrototype = Object.prototype; | ||
const OtS = {}.toString; | ||
@@ -24,2 +23,5 @@ function toString(obj) { | ||
} | ||
function isFunction(obj) { | ||
return typeof obj === 'function'; | ||
} | ||
const TargetSlot = Symbol(); | ||
@@ -33,13 +35,2 @@ // TODO: we are using a funky and leaky abstraction here to try to identify if | ||
: (replicaOrAny) => (replicaOrAny && replicaOrAny[TargetSlot]) || replicaOrAny; | ||
function isObservable(value) { | ||
// intentionally checking for null and undefined | ||
if (value == null) { | ||
return false; | ||
} | ||
if (isArray(value)) { | ||
return true; | ||
} | ||
const proto = getPrototypeOf(value); | ||
return (proto === ObjectDotPrototype || proto === null || getPrototypeOf(proto) === null); | ||
} | ||
function isObject(obj) { | ||
@@ -49,6 +40,9 @@ return typeof obj === 'object'; | ||
function wrapValue(membrane, value) { | ||
return membrane.valueIsObservable(value) ? membrane.getProxy(value) : value; | ||
} | ||
// Unwrap property descriptors | ||
// We only need to unwrap if value is specified | ||
function unwrapDescriptor(descriptor) { | ||
if ('value' in descriptor) { | ||
if (hasOwnProperty.call(descriptor, 'value')) { | ||
descriptor.value = unwrap(descriptor.value); | ||
@@ -58,8 +52,2 @@ } | ||
} | ||
function wrapDescriptor(membrane, descriptor) { | ||
if ('value' in descriptor) { | ||
descriptor.value = isObservable(descriptor.value) ? membrane.getProxy(descriptor.value) : descriptor.value; | ||
} | ||
return descriptor; | ||
} | ||
function lockShadowTarget(membrane, shadowTarget, originalTarget) { | ||
@@ -69,3 +57,3 @@ const targetKeys = ArrayConcat.call(getOwnPropertyNames(originalTarget), getOwnPropertySymbols(originalTarget)); | ||
let descriptor = getOwnPropertyDescriptor(originalTarget, key); | ||
// We do not need to wrap the descriptor if not configurable | ||
// We do not need to wrap the descriptor if configurable | ||
// Because we can deal with wrapping it when user goes through | ||
@@ -76,3 +64,3 @@ // Get own property descriptor. There is also a chance that this descriptor | ||
if (!descriptor.configurable) { | ||
descriptor = wrapDescriptor(membrane, descriptor); | ||
descriptor = wrapDescriptor(membrane, descriptor, wrapValue); | ||
} | ||
@@ -84,9 +72,5 @@ ObjectDefineProperty(shadowTarget, key, descriptor); | ||
class ReactiveProxyHandler { | ||
constructor(membrane, value, options) { | ||
constructor(membrane, value) { | ||
this.originalTarget = value; | ||
this.membrane = membrane; | ||
if (!isUndefined(options)) { | ||
this.valueMutated = options.valueMutated; | ||
this.valueObserved = options.valueObserved; | ||
} | ||
} | ||
@@ -99,16 +83,12 @@ get(shadowTarget, key) { | ||
const value = originalTarget[key]; | ||
const { valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { valueObserved } = membrane; | ||
valueObserved(originalTarget, key); | ||
return membrane.getProxy(value); | ||
} | ||
set(shadowTarget, key, value) { | ||
const { originalTarget, valueMutated } = this; | ||
const { originalTarget, membrane: { valueMutated } } = this; | ||
const oldValue = originalTarget[key]; | ||
if (oldValue !== value) { | ||
originalTarget[key] = value; | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
} | ||
@@ -120,5 +100,3 @@ else if (key === 'length' && isArray(originalTarget)) { | ||
// to support this use case. | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
} | ||
@@ -128,7 +106,5 @@ return true; | ||
deleteProperty(shadowTarget, key) { | ||
const { originalTarget, valueMutated } = this; | ||
const { originalTarget, membrane: { valueMutated } } = this; | ||
delete originalTarget[key]; | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
return true; | ||
@@ -143,6 +119,4 @@ } | ||
has(shadowTarget, key) { | ||
const { originalTarget, valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { originalTarget, membrane: { valueObserved } } = this; | ||
valueObserved(originalTarget, key); | ||
return key in originalTarget; | ||
@@ -176,7 +150,6 @@ } | ||
getOwnPropertyDescriptor(shadowTarget, key) { | ||
const { originalTarget, membrane, valueObserved } = this; | ||
const { originalTarget, membrane } = this; | ||
const { valueObserved } = this.membrane; | ||
// keys looked up via hasOwnProperty need to be reactive | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
valueObserved(originalTarget, key); | ||
let desc = getOwnPropertyDescriptor(originalTarget, key); | ||
@@ -187,3 +160,10 @@ if (isUndefined(desc)) { | ||
const shadowDescriptor = getOwnPropertyDescriptor(shadowTarget, key); | ||
if (!desc.configurable && !shadowDescriptor) { | ||
if (!isUndefined(shadowDescriptor)) { | ||
return shadowDescriptor; | ||
} | ||
// Note: by accessing the descriptor, the key is marked as observed | ||
// but access to the value, setter or getter (if available) cannot observe | ||
// mutations, just like regular methods, in which case we just do nothing. | ||
desc = wrapDescriptor(membrane, desc, wrapValue); | ||
if (!desc.configurable) { | ||
// If descriptor from original target is not configurable, | ||
@@ -194,6 +174,5 @@ // We must copy the wrapped descriptor over to the shadow target. | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor#Invariants | ||
desc = wrapDescriptor(membrane, desc); | ||
ObjectDefineProperty(shadowTarget, key, desc); | ||
} | ||
return shadowDescriptor || desc; | ||
return desc; | ||
} | ||
@@ -207,3 +186,4 @@ preventExtensions(shadowTarget) { | ||
defineProperty(shadowTarget, key, descriptor) { | ||
const { originalTarget, membrane, valueMutated } = this; | ||
const { originalTarget, membrane } = this; | ||
const { valueMutated } = membrane; | ||
const { configurable } = descriptor; | ||
@@ -217,3 +197,3 @@ // We have to check for value in descriptor | ||
// value is present. This eliminates getter and setter descriptors | ||
if ('writable' in descriptor && !('value' in descriptor)) { | ||
if (hasOwnProperty.call(descriptor, 'writable') && !hasOwnProperty.call(descriptor, 'value')) { | ||
const originalDescriptor = getOwnPropertyDescriptor(originalTarget, key); | ||
@@ -224,7 +204,5 @@ descriptor.value = originalDescriptor.value; | ||
if (configurable === false) { | ||
ObjectDefineProperty(shadowTarget, key, wrapDescriptor(membrane, descriptor)); | ||
ObjectDefineProperty(shadowTarget, key, wrapDescriptor(membrane, descriptor, wrapValue)); | ||
} | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
return true; | ||
@@ -234,15 +212,9 @@ } | ||
function wrapDescriptor$1(membrane, descriptor) { | ||
if ('value' in descriptor) { | ||
descriptor.value = isObservable(descriptor.value) ? membrane.getReadOnlyProxy(descriptor.value) : descriptor.value; | ||
} | ||
return descriptor; | ||
function wrapReadOnlyValue(membrane, value) { | ||
return membrane.valueIsObservable(value) ? membrane.getReadOnlyProxy(value) : value; | ||
} | ||
class ReadOnlyHandler { | ||
constructor(membrane, value, options) { | ||
constructor(membrane, value) { | ||
this.originalTarget = value; | ||
this.membrane = membrane; | ||
if (!isUndefined(options)) { | ||
this.valueObserved = options.valueObserved; | ||
} | ||
} | ||
@@ -255,6 +227,4 @@ get(shadowTarget, key) { | ||
const value = originalTarget[key]; | ||
const { valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { valueObserved } = membrane; | ||
valueObserved(originalTarget, key); | ||
return membrane.getReadOnlyProxy(value); | ||
@@ -283,7 +253,4 @@ } | ||
has(shadowTarget, key) { | ||
const { originalTarget } = this; | ||
const { valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { originalTarget, membrane: { valueObserved } } = this; | ||
valueObserved(originalTarget, key); | ||
return key in originalTarget; | ||
@@ -302,7 +269,6 @@ } | ||
getOwnPropertyDescriptor(shadowTarget, key) { | ||
const { originalTarget, membrane, valueObserved } = this; | ||
const { originalTarget, membrane } = this; | ||
const { valueObserved } = membrane; | ||
// keys looked up via hasOwnProperty need to be reactive | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
valueObserved(originalTarget, key); | ||
let desc = getOwnPropertyDescriptor(originalTarget, key); | ||
@@ -313,3 +279,13 @@ if (isUndefined(desc)) { | ||
const shadowDescriptor = getOwnPropertyDescriptor(shadowTarget, key); | ||
if (!desc.configurable && !shadowDescriptor) { | ||
if (!isUndefined(shadowDescriptor)) { | ||
return shadowDescriptor; | ||
} | ||
// Note: by accessing the descriptor, the key is marked as observed | ||
// but access to the value or getter (if available) cannot be observed, | ||
// just like regular methods, in which case we just do nothing. | ||
desc = wrapDescriptor(membrane, desc, wrapReadOnlyValue); | ||
if (hasOwnProperty.call(desc, 'set')) { | ||
desc.set = undefined; // readOnly membrane does not allow setters | ||
} | ||
if (!desc.configurable) { | ||
// If descriptor from original target is not configurable, | ||
@@ -320,6 +296,5 @@ // We must copy the wrapped descriptor over to the shadow target. | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor#Invariants | ||
desc = wrapDescriptor$1(membrane, desc); | ||
ObjectDefineProperty(shadowTarget, key, desc); | ||
} | ||
return shadowDescriptor || desc; | ||
return desc; | ||
} | ||
@@ -415,15 +390,64 @@ preventExtensions(shadowTarget) { | ||
} | ||
const ObjectDotPrototype = Object.prototype; | ||
function defaultValueIsObservable(value) { | ||
// intentionally checking for null and undefined | ||
if (value == null) { | ||
return false; | ||
} | ||
if (isArray(value)) { | ||
return true; | ||
} | ||
const proto = getPrototypeOf(value); | ||
return (proto === ObjectDotPrototype || proto === null || getPrototypeOf(proto) === null); | ||
} | ||
const defaultValueObserved = (obj, key) => { | ||
/* do nothing */ | ||
}; | ||
const defaultValueMutated = (obj, key) => { | ||
/* do nothing */ | ||
}; | ||
const defaultValueDistortion = (value) => value; | ||
function wrapDescriptor(membrane, descriptor, getValue) { | ||
const { set, get } = descriptor; | ||
if (hasOwnProperty.call(descriptor, 'value')) { | ||
descriptor.value = getValue(membrane, descriptor.value); | ||
} | ||
else { | ||
if (!isUndefined(get)) { | ||
descriptor.get = function () { | ||
// invoking the original getter with the original target | ||
return getValue(membrane, get.call(unwrap(this))); | ||
}; | ||
} | ||
if (!isUndefined(set)) { | ||
descriptor.set = function (value) { | ||
// At this point we don't have a clear indication of whether | ||
// or not a valid mutation will occur, we don't have the key, | ||
// and we are not sure why and how they are invoking this setter. | ||
// Nevertheless we preserve the original semantics by invoking the | ||
// original setter with the original target and the unwrapped value | ||
set.call(unwrap(this), membrane.unwrapProxy(value)); | ||
}; | ||
} | ||
} | ||
return descriptor; | ||
} | ||
class ReactiveMembrane { | ||
constructor(options) { | ||
this.valueDistortion = defaultValueDistortion; | ||
this.valueMutated = defaultValueMutated; | ||
this.valueObserved = defaultValueObserved; | ||
this.valueIsObservable = defaultValueIsObservable; | ||
this.objectGraph = new WeakMap(); | ||
if (!isUndefined(options)) { | ||
this.valueDistortion = options.valueDistortion; | ||
this.valueMutated = options.valueMutated; | ||
this.valueObserved = options.valueObserved; | ||
const { valueDistortion, valueMutated, valueObserved, valueIsObservable } = options; | ||
this.valueDistortion = isFunction(valueDistortion) ? valueDistortion : defaultValueDistortion; | ||
this.valueMutated = isFunction(valueMutated) ? valueMutated : defaultValueMutated; | ||
this.valueObserved = isFunction(valueObserved) ? valueObserved : defaultValueObserved; | ||
this.valueIsObservable = isFunction(valueIsObservable) ? valueIsObservable : defaultValueIsObservable; | ||
} | ||
} | ||
getProxy(value) { | ||
const { valueDistortion } = this; | ||
const distorted = isUndefined(valueDistortion) ? value : valueDistortion(value); | ||
if (isObservable(distorted)) { | ||
const distorted = this.valueDistortion(value); | ||
if (this.valueIsObservable(distorted)) { | ||
const o = this.getReactiveState(distorted); | ||
@@ -437,5 +461,4 @@ // when trying to extract the writable version of a readonly | ||
getReadOnlyProxy(value) { | ||
const { valueDistortion } = this; | ||
const distorted = isUndefined(valueDistortion) ? value : valueDistortion(value); | ||
if (isObservable(distorted)) { | ||
const distorted = this.valueDistortion(value); | ||
if (this.valueIsObservable(distorted)) { | ||
return this.getReactiveState(distorted).readOnly; | ||
@@ -449,4 +472,3 @@ } | ||
getReactiveState(value) { | ||
const membrane = this; | ||
const { objectGraph, valueMutated, valueObserved, } = membrane; | ||
const { objectGraph, } = this; | ||
value = unwrap(value); | ||
@@ -457,29 +479,19 @@ let reactiveState = objectGraph.get(value); | ||
} | ||
reactiveState = ObjectDefineProperties(ObjectCreate(null), { | ||
reactive: { | ||
get() { | ||
const reactiveHandler = new ReactiveProxyHandler(membrane, value, { | ||
valueMutated, | ||
valueObserved, | ||
}); | ||
// caching the reactive proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), reactiveHandler); | ||
ObjectDefineProperty(this, 'reactive', { value: proxy }); | ||
return proxy; | ||
}, | ||
configurable: true, | ||
const membrane = this; | ||
reactiveState = { | ||
get reactive() { | ||
const reactiveHandler = new ReactiveProxyHandler(membrane, value); | ||
// caching the reactive proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), reactiveHandler); | ||
ObjectDefineProperty(this, 'reactive', { value: proxy }); | ||
return proxy; | ||
}, | ||
readOnly: { | ||
get() { | ||
const readOnlyHandler = new ReadOnlyHandler(membrane, value, { | ||
valueObserved, | ||
}); | ||
// caching the readOnly proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), readOnlyHandler); | ||
ObjectDefineProperty(this, 'readOnly', { value: proxy }); | ||
return proxy; | ||
}, | ||
configurable: true, | ||
get readOnly() { | ||
const readOnlyHandler = new ReadOnlyHandler(membrane, value); | ||
// caching the readOnly proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), readOnlyHandler); | ||
ObjectDefineProperty(this, 'readOnly', { value: proxy }); | ||
return proxy; | ||
} | ||
}); | ||
}; | ||
objectGraph.set(value, reactiveState); | ||
@@ -486,0 +498,0 @@ return reactiveState; |
@@ -1,12 +0,6 @@ | ||
import { ReactiveMembrane, ReactiveMembraneShadowTarget, ReactiveMembraneMutationCallback, ReactiveMembraneAccessCallback } from './reactive-membrane'; | ||
export interface ReactiveProxyHandlerInit { | ||
valueMutated?: ReactiveMembraneMutationCallback; | ||
valueObserved?: ReactiveMembraneAccessCallback; | ||
} | ||
import { ReactiveMembrane, ReactiveMembraneShadowTarget } from './reactive-membrane'; | ||
export declare class ReactiveProxyHandler { | ||
private originalTarget; | ||
private membrane; | ||
private valueObserved?; | ||
private valueMutated?; | ||
constructor(membrane: ReactiveMembrane, value: any, options?: ReactiveProxyHandlerInit); | ||
constructor(membrane: ReactiveMembrane, value: any); | ||
get(shadowTarget: ReactiveMembraneShadowTarget, key: PropertyKey): any; | ||
@@ -13,0 +7,0 @@ set(shadowTarget: ReactiveMembraneShadowTarget, key: PropertyKey, value: any): boolean; |
@@ -5,2 +5,3 @@ export declare type ReactiveMembraneShadowTarget = object | any[]; | ||
export declare type ReactiveMembraneDistortionCallback = (value: any) => any; | ||
export declare type ReactiveMembraneObservableCallback = (value: any) => boolean; | ||
export interface ObservableMembraneInit { | ||
@@ -10,7 +11,10 @@ valueMutated?: ReactiveMembraneMutationCallback; | ||
valueDistortion?: ReactiveMembraneDistortionCallback; | ||
valueIsObservable?: ReactiveMembraneObservableCallback; | ||
} | ||
export declare function wrapDescriptor(membrane: ReactiveMembrane, descriptor: PropertyDescriptor, getValue: (membrane: ReactiveMembrane, originalValue: any) => any): PropertyDescriptor; | ||
export declare class ReactiveMembrane { | ||
private valueDistortion?; | ||
private valueMutated?; | ||
private valueObserved?; | ||
valueDistortion: ReactiveMembraneDistortionCallback; | ||
valueMutated: ReactiveMembraneMutationCallback; | ||
valueObserved: ReactiveMembraneAccessCallback; | ||
valueIsObservable: ReactiveMembraneObservableCallback; | ||
private objectGraph; | ||
@@ -17,0 +21,0 @@ constructor(options?: ObservableMembraneInit); |
@@ -1,10 +0,6 @@ | ||
import { ReactiveMembrane, ReactiveMembraneShadowTarget, ReactiveMembraneAccessCallback } from './reactive-membrane'; | ||
export interface ReadOnlyHandlerInit { | ||
valueObserved?: ReactiveMembraneAccessCallback; | ||
} | ||
import { ReactiveMembrane, ReactiveMembraneShadowTarget } from './reactive-membrane'; | ||
export declare class ReadOnlyHandler { | ||
private originalTarget; | ||
private membrane; | ||
private valueObserved?; | ||
constructor(membrane: ReactiveMembrane, value: any, options?: ReadOnlyHandlerInit); | ||
constructor(membrane: ReactiveMembrane, value: any); | ||
get(shadowTarget: ReactiveMembraneShadowTarget, key: PropertyKey): any; | ||
@@ -11,0 +7,0 @@ set(shadowTarget: ReactiveMembraneShadowTarget, key: PropertyKey, value: any): boolean; |
@@ -11,3 +11,6 @@ declare const isArray: (arg: any) => arg is any[]; | ||
(o: any, propertyKey: PropertyKey): PropertyDescriptor | undefined; | ||
}, getOwnPropertyNames: (o: any) => string[], getOwnPropertySymbols: (o: any) => symbol[], preventExtensions: <T>(o: T) => T; | ||
}, getOwnPropertyNames: (o: any) => string[], getOwnPropertySymbols: (o: any) => symbol[], preventExtensions: <T>(o: T) => T, hasOwnProperty: { | ||
(v: string): boolean; | ||
(v: PropertyKey): boolean; | ||
}; | ||
declare const ArrayPush: (...items: any[]) => number, ArrayConcat: { | ||
@@ -17,8 +20,8 @@ (...items: ConcatArray<any>[]): any[]; | ||
}, ArrayMap: <U>(callbackfn: (value: any, index: number, array: any[]) => U, thisArg?: any) => U[]; | ||
export { ArrayPush, ArrayConcat, ArrayMap, isArray, getPrototypeOf, ObjectCreate, ObjectDefineProperty, ObjectDefineProperties, isExtensible, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, preventExtensions }; | ||
export { ArrayPush, ArrayConcat, ArrayMap, isArray, getPrototypeOf, ObjectCreate, ObjectDefineProperty, ObjectDefineProperties, isExtensible, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, preventExtensions, hasOwnProperty }; | ||
export declare function toString(obj: any): string; | ||
export declare function isUndefined(obj: any): obj is undefined; | ||
export declare function isFunction(obj: any): obj is Function; | ||
export declare const TargetSlot: unique symbol; | ||
export declare const unwrap: (replicaOrAny: any) => any; | ||
export declare function isObservable(value: any): boolean; | ||
export declare function isObject(obj: any): obj is object; |
@@ -11,5 +11,4 @@ /** | ||
const { isArray } = Array; | ||
const { getPrototypeOf, create: ObjectCreate, defineProperty: ObjectDefineProperty, defineProperties: ObjectDefineProperties, isExtensible, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, preventExtensions, } = Object; | ||
const { getPrototypeOf, create: ObjectCreate, defineProperty: ObjectDefineProperty, defineProperties: ObjectDefineProperties, isExtensible, getOwnPropertyDescriptor, getOwnPropertyNames, getOwnPropertySymbols, preventExtensions, hasOwnProperty, } = Object; | ||
const { push: ArrayPush, concat: ArrayConcat, map: ArrayMap, } = Array.prototype; | ||
const ObjectDotPrototype = Object.prototype; | ||
const OtS = {}.toString; | ||
@@ -30,2 +29,5 @@ function toString(obj) { | ||
} | ||
function isFunction(obj) { | ||
return typeof obj === 'function'; | ||
} | ||
const TargetSlot = Symbol(); | ||
@@ -39,13 +41,2 @@ // TODO: we are using a funky and leaky abstraction here to try to identify if | ||
: (replicaOrAny) => (replicaOrAny && replicaOrAny[TargetSlot]) || replicaOrAny; | ||
function isObservable(value) { | ||
// intentionally checking for null and undefined | ||
if (value == null) { | ||
return false; | ||
} | ||
if (isArray(value)) { | ||
return true; | ||
} | ||
const proto = getPrototypeOf(value); | ||
return (proto === ObjectDotPrototype || proto === null || getPrototypeOf(proto) === null); | ||
} | ||
function isObject(obj) { | ||
@@ -55,6 +46,9 @@ return typeof obj === 'object'; | ||
function wrapValue(membrane, value) { | ||
return membrane.valueIsObservable(value) ? membrane.getProxy(value) : value; | ||
} | ||
// Unwrap property descriptors | ||
// We only need to unwrap if value is specified | ||
function unwrapDescriptor(descriptor) { | ||
if ('value' in descriptor) { | ||
if (hasOwnProperty.call(descriptor, 'value')) { | ||
descriptor.value = unwrap(descriptor.value); | ||
@@ -64,8 +58,2 @@ } | ||
} | ||
function wrapDescriptor(membrane, descriptor) { | ||
if ('value' in descriptor) { | ||
descriptor.value = isObservable(descriptor.value) ? membrane.getProxy(descriptor.value) : descriptor.value; | ||
} | ||
return descriptor; | ||
} | ||
function lockShadowTarget(membrane, shadowTarget, originalTarget) { | ||
@@ -75,3 +63,3 @@ const targetKeys = ArrayConcat.call(getOwnPropertyNames(originalTarget), getOwnPropertySymbols(originalTarget)); | ||
let descriptor = getOwnPropertyDescriptor(originalTarget, key); | ||
// We do not need to wrap the descriptor if not configurable | ||
// We do not need to wrap the descriptor if configurable | ||
// Because we can deal with wrapping it when user goes through | ||
@@ -82,3 +70,3 @@ // Get own property descriptor. There is also a chance that this descriptor | ||
if (!descriptor.configurable) { | ||
descriptor = wrapDescriptor(membrane, descriptor); | ||
descriptor = wrapDescriptor(membrane, descriptor, wrapValue); | ||
} | ||
@@ -90,9 +78,5 @@ ObjectDefineProperty(shadowTarget, key, descriptor); | ||
class ReactiveProxyHandler { | ||
constructor(membrane, value, options) { | ||
constructor(membrane, value) { | ||
this.originalTarget = value; | ||
this.membrane = membrane; | ||
if (!isUndefined(options)) { | ||
this.valueMutated = options.valueMutated; | ||
this.valueObserved = options.valueObserved; | ||
} | ||
} | ||
@@ -105,16 +89,12 @@ get(shadowTarget, key) { | ||
const value = originalTarget[key]; | ||
const { valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { valueObserved } = membrane; | ||
valueObserved(originalTarget, key); | ||
return membrane.getProxy(value); | ||
} | ||
set(shadowTarget, key, value) { | ||
const { originalTarget, valueMutated } = this; | ||
const { originalTarget, membrane: { valueMutated } } = this; | ||
const oldValue = originalTarget[key]; | ||
if (oldValue !== value) { | ||
originalTarget[key] = value; | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
} | ||
@@ -126,5 +106,3 @@ else if (key === 'length' && isArray(originalTarget)) { | ||
// to support this use case. | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
} | ||
@@ -134,7 +112,5 @@ return true; | ||
deleteProperty(shadowTarget, key) { | ||
const { originalTarget, valueMutated } = this; | ||
const { originalTarget, membrane: { valueMutated } } = this; | ||
delete originalTarget[key]; | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
return true; | ||
@@ -149,6 +125,4 @@ } | ||
has(shadowTarget, key) { | ||
const { originalTarget, valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { originalTarget, membrane: { valueObserved } } = this; | ||
valueObserved(originalTarget, key); | ||
return key in originalTarget; | ||
@@ -182,7 +156,6 @@ } | ||
getOwnPropertyDescriptor(shadowTarget, key) { | ||
const { originalTarget, membrane, valueObserved } = this; | ||
const { originalTarget, membrane } = this; | ||
const { valueObserved } = this.membrane; | ||
// keys looked up via hasOwnProperty need to be reactive | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
valueObserved(originalTarget, key); | ||
let desc = getOwnPropertyDescriptor(originalTarget, key); | ||
@@ -193,3 +166,10 @@ if (isUndefined(desc)) { | ||
const shadowDescriptor = getOwnPropertyDescriptor(shadowTarget, key); | ||
if (!desc.configurable && !shadowDescriptor) { | ||
if (!isUndefined(shadowDescriptor)) { | ||
return shadowDescriptor; | ||
} | ||
// Note: by accessing the descriptor, the key is marked as observed | ||
// but access to the value, setter or getter (if available) cannot observe | ||
// mutations, just like regular methods, in which case we just do nothing. | ||
desc = wrapDescriptor(membrane, desc, wrapValue); | ||
if (!desc.configurable) { | ||
// If descriptor from original target is not configurable, | ||
@@ -200,6 +180,5 @@ // We must copy the wrapped descriptor over to the shadow target. | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor#Invariants | ||
desc = wrapDescriptor(membrane, desc); | ||
ObjectDefineProperty(shadowTarget, key, desc); | ||
} | ||
return shadowDescriptor || desc; | ||
return desc; | ||
} | ||
@@ -213,3 +192,4 @@ preventExtensions(shadowTarget) { | ||
defineProperty(shadowTarget, key, descriptor) { | ||
const { originalTarget, membrane, valueMutated } = this; | ||
const { originalTarget, membrane } = this; | ||
const { valueMutated } = membrane; | ||
const { configurable } = descriptor; | ||
@@ -223,3 +203,3 @@ // We have to check for value in descriptor | ||
// value is present. This eliminates getter and setter descriptors | ||
if ('writable' in descriptor && !('value' in descriptor)) { | ||
if (hasOwnProperty.call(descriptor, 'writable') && !hasOwnProperty.call(descriptor, 'value')) { | ||
const originalDescriptor = getOwnPropertyDescriptor(originalTarget, key); | ||
@@ -230,7 +210,5 @@ descriptor.value = originalDescriptor.value; | ||
if (configurable === false) { | ||
ObjectDefineProperty(shadowTarget, key, wrapDescriptor(membrane, descriptor)); | ||
ObjectDefineProperty(shadowTarget, key, wrapDescriptor(membrane, descriptor, wrapValue)); | ||
} | ||
if (!isUndefined(valueMutated)) { | ||
valueMutated(originalTarget, key); | ||
} | ||
valueMutated(originalTarget, key); | ||
return true; | ||
@@ -240,15 +218,9 @@ } | ||
function wrapDescriptor$1(membrane, descriptor) { | ||
if ('value' in descriptor) { | ||
descriptor.value = isObservable(descriptor.value) ? membrane.getReadOnlyProxy(descriptor.value) : descriptor.value; | ||
} | ||
return descriptor; | ||
function wrapReadOnlyValue(membrane, value) { | ||
return membrane.valueIsObservable(value) ? membrane.getReadOnlyProxy(value) : value; | ||
} | ||
class ReadOnlyHandler { | ||
constructor(membrane, value, options) { | ||
constructor(membrane, value) { | ||
this.originalTarget = value; | ||
this.membrane = membrane; | ||
if (!isUndefined(options)) { | ||
this.valueObserved = options.valueObserved; | ||
} | ||
} | ||
@@ -261,6 +233,4 @@ get(shadowTarget, key) { | ||
const value = originalTarget[key]; | ||
const { valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { valueObserved } = membrane; | ||
valueObserved(originalTarget, key); | ||
return membrane.getReadOnlyProxy(value); | ||
@@ -289,7 +259,4 @@ } | ||
has(shadowTarget, key) { | ||
const { originalTarget } = this; | ||
const { valueObserved } = this; | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
const { originalTarget, membrane: { valueObserved } } = this; | ||
valueObserved(originalTarget, key); | ||
return key in originalTarget; | ||
@@ -308,7 +275,6 @@ } | ||
getOwnPropertyDescriptor(shadowTarget, key) { | ||
const { originalTarget, membrane, valueObserved } = this; | ||
const { originalTarget, membrane } = this; | ||
const { valueObserved } = membrane; | ||
// keys looked up via hasOwnProperty need to be reactive | ||
if (!isUndefined(valueObserved)) { | ||
valueObserved(originalTarget, key); | ||
} | ||
valueObserved(originalTarget, key); | ||
let desc = getOwnPropertyDescriptor(originalTarget, key); | ||
@@ -319,3 +285,13 @@ if (isUndefined(desc)) { | ||
const shadowDescriptor = getOwnPropertyDescriptor(shadowTarget, key); | ||
if (!desc.configurable && !shadowDescriptor) { | ||
if (!isUndefined(shadowDescriptor)) { | ||
return shadowDescriptor; | ||
} | ||
// Note: by accessing the descriptor, the key is marked as observed | ||
// but access to the value or getter (if available) cannot be observed, | ||
// just like regular methods, in which case we just do nothing. | ||
desc = wrapDescriptor(membrane, desc, wrapReadOnlyValue); | ||
if (hasOwnProperty.call(desc, 'set')) { | ||
desc.set = undefined; // readOnly membrane does not allow setters | ||
} | ||
if (!desc.configurable) { | ||
// If descriptor from original target is not configurable, | ||
@@ -326,6 +302,5 @@ // We must copy the wrapped descriptor over to the shadow target. | ||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor#Invariants | ||
desc = wrapDescriptor$1(membrane, desc); | ||
ObjectDefineProperty(shadowTarget, key, desc); | ||
} | ||
return shadowDescriptor || desc; | ||
return desc; | ||
} | ||
@@ -417,15 +392,64 @@ preventExtensions(shadowTarget) { | ||
} | ||
const ObjectDotPrototype = Object.prototype; | ||
function defaultValueIsObservable(value) { | ||
// intentionally checking for null and undefined | ||
if (value == null) { | ||
return false; | ||
} | ||
if (isArray(value)) { | ||
return true; | ||
} | ||
const proto = getPrototypeOf(value); | ||
return (proto === ObjectDotPrototype || proto === null || getPrototypeOf(proto) === null); | ||
} | ||
const defaultValueObserved = (obj, key) => { | ||
/* do nothing */ | ||
}; | ||
const defaultValueMutated = (obj, key) => { | ||
/* do nothing */ | ||
}; | ||
const defaultValueDistortion = (value) => value; | ||
function wrapDescriptor(membrane, descriptor, getValue) { | ||
const { set, get } = descriptor; | ||
if (hasOwnProperty.call(descriptor, 'value')) { | ||
descriptor.value = getValue(membrane, descriptor.value); | ||
} | ||
else { | ||
if (!isUndefined(get)) { | ||
descriptor.get = function () { | ||
// invoking the original getter with the original target | ||
return getValue(membrane, get.call(unwrap(this))); | ||
}; | ||
} | ||
if (!isUndefined(set)) { | ||
descriptor.set = function (value) { | ||
// At this point we don't have a clear indication of whether | ||
// or not a valid mutation will occur, we don't have the key, | ||
// and we are not sure why and how they are invoking this setter. | ||
// Nevertheless we preserve the original semantics by invoking the | ||
// original setter with the original target and the unwrapped value | ||
set.call(unwrap(this), membrane.unwrapProxy(value)); | ||
}; | ||
} | ||
} | ||
return descriptor; | ||
} | ||
class ReactiveMembrane { | ||
constructor(options) { | ||
this.valueDistortion = defaultValueDistortion; | ||
this.valueMutated = defaultValueMutated; | ||
this.valueObserved = defaultValueObserved; | ||
this.valueIsObservable = defaultValueIsObservable; | ||
this.objectGraph = new WeakMap(); | ||
if (!isUndefined(options)) { | ||
this.valueDistortion = options.valueDistortion; | ||
this.valueMutated = options.valueMutated; | ||
this.valueObserved = options.valueObserved; | ||
const { valueDistortion, valueMutated, valueObserved, valueIsObservable } = options; | ||
this.valueDistortion = isFunction(valueDistortion) ? valueDistortion : defaultValueDistortion; | ||
this.valueMutated = isFunction(valueMutated) ? valueMutated : defaultValueMutated; | ||
this.valueObserved = isFunction(valueObserved) ? valueObserved : defaultValueObserved; | ||
this.valueIsObservable = isFunction(valueIsObservable) ? valueIsObservable : defaultValueIsObservable; | ||
} | ||
} | ||
getProxy(value) { | ||
const { valueDistortion } = this; | ||
const distorted = isUndefined(valueDistortion) ? value : valueDistortion(value); | ||
if (isObservable(distorted)) { | ||
const distorted = this.valueDistortion(value); | ||
if (this.valueIsObservable(distorted)) { | ||
const o = this.getReactiveState(distorted); | ||
@@ -439,5 +463,4 @@ // when trying to extract the writable version of a readonly | ||
getReadOnlyProxy(value) { | ||
const { valueDistortion } = this; | ||
const distorted = isUndefined(valueDistortion) ? value : valueDistortion(value); | ||
if (isObservable(distorted)) { | ||
const distorted = this.valueDistortion(value); | ||
if (this.valueIsObservable(distorted)) { | ||
return this.getReactiveState(distorted).readOnly; | ||
@@ -451,4 +474,3 @@ } | ||
getReactiveState(value) { | ||
const membrane = this; | ||
const { objectGraph, valueMutated, valueObserved, } = membrane; | ||
const { objectGraph, } = this; | ||
value = unwrap(value); | ||
@@ -459,29 +481,19 @@ let reactiveState = objectGraph.get(value); | ||
} | ||
reactiveState = ObjectDefineProperties(ObjectCreate(null), { | ||
reactive: { | ||
get() { | ||
const reactiveHandler = new ReactiveProxyHandler(membrane, value, { | ||
valueMutated, | ||
valueObserved, | ||
}); | ||
// caching the reactive proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), reactiveHandler); | ||
ObjectDefineProperty(this, 'reactive', { value: proxy }); | ||
return proxy; | ||
}, | ||
configurable: true, | ||
const membrane = this; | ||
reactiveState = { | ||
get reactive() { | ||
const reactiveHandler = new ReactiveProxyHandler(membrane, value); | ||
// caching the reactive proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), reactiveHandler); | ||
ObjectDefineProperty(this, 'reactive', { value: proxy }); | ||
return proxy; | ||
}, | ||
readOnly: { | ||
get() { | ||
const readOnlyHandler = new ReadOnlyHandler(membrane, value, { | ||
valueObserved, | ||
}); | ||
// caching the readOnly proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), readOnlyHandler); | ||
ObjectDefineProperty(this, 'readOnly', { value: proxy }); | ||
return proxy; | ||
}, | ||
configurable: true, | ||
get readOnly() { | ||
const readOnlyHandler = new ReadOnlyHandler(membrane, value); | ||
// caching the readOnly proxy after the first time it is accessed | ||
const proxy = new Proxy(createShadowTarget(value), readOnlyHandler); | ||
ObjectDefineProperty(this, 'readOnly', { value: proxy }); | ||
return proxy; | ||
} | ||
}); | ||
}; | ||
objectGraph.set(value, reactiveState); | ||
@@ -488,0 +500,0 @@ return reactiveState; |
@@ -1,1 +0,1 @@ | ||
(function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.ObservableMembrane=b()})(this,function(){"use strict";function a(a){return a===void 0}function b(a){if(null==a)return!1;if(i(a))return!0;const b=j(a);return b===v||null===b||null===j(b)}function c(a){return"object"==typeof a}function d(a){return"value"in a&&(a.value=y(a.value)),a}function e(a,c){return"value"in c&&(c.value=b(c.value)?a.getProxy(c.value):c.value),c}function f(a,b,c){const d=t.call(p(c),q(c));d.forEach(d=>{let f=o(c,d);f.configurable||(f=e(a,f)),l(b,d,f)}),r(b)}function g(a,c){return"value"in c&&(c.value=b(c.value)?a.getReadOnlyProxy(c.value):c.value),c}function h(a){let b;return i(a)?b=[]:c(a)&&(b={}),b}const{isArray:i}=Array,{getPrototypeOf:j,create:k,defineProperty:l,defineProperties:m,isExtensible:n,getOwnPropertyDescriptor:o,getOwnPropertyNames:p,getOwnPropertySymbols:q,preventExtensions:r}=Object,{push:s,concat:t,map:u}=Array.prototype,v=Object.prototype,w=Symbol(),{getKey:x}=Proxy,y=x?a=>a&&x(a,w)||a:a=>a&&a[w]||a;class z{constructor(b,c,d){this.originalTarget=c,this.membrane=b,a(d)||(this.valueMutated=d.valueMutated,this.valueObserved=d.valueObserved)}get(b,c){const{originalTarget:d,membrane:e}=this;if(c===w)return d;const f=d[c],{valueObserved:g}=this;return a(g)||g(d,c),e.getProxy(f)}set(b,c,d){const{originalTarget:e,valueMutated:f}=this,g=e[c];return g===d?"length"===c&&i(e)&&!a(f)&&f(e,c):(e[c]=d,!a(f)&&f(e,c)),!0}deleteProperty(b,c){const{originalTarget:d,valueMutated:e}=this;return delete d[c],a(e)||e(d,c),!0}apply(){}construct(){}has(b,c){const{originalTarget:d,valueObserved:e}=this;return a(e)||e(d,c),c in d}ownKeys(){const{originalTarget:a}=this;return t.call(p(a),q(a))}isExtensible(a){const b=n(a);if(!b)return b;const{originalTarget:c,membrane:d}=this,e=n(c);return e||f(d,a,c),e}setPrototypeOf(){}getPrototypeOf(){const{originalTarget:a}=this;return j(a)}getOwnPropertyDescriptor(b,c){const{originalTarget:d,membrane:f,valueObserved:g}=this;a(g)||g(d,c);let h=o(d,c);if(a(h))return h;const i=o(b,c);return h.configurable||i||(h=e(f,h),l(b,c,h)),i||h}preventExtensions(a){const{originalTarget:b,membrane:c}=this;return f(c,a,b),r(b),!0}defineProperty(b,c,f){const{originalTarget:g,membrane:h,valueMutated:i}=this,{configurable:j}=f;if("writable"in f&&!("value"in f)){const a=o(g,c);f.value=a.value}return l(g,c,d(f)),!1===j&&l(b,c,e(h,f)),a(i)||i(g,c),!0}}class A{constructor(b,c,d){this.originalTarget=c,this.membrane=b,a(d)||(this.valueObserved=d.valueObserved)}get(b,c){const{membrane:d,originalTarget:e}=this;if(c===w)return e;const f=e[c],{valueObserved:g}=this;return a(g)||g(e,c),d.getReadOnlyProxy(f)}set(){return!1}deleteProperty(){return!1}apply(){}construct(){}has(b,c){const{originalTarget:d}=this,{valueObserved:e}=this;return a(e)||e(d,c),c in d}ownKeys(){const{originalTarget:a}=this;return t.call(p(a),q(a))}setPrototypeOf(){}getOwnPropertyDescriptor(b,c){const{originalTarget:d,membrane:e,valueObserved:f}=this;a(f)||f(d,c);let h=o(d,c);if(a(h))return h;const i=o(b,c);return h.configurable||i||(h=g(e,h),l(b,c,h)),i||h}preventExtensions(){return!1}defineProperty(){return!1}}return class{constructor(b){this.objectGraph=new WeakMap,a(b)||(this.valueDistortion=b.valueDistortion,this.valueMutated=b.valueMutated,this.valueObserved=b.valueObserved)}getProxy(c){const{valueDistortion:d}=this,e=a(d)?c:d(c);if(b(e)){const a=this.getReactiveState(e);return a.readOnly===c?c:a.reactive}return e}getReadOnlyProxy(c){const{valueDistortion:d}=this,e=a(d)?c:d(c);return b(e)?this.getReactiveState(e).readOnly:e}unwrapProxy(a){return y(a)}getReactiveState(a){const b=this,{objectGraph:c,valueMutated:d,valueObserved:e}=b;a=y(a);let f=c.get(a);return f?f:(f=m(k(null),{reactive:{get(){const c=new z(b,a,{valueMutated:d,valueObserved:e}),f=new Proxy(h(a),c);return l(this,"reactive",{value:f}),f},configurable:!0},readOnly:{get(){const c=new A(b,a,{valueObserved:e}),d=new Proxy(h(a),c);return l(this,"readOnly",{value:d}),d},configurable:!0}}),c.set(a,f),f)}}}); | ||
(function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.ObservableMembrane=b()})(this,function(){"use strict";function a(a){return a===void 0}function b(a){return"function"==typeof a}function c(a){return"object"==typeof a}function d(a,b){return a.valueIsObservable(b)?a.getProxy(b):b}function e(a){return u.call(a,"value")&&(a.value=A(a.value)),a}function f(a,b,c){const e=w.call(r(c),s(c));e.forEach(e=>{let f=q(c,e);f.configurable||(f=j(a,f,d)),n(b,e,f)}),t(b)}function g(a,b){return a.valueIsObservable(b)?a.getReadOnlyProxy(b):b}function h(a){let b;return k(a)?b=[]:c(a)&&(b={}),b}function i(a){if(null==a)return!1;if(k(a))return!0;const b=l(a);return b===D||null===b||null===l(b)}function j(b,c,d){const{set:e,get:f}=c;return u.call(c,"value")?c.value=d(b,c.value):(!a(f)&&(c.get=function(){return d(b,f.call(A(this)))}),!a(e)&&(c.set=function(a){e.call(A(this),b.unwrapProxy(a))})),c}const{isArray:k}=Array,{getPrototypeOf:l,create:m,defineProperty:n,defineProperties:o,isExtensible:p,getOwnPropertyDescriptor:q,getOwnPropertyNames:r,getOwnPropertySymbols:s,preventExtensions:t,hasOwnProperty:u}=Object,{push:v,concat:w,map:x}=Array.prototype,y=Symbol(),{getKey:z}=Proxy,A=z?a=>a&&z(a,y)||a:a=>a&&a[y]||a;class B{constructor(a,b){this.originalTarget=b,this.membrane=a}get(a,b){const{originalTarget:c,membrane:d}=this;if(b===y)return c;const e=c[b],{valueObserved:f}=d;return f(c,b),d.getProxy(e)}set(a,b,c){const{originalTarget:d,membrane:{valueMutated:e}}=this,f=d[b];return f===c?"length"===b&&k(d)&&e(d,b):(d[b]=c,e(d,b)),!0}deleteProperty(a,b){const{originalTarget:c,membrane:{valueMutated:d}}=this;return delete c[b],d(c,b),!0}apply(){}construct(){}has(a,b){const{originalTarget:c,membrane:{valueObserved:d}}=this;return d(c,b),b in c}ownKeys(){const{originalTarget:a}=this;return w.call(r(a),s(a))}isExtensible(a){const b=p(a);if(!b)return b;const{originalTarget:c,membrane:d}=this,e=p(c);return e||f(d,a,c),e}setPrototypeOf(){}getPrototypeOf(){const{originalTarget:a}=this;return l(a)}getOwnPropertyDescriptor(b,c){const{originalTarget:e,membrane:f}=this,{valueObserved:g}=this.membrane;g(e,c);let h=q(e,c);if(a(h))return h;const i=q(b,c);return a(i)?(h=j(f,h,d),h.configurable||n(b,c,h),h):i}preventExtensions(a){const{originalTarget:b,membrane:c}=this;return f(c,a,b),t(b),!0}defineProperty(a,b,c){const{originalTarget:f,membrane:g}=this,{valueMutated:h}=g,{configurable:i}=c;if(u.call(c,"writable")&&!u.call(c,"value")){const a=q(f,b);c.value=a.value}return n(f,b,e(c)),!1===i&&n(a,b,j(g,c,d)),h(f,b),!0}}class C{constructor(a,b){this.originalTarget=b,this.membrane=a}get(a,b){const{membrane:c,originalTarget:d}=this;if(b===y)return d;const e=d[b],{valueObserved:f}=c;return f(d,b),c.getReadOnlyProxy(e)}set(){return!1}deleteProperty(){return!1}apply(){}construct(){}has(a,b){const{originalTarget:c,membrane:{valueObserved:d}}=this;return d(c,b),b in c}ownKeys(){const{originalTarget:a}=this;return w.call(r(a),s(a))}setPrototypeOf(){}getOwnPropertyDescriptor(b,c){const{originalTarget:d,membrane:e}=this,{valueObserved:f}=e;f(d,c);let h=q(d,c);if(a(h))return h;const i=q(b,c);return a(i)?(h=j(e,h,g),u.call(h,"set")&&(h.set=void 0),h.configurable||n(b,c,h),h):i}preventExtensions(){return!1}defineProperty(){return!1}}const D=Object.prototype,E=()=>{},F=()=>{},G=a=>a;return class{constructor(c){if(this.valueDistortion=G,this.valueMutated=F,this.valueObserved=E,this.valueIsObservable=i,this.objectGraph=new WeakMap,!a(c)){const{valueDistortion:a,valueMutated:d,valueObserved:e,valueIsObservable:f}=c;this.valueDistortion=b(a)?a:G,this.valueMutated=b(d)?d:F,this.valueObserved=b(e)?e:E,this.valueIsObservable=b(f)?f:i}}getProxy(a){const b=this.valueDistortion(a);if(this.valueIsObservable(b)){const c=this.getReactiveState(b);return c.readOnly===a?a:c.reactive}return b}getReadOnlyProxy(a){const b=this.valueDistortion(a);return this.valueIsObservable(b)?this.getReactiveState(b).readOnly:b}unwrapProxy(a){return A(a)}getReactiveState(a){const{objectGraph:b}=this;a=A(a);let c=b.get(a);if(c)return c;const d=this;return c={get reactive(){const b=new B(d,a),c=new Proxy(h(a),b);return n(this,"reactive",{value:c}),c},get readOnly(){const b=new C(d,a),c=new Proxy(h(a),b);return n(this,"readOnly",{value:c}),c}},b.set(a,c),c}}}); |
{ | ||
"name": "observable-membrane", | ||
"version": "0.25.0", | ||
"version": "0.26.0", | ||
"description": "A Javascript Membrane implementation using Proxies to observe mutation on an object graph", | ||
@@ -5,0 +5,0 @@ "main": "dist/commonjs/observable-membrane.js", |
@@ -10,2 +10,3 @@ # Observable Membrane | ||
* [Tom van Cutsem's original article, "Isolating application sub-components with membranes"](https://tvcutsem.github.io/membranes) | ||
* [Tom van Cutsem's original article, "Membranes in JavaScript"](https://tvcutsem.github.io/js-membranes) | ||
@@ -198,2 +199,6 @@ * [es-membrane library by Alexander J. Vincent](https://github.com/ajvincent/es-membrane) | ||
## Example | ||
There are [runnable examples](https://github.com/salesforce/observable-membrane/tree/master/examples) in this Git repository. You must build this package as described in the [Contributing Guide](CONTRIBUTING.md) before attempting to run the examples. Additionally, some of the examples might be relying on features that are not supported in all browsers (e.g.: [reactivo-element](https://github.com/salesforce/observable-membrane/tree/master/examples/reactivo-element) example relies on Web Components APIs). | ||
## API | ||
@@ -200,0 +205,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
82001
1517
267
1