# proxy-compare [![CI](https://img.shields.io/github/actions/workflow/status/dai-shi/proxy-compare/ci.yml?branch=main)](https://github.com/dai-shi/proxy-compare/actions?query=workflow%3ACI) [![npm](https://img.shields.io/npm/v/proxy-compare)](https://www.npmjs.com/package/proxy-compare) [![size](https://img.shields.io/bundlephobia/minzip/proxy-compare)](https://bundlephobia.com/result?p=proxy-compare) [![discord](https://img.shields.io/discord/627656437971288081)](https://discord.gg/MrQdmzd) Compare two objects using accessed properties with Proxy ## Introduction This is an internal library used in state management libraries such as [React Tracked](https://react-tracked.js.org) and [Valtio](https://github.com/pmndrs/valtio). ## Install ```bash npm install proxy-compare ``` ## Usage ```javascript $ node > const { createProxy, isChanged } = require('proxy-compare') undefined > state = { a: 1, b: 2 } { a: 1, b: 2 } > affected = new WeakMap() WeakMap { [items unknown] } > proxy = createProxy(state, affected) Proxy [ { a: 1, b: 2 }, { get: [Function: get], has: [Function: has], getOwnPropertyDescriptor: [Function: getOwnPropertyDescriptor], ownKeys: [Function: ownKeys] } ] > proxy.a 1 > isChanged(state, { a: 1, b: 22 }, affected) false > isChanged(state, { a: 11, b: 2 }, affected) true ``` ## API ### createProxy Create a proxy. This function will create a proxy at top level and proxy nested objects as you access them, in order to keep track of which properties were accessed via get/has proxy handlers: NOTE: Printing of WeakMap is hard to inspect and not very readable for this purpose you can use the `affectedToPathList` helper. #### Parameters * `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Object that will be wrapped on the proxy. * `affected` **[WeakMap](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)<[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object), unknown>** WeakMap that will hold the tracking of which properties in the proxied object were accessed. * `proxyCache` **[WeakMap](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)<[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object), unknown>?** WeakMap that will help keep referential identity for proxies. #### Examples ```javascript import { createProxy } from 'proxy-compare'; const original = { a: "1", c: "2", d: { e: "3" } }; const affected = new WeakMap(); const proxy = createProxy(original, affected); proxy.a // Will mark as used and track its value. // This will update the affected WeakMap with original as key // and a Set with "a" proxy.d // Will mark "d" as accessed to track and proxy itself ({ e: "3" }). // This will update the affected WeakMap with original as key // and a Set with "d" ``` Returns **[Proxy](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy)<[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)>** Object wrapped in a proxy. ### isChanged Compare changes on objects. This will compare the affected properties on tracked objects inside the proxy to check if there were any changes made to it, by default if no property was accessed on the proxy it will attempt to do a reference equality check for the objects provided (Object.is(a, b)). If you access a property on the proxy, then isChanged will only compare the affected properties. #### Parameters * `prevObj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The previous object to compare. * `nextObj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Object to compare with the previous one. * `affected` **[WeakMap](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)<[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object), unknown>** WeakMap that holds the tracking of which properties in the proxied object were accessed. * `cache` **[WeakMap](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)<[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object), unknown>?** WeakMap that holds a cache of the comparisons for better performance with repetitive comparisons, and to avoid infinite loop with circular structures. #### Examples ```javascript import { createProxy, isChanged } from 'proxy-compare'; const obj = { a: "1", c: "2", d: { e: "3" } }; const affected = new WeakMap(); const proxy = createProxy(obj, affected); proxy.a isChanged(obj, { a: "1" }, affected) // false proxy.a = "2" isChanged(obj, { a: "1" }, affected) // true ``` Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Boolean indicating if the affected property on the object has changed. ### getUntracked Unwrap proxy to get the original object. Used to retrieve the original object used to create the proxy instance with `createProxy`. #### Parameters * `obj` **[Proxy](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy)<[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)>** The proxy wrapper of the originial object. #### Examples ```javascript import { createProxy, getUntracked } from 'proxy-compare'; const original = { a: "1", c: "2", d: { e: "3" } }; const affected = new WeakMap(); const proxy = createProxy(original, affected); const originalFromProxy = getUntracked(proxy) Object.is(original, originalFromProxy) // true isChanged(original, originalFromProxy, affected) // false ``` Returns **([object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object) | null)** Return either the unwrapped object if exists. ### markToTrack Mark object to be tracked. This function marks an object that will be passed into `createProxy` as marked to track or not. By default only Array and Object are marked to track, so this is useful for example to mark a class instance to track or to mark a object to be untracked when creating your proxy. #### Parameters * `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Object to mark as tracked or not. * `mark` Boolean indicating whether you want to track this object or not. (optional, default `true`) #### Examples ```javascript import { createProxy, markToTrack, isChanged } from 'proxy-compare'; const nested = { e: "3" } markToTrack(nested, false) const original = { a: "1", c: "2", d: nested }; const affected = new WeakMap(); const proxy = createProxy(original, affected); proxy.d.e isChanged(original, { d: { e: "3" } }, affected) // true ``` Returns **any** No return. ### affectedToPathList Convert `affected` to path list `affected` is a weak map which is not printable. This function is can convert it to printable path list. It's for debugging purpose. #### Parameters * `obj` **any** An object that is used with `createProxy`. * `affected` **[WeakMap](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)<[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object), any>** A weak map that is used with `createProxy`. * `onlyWithValues` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** An optional boolean to exclude object getters. Returns **any** An array of paths. ### replaceNewProxy replace newProxy function. This can be used if you want to use proxy-polyfill. Note that proxy-polyfill can't polyfill everything. Use it at your own risk. #### Parameters * `fn` **any** ## Projects using this library * [react-tracked](https://github.com/dai-shi/react-tracked) * [reactive-react-redux](https://github.com/dai-shi/reactive-react-redux) * [svelte3-redux](https://github.com/dai-shi/svelte3-redux) * [proxy-memoize](https://github.com/dai-shi/proxy-memoize) * [valtio](https://github.com/pmndrs/valtio) ## Similar libraries * [proxyequal](https://www.npmjs.com/package/proxyequal) * [proxy-state-tree](https://www.npmjs.com/package/proxy-state-tree) * [proxy-watcher](https://www.npmjs.com/package/proxy-watcher)