@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
520 lines (503 loc) • 18.8 kB
TypeScript
import * as _apollo_client_index_js from '@apollo/client/index.js';
import { FetchResult, ApolloLink, WatchQueryOptions, QueryOptions, OperationVariables, DocumentNode, TypedDocumentNode, ApolloClient as ApolloClient$1, Operation, NextLink, InMemoryCache as InMemoryCache$1, InMemoryCacheConfig, NormalizedCacheObject, Observable as Observable$1, ApolloClientOptions } from '@apollo/client/index.js';
import React, { ReactNode } from 'react';
import { DocumentNode as DocumentNode$1, TypedDocumentNode as TypedDocumentNode$1 } from '@apollo/client';
import { wrapQueryRef } from '@apollo/client/react/internal/index.js';
import { QueryRef } from '@apollo/client/react/index.js';
import { Observable } from '@apollo/client/utilities/index.js';
/**
* @internal
*/
type ReadableStreamLinkEvent = {
type: "next";
value: FetchResult;
} | {
type: "completed";
} | {
type: "error";
};
/**
* Called when the link is hit, before the request is forwarded.
*
* Should return the controller for the readable stream.
*
* This is useful because when starting a query, it's not always
* clear if the query will hit the network or will be served from
* cache, deduplicated etc.
* This allows to inject the "start event" into the stream only
* when we know that more chunks will actually follow.
*/
type OnLinkHitFunction = () => ReadableStreamDefaultController<ReadableStreamLinkEvent>;
interface InternalContext {
[teeToReadableStreamKey]?: OnLinkHitFunction;
[readFromReadableStreamKey]?: ReadableStream<ReadableStreamLinkEvent>;
}
declare const teeToReadableStreamKey: unique symbol;
declare const readFromReadableStreamKey: unique symbol;
/**
* Apply to a context that will be passed to a link chain containing `TeeToReadableStreamLink`.
* @public
*/
declare function teeToReadableStream<T extends Record<string, any>>(onLinkHit: OnLinkHitFunction, context: T): T & InternalContext;
/**
* Apply to a context that will be passed to a link chain containing `ReadFromReadableStreamLink`.
* @public
*/
declare function readFromReadableStream<T extends Record<string, any>>(readableStream: ReadableStream<ReadableStreamLinkEvent>, context: T): T & InternalContext;
/**
* A link that allows the request to be cloned into a readable stream, e.g. for
* transport of multipart responses from RSC or a server loader to the browser.
* @public
*/
declare class TeeToReadableStreamLink extends ApolloLink {
constructor();
}
/**
* A link that allows the response to be read from a readable stream, e.g. for
* hydration of a multipart response from RSC or a server loader in the browser.
* @public
*/
declare class ReadFromReadableStreamLink extends ApolloLink {
constructor();
}
type TransportedOptions = {
query: string;
} & Omit<WatchQueryOptions, "query">;
type JsonString<Encoded> = string & {
__jsonString?: [Encoded];
};
type RestrictedPreloadOptions = {
fetchPolicy?: "network-only" | "cache-and-network" | "cache-first";
returnPartialData?: false;
nextFetchPolicy?: undefined;
pollInterval?: undefined;
};
/** @public */
type PreloadTransportedQueryOptions<TVariables, TData> = Omit<QueryOptions<TVariables, TData>, "query"> & RestrictedPreloadOptions;
type TransportedQueryRefOptions = TransportedOptions & RestrictedPreloadOptions;
/**
* A `TransportedQueryRef` is an opaque object accessible via renderProp within `PreloadQuery`.
*
* A child client component reading the `TransportedQueryRef` via useReadQuery will suspend until the promise resolves.
*
* @public
*/
interface TransportedQueryRef<TData = unknown, TVariables extends OperationVariables = OperationVariables> extends QueryRef<TData, TVariables> {
/**
* Temporarily disabled - see https://github.com/apollographql/apollo-client-integrations/issues/332
*
* Will now be be `undefined` both in React Server Components and Client Components until we can find a better resolution.
*/
toPromise?: undefined;
/** @internal */
$__apollo_queryRef: {
options: TransportedQueryRefOptions;
stream: ReadableStream<JsonString<ReadableStreamLinkEvent>>;
/**
* A unique key for this query, to ensure it is only hydrated once,
* even if it should get transported over the wire in a way that results
* in multiple objects describing the same queryRef.
* This key will be used to store the queryRef in the suspence cache.
*
* The chances of this happening should be slim (it is handled within
* React thanks to https://github.com/facebook/react/pull/28996), but
* as we use transported queryRefs with multiple frameworks with distinct
* transport mechanisms, this seems like a safe option.
*/
queryKey: string;
};
}
/** @public */
interface PreloadTransportedQueryFunction {
<TData = unknown, TVariables extends OperationVariables = OperationVariables>(query: DocumentNode | TypedDocumentNode<TData, TVariables>, options?: PreloadTransportedQueryOptions<NoInfer<TVariables>, TData>): TransportedQueryRef<TData, TVariables>;
}
/** @public */
declare function createTransportedQueryPreloader(client: ApolloClient$1<any>): PreloadTransportedQueryFunction;
/** @public */
declare function reviveTransportedQueryRef(queryRef: TransportedQueryRef, client: ApolloClient$1<any>): asserts queryRef is TransportedQueryRef & ReturnType<typeof wrapQueryRef<any, any>>;
/** @public */
declare function isTransportedQueryRef(queryRef: any): queryRef is TransportedQueryRef;
type PreloadQueryOptions<TVariables, TData> = PreloadTransportedQueryOptions<TVariables, TData> & {
query: DocumentNode$1 | TypedDocumentNode$1<TData, TVariables>;
};
/**
* > This export is only available in React Server Components
*
* Ensures that you can always access the same instance of ApolloClient
* during RSC for an ongoing request, while always returning
* a new instance for different requests.
*
* @example
* ```ts
* export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
* return new ApolloClient({
* cache: new InMemoryCache(),
* link: new HttpLink({
* uri: "http://example.com/api/graphql",
* }),
* });
* });
* ```
*
* @public
*/
declare function registerApolloClient<ApolloClientOrPromise extends Promise<ApolloClient$1<any>> | ApolloClient$1<any>>(makeClient: () => ApolloClientOrPromise): {
getClient: () => ApolloClientOrPromise;
query: Awaited<ApolloClientOrPromise>["query"];
/**
* Preloads data in React Server Components to be hydrated
* in Client Components.
*
* ### Example with `queryRef`
* `ClientChild` would call `useReadQuery` with the `queryRef`, the `Suspense` boundary is optional:
* ```jsx
* <PreloadQuery
* query={QUERY}
* variables={{
* foo: 1
* }}
* >
* {(queryRef) => (
* <Suspense fallback={<>loading</>}>
* <ClientChild queryRef={queryRef} />
* </Suspense>
* )}
* </PreloadQuery>
* ```
*
* ### Example for `useSuspenseQuery`
* `ClientChild` would call the same query with `useSuspenseQuery`, the `Suspense` boundary is optional:
* ```jsx
* <PreloadQuery
* query={QUERY}
* variables={{
* foo: 1
* }}
* >
* <Suspense fallback={<>loading</>}>
* <ClientChild />
* </Suspense>
* </PreloadQuery>
* ```
*/
PreloadQuery: PreloadQueryComponent;
};
/**
* Props for `PreloadQueryComponent`
* @see {@link PreloadQueryComponent}
* @public
*/
interface PreloadQueryProps<TData, TVariables extends OperationVariables> extends PreloadQueryOptions<TVariables, TData> {
children: ReactNode | ((queryRef: TransportedQueryRef<NoInfer<TData>, NoInfer<TVariables>>) => ReactNode);
}
/**
* Preloads data in React Server Components to be hydrated
* in Client Components.
*
* ### Example with `queryRef`
* `ClientChild` would call `useReadQuery` with the `queryRef`, the `Suspense` boundary is optional:
* ```jsx
* <PreloadQuery
* query={QUERY}
* variables={{
* foo: 1
* }}
* >
* {(queryRef) => (
* <Suspense fallback={<>loading</>}>
* <ClientChild queryRef={queryRef} />
* </Suspense>
* )}
* </PreloadQuery>
* ```
*
* ### Example for `useSuspenseQuery`
* `ClientChild` would call the same query with `useSuspenseQuery`, the `Suspense` boundary is optional:
* ```jsx
* <PreloadQuery
* query={QUERY}
* variables={{
* foo: 1
* }}
* >
* <Suspense fallback={<>loading</>}>
* <ClientChild />
* </Suspense>
* </PreloadQuery>
* ```
*
* @public
*/
interface PreloadQueryComponent {
<TData, TVariables extends OperationVariables>(props: PreloadQueryProps<TData, TVariables>): React.ReactElement;
}
interface AccumulateMultipartResponsesConfig {
/**
* The maximum delay in milliseconds
* from receiving the first response
* until the accumulated data will be flushed
* and the connection will be closed.
*/
cutoffDelay: number;
}
/**
*
* This link can be used to "debounce" the initial response of a multipart request. Any incremental data received during the `cutoffDelay` time will be merged into the initial response.
*
* After `cutoffDelay`, the link will return the initial response, even if there is still incremental data pending, and close the network connection.
*
* If `cutoffDelay` is `0`, the link will immediately return data as soon as it is received, without waiting for incremental data, and immediately close the network connection.
*
* @example
* ```ts
* new AccumulateMultipartResponsesLink({
* // The maximum delay in milliseconds
* // from receiving the first response
* // until the accumulated data will be flushed
* // and the connection will be closed.
* cutoffDelay: 100,
* });
* ```
*
* @public
*/
declare class AccumulateMultipartResponsesLink extends ApolloLink {
private maxDelay;
constructor(config: AccumulateMultipartResponsesConfig);
request(operation: Operation, forward?: NextLink): Observable<FetchResult<Record<string, any>, Record<string, any>, Record<string, any>>>;
}
interface RemoveMultipartDirectivesConfig {
/**
* Whether to strip fragments with `@defer` directives
* from queries before sending them to the server.
*
* Defaults to `true`.
*
* Can be overwritten by adding a label starting
* with either `"SsrDontStrip"` or `"SsrStrip"` to the
* directive.
*/
stripDefer?: boolean;
}
/**
* This link will (if called with `stripDefer: true`) strip all `@defer` fragments from your query.
*
* This is used to prevent the server from doing additional work in SSR scenarios where multipart responses cannot be handled anyways.
*
* You can exclude certain fragments from this behavior by giving them a label starting with `"SsrDontStrip"`.
* The "starting with" is important, because labels have to be unique per operation. So if you have multiple directives where you want to override the default stipping behaviour,
* you can do this by annotating them like
* ```graphql
* query myQuery {
* fastField
* ... @defer(label: "SsrDontStrip1") {
* slowField1
* }
* ... @defer(label: "SsrDontStrip2") {
* slowField2
* }
* }
* ```
*
* You can also use the link with `stripDefer: false` and mark certain fragments to be stripped by giving them a label starting with `"SsrStrip"`.
*
* @example
* ```ts
* new RemoveMultipartDirectivesLink({
* // Whether to strip fragments with `@defer` directives
* // from queries before sending them to the server.
* //
* // Defaults to `true`.
* //
* // Can be overwritten by adding a label starting
* // with either `"SsrDontStrip"` or `"SsrStrip"` to the
* // directive.
* stripDefer: true,
* });
* ```
*
* @public
*/
declare class RemoveMultipartDirectivesLink extends ApolloLink {
private stripDirectives;
constructor(config: RemoveMultipartDirectivesConfig);
request(operation: Operation, forward?: NextLink): Observable<{}>;
}
interface SSRMultipartLinkConfig {
/**
* Whether to strip fragments with `@defer` directives
* from queries before sending them to the server.
*
* Defaults to `true`.
*
* Can be overwritten by adding a label starting
* with either `"SsrDontStrip"` or `"SsrStrip"` to the
* directive.
*/
stripDefer?: boolean;
/**
* The maximum delay in milliseconds
* from receiving the first response
* until the accumulated data will be flushed
* and the connection will be closed.
*
* Defaults to `0`.
*/
cutoffDelay?: number;
}
/**
* A convenient combination of `RemoveMultipartDirectivesLink` and `AccumulateMultipartResponsesLink`.
*
* @example
* ```ts
* new SSRMultipartLink({
* // Whether to strip fragments with `@defer` directives
* // from queries before sending them to the server.
* //
* // Defaults to `true`.
* //
* // Can be overwritten by adding a label starting
* // with either `"SsrDontStrip"` or `"SsrStrip"` to the
* // directive.
* stripDefer: true,
* // The maximum delay in milliseconds
* // from receiving the first response
* // until the accumulated data will be flushed
* // and the connection will be closed.
* //
* // Defaults to `0`.
* //
* cutoffDelay: 100,
* });
* ```
*
* @public
*/
declare class SSRMultipartLink extends ApolloLink {
constructor(config?: SSRMultipartLinkConfig);
}
declare const sourceSymbol: unique symbol;
/**
* A version of `InMemoryCache` to be used with streaming SSR.
*
* For more documentation, please see {@link https://www.apollographql.com/docs/react/api/cache/InMemoryCache | the Apollo Client API documentation}.
*
* @public
*/
declare class InMemoryCache extends InMemoryCache$1 {
/**
* Information about the current package and it's export names, for use in error messages.
*
* @internal
*/
static readonly info: {
pkg: string;
};
[sourceSymbol]: string;
constructor(config?: InMemoryCacheConfig | undefined);
}
type TransportIdentifier = string & {
__transportIdentifier: true;
};
/**
* Events that will be emitted by a wrapped ApolloClient instance during
* SSR on `DataTransportProviderImplementation.registerDispatchRequestStarted`,
* to be transported to the browser and replayed there using
* `DataTransportProviderImplementation.onQueryEvent`.
*
* @public
*/
type QueryEvent = {
type: "started";
options: TransportedOptions;
id: TransportIdentifier;
} | (ReadableStreamLinkEvent & {
id: TransportIdentifier;
});
type ProgressEvent = Exclude<QueryEvent, {
type: "started";
}>;
type SimulatedQueryInfo = {
controller: ReadableStreamDefaultController<ReadableStreamLinkEvent>;
options: WatchQueryOptions<OperationVariables, any>;
};
interface WrappedApolloClientOptions extends Omit<ApolloClientOptions<NormalizedCacheObject>, "cache" | "ssrMode" | "ssrForceFetchDelay"> {
cache: InMemoryCache;
}
declare class ApolloClientBase extends ApolloClient$1<NormalizedCacheObject> {
/**
* Information about the current package and it's export names, for use in error messages.
*
* @internal
*/
static readonly info: {
pkg: string;
};
[sourceSymbol]: string;
constructor(options: WrappedApolloClientOptions);
setLink(newLink: ApolloLink): void;
}
declare class ApolloClientClientBaseImpl extends ApolloClientBase {
constructor(options: WrappedApolloClientOptions);
private simulatedStreamingQueries;
onQueryStarted({ options, id }: Extract<QueryEvent, {
type: "started";
}>): void;
onQueryProgress: (event: ProgressEvent) => void;
/**
* Can be called when the stream closed unexpectedly while there might still be unresolved
* simulated server-side queries going on.
* Those queries will be cancelled and then re-run in the browser.
*/
rerunSimulatedQueries: () => void;
rerunSimulatedQuery: (queryInfo: SimulatedQueryInfo) => void;
}
declare class ApolloClientSSRImpl extends ApolloClientClientBaseImpl {
watchQueryQueue: {
push: (value: {
event: Extract<QueryEvent, {
type: "started";
}>;
observable: Observable$1<Exclude<QueryEvent, {
type: "started";
}>>;
}) => void;
register: (callback: ((value: {
event: Extract<QueryEvent, {
type: "started";
}>;
observable: Observable$1<Exclude<QueryEvent, {
type: "started";
}>>;
}) => void) | null) => void;
};
pushEventStream(options: WatchQueryOptions<any, any>): ReadableStreamDefaultController<ReadableStreamLinkEvent>;
watchQuery<T = any, TVariables extends OperationVariables = OperationVariables>(options: WatchQueryOptions<TVariables, T>): _apollo_client_index_js.ObservableQuery<T, TVariables>;
}
declare const ApolloClient_base: typeof ApolloClientBase;
/**
* A version of `ApolloClient` to be used with streaming SSR or in React Server Components.
*
* For more documentation, please see {@link https://www.apollographql.com/docs/react/api/core/ApolloClient | the Apollo Client API documentation}.
*
* @public
*/
declare class ApolloClient<Ignored = NormalizedCacheObject> extends ApolloClient_base implements Partial<ApolloClientSSRImpl> {
/** @internal */
onQueryStarted?: ApolloClientSSRImpl["onQueryStarted"];
/** @internal */
onQueryProgress?: ApolloClientSSRImpl["onQueryProgress"];
/** @internal */
rerunSimulatedQueries?: ApolloClientSSRImpl["rerunSimulatedQueries"];
/** @internal */
rerunSimulatedQuery?: ApolloClientSSRImpl["rerunSimulatedQuery"];
/** @internal */
watchQueryQueue?: ApolloClientSSRImpl["watchQueryQueue"];
}
declare const ApolloClientSingleton: unique symbol;
declare global {
interface Window {
[ApolloClientSingleton]?: ApolloClient<any>;
}
}
export { ApolloClient, AccumulateMultipartResponsesLink as DebounceMultipartResponsesLink, InMemoryCache, type PreloadQueryComponent, type PreloadQueryProps, type PreloadTransportedQueryFunction, type PreloadTransportedQueryOptions, ReadFromReadableStreamLink, type ReadableStreamLinkEvent, RemoveMultipartDirectivesLink, SSRMultipartLink, TeeToReadableStreamLink, type TransportedQueryRef, createTransportedQueryPreloader, isTransportedQueryRef, readFromReadableStream, registerApolloClient, reviveTransportedQueryRef, teeToReadableStream };