UNPKG

@apollo/client

Version:

A fully-featured caching GraphQL client.

245 lines (244 loc) 11.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PersistedQueryLink = exports.createPersistedQueryLink = exports.VERSION = void 0; const rxjs_1 = require("rxjs"); const errors_1 = require("@apollo/client/errors"); const link_1 = require("@apollo/client/link"); const utilities_1 = require("@apollo/client/utilities"); const utilities_2 = require("@apollo/client/utilities"); const environment_1 = require("@apollo/client/utilities/environment"); const internal_1 = require("@apollo/client/utilities/internal"); const invariant_1 = require("@apollo/client/utilities/invariant"); exports.VERSION = 1; function processErrors(graphQLErrors) { const byMessage = {}, byCode = {}; if ((0, internal_1.isNonEmptyArray)(graphQLErrors)) { graphQLErrors.forEach((error) => { byMessage[error.message] = error; if (typeof error.extensions?.code == "string") byCode[error.extensions.code] = error; }); } return { persistedQueryNotSupported: !!(byMessage.PersistedQueryNotSupported || byCode.PERSISTED_QUERY_NOT_SUPPORTED), persistedQueryNotFound: !!(byMessage.PersistedQueryNotFound || byCode.PERSISTED_QUERY_NOT_FOUND), }; } const defaultOptions = { disable: ({ meta }) => meta.persistedQueryNotSupported, retry: ({ meta }) => meta.persistedQueryNotSupported || meta.persistedQueryNotFound, useGETForHashedQueries: false, }; function operationDefinesMutation(operation) { return operation.query.definitions.some((d) => d.kind === "OperationDefinition" && d.operation === "mutation"); } /** * @deprecated * Use `PersistedQueryLink` from `@apollo/client/link/persisted-queries` instead. */ const createPersistedQueryLink = (options) => new PersistedQueryLink(options); exports.createPersistedQueryLink = createPersistedQueryLink; /** * `PersistedQueryLink` is a non-terminating link that enables the use of * persisted queries, a technique that reduces bandwidth by sending query hashes * instead of full query strings. * * @example * * ```ts * import { PersistedQueryLink } from "@apollo/client/link/persisted-queries"; * import { sha256 } from "crypto-hash"; * * const link = new PersistedQueryLink({ * sha256: (queryString) => sha256(queryString), * }); * ``` */ class PersistedQueryLink extends link_1.ApolloLink { constructor(options) { let hashesByQuery; function resetHashCache() { hashesByQuery = undefined; } // Ensure a SHA-256 hash function is provided, if a custom hash // generation function is not provided. We don't supply a SHA-256 hash // function by default, to avoid forcing one as a dependency. Developers // should pick the most appropriate SHA-256 function (sync or async) for // their needs/environment, or provide a fully custom hash generation // function (via the `generateHash` option) if they want to handle // hashing with something other than SHA-256. (0, invariant_1.invariant)(options && (typeof options.sha256 === "function" || typeof options.generateHash === "function"), 57); const { sha256, // If both a `sha256` and `generateHash` option are provided, the // `sha256` option will be ignored. Developers can configure and // use any hashing approach they want in a custom `generateHash` // function; they aren't limited to SHA-256. generateHash = (query) => Promise.resolve(sha256((0, utilities_1.print)(query))), disable, retry, useGETForHashedQueries, } = (0, internal_1.compact)(defaultOptions, options); let enabled = true; const getHashPromise = (query) => new Promise((resolve) => resolve(generateHash(query))); function getQueryHash(query) { if (!query || typeof query !== "object") { // If the query is not an object, we won't be able to store its hash as // a property of query[hashesKey], so we let generateHash(query) decide // what to do with the bogus query. return getHashPromise(query); } if (!hashesByQuery) { hashesByQuery = new internal_1.AutoCleanedWeakCache(utilities_2.cacheSizes["PersistedQueryLink.persistedQueryHashes"] || 2000 /* defaultCacheSizes["PersistedQueryLink.persistedQueryHashes"] */); } let hash = hashesByQuery.get(query); if (!hash) hashesByQuery.set(query, (hash = getHashPromise(query))); return hash; } super((operation, forward) => { (0, invariant_1.invariant)(forward, 58); const { query } = operation; return new rxjs_1.Observable((observer) => { let subscription; let retried = false; let originalFetchOptions; let setFetchOptions = false; function handleRetry(options, cb) { if (retried) { return cb(); } retried = true; // if the server doesn't support persisted queries, don't try anymore enabled = !disable(options); if (!enabled) { delete operation.extensions.persistedQuery; // clear hashes from cache, we don't need them anymore resetHashCache(); } // if its not found, we can try it again, otherwise just report the error if (retry(options)) { // need to recall the link chain if (subscription) subscription.unsubscribe(); // actually send the query this time operation.setContext({ http: { includeQuery: true, ...(enabled ? { includeExtensions: true } : {}), }, fetchOptions: { // Since we're including the full query, which may be // large, we should send it in the body of a POST request. // See issue #7456. method: "POST", }, }); if (setFetchOptions) { operation.setContext({ fetchOptions: originalFetchOptions }); } subscription = forward(operation).subscribe(handler); return; } cb(); } const handler = { next: (result) => { if (!(0, utilities_2.isFormattedExecutionResult)(result) || !result.errors) { return observer.next(result); } handleRetry({ operation, error: new errors_1.CombinedGraphQLErrors(result), meta: processErrors(result.errors), result, }, () => observer.next(result)); }, error: (incomingError) => { const error = (0, errors_1.toErrorLike)(incomingError); const callback = () => observer.error(incomingError); // This is persisted-query specific (see #9410) and deviates from the // GraphQL-over-HTTP spec for application/json responses. // This is intentional. if (errors_1.ServerError.is(error) && error.bodyText) { try { const result = JSON.parse(error.bodyText); if ((0, utilities_2.isFormattedExecutionResult)(result)) { return handleRetry({ error: new errors_1.CombinedGraphQLErrors(result), result, operation, meta: processErrors(result.errors), }, callback); } } catch { } } handleRetry({ error, operation, meta: { persistedQueryNotSupported: false, persistedQueryNotFound: false, }, }, callback); }, complete: observer.complete.bind(observer), }; // don't send the query the first time operation.setContext({ http: enabled ? { includeQuery: false, includeExtensions: true } : {}, }); // If requested, set method to GET if there are no mutations. Remember the // original fetchOptions so we can restore them if we fall back to a // non-hashed request. if (useGETForHashedQueries && enabled && !operationDefinesMutation(operation)) { operation.setContext(({ fetchOptions = {} }) => { originalFetchOptions = fetchOptions; return { fetchOptions: { ...fetchOptions, method: "GET", }, }; }); setFetchOptions = true; } if (enabled) { getQueryHash(query) .then((sha256Hash) => { operation.extensions.persistedQuery = { version: exports.VERSION, sha256Hash, }; subscription = forward(operation).subscribe(handler); }) .catch(observer.error.bind(observer)); } else { subscription = forward(operation).subscribe(handler); } return () => { if (subscription) subscription.unsubscribe(); }; }); }); if (environment_1.__DEV__) { Object.assign(this, { getMemoryInternals() { return { PersistedQueryLink: { persistedQueryHashes: hashesByQuery?.size ?? 0, }, }; }, }); } this.resetHashCache = resetHashCache; } resetHashCache; } exports.PersistedQueryLink = PersistedQueryLink; //# sourceMappingURL=index.cjs.map