@apollo/client
Version:
A fully-featured caching GraphQL client.
126 lines • 4.84 kB
JavaScript
import { WeakCache } from "@wry/caches";
import { visit } from "graphql";
import { wrap } from "optimism";
import { cacheSizes } from "@apollo/client/utilities";
import { getFragmentDefinitions } from "@apollo/client/utilities/internal";
// As long as createFragmentRegistry is not imported or used, the
// FragmentRegistry example implementation provided below should not be bundled
// (by tree-shaking bundlers like Rollup), because the implementation of
// InMemoryCache refers only to the TypeScript interface FragmentRegistryAPI,
// never the concrete implementation FragmentRegistry (which is deliberately not
// exported from this module).
export function createFragmentRegistry(...fragments) {
return new FragmentRegistry(...fragments);
}
class FragmentRegistry {
registry = {};
// Call `createFragmentRegistry` instead of invoking the
// FragmentRegistry constructor directly. This reserves the constructor for
// future configuration of the FragmentRegistry.
constructor(...fragments) {
this.resetCaches();
if (fragments.length) {
this.register(...fragments);
}
}
register(...fragments) {
const definitions = new Map();
fragments.forEach((doc) => {
getFragmentDefinitions(doc).forEach((node) => {
definitions.set(node.name.value, node);
});
});
definitions.forEach((node, name) => {
if (node !== this.registry[name]) {
this.registry[name] = node;
this.invalidate(name);
}
});
return this;
}
// Overridden in the resetCaches method below.
invalidate(name) { }
resetCaches() {
const proto = FragmentRegistry.prototype;
this.invalidate = (this.lookup = wrap(proto.lookup.bind(this), {
makeCacheKey: (arg) => arg,
max: cacheSizes["fragmentRegistry.lookup"] ||
1000 /* defaultCacheSizes["fragmentRegistry.lookup"] */,
})).dirty; // This dirty function is bound to the wrapped lookup method.
this.transform = wrap(proto.transform.bind(this), {
cache: WeakCache,
max: cacheSizes["fragmentRegistry.transform"] ||
2000 /* defaultCacheSizes["fragmentRegistry.transform"] */,
});
this.findFragmentSpreads = wrap(proto.findFragmentSpreads.bind(this), {
cache: WeakCache,
max: cacheSizes["fragmentRegistry.findFragmentSpreads"] ||
4000 /* defaultCacheSizes["fragmentRegistry.findFragmentSpreads"] */,
});
}
/*
* Note:
* This method is only memoized so it can serve as a dependency to `transform`,
* so calling `invalidate` will invalidate cache entries for `transform`.
*/
lookup(fragmentName) {
return this.registry[fragmentName] || null;
}
transform(document) {
const defined = new Map();
getFragmentDefinitions(document).forEach((def) => {
defined.set(def.name.value, def);
});
const unbound = new Set();
const enqueue = (spreadName) => {
if (!defined.has(spreadName)) {
unbound.add(spreadName);
}
};
const enqueueChildSpreads = (node) => Object.keys(this.findFragmentSpreads(node)).forEach(enqueue);
enqueueChildSpreads(document);
const missing = [];
const map = {};
// This Set forEach loop can be extended during iteration by adding
// additional strings to the unbound set.
unbound.forEach((fragmentName) => {
const knownFragmentDef = defined.get(fragmentName);
if (knownFragmentDef) {
enqueueChildSpreads((map[fragmentName] = knownFragmentDef));
}
else {
missing.push(fragmentName);
const def = this.lookup(fragmentName);
if (def) {
enqueueChildSpreads((map[fragmentName] = def));
}
}
});
if (missing.length) {
const defsToAppend = [];
missing.forEach((name) => {
const def = map[name];
if (def) {
defsToAppend.push(def);
}
});
if (defsToAppend.length) {
document = {
...document,
definitions: document.definitions.concat(defsToAppend),
};
}
}
return document;
}
findFragmentSpreads(root) {
const spreads = {};
visit(root, {
FragmentSpread(node) {
spreads[node.name.value] = node;
},
});
return spreads;
}
}
//# sourceMappingURL=fragmentRegistry.js.map