UNPKG

@apollo/client-react-streaming

Version:

This package provides building blocks to create framework-level integration of Apollo Client with React's streaming SSR. See the [@apollo/client-integration-nextjs](https://github.com/apollographql/apollo-client-integrations/tree/main/packages/nextjs) pac

617 lines (605 loc) 20.1 kB
'use strict'; var React2 = require('react'); var index_cc_cjs = require('./index.cc.cjs'); var internal = require('@apollo/client/react/internal'); var client = require('@apollo/client'); var invariant = require('@apollo/client/utilities/invariant'); var utilities = require('@apollo/client/utilities'); var graphql = require('graphql'); var streamUtils = require('@apollo/client-react-streaming/stream-utils'); var environment = require('@apollo/client/utilities/environment'); var internal$1 = require('@apollo/client/utilities/internal'); var rxjs = require('rxjs'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var React2__default = /*#__PURE__*/_interopDefault(React2); // src/registerApolloClient.tsx var teeToReadableStreamKey = Symbol.for( "apollo.tee.readableStreamController" ); var readFromReadableStreamKey = Symbol.for("apollo.read.readableStream"); function teeToReadableStream(onLinkHit, context) { return Object.assign(context, { [teeToReadableStreamKey]: onLinkHit }); } function readFromReadableStream(readableStream, context) { return Object.assign(context, { [readFromReadableStreamKey]: readableStream }); } var TeeToReadableStreamLink = class extends client.ApolloLink { constructor() { super((operation, forward) => { const context = operation.getContext(); const onLinkHit = context[teeToReadableStreamKey]; if (onLinkHit) { const controller = onLinkHit(); const tryClose = () => { try { controller.close(); } catch { } }; return new client.Observable((observer) => { let subscribed = true; forward(operation).subscribe({ next(result) { controller.enqueue({ type: "next", value: result }); if (subscribed) { observer.next(result); } }, error(error) { controller.enqueue({ type: "error" }); tryClose(); if (subscribed) { observer.error(error); } }, complete() { controller.enqueue({ type: "completed" }); tryClose(); if (subscribed) { observer.complete(); } } }); return () => { subscribed = false; }; }); } return forward(operation); }); } }; var ReadFromReadableStreamLink = class extends client.ApolloLink { constructor() { super((operation, forward) => { const context = operation.getContext(); const eventSteam = context[readFromReadableStreamKey]; if (eventSteam) { return new client.Observable((observer) => { let aborted = false; const reader = (() => { try { return eventSteam.getReader(); } catch { } })(); if (!reader) { const subscription = forward(operation).subscribe(observer); return () => subscription.unsubscribe(); } let forwarded = false; consume(reader).finally(() => { if (!observer.closed && !forwarded) { observer.complete(); } }); let onAbort = () => { aborted = true; reader.cancel(); }; return () => onAbort(); async function consume(reader2) { let event = undefined; while (!aborted && !event?.done) { event = await reader2.read(); if (aborted) break; if (event.value) { switch (event.value.type) { case "next": observer.next(event.value.value); break; case "completed": observer.complete(); break; case "error": { onAbort(); forwarded = true; const subscription = forward(operation).subscribe(observer); onAbort = () => subscription.unsubscribe(); } break; } } } } }); } return forward(operation); }); } }; // src/bundleInfo.ts var bundle = { pkg: "@apollo/client-react-streaming" }; var sourceSymbol = Symbol.for("apollo.source_package"); // src/DataTransportAbstraction/WrappedInMemoryCache.tsx var InMemoryCache = class extends client.InMemoryCache { /** * Information about the current package and it's export names, for use in error messages. * * @internal */ static info = bundle; [sourceSymbol]; constructor(config) { super(config); const info = this.constructor.info; this[sourceSymbol] = `${info.pkg}:InMemoryCache`; } }; function serializeOptions(options) { invariant.invariant( typeof options.fetchPolicy !== "function", "`nextFetchPolicy` cannot be a function in server-client streaming" ); invariant.invariant( typeof options.skipPollAttempt !== "function", "`skipPollAttempt` cannot be used with server-client streaming" ); return { ...options, query: printMinified(options.query) }; } function deserializeOptions(options) { return { ...options, // `gql` memoizes results, but based on the input string. // We parse-stringify-parse here to ensure that our minified query // has the best chance of being the referential same query as the one used in // client-side code. query: client.gql(utilities.print(client.gql(options.query))) }; } function printMinified(query) { return graphql.stripIgnoredCharacters(utilities.print(query)); } // src/assertInstance.ts function assertInstance(value, info, name) { if (value[sourceSymbol] !== `${info.pkg}:${name}`) { throw new Error( `When using \`${name}\` in streaming SSR, you must use the \`${name}\` export provided by \`"${info.pkg}"\`.` ); } } var ApolloClientBase = class extends client.ApolloClient { /** * Information about the current package and it's export names, for use in error messages. * * @internal */ static info = bundle; [sourceSymbol]; constructor(options) { const warnings = []; if ("ssrMode" in options) { delete options.ssrMode; warnings.push( "The `ssrMode` option is not supported in %s. Please remove it from your %s constructor options." ); } if ("ssrForceFetchDelay" in options) { delete options.ssrForceFetchDelay; warnings.push( "The `ssrForceFetchDelay` option is not supported in %s. Please remove it from your %s constructor options." ); } super( { devtools: { enabled: false, ...options.devtools }, ...options } ); const info = this.constructor.info; this[sourceSymbol] = `${info.pkg}:ApolloClient`; for (const warning of warnings) { console.warn(warning, info.pkg, "ApolloClient"); } assertInstance( this.cache, info, "InMemoryCache" ); this.setLink(this.link); } setLink(newLink) { super.setLink.call( this, client.ApolloLink.from([ new ReadFromReadableStreamLink(), new TeeToReadableStreamLink(), newLink ]) ); } }; var skipDataTransportKey = Symbol.for("apollo.dataTransport.skip"); function skipDataTransport(context) { return Object.assign(context, { [skipDataTransportKey]: true }); } var ApolloClientRSCImpl = class extends ApolloClientBase { }; var ApolloClientImplementation = ApolloClientRSCImpl; var ApolloClient = class extends ApolloClientImplementation { }; function getInjectableEventStream() { let controller; const stream = new ReadableStream({ start(c) { controller = c; } }); return [controller, stream]; } function createTransportedQueryPreloader(client, { prepareForReuse = false, notTransportedOptionOverrides = {} } = {}) { return (...[query, options]) => { options = { ...options }; delete options.returnPartialData; delete options.nextFetchPolicy; delete options.pollInterval; const [controller, stream] = getInjectableEventStream(); const transportedQueryRef = createTransportedQueryRef( query, options, crypto.randomUUID(), stream ); const watchQueryOptions = { query, ...options, notifyOnNetworkStatusChange: false, context: skipDataTransport( teeToReadableStream(() => controller, { ...options?.context, // we want to do this even if the query is already running for another reason queryDeduplication: false }) ), ...notTransportedOptionOverrides }; if (watchQueryOptions.fetchPolicy !== "no-cache" && watchQueryOptions.fetchPolicy !== "network-only" && (!prepareForReuse || watchQueryOptions.fetchPolicy !== "cache-and-network")) { watchQueryOptions.fetchPolicy = "network-only"; } if (prepareForReuse) { const internalQueryRef = getInternalQueryRef( client, { query, ...options }, watchQueryOptions ); return Object.assign(transportedQueryRef, internal.wrapQueryRef(internalQueryRef)); } else { const subscription = client.watchQuery(watchQueryOptions).subscribe({ next() { subscription.unsubscribe(); } }); } return transportedQueryRef; }; } function createTransportedQueryRef(query, options, queryKey, stream) { return { $__apollo_queryRef: { options: sanitizeForTransport(serializeOptions({ query, ...options })), queryKey, stream: stream.pipeThrough(new streamUtils.JSONEncodeStream()) } }; } function reviveTransportedQueryRef(queryRef, client) { if (internal.unwrapQueryRef(queryRef)) return; const { $__apollo_queryRef: { options, stream } } = queryRef; const hydratedOptions = deserializeOptions(options); const internalQueryRef = getInternalQueryRef(client, hydratedOptions, { ...hydratedOptions, fetchPolicy: "network-only", context: skipDataTransport( readFromReadableStream(stream.pipeThrough(new streamUtils.JSONDecodeStream()), { ...hydratedOptions.context, queryDeduplication: true }) ) }); Object.assign(queryRef, internal.wrapQueryRef(internalQueryRef)); } function getInternalQueryRef(client, userOptions, initialFetchOptions) { if (environment.__DEV__) { invariant.invariant( userOptions.nextFetchPolicy === initialFetchOptions.nextFetchPolicy, "Encountered an unexpected bug in @apollo/client-react-streaming. Please file an issue." ); } const observable = client.watchQuery(userOptions); const optionsAfterCreation = { // context might still be `undefined`, so we need to make sure the property is at least present // `undefined` won't merge in as `applyOptions` uses `compact`, so we use an empty object instead context: {}, ...observable.options }; observable.applyOptions(initialFetchOptions); const internalQueryRef = new internal.InternalQueryReference(observable, { autoDisposeTimeoutMs: client.defaultOptions.react?.suspense?.autoDisposeTimeoutMs }); observable.applyOptions({ ...optionsAfterCreation, fetchPolicy: observable.options.fetchPolicy === initialFetchOptions.fetchPolicy ? ( // restore `userOptions.fetchPolicy` for future fetches optionsAfterCreation.fetchPolicy ) : ( // otherwise `fetchPolicy` was changed from `initialFetchOptions`, `nextFetchPolicy` has been applied and we're not going to touch it observable.options.fetchPolicy ) }); return internalQueryRef; } function isTransportedQueryRef(queryRef) { return !!(queryRef && queryRef.$__apollo_queryRef); } function sanitizeForTransport(value) { return JSON.parse(JSON.stringify(value)); } // src/PreloadQuery.tsx async function PreloadQuery({ getClient, children, query, ...transportedOptions }) { const preloader = createTransportedQueryPreloader(await getClient(), { notTransportedOptionOverrides: { fetchPolicy: "no-cache" } }); const queryRef = preloader( query, transportedOptions ); return /* @__PURE__ */ React2__default.default.createElement(index_cc_cjs.SimulatePreloadedQuery, { queryRef }, typeof children === "function" ? children(queryRef) : children); } // src/registerApolloClient.tsx var seenWrappers = WeakSet ? /* @__PURE__ */ new WeakSet() : undefined; var seenClients = WeakSet ? /* @__PURE__ */ new WeakSet() : undefined; var checkForStableCache = React2.cache(() => ({})); function registerApolloClient(makeClient) { const getClient = makeGetClient(makeClient); const getPreloadClient = makeGetClient(makeClient); const PreloadQuery2 = makePreloadQuery(getPreloadClient); return { getClient, query: async (...args) => { if (checkForStableCache() !== checkForStableCache()) { console.warn( ` The \`query\` shortcut returned from \`registerApolloClient\` should not be used in Server Action or Middleware environments. Calling it multiple times in those environments would create multiple independent \`ApolloClient\` instances. Please create a single \`ApolloClient\` instance by calling \`getClient()\` at the beginning of your Server Action or Middleware function and then call \`client.query\` multiple times instead. `.trim() ); } return (await getClient()).query(...args); }, PreloadQuery: PreloadQuery2 }; } function makeGetClient(makeClient) { function makeWrappedClient() { return { client: makeClient() }; } const cachedMakeWrappedClient = React2.cache(makeWrappedClient); function getClient() { if (arguments.length) { throw new Error( ` You cannot pass arguments into \`getClient\`. Passing arguments to \`getClient\` returns a different instance of Apollo Client each time it is called with different arguments, potentially resulting in duplicate requests and a non-functional cache. `.trim() ); } const wrapper = cachedMakeWrappedClient(); if (seenWrappers && seenClients) { if (!seenWrappers.has(wrapper)) { if (seenClients.has(wrapper.client)) { console.warn( ` Multiple calls to \`getClient\` for different requests returned the same client instance. This means that private user data could accidentally be shared between requests. This happens, for example, if you create a global \`ApolloClient\` instance and your \`makeClient\` implementation just looks like \`() => client\`. Always call \`new ApolloClient\` **inside** your \`makeClient\` function and return a new instance every time \`makeClient\` is called. `.trim() ); } seenWrappers.add(wrapper); seenClients.add(wrapper.client); } } return wrapper.client; } return getClient; } function makePreloadQuery(getClient) { return function PreloadQuery2(props) { return /* @__PURE__ */ React2__default.default.createElement(PreloadQuery, { getClient, ...props }); }; } var AccumulateMultipartResponsesLink = class extends client.ApolloLink { maxDelay; constructor(config) { super(); this.maxDelay = config.cutoffDelay; } request(operation, forward) { if (!forward) { throw new Error("This is not a terminal link!"); } const operationContainsMultipartDirectives = internal$1.hasDirectives( ["defer"], operation.query ); const upstream = forward(operation); if (!operationContainsMultipartDirectives) return upstream; const maxDelay = this.maxDelay; let accumulatedData, maxDelayTimeout; const incrementalHandler = operation.client["queryManager"].incrementalHandler; let incremental; return new utilities.Observable((subscriber) => { const upstreamSubscription = upstream.subscribe({ next: (result) => { if (incrementalHandler.isIncrementalResult(result)) { incremental ||= incrementalHandler.startRequest({ query: operation.query }); accumulatedData = incremental.handle(accumulatedData?.data, result); } else { accumulatedData = result; } if (!maxDelay) { flushAccumulatedData(); } else if (!maxDelayTimeout) { maxDelayTimeout = setTimeout(flushAccumulatedData, maxDelay); } }, error: (error) => { if (maxDelayTimeout) clearTimeout(maxDelayTimeout); subscriber.error(error); }, complete: () => { if (maxDelayTimeout) { clearTimeout(maxDelayTimeout); flushAccumulatedData(); } subscriber.complete(); } }); function flushAccumulatedData() { if (accumulatedData) { subscriber.next(accumulatedData); } subscriber.complete(); upstreamSubscription.unsubscribe(); } return function cleanUp() { clearTimeout(maxDelayTimeout); upstreamSubscription.unsubscribe(); }; }); } }; function getDirectiveArgumentValue(directive, argument) { return directive.arguments?.find((arg) => arg.name.value === argument)?.value; } var RemoveMultipartDirectivesLink = class extends client.ApolloLink { stripDirectives = []; constructor(config) { super(); if (config.stripDefer !== false) this.stripDirectives.push("defer"); } request(operation, forward) { if (!forward) { throw new Error("This is not a terminal link!"); } const { query } = operation; let modifiedQuery = query; modifiedQuery = internal$1.removeDirectivesFromDocument( this.stripDirectives.map( (directive) => ({ test(node) { let shouldStrip = node.kind === "Directive" && node.name.value === directive; const label = getDirectiveArgumentValue(node, "label"); if (label?.kind === "StringValue" && label.value.startsWith("SsrDontStrip")) { shouldStrip = false; } return shouldStrip; }, remove: true }) ).concat({ test(node) { if (node.kind !== "Directive") return false; const label = getDirectiveArgumentValue(node, "label"); return label?.kind === "StringValue" && label.value.startsWith("SsrStrip"); }, remove: true }), modifiedQuery ); if (modifiedQuery === null) { return rxjs.of({}); } operation.query = modifiedQuery; return forward(operation); } }; var SSRMultipartLink = class extends client.ApolloLink { constructor(config = {}) { const combined = client.ApolloLink.from([ new RemoveMultipartDirectivesLink({ stripDefer: config.stripDefer }), new AccumulateMultipartResponsesLink({ cutoffDelay: config.cutoffDelay || 0 }) ]); super(combined.request); } }; exports.built_for_rsc = true; exports.ApolloClient = ApolloClient; exports.DebounceMultipartResponsesLink = AccumulateMultipartResponsesLink; exports.InMemoryCache = InMemoryCache; exports.ReadFromReadableStreamLink = ReadFromReadableStreamLink; exports.RemoveMultipartDirectivesLink = RemoveMultipartDirectivesLink; exports.SSRMultipartLink = SSRMultipartLink; exports.TeeToReadableStreamLink = TeeToReadableStreamLink; exports.createTransportedQueryPreloader = createTransportedQueryPreloader; exports.isTransportedQueryRef = isTransportedQueryRef; exports.readFromReadableStream = readFromReadableStream; exports.registerApolloClient = registerApolloClient; exports.reviveTransportedQueryRef = reviveTransportedQueryRef; exports.teeToReadableStream = teeToReadableStream; //# sourceMappingURL=out.js.map //# sourceMappingURL=index.rsc.cjs.map