From cded2f30bc0ca10e106c5c510d255b897b93fe85 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 5 Jul 2024 10:25:00 +0200 Subject: [PATCH 1/6] syntax adjustment for compiler --- src/react/hooks/useSubscription.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/react/hooks/useSubscription.ts b/src/react/hooks/useSubscription.ts index 3b0d7f4303d..b5d38995b52 100644 --- a/src/react/hooks/useSubscription.ts +++ b/src/react/hooks/useSubscription.ts @@ -295,12 +295,14 @@ function createSubscription< new Observable>((observer) => { // lazily start the subscription when the first observer subscribes // to get around strict mode - observable ||= client.subscribe({ + if (!observable) { + observable = client.subscribe({ query, variables, fetchPolicy, context, }); + } const sub = observable.subscribe(observer); return () => sub.unsubscribe(); }), From 834bd56773b0a2dd01afadb9b79807ead38ad067 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 5 Jul 2024 11:49:54 +0200 Subject: [PATCH 2/6] Add `restart` function to `useSubscription`. --- .api-reports/api-report-react.api.md | 8 +++- .api-reports/api-report-react_hooks.api.md | 8 +++- .api-reports/api-report.api.md | 8 +++- .changeset/clever-bikes-admire.md | 5 +++ .size-limits.json | 4 +- src/react/hooks/useSubscription.ts | 44 ++++++++++++++-------- 6 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 .changeset/clever-bikes-admire.md diff --git a/.api-reports/api-report-react.api.md b/.api-reports/api-report-react.api.md index 74721b26f76..33cb62a8dac 100644 --- a/.api-reports/api-report-react.api.md +++ b/.api-reports/api-report-react.api.md @@ -2183,7 +2183,13 @@ export interface UseReadQueryResult { } // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): { + restart(): void; + loading: boolean; + data?: TData | undefined; + error?: ApolloError; + variables?: TVariables | undefined; +}; // @public (undocumented) export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; diff --git a/.api-reports/api-report-react_hooks.api.md b/.api-reports/api-report-react_hooks.api.md index 9a8d8096055..6a0e16eb1c9 100644 --- a/.api-reports/api-report-react_hooks.api.md +++ b/.api-reports/api-report-react_hooks.api.md @@ -2016,7 +2016,13 @@ export interface UseReadQueryResult { // Warning: (ae-forgotten-export) The symbol "SubscriptionHookOptions" needs to be exported by the entry point index.d.ts // // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): { + restart(): void; + loading: boolean; + data?: TData | undefined; + error?: ApolloError; + variables?: TVariables | undefined; +}; // Warning: (ae-forgotten-export) The symbol "SuspenseQueryHookOptions" needs to be exported by the entry point index.d.ts // diff --git a/.api-reports/api-report.api.md b/.api-reports/api-report.api.md index 95cc0a4694b..6cfd75b3f97 100644 --- a/.api-reports/api-report.api.md +++ b/.api-reports/api-report.api.md @@ -2844,7 +2844,13 @@ export interface UseReadQueryResult { } // @public -export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): SubscriptionResult; +export function useSubscription(subscription: DocumentNode | TypedDocumentNode, options?: SubscriptionHookOptions, NoInfer_2>): { + restart(): void; + loading: boolean; + data?: TData | undefined; + error?: ApolloError; + variables?: TVariables | undefined; +}; // @public (undocumented) export function useSuspenseQuery, "variables">>(query: DocumentNode | TypedDocumentNode, options?: SuspenseQueryHookOptions, NoInfer_2> & TOptions): UseSuspenseQueryResult | undefined : TData | undefined : TOptions["returnPartialData"] extends true ? TOptions["skip"] extends boolean ? DeepPartial | undefined : DeepPartial : TOptions["skip"] extends boolean ? TData | undefined : TData, TVariables>; diff --git a/.changeset/clever-bikes-admire.md b/.changeset/clever-bikes-admire.md new file mode 100644 index 00000000000..36b9ba5de3a --- /dev/null +++ b/.changeset/clever-bikes-admire.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Add `restart` function to `useSubscription`. diff --git a/.size-limits.json b/.size-limits.json index 79e71c06997..157a94d3863 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39825, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32851 + "dist/apollo-client.min.cjs": 39865, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32852 } diff --git a/src/react/hooks/useSubscription.ts b/src/react/hooks/useSubscription.ts index b5d38995b52..b1f3a9a733b 100644 --- a/src/react/hooks/useSubscription.ts +++ b/src/react/hooks/useSubscription.ts @@ -21,6 +21,7 @@ import { Observable } from "../../core/index.js"; import { useApolloClient } from "./useApolloClient.js"; import { useDeepMemo } from "./internal/useDeepMemo.js"; import { useSyncExternalStore } from "./useSyncExternalStore.js"; +import { useIsomorphicLayoutEffect } from "./internal/useIsomorphicLayoutEffect.js"; /** * > Refer to the [Subscriptions](https://www.apollographql.com/docs/react/data/subscriptions/) section for a more in-depth overview of `useSubscription`. @@ -146,6 +147,14 @@ export function useSubscription< ) ); + const recreate = () => + createSubscription(client, subscription, variables, fetchPolicy, context); + + const recreateRef = React.useRef(recreate); + useIsomorphicLayoutEffect(() => { + recreateRef.current = recreate; + }); + if (skip) { if (observable) { setObservable((observable = null)); @@ -160,15 +169,7 @@ export function useSubscription< !!shouldResubscribe(options!) : shouldResubscribe) !== false) ) { - setObservable( - (observable = createSubscription( - client, - subscription, - variables, - fetchPolicy, - context - )) - ); + setObservable((observable = recreate())); } const optionsRef = React.useRef(options); @@ -186,7 +187,7 @@ export function useSubscription< [skip, variables] ); - return useSyncExternalStore>( + const ret = useSyncExternalStore>( React.useCallback( (update) => { if (!observable) { @@ -262,6 +263,19 @@ export function useSubscription< ), () => (observable && !skip ? observable.__.result : fallbackResult) ); + return React.useMemo( + () => ({ + ...ret, + restart() { + invariant( + !optionsRef.current.skip, + "A subscription that is skipped cannot be restarted." + ); + setObservable(recreateRef.current()); + }, + }), + [ret] + ); } function createSubscription< @@ -297,11 +311,11 @@ function createSubscription< // to get around strict mode if (!observable) { observable = client.subscribe({ - query, - variables, - fetchPolicy, - context, - }); + query, + variables, + fetchPolicy, + context, + }); } const sub = observable.subscribe(observer); return () => sub.unsubscribe(); From 2ba59a4a204b618f414b9214b41adc7b6b17753a Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 5 Jul 2024 14:44:30 +0200 Subject: [PATCH 3/6] add tests --- .../hooks/__tests__/useSubscription.test.tsx | 337 +++++++++++++++++- 1 file changed, 335 insertions(+), 2 deletions(-) diff --git a/src/react/hooks/__tests__/useSubscription.test.tsx b/src/react/hooks/__tests__/useSubscription.test.tsx index decdd17b973..c78a0ae98a8 100644 --- a/src/react/hooks/__tests__/useSubscription.test.tsx +++ b/src/react/hooks/__tests__/useSubscription.test.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { renderHook, waitFor } from "@testing-library/react"; +import { render, renderHook, waitFor } from "@testing-library/react"; import gql from "graphql-tag"; import { @@ -14,7 +14,10 @@ import { InMemoryCache as Cache } from "../../../cache"; import { ApolloProvider } from "../../context"; import { MockSubscriptionLink } from "../../../testing"; import { useSubscription } from "../useSubscription"; -import { spyOnConsole } from "../../../testing/internal"; +import { profileHook, spyOnConsole } from "../../../testing/internal"; +import { SubscriptionHookOptions } from "../../types/types"; +import { GraphQLError } from "graphql"; +import { InvariantError } from "ts-invariant"; describe("useSubscription Hook", () => { it("should handle a simple subscription properly", async () => { @@ -1122,6 +1125,336 @@ followed by new in-flight setup", async () => { }); }); +describe("`restart` callback", () => { + function setup() { + const subscription: TypedDocumentNode< + { totalLikes: number }, + { id: string } + > = gql` + subscription ($id: ID!) { + totalLikes(postId: $id) + } + `; + const onSubscribe = jest.fn(); + const onUnsubscribe = jest.fn(); + const link = new MockSubscriptionLink(); + link.onSetup(onSubscribe); + link.onUnsubscribe(onUnsubscribe); + const client = new ApolloClient({ + link, + cache: new Cache(), + }); + const ProfiledHook = profileHook( + ( + options: SubscriptionHookOptions<{ totalLikes: number }, { id: string }> + ) => useSubscription(subscription, options) + ); + return { client, link, ProfiledHook, onSubscribe, onUnsubscribe }; + } + it("can restart a running subscription", async () => { + const { client, link, ProfiledHook, onSubscribe, onUnsubscribe } = setup(); + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + link.simulateResult({ result: { data: { totalLikes: 1 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 1 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await expect(ProfiledHook).not.toRerender({ timesout: 20 }); + expect(onUnsubscribe).toHaveBeenCalledTimes(0); + expect(onSubscribe).toHaveBeenCalledTimes(1); + + ProfiledHook.getCurrentSnapshot().restart(); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await waitFor(() => expect(onUnsubscribe).toHaveBeenCalledTimes(1)); + expect(onSubscribe).toHaveBeenCalledTimes(2); + + link.simulateResult({ result: { data: { totalLikes: 2 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 2 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + }); + it("will use the most recently passed in options", async () => { + const { client, link, ProfiledHook, onSubscribe, onUnsubscribe } = setup(); + const { rerender } = render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + // deliberately keeping a reference to a very old `restart` function + // to show that the most recent options are used even with that + const restart = ProfiledHook.getCurrentSnapshot().restart; + link.simulateResult({ result: { data: { totalLikes: 1 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 1 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await expect(ProfiledHook).not.toRerender({ timesout: 20 }); + expect(onUnsubscribe).toHaveBeenCalledTimes(0); + expect(onSubscribe).toHaveBeenCalledTimes(1); + + rerender(); + await waitFor(() => expect(onUnsubscribe).toHaveBeenCalledTimes(1)); + expect(onSubscribe).toHaveBeenCalledTimes(2); + expect(link.operation?.variables).toStrictEqual({ id: "2" }); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "2" }, + }); + } + link.simulateResult({ result: { data: { totalLikes: 1000 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 1000 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "2" }, + }); + } + + expect(onUnsubscribe).toHaveBeenCalledTimes(1); + expect(onSubscribe).toHaveBeenCalledTimes(2); + expect(link.operation?.variables).toStrictEqual({ id: "2" }); + + restart(); + + await waitFor(() => expect(onUnsubscribe).toHaveBeenCalledTimes(2)); + expect(onSubscribe).toHaveBeenCalledTimes(3); + expect(link.operation?.variables).toStrictEqual({ id: "2" }); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "2" }, + }); + } + link.simulateResult({ result: { data: { totalLikes: 1005 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 1005 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "2" }, + }); + } + }); + it("can restart a subscription that has completed", async () => { + const { client, link, ProfiledHook, onSubscribe, onUnsubscribe } = setup(); + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + link.simulateResult({ result: { data: { totalLikes: 1 } } }, true); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 1 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await expect(ProfiledHook).not.toRerender({ timesout: 20 }); + expect(onUnsubscribe).toHaveBeenCalledTimes(1); + expect(onSubscribe).toHaveBeenCalledTimes(1); + + ProfiledHook.getCurrentSnapshot().restart(); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + expect(onUnsubscribe).toHaveBeenCalledTimes(1); + expect(onSubscribe).toHaveBeenCalledTimes(2); + + link.simulateResult({ result: { data: { totalLikes: 2 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 2 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + }); + it("can restart a subscription that has errored", async () => { + const { client, link, ProfiledHook, onSubscribe, onUnsubscribe } = setup(); + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + const error = new GraphQLError("error"); + link.simulateResult({ + result: { errors: [error] }, + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: undefined, + error: new ApolloError({ graphQLErrors: [error] }), + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + await expect(ProfiledHook).not.toRerender({ timesout: 20 }); + expect(onUnsubscribe).toHaveBeenCalledTimes(1); + expect(onSubscribe).toHaveBeenCalledTimes(1); + + ProfiledHook.getCurrentSnapshot().restart(); + + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: true, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + expect(onUnsubscribe).toHaveBeenCalledTimes(1); + expect(onSubscribe).toHaveBeenCalledTimes(2); + + link.simulateResult({ result: { data: { totalLikes: 2 } } }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: { totalLikes: 2 }, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + }); + it("will not restart a subscription that has been `skip`ped", async () => { + const { client, ProfiledHook, onSubscribe, onUnsubscribe } = setup(); + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + { + const snapshot = await ProfiledHook.takeSnapshot(); + expect(snapshot).toStrictEqual({ + loading: false, + data: undefined, + error: undefined, + restart: expect.any(Function), + variables: { id: "1" }, + }); + } + expect(onUnsubscribe).toHaveBeenCalledTimes(0); + expect(onSubscribe).toHaveBeenCalledTimes(0); + + expect(() => ProfiledHook.getCurrentSnapshot().restart()).toThrow( + new InvariantError("A subscription that is skipped cannot be restarted.") + ); + + await expect(ProfiledHook).not.toRerender({ timesout: 20 }); + expect(onUnsubscribe).toHaveBeenCalledTimes(0); + expect(onSubscribe).toHaveBeenCalledTimes(0); + }); +}); + describe.skip("Type Tests", () => { test("NoInfer prevents adding arbitrary additional variables", () => { const typedNode = {} as TypedDocumentNode<{ foo: string }, { bar: number }>; From afaa8f6ab25b1367c29479c36f221e7883b40145 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 5 Jul 2024 14:56:44 +0200 Subject: [PATCH 4/6] adjust test timing to accomodate for React 17 --- src/react/hooks/__tests__/useSubscription.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/react/hooks/__tests__/useSubscription.test.tsx b/src/react/hooks/__tests__/useSubscription.test.tsx index c78a0ae98a8..e9ffd0a82b3 100644 --- a/src/react/hooks/__tests__/useSubscription.test.tsx +++ b/src/react/hooks/__tests__/useSubscription.test.tsx @@ -1348,8 +1348,8 @@ describe("`restart` callback", () => { variables: { id: "1" }, }); } + await waitFor(() => expect(onSubscribe).toHaveBeenCalledTimes(2)); expect(onUnsubscribe).toHaveBeenCalledTimes(1); - expect(onSubscribe).toHaveBeenCalledTimes(2); link.simulateResult({ result: { data: { totalLikes: 2 } } }); { @@ -1410,8 +1410,8 @@ describe("`restart` callback", () => { variables: { id: "1" }, }); } + await waitFor(() => expect(onSubscribe).toHaveBeenCalledTimes(2)); expect(onUnsubscribe).toHaveBeenCalledTimes(1); - expect(onSubscribe).toHaveBeenCalledTimes(2); link.simulateResult({ result: { data: { totalLikes: 2 } } }); { From afc67b78768948fdec47e6fd954ceccd945ea32d Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Mon, 8 Jul 2024 11:29:18 +0200 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Jerel Miller --- src/react/hooks/__tests__/useSubscription.test.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/react/hooks/__tests__/useSubscription.test.tsx b/src/react/hooks/__tests__/useSubscription.test.tsx index e9ffd0a82b3..e955ae1e00c 100644 --- a/src/react/hooks/__tests__/useSubscription.test.tsx +++ b/src/react/hooks/__tests__/useSubscription.test.tsx @@ -1179,7 +1179,7 @@ describe("`restart` callback", () => { variables: { id: "1" }, }); } - await expect(ProfiledHook).not.toRerender({ timesout: 20 }); + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); expect(onUnsubscribe).toHaveBeenCalledTimes(0); expect(onSubscribe).toHaveBeenCalledTimes(1); @@ -1241,7 +1241,7 @@ describe("`restart` callback", () => { variables: { id: "1" }, }); } - await expect(ProfiledHook).not.toRerender({ timesout: 20 }); + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); expect(onUnsubscribe).toHaveBeenCalledTimes(0); expect(onSubscribe).toHaveBeenCalledTimes(1); @@ -1332,7 +1332,7 @@ describe("`restart` callback", () => { variables: { id: "1" }, }); } - await expect(ProfiledHook).not.toRerender({ timesout: 20 }); + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); expect(onUnsubscribe).toHaveBeenCalledTimes(1); expect(onSubscribe).toHaveBeenCalledTimes(1); @@ -1394,7 +1394,7 @@ describe("`restart` callback", () => { variables: { id: "1" }, }); } - await expect(ProfiledHook).not.toRerender({ timesout: 20 }); + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); expect(onUnsubscribe).toHaveBeenCalledTimes(1); expect(onSubscribe).toHaveBeenCalledTimes(1); @@ -1449,7 +1449,7 @@ describe("`restart` callback", () => { new InvariantError("A subscription that is skipped cannot be restarted.") ); - await expect(ProfiledHook).not.toRerender({ timesout: 20 }); + await expect(ProfiledHook).not.toRerender({ timeout: 20 }); expect(onUnsubscribe).toHaveBeenCalledTimes(0); expect(onSubscribe).toHaveBeenCalledTimes(0); }); From 2224ff3fccfab3e66cc01a821ae7e1981a133938 Mon Sep 17 00:00:00 2001 From: phryneas Date: Mon, 8 Jul 2024 09:31:58 +0000 Subject: [PATCH 6/6] Clean up Prettier, Size-limit, and Api-Extractor --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index c9a1233d358..4e756f84c34 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39924, + "dist/apollo-client.min.cjs": 39971, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32903 }