@apollo/client
Version:
A fully-featured caching GraphQL client.
190 lines (189 loc) • 7.58 kB
JavaScript
import { WeakCache } from "@wry/caches";
import { wrap } from "optimism";
import { Observable } from "rxjs";
import { cacheSizes } from "@apollo/client/utilities";
import { __DEV__ } from "@apollo/client/utilities/environment";
import { equalByQuery, getApolloCacheMemoryInternals, getFragmentDefinition, getFragmentQueryDocument, } from "@apollo/client/utilities/internal";
import { invariant } from "@apollo/client/utilities/invariant";
export class ApolloCache {
assumeImmutableResults = false;
// Function used to lookup a fragment when a fragment definition is not part
// of the GraphQL document. This is useful for caches, such as InMemoryCache,
// that register fragments ahead of time so they can be referenced by name.
lookupFragment(fragmentName) {
return null;
}
// Transactional API
// The batch method is intended to replace/subsume both performTransaction
// and recordOptimisticTransaction, but performTransaction came first, so we
// provide a default batch implementation that's just another way of calling
// performTransaction. Subclasses of ApolloCache (such as InMemoryCache) can
// override the batch method to do more interesting things with its options.
batch(options) {
const optimisticId = typeof options.optimistic === "string" ? options.optimistic
: options.optimistic === false ? null
: void 0;
let updateResult;
this.performTransaction(() => (updateResult = options.update(this)), optimisticId);
return updateResult;
}
recordOptimisticTransaction(transaction, optimisticId) {
this.performTransaction(transaction, optimisticId);
}
// Optional API
// Called once per input document, allowing the cache to make static changes
// to the query, such as adding __typename fields.
transformDocument(document) {
return document;
}
// Called before each ApolloLink request, allowing the cache to make dynamic
// changes to the query, such as filling in missing fragment definitions.
transformForLink(document) {
return document;
}
identify(object) {
return;
}
gc() {
return [];
}
modify(options) {
return false;
}
readQuery(options, optimistic = !!options.optimistic) {
return this.read({
...options,
rootId: options.id || "ROOT_QUERY",
optimistic,
});
}
/**
* Watches the cache store of the fragment according to the options specified
* and returns an `Observable`. We can subscribe to this
* `Observable` and receive updated results through an
* observer when the cache store changes.
*
* You must pass in a GraphQL document with a single fragment or a document
* with multiple fragments that represent what you are reading. If you pass
* in a document with multiple fragments then you must also specify a
* `fragmentName`.
*
* @since 3.10.0
* @param options - An object of type `WatchFragmentOptions` that allows
* the cache to identify the fragment and optionally specify whether to react
* to optimistic updates.
*/
watchFragment(options) {
const { fragment, fragmentName, from, optimistic = true, ...otherOptions } = options;
const query = this.getFragmentDoc(fragment, fragmentName);
// While our TypeScript types do not allow for `undefined` as a valid
// `from`, its possible `useFragment` gives us an `undefined` since it
// calls` cache.identify` and provides that value to `from`. We are
// adding this fix here however to ensure those using plain JavaScript
// and using `cache.identify` themselves will avoid seeing the obscure
// warning.
const id = typeof from === "undefined" || typeof from === "string" ?
from
: this.identify(from);
if (__DEV__) {
const actualFragmentName = fragmentName || getFragmentDefinition(fragment).name.value;
if (!id) {
__DEV__ && invariant.warn(110, actualFragmentName);
}
}
const diffOptions = {
...otherOptions,
returnPartialData: true,
id,
query,
optimistic,
};
let latestDiff;
return new Observable((observer) => {
return this.watch({
...diffOptions,
immediate: true,
callback: (diff) => {
let data = diff.result;
// TODO: Remove this once `watchFragment` supports `null` as valid
// value emitted
if (data === null) {
data = {};
}
if (
// Always ensure we deliver the first result
latestDiff &&
equalByQuery(query, { data: latestDiff.result }, { data }, options.variables)) {
return;
}
const result = {
data,
dataState: diff.complete ? "complete" : "partial",
complete: !!diff.complete,
};
if (diff.missing) {
result.missing = diff.missing.missing;
}
latestDiff = { ...diff, result: data };
observer.next(result);
},
});
});
}
// Make sure we compute the same (===) fragment query document every
// time we receive the same fragment in readFragment.
getFragmentDoc = wrap(getFragmentQueryDocument, {
max: cacheSizes["cache.fragmentQueryDocuments"] ||
1000 /* defaultCacheSizes["cache.fragmentQueryDocuments"] */,
cache: WeakCache,
});
readFragment(options, optimistic = !!options.optimistic) {
return this.read({
...options,
query: this.getFragmentDoc(options.fragment, options.fragmentName),
rootId: options.id,
optimistic,
});
}
writeQuery({ id, data, ...options }) {
return this.write(Object.assign(options, {
dataId: id || "ROOT_QUERY",
result: data,
}));
}
writeFragment({ id, data, fragment, fragmentName, ...options }) {
return this.write(Object.assign(options, {
query: this.getFragmentDoc(fragment, fragmentName),
dataId: id,
result: data,
}));
}
updateQuery(options, update) {
return this.batch({
update(cache) {
const value = cache.readQuery(options);
const data = update(value);
if (data === void 0 || data === null)
return value;
cache.writeQuery({ ...options, data });
return data;
},
});
}
updateFragment(options, update) {
return this.batch({
update(cache) {
const value = cache.readFragment(options);
const data = update(value);
if (data === void 0 || data === null)
return value;
cache.writeFragment({ ...options, data });
return data;
},
});
}
}
if (__DEV__) {
ApolloCache.prototype.getMemoryInternals = getApolloCacheMemoryInternals;
}
//# sourceMappingURL=cache.js.map