@data-client/normalizr
Version:
Normalizes and denormalizes JSON according to schema for Redux and Flux applications
85 lines (79 loc) • 14 kB
JavaScript
import GlobalCache from './globalCache.js';
import WeakDependencyMap from './WeakDependencyMap.js';
import buildQueryKey from '../buildQueryKey.js';
import { getEntityCaches } from './entitiesCache.js';
import { MemoPolicy } from './Policy.js';
import { getDependency } from '../delegate/BaseDelegate.js';
import getUnvisit from '../denormalize/unvisit.js';
// TODO: make MemoCache generic on the arguments sent to Delegate constructor
/** Singleton to store the memoization cache for denormalization methods */
export default class MemoCache {
constructor(policy = MemoPolicy) {
/** Cache for every entity based on its dependencies and its own input */
/** Caches the final denormalized form based on input, entities */
this.endpoints = new WeakDependencyMap();
/** Caches the queryKey based on schema, args, and any used entities or indexes */
this.queryKeys = new Map();
this.policy = policy;
this._getCache = getEntityCaches(new Map());
}
/** Compute denormalized form maintaining referential equality for same inputs */
denormalize(schema, input, entities, args = []) {
// we already vary based on input, so we don't need endpointKey? TODO: verify
// if (!this.endpoints[endpointKey])
// this.endpoints[endpointKey] = new WeakDependencyMap<EntityPath>();
// undefined means don't do anything
if (schema === undefined) {
return {
data: input,
paths: []
};
}
if (input === undefined) {
return {
data: undefined,
paths: []
};
}
const getEntity = this.policy.getEntities(entities);
return getUnvisit(getEntity, new GlobalCache(getEntity, this._getCache, this.endpoints), args)(schema, input);
}
/** Compute denormalized form maintaining referential equality for same inputs */
query(schema, args, state,
// NOTE: different orders can result in cache busting here; but since it's just a perf penalty we will allow for now
argsKey = JSON.stringify(args)) {
const input = this.buildQueryKey(schema, args, state, argsKey);
if (!input) {
return {
data: undefined,
paths: []
};
}
return this.denormalize(schema, input, state.entities, args);
}
buildQueryKey(schema, args, state,
// NOTE: different orders can result in cache busting here; but since it's just a perf penalty we will allow for now
argsKey = JSON.stringify(args)) {
// This is redundant for buildQueryKey checks, but that was is used for recursion so we still need the checks there
// TODO: If we make each recursive call include cache lookups, we combine these checks together
// null is object so we need double check
if (typeof schema !== 'object' && typeof schema.queryKey !== 'function' || !schema) return schema;
// cache lookup: argsKey -> schema -> ...touched indexes or entities
let queryCache = this.queryKeys.get(argsKey);
if (!queryCache) {
queryCache = new WeakDependencyMap();
this.queryKeys.set(argsKey, queryCache);
}
const baseDelegate = new this.policy.QueryDelegate(state);
// eslint-disable-next-line prefer-const
let [value, paths] = queryCache.get(schema, getDependency(baseDelegate));
// paths undefined is the only way to truly tell nothing was found (the value could have actually been undefined)
if (!paths) {
const [delegate, dependencies] = baseDelegate.tracked(schema);
value = buildQueryKey(delegate)(schema, args);
queryCache.set(dependencies, value);
}
return value;
}
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJHbG9iYWxDYWNoZSIsIldlYWtEZXBlbmRlbmN5TWFwIiwiYnVpbGRRdWVyeUtleSIsImdldEVudGl0eUNhY2hlcyIsIk1lbW9Qb2xpY3kiLCJnZXREZXBlbmRlbmN5IiwiZ2V0VW52aXNpdCIsIk1lbW9DYWNoZSIsImNvbnN0cnVjdG9yIiwicG9saWN5IiwiZW5kcG9pbnRzIiwicXVlcnlLZXlzIiwiTWFwIiwiX2dldENhY2hlIiwiZGVub3JtYWxpemUiLCJzY2hlbWEiLCJpbnB1dCIsImVudGl0aWVzIiwiYXJncyIsInVuZGVmaW5lZCIsImRhdGEiLCJwYXRocyIsImdldEVudGl0eSIsImdldEVudGl0aWVzIiwicXVlcnkiLCJzdGF0ZSIsImFyZ3NLZXkiLCJKU09OIiwic3RyaW5naWZ5IiwicXVlcnlLZXkiLCJxdWVyeUNhY2hlIiwiZ2V0Iiwic2V0IiwiYmFzZURlbGVnYXRlIiwiUXVlcnlEZWxlZ2F0ZSIsInZhbHVlIiwiZGVsZWdhdGUiLCJkZXBlbmRlbmNpZXMiLCJ0cmFja2VkIl0sInNvdXJjZXMiOlsiLi4vLi4vc3JjL21lbW8vTWVtb0NhY2hlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBHbG9iYWxDYWNoZSBmcm9tICcuL2dsb2JhbENhY2hlLmpzJztcbmltcG9ydCBXZWFrRGVwZW5kZW5jeU1hcCBmcm9tICcuL1dlYWtEZXBlbmRlbmN5TWFwLmpzJztcbmltcG9ydCBidWlsZFF1ZXJ5S2V5IGZyb20gJy4uL2J1aWxkUXVlcnlLZXkuanMnO1xuaW1wb3J0IHsgR2V0RW50aXR5Q2FjaGUsIGdldEVudGl0eUNhY2hlcyB9IGZyb20gJy4vZW50aXRpZXNDYWNoZS5qcyc7XG5pbXBvcnQgeyBNZW1vUG9saWN5IH0gZnJvbSAnLi9Qb2xpY3kuanMnO1xuaW1wb3J0IHsgZ2V0RGVwZW5kZW5jeSB9IGZyb20gJy4uL2RlbGVnYXRlL0Jhc2VEZWxlZ2F0ZS5qcyc7XG5pbXBvcnQgdHlwZSB7IElOVkFMSUQgfSBmcm9tICcuLi9kZW5vcm1hbGl6ZS9zeW1ib2wuanMnO1xuaW1wb3J0IGdldFVudmlzaXQgZnJvbSAnLi4vZGVub3JtYWxpemUvdW52aXNpdC5qcyc7XG5pbXBvcnQgdHlwZSB7XG4gIEVudGl0eVBhdGgsXG4gIE5vcm1hbGl6ZWRJbmRleCxcbiAgUXVlcnlQYXRoLFxuICBTY2hlbWEsXG59IGZyb20gJy4uL2ludGVyZmFjZS5qcyc7XG5pbXBvcnQgdHlwZSB7IERlbm9ybWFsaXplTnVsbGFibGUsIE5vcm1hbGl6ZU51bGxhYmxlIH0gZnJvbSAnLi4vdHlwZXMuanMnO1xuaW1wb3J0IHR5cGUgeyBJTWVtb1BvbGljeSwgRW5kcG9pbnRzQ2FjaGUgfSBmcm9tICcuL3R5cGVzLmpzJztcblxuLy8gVE9ETzogbWFrZSBNZW1vQ2FjaGUgZ2VuZXJpYyBvbiB0aGUgYXJndW1lbnRzIHNlbnQgdG8gRGVsZWdhdGUgY29uc3RydWN0b3JcblxuLyoqIFNpbmdsZXRvbiB0byBzdG9yZSB0aGUgbWVtb2l6YXRpb24gY2FjaGUgZm9yIGRlbm9ybWFsaXphdGlvbiBtZXRob2RzICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBNZW1vQ2FjaGUge1xuICAvKiogQ2FjaGUgZm9yIGV2ZXJ5IGVudGl0eSBiYXNlZCBvbiBpdHMgZGVwZW5kZW5jaWVzIGFuZCBpdHMgb3duIGlucHV0ICovXG4gIGRlY2xhcmUgcHJvdGVjdGVkIF9nZXRDYWNoZTogR2V0RW50aXR5Q2FjaGU7XG4gIC8qKiBDYWNoZXMgdGhlIGZpbmFsIGRlbm9ybWFsaXplZCBmb3JtIGJhc2VkIG9uIGlucHV0LCBlbnRpdGllcyAqL1xuICBwcm90ZWN0ZWQgZW5kcG9pbnRzOiBFbmRwb2ludHNDYWNoZSA9IG5ldyBXZWFrRGVwZW5kZW5jeU1hcDxFbnRpdHlQYXRoPigpO1xuICAvKiogQ2FjaGVzIHRoZSBxdWVyeUtleSBiYXNlZCBvbiBzY2hlbWEsIGFyZ3MsIGFuZCBhbnkgdXNlZCBlbnRpdGllcyBvciBpbmRleGVzICovXG4gIHByb3RlY3RlZCBxdWVyeUtleXM6IE1hcDxzdHJpbmcsIFdlYWtEZXBlbmRlbmN5TWFwPFF1ZXJ5UGF0aD4+ID0gbmV3IE1hcCgpO1xuXG4gIGRlY2xhcmUgcHJvdGVjdGVkIHBvbGljeTogSU1lbW9Qb2xpY3k7XG5cbiAgY29uc3RydWN0b3IocG9saWN5OiBJTWVtb1BvbGljeSA9IE1lbW9Qb2xpY3kpIHtcbiAgICB0aGlzLnBvbGljeSA9IHBvbGljeTtcbiAgICB0aGlzLl9nZXRDYWNoZSA9IGdldEVudGl0eUNhY2hlcyhuZXcgTWFwKCkpO1xuICB9XG5cbiAgLyoqIENvbXB1dGUgZGVub3JtYWxpemVkIGZvcm0gbWFpbnRhaW5pbmcgcmVmZXJlbnRpYWwgZXF1YWxpdHkgZm9yIHNhbWUgaW5wdXRzICovXG4gIGRlbm9ybWFsaXplPFMgZXh0ZW5kcyBTY2hlbWE+KFxuICAgIHNjaGVtYTogUyB8IHVuZGVmaW5lZCxcbiAgICBpbnB1dDogdW5rbm93bixcbiAgICBlbnRpdGllczogYW55LFxuICAgIGFyZ3M6IHJlYWRvbmx5IGFueVtdID0gW10sXG4gICk6IHtcbiAgICBkYXRhOiBEZW5vcm1hbGl6ZU51bGxhYmxlPFM+IHwgdHlwZW9mIElOVkFMSUQ7XG4gICAgcGF0aHM6IEVudGl0eVBhdGhbXTtcbiAgfSB7XG4gICAgLy8gd2UgYWxyZWFkeSB2YXJ5IGJhc2VkIG9uIGlucHV0LCBzbyB3ZSBkb24ndCBuZWVkIGVuZHBvaW50S2V5PyBUT0RPOiB2ZXJpZnlcbiAgICAvLyBpZiAoIXRoaXMuZW5kcG9pbnRzW2VuZHBvaW50S2V5XSlcbiAgICAvLyAgIHRoaXMuZW5kcG9pbnRzW2VuZHBvaW50S2V5XSA9IG5ldyBXZWFrRGVwZW5kZW5jeU1hcDxFbnRpdHlQYXRoPigpO1xuXG4gICAgLy8gdW5kZWZpbmVkIG1lYW5zIGRvbid0IGRvIGFueXRoaW5nXG4gICAgaWYgKHNjaGVtYSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICByZXR1cm4geyBkYXRhOiBpbnB1dCBhcyBhbnksIHBhdGhzOiBbXSB9O1xuICAgIH1cbiAgICBpZiAoaW5wdXQgPT09IHVuZGVmaW5lZCkge1xuICAgICAgcmV0dXJuIHsgZGF0YTogdW5kZWZpbmVkIGFzIGFueSwgcGF0aHM6IFtdIH07XG4gICAgfVxuICAgIGNvbnN0IGdldEVudGl0eSA9IHRoaXMucG9saWN5LmdldEVudGl0aWVzKGVudGl0aWVzKTtcblxuICAgIHJldHVybiBnZXRVbnZpc2l0KFxuICAgICAgZ2V0RW50aXR5LFxuICAgICAgbmV3IEdsb2JhbENhY2hlKGdldEVudGl0eSwgdGhpcy5fZ2V0Q2FjaGUsIHRoaXMuZW5kcG9pbnRzKSxcbiAgICAgIGFyZ3MsXG4gICAgKShzY2hlbWEsIGlucHV0KTtcbiAgfVxuXG4gIC8qKiBDb21wdXRlIGRlbm9ybWFsaXplZCBmb3JtIG1haW50YWluaW5nIHJlZmVyZW50aWFsIGVxdWFsaXR5IGZvciBzYW1lIGlucHV0cyAqL1xuICBxdWVyeTxTIGV4dGVuZHMgU2NoZW1hPihcbiAgICBzY2hlbWE6IFMsXG4gICAgYXJnczogcmVhZG9ubHkgYW55W10sXG4gICAgc3RhdGU6IFN0YXRlSW50ZXJmYWNlLFxuICAgIC8vIE5PVEU6IGRpZmZlcmVudCBvcmRlcnMgY2FuIHJlc3VsdCBpbiBjYWNoZSBidXN0aW5nIGhlcmU7IGJ1dCBzaW5jZSBpdCdzIGp1c3QgYSBwZXJmIHBlbmFsdHkgd2Ugd2lsbCBhbGxvdyBmb3Igbm93XG4gICAgYXJnc0tleTogc3RyaW5nID0gSlNPTi5zdHJpbmdpZnkoYXJncyksXG4gICk6IHtcbiAgICBkYXRhOiBEZW5vcm1hbGl6ZU51bGxhYmxlPFM+IHwgdHlwZW9mIElOVkFMSUQ7XG4gICAgcGF0aHM6IEVudGl0eVBhdGhbXTtcbiAgfSB7XG4gICAgY29uc3QgaW5wdXQgPSB0aGlzLmJ1aWxkUXVlcnlLZXkoc2NoZW1hLCBhcmdzLCBzdGF0ZSwgYXJnc0tleSk7XG5cbiAgICBpZiAoIWlucHV0KSB7XG4gICAgICByZXR1cm4geyBkYXRhOiB1bmRlZmluZWQgYXMgYW55LCBwYXRoczogW10gfTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5kZW5vcm1hbGl6ZShzY2hlbWEsIGlucHV0LCBzdGF0ZS5lbnRpdGllcywgYXJncyk7XG4gIH1cblxuICBidWlsZFF1ZXJ5S2V5PFMgZXh0ZW5kcyBTY2hlbWE+KFxuICAgIHNjaGVtYTogUyxcbiAgICBhcmdzOiByZWFkb25seSBhbnlbXSxcbiAgICBzdGF0ZTogU3RhdGVJbnRlcmZhY2UsXG4gICAgLy8gTk9URTogZGlmZmVyZW50IG9yZGVycyBjYW4gcmVzdWx0IGluIGNhY2hlIGJ1c3RpbmcgaGVyZTsgYnV0IHNpbmNlIGl0J3MganVzdCBhIHBlcmYgcGVuYWx0eSB3ZSB3aWxsIGFsbG93IGZvciBub3dcbiAgICBhcmdzS2V5OiBzdHJpbmcgPSBKU09OLnN0cmluZ2lmeShhcmdzKSxcbiAgKTogTm9ybWFsaXplTnVsbGFibGU8Uz4ge1xuICAgIC8vIFRoaXMgaXMgcmVkdW5kYW50IGZvciBidWlsZFF1ZXJ5S2V5IGNoZWNrcywgYnV0IHRoYXQgd2FzIGlzIHVzZWQgZm9yIHJlY3Vyc2lvbiBzbyB3ZSBzdGlsbCBuZWVkIHRoZSBjaGVja3MgdGhlcmVcbiAgICAvLyBUT0RPOiBJZiB3ZSBtYWtlIGVhY2ggcmVjdXJzaXZlIGNhbGwgaW5jbHVkZSBjYWNoZSBsb29rdXBzLCB3ZSBjb21iaW5lIHRoZXNlIGNoZWNrcyB0b2dldGhlclxuICAgIC8vIG51bGwgaXMgb2JqZWN0IHNvIHdlIG5lZWQgZG91YmxlIGNoZWNrXG4gICAgaWYgKFxuICAgICAgKHR5cGVvZiBzY2hlbWEgIT09ICdvYmplY3QnICYmXG4gICAgICAgIHR5cGVvZiAoc2NoZW1hIGFzIGFueSkucXVlcnlLZXkgIT09ICdmdW5jdGlvbicpIHx8XG4gICAgICAhc2NoZW1hXG4gICAgKVxuICAgICAgcmV0dXJuIHNjaGVtYSBhcyBhbnk7XG5cbiAgICAvLyBjYWNoZSBsb29rdXA6IGFyZ3NLZXkgLT4gc2NoZW1hIC0+IC4uLnRvdWNoZWQgaW5kZXhlcyBvciBlbnRpdGllc1xuICAgIGxldCBxdWVyeUNhY2hlID0gdGhpcy5xdWVyeUtleXMuZ2V0KGFyZ3NLZXkpIGFzXG4gICAgICB8IFdlYWtEZXBlbmRlbmN5TWFwPFF1ZXJ5UGF0aCwgb2JqZWN0LCBhbnk+XG4gICAgICB8IHVuZGVmaW5lZDtcbiAgICBpZiAoIXF1ZXJ5Q2FjaGUpIHtcbiAgICAgIHF1ZXJ5Q2FjaGUgPSBuZXcgV2Vha0RlcGVuZGVuY3lNYXA8UXVlcnlQYXRoPigpO1xuICAgICAgdGhpcy5xdWVyeUtleXMuc2V0KGFyZ3NLZXksIHF1ZXJ5Q2FjaGUpO1xuICAgIH1cblxuICAgIGNvbnN0IGJhc2VEZWxlZ2F0ZSA9IG5ldyB0aGlzLnBvbGljeS5RdWVyeURlbGVnYXRlKHN0YXRlKTtcbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgcHJlZmVyLWNvbnN0XG4gICAgbGV0IFt2YWx1ZSwgcGF0aHNdID0gcXVlcnlDYWNoZS5nZXQoXG4gICAgICBzY2hlbWEgYXMgYW55LFxuICAgICAgZ2V0RGVwZW5kZW5jeShiYXNlRGVsZWdhdGUpLFxuICAgICk7XG5cbiAgICAvLyBwYXRocyB1bmRlZmluZWQgaXMgdGhlIG9ubHkgd2F5IHRvIHRydWx5IHRlbGwgbm90aGluZyB3YXMgZm91bmQgKHRoZSB2YWx1ZSBjb3VsZCBoYXZlIGFjdHVhbGx5IGJlZW4gdW5kZWZpbmVkKVxuICAgIGlmICghcGF0aHMpIHtcbiAgICAgIGNvbnN0IFtkZWxlZ2F0ZSwgZGVwZW5kZW5jaWVzXSA9IGJhc2VEZWxlZ2F0ZS50cmFja2VkKHNjaGVtYSk7XG5cbiAgICAgIHZhbHVlID0gYnVpbGRRdWVyeUtleShkZWxlZ2F0ZSkoc2NoZW1hLCBhcmdzKTtcbiAgICAgIHF1ZXJ5Q2FjaGUuc2V0KGRlcGVuZGVuY2llcywgdmFsdWUpO1xuICAgIH1cbiAgICByZXR1cm4gdmFsdWU7XG4gIH1cbn1cblxudHlwZSBTdGF0ZUludGVyZmFjZSA9IHtcbiAgZW50aXRpZXM6XG4gICAgfCBSZWNvcmQ8c3RyaW5nLCBSZWNvcmQ8c3RyaW5nLCBhbnk+IHwgdW5kZWZpbmVkPlxuICAgIHwge1xuICAgICAgICBnZXRJbihrOiBzdHJpbmdbXSk6IGFueTtcbiAgICAgIH07XG4gIGluZGV4ZXM6XG4gICAgfCBOb3JtYWxpemVkSW5kZXhcbiAgICB8IHtcbiAgICAgICAgZ2V0SW4oazogc3RyaW5nW10pOiBhbnk7XG4gICAgICB9O1xufTtcbiJdLCJtYXBwaW5ncyI6IkFBQUEsT0FBT0EsV0FBVyxNQUFNLGtCQUFrQjtBQUMxQyxPQUFPQyxpQkFBaUIsTUFBTSx3QkFBd0I7QUFDdEQsT0FBT0MsYUFBYSxNQUFNLHFCQUFxQjtBQUMvQyxTQUF5QkMsZUFBZSxRQUFRLG9CQUFvQjtBQUNwRSxTQUFTQyxVQUFVLFFBQVEsYUFBYTtBQUN4QyxTQUFTQyxhQUFhLFFBQVEsNkJBQTZCO0FBRTNELE9BQU9DLFVBQVUsTUFBTSwyQkFBMkI7QUFVbEQ7O0FBRUE7QUFDQSxlQUFlLE1BQU1DLFNBQVMsQ0FBQztFQVU3QkMsV0FBV0EsQ0FBQ0MsTUFBbUIsR0FBR0wsVUFBVSxFQUFFO0lBVDlDO0lBRUE7SUFBQSxLQUNVTSxTQUFTLEdBQW1CLElBQUlULGlCQUFpQixDQUFhLENBQUM7SUFDekU7SUFBQSxLQUNVVSxTQUFTLEdBQThDLElBQUlDLEdBQUcsQ0FBQyxDQUFDO0lBS3hFLElBQUksQ0FBQ0gsTUFBTSxHQUFHQSxNQUFNO0lBQ3BCLElBQUksQ0FBQ0ksU0FBUyxHQUFHVixlQUFlLENBQUMsSUFBSVMsR0FBRyxDQUFDLENBQUMsQ0FBQztFQUM3Qzs7RUFFQTtFQUNBRSxXQUFXQSxDQUNUQyxNQUFxQixFQUNyQkMsS0FBYyxFQUNkQyxRQUFhLEVBQ2JDLElBQW9CLEdBQUcsRUFBRSxFQUl6QjtJQUNBO0lBQ0E7SUFDQTs7SUFFQTtJQUNBLElBQUlILE1BQU0sS0FBS0ksU0FBUyxFQUFFO01BQ3hCLE9BQU87UUFBRUMsSUFBSSxFQUFFSixLQUFZO1FBQUVLLEtBQUssRUFBRTtNQUFHLENBQUM7SUFDMUM7SUFDQSxJQUFJTCxLQUFLLEtBQUtHLFNBQVMsRUFBRTtNQUN2QixPQUFPO1FBQUVDLElBQUksRUFBRUQsU0FBZ0I7UUFBRUUsS0FBSyxFQUFF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