A library that recursively watches an object for mutations via Proxys
and tells you which paths changed.
The following values are fully supported: primitives, functions, getters, setters, Dates, RegExps, Objects, Arrays, ArrayBuffers, TypedArrays, Maps and Sets.
Other values are partially supported: Promises, WeakMaps, WeakSets and custom classes. Basically mutations happening inside them won't be detected, however setting any of these as a value will be detected as a mutation.
The following functions are provided.
- watch: starts watching an object for mutations, and returns a proxy object.
- unwatch: stops watching an object for mutations, and returns the raw object being proxied.
- record: records and returns an array or map of root paths that have been accessed while executing the provided function.
- target: returns the raw object being proxied.
- isProxy: returns a boolean indicating if the provided object is a proxy object or not.
- Mutations happening at locations that need to be reached via a Symbol aren't detected, as a precise string path for them can't be generated (e.g.
{ [Symbol ()]: { unreachableViaStringPath: true }
). - Referencing the same object under multiple paths at the same time will throw an error. This is not supported because the library can't lazily deeply watch the watched object safely when duplicate objects are used.
- Referencing the watched object within itself will thrown an error.
- A path is a dot-separated string of keys, therefore using only dots as your keys may lead to some weird paths generated that can't be parsed properly (e.g.
foo.....bar
, is thatfoo/.../bar
orfoo/././bar
?) - Proxys will make certain operations even 100x slower on current engines, however those operations are simple things like property accesses which will almost never be your actual bottleneck, even with this kind of performance hit.
- Proxys are un-polyfillable, if you have to support platforms that don't support them you can't use this library.
- It's possible that the callback will be called when nothing actually changed (e.g. it will happen if you to flip a boolean twice synchronously).
npm install --save proxy-watcher
The following interface is provided:
type Disposer = () => void;
function watch ( object: Object, callback: ( paths: string[] ) => any ): [Proxy, Disposer];
function unwatch ( proxy: Proxy ): Object;
function record ( proxy: Proxy, fn: ( proxy: Proxy ) => void ): string[];
function record ( proxies: Proxy[], fn: ( ...proxies: Proxy[] ) => void ): Map<Proxy, string[]>;
function target ( proxy: Proxy ): Object;
function isProxy ( object: Object | Proxy ): boolean;
Basically you have to pass the watch
function an object, which will be watched for any changes, and a callback, which will be called with an array of paths changed whenever changes are detected.
The function will return an array containing a proxy object, always use this object rather than the raw object you pass the watch
function, and a disposer function, which when called will stop the watching operation and will return back the original unproxied object.
import {watch, unwatch, record, target, isProxy} from 'proxy-watcher';
/* WATCH */
const [proxy, dispose] = watch ({
foo: true,
arr: [1, 2, 3]
}, paths => {
console.log ( 'Something changed at these paths:', paths );
});
proxy.foo; // => true
proxy.arr; // => [1, 2, 3]
proxy.foo = true; // Nothing actually changed, the callback won't be called
proxy.arr[0] = 1; // Nothing actually changed, the callback won't be called
proxy.foo = false; // Callback called with paths: ['foo']
proxy.bar = true; // Callback called with paths: ['bar']
proxy.arr.push ( 4 ) = true; // Callback called with paths: ['arr.3', 'arr']
/* RECORD */ // Record root keys accessed
record ( proxy, () => {
console.log ( proxy.foo );
}); // => ['foo']
/* TARGET */ // Return the raw unproxied object
target ( proxy ); // => { foo: false, bar = true, arr: [1, 2, 3, 4] }
/* IS PROXY */
isProxy ( proxy ); // => true
isProxy ( target ( proxy ) ); // => false
/* UNWATCH */
dispose (); // Stop watching
unwatch ( proxy ); // Altertnative way to stop watching
MIT © Fabio Spampinato