diff --git a/examples/hack-the-supergraph-ssr/app/layout.tsx b/examples/hack-the-supergraph-ssr/app/layout.tsx
index ffa68164..b5fdacb6 100644
--- a/examples/hack-the-supergraph-ssr/app/layout.tsx
+++ b/examples/hack-the-supergraph-ssr/app/layout.tsx
@@ -1,21 +1,87 @@
import { cookies } from "next/headers";
-import { ClientLayout } from "./ClientLayout";
+import { registerApolloClient } from "@apollo/client-react-streaming";
+import {
+ gql,
+ ApolloClient,
+ InMemoryCache,
+ HttpLink,
+ TypedDocumentNode,
+} from "@apollo/client";
+import { ClientLayout } from "./ClientLayout";
import { ApolloWrapper } from "./ApolloWrapper";
+const { PreloadQuery } = registerApolloClient(() => {
+ return new ApolloClient({
+ cache: new InMemoryCache(),
+ link: new HttpLink({
+ // this needs to be an absolute url, as relative urls cannot be used in SSR
+ uri: "https://main--hack-the-e-commerce.apollographos.net/graphql",
+ // you can disable result caching here if you want to
+ // (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
+ fetchOptions: { cache: "no-store" },
+ // fetchOptions: { headers: { "x-custom-delay": "5000" } },
+ }),
+ });
+});
+
+const ProductCardProductFragment: TypedDocumentNode<{
+ id: string;
+ title: string;
+ description: string;
+ mediaUrl: string;
+}> = gql`
+ fragment ProductCardProductFragment on Product {
+ id
+ title
+ description
+ mediaUrl
+ }
+`;
+
+const ReviewsFragment: TypedDocumentNode<{
+ id: string;
+ reviews: {
+ rating: number;
+ };
+}> = gql`
+ fragment ReviewsFragment on Product {
+ description
+ reviews {
+ rating
+ }
+ }
+`;
+
+const GET_LATEST_PRODUCTS: TypedDocumentNode<{
+ products: { id: string }[];
+}> = gql`
+ query HomepageProducts {
+ products {
+ id
+ ...ProductCardProductFragment
+ ...ReviewsFragment @defer
+ }
+ }
+ ${ProductCardProductFragment}
+ ${ReviewsFragment}
+`;
+
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const cookieStore = cookies();
- const delay = Number(cookieStore.get("apollo-x-custom-delay")?.value ?? 1000);
+ const delay = Number(cookieStore.get("apollo-x-custom-delay")?.value ?? 5000);
return (
- {children}
+
+ {children}
+
diff --git a/examples/hack-the-supergraph-ssr/app/not-found.tsx b/examples/hack-the-supergraph-ssr/app/not-found.tsx
index afa86895..98117e00 100644
--- a/examples/hack-the-supergraph-ssr/app/not-found.tsx
+++ b/examples/hack-the-supergraph-ssr/app/not-found.tsx
@@ -6,11 +6,8 @@ import Error from "./error";
import Link from "next/link";
export const Fallback = () => (
-
-
-
+ hi
+ //
);
export default Fallback;
diff --git a/examples/hack-the-supergraph-ssr/app/page.tsx b/examples/hack-the-supergraph-ssr/app/page.tsx
index 0a4dd36f..b5e814e2 100644
--- a/examples/hack-the-supergraph-ssr/app/page.tsx
+++ b/examples/hack-the-supergraph-ssr/app/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import ProductCard from "../components/ProductCard";
+import ProductCard, { Reviews } from "../components/ProductCard";
import { Heading, SimpleGrid, Stack, Text, VStack } from "@chakra-ui/react";
import { useSuspenseQuery, gql, TypedDocumentNode } from "@apollo/client";
@@ -11,14 +11,15 @@ const GET_LATEST_PRODUCTS: TypedDocumentNode<{
products {
id
...ProductCardProductFragment
+ ...ReviewsFragment @defer
}
}
${ProductCard.fragments.ProductCardProductFragment}
+ ${Reviews.fragments.ReviewsFragment}
`;
export default function HomePage() {
- const { data } = useSuspenseQuery(GET_LATEST_PRODUCTS, {
- fetchPolicy: "cache-first",
- });
+ const { data } = useSuspenseQuery(GET_LATEST_PRODUCTS);
+
return (
diff --git a/examples/hack-the-supergraph-ssr/app/product/[id]/page.tsx b/examples/hack-the-supergraph-ssr/app/product/[id]/page.tsx
index 516c9077..23601cc5 100644
--- a/examples/hack-the-supergraph-ssr/app/product/[id]/page.tsx
+++ b/examples/hack-the-supergraph-ssr/app/product/[id]/page.tsx
@@ -31,7 +31,6 @@ const GET_PRODUCT_DETAILS: TypedDocumentNode<{
};
}> = gql`
fragment ProductFragment on Product {
- averageRating
reviews {
content
rating
diff --git a/examples/hack-the-supergraph-ssr/components/DelaySlider.tsx b/examples/hack-the-supergraph-ssr/components/DelaySlider.tsx
index 33d8bc13..8b8a1549 100644
--- a/examples/hack-the-supergraph-ssr/components/DelaySlider.tsx
+++ b/examples/hack-the-supergraph-ssr/components/DelaySlider.tsx
@@ -25,7 +25,7 @@ export default function DelaySlider() {
Custom @defer
Delay: {delay}ms
-
+
diff --git a/examples/hack-the-supergraph-ssr/components/ProductCard.tsx b/examples/hack-the-supergraph-ssr/components/ProductCard.tsx
index 06650aea..86ba81f2 100644
--- a/examples/hack-the-supergraph-ssr/components/ProductCard.tsx
+++ b/examples/hack-the-supergraph-ssr/components/ProductCard.tsx
@@ -1,3 +1,4 @@
+import { Suspense } from "react";
import Button from "./Button";
import ReviewRating from "./ReviewRating";
import {
@@ -9,22 +10,33 @@ import {
usePrefersReducedMotion,
} from "@chakra-ui/react";
import Link from "next/link";
-import { useFragment, TypedDocumentNode, gql } from "@apollo/client";
+import { useSuspenseFragment, TypedDocumentNode, gql } from "@apollo/client";
const ProductCardProductFragment: TypedDocumentNode<{
id: string;
title: string;
description: string;
mediaUrl: string;
- averageRating: number;
}> = gql`
fragment ProductCardProductFragment on Product {
id
title
description
mediaUrl
- ... @defer {
- averageRating
+ }
+`;
+
+const ReviewsFragment: TypedDocumentNode<{
+ id: string;
+ description: string;
+ reviews: Array<{
+ rating: number;
+ }>;
+}> = gql`
+ fragment ReviewsFragment on Product {
+ description
+ reviews {
+ rating
}
}
`;
@@ -32,7 +44,7 @@ const ProductCardProductFragment: TypedDocumentNode<{
function ProductCard({ id }: { id: string }) {
const prefersReducedMotion = usePrefersReducedMotion();
- const { data } = useFragment({
+ const { data } = useSuspenseFragment({
fragment: ProductCardProductFragment,
from: `Product:${id}`,
});
@@ -60,24 +72,52 @@ function ProductCard({ id }: { id: string }) {
{data?.title}
- {data?.averageRating ? (
-
- {`"${data?.description}"`}
-
-
-
-
-
- ) : (
-
-
-
- )}
+
+
+
);
}
+export function Reviews({ id }: { id: string }) {
+ const { data } = useSuspenseFragment({
+ fragment: ReviewsFragment,
+ from: `Product:${id}`,
+ });
+
+ // console.log({ data });
+
+ const average = (array: Array) =>
+ array.reduce((a, b) => a + b) / array.length;
+
+ const averageRating = data.reviews.length
+ ? average(data.reviews.map((review) => review.rating))
+ : 0;
+
+ // console.log({ averageRating });
+
+ return (
+ <>
+ {averageRating ? (
+
+ {`"${data?.description}"`}
+
+
+
+
+
+ ) : (
+
+
+
+ )}
+ >
+ );
+}
+
+Reviews.fragments = { ReviewsFragment };
+
ProductCard.fragments = {
ProductCardProductFragment,
};
diff --git a/examples/hack-the-supergraph-ssr/components/SettingsModal.tsx b/examples/hack-the-supergraph-ssr/components/SettingsModal.tsx
index cace5837..77203ee1 100644
--- a/examples/hack-the-supergraph-ssr/components/SettingsModal.tsx
+++ b/examples/hack-the-supergraph-ssr/components/SettingsModal.tsx
@@ -1,3 +1,4 @@
+"use client";
import DelaySlider from "./DelaySlider";
import {
Button,
diff --git a/examples/hack-the-supergraph-ssr/package.json b/examples/hack-the-supergraph-ssr/package.json
index 69a58a4e..eff8f912 100644
--- a/examples/hack-the-supergraph-ssr/package.json
+++ b/examples/hack-the-supergraph-ssr/package.json
@@ -4,13 +4,14 @@
"private": true,
"scripts": {
"dev": "next dev",
- "prebuild": "yarn workspace @apollo/experimental-nextjs-app-support run build",
+ "prebuild": "yarn workspace @apollo/experimental-nextjs-app-support run build && yarn workspace @apollo/client-react-streaming run build",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
- "@apollo/client": "3.10.4",
+ "@apollo/client": "https://pkg.pr.new/@apollo/client@12066",
+ "@apollo/client-react-streaming": "workspace:^",
"@apollo/experimental-nextjs-app-support": "workspace:^",
"@apollo/space-kit": "^9.11.0",
"@chakra-ui/next-js": "^2.1.2",
diff --git a/package.json b/package.json
index 95c7b55b..7869a1f6 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"build:docmodel": "yarn workspaces foreach --all --include \"@apollo/*\" exec api-extractor run"
},
"resolutions": {
+ "@apollo/client": "https://pkg.pr.new/@apollo/client@12066",
"react": "19.0.0-rc-935180c7e0-20240524",
"react-dom": "19.0.0-rc-935180c7e0-20240524",
"react-server-dom-webpack": "19.0.0-beta-94eed63c49-20240425",
diff --git a/packages/client-react-streaming/src/PreloadQuery.tsx b/packages/client-react-streaming/src/PreloadQuery.tsx
index 628d037a..30db778e 100644
--- a/packages/client-react-streaming/src/PreloadQuery.tsx
+++ b/packages/client-react-streaming/src/PreloadQuery.tsx
@@ -1,6 +1,8 @@
import { SimulatePreloadedQuery } from "./index.cc.js";
import type {
ApolloClient,
+ ApolloQueryResult,
+ Observable,
OperationVariables,
QueryOptions,
} from "@apollo/client";
@@ -48,30 +50,88 @@ export function PreloadQuery({
serializeOptions(preloadOptions)
);
- const resultPromise = Promise.resolve(getClient())
- .then((client) => client.query(preloadOptions))
- .then>, Array>>(
- (result) => [
- { type: "data", result: sanitizeForTransport(result) },
- { type: "complete" },
- ],
- () => [{ type: "error" }]
+ // const resultPromise = Promise.resolve(getClient())
+ // .then((client) => client.query(preloadOptions))
+ // .then>, Array>>(
+ // (result) => [
+ // { type: "data", result: sanitizeForTransport(result) },
+ // { type: "complete" },
+ // ],
+ // () => [{ type: "error" }]
+ // );
+
+ type ObservableEvent =
+ | { type: "error" | "complete" }
+ | { type: "data"; result: ApolloQueryResult };
+
+ async function* observableToAsyncEventIterator(
+ observable: Observable>
+ ) {
+ let resolveNext: (value: ObservableEvent) => void;
+ const promises: Promise>[] = [];
+ queuePromise();
+
+ function queuePromise() {
+ promises.push(
+ new Promise>((resolve) => {
+ resolveNext = (event: ObservableEvent) => {
+ resolve(event);
+ queuePromise();
+ };
+ })
+ );
+ }
+
+ observable.subscribe(
+ (value) =>
+ resolveNext({ type: "data", result: sanitizeForTransport(value) }),
+ () => resolveNext({ type: "error" }),
+ () => resolveNext({ type: "complete" })
);
+ yield "initialization value" as unknown as Promise>;
+
+ while (true) {
+ const val = await promises.shift()!;
+ yield val;
+ }
+ }
+
+ async function* resultAsyncGeneratorFunction(): AsyncGenerator<
+ Omit
+ > {
+ const client = await getClient();
+
+ const obsQuery = client.watchQuery(preloadOptions);
+
+ const asyncEventIterator = observableToAsyncEventIterator(obsQuery);
+
+ for await (const event of asyncEventIterator) {
+ yield event;
+ const cacheDiff = client.cache.diff({
+ query: preloadOptions.query,
+ optimistic: false,
+ // variables: preloadOptions.variables,
+ });
+ if (cacheDiff.complete || event.type === "error") {
+ return;
+ }
+ }
+ }
const queryKey = crypto.randomUUID();
return (
options={transportedOptions}
- result={resultPromise}
+ result={resultAsyncGeneratorFunction()}
queryKey={typeof children === "function" ? queryKey : undefined}
>
{typeof children === "function"
? children(
createTransportedQueryRef(
transportedOptions,
- queryKey,
- resultPromise
+ queryKey
+ // resultAsyncGeneratorFunction
)
)
: children}
diff --git a/packages/client-react-streaming/src/SimulatePreloadedQuery.cc.ts b/packages/client-react-streaming/src/SimulatePreloadedQuery.cc.ts
index b7122b4e..6b20defb 100644
--- a/packages/client-react-streaming/src/SimulatePreloadedQuery.cc.ts
+++ b/packages/client-react-streaming/src/SimulatePreloadedQuery.cc.ts
@@ -15,7 +15,7 @@ import {
type TransportedOptions,
} from "./DataTransportAbstraction/transportedOptions.js";
import type { QueryManager } from "@apollo/client/core/QueryManager.js";
-import { useMemo } from "react";
+import { useMemo, useEffect } from "react";
import type { ReactNode } from "react";
import invariant from "ts-invariant";
import type { TransportedQueryRefOptions } from "./transportedQueryRef.js";
@@ -30,11 +30,13 @@ export default function SimulatePreloadedQuery({
queryKey,
}: {
options: TransportedQueryRefOptions;
- result: Promise>>;
+ // result: Promise>>;
+ result: AsyncGenerator>;
children: ReactNode;
queryKey?: string;
}) {
const client = useApolloClient() as WrappedApolloClient;
+
if (!handledRequests.has(options)) {
const id =
`preloadedQuery:${(client["queryManager"] as QueryManager).generateQueryId()}` as TransportIdentifier;
@@ -49,12 +51,15 @@ export default function SimulatePreloadedQuery({
options,
});
- result.then((results) => {
- invariant.debug("Preloaded query %s: received events: %o", id, results);
- for (const event of results) {
- client.onQueryProgress!({ ...event, id } as ProgressEvent);
+ // eslint-disable-next-line no-inner-declarations
+ async function consume() {
+ for await (const chunk of result) {
+ invariant.debug("Preloaded query %s: received events: %o", id, chunk);
+ client.onQueryProgress!({ ...chunk, id } as ProgressEvent);
}
- });
+ }
+
+ consume();
}
const bgQueryArgs = useMemo>(() => {
diff --git a/packages/client-react-streaming/src/transportedQueryRef.ts b/packages/client-react-streaming/src/transportedQueryRef.ts
index 5100df2b..1681435a 100644
--- a/packages/client-react-streaming/src/transportedQueryRef.ts
+++ b/packages/client-react-streaming/src/transportedQueryRef.ts
@@ -50,8 +50,8 @@ export interface InternalTransportedQueryRef<
export function createTransportedQueryRef(
options: TransportedQueryRefOptions,
- queryKey: string,
- _promise: Promise
+ queryKey: string
+ // _promise: Promise
): InternalTransportedQueryRef {
const ref: InternalTransportedQueryRef = {
__transportedQueryRef: true,
diff --git a/yarn.lock b/yarn.lock
index 2389574c..8c145f8a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -45,7 +45,7 @@ __metadata:
languageName: node
linkType: hard
-"@apollo/client-react-streaming@workspace:*, @apollo/client-react-streaming@workspace:packages/client-react-streaming":
+"@apollo/client-react-streaming@workspace:*, @apollo/client-react-streaming@workspace:^, @apollo/client-react-streaming@workspace:packages/client-react-streaming":
version: 0.0.0-use.local
resolution: "@apollo/client-react-streaming@workspace:packages/client-react-streaming"
dependencies:
@@ -86,9 +86,9 @@ __metadata:
languageName: unknown
linkType: soft
-"@apollo/client@npm:3.10.4, @apollo/client@npm:^3.10.4":
- version: 3.10.4
- resolution: "@apollo/client@npm:3.10.4"
+"@apollo/client@https://pkg.pr.new/@apollo/client@12066":
+ version: 3.11.8
+ resolution: "@apollo/client@https://pkg.pr.new/@apollo/client@12066"
dependencies:
"@graphql-typed-document-node/core": "npm:^3.1.1"
"@wry/caches": "npm:^1.0.0"
@@ -107,8 +107,8 @@ __metadata:
peerDependencies:
graphql: ^15.0.0 || ^16.0.0
graphql-ws: ^5.5.5
- react: ^16.8.0 || ^17.0.0 || ^18.0.0
- react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0
subscriptions-transport-ws: ^0.9.0 || ^0.11.0
peerDependenciesMeta:
graphql-ws:
@@ -119,7 +119,7 @@ __metadata:
optional: true
subscriptions-transport-ws:
optional: true
- checksum: 10/8db77625bb96f3330187a6b45c9792edf338c42d4e48ed66f6b0ce38c7cea503db9a5de27f9987b7d83306201a57f90e8ef7ebc06c8a6899aaadb8a090b175cb
+ checksum: 10/07390b7bb044f64f5228844a8e5cfa1aa9c13c419afc7d0f32b4d20831a995c0d0c2a044e59211923a33f6407e8131a811587a18fb4adb20137cd134b8e8789e
languageName: node
linkType: hard
@@ -10093,7 +10093,8 @@ __metadata:
version: 0.0.0-use.local
resolution: "hack-the-supergraph-ssr@workspace:examples/hack-the-supergraph-ssr"
dependencies:
- "@apollo/client": "npm:3.10.4"
+ "@apollo/client": "https://pkg.pr.new/@apollo/client@12066"
+ "@apollo/client-react-streaming": "workspace:^"
"@apollo/experimental-nextjs-app-support": "workspace:^"
"@apollo/space-kit": "npm:^9.11.0"
"@chakra-ui/next-js": "npm:^2.1.2"