@data-client/normalizr
Version:
Normalizes and denormalizes JSON according to schema for Redux and Flux applications
72 lines (63 loc) • 2.33 kB
text/typescript
import { UNDEF } from '../denormalize/UNDEF.js';
/** Maps a (ordered) list of dependencies to a value.
*
* Useful as a memoization cache for flat/normalized stores.
*
* All dependencies are only weakly referenced, allowing automatic garbage collection
* when any dependencies are no longer used.
*/
export default class WeakDependencyMap<
Path,
K extends object = object,
V = any,
> {
private readonly next = new WeakMap<K, Link<Path, K, V>>();
private nextPath: Path | undefined = undefined;
get(entity: K, getDependency: GetDependency<Path, K | symbol>) {
let curLink = this.next.get(entity);
if (!curLink) return EMPTY;
while (curLink.nextPath) {
// we cannot perform lookups with `undefined`, so we use a special object to represent undefined
const nextEntity = getDependency(curLink.nextPath) ?? UNDEF;
curLink = curLink.next.get(nextEntity as any);
if (!curLink) return EMPTY;
}
// curLink exists, but has no path - so must have a value
return [curLink.value, curLink.journey] as readonly [V, Path[]];
}
set(dependencies: Dep<Path, K>[], value: V) {
if (dependencies.length < 1) throw new KeySize();
let curLink: Link<Path, K, V> = this as any;
for (const { entity, path } of dependencies) {
let nextLink = curLink.next.get(entity);
if (!nextLink) {
nextLink = new Link<Path, K, V>();
// void members are represented as a symbol so we can lookup
curLink.next.set(entity ?? UNDEF, nextLink);
}
curLink.nextPath = path;
curLink = nextLink;
}
// in case there used to be more
curLink.nextPath = undefined;
curLink.value = value;
// we could recompute this on get, but it would have a cost and we optimize for `get`
curLink.journey = dependencies.map(dep => dep.path);
}
}
export type GetDependency<Path, K = object | symbol> = (lookup: Path) => K;
export interface Dep<Path, K = object> {
path: Path;
entity: K;
}
const EMPTY = [undefined, undefined] as const;
/** Link in a chain */
class Link<Path, K extends object, V> {
next = new WeakMap<K, Link<Path, K, V>>();
value: V | undefined = undefined;
journey: Path[] = [];
nextPath: Path | undefined = undefined;
}
class KeySize extends Error {
message = 'Keys must include at least one member';
}