-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implement multipart streaming for PreloadQuery
#389
base: next
Are you sure you want to change the base?
Changes from 7 commits
f96ec74
6890bd9
e77d7d7
f3a27e6
f6977a3
711208b
51562de
09c1be0
1554899
6337e38
88e5e63
ecc65ff
5e28770
888130c
c4c4282
56adfdb
92abc98
b3ae471
e0dbe98
5bacf2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ import type { | |
FetchResult, | ||
DocumentNode, | ||
NormalizedCacheObject, | ||
ApolloLink, | ||
} from "@apollo/client/index.js"; | ||
import { | ||
ApolloClient as OrigApolloClient, | ||
|
@@ -28,6 +29,10 @@ import type { | |
import { bundle, sourceSymbol } from "../bundleInfo.js"; | ||
import { serializeOptions, deserializeOptions } from "./transportedOptions.js"; | ||
import { assertInstance } from "../assertInstance.js"; | ||
import { | ||
ReadFromReadableStreamLink, | ||
TeeToReadableStreamLink, | ||
} from "../ReadableStreamLink.js"; | ||
|
||
function getQueryManager( | ||
client: OrigApolloClient<unknown> | ||
|
@@ -298,9 +303,37 @@ export class ApolloClientClientBaseImpl extends ApolloClientBase { | |
}; | ||
} | ||
|
||
const skipDataTransportKey = Symbol.for("apollo.dataTransport.skip"); | ||
interface InternalContext { | ||
[skipDataTransportKey]?: boolean; | ||
} | ||
|
||
/** | ||
* Apply to a context to prevent this operation from being transported over the SSR data transport mechanism. | ||
* @param readableStream | ||
* @param context | ||
* @returns | ||
*/ | ||
export function skipDataTransport<T extends Record<string, any>>( | ||
context: T | ||
): T & InternalContext { | ||
return Object.assign(context, { | ||
[skipDataTransportKey]: true, | ||
}); | ||
} | ||
|
||
class ApolloClientSSRImpl extends ApolloClientClientBaseImpl { | ||
private forwardedQueries = new (getTrieConstructor(this))(); | ||
|
||
constructor(options: WrappedApolloClientOptions) { | ||
super(options); | ||
this.setLink(this.link); | ||
} | ||
|
||
setLink(newLink: ApolloLink) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. RSC: adds |
||
super.setLink.call(this, ReadFromReadableStreamLink.concat(newLink)); | ||
} | ||
|
||
watchQueryQueue = createBackpressuredCallback<{ | ||
event: Extract<QueryEvent, { type: "started" }>; | ||
observable: Observable<Exclude<QueryEvent, { type: "started" }>>; | ||
|
@@ -315,6 +348,9 @@ class ApolloClientSSRImpl extends ApolloClientClientBaseImpl { | |
if ( | ||
options.fetchPolicy !== "cache-only" && | ||
options.fetchPolicy !== "standby" && | ||
!(options.context as InternalContext | undefined)?.[ | ||
skipDataTransportKey | ||
] && | ||
!this.forwardedQueries.peekArray(cacheKeyArr) | ||
) { | ||
// don't transport the same query over twice | ||
|
@@ -376,14 +412,34 @@ class ApolloClientSSRImpl extends ApolloClientClientBaseImpl { | |
} | ||
} | ||
|
||
export class ApolloClientBrowserImpl extends ApolloClientClientBaseImpl {} | ||
export class ApolloClientBrowserImpl extends ApolloClientClientBaseImpl { | ||
constructor(options: WrappedApolloClientOptions) { | ||
super(options); | ||
this.setLink(this.link); | ||
} | ||
|
||
setLink(newLink: ApolloLink) { | ||
super.setLink.call(this, ReadFromReadableStreamLink.concat(newLink)); | ||
} | ||
} | ||
|
||
export class ApolloClientRSCImpl extends ApolloClientBase { | ||
constructor(options: WrappedApolloClientOptions) { | ||
super(options); | ||
this.setLink(this.link); | ||
} | ||
|
||
setLink(newLink: ApolloLink) { | ||
super.setLink.call(this, TeeToReadableStreamLink.concat(newLink)); | ||
} | ||
} | ||
|
||
const ApolloClientImplementation = | ||
/*#__PURE__*/ process.env.REACT_ENV === "ssr" | ||
? ApolloClientSSRImpl | ||
: process.env.REACT_ENV === "browser" | ||
? ApolloClientBrowserImpl | ||
: ApolloClientBase; | ||
: ApolloClientRSCImpl; | ||
|
||
/** | ||
* A version of `ApolloClient` to be used with streaming SSR or in React Server Components. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,26 @@ | ||
import { SimulatePreloadedQuery } from "./index.cc.js"; | ||
import type { | ||
ApolloClient, | ||
DocumentNode, | ||
OperationVariables, | ||
QueryOptions, | ||
TypedDocumentNode, | ||
} from "@apollo/client"; | ||
import type { ReactNode } from "react"; | ||
import React from "react"; | ||
import { serializeOptions } from "./DataTransportAbstraction/transportedOptions.js"; | ||
import type { TransportedQueryRef } from "./transportedQueryRef.js"; | ||
import { createTransportedQueryRef } from "./transportedQueryRef.js"; | ||
import type { ProgressEvent } from "./DataTransportAbstraction/DataTransportAbstraction.js"; | ||
|
||
export type RestrictedPreloadOptions = { | ||
fetchPolicy?: "cache-first"; | ||
returnPartialData?: false; | ||
nextFetchPolicy?: undefined; | ||
pollInterval?: undefined; | ||
}; | ||
|
||
export type PreloadQueryOptions<TVariables, TData> = QueryOptions< | ||
TVariables, | ||
TData | ||
> & | ||
RestrictedPreloadOptions; | ||
|
||
export function PreloadQuery<TData, TVariables extends OperationVariables>({ | ||
import type { | ||
PreloadTransportedQueryOptions, | ||
TransportedQueryRef, | ||
} from "./transportedQueryRef.js"; | ||
import { createTransportedQueryPreloader } from "./transportedQueryRef.js"; | ||
|
||
export type PreloadQueryOptions<TVariables, TData> = | ||
PreloadTransportedQueryOptions<TVariables, TData> & { | ||
query: DocumentNode | TypedDocumentNode<TData, TVariables>; | ||
}; | ||
export async function PreloadQuery< | ||
TData, | ||
TVariables extends OperationVariables, | ||
>({ | ||
getClient, | ||
children, | ||
...options | ||
|
@@ -35,50 +31,14 @@ export function PreloadQuery<TData, TVariables extends OperationVariables>({ | |
| (( | ||
queryRef: TransportedQueryRef<NoInfer<TData>, NoInfer<TVariables>> | ||
) => ReactNode); | ||
}): React.ReactElement { | ||
const preloadOptions = { | ||
...options, | ||
fetchPolicy: "cache-first" as const, | ||
returnPartialData: false, | ||
pollInterval: undefined, | ||
nextFetchPolicy: undefined, | ||
} satisfies RestrictedPreloadOptions; | ||
|
||
const transportedOptions = sanitizeForTransport( | ||
serializeOptions(preloadOptions) | ||
); | ||
|
||
const resultPromise = Promise.resolve(getClient()) | ||
.then((client) => client.query<TData, TVariables>(preloadOptions)) | ||
.then<Array<Omit<ProgressEvent, "id">>, Array<Omit<ProgressEvent, "id">>>( | ||
(result) => [ | ||
{ type: "data", result: sanitizeForTransport(result) }, | ||
{ type: "complete" }, | ||
], | ||
() => [{ type: "error" }] | ||
); | ||
|
||
const queryKey = crypto.randomUUID(); | ||
}): Promise<React.ReactElement> { | ||
const preloader = createTransportedQueryPreloader(await getClient()); | ||
const { query, ...transportedOptions } = options; | ||
const queryRef = preloader(query, transportedOptions); | ||
Comment on lines
+34
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the logic now moved into |
||
|
||
return ( | ||
<SimulatePreloadedQuery<TData> | ||
options={transportedOptions} | ||
result={resultPromise} | ||
queryKey={typeof children === "function" ? queryKey : undefined} | ||
> | ||
{typeof children === "function" | ||
? children( | ||
createTransportedQueryRef<TData, TVariables>( | ||
transportedOptions, | ||
queryKey, | ||
resultPromise | ||
) | ||
) | ||
: children} | ||
<SimulatePreloadedQuery<TData> queryRef={queryRef}> | ||
{typeof children === "function" ? children(queryRef) : children} | ||
</SimulatePreloadedQuery> | ||
); | ||
} | ||
|
||
function sanitizeForTransport<T>(value: T) { | ||
return JSON.parse(JSON.stringify(value)) as T; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are still leftovers from changing to Next 15 and only surfaced now.