A cached Proxy with optional timeouts per property to simplify complex remote scenarios
npm install cached-proxy 
Social Media Photo by Bozhin Karaivanov on Unsplash
A cached Proxy with optional timeouts per property to simplify complex remote scenarios.
``js
import CachedProxy from 'https://esm.run/cached-proxy';
const remote = new CachedProxy({ id: 123 }, {
// optional: 0 means ASAP, otherwise cached for N milliseconds
timeout: 0,
// example: compute some remote value synchronously
get(target, property) {
const xhr = new XMLHttpRequest;
xhr.open('GET', 'end/point', false);
xhr.send(JSON.stringify({ trap: 'get', args: [target.id, property] }));
return JSON.parse(xhr.responseText);
}
});
// executes the XHR once, returns whatever value was cached
remote.test === remote.test;
setTimeout(() => {
remote.test; // runs the XHR again
}, 10);
`
The example is there to explain that given enough amount of traps, it is possible via Atomics or synchronous / asynchronous behavior to retrieve once properties and values from elsewhere, similarly to how reflected-ffi memoized cache works but in an easier to orchestrate way.
| | cached | timeout | drop | reset |
| :----------------------- | :----: | :-----: | :--: | :---: |
| apply | | | | |
| construct | | | | |
| defineProperty | | | ☑️ | |
| deleteProperty | | | ☑️ | |
| get | ☑️ | ☑️ | | |
| getOwnPropertyDescriptor | ☑️ | ☑️ | | |
| getPrototypeOf | ☑️ | | | |
| has | ☑️ | ☑️ | | |
| isExtensible | ☑️ | | | |
| ownKeys * | ☑️ | ✔️ | | |
| preventExtensions | | | | |
| set | | | ☑️ | |
| setPrototypeOf | | | | ☑️ |
#### Traps Explainer
* cached means each trap result is weakly stored
timeout means that, if a timeout optional integer is passed as proxy handler field, get, getOwnPropertyDescriptor and has will be dropped* after that amount of time (in milliseconds)
drop means that the eventually stored value for that property or accessor will be instantly removed from the cache*, affecting also ownKeys but without affecting isExtensible and getPrototypeOf
* reset means that all weakly related values will be erased per property or target reference, effectively invalidating the whole cache for any trap that has one
The drop and reset utilities are also exposed via the module where drop(ref, property) will invalidate both ownKeys and the cache per specific property while reset(ref) will invalidate the whole cache per specific reference.
Please note: the reference is not the proxied one, it's the original one you must own, otherwise nothing will happen/work as expected, example:
`js
import Proxy, { drop, reset } from 'https://esm.run/cached-proxy';
const ref = { my: 'reference' };
const proxied = Proxy(ref);
// when/if needed, this works:
drop(ref, 'property');
// ... or ...
reset(ref);
// while this will not work:
drop(proxied, 'property');
// ... or ...
reset(proxied);
`
This is to avoid leaking the cache intent of the proxy owner/creator.
arrays have a get trap that resets the cache if the retrieved property is neither length nor an index (an unsigned integer, 0 to max array length). This makes usage of a timeout almost irrevelant because methods that mutate the array should drop* properties as needed.
dom nodes should likely use no cache due their highly mutable nature, however it is possible to use a handler that checks the returned value by specifying a getCached(target, property, value) that if returns true will cache the entry, otherwise it won't cache anything and, if the value is a function able to mutate the instance, it should likely reset(target) reference* to avoid any undesired cache.
The Cached suffix can be used for each of these traps:
* getCached, to skip caching undesired results or reset the cache in case the value is a function with side effects (i.e. methods that mutate internally the proxied reference)getOwnPropertyDescriptorCached
* , to skip caching a descriptor or reset the cache in case it is an accessor with side effects (i.e. textContent or others)has
* , to skip caching a specific key in proxy` check