UNPKG

gqty

Version:

The No-GraphQL Client for TypeScript

314 lines (307 loc) • 11.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); const frailMap = require('frail-map'); const set = require('just-safe-set'); const multidict = require('multidict'); const skeleton = require('../Accessor/skeleton.js'); const deepCopy = require('../Helpers/deepCopy.js'); require('graphql'); require('../Utils/hash.js'); const select = require('../Helpers/select.js'); require('just-safe-get'); const crawl = require('./crawl.js'); const normalization = require('./normalization.js'); const persistence = require('./persistence.js'); const utils = require('./utils.js'); function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; } const set__default = /*#__PURE__*/_interopDefault(set); var __typeError = (msg) => { throw TypeError(msg); }; var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); var _maxAge, _staleWhileRevalidate, _normalizationOptions, _data, _normalizedObjects, _dataRefs, _subscriptions, _normalizedSubscriptions, _Cache_instances, subscribeNormalized_fn, _notifySubscribers; const MINIMUM_CACHE_AGE = 100; class Cache { constructor(data, { maxAge = Infinity, staleWhileRevalidate = 5 * 30 * 1e3, normalization: normalization$1 } = {}) { __privateAdd(this, _Cache_instances); __privateAdd(this, _maxAge, Infinity); __privateAdd(this, _staleWhileRevalidate, 0); __privateAdd(this, _normalizationOptions); /** * The actual data cache. Cache keys are formatted as the first 2 layer of * the selection path, e.g. `['query', 'user']` for `query.user`. * * This enables a cache expiry/eviction strategy based on top-level queries. */ __privateAdd(this, _data, new frailMap.FrailMap()); /** Look up table for normalized objects. */ __privateAdd(this, _normalizedObjects, new frailMap.FrailMap()); /** Temporary strong references for the WeakRefs in FrailMap. */ __privateAdd(this, _dataRefs, /* @__PURE__ */ new Set()); /** Subscription paths and it's listener function. */ __privateAdd(this, _subscriptions, /* @__PURE__ */ new Map()); /** Subscription paths that reached a normalized object. */ __privateAdd(this, _normalizedSubscriptions, new multidict.MultiDict()); // [ ] Optimization // This is pretty inefficient, but maintaining an indexed tree is too much // effort right now. Accepting PRs. __privateAdd(this, _notifySubscribers, (value) => { var _a, _b; const listeners = /* @__PURE__ */ new Set(); const subs = __privateGet(this, _subscriptions); const nsubs = __privateGet(this, _normalizedSubscriptions); const getId = (_a = this.normalizationOptions) == null ? void 0 : _a.identity; for (const [paths, notify] of subs) { for (const path of paths) { const parts = path.split("."); const node = select.select(value, parts, (node2) => { var _a2; if ((getId == null ? void 0 : getId(node2)) && utils.isCacheObject(node2)) { (_a2 = nsubs.get(node2)) == null ? void 0 : _a2.forEach((notify2) => { listeners.add(notify2); }); nsubs.set(node2, notify); } return node2; }); if (Array.isArray(node) ? node.flat(Infinity).some((item) => item !== void 0) : node !== void 0) { listeners.add(notify); break; } } } if (getId) { const norbjs = /* @__PURE__ */ new Set(); crawl.crawl(value, (node) => { if (getId(node) && utils.isCacheObject(node)) { norbjs.add(node); } }); const resubscribingListeners = /* @__PURE__ */ new Set(); for (const norbj of norbjs) { for (const listener of (_b = nsubs.get(norbj)) != null ? _b : []) { listeners.add(listener); resubscribingListeners.add(listener); } } for (const listener of resubscribingListeners) { for (const [paths, _listener] of subs) { if (listener === _listener) { __privateMethod(this, _Cache_instances, subscribeNormalized_fn).call(this, paths, listener); } } } } if (listeners.size > 0) { const valueSnapshot = deepCopy.deepCopy(value); for (const notify of listeners) { notify(valueSnapshot); } } }); __privateSet(this, _maxAge, Math.max(maxAge, MINIMUM_CACHE_AGE)); __privateSet(this, _staleWhileRevalidate, Math.max(staleWhileRevalidate, 0)); if (normalization$1) { __privateSet(this, _normalizationOptions, normalization$1 === true ? normalization.defaultNormalizationHandler : Object.freeze({ ...normalization$1 })); } if (data) { this.restore(data); } } /** * Maximum age of cache data in milliseconds, expired data nodes are subjected * to garbage collection. */ get maxAge() { return __privateGet(this, _maxAge); } /** * Maximum time in milliseconds to keep stale data in cache, while allowing * stale-while-revalidate background fetches. */ get staleWhileRevalidate() { return __privateGet(this, _staleWhileRevalidate); } get normalizationOptions() { return __privateGet(this, _normalizationOptions); } restore(data) { var _a; const { query, mutation, subscription, normalizedObjects } = (_a = persistence.importCacheSnapshot(data, this.normalizationOptions)) != null ? _a : {}; __privateSet(this, _normalizedObjects, normalizedObjects != null ? normalizedObjects : new frailMap.FrailMap()); __privateSet(this, _data, new frailMap.FrailMap()); this.set({ query, mutation, subscription }, { skipNotify: true }); } /** Subscribe to cache changes. */ subscribe(paths, fn) { const pathsSnapshot = Object.freeze([...paths]); __privateGet(this, _subscriptions).set(pathsSnapshot, fn); __privateMethod(this, _Cache_instances, subscribeNormalized_fn).call(this, pathsSnapshot, fn); return () => { __privateGet(this, _subscriptions).delete(pathsSnapshot); __privateGet(this, _normalizedSubscriptions).delete(fn); }; } /** * Retrieve cache values by first 2 path segments, e.g. `query.todos` or * `mutation.createTodo`. */ get(path, options) { var _a; const [, type, key, subpath] = (_a = path.match(/^([a-z]+(?:\w*))\.(?:__)?([a-z]+(?:\w*))(.*[^.])?$/i)) != null ? _a : []; if (!type || !key) { throw new ReferenceError( "Cache path must starts with `${type}.`: " + path ); } const cacheKey = `${type}.${key}`; const dataContainer = __privateGet(this, _data).get(cacheKey); if (dataContainer === void 0) return; const { expiresAt, swrBefore } = dataContainer; let { data } = dataContainer; if (expiresAt < Date.now() && !(options == null ? void 0 : options.includeExpired)) { data = void 0; } else if (subpath) { data = select.select(data, subpath.slice(1).split(".")); } return { data, get expiresAt() { return expiresAt; }, get swrBefore() { return swrBefore; } }; } /** * Merge objects into the current cache, recursively normalize incoming values * if normalization is enabled. Notifies cache listeners afterwards. * * Example value: `{ query: { foo: "bar" } }` */ set(values, { skipNotify = false } = {}) { var _a; const age = this.maxAge; const swr = this.staleWhileRevalidate; const now = Date.now(); if (this.normalizationOptions) { values = normalization.deepNormalizeObject(values, { ...this.normalizationOptions, store: __privateGet(this, _normalizedObjects) }); } for (const [type, cacheObjects = {}] of Object.entries(values)) { for (const [field, data] of Object.entries(cacheObjects)) { const cacheKey = `${type}.${field}`; let unrefTimer; const unref = () => { clearTimeout(unrefTimer); __privateGet(this, _dataRefs).delete(dataContainer); }; const dataContainer = ( // Mutation and subscription results should be returned right away for // immediate use. Their responses are only meaningful to a cache with // normalization enabled, where it already updates listeners. // // We force a short expiration here to let it survive the next render. type === "mutation" || type === "subscription" ? { data, expiresAt: now + 100, swrBefore: now, unref } : { data, expiresAt: age + now, swrBefore: age + swr + now, unref } ); const existing = __privateGet(this, _data).get(cacheKey); if (existing) { (_a = existing.unref) == null ? void 0 : _a.call(existing); Object.assign(existing, dataContainer); } else { __privateGet(this, _data).set(cacheKey, dataContainer, { strong: !isFinite(age) }); } if (isFinite(age + swr)) { unrefTimer = setTimeout(unref, age + swr); if (typeof unrefTimer === "object") { unrefTimer.unref(); } __privateGet(this, _dataRefs).add(dataContainer); } } } if (!skipNotify) { __privateGet(this, _notifySubscribers).call(this, values); } } clear() { __privateGet(this, _data).clear(); __privateGet(this, _normalizedObjects).clear(); __privateGet(this, _dataRefs).clear(); } toJSON() { const snapshot = ( // Remove skeletons crawl.crawl( [...__privateGet(this, _data)].reduce((prev, [key, { data }]) => { set__default["default"](prev, key, data); return prev; }, {}), (it, key, obj) => { if (skeleton.isSkeleton(it)) { Reflect.deleteProperty(obj, key); } } ) ); if (this.normalizationOptions) { return persistence.exportCacheSnapshot(snapshot, this.normalizationOptions); } else { return snapshot; } } } _maxAge = new WeakMap(); _staleWhileRevalidate = new WeakMap(); _normalizationOptions = new WeakMap(); _data = new WeakMap(); _normalizedObjects = new WeakMap(); _dataRefs = new WeakMap(); _subscriptions = new WeakMap(); _normalizedSubscriptions = new WeakMap(); _Cache_instances = new WeakSet(); subscribeNormalized_fn = function(paths, fn) { var _a, _b; const getId = (_a = this.normalizationOptions) == null ? void 0 : _a.identity; if (!getId) return; const store = __privateGet(this, _normalizedObjects); const nsubs = __privateGet(this, _normalizedSubscriptions); nsubs.delete(fn); for (const path of paths) { const [type, field, ...parts] = path.split("."); select.select( (_b = this.get(`${type}.${field}`, { includeExpired: true })) == null ? void 0 : _b.data, parts, (node) => { const id = getId(node); if (id && store.has(id) && utils.isCacheObject(node)) { nsubs.set(node, fn); } return node; } ); } }; _notifySubscribers = new WeakMap(); exports.Cache = Cache;