@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
558 lines (546 loc) • 17.7 kB
JavaScript
;
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 utilities = require('@apollo/client/utilities');
var graphql = require('graphql');
var cache = require('@apollo/client/cache');
var streamUtils = require('@apollo/client-react-streaming/stream-utils');
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) => {
const subscription = forward(operation).subscribe({
next(result) {
controller.enqueue({ type: "next", value: result });
observer.next(result);
},
error(error) {
controller.enqueue({ type: "error" });
tryClose();
observer.error(error);
},
complete() {
controller.enqueue({ type: "completed" });
tryClose();
observer.complete();
}
});
return () => {
tryClose();
subscription.unsubscribe();
};
});
}
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();
}
consume(reader);
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();
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) {
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(
{
connectToDevTools: false,
...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) {
return (...[query, options]) => {
options = { ...options };
delete options.returnPartialData;
delete options.nextFetchPolicy;
delete options.pollInterval;
const [controller, stream] = getInjectableEventStream();
client.query({
query,
...options,
// ensure that this query makes it to the network
fetchPolicy: "no-cache",
context: skipDataTransport(
teeToReadableStream(() => controller, {
...options?.context,
// we want to do this even if the query is already running for another reason
queryDeduplication: false
})
)
}).catch(() => {
});
return createTransportedQueryRef(
query,
options,
crypto.randomUUID(),
stream
);
};
}
function createTransportedQueryRef(query, options, queryKey, stream) {
return {
$__apollo_queryRef: {
options: sanitizeForTransport(serializeOptions({ query, ...options })),
queryKey,
stream: stream.pipeThrough(new streamUtils.JSONEncodeStream())
}
};
}
var hydrationCache = /* @__PURE__ */ new WeakMap();
function reviveTransportedQueryRef(queryRef, client) {
const {
$__apollo_queryRef: { options, stream, queryKey }
} = queryRef;
if (!hydrationCache.has(queryRef)) {
const hydratedOptions = deserializeOptions(options);
const cacheKey = [
hydratedOptions.query,
cache.canonicalStringify(hydratedOptions.variables),
queryKey
];
hydrationCache.set(queryRef, { cacheKey });
const internalQueryRef = internal.getSuspenseCache(client).getQueryRef(
cacheKey,
() => client.watchQuery({
...hydratedOptions,
fetchPolicy: "network-only",
context: skipDataTransport(
readFromReadableStream(stream.pipeThrough(new streamUtils.JSONDecodeStream()), {
...hydratedOptions.context,
queryDeduplication: true
})
)
})
);
Object.assign(queryRef, internal.wrapQueryRef(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,
...options
}) {
const preloader = createTransportedQueryPreloader(await getClient());
const { query, ...transportedOptions } = options;
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 = utilities.hasDirectives(
["defer"],
operation.query
);
const upstream = forward(operation);
if (!operationContainsMultipartDirectives)
return upstream;
const maxDelay = this.maxDelay;
let accumulatedData, maxDelayTimeout;
return new utilities.Observable((subscriber) => {
const upstreamSubscription = upstream.subscribe({
next: (result) => {
if (accumulatedData) {
if (accumulatedData.data && "incremental" in result) {
accumulatedData.data = utilities.mergeIncrementalData(
accumulatedData.data,
result
);
} else if (result.data) {
accumulatedData.data = result.data;
}
if (result.errors) {
accumulatedData.errors = [
...accumulatedData.errors || [],
...result.errors || []
];
}
if (result.extensions)
accumulatedData.extensions = {
...accumulatedData.extensions,
...result.extensions
};
} 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() {
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 = utilities.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 utilities.Observable.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