-
-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Callback triggered for changes on inheriting objects #74
Comments
Would you mind providing a simple working code sample? Thanks! |
Sure thing, will prepare and post here in next few days. |
@DarrenPaulWright please see https://codepen.io/alex-shnayder/pen/rNWGBQL In the examples I have both plain JS example and one using on-change. Thanks. |
@DarrenPaulWright did you have a chance to look at my example. I would like to hear yuour feedback, and understand if may be it will make sense to adjust on-change to support this scenario. Thanks. |
Sorry if I'm missing something, but could you just do |
@DarrenPaulWright what do you mean by do |
In your code you are using As far as I can tell, there is no way for a Proxy to detect when it gets added as a prototype to another object or to detect later if it is part of a prototype chain. Another option if you must use |
@DarrenPaulWright I tried to not deep dive into details of what I'm doing to make it simpler, but may be that actually made it more complicated to understand the use case. We have some objects which we are trying to protect from modification during development, today we are using Object.freeze to do deep freeze of objects. If you freeze an object and try to modify any of it's properties it will fail. The problem with using Object.freeze is that when you have large objects with deep nest level that becomes an expensive operation with real impact on application performance. I was hoping to use on-change to solve same problem of preventing modification to objects while at same not paying same performance price up front. So back to your question original question, skipping I will look at your suggestion of using check inside As for not been able to detect inside Proxy when operation is performed on watchedSubject or on derived object. Thanks. |
Checked and not possible, because Here
reciever is the object which I'm actually intrested in, if I had that I could have performed check to see if operation is performed on original or derived object, but it's not passed in into Does it make sense to add |
I think I will just go with custom Proxy based solution, while it would have been nice to use on-change I think it might be a bit much to start doing changes to it just for this use case. I will link here to my code sample once I'm done |
In case any one will be intrested in the final solution I'm going with /**
* Utility for making objects read only using Proxy.
* Instead of using Object.freeze which is expensive at runtime as it will eagerly freeze all props.
* An operation which can take long time for large and deep nested objects.
*/
type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
type ImmutableObject<T> = {readonly [K in keyof T]: Immutable<T[K]>};
export type Immutable<T> = T extends ImmutablePrimitive
? T
: T extends Map<infer K, infer V>
? ImmutableMap<K, V>
: T extends Set<infer M>
? ImmutableSet<M>
: ImmutableObject<T>;
class ReadonlyObjectModification extends TypeError {
constructor(message: string, public target: object) {
super(message);
}
}
const objectToProxyMap = new WeakMap<object, ProxyHandler<object>>();
const readOnlyHandler: ProxyHandler<object> = {
/**
* Lazy wrap each nested property as it's own read only Proxy
*/
get(target, propertyKey, receiver?): any {
const value = Reflect.get(target, propertyKey, receiver);
if (typeof value === 'object' && value !== null) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, propertyKey);
if (descriptor?.configurable) {
return buildReadonlyProxy(value);
}
}
return value;
},
/**
* Fail set operation when doing modification of read only object.
* Allow set operation if it's performed on derived object rather original one
*/
set(target, propertyKey, value, receiver) {
const proxy = objectToProxyMap.get(target);
if (receiver === proxy) {
throw new ReadonlyObjectModification(
`Can not set property '${String(propertyKey)}' on readonly object`,
target
);
return false;
}
return Reflect.set(target, propertyKey, value, receiver);
},
/**
* Prevent deletion of properties from readonly object
*/
deleteProperty(target, propertyKey) {
throw new ReadonlyObjectModification(
`Can not delete property '${String(propertyKey)}' on readonly object`,
target
);
},
/**
* Prevent addition of new properties to read only object
*/
defineProperty(target, propertyKey) {
throw new ReadonlyObjectModification(
`Can not define property '${String(propertyKey)}' on readonly object`,
target
);
},
};
/**
* Build read only proxy which will generate nested read only proxies as needed
* @param object
*/
export function buildReadonlyProxy<T extends object>(object: T): Immutable<T> {
let proxy = objectToProxyMap.get(object);
if (proxy) {
return proxy as Immutable<T>;
}
proxy = new Proxy(object, readOnlyHandler);
objectToProxyMap.set(object, proxy);
return proxy as Immutable<T>;
} |
I see some unexpected behavior and was wondering if may be I'm streching beyond on-change intended use case.
Lets I have an objects (lets call it
obj
) for which I created a proxy using on-change.Now I have a flow that creates new object (lets call it
derived
) using the proxy as prototype.When I set some property on
obj
I expect callback to be called and indeed it is.When I set some new property on
derived
that does not exist onobj
, here again callback is called.Since I don't modify watched object, but rather object that has watched object as prototype, I expect that callback won't be called, but it is.
From what I can see, on-change does not take this scenario into account or may be that is by design ?
Would love to hear your opinion on this.
P.S. What I'm trying to do is to protect objects from been modified, but still allow objects that have them as prototypes be modified to mask values on prototype.
Thanks.
The text was updated successfully, but these errors were encountered: