├── .api-reports ├── api-report-cache.api.md ├── api-report-core.api.md ├── api-report-dev.api.md ├── api-report-errors.api.md ├── api-report-link_batch-http.api.md ├── api-report-link_batch.api.md ├── api-report-link_context.api.md ├── api-report-link_core.api.md ├── api-report-link_error.api.md ├── api-report-link_http.api.md ├── api-report-link_persisted-queries.api.md ├── api-report-link_remove-typename.api.md ├── api-report-link_retry.api.md ├── api-report-link_schema.api.md ├── api-report-link_subscriptions.api.md ├── api-report-link_utils.api.md ├── api-report-link_ws.api.md ├── api-report-masking.api.md ├── api-report-react.api.md ├── api-report-react_components.api.md ├── api-report-react_context.api.md ├── api-report-react_hoc.api.md ├── api-report-react_hooks.api.md ├── api-report-react_internal.api.md ├── api-report-react_parser.api.md ├── api-report-react_ssr.api.md ├── api-report-testing.api.md ├── api-report-testing_core.api.md ├── api-report-testing_experimental.api.md ├── api-report-utilities.api.md ├── api-report-utilities_globals.api.md ├── api-report-utilities_subscriptions_relay.api.md ├── api-report-utilities_subscriptions_urql.api.md └── api-report.api.md ├── .attw.json ├── .changeset ├── README.md └── config.json ├── .circleci └── config.yml ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── feature-request.md │ └── question-discussion.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── api-extractor.yml │ ├── arethetypeswrong.yml │ ├── change-prerelease-tag.yml │ ├── cleanup-checks.mjs │ ├── cleanup-checks.yml │ ├── close-stale-issues.yml │ ├── compare-build-output.yml │ ├── devtools-errorcodes.yml │ ├── exit-prerelease.yml │ ├── issue-close-user-survey.yml │ ├── lock.yml │ ├── prerelease.yml │ ├── publish-pr-releases.yml │ ├── release.yml │ ├── scheduled-test-canary.yml │ ├── size-limit.yml │ └── snapshot-release.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .semgrepignore ├── .size-limit.cjs ├── .size-limits.json ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── COLLABORATORS.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── ROADMAP.md ├── api-extractor.json ├── config ├── FixJSDOMEnvironment.js ├── apiExtractor.ts ├── bundlesize.ts ├── compare-build-output-to.sh ├── entryPoints.js ├── helpers.ts ├── inlineInheritDoc.ts ├── jest.config.js ├── jest │ └── react-dom-17-client.js ├── postprocessDist.ts ├── precheck.js ├── prepareChangesetsRelease.ts ├── prepareDist.js ├── processInvariants.ts ├── rewriteSourceMaps.ts ├── rollup.config.js ├── tsconfig.json └── version.js ├── docs ├── README.md ├── shared │ ├── ApiDoc │ │ ├── Context.js │ │ ├── DocBlock.js │ │ ├── EnumDetails.js │ │ ├── Function.js │ │ ├── Heading.js │ │ ├── InterfaceDetails.js │ │ ├── ParameterTable.js │ │ ├── PropertyDetails.js │ │ ├── PropertySignatureTable.js │ │ ├── ResponsiveGrid.js │ │ ├── SourceLink.js │ │ ├── Tuple.js │ │ ├── index.js │ │ └── sortWithCustomOrder.js │ ├── DisplayClientError.js │ ├── Overrides │ │ └── UseLoadableQueryResult.js │ ├── link-chain.mdx │ ├── refetchQueries-options.mdx │ ├── useBackgroundQuery-options.mdx │ ├── useBackgroundQuery-result.mdx │ ├── useFragment-options.mdx │ ├── useFragment-result.mdx │ ├── useReadQuery-result.mdx │ └── useSuspenseQuery-result.mdx └── source │ ├── _sidebar.yaml │ ├── api │ ├── cache │ │ └── InMemoryCache.mdx │ ├── core │ │ ├── ApolloClient.mdx │ │ └── ObservableQuery.mdx │ ├── link │ │ ├── apollo-link-batch-http.mdx │ │ ├── apollo-link-context.mdx │ │ ├── apollo-link-error.mdx │ │ ├── apollo-link-http.mdx │ │ ├── apollo-link-remove-typename.mdx │ │ ├── apollo-link-rest.mdx │ │ ├── apollo-link-retry.mdx │ │ ├── apollo-link-schema.mdx │ │ ├── apollo-link-subscriptions.mdx │ │ ├── apollo-link-ws.mdx │ │ ├── community-links.mdx │ │ ├── introduction.mdx │ │ └── persisted-queries.mdx │ └── react │ │ ├── components.mdx │ │ ├── hoc.mdx │ │ ├── hooks-experimental.mdx │ │ ├── hooks.mdx │ │ ├── preloading.mdx │ │ ├── ssr.mdx │ │ └── testing.mdx │ ├── assets │ ├── client-diagrams │ │ ├── 1-overview.png │ │ ├── 2-map.png │ │ ├── 3-minimize.png │ │ └── 4-normalize.png │ ├── client-mocking │ │ ├── cli-clientcheck.png │ │ ├── devtools-clientstate.png │ │ ├── vscode-autocomplete.png │ │ ├── vscode-errors.png │ │ └── vscode-typeinfo.png │ ├── client-schema.png │ ├── devtools │ │ ├── apollo-client-devtools │ │ │ └── ac-browser-devtools-3.png │ │ ├── devtools.png │ │ ├── mutation-init.png │ │ ├── mutation-result-data.png │ │ ├── mutation-result.png │ │ ├── query-init-data.png │ │ ├── query-init.png │ │ ├── query-result.png │ │ ├── vscode-panel.png │ │ └── vscode-setting.png │ └── link │ │ ├── concepts-intro-1.png │ │ ├── concepts-intro-2.png │ │ └── error-request-flow.png │ ├── caching │ ├── advanced-topics.mdx │ ├── cache-configuration.mdx │ ├── cache-field-behavior.mdx │ ├── cache-interaction.mdx │ ├── garbage-collection.mdx │ ├── memory-management.mdx │ └── overview.mdx │ ├── data │ ├── defer.mdx │ ├── directives.mdx │ ├── document-transforms.mdx │ ├── error-handling.mdx │ ├── file-uploads.mdx │ ├── fragments.mdx │ ├── mutations.mdx │ ├── operation-best-practices.mdx │ ├── queries.mdx │ ├── refetching.mdx │ ├── subscriptions.mdx │ └── suspense.mdx │ ├── development-testing │ ├── client-schema-mocking.mdx │ ├── developer-tooling.mdx │ ├── reducing-bundle-size.mdx │ ├── schema-driven-testing.mdx │ ├── static-typing.mdx │ └── testing.mdx │ ├── get-started.mdx │ ├── img │ ├── cache-inspector.jpg │ ├── client-schema.png │ ├── githunt.png │ └── spotify-playlists.jpg │ ├── index.mdx │ ├── integrations │ ├── integrations.mdx │ ├── react-native.mdx │ └── webpack.mdx │ ├── local-state │ ├── client-side-schema.mdx │ ├── local-resolvers.mdx │ ├── local-state-management.mdx │ ├── managing-state-with-field-policies.mdx │ └── reactive-variables.mdx │ ├── logo │ ├── favicon.png │ ├── icon-apollo-white-200x200.png │ ├── large.png │ ├── logo-apollo-space.svg │ └── square.png │ ├── migrating │ ├── apollo-client-3-migration.mdx │ └── hooks-migration.mdx │ ├── networking │ ├── advanced-http-networking.mdx │ ├── authentication.mdx │ └── basic-http-networking.mdx │ ├── pagination │ ├── core-api.mdx │ ├── cursor-based.mdx │ ├── key-args.mdx │ ├── offset-based.mdx │ └── overview.mdx │ ├── performance │ ├── babel.mdx │ ├── optimistic-ui.mdx │ ├── performance.mdx │ └── server-side-rendering.mdx │ └── why-apollo.mdx ├── eslint-local-rules ├── fixtures │ ├── file.ts │ ├── react.tsx │ └── tsconfig.json ├── forbid-act-in-disabled-act-environment.test.ts ├── forbid-act-in-disabled-act-environment.ts ├── index.js ├── package.json ├── require-disable-act-environment.test.ts ├── require-disable-act-environment.ts ├── require-using-disposable.test.ts ├── require-using-disposable.ts ├── testSetup.ts └── tsconfig.json ├── eslint.config.mjs ├── integration-tests ├── .gitignore ├── api.har ├── browser-esm │ ├── html │ │ ├── jsdeliver-esm.html │ │ ├── jspm-prepared.html │ │ └── unpkg-unmangled.html │ ├── package.json │ ├── playwright.config.ts │ └── tests │ │ └── playwright │ │ ├── jsdeliver-esm.test.ts │ │ ├── jspm-prepared.test.ts │ │ └── unpkg-unmangled.test.ts ├── cra4 │ ├── .env │ ├── package.json │ ├── playwright.config.ts │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ └── index.tsx │ ├── tests │ │ └── playwright │ │ │ └── apollo-client.test.ts │ └── tsconfig.json ├── cra5 │ ├── package.json │ ├── playwright.config.ts │ ├── public │ │ └── index.html │ ├── src │ │ ├── App.tsx │ │ └── index.tsx │ ├── tests │ │ └── playwright │ │ │ └── apollo-client.test.ts │ └── tsconfig.json ├── empty.har ├── next │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── playwright.config.ts │ ├── src │ │ ├── app │ │ │ ├── cc │ │ │ │ ├── ApolloWrapper.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ │ ├── client.ts │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── libs │ │ │ ├── apolloClient.ts │ │ │ └── schemaLink.ts │ │ └── pages │ │ │ ├── _app.tsx │ │ │ ├── pages-no-ssr.tsx │ │ │ └── pages.tsx │ ├── tests │ │ └── playwright │ │ │ └── apollo-client.test.ts │ └── tsconfig.json ├── node-esm │ ├── package.json │ ├── test-cjs.cjs │ └── test-esm.mjs ├── node-standard │ ├── package.json │ ├── test-cjs.js │ └── test-esm.mjs ├── package-lock.json ├── package.json ├── peerdeps-tsc │ ├── .gitignore │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── shared │ ├── fixture.ts │ ├── package.json │ └── playwright.config.ts ├── vite-swc │ ├── index.html │ ├── package.json │ ├── playwright.config.ts │ ├── src │ │ ├── App.tsx │ │ └── main.tsx │ ├── tests │ │ └── playwright │ │ │ └── apollo-client.test.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts └── vite │ ├── index.html │ ├── package.json │ ├── playwright.config.ts │ ├── src │ ├── App.tsx │ └── main.tsx │ ├── tests │ └── playwright │ │ └── apollo-client.test.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── package-lock.json ├── package.json ├── patches ├── @testing-library+react+16.1.0.patch ├── eslint-plugin-testing-library+7.1.1.patch ├── pretty-format+29.7.0.patch └── react-dom-17+17.0.2.patch ├── renovate.json ├── scripts ├── codemods │ ├── ac2-to-ac3 │ │ ├── README.md │ │ ├── examples │ │ │ ├── client-and-cache.ts │ │ │ ├── link-packages.js │ │ │ └── react-packages.tsx │ │ ├── imports.js │ │ └── package.json │ ├── data-masking │ │ ├── examples │ │ │ ├── queries-codegen.ts │ │ │ ├── queries-react.tsx │ │ │ ├── queries.graphql │ │ │ └── queries.ts │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── unmask.ts │ └── misc │ │ └── mockLinkRejection.ts └── memory │ ├── README.md │ ├── package.json │ └── tests.js ├── src ├── __tests__ │ ├── ApolloClient.ts │ ├── __snapshots__ │ │ ├── ApolloClient.ts.snap │ │ ├── client.ts.snap │ │ ├── exports.ts.snap │ │ └── mutationResults.ts.snap │ ├── client.ts │ ├── dataMasking.ts │ ├── exports.ts │ ├── fetchMore.ts │ ├── graphqlSubscriptions.ts │ ├── local-state │ │ ├── __snapshots__ │ │ │ ├── export.ts.snap │ │ │ └── general.ts.snap │ │ ├── export.ts │ │ ├── general.ts │ │ ├── resolvers.ts │ │ └── subscriptions.ts │ ├── mutationResults.ts │ ├── optimistic.ts │ ├── refetchQueries.ts │ ├── resultCacheCleaning.ts │ └── subscribeToMore.ts ├── cache │ ├── core │ │ ├── __tests__ │ │ │ └── cache.ts │ │ ├── cache.ts │ │ └── types │ │ │ ├── Cache.ts │ │ │ ├── DataProxy.ts │ │ │ └── common.ts │ ├── index.ts │ └── inmemory │ │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ ├── cache.ts.snap │ │ │ ├── entityStore.ts.snap │ │ │ ├── fragmentMatcher.ts.snap │ │ │ ├── policies.ts.snap │ │ │ ├── readFromStore.ts.snap │ │ │ ├── roundtrip.ts.snap │ │ │ └── writeToStore.ts.snap │ │ ├── cache.ts │ │ ├── diffAgainstStore.ts │ │ ├── entityStore.ts │ │ ├── fragmentMatcher.ts │ │ ├── fragmentRegistry.ts │ │ ├── helpers.ts │ │ ├── key-extractor.ts │ │ ├── object-canon.ts │ │ ├── optimistic.ts │ │ ├── policies.ts │ │ ├── readFromStore.ts │ │ ├── recordingCache.ts │ │ ├── roundtrip.ts │ │ └── writeToStore.ts │ │ ├── entityStore.ts │ │ ├── fixPolyfills.native.ts │ │ ├── fixPolyfills.ts │ │ ├── fragmentRegistry.ts │ │ ├── helpers.ts │ │ ├── inMemoryCache.ts │ │ ├── key-extractor.ts │ │ ├── object-canon.ts │ │ ├── policies.ts │ │ ├── reactiveVars.ts │ │ ├── readFromStore.ts │ │ ├── types.ts │ │ └── writeToStore.ts ├── config │ └── jest │ │ ├── areApolloErrorsEqual.ts │ │ ├── areGraphQlErrorsEqual.ts │ │ └── setup.ts ├── core │ ├── ApolloClient.ts │ ├── LocalState.ts │ ├── ObservableQuery.ts │ ├── QueryInfo.ts │ ├── QueryManager.ts │ ├── __tests__ │ │ ├── ApolloClient │ │ │ ├── general.test.ts │ │ │ ├── links.test.ts │ │ │ └── multiple-results.test.ts │ │ ├── LocalState.ts │ │ ├── ObservableQuery.ts │ │ ├── equalByQuery.ts │ │ └── fetchPolicies.ts │ ├── equalByQuery.ts │ ├── index.ts │ ├── networkStatus.ts │ ├── types.ts │ └── watchQueryOptions.ts ├── dev │ ├── index.ts │ ├── loadDevMessages.ts │ ├── loadErrorMessageHandler.ts │ ├── loadErrorMessages.ts │ └── setErrorMessageHandler.ts ├── errors │ ├── __tests__ │ │ └── ApolloError.ts │ └── index.ts ├── index.ts ├── invariantErrorCodes.ts ├── link │ ├── batch-http │ │ ├── __tests__ │ │ │ └── batchHttpLink.ts │ │ ├── batchHttpLink.ts │ │ └── index.ts │ ├── batch │ │ ├── __tests__ │ │ │ └── batchLink.ts │ │ ├── batchLink.ts │ │ ├── batching.ts │ │ └── index.ts │ ├── context │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── core │ │ ├── ApolloLink.ts │ │ ├── __tests__ │ │ │ └── ApolloLink.ts │ │ ├── concat.ts │ │ ├── empty.ts │ │ ├── execute.ts │ │ ├── from.ts │ │ ├── index.ts │ │ ├── split.ts │ │ └── types.ts │ ├── error │ │ ├── __tests__ │ │ │ └── index.ts │ │ └── index.ts │ ├── http │ │ ├── HttpLink.ts │ │ ├── __tests__ │ │ │ ├── HttpLink.ts │ │ │ ├── checkFetcher.ts │ │ │ ├── headerNormalization.ts │ │ │ ├── helpers.ts │ │ │ ├── parseAndCheckHttpResponse.ts │ │ │ ├── responseIterator.ts │ │ │ ├── responseIteratorNoAsyncIterator.ts │ │ │ ├── selectHttpOptionsAndBody.ts │ │ │ ├── selectURI.ts │ │ │ └── serializeFetchParameter.ts │ │ ├── checkFetcher.ts │ │ ├── createHttpLink.ts │ │ ├── createSignalIfSupported.ts │ │ ├── index.ts │ │ ├── iterators │ │ │ ├── LICENSE │ │ │ ├── async.ts │ │ │ ├── nodeStream.ts │ │ │ ├── promise.ts │ │ │ └── reader.ts │ │ ├── parseAndCheckHttpResponse.ts │ │ ├── responseIterator.ts │ │ ├── rewriteURIForGET.ts │ │ ├── selectHttpOptionsAndBody.ts │ │ ├── selectURI.ts │ │ └── serializeFetchParameter.ts │ ├── persisted-queries │ │ ├── __tests__ │ │ │ ├── persisted-queries.test.ts │ │ │ └── react.test.tsx │ │ └── index.ts │ ├── remove-typename │ │ ├── __tests__ │ │ │ └── removeTypenameFromVariables.ts │ │ ├── index.ts │ │ └── removeTypenameFromVariables.ts │ ├── retry │ │ ├── __tests__ │ │ │ ├── delayFunction.ts │ │ │ ├── retryFunction.ts │ │ │ └── retryLink.ts │ │ ├── delayFunction.ts │ │ ├── index.ts │ │ ├── retryFunction.ts │ │ └── retryLink.ts │ ├── schema │ │ ├── __tests__ │ │ │ └── schemaLink.ts │ │ └── index.ts │ ├── subscriptions │ │ ├── __tests__ │ │ │ └── graphqlWsLink.ts │ │ └── index.ts │ ├── utils │ │ ├── __tests__ │ │ │ ├── filterOperationVariables.ts │ │ │ ├── fromError.ts │ │ │ ├── fromPromise.ts │ │ │ ├── toPromise.ts │ │ │ └── validateOperation.ts │ │ ├── createOperation.ts │ │ ├── filterOperationVariables.ts │ │ ├── fromError.ts │ │ ├── fromPromise.ts │ │ ├── index.ts │ │ ├── throwServerError.ts │ │ ├── toPromise.ts │ │ ├── transformOperation.ts │ │ └── validateOperation.ts │ └── ws │ │ ├── __tests__ │ │ └── webSocketLink.ts │ │ └── index.ts ├── masking │ ├── __benches__ │ │ └── types.bench.ts │ ├── __tests__ │ │ ├── maskFragment.test.ts │ │ └── maskOperation.test.ts │ ├── index.ts │ ├── internal │ │ └── types.ts │ ├── maskDefinition.ts │ ├── maskFragment.ts │ ├── maskOperation.ts │ ├── types.ts │ └── utils.ts ├── react │ ├── components │ │ ├── Mutation.tsx │ │ ├── Query.tsx │ │ ├── Subscription.tsx │ │ ├── __tests__ │ │ │ ├── client │ │ │ │ ├── Mutation.test.tsx │ │ │ │ ├── Query.test.tsx │ │ │ │ └── Subscription.test.tsx │ │ │ └── ssr │ │ │ │ ├── getDataFromTree.test.tsx │ │ │ │ └── server.test.tsx │ │ ├── index.ts │ │ └── types.ts │ ├── context │ │ ├── ApolloConsumer.tsx │ │ ├── ApolloContext.ts │ │ ├── ApolloProvider.tsx │ │ ├── __tests__ │ │ │ ├── ApolloConsumer.test.tsx │ │ │ └── ApolloProvider.test.tsx │ │ └── index.ts │ ├── hoc │ │ ├── __tests__ │ │ │ ├── client-option.test.tsx │ │ │ ├── fragments.test.tsx │ │ │ ├── mutations │ │ │ │ ├── index.test.tsx │ │ │ │ ├── lifecycle.test.tsx │ │ │ │ ├── queries.test.tsx │ │ │ │ └── recycled-queries.test.tsx │ │ │ ├── queries │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── lifecycle.test.tsx.snap │ │ │ │ ├── api.test.tsx │ │ │ │ ├── errors.test.tsx │ │ │ │ ├── index.test.tsx │ │ │ │ ├── lifecycle.test.tsx │ │ │ │ ├── loading.test.tsx │ │ │ │ ├── observableQuery.test.tsx │ │ │ │ ├── polling.test.tsx │ │ │ │ ├── recomposeWithState.tsx │ │ │ │ ├── reducer.test.tsx │ │ │ │ ├── skip.test.tsx │ │ │ │ └── updateQuery.test.tsx │ │ │ ├── shared-operations.test.tsx │ │ │ ├── ssr │ │ │ │ ├── getDataFromTree.test.tsx │ │ │ │ └── server.test.tsx │ │ │ ├── statics.test.tsx │ │ │ └── subscriptions │ │ │ │ └── subscriptions.test.tsx │ │ ├── graphql.tsx │ │ ├── hoc-utils.tsx │ │ ├── index.ts │ │ ├── mutation-hoc.tsx │ │ ├── query-hoc.tsx │ │ ├── subscription-hoc.tsx │ │ ├── types.ts │ │ └── withApollo.tsx │ ├── hooks │ │ ├── __tests__ │ │ │ ├── useApolloClient.test.tsx │ │ │ ├── useBackgroundQuery.test.tsx │ │ │ ├── useFragment.test.tsx │ │ │ ├── useLazyQuery.test.tsx │ │ │ ├── useLoadableQuery.test.tsx │ │ │ ├── useMutation.test.tsx │ │ │ ├── useQuery.test.tsx │ │ │ ├── useQueryRefHandlers.test.tsx │ │ │ ├── useReactiveVar.test.tsx │ │ │ ├── useSubscription.test.tsx │ │ │ ├── useSuspenseFragment.test.tsx │ │ │ └── useSuspenseQuery.test.tsx │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── internal │ │ │ ├── __tests__ │ │ │ │ ├── useDeepMemo.test.ts │ │ │ │ └── useRenderGuard.test.tsx │ │ │ ├── __use.ts │ │ │ ├── index.ts │ │ │ ├── useDeepMemo.ts │ │ │ ├── useIsomorphicLayoutEffect.ts │ │ │ ├── useRenderGuard.ts │ │ │ └── wrapHook.ts │ │ ├── useApolloClient.ts │ │ ├── useBackgroundQuery.ts │ │ ├── useFragment.ts │ │ ├── useLazyQuery.ts │ │ ├── useLoadableQuery.ts │ │ ├── useMutation.ts │ │ ├── useQuery.ts │ │ ├── useQueryRefHandlers.ts │ │ ├── useReactiveVar.ts │ │ ├── useReadQuery.ts │ │ ├── useSubscription.ts │ │ ├── useSuspenseFragment.ts │ │ ├── useSuspenseQuery.ts │ │ └── useSyncExternalStore.ts │ ├── index.ts │ ├── internal │ │ ├── cache │ │ │ ├── FragmentReference.ts │ │ │ ├── QueryReference.ts │ │ │ ├── SuspenseCache.ts │ │ │ ├── __tests__ │ │ │ │ └── QueryReference.test.tsx │ │ │ ├── getSuspenseCache.ts │ │ │ └── types.ts │ │ └── index.ts │ ├── parser │ │ ├── __tests__ │ │ │ └── parser.test.ts │ │ └── index.ts │ ├── query-preloader │ │ ├── __tests__ │ │ │ └── createQueryPreloader.test.tsx │ │ └── createQueryPreloader.ts │ ├── ssr │ │ ├── RenderPromises.ts │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── useQuery.test.tsx.snap │ │ │ ├── useLazyQuery.test.tsx │ │ │ ├── useQuery.test.tsx │ │ │ └── useReactiveVar.test.tsx │ │ ├── getDataFromTree.ts │ │ ├── index.ts │ │ └── renderToStringWithData.ts │ └── types │ │ ├── types.documentation.ts │ │ └── types.ts ├── testing │ ├── core │ │ ├── index.ts │ │ ├── itAsync.ts │ │ ├── mocking │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── mockLink.ts.snap │ │ │ │ └── mockLink.ts │ │ │ ├── mockClient.ts │ │ │ ├── mockFetch.ts │ │ │ ├── mockLink.ts │ │ │ └── mockSubscriptionLink.ts │ │ ├── subscribeAndCount.ts │ │ ├── wait.ts │ │ ├── withConsoleSpy.ts │ │ └── wrap.ts │ ├── experimental │ │ ├── __tests__ │ │ │ └── createTestSchema.test.tsx │ │ ├── createSchemaFetch.ts │ │ ├── createTestSchema.ts │ │ ├── graphql-tools │ │ │ ├── LICENSE │ │ │ ├── utils.test.ts │ │ │ └── utils.ts │ │ └── index.ts │ ├── index.ts │ ├── internal │ │ ├── ObservableStream.ts │ │ ├── __tests__ │ │ │ └── ObservableStream.test.ts │ │ ├── disposables │ │ │ ├── __tests__ │ │ │ │ ├── spyOnConsole.test.ts │ │ │ │ └── withCleanup.test.ts │ │ │ ├── enableFakeTimers.ts │ │ │ ├── index.ts │ │ │ ├── spyOnConsole.ts │ │ │ ├── withCleanup.ts │ │ │ └── withProdMode.ts │ │ ├── incremental.ts │ │ ├── index.ts │ │ ├── renderHelpers.tsx │ │ ├── rtl │ │ │ ├── actAsync.ts │ │ │ ├── renderAsync.ts │ │ │ └── renderHookAsync.tsx │ │ └── scenarios │ │ │ └── index.ts │ ├── matchers │ │ ├── index.d.ts │ │ ├── index.ts │ │ ├── toBeDisposed.ts │ │ ├── toBeGarbageCollected.ts │ │ ├── toComplete.ts │ │ ├── toEmitAnything.ts │ │ ├── toEmitApolloQueryResult.ts │ │ ├── toEmitError.ts │ │ ├── toEmitFetchResult.ts │ │ ├── toEmitMatchedValue.ts │ │ ├── toEmitNext.ts │ │ ├── toEmitValue.ts │ │ ├── toEmitValueStrict.ts │ │ ├── toEqualApolloQueryResult.ts │ │ ├── toEqualFetchResult.ts │ │ ├── toEqualQueryResult.ts │ │ ├── toHaveSuspenseCacheEntryUsing.ts │ │ └── toMatchDocument.ts │ └── react │ │ ├── MockedProvider.tsx │ │ └── __tests__ │ │ ├── MockedProvider.test.tsx │ │ ├── __snapshots__ │ │ └── MockedProvider.test.tsx.snap │ │ └── mockSubscriptionLink.test.tsx ├── tsconfig.json ├── utilities │ ├── caching │ │ ├── __tests__ │ │ │ ├── getMemoryInternals.ts │ │ │ └── sizes.test.ts │ │ ├── caches.ts │ │ ├── getMemoryInternals.ts │ │ ├── index.ts │ │ └── sizes.ts │ ├── common │ │ ├── __tests__ │ │ │ ├── canUse.ts │ │ │ ├── canonicalStringify.ts │ │ │ ├── cloneDeep.ts │ │ │ ├── compact.ts │ │ │ ├── maybeDeepFeeze.ts │ │ │ ├── mergeDeep.ts │ │ │ ├── omitDeep.ts │ │ │ └── stripTypename.ts │ │ ├── arrays.ts │ │ ├── canUse.ts │ │ ├── canonicalStringify.ts │ │ ├── cloneDeep.ts │ │ ├── compact.ts │ │ ├── errorHandling.ts │ │ ├── incrementalResult.ts │ │ ├── makeUniqueId.ts │ │ ├── maybeDeepFreeze.ts │ │ ├── mergeDeep.ts │ │ ├── mergeOptions.ts │ │ ├── objects.ts │ │ ├── omitDeep.ts │ │ ├── stringifyForDisplay.ts │ │ └── stripTypename.ts │ ├── globals │ │ ├── __tests__ │ │ │ └── invariantWrappers.test.ts │ │ ├── global.ts │ │ ├── index.ts │ │ ├── invariantWrappers.ts │ │ └── maybe.ts │ ├── graphql │ │ ├── DocumentTransform.ts │ │ ├── __tests__ │ │ │ ├── DocumentTransform.ts │ │ │ ├── directives.ts │ │ │ ├── fragments.ts │ │ │ ├── getFromAST.ts │ │ │ ├── storeUtils.ts │ │ │ └── transform.ts │ │ ├── directives.ts │ │ ├── fragments.ts │ │ ├── getFromAST.ts │ │ ├── operations.ts │ │ ├── print.ts │ │ ├── storeUtils.ts │ │ └── transform.ts │ ├── index.ts │ ├── observables │ │ ├── Concast.ts │ │ ├── Observable.ts │ │ ├── __tests__ │ │ │ ├── Concast.ts │ │ │ ├── Observable.ts │ │ │ ├── asyncMap.ts │ │ │ └── subclassing.ts │ │ ├── asyncMap.ts │ │ ├── iteration.ts │ │ └── subclassing.ts │ ├── policies │ │ ├── __tests__ │ │ │ └── relayStylePagination.test.ts │ │ └── pagination.ts │ ├── promises │ │ ├── decoration.ts │ │ └── preventUnhandledRejection.ts │ ├── subscriptions │ │ ├── relay │ │ │ └── index.ts │ │ ├── shared.ts │ │ └── urql │ │ │ └── index.ts │ └── types │ │ ├── DeepOmit.ts │ │ ├── DeepPartial.ts │ │ ├── IsStrictlyAny.ts │ │ ├── NoInfer.ts │ │ ├── OnlyRequiredProperties.ts │ │ ├── Prettify.ts │ │ ├── Primitive.ts │ │ ├── RemoveIndexSignature.ts │ │ ├── TODO.ts │ │ └── UnionToIntersection.ts └── version.ts ├── tsconfig.json ├── tsconfig.tests.json └── tsdoc.json /.api-reports/api-report-dev.api.md: -------------------------------------------------------------------------------- 1 | ## API Report File for "@apollo/client" 2 | 3 | > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). 4 | 5 | ```ts 6 | 7 | // @public (undocumented) 8 | interface ErrorCodes { 9 | // (undocumented) 10 | [key: number]: { 11 | file: string; 12 | condition?: string; 13 | message?: string; 14 | }; 15 | } 16 | 17 | // @public 18 | export type ErrorMessageHandler = { 19 | (message: string | number, args: string[]): string | undefined; 20 | }; 21 | 22 | // @public (undocumented) 23 | export function loadDevMessages(): void; 24 | 25 | // Warning: (ae-forgotten-export) The symbol "ErrorCodes" needs to be exported by the entry point index.d.ts 26 | // 27 | // @public 28 | export function loadErrorMessageHandler(...errorCodes: ErrorCodes[]): ErrorMessageHandler & ErrorCodes; 29 | 30 | // @public (undocumented) 31 | export function loadErrorMessages(): void; 32 | 33 | // @public 34 | export function setErrorMessageHandler(handler: ErrorMessageHandler): void; 35 | 36 | // (No @packageDocumentation comment for this package) 37 | 38 | ``` 39 | -------------------------------------------------------------------------------- /.api-reports/api-report-react_parser.api.md: -------------------------------------------------------------------------------- 1 | ## API Report File for "@apollo/client" 2 | 3 | > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). 4 | 5 | ```ts 6 | 7 | import type { DocumentNode } from 'graphql'; 8 | import type { VariableDefinitionNode } from 'graphql'; 9 | 10 | // @public (undocumented) 11 | enum DocumentType_2 { 12 | // (undocumented) 13 | Mutation = 1, 14 | // (undocumented) 15 | Query = 0, 16 | // (undocumented) 17 | Subscription = 2 18 | } 19 | export { DocumentType_2 as DocumentType } 20 | 21 | // @public (undocumented) 22 | export interface IDocumentDefinition { 23 | // (undocumented) 24 | name: string; 25 | // (undocumented) 26 | type: DocumentType_2; 27 | // (undocumented) 28 | variables: ReadonlyArray; 29 | } 30 | 31 | // @public (undocumented) 32 | export function operationName(type: DocumentType_2): string; 33 | 34 | // @public (undocumented) 35 | export function parser(document: DocumentNode): IDocumentDefinition; 36 | 37 | // @public (undocumented) 38 | export namespace parser { 39 | var // (undocumented) 40 | resetCache: () => void; 41 | } 42 | 43 | // @public (undocumented) 44 | export function verifyDocumentType(document: DocumentNode, type: DocumentType_2): void; 45 | 46 | // (No @packageDocumentation comment for this package) 47 | 48 | ``` 49 | -------------------------------------------------------------------------------- /.api-reports/api-report-utilities_subscriptions_relay.api.md: -------------------------------------------------------------------------------- 1 | ## API Report File for "@apollo/client" 2 | 3 | > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). 4 | 5 | ```ts 6 | 7 | import type { GraphQLResponse } from 'relay-runtime'; 8 | import { Observable } from 'relay-runtime'; 9 | import type { RequestParameters } from 'relay-runtime'; 10 | 11 | // Warning: (ae-forgotten-export) The symbol "CreateMultipartSubscriptionOptions" needs to be exported by the entry point index.d.ts 12 | // Warning: (ae-forgotten-export) The symbol "OperationVariables" needs to be exported by the entry point index.d.ts 13 | // 14 | // @public (undocumented) 15 | export function createFetchMultipartSubscription(uri: string, { fetch: preferredFetch, headers }?: CreateMultipartSubscriptionOptions): (operation: RequestParameters, variables: OperationVariables) => Observable; 16 | 17 | // @public (undocumented) 18 | type CreateMultipartSubscriptionOptions = { 19 | fetch?: WindowOrWorkerGlobalScope["fetch"]; 20 | headers?: Record; 21 | }; 22 | 23 | // @public (undocumented) 24 | type OperationVariables = Record; 25 | 26 | // (No @packageDocumentation comment for this package) 27 | 28 | ``` 29 | -------------------------------------------------------------------------------- /.api-reports/api-report-utilities_subscriptions_urql.api.md: -------------------------------------------------------------------------------- 1 | ## API Report File for "@apollo/client" 2 | 3 | > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). 4 | 5 | ```ts 6 | 7 | import { Observable } from 'zen-observable-ts'; 8 | 9 | // Warning: (ae-forgotten-export) The symbol "CreateMultipartSubscriptionOptions" needs to be exported by the entry point index.d.ts 10 | // 11 | // @public (undocumented) 12 | export function createFetchMultipartSubscription(uri: string, { fetch: preferredFetch, headers }?: CreateMultipartSubscriptionOptions): ({ query, variables, }: { 13 | query?: string; 14 | variables: undefined | Record; 15 | }) => Observable; 16 | 17 | // @public (undocumented) 18 | type CreateMultipartSubscriptionOptions = { 19 | fetch?: WindowOrWorkerGlobalScope["fetch"]; 20 | headers?: Record; 21 | }; 22 | 23 | // (No @packageDocumentation comment for this package) 24 | 25 | ``` 26 | -------------------------------------------------------------------------------- /.attw.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreRules": ["false-esm", "cjs-resolves-to-esm"] 3 | } 4 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { "repo": "apollographql/apollo-client" } 6 | ], 7 | "commit": false, 8 | "fixed": [], 9 | "linked": [], 10 | "access": "public", 11 | "baseBranch": "main", 12 | "updateInternalDependencies": "patch", 13 | "ignore": [] 14 | } 15 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # format "ObservableQuery" test 2 | 0a67647b73abd94b706242f32b88d21a1400ad50 3 | # format "ObservableQuery" test (in #10597) 4 | 104bf11765b1db50292f9656aa8fe48e2d749a83 5 | # format changes from ee0b4ae 6 | f7890ae96a3ba900d3de9bf8b23254bcfba18a25 7 | # Format "DisplayClientError.js" (#10909) 8 | 0cb68364f2c3828badde8c74de44e9c1864fb6d4 9 | # Format `react` folder, upgrade to prettier 3.0 (#11111) 10 | ba3e7d9fa7d46e4c636148bbf01552833db0ceda 11 | # Format all non-docs files with prettier (#11170) 12 | 352c4a9ad4d140d58850688bd1b2d5513f62c6cb 13 | # Format remaining files from #11170 (#11185) 14 | 994ae91f16ea4c8ee28818bd7eaac47e4765c660 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @apollographql/client-typescript 2 | 3 | /docs/ @apollographql/docs 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature Request 3 | about: Feature requests are managed in the Apollo Feature Request repo (https://github.com/apollographql/apollo-feature-requests). 4 | --- 5 | 6 | Thanks for your interest in helping make Apollo Client better! 7 | 8 | Feature requests and non-bug related discussions are no longer managed in this repo. Feature requests should be opened in https://github.com/apollographql/apollo-feature-requests. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question-discussion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🤗 Question / Discussion 3 | about: Questions / discussions are best posted in our community forums or StackOverflow. 4 | --- 5 | 6 | Need help or want to talk all things Apollo Client? Issues here are reserved for bugs, but one of the following resources should help: 7 | 8 | * Apollo GraphQL community forums: https://community.apollographql.com 9 | * StackOverflow (`apollo-client` tag): https://stackoverflow.com/questions/tagged/apollo-client 10 | * Apollo Feature Request repo: https://github.com/apollographql/apollo-feature-requests 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "npm" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | # Disable @dependabot (except for security updates) because we use @renovate instead 11 | open-pull-requests-limit: 0 12 | -------------------------------------------------------------------------------- /.github/workflows/api-extractor.yml: -------------------------------------------------------------------------------- 1 | name: Api Extractor 2 | on: pull_request 3 | 4 | concurrency: ${{ github.workflow }}-${{ github.ref }} 5 | 6 | jobs: 7 | api-extractor: 8 | name: Api-Extractor 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repo 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup Node.js 20.x 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: 20.x 18 | 19 | - name: Install dependencies (with cache) 20 | uses: bahmutov/npm-install@v1 21 | 22 | # Builds the library and runs the api extractor 23 | - name: Run Api-Extractor 24 | run: npm run extract-api 25 | -------------------------------------------------------------------------------- /.github/workflows/arethetypeswrong.yml: -------------------------------------------------------------------------------- 1 | name: AreTheTypesWrong 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | - release-* 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | arethetypeswrong: 12 | name: Are the types wrong 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v4 17 | - name: Setup Node.js 20.x 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20.x 21 | - name: Install dependencies (with cache) 22 | uses: bahmutov/npm-install@v1 23 | 24 | - name: Run build 25 | run: npm run build 26 | - name: Run AreTheTypesWrong 27 | id: attw 28 | run: ./node_modules/.bin/attw --format ascii --pack dist > $GITHUB_STEP_SUMMARY 29 | -------------------------------------------------------------------------------- /.github/workflows/cleanup-checks.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /** @param {import('@actions/github-script').AsyncFunctionArguments} AsyncFunctionArguments */ 3 | export function setup({ context, github }) { 4 | return { 5 | async add_cleanup_label() { 6 | await github.rest.issues.addLabels({ 7 | owner: context.repo.owner, 8 | repo: context.repo.repo, 9 | issue_number: context.issue.number, 10 | labels: ["auto-cleanup"], 11 | }); 12 | }, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/compare-build-output.yml: -------------------------------------------------------------------------------- 1 | name: Compare Build Output 2 | on: 3 | pull_request: 4 | 5 | concurrency: ${{ github.workflow }}-${{ github.ref }} 6 | 7 | jobs: 8 | comparebuildoutput: 9 | name: Compare Build Output 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repo 13 | uses: actions/checkout@v4 14 | with: 15 | # Fetch entire git history so we have the parent commit to compare against 16 | fetch-depth: 0 17 | - name: Setup Node.js 20.x 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20.x 21 | - name: Install dependencies (with cache) 22 | uses: bahmutov/npm-install@v1 23 | 24 | - name: Run comparison script 25 | id: attw 26 | run: ./config/compare-build-output-to.sh $(git merge-base HEAD origin/${{ github.base_ref }}) > $GITHUB_STEP_SUMMARY 27 | env: 28 | RUNNER_TEMP: ${{ runner.temp }} 29 | -------------------------------------------------------------------------------- /.github/workflows/devtools-errorcodes.yml: -------------------------------------------------------------------------------- 1 | name: Devtools - Trigger Error Code PR after npm Release 2 | on: 3 | workflow_dispatch: # for testing 4 | workflow_run: 5 | workflows: ["Prerelease", "Release"] 6 | types: 7 | - completed 8 | jobs: 9 | dispatch: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/create-github-app-token@v1 13 | id: github-actions-bot-app-token 14 | with: 15 | app-id: 819772 16 | private-key: ${{ secrets.APOLLO_GITHUB_ACTIONS_BOT_PRIVATE_KEY }} 17 | repositories: apollo-client-devtools 18 | - uses: benc-uk/workflow-dispatch@v1 19 | with: 20 | workflow: update-errorcodes.yml 21 | repo: apollographql/apollo-client-devtools 22 | token: ${{ steps.github-actions-bot-app-token.outputs.token }} 23 | -------------------------------------------------------------------------------- /.github/workflows/issue-close-user-survey.yml: -------------------------------------------------------------------------------- 1 | name: Issue Close User Survey 2 | 3 | on: 4 | issues: 5 | types: [closed] 6 | 7 | jobs: 8 | user-survey-comment: 9 | permissions: 10 | issues: write 11 | runs-on: ubuntu-latest 12 | if: github.repository == 'apollographql/apollo-client' 13 | steps: 14 | - run: | 15 | if [ "$STATE_REASON" == "completed" ] || [ "$SENDER" != "github-actions" ]; then 16 | gh issue comment "$NUMBER" --body "$BODY" 17 | else 18 | echo "Issue was closed as not planned, skipping comment." 19 | fi 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | GH_REPO: ${{ github.repository }} 23 | NUMBER: ${{ github.event.issue.number }} 24 | STATE_REASON: ${{ github.event.issue.state_reason }} 25 | SENDER: ${{ github.event.sender.login }} 26 | BODY: > 27 | Do you have any feedback for the maintainers? Please tell us by taking a [one-minute survey](https://docs.google.com/forms/d/e/1FAIpQLSczNDXfJne3ZUOXjk9Ursm9JYvhTh1_nFTDfdq3XBAFWCzplQ/viewform?usp=pp_url&entry.1170701325=Apollo+Client&entry.204965213=GitHub+Issue). Your responses will help us understand Apollo Client usage and allow us to serve you better. 28 | -------------------------------------------------------------------------------- /.github/workflows/lock.yml: -------------------------------------------------------------------------------- 1 | name: "Lock Threads" 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | permissions: 8 | issues: write 9 | pull-requests: write 10 | 11 | concurrency: 12 | group: lock 13 | 14 | jobs: 15 | lock: 16 | if: github.repository == 'apollographql/apollo-client' 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: dessant/lock-threads@v5 20 | with: 21 | github-token: ${{ github.token }} 22 | log-output: true 23 | issue-inactive-days: "30" 24 | exclude-any-issue-labels: "discussion" 25 | issue-comment: > 26 | This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. 27 | 28 | For general questions, we recommend using our [Community Forum](https://community.apollographql.com/) or [Stack Overflow](https://stackoverflow.com/questions/tagged/apollo-client). 29 | 30 | pr-inactive-days: "30" 31 | exclude-any-pr-labels: "discussion" 32 | -------------------------------------------------------------------------------- /.github/workflows/publish-pr-releases.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Prerelease 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - "**" 8 | tags: 9 | - "!**" 10 | 11 | jobs: 12 | prerelease: 13 | name: Pull Request Prerelease 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout repo 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Node.js 20.x 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 20.x 23 | 24 | - name: Install dependencies with cache 25 | uses: bahmutov/npm-install@v1 26 | 27 | - name: Build and publish to pkg.pr.new 28 | run: npm run pkg-pr-new-publish 29 | -------------------------------------------------------------------------------- /.github/workflows/size-limit.yml: -------------------------------------------------------------------------------- 1 | name: "size" 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | - release-* 7 | - pr/* 8 | jobs: 9 | size: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repo 13 | uses: actions/checkout@v4 14 | - name: Setup Node.js 20.x 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: 20.x 18 | - name: Install dependencies (with cache) 19 | uses: bahmutov/npm-install@v1 20 | - name: Run size-limit 21 | uses: andresz1/size-limit-action@v1 22 | with: 23 | github_token: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # fs 2 | .DS_Store 3 | .rpt2_cache 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Ignore Wallaby.js configuration file 18 | wallaby.js 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directory 33 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 34 | node_modules 35 | 36 | # don't commit compiled files 37 | lib 38 | benchmark_lib 39 | test-lib 40 | dist 41 | npm 42 | !flow-typed/npm 43 | 44 | # Unpublished output from @apollo/client build, like bundlesize artifacts 45 | temp/ 46 | 47 | # webstorm 48 | .idea/ 49 | 50 | # alm editor 51 | .alm 52 | 53 | yarn.lock 54 | 55 | # docs 56 | db.json 57 | docs.json 58 | *.log 59 | docs/public/* 60 | .idea/ 61 | 62 | junit.xml 63 | 64 | .rpt2_cache 65 | 66 | # Local Netlify folder 67 | .netlify 68 | 69 | # Ignore generated test report output 70 | reports 71 | 72 | esbuild-why-*.html 73 | 74 | .yalc 75 | yalc.lock 76 | 77 | .attest 78 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | ##### DISCLAIMER ###### 2 | # We have disabled the use of prettier in this project for a variety of reasons. 3 | # Because much of this project has not been formatted, we don't want to want to 4 | # apply formatting to everything and skew `git blame` stats. Instead, we should 5 | # only format newly created files that we can guarantee have no existing git 6 | # history. For this reason, we have disabled prettier project-wide except for 7 | # a handful of files. 8 | # 9 | # ONLY ADD NEWLY CREATED FILES/PATHS TO THE LIST BELOW. DO NOT ADD EXISTING 10 | # PROJECT FILES. 11 | 12 | # ignores all files in /docs directory 13 | !/docs 14 | /docs/** 15 | 16 | # Ignore all mdx & md files: 17 | *.mdx 18 | *.md 19 | *.har 20 | *.snap 21 | 22 | !/docs/source 23 | /docs/source/** 24 | !/docs/source/development-testing 25 | /docs/source/development-testing/** 26 | !/docs/source/development-testing/reducing-bundle-size.mdx 27 | !/docs/source/development-testing/schema-driven-testing.mdx 28 | 29 | !docs/shared 30 | /docs/shared/** 31 | !/docs/shared/ApiDoc 32 | !/docs/shared/ApiDoc/** 33 | !/docs/shared/Overrides 34 | !/docs/shared/Overrides/** 35 | 36 | node_modules/ 37 | .yalc/ 38 | .next/ 39 | .changeset/ 40 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "printWidth": 80, 4 | "semi": true, 5 | "singleQuote": false, 6 | "tabWidth": 2, 7 | "trailingComma": "es5", 8 | "experimentalTernaries": true 9 | } 10 | -------------------------------------------------------------------------------- /.semgrepignore: -------------------------------------------------------------------------------- 1 | # semgrep defaults 2 | # https://semgrep.dev/docs/ignoring-files-folders-code#defining-ignored-files-and-folders-in-semgrepignore 3 | node_modules/ 4 | dist/ 5 | *.min.js 6 | .npm/ 7 | .yarn/ 8 | .semgrep 9 | .semgrep_logs/ 10 | 11 | # custom paths 12 | __tests__/ 13 | ./docs/source/data/subscriptions.mdx 14 | ./docs/source/development-testing/developer-tooling.mdx -------------------------------------------------------------------------------- /.size-limits.json: -------------------------------------------------------------------------------- 1 | { 2 | "dist/apollo-client.min.cjs": 42332, 3 | "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production)": 34542 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Jest Attach Node Inspector for Current File", 8 | "cwd": "${workspaceFolder}", 9 | "runtimeArgs": [ 10 | "--inspect-brk", 11 | "${workspaceRoot}/node_modules/.bin/jest", 12 | "${relativeFile}", 13 | "--config", 14 | "./config/jest.config.js", 15 | "--runInBand", 16 | "--watch" 17 | ], 18 | "console": "integratedTerminal", 19 | "internalConsoleOptions": "neverOpen" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.tabSize": 2, 4 | "editor.rulers": [80], 5 | "files.trimTrailingWhitespace": true, 6 | "files.insertFinalNewline": true, 7 | "typescript.tsdk": "node_modules/typescript/lib", 8 | "editor.codeActionsOnSave": { 9 | "source.organizeImports": "never" 10 | }, 11 | "cSpell.enableFiletypes": ["mdx"], 12 | "jest.jestCommandLine": "node --expose-gc node_modules/.bin/jest --config ./config/jest.config.js --ignoreProjects 'ReactDOM 17' --runInBand" 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Apollo Graph, Inc. (Formerly Meteor Development Group, Inc.) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /config/FixJSDOMEnvironment.js: -------------------------------------------------------------------------------- 1 | const { default: JSDOMEnvironment } = require("jest-environment-jsdom"); 2 | 3 | // https://github.com/facebook/jest/blob/v29.4.3/website/versioned_docs/version-29.4/Configuration.md#testenvironment-string 4 | class FixJSDOMEnvironment extends JSDOMEnvironment { 5 | constructor(...args) { 6 | super(...args); 7 | 8 | // FIXME https://github.com/jsdom/jsdom/issues/1724 9 | this.global.Headers = Headers; 10 | this.global.Request = Request; 11 | this.global.Response = Response; 12 | 13 | // FIXME: setting a global fetch breaks HttpLink tests 14 | // and setting AbortController breaks PersistedQueryLink tests, which may 15 | // indicate a memory leak 16 | // this.global.fetch = fetch; 17 | this.global.AbortController = AbortController; 18 | } 19 | } 20 | 21 | module.exports = FixJSDOMEnvironment; 22 | -------------------------------------------------------------------------------- /config/bundlesize.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs"; 2 | import { join } from "path"; 3 | import { gzipSync } from "zlib"; 4 | import bytes from "bytes"; 5 | 6 | const gzipBundleByteLengthLimit = bytes("35.25KB"); 7 | const minFile = join("dist", "apollo-client.min.cjs"); 8 | const minPath = join(__dirname, "..", minFile); 9 | const gzipByteLen = gzipSync(readFileSync(minPath)).byteLength; 10 | const overLimit = gzipByteLen > gzipBundleByteLengthLimit; 11 | 12 | const message = `Minified + GZIP-encoded bundle size for ${minFile} = ${bytes( 13 | gzipByteLen, 14 | { unit: "KB" } 15 | )}, ${overLimit ? "exceeding" : "under"} limit ${bytes( 16 | gzipBundleByteLengthLimit, 17 | { unit: "KB" } 18 | )}`; 19 | 20 | if (overLimit) { 21 | throw new Error(message); 22 | } else { 23 | console.log(message); 24 | } 25 | -------------------------------------------------------------------------------- /config/jest/react-dom-17-client.js: -------------------------------------------------------------------------------- 1 | // Shim for React 17 react-dom/client entrypoint imported by React Testing 2 | // Library 3 | 4 | module.exports = { 5 | hydrateRoot: () => { 6 | throw new Error( 7 | "Cannot use hydrateRoot with React 17. Ensure this uses legacy root instead" 8 | ); 9 | }, 10 | createRoot: () => { 11 | throw new Error( 12 | "Cannot use createRoot with React 17. Ensure this uses legacy root instead" 13 | ); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /config/postprocessDist.ts: -------------------------------------------------------------------------------- 1 | import { distDir } from "./helpers.ts"; 2 | import fs from "node:fs"; 3 | import path from "node:path"; 4 | 5 | const globalTypesFile = path.resolve(distDir, "utilities/globals/global.d.ts"); 6 | fs.writeFileSync( 7 | globalTypesFile, 8 | fs 9 | .readFileSync(globalTypesFile, "utf8") 10 | .split("\n") 11 | .filter((line) => line.trim() !== "const __DEV__: boolean;") 12 | .join("\n"), 13 | "utf8" 14 | ); 15 | -------------------------------------------------------------------------------- /config/precheck.js: -------------------------------------------------------------------------------- 1 | const { lockfileVersion } = require("../package-lock.json"); 2 | 3 | const expectedVersion = 2; 4 | 5 | if (typeof lockfileVersion !== "number" || lockfileVersion < expectedVersion) { 6 | throw new Error( 7 | `Old lockfileVersion (${lockfileVersion}) found in package-lock.json (expected ${expectedVersion} or later)` 8 | ); 9 | } 10 | 11 | console.log("ok", { 12 | lockfileVersion, 13 | expectedVersion, 14 | }); 15 | -------------------------------------------------------------------------------- /config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "strictNullChecks": true, 5 | "noUnusedParameters": false, 6 | "noUnusedLocals": true, 7 | "skipLibCheck": true, 8 | "moduleResolution": "node", 9 | "importHelpers": true, 10 | "target": "es5", 11 | "module": "commonjs", 12 | "esModuleInterop": true, 13 | "sourceMap": true, 14 | "inlineSourceMap": false, 15 | "noEmit": true, 16 | "allowImportingTsExtensions": true, 17 | // This option should cause sourcesContent to be included in the 18 | // source map JSON, so we don't have to rely on the (nonexistent) 19 | // relative paths in the sources array, but it appears tsc does not 20 | // respect this option unless the source maps are inlined into the 21 | // source files, which we definitely do not want. Instead, we use the 22 | // config/rewriteSourceMaps.ts script to add sourcesContent. 23 | "inlineSources": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | This is the documentation **source** for this repository. 4 | 5 | The **deployed** version of the documentation for this repository is available at: 6 | 7 | * https://www.apollographql.com/docs/react/ 8 | 9 | For general local installation and development instructions, see the [docs site README](https://github.com/apollographql/docs). 10 | 11 | In order to build and develop the Apollo Client docs locally, you will need to follow these additional steps: 12 | 13 | 1. Clone the docs site repository ([https://github.com/apollographql/docs](https://github.com/apollographql/docs)) in a sibling directory to your local copy of the [`apollo-client`](https://github.com/apollographql/apollo-client) repository. 14 | 2. `cd docs && npm i` 15 | 3. Open a new terminal, `cd apollo-client`, make changes to the docs and run `npm run docmodel` 16 | 4. Back in the terminal window where you've checked out and cd'd into the `docs` repository, run `DOCS_MODE='local' npm run start:local -- ../apollo-client` 17 | 5. Open a browser and visit `http://localhost:3000` 18 | 6. Note: you'll need to manually remove the `/react` segment of the URL from the path inside the browser 19 | -------------------------------------------------------------------------------- /docs/shared/ApiDoc/Context.js: -------------------------------------------------------------------------------- 1 | import { useMDXComponents } from "@mdx-js/react"; 2 | 3 | export const useApiDocContext = function () { 4 | const MDX = useMDXComponents(); 5 | return MDX.useApiDocContext(this, arguments); 6 | }; 7 | -------------------------------------------------------------------------------- /docs/shared/ApiDoc/InterfaceDetails.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | import { GridItem } from "@chakra-ui/react"; 4 | import { 5 | ApiDocHeading, 6 | DocBlock, 7 | PropertySignatureTable, 8 | useApiDocContext, 9 | SectionHeading, 10 | } from "."; 11 | export function InterfaceDetails({ 12 | canonicalReference, 13 | headingLevel, 14 | link, 15 | customPropertyOrder, 16 | }) { 17 | const getItem = useApiDocContext(); 18 | const item = getItem(canonicalReference); 19 | return ( 20 | <> 21 | 26 | 27 | 28 | Properties 29 | 30 | 36 | 37 | ); 38 | } 39 | 40 | InterfaceDetails.propTypes = { 41 | canonicalReference: PropTypes.string.isRequired, 42 | headingLevel: PropTypes.number.isRequired, 43 | link: PropTypes.bool, 44 | customPropertyOrder: PropTypes.arrayOf(PropTypes.string), 45 | }; 46 | -------------------------------------------------------------------------------- /docs/shared/ApiDoc/PropertyDetails.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import React from "react"; 3 | import { ApiDocHeading, DocBlock } from "."; 4 | 5 | export function PropertyDetails({ canonicalReference, headingLevel }) { 6 | return ( 7 | <> 8 | 13 | 20 | 21 | ); 22 | } 23 | 24 | PropertyDetails.propTypes = { 25 | canonicalReference: PropTypes.string.isRequired, 26 | headingLevel: PropTypes.number.isRequired, 27 | }; 28 | -------------------------------------------------------------------------------- /docs/shared/ApiDoc/SourceLink.js: -------------------------------------------------------------------------------- 1 | import { useMDXComponents } from "@mdx-js/react"; 2 | import PropTypes from "prop-types"; 3 | import React from "react"; 4 | import { Text } from "@chakra-ui/react"; 5 | import { useApiDocContext } from "./Context"; 6 | 7 | export function SourceLink({ canonicalReference }) { 8 | const MDX = useMDXComponents(); 9 | const getItem = useApiDocContext(); 10 | const item = getItem(canonicalReference); 11 | return item.file ? 12 | 13 | 17 | ({item.file}) 18 | 19 | 20 | : null; 21 | } 22 | SourceLink.propTypes = { 23 | canonicalReference: PropTypes.string.isRequired, 24 | }; 25 | -------------------------------------------------------------------------------- /docs/shared/ApiDoc/index.js: -------------------------------------------------------------------------------- 1 | export { useApiDocContext } from "./Context"; 2 | export { 3 | DocBlock, 4 | Deprecated, 5 | Example, 6 | Remarks, 7 | ReleaseTag, 8 | Summary, 9 | } from "./DocBlock"; 10 | export { PropertySignatureTable } from "./PropertySignatureTable"; 11 | export { ApiDocHeading, SubHeading, SectionHeading } from "./Heading"; 12 | export { InterfaceDetails } from "./InterfaceDetails"; 13 | export { FunctionSignature, FunctionDetails } from "./Function"; 14 | export { ParameterTable } from "./ParameterTable"; 15 | export { PropertyDetails } from "./PropertyDetails"; 16 | export { EnumDetails } from "./EnumDetails"; 17 | export { ManualTuple } from "./Tuple"; 18 | export { SourceLink } from "./SourceLink"; 19 | -------------------------------------------------------------------------------- /docs/shared/link-chain.mdx: -------------------------------------------------------------------------------- 1 | ```js title="index.js" 2 | import { ApolloClient, InMemoryCache, HttpLink, from } from "@apollo/client"; 3 | import { onError } from "@apollo/client/link/error"; 4 | 5 | const httpLink = new HttpLink({ 6 | uri: "http://localhost:4000/graphql" 7 | }); 8 | 9 | const errorLink = onError(({ graphQLErrors, networkError }) => { 10 | if (graphQLErrors) 11 | graphQLErrors.forEach(({ message, locations, path }) => 12 | console.log( 13 | `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`, 14 | ), 15 | ); 16 | 17 | if (networkError) console.log(`[Network error]: ${networkError}`); 18 | }); 19 | 20 | // If you provide a link chain to ApolloClient, you 21 | // don't provide the `uri` option. 22 | const client = new ApolloClient({ 23 | // The `from` function combines an array of individual links 24 | // into a link chain 25 | link: from([errorLink, httpLink]), 26 | cache: new InMemoryCache() 27 | }); 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/shared/useFragment-result.mdx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 27 | 28 | 35 | 36 | 37 | 38 | 44 | 45 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 66 | 67 | 68 | 69 |
Name /
Type
Description
14 | 15 | **Operation result** 16 | 17 |
22 | 23 | ###### `data` 24 | 25 | `TData` 26 | 29 | 30 | An object containing the data for a given GraphQL fragment. 31 | 32 | This value might be `undefined` if a query results in one or more errors (depending on the query's `errorPolicy`). 33 | 34 |
39 | 40 | ###### `complete` 41 | 42 | `boolean` 43 | 46 | 47 | A boolean indicating whether the data returned for the fragment is complete. When `false`, the `missing` field should explain which fields were responsible for the incompleteness. 48 | 49 |
55 | 56 | ###### `missing` 57 | 58 | `MissingTree` 59 | 62 | 63 | A tree of all `MissingFieldError` messages reported during fragment reading, where the branches of the tree indicate the paths of the errors within the query result. 64 | 65 |
70 | -------------------------------------------------------------------------------- /docs/source/api/react/hooks-experimental.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hooks (experimental) 3 | description: Apollo Client experimental react hooks API reference 4 | --- 5 | 6 | The latest minor version of Apollo Client has no experimental hooks. Please see the [Hooks page](./hooks) for a list of available stable React hooks. 7 | -------------------------------------------------------------------------------- /docs/source/api/react/preloading.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Preloading 3 | description: Apollo Client preloading API reference 4 | minVersion: 3.9.0 5 | api_doc: 6 | - "@apollo/client!createQueryPreloader:function(1)" 7 | --- 8 | 9 | import { FunctionDetails } from '../../../shared/ApiDoc'; 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/source/assets/client-diagrams/1-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/client-diagrams/1-overview.png -------------------------------------------------------------------------------- /docs/source/assets/client-diagrams/2-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/client-diagrams/2-map.png -------------------------------------------------------------------------------- /docs/source/assets/client-diagrams/3-minimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/client-diagrams/3-minimize.png -------------------------------------------------------------------------------- /docs/source/assets/client-diagrams/4-normalize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/client-diagrams/4-normalize.png -------------------------------------------------------------------------------- /docs/source/assets/client-mocking/cli-clientcheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/client-mocking/cli-clientcheck.png -------------------------------------------------------------------------------- /docs/source/assets/client-mocking/devtools-clientstate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/client-mocking/devtools-clientstate.png -------------------------------------------------------------------------------- /docs/source/assets/client-mocking/vscode-autocomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/client-mocking/vscode-autocomplete.png -------------------------------------------------------------------------------- /docs/source/assets/client-mocking/vscode-errors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/client-mocking/vscode-errors.png -------------------------------------------------------------------------------- /docs/source/assets/client-mocking/vscode-typeinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/client-mocking/vscode-typeinfo.png -------------------------------------------------------------------------------- /docs/source/assets/client-schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/client-schema.png -------------------------------------------------------------------------------- /docs/source/assets/devtools/apollo-client-devtools/ac-browser-devtools-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/devtools/apollo-client-devtools/ac-browser-devtools-3.png -------------------------------------------------------------------------------- /docs/source/assets/devtools/devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/devtools/devtools.png -------------------------------------------------------------------------------- /docs/source/assets/devtools/mutation-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/devtools/mutation-init.png -------------------------------------------------------------------------------- /docs/source/assets/devtools/mutation-result-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/devtools/mutation-result-data.png -------------------------------------------------------------------------------- /docs/source/assets/devtools/mutation-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/devtools/mutation-result.png -------------------------------------------------------------------------------- /docs/source/assets/devtools/query-init-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/devtools/query-init-data.png -------------------------------------------------------------------------------- /docs/source/assets/devtools/query-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/devtools/query-init.png -------------------------------------------------------------------------------- /docs/source/assets/devtools/query-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/devtools/query-result.png -------------------------------------------------------------------------------- /docs/source/assets/devtools/vscode-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/devtools/vscode-panel.png -------------------------------------------------------------------------------- /docs/source/assets/devtools/vscode-setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/devtools/vscode-setting.png -------------------------------------------------------------------------------- /docs/source/assets/link/concepts-intro-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/link/concepts-intro-1.png -------------------------------------------------------------------------------- /docs/source/assets/link/concepts-intro-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/link/concepts-intro-2.png -------------------------------------------------------------------------------- /docs/source/assets/link/error-request-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/assets/link/error-request-flow.png -------------------------------------------------------------------------------- /docs/source/data/file-uploads.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: File uploads 3 | description: Enabling file uploads in Apollo Client 4 | --- 5 | 6 | Apollo Client doesn't support a file upload feature out of the box. If you'd like to enable file upload capabilities, you will have to set Apollo Client up manually with a 3rd party package. 7 | 8 | Detailed instructions on how to setup Apollo Client for file upload can be found here: [https://github.com/jaydenseric/apollo-upload-client](https://github.com/jaydenseric/apollo-upload-client). 9 | 10 | An example configuration is show below using the [apollo-upload-client](https://github.com/jaydenseric/apollo-upload-client) package. 11 | 12 | ```bash 13 | npm install apollo-upload-client 14 | ``` 15 | 16 | Basic setup for the Apollo Client: 17 | 18 | ```js 19 | const { ApolloClient } = require('apollo-client') 20 | const { InMemoryCache } = require('apollo-cache-inmemory') 21 | const { createUploadLink } = require('apollo-upload-client') 22 | 23 | const client = new ApolloClient({ 24 | cache: new InMemoryCache(), 25 | link: createUploadLink() 26 | }) 27 | ``` -------------------------------------------------------------------------------- /docs/source/img/cache-inspector.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/img/cache-inspector.jpg -------------------------------------------------------------------------------- /docs/source/img/client-schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/img/client-schema.png -------------------------------------------------------------------------------- /docs/source/img/githunt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/img/githunt.png -------------------------------------------------------------------------------- /docs/source/img/spotify-playlists.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/img/spotify-playlists.jpg -------------------------------------------------------------------------------- /docs/source/logo/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/logo/favicon.png -------------------------------------------------------------------------------- /docs/source/logo/icon-apollo-white-200x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/logo/icon-apollo-white-200x200.png -------------------------------------------------------------------------------- /docs/source/logo/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/logo/large.png -------------------------------------------------------------------------------- /docs/source/logo/square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/docs/source/logo/square.png -------------------------------------------------------------------------------- /eslint-local-rules/fixtures/file.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/eslint-local-rules/fixtures/file.ts -------------------------------------------------------------------------------- /eslint-local-rules/fixtures/react.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apollographql/apollo-client/17d1a5e6677c25ce65e163a9457b7b01c14db082/eslint-local-rules/fixtures/react.tsx -------------------------------------------------------------------------------- /eslint-local-rules/fixtures/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "esnext" 5 | }, 6 | "include": ["file.ts", "react.tsx"] 7 | } 8 | -------------------------------------------------------------------------------- /eslint-local-rules/forbid-act-in-disabled-act-environment.test.ts: -------------------------------------------------------------------------------- 1 | import { rule } from "./forbid-act-in-disabled-act-environment"; 2 | import { ruleTester } from "./testSetup"; 3 | 4 | ruleTester.run("forbid-act-in-disabled-act-environment", rule, { 5 | valid: [ 6 | ` 7 | () => { 8 | using _disabledAct = disableActEnvironment(); 9 | } 10 | () => { 11 | act(() => {}) 12 | } 13 | `, 14 | ` 15 | () => { 16 | using _disabledAct = disableActEnvironment(); 17 | } 18 | () => { 19 | actAsync(() => {}) 20 | } 21 | `, 22 | ], 23 | invalid: [ 24 | ` 25 | () => { 26 | using _disabledAct = disableActEnvironment(); 27 | act(() => {}) 28 | } 29 | `, 30 | ` 31 | () => { 32 | using _disabledAct = disableActEnvironment(); 33 | actAsync(() => {}) 34 | } 35 | `, 36 | ` 37 | () => { 38 | using _disabledAct = disableActEnvironment(); 39 | () => { 40 | act(() => {}) 41 | } 42 | } 43 | `, 44 | ` 45 | () => { 46 | using _disabledAct = disableActEnvironment(); 47 | () => { 48 | actAsync(() => {}) 49 | } 50 | } 51 | `, 52 | ].map((code) => ({ 53 | code, 54 | errors: [{ messageId: "forbiddenActInNonActEnvironment" }], 55 | })), 56 | }); 57 | -------------------------------------------------------------------------------- /eslint-local-rules/index.js: -------------------------------------------------------------------------------- 1 | require("ts-node").register({ 2 | transpileOnly: true, 3 | compilerOptions: { 4 | // we need this to be nodenext in the tsconfig, because 5 | // @typescript-eslint/utils only seems to export ESM 6 | // in TypeScript's eyes, but it totally works 7 | module: "commonjs", 8 | moduleResolution: "node", 9 | }, 10 | }); 11 | 12 | module.exports = { 13 | "require-using-disposable": require("./require-using-disposable").rule, 14 | "require-disable-act-environment": 15 | require("./require-disable-act-environment").rule, 16 | "forbid-act-in-disabled-act-environment": 17 | require("./forbid-act-in-disabled-act-environment").rule, 18 | }; 19 | -------------------------------------------------------------------------------- /eslint-local-rules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "node -r ts-node/register/transpile-only --no-warnings --test --watch *.test.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /eslint-local-rules/require-using-disposable.test.ts: -------------------------------------------------------------------------------- 1 | import { rule } from "./require-using-disposable"; 2 | import { ruleTester } from "./testSetup"; 3 | 4 | ruleTester.run("require-using-disposable", rule, { 5 | valid: [ 6 | ` 7 | function foo(): Disposable {} 8 | using bar = foo() 9 | `, 10 | ` 11 | function foo(): AsyncDisposable {} 12 | await using bar = foo() 13 | `, 14 | ], 15 | invalid: [ 16 | { 17 | code: ` 18 | function foo(): Disposable {} 19 | const bar = foo() 20 | `, 21 | errors: [{ messageId: "missingUsing" }], 22 | }, 23 | { 24 | code: ` 25 | function foo(): AsyncDisposable {} 26 | const bar = foo() 27 | `, 28 | errors: [{ messageId: "missingAwaitUsing" }], 29 | }, 30 | ], 31 | }); 32 | -------------------------------------------------------------------------------- /eslint-local-rules/testSetup.ts: -------------------------------------------------------------------------------- 1 | import { RuleTester } from "@typescript-eslint/rule-tester"; 2 | import tsParser from "@typescript-eslint/parser"; 3 | import nodeTest from "node:test"; 4 | 5 | RuleTester.it = nodeTest.it; 6 | RuleTester.itOnly = nodeTest.only; 7 | RuleTester.describe = nodeTest.describe; 8 | RuleTester.afterAll = nodeTest.after; 9 | 10 | export const ruleTester = new RuleTester({ 11 | languageOptions: { 12 | parser: tsParser, 13 | parserOptions: { 14 | project: "./tsconfig.json", 15 | tsconfigRootDir: __dirname + "/fixtures", 16 | }, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /eslint-local-rules/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "noEmit": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /integration-tests/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | test-results/ 3 | .next 4 | -------------------------------------------------------------------------------- /integration-tests/browser-esm/html/jsdeliver-esm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 40 | 41 | 42 |

https://cdn.jsdelivr.net/npm/@apollo/client/+esm

43 |
44 |

loading

45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /integration-tests/browser-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-esm", 3 | "scripts": { 4 | "serve-app": "yarn serve html", 5 | "test": "playwright test" 6 | }, 7 | "devDependencies": { 8 | "shared": "*", 9 | "playwright": "*", 10 | "@playwright/test": "*", 11 | "serve": "*" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /integration-tests/browser-esm/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { baseConfig } from "shared/playwright.config"; 2 | import { defineConfig } from "@playwright/test"; 3 | 4 | export default defineConfig(baseConfig); 5 | -------------------------------------------------------------------------------- /integration-tests/browser-esm/tests/playwright/jsdeliver-esm.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "shared/fixture"; 3 | 4 | test("Basic Test", async ({ page, withHar }) => { 5 | await page.goto("http://localhost:3000/jsdeliver-esm.html"); 6 | 7 | await expect(page.getByText("loading")).toBeVisible(); 8 | await expect(page.getByText("loading")).not.toBeVisible({ timeout: 10000 }); 9 | await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 10 | }); 11 | -------------------------------------------------------------------------------- /integration-tests/browser-esm/tests/playwright/jspm-prepared.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "shared/fixture"; 3 | 4 | test("Basic Test", async ({ page, withHar }) => { 5 | await page.goto("http://localhost:3000/jspm-prepared.html"); 6 | 7 | await expect(page.getByText("loading")).toBeVisible(); 8 | await expect(page.getByText("loading")).not.toBeVisible({ timeout: 10000 }); 9 | await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 10 | }); 11 | -------------------------------------------------------------------------------- /integration-tests/browser-esm/tests/playwright/unpkg-unmangled.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "shared/fixture"; 3 | 4 | test("Basic Test", async ({ withHar }) => { 5 | await withHar.goto("http://localhost:3000/unpkg-unmangled.html"); 6 | 7 | await expect(withHar.getByText("loading")).toBeVisible(); 8 | await expect(withHar.getByText("loading")).not.toBeVisible({ 9 | timeout: 10000, 10 | }); 11 | await expect(withHar.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 12 | }); 13 | -------------------------------------------------------------------------------- /integration-tests/cra4/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /integration-tests/cra4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cra4", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.10.1", 7 | "graphql": "^16.8.1", 8 | "react": "^18.3.0", 9 | "react-dom": "^18.3.0", 10 | "react-scripts": "^4" 11 | }, 12 | "scripts": { 13 | "start": "PORT=3000 react-scripts start", 14 | "build": "NODE_OPTIONS=--openssl-legacy-provider react-scripts build", 15 | "serve-app": "yarn serve -s build", 16 | "test": "playwright test" 17 | }, 18 | "eslintConfig": { 19 | "extends": [ 20 | "react-app", 21 | "react-app/jest" 22 | ] 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | }, 36 | "devDependencies": { 37 | "@playwright/test": "*", 38 | "@types/react": "^18.0.26", 39 | "@types/react-dom": "^18.0.10", 40 | "playwright": "*", 41 | "serve": "*", 42 | "shared": "*", 43 | "typescript": "^4.9.4" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /integration-tests/cra4/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { baseConfig } from "shared/playwright.config"; 2 | import { defineConfig } from "@playwright/test"; 3 | 4 | export default defineConfig(baseConfig); 5 | -------------------------------------------------------------------------------- /integration-tests/cra4/src/App.tsx: -------------------------------------------------------------------------------- 1 | import type { TypedDocumentNode } from "@apollo/client"; 2 | 3 | import { 4 | useQuery, 5 | gql, 6 | InMemoryCache, 7 | ApolloClient, 8 | ApolloProvider, 9 | ApolloLink, 10 | Observable, 11 | HttpLink, 12 | } from "@apollo/client"; 13 | 14 | const delayLink = new ApolloLink((operation, forward) => { 15 | return new Observable((observer) => { 16 | const handle = setTimeout(() => { 17 | forward(operation).subscribe(observer); 18 | }, 1000); 19 | 20 | return () => clearTimeout(handle); 21 | }); 22 | }); 23 | 24 | const httpLink = new HttpLink({ 25 | uri: "https://main--hack-the-e-commerce.apollographos.net/graphql", 26 | }); 27 | 28 | const client = new ApolloClient({ 29 | cache: new InMemoryCache(), 30 | link: ApolloLink.from([delayLink, httpLink]), 31 | }); 32 | 33 | const QUERY: TypedDocumentNode<{ 34 | products: { 35 | id: string; 36 | title: string; 37 | }[]; 38 | }> = gql` 39 | query { 40 | products { 41 | id 42 | title 43 | } 44 | } 45 | `; 46 | 47 | export default function App() { 48 | return ( 49 | 50 |
51 | 52 | ); 53 | } 54 | 55 | function Main() { 56 | const { data } = useQuery(QUERY); 57 | 58 | return data ? 59 |
    60 | {data?.products.map(({ id, title }) =>
  • {title}
  • )} 61 |
62 | : <>loading; 63 | } 64 | -------------------------------------------------------------------------------- /integration-tests/cra4/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | // Initialize the msw worker, wait for the service worker registration to resolve, then mount 6 | async function render() { 7 | const rootNode = ReactDOM.createRoot( 8 | document.getElementById("root") as HTMLElement 9 | ); 10 | 11 | rootNode.render( 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | render(); 19 | -------------------------------------------------------------------------------- /integration-tests/cra4/tests/playwright/apollo-client.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "shared/fixture"; 3 | 4 | test("Basic Test", async ({ page, withHar }) => { 5 | await page.goto("http://localhost:3000"); 6 | 7 | await expect(page.getByText("loading")).toBeVisible(); 8 | await expect(page.getByText("loading")).not.toBeVisible(); 9 | await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 10 | }); 11 | -------------------------------------------------------------------------------- /integration-tests/cra4/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /integration-tests/cra5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cra5", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.10.1", 7 | "@types/react": "^18.2.14", 8 | "@types/react-dom": "^18.2.6", 9 | "graphql": "^16.8.1", 10 | "react": "^18.3.0", 11 | "react-dom": "^18.3.0", 12 | "react-scripts": "5.0.1", 13 | "typescript": "^4.9.5" 14 | }, 15 | "scripts": { 16 | "start": "PORT=3000 react-scripts start", 17 | "build": "react-scripts build", 18 | "serve-app": "yarn serve -s build", 19 | "test": "playwright test" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app", 24 | "react-app/jest" 25 | ] 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | }, 39 | "devDependencies": { 40 | "@playwright/test": "*", 41 | "playwright": "*", 42 | "serve": "*", 43 | "shared": "*" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /integration-tests/cra5/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { baseConfig } from "shared/playwright.config"; 2 | import { defineConfig } from "@playwright/test"; 3 | 4 | export default defineConfig(baseConfig); 5 | -------------------------------------------------------------------------------- /integration-tests/cra5/src/App.tsx: -------------------------------------------------------------------------------- 1 | import type { TypedDocumentNode } from "@apollo/client"; 2 | 3 | import { 4 | useQuery, 5 | gql, 6 | InMemoryCache, 7 | ApolloClient, 8 | ApolloProvider, 9 | ApolloLink, 10 | Observable, 11 | HttpLink, 12 | } from "@apollo/client"; 13 | 14 | const delayLink = new ApolloLink((operation, forward) => { 15 | return new Observable((observer) => { 16 | const handle = setTimeout(() => { 17 | forward(operation).subscribe(observer); 18 | }, 1000); 19 | 20 | return () => clearTimeout(handle); 21 | }); 22 | }); 23 | 24 | const httpLink = new HttpLink({ 25 | uri: "https://main--hack-the-e-commerce.apollographos.net/graphql", 26 | }); 27 | 28 | const client = new ApolloClient({ 29 | cache: new InMemoryCache(), 30 | link: ApolloLink.from([delayLink, httpLink]), 31 | }); 32 | 33 | const QUERY: TypedDocumentNode<{ 34 | products: { 35 | id: string; 36 | title: string; 37 | }[]; 38 | }> = gql` 39 | query { 40 | products { 41 | id 42 | title 43 | } 44 | } 45 | `; 46 | 47 | export default function App() { 48 | return ( 49 | 50 |
51 | 52 | ); 53 | } 54 | 55 | function Main() { 56 | const { data } = useQuery(QUERY); 57 | 58 | return data ? 59 |
    60 | {data?.products.map(({ id, title }) =>
  • {title}
  • )} 61 |
62 | : <>loading; 63 | } 64 | -------------------------------------------------------------------------------- /integration-tests/cra5/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | const root = ReactDOM.createRoot( 6 | document.getElementById("root") as HTMLElement 7 | ); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /integration-tests/cra5/tests/playwright/apollo-client.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "shared/fixture"; 3 | 4 | test("Basic Test", async ({ page, withHar }) => { 5 | await page.goto("http://localhost:3000"); 6 | 7 | await expect(page.getByText("loading")).toBeVisible(); 8 | await expect(page.getByText("loading")).not.toBeVisible(); 9 | await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 10 | }); 11 | -------------------------------------------------------------------------------- /integration-tests/cra5/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /integration-tests/empty.har: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "version": "1.2", 4 | "creator": { 5 | "name": "Playwright", 6 | "version": "1.35.1" 7 | }, 8 | "browser": { 9 | "name": "chromium", 10 | "version": "115.0.5790.24" 11 | }, 12 | "entries": [] 13 | } 14 | } -------------------------------------------------------------------------------- /integration-tests/next/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /integration-tests/next/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | module.exports = nextConfig; 5 | -------------------------------------------------------------------------------- /integration-tests/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "serve-app": "npm run start", 10 | "ci-preparations": "npm install --legacy-peer-deps @apollo/experimental-nextjs-app-support", 11 | "test": "playwright test" 12 | }, 13 | "dependencies": { 14 | "@apollo/client": "^3.10.1", 15 | "@apollo/experimental-nextjs-app-support": "^0.10.0", 16 | "@graphql-tools/schema": "^10.0.0", 17 | "@types/node": "20.3.1", 18 | "@types/react": "18.2.14", 19 | "@types/react-dom": "18.2.6", 20 | "deepmerge": "^4.3.1", 21 | "graphql": "^16.8.1", 22 | "lodash": "^4.17.21", 23 | "next": "^14.2.3", 24 | "react": "^18.3.0", 25 | "react-dom": "^18.3.0", 26 | "typescript": "5.1.3" 27 | }, 28 | "devDependencies": { 29 | "@playwright/test": "*", 30 | "@types/lodash": "^4.14.195", 31 | "playwright": "*", 32 | "shared": "*" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /integration-tests/next/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { baseConfig } from "shared/playwright.config"; 2 | import { defineConfig } from "@playwright/test"; 3 | 4 | export default defineConfig(baseConfig); 5 | -------------------------------------------------------------------------------- /integration-tests/next/src/app/cc/ApolloWrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import * as React from "react"; 3 | import { ApolloLink, HttpLink } from "@apollo/client"; 4 | import { 5 | ApolloNextAppProvider, 6 | NextSSRInMemoryCache, 7 | NextSSRApolloClient, 8 | } from "@apollo/experimental-nextjs-app-support/ssr"; 9 | 10 | import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev"; 11 | import { setVerbosity } from "ts-invariant"; 12 | import { schemaLink } from "@/libs/schemaLink.ts"; 13 | 14 | //if (process.env.NODE_ENV === 'development') { 15 | setVerbosity("debug"); 16 | loadDevMessages(); 17 | loadErrorMessages(); 18 | //} 19 | 20 | export function ApolloWrapper({ children }: React.PropsWithChildren<{}>) { 21 | return ( 22 | 23 | {children} 24 | 25 | ); 26 | 27 | function makeClient() { 28 | const httpLink = new HttpLink({ 29 | uri: "https://main--hack-the-e-commerce.apollographos.net/graphql", 30 | }); 31 | 32 | return new NextSSRApolloClient({ 33 | cache: new NextSSRInMemoryCache(), 34 | link: 35 | typeof window === "undefined" ? 36 | (schemaLink as ApolloLink) 37 | : (httpLink as ApolloLink), 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /integration-tests/next/src/app/cc/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ApolloWrapper } from "./ApolloWrapper.tsx"; 2 | 3 | export default async function Layout({ 4 | children, 5 | }: { 6 | children: React.ReactNode; 7 | }) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /integration-tests/next/src/app/cc/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useSuspenseQuery } from "@apollo/experimental-nextjs-app-support/ssr"; 4 | import type { TypedDocumentNode } from "@apollo/client"; 5 | import { gql } from "@apollo/client"; 6 | 7 | const QUERY: TypedDocumentNode<{ 8 | products: { 9 | id: string; 10 | title: string; 11 | }[]; 12 | }> = gql` 13 | query { 14 | products { 15 | id 16 | title 17 | } 18 | } 19 | `; 20 | 21 | export default function Page() { 22 | const { data } = useSuspenseQuery(QUERY); 23 | 24 | return ( 25 |
    26 | {data.products.map(({ id, title }) => ( 27 |
  • {title}
  • 28 | ))} 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /integration-tests/next/src/app/client.ts: -------------------------------------------------------------------------------- 1 | import { schemaLink } from "@/libs/schemaLink.ts"; 2 | import { ApolloClient, InMemoryCache } from "@apollo/client"; 3 | import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc"; 4 | 5 | export const { getClient } = registerApolloClient(() => { 6 | return new ApolloClient({ 7 | cache: new InMemoryCache(), 8 | link: schemaLink, 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /integration-tests/next/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Create Next App", 3 | description: "Generated by create next app", 4 | }; 5 | 6 | export default function RootLayout({ 7 | children, 8 | }: { 9 | children: React.ReactNode; 10 | }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /integration-tests/next/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import type { TypedDocumentNode } from "@apollo/client"; 2 | import { gql } from "@apollo/client"; 3 | import { getClient } from "./client.ts"; 4 | 5 | const QUERY: TypedDocumentNode<{ 6 | products: { 7 | id: string; 8 | title: string; 9 | }[]; 10 | }> = gql` 11 | query { 12 | products { 13 | id 14 | title 15 | } 16 | } 17 | `; 18 | 19 | export default async function Home() { 20 | const { data } = await getClient().query({ query: QUERY }); 21 | return ( 22 |
    23 | {data.products.map(({ id, title }) => ( 24 |
  • {title}
  • 25 | ))} 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /integration-tests/next/src/libs/schemaLink.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from "@graphql-tools/schema"; 2 | import { gql } from "graphql-tag"; 3 | import { SchemaLink } from "@apollo/client/link/schema"; 4 | 5 | const typeDefs = gql` 6 | type Product { 7 | id: String! 8 | title: String! 9 | } 10 | type Query { 11 | products: [Product!]! 12 | } 13 | `; 14 | 15 | const resolvers = { 16 | Query: { 17 | products: async () => [ 18 | { 19 | id: "product:5", 20 | title: "Soft Warm Apollo Beanie", 21 | }, 22 | { 23 | id: "product:2", 24 | title: "Stainless Steel Water Bottle", 25 | }, 26 | { 27 | id: "product:3", 28 | title: "Athletic Baseball Cap", 29 | }, 30 | { 31 | id: "product:4", 32 | title: "Baby Onesies", 33 | }, 34 | { 35 | id: "product:1", 36 | title: "The Apollo T-Shirt", 37 | }, 38 | { 39 | id: "product:6", 40 | title: "The Apollo Socks", 41 | }, 42 | ], 43 | }, 44 | }; 45 | 46 | export const schema = makeExecutableSchema({ 47 | typeDefs, 48 | resolvers, 49 | }); 50 | 51 | export const schemaLink = new SchemaLink({ schema }); 52 | -------------------------------------------------------------------------------- /integration-tests/next/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { ApolloProvider } from "@apollo/client"; 2 | import { useApollo } from "../libs/apolloClient.ts"; 3 | import type { AppProps } from "next/app"; 4 | 5 | export default function App({ Component, pageProps }: AppProps) { 6 | const apolloClient = useApollo(pageProps); 7 | 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /integration-tests/next/src/pages/pages-no-ssr.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type { TypedDocumentNode } from "@apollo/client"; 4 | import { gql, useQuery } from "@apollo/client"; 5 | 6 | const QUERY: TypedDocumentNode<{ 7 | products: { 8 | id: string; 9 | title: string; 10 | }[]; 11 | }> = gql` 12 | query { 13 | products { 14 | id 15 | title 16 | } 17 | } 18 | `; 19 | 20 | export default function Page() { 21 | const { data } = useQuery(QUERY); 22 | 23 | if (!data) { 24 | return

loading

; 25 | } 26 | 27 | return ( 28 |
    29 | {data.products.map(({ id, title }) => ( 30 |
  • {title}
  • 31 | ))} 32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /integration-tests/next/src/pages/pages.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type { TypedDocumentNode } from "@apollo/client"; 4 | import { gql, useQuery } from "@apollo/client"; 5 | import type { GetStaticProps } from "next"; 6 | import { addApolloState, initializeApollo } from "@/libs/apolloClient.ts"; 7 | 8 | const QUERY: TypedDocumentNode<{ 9 | products: { 10 | id: string; 11 | title: string; 12 | }[]; 13 | }> = gql` 14 | query { 15 | products { 16 | id 17 | title 18 | } 19 | } 20 | `; 21 | 22 | export default function Page() { 23 | const { data } = useQuery(QUERY); 24 | 25 | if (!data) { 26 | throw new Error("should not happen, we have getServerSideProps!"); 27 | } 28 | 29 | return ( 30 |
    31 | {data.products.map(({ id, title }) => ( 32 |
  • {title}
  • 33 | ))} 34 |
35 | ); 36 | } 37 | 38 | export const getStaticProps: GetStaticProps = async function () { 39 | const apolloClient = initializeApollo(); 40 | 41 | await apolloClient.query({ 42 | query: QUERY, 43 | }); 44 | return addApolloState(apolloClient, { 45 | props: {}, 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /integration-tests/next/tests/playwright/apollo-client.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "shared/fixture"; 3 | 4 | test("RSC", async ({ page, blockRequest }) => { 5 | await page.goto("http://localhost:3000"); 6 | 7 | await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 8 | }); 9 | 10 | test("CC", async ({ page, blockRequest }) => { 11 | await page.goto("http://localhost:3000/cc"); 12 | 13 | await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 14 | }); 15 | 16 | test("Pages", async ({ page, blockRequest }) => { 17 | await page.goto("http://localhost:3000/pages"); 18 | 19 | await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 20 | }); 21 | 22 | test("Pages without SSR", async ({ page, withHar }) => { 23 | await page.goto("http://localhost:3000/pages-no-ssr"); 24 | 25 | await expect(page.getByText("loading")).toBeVisible(); 26 | await expect(page.getByText("loading")).not.toBeVisible(); 27 | await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 28 | }); 29 | -------------------------------------------------------------------------------- /integration-tests/next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "allowImportingTsExtensions": true, 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "paths": { 24 | "@/*": ["./src/*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /integration-tests/node-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dual-module-test-esm", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "type": "module", 6 | "scripts": { 7 | "build": "echo Done", 8 | "test": "node test-cjs.cjs && node test-esm.mjs" 9 | }, 10 | "dependencies": { 11 | "@apollo/client": "^3.10.1", 12 | "react": "^18.3.0", 13 | "react-dom": "^18.3.0" 14 | }, 15 | "devDependencies": { 16 | "resolve-esm": "^1.4.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /integration-tests/node-standard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dual-module-test-standard", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "build": "echo Done", 7 | "test": "node test-cjs.js && node test-esm.mjs" 8 | }, 9 | "dependencies": { 10 | "@apollo/client": "^3.10.1", 11 | "react": "^18.3.0", 12 | "react-dom": "^18.3.0" 13 | }, 14 | "devDependencies": { 15 | "resolve-esm": "^1.4.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /integration-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "integration-tests", 3 | "license": "UNLICENSED", 4 | "overrides": { 5 | "@playwright/test": "1.43.1", 6 | "playwright": "1.43.1", 7 | "serve": "^14.2.0" 8 | }, 9 | "workspaces": [ 10 | "browser-esm", 11 | "cra4", 12 | "cra5", 13 | "next", 14 | "vite", 15 | "vite-swc", 16 | "node-standard", 17 | "node-esm", 18 | "shared" 19 | ], 20 | "devDependencies": { 21 | "@types/lodash": "^4.14.195" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /integration-tests/peerdeps-tsc/.gitignore: -------------------------------------------------------------------------------- 1 | # explicitly avoiding to check in this one 2 | # so we run this test always with the latest version 3 | package-lock.json 4 | dist 5 | node_modules 6 | -------------------------------------------------------------------------------- /integration-tests/peerdeps-tsc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "peerdeps-tsc", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tsc" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "graphql": "^16.0.0", 14 | "graphql-ws": "^5.5.5", 15 | "@types/react": "^18.0.0", 16 | "@types/react-dom": "^18.0.0", 17 | "subscriptions-transport-ws": "^0.11.0", 18 | "typescript": "latest" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /integration-tests/peerdeps-tsc/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "@apollo/client"; 2 | -------------------------------------------------------------------------------- /integration-tests/peerdeps-tsc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "rootDir": "./src", 6 | "outDir": "./dist", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "declarationMap": true, 10 | "esModuleInterop": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "strict": true, 13 | "skipLibCheck": false, 14 | "types": ["react", "react-dom"], 15 | "lib": ["es2018", "dom"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /integration-tests/shared/fixture.ts: -------------------------------------------------------------------------------- 1 | import { test as base, expect } from "@playwright/test"; 2 | 3 | export const test = base.extend<{ 4 | withHar: import("@playwright/test").Page; 5 | blockRequest: import("@playwright/test").Page; 6 | }>({ 7 | page: async ({ page }, use) => { 8 | page.on("pageerror", (error) => { 9 | expect(error.stack || error).toBe("no error"); 10 | }); 11 | await use(page); 12 | }, 13 | withHar: async ({ page }, use) => { 14 | await page.routeFromHAR("../api.har", { 15 | url: "**/graphql", 16 | notFound: "abort", 17 | }); 18 | await use(page); 19 | }, 20 | blockRequest: async ({ page }, use) => { 21 | await page.routeFromHAR("../empty.har", { 22 | url: "**/graphql", 23 | notFound: "abort", 24 | }); 25 | await use(page); 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /integration-tests/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shared", 3 | "license": "UNLICENSED", 4 | "peerDependencies": { 5 | "@playwright/test": "*" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /integration-tests/shared/playwright.config.ts: -------------------------------------------------------------------------------- 1 | export const baseConfig = { 2 | webServer: { 3 | command: "yarn serve-app", 4 | url: "http://localhost:3000", 5 | timeout: 120 * 1000, 6 | reuseExistingServer: !process.env.CI, 7 | }, 8 | timeout: 120 * 1000, 9 | use: { 10 | headless: true, 11 | viewport: { width: 1280, height: 720 }, 12 | ignoreHTTPSErrors: true, 13 | }, 14 | testDir: "tests/playwright/", 15 | } as const; 16 | -------------------------------------------------------------------------------- /integration-tests/vite-swc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite + React + TS 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /integration-tests/vite-swc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-swc", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --port 3000", 8 | "build": "tsc && vite build", 9 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview --port 3000", 11 | "serve-app": "npm run preview", 12 | "test": "playwright test" 13 | }, 14 | "dependencies": { 15 | "@apollo/client": "^3.10.1", 16 | "react": "^18.3.0", 17 | "react-dom": "^18.3.0" 18 | }, 19 | "devDependencies": { 20 | "@playwright/test": "*", 21 | "@types/react": "^18.0.37", 22 | "@types/react-dom": "^18.0.11", 23 | "@typescript-eslint/eslint-plugin": "^5.59.0", 24 | "@typescript-eslint/parser": "^5.59.0", 25 | "@vitejs/plugin-react-swc": "^3.0.0", 26 | "eslint": "^8.38.0", 27 | "eslint-plugin-react-hooks": "^4.6.0", 28 | "eslint-plugin-react-refresh": "^0.3.4", 29 | "playwright": "*", 30 | "shared": "*", 31 | "typescript": "^5.0.2", 32 | "vite": "^4.3.9" 33 | }, 34 | "overrides": { 35 | "next": { 36 | "@playwright/test": "$@playwright/test" 37 | }, 38 | "@apollo/client": { 39 | "react": "$react", 40 | "react-dom": "$react-dom" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /integration-tests/vite-swc/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { baseConfig } from "shared/playwright.config.ts"; 2 | import { defineConfig } from "@playwright/test"; 3 | 4 | export default defineConfig(baseConfig); 5 | -------------------------------------------------------------------------------- /integration-tests/vite-swc/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | 5 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 6 | 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /integration-tests/vite-swc/tests/playwright/apollo-client.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "shared/fixture.ts"; 3 | 4 | test("Basic Test", async ({ page, withHar }) => { 5 | await page.goto("http://localhost:3000"); 6 | 7 | await expect(page.getByText("loading")).toBeVisible(); 8 | await expect(page.getByText("loading")).not.toBeVisible(); 9 | await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 10 | }); 11 | -------------------------------------------------------------------------------- /integration-tests/vite-swc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /integration-tests/vite-swc/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /integration-tests/vite-swc/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react-swc"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }); 8 | -------------------------------------------------------------------------------- /integration-tests/vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite + React + TS 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /integration-tests/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --port 3000", 8 | "build": "tsc && vite build", 9 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview --port 3000", 11 | "serve-app": "npm run preview", 12 | "test": "playwright test" 13 | }, 14 | "dependencies": { 15 | "@apollo/client": "^3.10.1", 16 | "graphql": "^16.8.1", 17 | "react": "^18.3.0", 18 | "react-dom": "^18.3.0" 19 | }, 20 | "devDependencies": { 21 | "@playwright/test": "*", 22 | "@types/react": "^18.0.37", 23 | "@types/react-dom": "^18.0.11", 24 | "@typescript-eslint/eslint-plugin": "^5.59.0", 25 | "@typescript-eslint/parser": "^5.59.0", 26 | "@vitejs/plugin-react": "^4.0.0", 27 | "eslint": "^8.38.0", 28 | "eslint-plugin-react-hooks": "^4.6.0", 29 | "eslint-plugin-react-refresh": "^0.3.4", 30 | "playwright": "*", 31 | "shared": "*", 32 | "typescript": "^5.0.2", 33 | "vite": "^4.3.9" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /integration-tests/vite/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { baseConfig } from "shared/playwright.config.ts"; 2 | import { defineConfig } from "@playwright/test"; 3 | 4 | export default defineConfig(baseConfig); 5 | -------------------------------------------------------------------------------- /integration-tests/vite/src/App.tsx: -------------------------------------------------------------------------------- 1 | import type { TypedDocumentNode } from "@apollo/client"; 2 | 3 | import { 4 | useQuery, 5 | gql, 6 | InMemoryCache, 7 | ApolloClient, 8 | ApolloProvider, 9 | ApolloLink, 10 | Observable, 11 | HttpLink, 12 | } from "@apollo/client"; 13 | 14 | const delayLink = new ApolloLink((operation, forward) => { 15 | return new Observable((observer) => { 16 | const handle = setTimeout(() => { 17 | forward(operation).subscribe(observer); 18 | }, 1000); 19 | 20 | return () => clearTimeout(handle); 21 | }); 22 | }); 23 | 24 | const httpLink = new HttpLink({ 25 | uri: "https://main--hack-the-e-commerce.apollographos.net/graphql", 26 | }); 27 | 28 | const client = new ApolloClient({ 29 | cache: new InMemoryCache(), 30 | link: ApolloLink.from([delayLink, httpLink]), 31 | }); 32 | 33 | const QUERY: TypedDocumentNode<{ 34 | products: { 35 | id: string; 36 | title: string; 37 | }[]; 38 | }> = gql` 39 | query { 40 | products { 41 | id 42 | title 43 | } 44 | } 45 | `; 46 | 47 | export default function App() { 48 | return ( 49 | 50 |
51 | 52 | ); 53 | } 54 | 55 | function Main() { 56 | const { data } = useQuery(QUERY); 57 | 58 | return data ? 59 |
    60 | {data?.products.map(({ id, title }) =>
  • {title}
  • )} 61 |
62 | : <>loading; 63 | } 64 | -------------------------------------------------------------------------------- /integration-tests/vite/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | 5 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 6 | 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /integration-tests/vite/tests/playwright/apollo-client.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "shared/fixture.ts"; 3 | 4 | test("Basic Test", async ({ page, withHar }) => { 5 | await page.goto("http://localhost:3000"); 6 | 7 | await expect(page.getByText("loading")).toBeVisible(); 8 | await expect(page.getByText("loading")).not.toBeVisible(); 9 | await expect(page.getByText("Soft Warm Apollo Beanie")).toBeVisible(); 10 | }); 11 | -------------------------------------------------------------------------------- /integration-tests/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /integration-tests/vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /integration-tests/vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }); 8 | -------------------------------------------------------------------------------- /patches/@testing-library+react+16.1.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@testing-library/react/dist/pure.js b/node_modules/@testing-library/react/dist/pure.js 2 | index 7b62fa7..9ad1d9e 100644 3 | --- a/node_modules/@testing-library/react/dist/pure.js 4 | +++ b/node_modules/@testing-library/react/dist/pure.js 5 | @@ -223,7 +223,7 @@ function renderRoot(ui, { 6 | function render(ui, { 7 | container, 8 | baseElement = container, 9 | - legacyRoot = false, 10 | + legacyRoot = React.version.startsWith("17"), 11 | queries, 12 | hydrate = false, 13 | wrapper 14 | -------------------------------------------------------------------------------- /patches/eslint-plugin-testing-library+7.1.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/eslint-plugin-testing-library/dist/rules/render-result-naming-convention.js b/node_modules/eslint-plugin-testing-library/dist/rules/render-result-naming-convention.js 2 | index dd77b02..d1a581d 100644 3 | --- a/node_modules/eslint-plugin-testing-library/dist/rules/render-result-naming-convention.js 4 | +++ b/node_modules/eslint-plugin-testing-library/dist/rules/render-result-naming-convention.js 5 | @@ -5,7 +5,7 @@ const utils_1 = require("@typescript-eslint/utils"); 6 | const create_testing_library_rule_1 = require("../create-testing-library-rule"); 7 | const node_utils_1 = require("../node-utils"); 8 | exports.RULE_NAME = 'render-result-naming-convention'; 9 | -const ALLOWED_VAR_NAMES = ['view', 'utils']; 10 | +const ALLOWED_VAR_NAMES = ['view', 'utils', 'renderStream']; 11 | const ALLOWED_VAR_NAMES_TEXT = ALLOWED_VAR_NAMES.map((name) => `\`${name}\``) 12 | .join(', ') 13 | .replace(/, ([^,]*)$/, ', or $1'); 14 | -------------------------------------------------------------------------------- /patches/pretty-format+29.7.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/pretty-format/build/index.js b/node_modules/pretty-format/build/index.js 2 | index 8d3a562..879eed7 100644 3 | --- a/node_modules/pretty-format/build/index.js 4 | +++ b/node_modules/pretty-format/build/index.js 5 | @@ -103,6 +103,9 @@ function printBasicValue(val, printFunctionName, escapeRegex, escapeString) { 6 | if (val === null) { 7 | return 'null'; 8 | } 9 | + if (val.name === 'ApolloError' || val.name === 'GraphQLError') { 10 | + return null 11 | + } 12 | const typeOf = typeof val; 13 | if (typeOf === 'number') { 14 | return printNumber(val); 15 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["apollo-open-source"], 3 | "ignorePaths": ["**/integration-tests/**"], 4 | "dependencyDashboard": true, 5 | "pathRules": [ 6 | { 7 | "paths": ["docs/package.json"], 8 | "extends": ["apollo-docs"] 9 | } 10 | ], 11 | "packageRules": [ 12 | { 13 | "groupName": "all @types", 14 | "groupSlug": "all-types", 15 | "matchPackagePatterns": ["@types/*"] 16 | }, 17 | { 18 | "groupName": "all devDependencies", 19 | "groupSlug": "all-dev", 20 | "matchPackagePatterns": ["*"], 21 | "matchDepTypes": ["devDependencies"] 22 | }, 23 | { 24 | "groupName": "all dependencies - patch updates", 25 | "groupSlug": "all-patch", 26 | "matchPackagePatterns": ["*"], 27 | "matchUpdateTypes": ["patch"] 28 | } 29 | ], 30 | "ignoreDeps": [ 31 | "typedoc", 32 | "react-17", 33 | "react-dom-17", 34 | "react-18", 35 | "react-dom-18", 36 | "@testing-library/react-12", 37 | "@rollup/plugin-node-resolve", 38 | "rollup", 39 | "glob", 40 | "prettier", 41 | "eslint-plugin-react-compiler" 42 | ], 43 | "reviewers": [], 44 | "reviewersFromCodeOwners": false, 45 | "ignoreReviewers": ["team:@apollographql/client-typescript"] 46 | } 47 | -------------------------------------------------------------------------------- /scripts/codemods/ac2-to-ac3/examples/client-and-cache.ts: -------------------------------------------------------------------------------- 1 | import Client from "apollo-client"; 2 | import { InMemoryCache, InMemoryCacheConfig } from "apollo-cache-inmemory"; 3 | import gql from "graphql-tag"; 4 | 5 | const client = new Client({ 6 | cache: new InMemoryCache({} as InMemoryCacheConfig), 7 | }); 8 | 9 | client.query({ 10 | query: gql` 11 | query { 12 | __typename 13 | } 14 | `, 15 | }); 16 | -------------------------------------------------------------------------------- /scripts/codemods/ac2-to-ac3/examples/link-packages.js: -------------------------------------------------------------------------------- 1 | import { ApolloLink, Observable } from "apollo-link"; 2 | import { HttpLink } from "apollo-link-http"; 3 | import { BatchLink } from "apollo-link-batch"; 4 | import { BatchHttpLink } from "apollo-link-batch-http"; 5 | import { setContext } from "apollo-link-context"; 6 | import { ErrorLink } from "apollo-link-error"; 7 | import { 8 | VERSION, 9 | createPersistedQueryLink, 10 | } from "apollo-link-persisted-queries"; 11 | import { RetryLink } from "apollo-link-retry"; 12 | import { WebSocketLink } from "apollo-link-ws"; 13 | // This package was unusual for having a default export. 14 | import SchemaLink from "apollo-link-schema"; 15 | -------------------------------------------------------------------------------- /scripts/codemods/ac2-to-ac3/examples/react-packages.tsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@apollo/react-hooks"; 2 | import { graphql, withApollo, ChildProps } from "@apollo/react-hoc"; 3 | import { MockProvider } from "@apollo/react-testing"; 4 | 5 | import { 6 | getDataFromTree, 7 | getMarkupFromTree, 8 | renderToStringWithData, 9 | } from "@apollo/react-ssr"; 10 | 11 | import { Query, Mutation, Subscription } from "@apollo/react-components"; 12 | -------------------------------------------------------------------------------- /scripts/codemods/ac2-to-ac3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "jscodeshift": "0.16.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /scripts/codemods/data-masking/examples/queries-codegen.ts: -------------------------------------------------------------------------------- 1 | import { graphql } from "./gql"; 2 | 3 | export const query = graphql(` 4 | query GetCurrentUser { 5 | currentUser { 6 | id 7 | ...CurrentUserFields 8 | } 9 | } 10 | `); 11 | 12 | export const currentUserFieldsFragment = graphql(` 13 | fragment CurrentUserFields on User { 14 | name 15 | ...ProfileFields 16 | } 17 | `); 18 | 19 | export const profileFieldsFragment = graphql(` 20 | fragment ProfileFields on User { 21 | profile { 22 | id 23 | avatarUrl 24 | } 25 | } 26 | `); 27 | -------------------------------------------------------------------------------- /scripts/codemods/data-masking/examples/queries-react.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { gql, useQuery } from "@apollo/client"; 3 | 4 | const ProfileFieldsFragment = gql` 5 | fragment ProfileFields on User { 6 | profile { 7 | id 8 | avatarUrl 9 | } 10 | } 11 | `; 12 | 13 | const fragment = gql` 14 | fragment CurrentUserFields on User { 15 | name 16 | ...ProfileFields 17 | } 18 | 19 | ${ProfileFieldsFragment} 20 | `; 21 | 22 | export function MyComponent() { 23 | const { data, loading } = useQuery(gql` 24 | query GetCurrentUser { 25 | currentUser { 26 | id 27 | ...CurrentUserFields 28 | } 29 | } 30 | 31 | ${fragment} 32 | `); 33 | 34 | if (loading) { 35 | return
Loading...
; 36 | } 37 | 38 | return
{JSON.stringify(data)}
; 39 | } 40 | -------------------------------------------------------------------------------- /scripts/codemods/data-masking/examples/queries.graphql: -------------------------------------------------------------------------------- 1 | query GetCurrentUser { 2 | currentUser { 3 | id 4 | ...CurrentUserFields 5 | } 6 | } 7 | 8 | fragment CurrentUserFields on User { 9 | name 10 | ...ProfileFields 11 | } 12 | 13 | fragment ProfileFields on User { 14 | profile { 15 | id 16 | avatarUrl 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/codemods/data-masking/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "jscodeshift": "17.1.1", 5 | "@types/jscodeshift": "0.12.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /scripts/codemods/data-masking/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "strictNullChecks": true, 5 | "noUnusedParameters": false, 6 | "noUnusedLocals": true, 7 | "skipLibCheck": true, 8 | "moduleResolution": "node", 9 | "importHelpers": true, 10 | "sourceMap": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "target": "es5", 14 | "module": "es2015", 15 | "esModuleInterop": true, 16 | "lib": ["es2015", "esnext.asynciterable"], 17 | "strict": true, 18 | "noEmit": true 19 | }, 20 | "include": ["./unmask.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /scripts/codemods/misc/mockLinkRejection.ts: -------------------------------------------------------------------------------- 1 | import * as recast from "recast"; 2 | const n = recast.types.namedTypes; 3 | const b = recast.types.builders; 4 | 5 | export default function (fileInfo: any, api: any) { 6 | const ast = recast.parse(fileInfo.source, { 7 | parser: require("recast/parsers/typescript"), 8 | }); 9 | 10 | // Transform mockSingleLink(reject, ...) to 11 | // mockSingleLink(...).setOnError(reject): 12 | 13 | const transformed = recast.visit(ast, { 14 | visitCallExpression(path) { 15 | this.traverse(path); 16 | const node = path.node; 17 | if ( 18 | n.Identifier.check(node.callee) && 19 | node.callee.name === "mockSingleLink" 20 | ) { 21 | const firstArg = node.arguments[0]; 22 | if ( 23 | (n.Identifier.check(firstArg) && firstArg.name === "reject") || 24 | n.Function.check(firstArg) 25 | ) { 26 | path.get("arguments").shift(); 27 | path.replace( 28 | b.callExpression( 29 | b.memberExpression(node, b.identifier("setOnError"), false), 30 | [firstArg] 31 | ) 32 | ); 33 | } 34 | } 35 | }, 36 | }); 37 | 38 | return recast.print(transformed).code; 39 | } 40 | -------------------------------------------------------------------------------- /scripts/memory/README.md: -------------------------------------------------------------------------------- 1 | # Memory and Garbage Collection tests 2 | 3 | This directory contains a few important memory-related tests that were 4 | difficult to run within the usual Jest environment. Instead, these tests 5 | run directly in Node.js, importing `@apollo/client` from the `../../dist` 6 | directory, rather than from `../../src`. 7 | 8 | ## Running the tests 9 | 10 | Run `npm install` in this directory, followed by `npm test`. Failure is 11 | indicated by a non-zero exit code. 12 | -------------------------------------------------------------------------------- /scripts/memory/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apollo-client-memory-tests", 3 | "private": true, 4 | "scripts": { 5 | "test": "mocha --exit -n expose-gc tests.js" 6 | }, 7 | "dependencies": { 8 | "@apollo/client": "file:../../dist", 9 | "graphql": "^16.0.0" 10 | }, 11 | "devDependencies": { 12 | "mocha": "11.0.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/cache/inmemory/__tests__/__snapshots__/entityStore.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`EntityStore ignores retainment count for ROOT_QUERY 1`] = ` 4 | Object { 5 | "Author:Allie Brosh": Object { 6 | "__typename": "Author", 7 | "name": "Allie Brosh", 8 | }, 9 | "Book:1982156945": Object { 10 | "__typename": "Book", 11 | "author": Object { 12 | "__ref": "Author:Allie Brosh", 13 | }, 14 | "title": "Solutions and Other Problems", 15 | }, 16 | "ROOT_QUERY": Object { 17 | "__typename": "Query", 18 | "book": Object { 19 | "__ref": "Book:1982156945", 20 | }, 21 | }, 22 | "__META": Object { 23 | "extraRootIds": Array [ 24 | "Author:Allie Brosh", 25 | ], 26 | }, 27 | } 28 | `; 29 | 30 | exports[`EntityStore ignores retainment count for ROOT_QUERY 2`] = ` 31 | Object { 32 | "Author:Allie Brosh": Object { 33 | "__typename": "Author", 34 | "name": "Allie Brosh", 35 | }, 36 | "__META": Object { 37 | "extraRootIds": Array [ 38 | "Author:Allie Brosh", 39 | ], 40 | }, 41 | } 42 | `; 43 | -------------------------------------------------------------------------------- /src/cache/inmemory/__tests__/__snapshots__/fragmentMatcher.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`policies.fragmentMatches can infer fuzzy subtypes heuristically 1`] = ` 4 | Object { 5 | "ROOT_QUERY": Object { 6 | "__typename": "Query", 7 | "objects": Array [ 8 | Object { 9 | "__typename": "E", 10 | "c": "ce", 11 | }, 12 | Object { 13 | "__typename": "F", 14 | "c": "cf", 15 | }, 16 | Object { 17 | "__typename": "G", 18 | "c": "cg", 19 | }, 20 | Object { 21 | "__typename": "TooLong", 22 | }, 23 | Object { 24 | "__typename": "H", 25 | }, 26 | ], 27 | }, 28 | } 29 | `; 30 | -------------------------------------------------------------------------------- /src/cache/inmemory/__tests__/__snapshots__/readFromStore.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`reading from the store returns === results for different queries 1`] = ` 4 | Object { 5 | "ROOT_QUERY": Object { 6 | "__typename": "Query", 7 | "a": Array [ 8 | "a", 9 | "y", 10 | "y", 11 | ], 12 | "b": Object { 13 | "c": "C", 14 | "d": "D", 15 | }, 16 | }, 17 | } 18 | `; 19 | -------------------------------------------------------------------------------- /src/cache/inmemory/__tests__/__snapshots__/roundtrip.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`roundtrip fragments should throw an error on two of the same inline fragment types 1`] = ` 4 | [MockFunction] { 5 | "calls": Array [ 6 | Array [ 7 | "Missing field '%s' while writing result %o", 8 | "rank", 9 | Object { 10 | "__typename": "Jedi", 11 | "name": "Luke Skywalker", 12 | "side": "bright", 13 | }, 14 | ], 15 | ], 16 | "results": Array [ 17 | Object { 18 | "type": "return", 19 | "value": undefined, 20 | }, 21 | ], 22 | } 23 | `; 24 | 25 | exports[`roundtrip fragments should throw on error on two of the same spread fragment types 1`] = ` 26 | [MockFunction] { 27 | "calls": Array [ 28 | Array [ 29 | "Missing field '%s' while writing result %o", 30 | "rank", 31 | Object { 32 | "__typename": "Jedi", 33 | "name": "Luke Skywalker", 34 | "side": "bright", 35 | }, 36 | ], 37 | ], 38 | "results": Array [ 39 | Object { 40 | "type": "return", 41 | "value": undefined, 42 | }, 43 | ], 44 | } 45 | `; 46 | -------------------------------------------------------------------------------- /src/cache/inmemory/fixPolyfills.ts: -------------------------------------------------------------------------------- 1 | // Most JavaScript environments do not need the workarounds implemented in 2 | // fixPolyfills.native.ts, so importing fixPolyfills.ts merely imports 3 | // this empty module, adding nothing to bundle sizes or execution times. 4 | // When bundling for React Native, we substitute fixPolyfills.native.js 5 | // for fixPolyfills.js (see the "react-native" section of package.json), 6 | // to work around problems with Map and Set polyfills in older versions of 7 | // React Native (which should have been fixed in react-native@0.59.0): 8 | // https://github.com/apollographql/apollo-client/pull/5962 9 | -------------------------------------------------------------------------------- /src/config/jest/areApolloErrorsEqual.ts: -------------------------------------------------------------------------------- 1 | import type { ApolloError } from "../../errors/index.js"; 2 | import type { Tester } from "@jest/expect-utils"; 3 | function isApolloError(e: any): e is ApolloError { 4 | return e instanceof Error && e.name == "ApolloError"; 5 | } 6 | 7 | export const areApolloErrorsEqual: Tester = function (a, b, customTesters) { 8 | const isAApolloError = isApolloError(a); 9 | const isBApolloError = isApolloError(b); 10 | 11 | if (isAApolloError && isBApolloError) { 12 | return ( 13 | a.message === b.message && 14 | this.equals(a.graphQLErrors, b.graphQLErrors, customTesters) && 15 | this.equals(a.protocolErrors, b.protocolErrors, customTesters) && 16 | this.equals(a.clientErrors, b.clientErrors, customTesters) && 17 | this.equals(a.networkError, b.networkError, customTesters) && 18 | this.equals(a.cause, b.cause, customTesters) && 19 | this.equals(a.extraInfo, b.extraInfo, customTesters) 20 | ); 21 | } else if (isAApolloError === isBApolloError) { 22 | return undefined; 23 | } else { 24 | return false; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/config/jest/areGraphQlErrorsEqual.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from "graphql"; 2 | import type { Tester } from "@jest/expect-utils"; 3 | 4 | export const areGraphQLErrorsEqual: Tester = function (a, b, customTesters) { 5 | if (a instanceof GraphQLError || b instanceof GraphQLError) { 6 | return this.equals( 7 | a instanceof GraphQLError ? a.toJSON() : a, 8 | b instanceof GraphQLError ? b.toJSON() : b, 9 | customTesters 10 | ); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/config/jest/setup.ts: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | import { TextEncoder, TextDecoder } from "util"; 3 | global.TextEncoder ??= TextEncoder; 4 | // @ts-ignore 5 | global.TextDecoder ??= TextDecoder; 6 | import "@testing-library/jest-dom"; 7 | import { loadErrorMessageHandler } from "../../dev/loadErrorMessageHandler.js"; 8 | import "../../testing/matchers/index.js"; 9 | import { areApolloErrorsEqual } from "./areApolloErrorsEqual.js"; 10 | import { areGraphQLErrorsEqual } from "./areGraphQlErrorsEqual.js"; 11 | 12 | // Turn off warnings for repeated fragment names 13 | gql.disableFragmentWarnings(); 14 | 15 | process.on("unhandledRejection", () => {}); 16 | 17 | loadErrorMessageHandler(); 18 | 19 | function fail(reason = "fail was called in a test.") { 20 | expect(reason).toBe(undefined); 21 | } 22 | 23 | // @ts-ignore 24 | globalThis.fail = fail; 25 | 26 | if (!Symbol.dispose) { 27 | Object.defineProperty(Symbol, "dispose", { 28 | value: Symbol("dispose"), 29 | }); 30 | } 31 | if (!Symbol.asyncDispose) { 32 | Object.defineProperty(Symbol, "asyncDispose", { 33 | value: Symbol("asyncDispose"), 34 | }); 35 | } 36 | 37 | // @ts-ignore 38 | expect.addEqualityTesters([areApolloErrorsEqual, areGraphQLErrorsEqual]); 39 | 40 | // not available in JSDOM 🙄 41 | global.structuredClone = (val) => JSON.parse(JSON.stringify(val)); 42 | -------------------------------------------------------------------------------- /src/dev/index.ts: -------------------------------------------------------------------------------- 1 | export { loadDevMessages } from "./loadDevMessages.js"; 2 | export { loadErrorMessageHandler } from "./loadErrorMessageHandler.js"; 3 | export { loadErrorMessages } from "./loadErrorMessages.js"; 4 | export { setErrorMessageHandler } from "./setErrorMessageHandler.js"; 5 | export type { ErrorMessageHandler } from "./setErrorMessageHandler.js"; 6 | -------------------------------------------------------------------------------- /src/dev/loadDevMessages.ts: -------------------------------------------------------------------------------- 1 | import { devDebug, devError, devLog, devWarn } from "../invariantErrorCodes.js"; 2 | import { loadErrorMessageHandler } from "./loadErrorMessageHandler.js"; 3 | 4 | export function loadDevMessages() { 5 | loadErrorMessageHandler(devDebug, devError, devLog, devWarn); 6 | } 7 | -------------------------------------------------------------------------------- /src/dev/loadErrorMessageHandler.ts: -------------------------------------------------------------------------------- 1 | import type { ErrorCodes } from "../invariantErrorCodes.js"; 2 | import { global } from "../utilities/globals/index.js"; 3 | import { ApolloErrorMessageHandler } from "../utilities/globals/invariantWrappers.js"; 4 | import type { ErrorMessageHandler } from "./setErrorMessageHandler.js"; 5 | import { setErrorMessageHandler } from "./setErrorMessageHandler.js"; 6 | 7 | /** 8 | * Injects Apollo Client's default error message handler into the application and 9 | * also loads the error codes that are passed in as arguments. 10 | */ 11 | export function loadErrorMessageHandler(...errorCodes: ErrorCodes[]) { 12 | setErrorMessageHandler(handler as typeof handler & ErrorCodes); 13 | 14 | for (const codes of errorCodes) { 15 | Object.assign(handler, codes); 16 | } 17 | 18 | return handler; 19 | } 20 | 21 | const handler = ((message: string | number, args: unknown[]) => { 22 | if (typeof message === "number") { 23 | const definition = global[ApolloErrorMessageHandler]![message]; 24 | if (!message || !definition?.message) return; 25 | message = definition.message; 26 | } 27 | return args.reduce( 28 | (msg, arg) => msg.replace(/%[sdfo]/, String(arg)), 29 | String(message) 30 | ); 31 | }) as ErrorMessageHandler & ErrorCodes; 32 | -------------------------------------------------------------------------------- /src/dev/loadErrorMessages.ts: -------------------------------------------------------------------------------- 1 | import { errorCodes } from "../invariantErrorCodes.js"; 2 | import { loadErrorMessageHandler } from "./loadErrorMessageHandler.js"; 3 | 4 | export function loadErrorMessages() { 5 | loadErrorMessageHandler(errorCodes); 6 | } 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./core/index.js"; 2 | export * from "./react/index.js"; 3 | -------------------------------------------------------------------------------- /src/invariantErrorCodes.ts: -------------------------------------------------------------------------------- 1 | export interface ErrorCodes { 2 | [key: number]: { file: string; condition?: string; message?: string }; 3 | } 4 | 5 | export const errorCodes: ErrorCodes = {}; 6 | export const devDebug: ErrorCodes = {}; 7 | export const devLog: ErrorCodes = {}; 8 | export const devWarn: ErrorCodes = {}; 9 | export const devError: ErrorCodes = {}; 10 | -------------------------------------------------------------------------------- /src/link/batch-http/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./batchHttpLink.js"; 2 | -------------------------------------------------------------------------------- /src/link/batch/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./batchLink.js"; 2 | -------------------------------------------------------------------------------- /src/link/core/concat.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink } from "./ApolloLink.js"; 2 | 3 | export const concat = ApolloLink.concat; 4 | -------------------------------------------------------------------------------- /src/link/core/empty.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink } from "./ApolloLink.js"; 2 | 3 | export const empty = ApolloLink.empty; 4 | -------------------------------------------------------------------------------- /src/link/core/execute.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink } from "./ApolloLink.js"; 2 | 3 | export const execute = ApolloLink.execute; 4 | -------------------------------------------------------------------------------- /src/link/core/from.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink } from "./ApolloLink.js"; 2 | 3 | export const from = ApolloLink.from; 4 | -------------------------------------------------------------------------------- /src/link/core/index.ts: -------------------------------------------------------------------------------- 1 | import "../../utilities/globals/index.js"; 2 | 3 | export { empty } from "./empty.js"; 4 | export { from } from "./from.js"; 5 | export { split } from "./split.js"; 6 | export { concat } from "./concat.js"; 7 | export { execute } from "./execute.js"; 8 | export { ApolloLink } from "./ApolloLink.js"; 9 | 10 | export type * from "./types.js"; 11 | -------------------------------------------------------------------------------- /src/link/core/split.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink } from "./ApolloLink.js"; 2 | 3 | export const split = ApolloLink.split; 4 | -------------------------------------------------------------------------------- /src/link/http/HttpLink.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink } from "../core/index.js"; 2 | import type { HttpOptions } from "./selectHttpOptionsAndBody.js"; 3 | import { createHttpLink } from "./createHttpLink.js"; 4 | 5 | export class HttpLink extends ApolloLink { 6 | constructor(public options: HttpOptions = {}) { 7 | super(createHttpLink(options).request); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/link/http/__tests__/checkFetcher.ts: -------------------------------------------------------------------------------- 1 | import { checkFetcher } from "../checkFetcher"; 2 | import { voidFetchDuringEachTest } from "./helpers"; 3 | 4 | describe("checkFetcher", () => { 5 | voidFetchDuringEachTest(); 6 | 7 | it("throws if no fetch is present", () => { 8 | expect(() => checkFetcher(undefined)).toThrow( 9 | /has not been found globally/ 10 | ); 11 | }); 12 | 13 | it("does not throws if no fetch is present but a fetch is passed", () => { 14 | expect(() => checkFetcher((() => {}) as any)).not.toThrow(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/link/http/__tests__/helpers.ts: -------------------------------------------------------------------------------- 1 | export function voidFetchDuringEachTest() { 2 | let fetchDesc = Object.getOwnPropertyDescriptor(window, "fetch"); 3 | 4 | beforeEach(() => { 5 | fetchDesc = fetchDesc || Object.getOwnPropertyDescriptor(window, "fetch"); 6 | if (fetchDesc?.configurable) { 7 | delete (window as any).fetch; 8 | } 9 | }); 10 | 11 | afterEach(() => { 12 | if (fetchDesc?.configurable) { 13 | Object.defineProperty(window, "fetch", fetchDesc); 14 | } 15 | }); 16 | } 17 | 18 | describe("voidFetchDuringEachTest", () => { 19 | voidFetchDuringEachTest(); 20 | 21 | it("hides the global.fetch function", () => { 22 | expect(window.fetch).toBe(void 0); 23 | expect(() => fetch).toThrowError(ReferenceError); 24 | }); 25 | 26 | it("globalThis === window", () => { 27 | expect(globalThis).toBe(window); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/link/http/__tests__/selectURI.ts: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | 3 | import { createOperation } from "../../utils/createOperation"; 4 | import { selectURI } from "../selectURI"; 5 | 6 | const query = gql` 7 | query SampleQuery { 8 | stub { 9 | id 10 | } 11 | } 12 | `; 13 | 14 | describe("selectURI", () => { 15 | it("returns a passed in string", () => { 16 | const uri = "/somewhere"; 17 | const operation = createOperation({ uri }, { query }); 18 | expect(selectURI(operation)).toEqual(uri); 19 | }); 20 | 21 | it("returns a fallback of /graphql", () => { 22 | const uri = "/graphql"; 23 | const operation = createOperation({}, { query }); 24 | expect(selectURI(operation)).toEqual(uri); 25 | }); 26 | 27 | it("returns the result of a UriFunction", () => { 28 | const uri = "/somewhere"; 29 | const operation = createOperation({}, { query }); 30 | expect(selectURI(operation, () => uri)).toEqual(uri); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/link/http/__tests__/serializeFetchParameter.ts: -------------------------------------------------------------------------------- 1 | import { serializeFetchParameter } from "../serializeFetchParameter"; 2 | 3 | describe("serializeFetchParameter", () => { 4 | it("throws a parse error on an unparsable body", () => { 5 | const b = {}; 6 | const a = { b }; 7 | (b as any).a = a; 8 | 9 | expect(() => serializeFetchParameter(b, "Label")).toThrow(/Label/); 10 | }); 11 | 12 | it("returns a correctly parsed body", () => { 13 | const body = { no: "thing" }; 14 | 15 | expect(serializeFetchParameter(body, "Label")).toEqual('{"no":"thing"}'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/link/http/checkFetcher.ts: -------------------------------------------------------------------------------- 1 | import { newInvariantError } from "../../utilities/globals/index.js"; 2 | 3 | export const checkFetcher = (fetcher: typeof fetch | undefined) => { 4 | if (!fetcher && typeof fetch === "undefined") { 5 | throw newInvariantError(` 6 | "fetch" has not been found globally and no fetcher has been \ 7 | configured. To fix this, install a fetch package (like \ 8 | https://www.npmjs.com/package/cross-fetch), instantiate the \ 9 | fetcher, and pass it into your HttpLink constructor. For example: 10 | 11 | import fetch from 'cross-fetch'; 12 | import { ApolloClient, HttpLink } from '@apollo/client'; 13 | const client = new ApolloClient({ 14 | link: new HttpLink({ uri: '/graphql', fetch }) 15 | }); 16 | `); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/link/http/createSignalIfSupported.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @deprecated 3 | * This is not used internally any more and will be removed in 4 | * the next major version of Apollo Client. 5 | */ 6 | export const createSignalIfSupported = () => { 7 | if (typeof AbortController === "undefined") 8 | return { controller: false, signal: false }; 9 | 10 | const controller = new AbortController(); 11 | const signal = controller.signal; 12 | return { controller, signal }; 13 | }; 14 | -------------------------------------------------------------------------------- /src/link/http/index.ts: -------------------------------------------------------------------------------- 1 | import "../../utilities/globals/index.js"; 2 | 3 | export type { ServerParseError } from "./parseAndCheckHttpResponse.js"; 4 | export { parseAndCheckHttpResponse } from "./parseAndCheckHttpResponse.js"; 5 | export type { ClientParseError } from "./serializeFetchParameter.js"; 6 | export { serializeFetchParameter } from "./serializeFetchParameter.js"; 7 | export type { HttpOptions, UriFunction } from "./selectHttpOptionsAndBody.js"; 8 | export { 9 | fallbackHttpConfig, 10 | defaultPrinter, 11 | selectHttpOptionsAndBody, 12 | selectHttpOptionsAndBodyInternal, // needed by ../batch-http but not public 13 | } from "./selectHttpOptionsAndBody.js"; 14 | export { checkFetcher } from "./checkFetcher.js"; 15 | export { createSignalIfSupported } from "./createSignalIfSupported.js"; 16 | export { selectURI } from "./selectURI.js"; 17 | export { createHttpLink } from "./createHttpLink.js"; 18 | export { HttpLink } from "./HttpLink.js"; 19 | export { rewriteURIForGET } from "./rewriteURIForGET.js"; 20 | -------------------------------------------------------------------------------- /src/link/http/iterators/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2022 Kevin Malakoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/link/http/iterators/async.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Original source: 3 | * https://github.com/kmalakoff/response-iterator/blob/master/src/iterators/async.ts 4 | */ 5 | 6 | export default function asyncIterator( 7 | source: AsyncIterableIterator 8 | ): AsyncIterableIterator { 9 | const iterator = source[Symbol.asyncIterator](); 10 | return { 11 | next(): Promise> { 12 | return iterator.next(); 13 | }, 14 | [Symbol.asyncIterator](): AsyncIterableIterator { 15 | return this; 16 | }, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/link/http/iterators/promise.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Original source: 3 | * https://github.com/kmalakoff/response-iterator/blob/master/src/iterators/promise.ts 4 | */ 5 | 6 | import { canUseAsyncIteratorSymbol } from "../../../utilities/index.js"; 7 | 8 | interface PromiseIterator { 9 | next(): Promise>; 10 | [Symbol.asyncIterator]?(): AsyncIterator; 11 | } 12 | 13 | export default function promiseIterator( 14 | promise: Promise 15 | ): AsyncIterableIterator { 16 | let resolved = false; 17 | 18 | const iterator: PromiseIterator = { 19 | next(): Promise> { 20 | if (resolved) 21 | return Promise.resolve({ 22 | value: undefined, 23 | done: true, 24 | }); 25 | resolved = true; 26 | return new Promise(function (resolve, reject) { 27 | promise 28 | .then(function (value) { 29 | resolve({ value: value as unknown as T, done: false }); 30 | }) 31 | .catch(reject); 32 | }); 33 | }, 34 | }; 35 | 36 | if (canUseAsyncIteratorSymbol) { 37 | iterator[Symbol.asyncIterator] = function (): AsyncIterator { 38 | return this; 39 | }; 40 | } 41 | 42 | return iterator as AsyncIterableIterator; 43 | } 44 | -------------------------------------------------------------------------------- /src/link/http/iterators/reader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Original source: 3 | * https://github.com/kmalakoff/response-iterator/blob/master/src/iterators/reader.ts 4 | */ 5 | 6 | import { canUseAsyncIteratorSymbol } from "../../../utilities/index.js"; 7 | 8 | interface ReaderIterator { 9 | next(): Promise>; 10 | [Symbol.asyncIterator]?(): AsyncIterator; 11 | } 12 | 13 | export default function readerIterator( 14 | reader: ReadableStreamDefaultReader 15 | ): AsyncIterableIterator { 16 | const iterator: ReaderIterator = { 17 | next() { 18 | return reader.read() as Promise< 19 | | ReadableStreamReadValueResult 20 | // DoneResult has `value` optional, which doesn't comply with an 21 | // `IteratorResult`, so we assert it to `T | undefined` instead 22 | | Required> 23 | >; 24 | }, 25 | }; 26 | 27 | if (canUseAsyncIteratorSymbol) { 28 | iterator[Symbol.asyncIterator] = function (): AsyncIterator< 29 | T, 30 | T | undefined 31 | > { 32 | return this; 33 | }; 34 | } 35 | 36 | return iterator as AsyncIterableIterator; 37 | } 38 | -------------------------------------------------------------------------------- /src/link/http/selectURI.ts: -------------------------------------------------------------------------------- 1 | import type { Operation } from "../core/index.js"; 2 | 3 | export const selectURI = ( 4 | operation: Operation, 5 | fallbackURI?: string | ((operation: Operation) => string) 6 | ) => { 7 | const context = operation.getContext(); 8 | const contextURI = context.uri; 9 | 10 | if (contextURI) { 11 | return contextURI; 12 | } else if (typeof fallbackURI === "function") { 13 | return fallbackURI(operation); 14 | } else { 15 | return (fallbackURI as string) || "/graphql"; 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/link/http/serializeFetchParameter.ts: -------------------------------------------------------------------------------- 1 | import { newInvariantError } from "../../utilities/globals/index.js"; 2 | import type { InvariantError } from "../../utilities/globals/index.js"; 3 | 4 | export type ClientParseError = InvariantError & { 5 | parseError: Error; 6 | }; 7 | 8 | export const serializeFetchParameter = (p: any, label: string) => { 9 | let serialized; 10 | try { 11 | serialized = JSON.stringify(p); 12 | } catch (e: any) { 13 | const parseError = newInvariantError( 14 | `Network request failed. %s is not serializable: %s`, 15 | label, 16 | e.message 17 | ) as ClientParseError; 18 | parseError.parseError = e; 19 | throw parseError; 20 | } 21 | return serialized; 22 | }; 23 | -------------------------------------------------------------------------------- /src/link/remove-typename/index.ts: -------------------------------------------------------------------------------- 1 | export type { RemoveTypenameFromVariablesOptions } from "./removeTypenameFromVariables.js"; 2 | export { 3 | removeTypenameFromVariables, 4 | KEEP, 5 | } from "./removeTypenameFromVariables.js"; 6 | -------------------------------------------------------------------------------- /src/link/retry/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./retryLink.js"; 2 | -------------------------------------------------------------------------------- /src/link/retry/retryFunction.ts: -------------------------------------------------------------------------------- 1 | import type { Operation } from "../core/index.js"; 2 | 3 | /** 4 | * Advanced mode: a function that determines both whether a particular 5 | * response should be retried. 6 | */ 7 | export interface RetryFunction { 8 | (count: number, operation: Operation, error: any): boolean | Promise; 9 | } 10 | 11 | export interface RetryFunctionOptions { 12 | /** 13 | * The max number of times to try a single operation before giving up. 14 | * 15 | * Note that this INCLUDES the initial request as part of the count. 16 | * E.g. maxTries of 1 indicates no retrying should occur. 17 | * 18 | * Defaults to 5. Pass Infinity for infinite retries. 19 | */ 20 | max?: number; 21 | 22 | /** 23 | * Predicate function that determines whether a particular error should 24 | * trigger a retry. 25 | * 26 | * For example, you may want to not retry 4xx class HTTP errors. 27 | * 28 | * By default, all errors are retried. 29 | */ 30 | retryIf?: (error: any, operation: Operation) => boolean | Promise; 31 | } 32 | 33 | export function buildRetryFunction( 34 | retryOptions?: RetryFunctionOptions 35 | ): RetryFunction { 36 | const { retryIf, max = 5 } = retryOptions || ({} as RetryFunctionOptions); 37 | return function retryFunction(count, operation, error) { 38 | if (count >= max) return false; 39 | return retryIf ? retryIf(error, operation) : !!error; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/link/utils/__tests__/filterOperationVariables.ts: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | import { filterOperationVariables } from "../filterOperationVariables"; 3 | 4 | const sampleQueryWithVariables = gql` 5 | query MyQuery($a: Int!) { 6 | stub(a: $a) { 7 | id 8 | } 9 | } 10 | `; 11 | 12 | const sampleQueryWithoutVariables = gql` 13 | query MyQuery { 14 | stub { 15 | id 16 | } 17 | } 18 | `; 19 | 20 | describe("filterOperationVariables", () => { 21 | it("filters unused variables", () => { 22 | const variables = { a: 1, b: 2, c: 3 }; 23 | const result = filterOperationVariables( 24 | variables, 25 | sampleQueryWithoutVariables 26 | ); 27 | expect(result).toEqual({}); 28 | }); 29 | 30 | it("does not filter used variables", () => { 31 | const variables = { a: 1, b: 2, c: 3 }; 32 | const result = filterOperationVariables( 33 | variables, 34 | sampleQueryWithVariables 35 | ); 36 | expect(result).toEqual({ a: 1 }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/link/utils/__tests__/fromError.ts: -------------------------------------------------------------------------------- 1 | import { toPromise } from "../toPromise"; 2 | import { fromError } from "../fromError"; 3 | 4 | describe("fromError", () => { 5 | it("acts as error call", () => { 6 | const error = new Error("I always error"); 7 | const observable = fromError(error); 8 | return toPromise(observable) 9 | .then(() => { 10 | throw "should not have thrown"; 11 | }) 12 | .catch((actualError) => expect(error).toEqual(actualError)); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/link/utils/__tests__/fromPromise.ts: -------------------------------------------------------------------------------- 1 | import { fromPromise } from "../fromPromise"; 2 | import { toPromise } from "../toPromise"; 3 | 4 | describe("fromPromise", () => { 5 | const data = { 6 | data: { 7 | hello: "world", 8 | }, 9 | }; 10 | const error = new Error("I always error"); 11 | 12 | it("return next call as Promise resolution", () => { 13 | const observable = fromPromise(Promise.resolve(data)); 14 | return toPromise(observable).then((result) => expect(data).toEqual(result)); 15 | }); 16 | 17 | it("return Promise rejection as error call", () => { 18 | const observable = fromPromise(Promise.reject(error)); 19 | return toPromise(observable) 20 | .then(() => { 21 | throw "should not have thrown"; 22 | }) 23 | .catch((actualError) => expect(error).toEqual(actualError)); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/link/utils/__tests__/toPromise.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "../../../utilities/observables/Observable"; 2 | import { toPromise } from "../toPromise"; 3 | import { fromError } from "../fromError"; 4 | 5 | describe("toPromise", () => { 6 | const data = { 7 | data: { 8 | hello: "world", 9 | }, 10 | }; 11 | const error = new Error("I always error"); 12 | 13 | it("return next call as Promise resolution", () => { 14 | return toPromise(Observable.of(data)).then((result) => 15 | expect(data).toEqual(result) 16 | ); 17 | }); 18 | 19 | it("return error call as Promise rejection", () => { 20 | return toPromise(fromError(error)) 21 | .then(() => { 22 | throw "should not have thrown"; 23 | }) 24 | .catch((actualError) => expect(error).toEqual(actualError)); 25 | }); 26 | 27 | describe("warnings", () => { 28 | const spy = jest.fn(); 29 | let _warn: (message?: any, ...originalParams: any[]) => void; 30 | 31 | beforeEach(() => { 32 | _warn = console.warn; 33 | console.warn = spy; 34 | }); 35 | 36 | afterEach(() => { 37 | console.warn = _warn; 38 | }); 39 | 40 | it("return error call as Promise rejection", async () => { 41 | const result = await toPromise(Observable.of(data, data)); 42 | 43 | expect(data).toEqual(result); 44 | expect(spy).toHaveBeenCalled(); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/link/utils/__tests__/validateOperation.ts: -------------------------------------------------------------------------------- 1 | import { validateOperation } from "../validateOperation"; 2 | import gql from "graphql-tag"; 3 | 4 | describe("validateOperation", () => { 5 | it("should throw when invalid field in operation", () => { 6 | expect(() => validateOperation({ qwerty: "" })).toThrow(); 7 | }); 8 | 9 | it("should not throw when valid fields in operation", () => { 10 | expect(() => 11 | validateOperation({ 12 | query: gql` 13 | query SampleQuery { 14 | stub { 15 | id 16 | } 17 | } 18 | `, 19 | context: {}, 20 | variables: {}, 21 | }) 22 | ).not.toThrow(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/link/utils/createOperation.ts: -------------------------------------------------------------------------------- 1 | import type { GraphQLRequest, Operation } from "../core/index.js"; 2 | 3 | export function createOperation( 4 | starting: any, 5 | operation: GraphQLRequest 6 | ): Operation { 7 | let context = { ...starting }; 8 | const setContext: Operation["setContext"] = (next) => { 9 | if (typeof next === "function") { 10 | context = { ...context, ...next(context) }; 11 | } else { 12 | context = { ...context, ...next }; 13 | } 14 | }; 15 | const getContext: Operation["getContext"] = () => ({ ...context }); 16 | 17 | Object.defineProperty(operation, "setContext", { 18 | enumerable: false, 19 | value: setContext, 20 | }); 21 | 22 | Object.defineProperty(operation, "getContext", { 23 | enumerable: false, 24 | value: getContext, 25 | }); 26 | 27 | return operation as Operation; 28 | } 29 | -------------------------------------------------------------------------------- /src/link/utils/filterOperationVariables.ts: -------------------------------------------------------------------------------- 1 | import type { VariableDefinitionNode, DocumentNode } from "graphql"; 2 | import { visit } from "graphql"; 3 | 4 | export function filterOperationVariables( 5 | variables: Record, 6 | query: DocumentNode 7 | ) { 8 | const result = { ...variables }; 9 | const unusedNames = new Set(Object.keys(variables)); 10 | visit(query, { 11 | Variable(node, _key, parent) { 12 | // A variable type definition at the top level of a query is not 13 | // enough to silence server-side errors about the variable being 14 | // unused, so variable definitions do not count as usage. 15 | // https://spec.graphql.org/draft/#sec-All-Variables-Used 16 | if ( 17 | parent && 18 | (parent as VariableDefinitionNode).kind !== "VariableDefinition" 19 | ) { 20 | unusedNames.delete(node.name.value); 21 | } 22 | }, 23 | }); 24 | unusedNames.forEach((name) => { 25 | delete result![name]; 26 | }); 27 | return result; 28 | } 29 | -------------------------------------------------------------------------------- /src/link/utils/fromError.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "../../utilities/index.js"; 2 | 3 | export function fromError(errorValue: any): Observable { 4 | return new Observable((observer) => { 5 | observer.error(errorValue); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /src/link/utils/fromPromise.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "../../utilities/index.js"; 2 | 3 | export function fromPromise(promise: Promise): Observable { 4 | return new Observable((observer) => { 5 | promise 6 | .then((value: T) => { 7 | observer.next(value); 8 | observer.complete(); 9 | }) 10 | .catch(observer.error.bind(observer)); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/link/utils/index.ts: -------------------------------------------------------------------------------- 1 | import "../../utilities/globals/index.js"; 2 | 3 | export { fromError } from "./fromError.js"; 4 | export { toPromise } from "./toPromise.js"; 5 | export { fromPromise } from "./fromPromise.js"; 6 | export type { ServerError } from "./throwServerError.js"; 7 | export { throwServerError } from "./throwServerError.js"; 8 | export { validateOperation } from "./validateOperation.js"; 9 | export { createOperation } from "./createOperation.js"; 10 | export { transformOperation } from "./transformOperation.js"; 11 | export { filterOperationVariables } from "./filterOperationVariables.js"; 12 | -------------------------------------------------------------------------------- /src/link/utils/throwServerError.ts: -------------------------------------------------------------------------------- 1 | export type ServerError = Error & { 2 | response: Response; 3 | result: Record | string; 4 | statusCode: number; 5 | }; 6 | 7 | export const throwServerError = ( 8 | response: Response, 9 | result: any, 10 | message: string 11 | ) => { 12 | const error = new Error(message) as ServerError; 13 | error.name = "ServerError"; 14 | error.response = response; 15 | error.statusCode = response.status; 16 | error.result = result; 17 | throw error; 18 | }; 19 | -------------------------------------------------------------------------------- /src/link/utils/toPromise.ts: -------------------------------------------------------------------------------- 1 | import { invariant } from "../../utilities/globals/index.js"; 2 | import type { Observable } from "../../utilities/index.js"; 3 | 4 | export function toPromise(observable: Observable): Promise { 5 | let completed = false; 6 | return new Promise((resolve, reject) => { 7 | observable.subscribe({ 8 | next: (data) => { 9 | if (completed) { 10 | invariant.warn( 11 | `Promise Wrapper does not support multiple results from Observable` 12 | ); 13 | } else { 14 | completed = true; 15 | resolve(data); 16 | } 17 | }, 18 | error: reject, 19 | }); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/link/utils/transformOperation.ts: -------------------------------------------------------------------------------- 1 | import type { GraphQLRequest, Operation } from "../core/index.js"; 2 | import { getOperationName } from "../../utilities/index.js"; 3 | 4 | export function transformOperation(operation: GraphQLRequest): GraphQLRequest { 5 | const transformedOperation: GraphQLRequest = { 6 | variables: operation.variables || {}, 7 | extensions: operation.extensions || {}, 8 | operationName: operation.operationName, 9 | query: operation.query, 10 | }; 11 | 12 | // Best guess at an operation name 13 | if (!transformedOperation.operationName) { 14 | transformedOperation.operationName = 15 | typeof transformedOperation.query !== "string" ? 16 | getOperationName(transformedOperation.query) || undefined 17 | : ""; 18 | } 19 | 20 | return transformedOperation as Operation; 21 | } 22 | -------------------------------------------------------------------------------- /src/link/utils/validateOperation.ts: -------------------------------------------------------------------------------- 1 | import { newInvariantError } from "../../utilities/globals/index.js"; 2 | import type { GraphQLRequest } from "../core/index.js"; 3 | 4 | export function validateOperation(operation: GraphQLRequest): GraphQLRequest { 5 | const OPERATION_FIELDS = [ 6 | "query", 7 | "operationName", 8 | "variables", 9 | "extensions", 10 | "context", 11 | ]; 12 | for (let key of Object.keys(operation)) { 13 | if (OPERATION_FIELDS.indexOf(key) < 0) { 14 | throw newInvariantError(`illegal argument: %s`, key); 15 | } 16 | } 17 | 18 | return operation; 19 | } 20 | -------------------------------------------------------------------------------- /src/masking/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | DataMasking, 3 | FragmentType, 4 | Masked, 5 | MaskedDocumentNode, 6 | MaybeMasked, 7 | Unmasked, 8 | } from "./types.js"; 9 | export { disableWarningsSlot } from "./utils.js"; 10 | export { maskFragment } from "./maskFragment.js"; 11 | export { maskOperation } from "./maskOperation.js"; 12 | -------------------------------------------------------------------------------- /src/masking/utils.ts: -------------------------------------------------------------------------------- 1 | import { Slot } from "optimism"; 2 | import { invariant } from "../utilities/globals/index.js"; 3 | import { canUseWeakMap, canUseWeakSet } from "../utilities/index.js"; 4 | 5 | export const MapImpl = canUseWeakMap ? WeakMap : Map; 6 | export const SetImpl = canUseWeakSet ? WeakSet : Set; 7 | 8 | // Contextual slot that allows us to disable accessor warnings on fields when in 9 | // migrate mode. 10 | /** @internal */ 11 | export const disableWarningsSlot = new Slot(); 12 | 13 | let issuedWarning = false; 14 | export function warnOnImproperCacheImplementation() { 15 | if (!issuedWarning) { 16 | issuedWarning = true; 17 | invariant.warn( 18 | "The configured cache does not support data masking which effectively disables it. Please use a cache that supports data masking or disable data masking to silence this warning." 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/react/components/index.ts: -------------------------------------------------------------------------------- 1 | export { Query } from "./Query.js"; 2 | export { Mutation } from "./Mutation.js"; 3 | export { Subscription } from "./Subscription.js"; 4 | 5 | export type * from "./types.js"; 6 | -------------------------------------------------------------------------------- /src/react/context/ApolloConsumer.tsx: -------------------------------------------------------------------------------- 1 | import { invariant } from "../../utilities/globals/index.js"; 2 | 3 | import * as React from "rehackt"; 4 | import type * as ReactTypes from "react"; 5 | 6 | import type { ApolloClient } from "../../core/index.js"; 7 | import { getApolloContext } from "./ApolloContext.js"; 8 | 9 | export interface ApolloConsumerProps { 10 | children: (client: ApolloClient) => ReactTypes.ReactNode; 11 | } 12 | 13 | export const ApolloConsumer: ReactTypes.FC = (props) => { 14 | const ApolloContext = getApolloContext(); 15 | return ( 16 | 17 | {(context: any) => { 18 | invariant( 19 | context && context.client, 20 | 'Could not find "client" in the context of ApolloConsumer. ' + 21 | "Wrap the root component in an ." 22 | ); 23 | return props.children(context.client); 24 | }} 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/react/context/ApolloProvider.tsx: -------------------------------------------------------------------------------- 1 | import { invariant } from "../../utilities/globals/index.js"; 2 | 3 | import * as React from "rehackt"; 4 | import type * as ReactTypes from "react"; 5 | 6 | import type { ApolloClient } from "../../core/index.js"; 7 | import { getApolloContext } from "./ApolloContext.js"; 8 | 9 | export interface ApolloProviderProps { 10 | client: ApolloClient; 11 | children: ReactTypes.ReactNode | ReactTypes.ReactNode[] | null; 12 | } 13 | 14 | export const ApolloProvider: ReactTypes.FC> = ({ 15 | client, 16 | children, 17 | }) => { 18 | const ApolloContext = getApolloContext(); 19 | const parentContext = React.useContext(ApolloContext); 20 | 21 | const context = React.useMemo(() => { 22 | return { 23 | ...parentContext, 24 | client: client || parentContext.client, 25 | }; 26 | }, [parentContext, client]); 27 | 28 | invariant( 29 | context.client, 30 | "ApolloProvider was not passed a client instance. Make " + 31 | 'sure you pass in your client via the "client" prop.' 32 | ); 33 | 34 | return ( 35 | {children} 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /src/react/context/index.ts: -------------------------------------------------------------------------------- 1 | import "../../utilities/globals/index.js"; 2 | 3 | export type { ApolloConsumerProps } from "./ApolloConsumer.js"; 4 | export { ApolloConsumer } from "./ApolloConsumer.js"; 5 | export type { ApolloContextValue } from "./ApolloContext.js"; 6 | export { getApolloContext, resetApolloContext } from "./ApolloContext.js"; 7 | export type { ApolloProviderProps } from "./ApolloProvider.js"; 8 | export { ApolloProvider } from "./ApolloProvider.js"; 9 | -------------------------------------------------------------------------------- /src/react/hoc/__tests__/queries/__snapshots__/lifecycle.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`[queries] lifecycle handles asynchronous racecondition with prefilled data from the server 1`] = ` 4 |
5 |

6 | stub 7 |

8 |
9 | `; 10 | -------------------------------------------------------------------------------- /src/react/hoc/__tests__/statics.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import gql from "graphql-tag"; 3 | 4 | import { graphql } from "../graphql"; 5 | 6 | let sampleOperation = gql` 7 | { 8 | user { 9 | name 10 | } 11 | } 12 | `; 13 | 14 | describe("statics", () => { 15 | it("should be preserved", () => { 16 | const ApolloContainer = graphql(sampleOperation)( 17 | class extends React.Component { 18 | static veryStatic = "such global"; 19 | } 20 | ); 21 | 22 | expect((ApolloContainer as any).veryStatic).toBe("such global"); 23 | }); 24 | 25 | it("exposes a debuggable displayName", () => { 26 | @graphql(sampleOperation) 27 | class ApolloContainer extends React.Component {} 28 | 29 | expect((ApolloContainer as any).displayName).toBe( 30 | "Apollo(ApolloContainer)" 31 | ); 32 | }); 33 | 34 | it("honors custom display names", () => { 35 | const ApolloContainer = graphql(sampleOperation)( 36 | class extends React.Component { 37 | static displayName = "Foo"; 38 | } 39 | ); 40 | 41 | expect((ApolloContainer as any).displayName).toBe("Apollo(Foo)"); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/react/hoc/index.ts: -------------------------------------------------------------------------------- 1 | import "../../utilities/globals/index.js"; 2 | 3 | export { graphql } from "./graphql.js"; 4 | 5 | export { withQuery } from "./query-hoc.js"; 6 | export { withMutation } from "./mutation-hoc.js"; 7 | export { withSubscription } from "./subscription-hoc.js"; 8 | export { withApollo } from "./withApollo.js"; 9 | 10 | export type * from "./types.js"; 11 | -------------------------------------------------------------------------------- /src/react/hooks/__tests__/useApolloClient.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "@testing-library/react"; 3 | import { InvariantError } from "ts-invariant"; 4 | 5 | import { ApolloClient } from "../../../core"; 6 | import { ApolloLink } from "../../../link/core"; 7 | import { ApolloProvider } from "../../context"; 8 | import { InMemoryCache } from "../../../cache"; 9 | import { useApolloClient } from "../useApolloClient"; 10 | 11 | describe("useApolloClient Hook", () => { 12 | it("should return a client instance from the context if available", () => { 13 | const client = new ApolloClient({ 14 | cache: new InMemoryCache(), 15 | link: ApolloLink.empty(), 16 | }); 17 | 18 | function App() { 19 | expect(useApolloClient()).toEqual(client); 20 | return null; 21 | } 22 | 23 | render( 24 | 25 | 26 | 27 | ); 28 | }); 29 | 30 | it("should error if a client instance can't be found in the context", () => { 31 | function App() { 32 | expect(() => useApolloClient()).toThrow(InvariantError); 33 | return null; 34 | } 35 | 36 | render(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/react/hooks/constants.ts: -------------------------------------------------------------------------------- 1 | export const skipToken = Symbol.for("apollo.skipToken"); 2 | export type SkipToken = typeof skipToken; 3 | -------------------------------------------------------------------------------- /src/react/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import "../../utilities/globals/index.js"; 2 | 3 | export * from "./useApolloClient.js"; 4 | export * from "./useLazyQuery.js"; 5 | export * from "./useMutation.js"; 6 | export { useQuery } from "./useQuery.js"; 7 | export * from "./useSubscription.js"; 8 | export * from "./useReactiveVar.js"; 9 | export * from "./useFragment.js"; 10 | export type { UseSuspenseQueryResult } from "./useSuspenseQuery.js"; 11 | export { useSuspenseQuery } from "./useSuspenseQuery.js"; 12 | export type { UseBackgroundQueryResult } from "./useBackgroundQuery.js"; 13 | export { useBackgroundQuery } from "./useBackgroundQuery.js"; 14 | export type { 15 | UseSuspenseFragmentResult, 16 | UseSuspenseFragmentOptions, 17 | } from "./useSuspenseFragment.js"; 18 | export { useSuspenseFragment } from "./useSuspenseFragment.js"; 19 | export type { 20 | LoadQueryFunction, 21 | UseLoadableQueryResult, 22 | } from "./useLoadableQuery.js"; 23 | export { useLoadableQuery } from "./useLoadableQuery.js"; 24 | export type { UseQueryRefHandlersResult } from "./useQueryRefHandlers.js"; 25 | export { useQueryRefHandlers } from "./useQueryRefHandlers.js"; 26 | export type { UseReadQueryResult } from "./useReadQuery.js"; 27 | export { useReadQuery } from "./useReadQuery.js"; 28 | export { skipToken } from "./constants.js"; 29 | export type { SkipToken } from "./constants.js"; 30 | -------------------------------------------------------------------------------- /src/react/hooks/internal/__tests__/useRenderGuard.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable testing-library/render-result-naming-convention */ 2 | import * as React from "rehackt"; 3 | import { useRenderGuard } from "../useRenderGuard"; 4 | import { render, waitFor } from "@testing-library/react"; 5 | 6 | const UNDEF = {}; 7 | const IS_REACT_19 = React.version.startsWith("19"); 8 | 9 | it("returns a function that returns `true` if called during render", () => { 10 | // We don't provide this functionality with React 19 anymore since it requires internals access 11 | if (IS_REACT_19) return; 12 | let result: boolean | typeof UNDEF = UNDEF; 13 | function TestComponent() { 14 | const calledDuringRender = useRenderGuard(); 15 | result = calledDuringRender(); 16 | return <>Test; 17 | } 18 | render(); 19 | expect(result).toBe(true); 20 | }); 21 | 22 | it("returns a function that returns `false` if called after render", async () => { 23 | let result: boolean | typeof UNDEF = UNDEF; 24 | function TestComponent() { 25 | const calledDuringRender = useRenderGuard(); 26 | React.useEffect(() => { 27 | result = calledDuringRender(); 28 | }); 29 | return <>Test; 30 | } 31 | render(); 32 | await waitFor(() => { 33 | expect(result).not.toBe(UNDEF); 34 | }); 35 | expect(result).toBe(false); 36 | }); 37 | -------------------------------------------------------------------------------- /src/react/hooks/internal/__use.ts: -------------------------------------------------------------------------------- 1 | import { wrapPromiseWithState } from "../../../utilities/index.js"; 2 | import * as React from "rehackt"; 3 | 4 | type Use = (promise: Promise) => T; 5 | // Prevent webpack from complaining about our feature detection of the 6 | // use property of the React namespace, which is expected not 7 | // to exist when using current stable versions, and that's fine. 8 | const useKey = "use" as keyof typeof React; 9 | const realHook = React[useKey] as Use | undefined; 10 | 11 | // This is named with two underscores to allow this hook to evade typical rules of 12 | // hooks (i.e. it can be used conditionally) 13 | export const __use = 14 | realHook || 15 | function __use(promise: Promise) { 16 | const statefulPromise = wrapPromiseWithState(promise); 17 | 18 | switch (statefulPromise.status) { 19 | case "pending": 20 | throw statefulPromise; 21 | case "rejected": 22 | throw statefulPromise.reason; 23 | case "fulfilled": 24 | return statefulPromise.value; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/react/hooks/internal/index.ts: -------------------------------------------------------------------------------- 1 | // These hooks are used internally and are not exported publicly by the library 2 | export { useDeepMemo } from "./useDeepMemo.js"; 3 | export { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js"; 4 | export { useRenderGuard } from "./useRenderGuard.js"; 5 | export { __use } from "./__use.js"; 6 | export { wrapHook } from "./wrapHook.js"; 7 | -------------------------------------------------------------------------------- /src/react/hooks/internal/useDeepMemo.ts: -------------------------------------------------------------------------------- 1 | import type { DependencyList } from "react"; 2 | import * as React from "rehackt"; 3 | import { equal } from "@wry/equality"; 4 | 5 | export function useDeepMemo( 6 | memoFn: () => TValue, 7 | deps: DependencyList 8 | ) { 9 | const ref = React.useRef<{ deps: DependencyList; value: TValue }>(void 0); 10 | 11 | if (!ref.current || !equal(ref.current.deps, deps)) { 12 | // eslint-disable-next-line react-compiler/react-compiler 13 | ref.current = { value: memoFn(), deps }; 14 | } 15 | 16 | return ref.current.value; 17 | } 18 | -------------------------------------------------------------------------------- /src/react/hooks/internal/useIsomorphicLayoutEffect.ts: -------------------------------------------------------------------------------- 1 | import * as React from "rehackt"; 2 | import { canUseDOM } from "../../../utilities/index.js"; 3 | 4 | // use canUseDOM here instead of canUseLayoutEffect because we want to be able 5 | // to use useLayoutEffect in our jest tests. useLayoutEffect seems to work fine 6 | // in useSuspenseQuery tests, but to honor the original comment about the 7 | // warnings for useSyncExternalStore implementation, canUseLayoutEffect is left 8 | // alone. 9 | export const useIsomorphicLayoutEffect = 10 | canUseDOM ? React.useLayoutEffect : React.useEffect; 11 | -------------------------------------------------------------------------------- /src/react/hooks/useApolloClient.ts: -------------------------------------------------------------------------------- 1 | import { invariant } from "../../utilities/globals/index.js"; 2 | import * as React from "rehackt"; 3 | import type { ApolloClient } from "../../core/index.js"; 4 | import { getApolloContext } from "../context/index.js"; 5 | 6 | /** 7 | * @example 8 | * ```jsx 9 | * import { useApolloClient } from '@apollo/client'; 10 | * 11 | * function SomeComponent() { 12 | * const client = useApolloClient(); 13 | * // `client` is now set to the `ApolloClient` instance being used by the 14 | * // application (that was configured using something like `ApolloProvider`) 15 | * } 16 | * ``` 17 | * 18 | * @since 3.0.0 19 | * @returns The `ApolloClient` instance being used by the application. 20 | */ 21 | export function useApolloClient( 22 | override?: ApolloClient 23 | ): ApolloClient { 24 | const context = React.useContext(getApolloContext()); 25 | const client = override || context.client; 26 | invariant( 27 | !!client, 28 | 'Could not find "client" in the context or passed in as an option. ' + 29 | "Wrap the root component in an , or pass an ApolloClient " + 30 | "instance in via options." 31 | ); 32 | 33 | return client; 34 | } 35 | -------------------------------------------------------------------------------- /src/react/index.ts: -------------------------------------------------------------------------------- 1 | import "../utilities/globals/index.js"; 2 | 3 | export type { ApolloContextValue } from "./context/index.js"; 4 | export { 5 | ApolloProvider, 6 | ApolloConsumer, 7 | getApolloContext, 8 | resetApolloContext, 9 | } from "./context/index.js"; 10 | 11 | export * from "./hooks/index.js"; 12 | 13 | export type { IDocumentDefinition } from "./parser/index.js"; 14 | export { DocumentType, operationName, parser } from "./parser/index.js"; 15 | 16 | export type { 17 | PreloadQueryOptions, 18 | PreloadQueryFetchPolicy, 19 | PreloadQueryFunction, 20 | } from "./query-preloader/createQueryPreloader.js"; 21 | export { createQueryPreloader } from "./query-preloader/createQueryPreloader.js"; 22 | 23 | export type * from "./types/types.js"; 24 | -------------------------------------------------------------------------------- /src/react/internal/cache/getSuspenseCache.ts: -------------------------------------------------------------------------------- 1 | import type { SuspenseCacheOptions } from "../index.js"; 2 | import { SuspenseCache } from "./SuspenseCache.js"; 3 | import type { ApolloClient } from "../../../core/ApolloClient.js"; 4 | 5 | declare module "../../../core/ApolloClient.js" { 6 | interface DefaultOptions { 7 | react?: { 8 | suspense?: Readonly; 9 | }; 10 | } 11 | } 12 | 13 | const suspenseCacheSymbol = Symbol.for("apollo.suspenseCache"); 14 | 15 | export function getSuspenseCache( 16 | client: ApolloClient & { 17 | [suspenseCacheSymbol]?: SuspenseCache; 18 | } 19 | ) { 20 | if (!client[suspenseCacheSymbol]) { 21 | client[suspenseCacheSymbol] = new SuspenseCache( 22 | client.defaultOptions.react?.suspense 23 | ); 24 | } 25 | 26 | return client[suspenseCacheSymbol]; 27 | } 28 | -------------------------------------------------------------------------------- /src/react/internal/cache/types.ts: -------------------------------------------------------------------------------- 1 | import type { DocumentNode } from "graphql"; 2 | 3 | export type CacheKey = [ 4 | query: DocumentNode, 5 | stringifiedVariables: string, 6 | ...queryKey: any[], 7 | ]; 8 | 9 | export type FragmentCacheKey = [ 10 | cacheId: string, 11 | fragment: DocumentNode, 12 | stringifiedVariables: string, 13 | ]; 14 | 15 | export interface QueryKey { 16 | __queryKey?: string; 17 | } 18 | 19 | export interface FragmentKey { 20 | __fragmentKey?: string; 21 | } 22 | -------------------------------------------------------------------------------- /src/react/internal/index.ts: -------------------------------------------------------------------------------- 1 | export { getSuspenseCache } from "./cache/getSuspenseCache.js"; 2 | export type { CacheKey, QueryKey } from "./cache/types.js"; 3 | export type { 4 | QueryReference, 5 | QueryRef, 6 | PreloadedQueryRef, 7 | } from "./cache/QueryReference.js"; 8 | export { 9 | InternalQueryReference, 10 | getWrappedPromise, 11 | unwrapQueryRef, 12 | updateWrappedQueryRef, 13 | wrapQueryRef, 14 | assertWrappedQueryRef, 15 | } from "./cache/QueryReference.js"; 16 | export type { SuspenseCacheOptions } from "./cache/SuspenseCache.js"; 17 | export type { HookWrappers } from "../hooks/internal/wrapHook.js"; 18 | -------------------------------------------------------------------------------- /src/react/ssr/__tests__/__snapshots__/useQuery.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`useQuery Hook SSR should return data written previously to cache during SSR pass if using cache-only fetchPolicy 1`] = ` 4 | Object { 5 | "Order:{\\"selection\\":\\"RELEVANCE\\"}": Object { 6 | "__typename": "Order", 7 | "selection": "RELEVANCE", 8 | }, 9 | "ROOT_QUERY": Object { 10 | "__typename": "Query", 11 | "getSearchResults": Object { 12 | "__typename": "SearchResults", 13 | "locale": "en-US", 14 | "order": Object { 15 | "__ref": "Order:{\\"selection\\":\\"RELEVANCE\\"}", 16 | }, 17 | "pagination": Object { 18 | "pageLimit": 3, 19 | }, 20 | "results": Array [ 21 | Object { 22 | "__ref": "SearchResult:1", 23 | }, 24 | Object { 25 | "__ref": "SearchResult:2", 26 | }, 27 | Object { 28 | "__ref": "SearchResult:3", 29 | }, 30 | ], 31 | }, 32 | }, 33 | "SearchResult:1": Object { 34 | "__typename": "SearchResult", 35 | "id": 1, 36 | "text": "hi", 37 | }, 38 | "SearchResult:2": Object { 39 | "__typename": "SearchResult", 40 | "id": 2, 41 | "text": "hello", 42 | }, 43 | "SearchResult:3": Object { 44 | "__typename": "SearchResult", 45 | "id": 3, 46 | "text": "hey", 47 | }, 48 | } 49 | `; 50 | -------------------------------------------------------------------------------- /src/react/ssr/__tests__/useReactiveVar.test.tsx: -------------------------------------------------------------------------------- 1 | /** @jest-environment node */ 2 | import React from "react"; 3 | import { makeVar } from "../../../core"; 4 | import { useReactiveVar } from "../../hooks"; 5 | import { renderToStringWithData } from "../"; 6 | import { spyOnConsole } from "../../../testing/internal"; 7 | 8 | describe("useReactiveVar Hook SSR", () => { 9 | it("does not cause warnings", async () => { 10 | using consoleSpy = spyOnConsole("error"); 11 | const counterVar = makeVar(0); 12 | function Component() { 13 | const count = useReactiveVar(counterVar); 14 | counterVar(1); 15 | counterVar(2); 16 | return
{count}
; 17 | } 18 | 19 | // eslint-disable-next-line testing-library/render-result-naming-convention 20 | const value = await renderToStringWithData(); 21 | expect(value).toEqual("
0
"); 22 | expect(consoleSpy.error).toHaveBeenCalledTimes(0); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/react/ssr/index.ts: -------------------------------------------------------------------------------- 1 | export { getMarkupFromTree, getDataFromTree } from "./getDataFromTree.js"; 2 | export { renderToStringWithData } from "./renderToStringWithData.js"; 3 | export { RenderPromises } from "./RenderPromises.js"; 4 | -------------------------------------------------------------------------------- /src/react/ssr/renderToStringWithData.ts: -------------------------------------------------------------------------------- 1 | import type * as ReactTypes from "react"; 2 | import { getMarkupFromTree } from "./getDataFromTree.js"; 3 | import { renderToString } from "react-dom/server"; 4 | 5 | export function renderToStringWithData( 6 | component: ReactTypes.ReactElement 7 | ): Promise { 8 | return getMarkupFromTree({ 9 | tree: component, 10 | renderFunction: renderToString, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/testing/core/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | MockedResponse, 3 | MockLinkOptions, 4 | ResultFunction, 5 | } from "./mocking/mockLink.js"; 6 | export { MockLink, mockSingleLink } from "./mocking/mockLink.js"; 7 | export { 8 | MockSubscriptionLink, 9 | mockObservableLink, 10 | } from "./mocking/mockSubscriptionLink.js"; 11 | export { createMockClient } from "./mocking/mockClient.js"; 12 | export { default as subscribeAndCount } from "./subscribeAndCount.js"; 13 | export { itAsync } from "./itAsync.js"; 14 | export { wait, tick } from "./wait.js"; 15 | export * from "./withConsoleSpy.js"; 16 | -------------------------------------------------------------------------------- /src/testing/core/itAsync.ts: -------------------------------------------------------------------------------- 1 | function wrap(key?: "only" | "skip" | "todo") { 2 | return ( 3 | message: string, 4 | callback: ( 5 | resolve: (result?: any) => void, 6 | reject: (reason?: any) => void 7 | ) => any, 8 | timeout?: number 9 | ) => 10 | (key ? it[key] : it)( 11 | message, 12 | function (this: unknown) { 13 | return new Promise((resolve, reject) => 14 | callback.call(this, resolve, reject) 15 | ); 16 | }, 17 | timeout 18 | ); 19 | } 20 | 21 | const wrappedIt = wrap(); 22 | 23 | export const itAsync = Object.assign( 24 | function (this: unknown, ...args: Parameters) { 25 | return wrappedIt.apply(this, args); 26 | }, 27 | { 28 | only: wrap("only"), 29 | skip: wrap("skip"), 30 | todo: wrap("todo"), 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /src/testing/core/mocking/__tests__/__snapshots__/mockLink.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`shows undefined and NaN in debug messages 1`] = ` 4 | "No more mocked responses for the query: query ($id: ID!, $filter: Boolean) { 5 | usersByTestId(id: $id, filter: $filter) { 6 | id 7 | } 8 | } 9 | Expected variables: {\\"id\\":NaN,\\"filter\\":} 10 | 11 | Failed to match 1 mock for this query. The mocked response had the following variables: 12 | {\\"id\\":1,\\"filter\\":true} 13 | " 14 | `; 15 | -------------------------------------------------------------------------------- /src/testing/core/mocking/mockClient.ts: -------------------------------------------------------------------------------- 1 | import type { DocumentNode } from "graphql"; 2 | 3 | import { ApolloClient } from "../../../core/index.js"; 4 | import type { NormalizedCacheObject } from "../../../cache/index.js"; 5 | import { InMemoryCache } from "../../../cache/index.js"; 6 | import { mockSingleLink } from "./mockLink.js"; 7 | 8 | export function createMockClient( 9 | data: TData, 10 | query: DocumentNode, 11 | variables = {} 12 | ): ApolloClient { 13 | return new ApolloClient({ 14 | link: mockSingleLink({ 15 | request: { query, variables }, 16 | result: { data }, 17 | }).setOnError((error) => { 18 | throw error; 19 | }), 20 | cache: new InMemoryCache({ addTypename: false }), 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/testing/core/subscribeAndCount.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ObservableSubscription, 3 | Observable, 4 | } from "../../utilities/index.js"; 5 | import { asyncMap } from "../../utilities/index.js"; 6 | 7 | export default function subscribeAndCount( 8 | reject: (reason: any) => any, 9 | observable: Observable, 10 | cb: (handleCount: number, result: TResult) => any 11 | ): ObservableSubscription { 12 | // Use a Promise queue to prevent callbacks from being run out of order. 13 | let queue = Promise.resolve(); 14 | let handleCount = 0; 15 | 16 | const subscription = asyncMap(observable, (result) => { 17 | // All previous asynchronous callbacks must complete before cb can 18 | // be invoked with this result. 19 | return (queue = queue 20 | .then(() => { 21 | return cb(++handleCount, result); 22 | }) 23 | .catch(error)); 24 | }).subscribe({ error }); 25 | 26 | function error(e: any) { 27 | subscription.unsubscribe(); 28 | reject(e); 29 | } 30 | 31 | return subscription; 32 | } 33 | -------------------------------------------------------------------------------- /src/testing/core/wait.ts: -------------------------------------------------------------------------------- 1 | export async function wait(ms: number) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | 5 | export async function tick() { 6 | return wait(0); 7 | } 8 | -------------------------------------------------------------------------------- /src/testing/core/wrap.ts: -------------------------------------------------------------------------------- 1 | // I'm not sure why mocha doesn't provide something like this, you can't 2 | // always use promises 3 | export default ( 4 | reject: (reason: any) => any, 5 | cb: (...args: TArgs) => TResult 6 | ) => 7 | (...args: TArgs) => { 8 | try { 9 | return cb(...args); 10 | } catch (e) { 11 | reject(e); 12 | } 13 | }; 14 | 15 | export function withError(func: Function, regex: RegExp) { 16 | let message: string = null as never; 17 | const oldError = console.error; 18 | 19 | console.error = (m: string) => (message = m); 20 | 21 | try { 22 | const result = func(); 23 | expect(message).toMatch(regex); 24 | return result; 25 | } finally { 26 | console.error = oldError; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/testing/experimental/graphql-tools/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 The Guild, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/testing/experimental/index.ts: -------------------------------------------------------------------------------- 1 | export { createTestSchema } from "./createTestSchema.js"; 2 | export { createSchemaFetch } from "./createSchemaFetch.js"; 3 | -------------------------------------------------------------------------------- /src/testing/index.ts: -------------------------------------------------------------------------------- 1 | import "../utilities/globals/index.js"; 2 | export type { MockedProviderProps } from "./react/MockedProvider.js"; 3 | export { MockedProvider } from "./react/MockedProvider.js"; 4 | export * from "./core/index.js"; 5 | -------------------------------------------------------------------------------- /src/testing/internal/disposables/__tests__/withCleanup.test.ts: -------------------------------------------------------------------------------- 1 | import { withCleanup } from "../index.js"; 2 | describe("withCleanup", () => { 3 | it("calls cleanup", () => { 4 | let cleanedUp = false; 5 | { 6 | using _x = withCleanup({}, () => { 7 | cleanedUp = true; 8 | }); 9 | expect(cleanedUp).toBe(false); 10 | } 11 | expect(cleanedUp).toBe(true); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/testing/internal/disposables/enableFakeTimers.ts: -------------------------------------------------------------------------------- 1 | import { withCleanup } from "./withCleanup.js"; 2 | 3 | declare global { 4 | interface DateConstructor { 5 | /* Jest uses @sinonjs/fake-timers, that add this flag */ 6 | isFake: boolean; 7 | } 8 | } 9 | 10 | export function enableFakeTimers( 11 | config?: FakeTimersConfig | LegacyFakeTimersConfig 12 | ) { 13 | if (global.Date.isFake === true) { 14 | // Nothing to do here, fake timers have already been set up. 15 | // That also means we don't want to clean that up later. 16 | return withCleanup({}, () => {}); 17 | } 18 | 19 | jest.useFakeTimers(config); 20 | return withCleanup({}, () => { 21 | if (global.Date.isFake === true) { 22 | jest.runOnlyPendingTimers(); 23 | jest.useRealTimers(); 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/testing/internal/disposables/index.ts: -------------------------------------------------------------------------------- 1 | export { spyOnConsole } from "./spyOnConsole.js"; 2 | export { withCleanup } from "./withCleanup.js"; 3 | export { enableFakeTimers } from "./enableFakeTimers.js"; 4 | export { withProdMode } from "./withProdMode.js"; 5 | -------------------------------------------------------------------------------- /src/testing/internal/disposables/spyOnConsole.ts: -------------------------------------------------------------------------------- 1 | import { withCleanup } from "./withCleanup.js"; 2 | 3 | const noOp = () => {}; 4 | const restore = (spy: jest.SpyInstance) => spy.mockRestore(); 5 | 6 | type ConsoleMethod = "log" | "info" | "warn" | "error" | "debug"; 7 | 8 | type Spies = Record< 9 | Keys[number], 10 | jest.SpyInstance 11 | >; 12 | 13 | /** @internal */ 14 | export function spyOnConsole( 15 | ...spyOn: Keys 16 | ): Spies & Disposable { 17 | const spies = {} as Spies; 18 | for (const key of spyOn) { 19 | // @ts-ignore 20 | spies[key] = jest.spyOn(console, key).mockImplementation(noOp); 21 | } 22 | return withCleanup(spies, (spies) => { 23 | for (const spy of Object.values(spies) as jest.SpyInstance[]) { 24 | restore(spy); 25 | } 26 | }); 27 | } 28 | 29 | spyOnConsole.takeSnapshots = ( 30 | ...spyOn: Keys 31 | ): Spies & Disposable => 32 | withCleanup(spyOnConsole(...spyOn), (spies) => { 33 | for (const spy of Object.values(spies) as jest.SpyInstance[]) { 34 | expect(spy).toMatchSnapshot(); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /src/testing/internal/disposables/withCleanup.ts: -------------------------------------------------------------------------------- 1 | /** @internal */ 2 | export function withCleanup( 3 | item: T, 4 | cleanup: (item: T) => void 5 | ): T & Disposable { 6 | return { 7 | ...item, 8 | [Symbol.dispose]() { 9 | cleanup(item); 10 | // if `item` already has a cleanup function, we also need to call the original cleanup function 11 | // (e.g. if something is wrapped in `withCleanup` twice) 12 | if (Symbol.dispose in item) { 13 | (item as Disposable)[Symbol.dispose](); 14 | } 15 | }, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/testing/internal/disposables/withProdMode.ts: -------------------------------------------------------------------------------- 1 | import { withCleanup } from "./withCleanup.js"; 2 | 3 | export function withProdMode() { 4 | const prev = { prevDEV: __DEV__ }; 5 | Object.defineProperty(globalThis, "__DEV__", { value: false }); 6 | 7 | return withCleanup(prev, ({ prevDEV }) => { 8 | Object.defineProperty(globalThis, "__DEV__", { value: prevDEV }); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /src/testing/internal/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./disposables/index.js"; 2 | export { ObservableStream } from "./ObservableStream.js"; 3 | 4 | export type { 5 | SimpleCaseData, 6 | PaginatedCaseData, 7 | PaginatedCaseVariables, 8 | VariablesCaseData, 9 | VariablesCaseVariables, 10 | } from "./scenarios/index.js"; 11 | export { 12 | setupSimpleCase, 13 | setupVariablesCase, 14 | setupPaginatedCase, 15 | addDelayToMocks, 16 | } from "./scenarios/index.js"; 17 | 18 | export type { 19 | RenderWithClientOptions, 20 | RenderWithMocksOptions, 21 | } from "./renderHelpers.js"; 22 | export { 23 | renderWithClient, 24 | renderWithMocks, 25 | createMockWrapper, 26 | createClientWrapper, 27 | } from "./renderHelpers.js"; 28 | export { actAsync } from "./rtl/actAsync.js"; 29 | export { renderAsync } from "./rtl/renderAsync.js"; 30 | export { renderHookAsync } from "./rtl/renderHookAsync.js"; 31 | export { 32 | mockIncrementalStream, 33 | mockDeferStream, 34 | mockMultipartSubscriptionStream, 35 | } from "./incremental.js"; 36 | -------------------------------------------------------------------------------- /src/testing/internal/rtl/actAsync.ts: -------------------------------------------------------------------------------- 1 | // This is a helper required for React 19 testing. 2 | // There are currently multiple directions this could play out in RTL and none of 3 | // them has been released yet, so we are inlining this helper for now. 4 | // See https://github.com/testing-library/react-testing-library/pull/1214 5 | // and https://github.com/testing-library/react-testing-library/pull/1365 6 | 7 | import * as React from "react"; 8 | import * as DeprecatedReactTestUtils from "react-dom/test-utils"; 9 | 10 | const reactAct = 11 | typeof React.act === "function" ? React.act : DeprecatedReactTestUtils.act; 12 | 13 | export function actAsync(scope: () => T | Promise): Promise { 14 | return reactAct(async () => { 15 | return await scope(); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/testing/matchers/toBeDisposed.ts: -------------------------------------------------------------------------------- 1 | import type { MatcherFunction } from "expect"; 2 | import type { QueryRef } from "../../react/internal/index.js"; 3 | import { 4 | assertWrappedQueryRef, 5 | unwrapQueryRef, 6 | } from "../../react/internal/index.js"; 7 | 8 | export const toBeDisposed: MatcherFunction<[]> = function (_queryRef) { 9 | const hint = this.utils.matcherHint("toBeDisposed", "queryRef", "", { 10 | isNot: this.isNot, 11 | }); 12 | 13 | const queryRef = _queryRef as QueryRef; 14 | assertWrappedQueryRef(queryRef); 15 | 16 | const pass = unwrapQueryRef(queryRef).disposed; 17 | 18 | return { 19 | pass, 20 | message: () => { 21 | return `${hint}\n\nExpected queryRef ${ 22 | this.isNot ? "not " : "" 23 | }to be disposed, but it was${this.isNot ? "" : " not"}.`; 24 | }, 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /src/testing/matchers/toComplete.ts: -------------------------------------------------------------------------------- 1 | import type { MatcherFunction } from "expect"; 2 | import type { ObservableStream } from "../internal/index.js"; 3 | import type { TakeOptions } from "../internal/ObservableStream.js"; 4 | 5 | export const toComplete: MatcherFunction<[options?: TakeOptions]> = 6 | async function (actual, options) { 7 | const stream = actual as ObservableStream; 8 | const hint = this.utils.matcherHint("toComplete", "stream", ""); 9 | 10 | try { 11 | await stream.takeComplete(options); 12 | 13 | return { 14 | pass: true, 15 | message: () => { 16 | return hint + "\n\nExpected stream not to complete but it did."; 17 | }, 18 | }; 19 | } catch (error) { 20 | if ( 21 | error instanceof Error && 22 | error.message === "Timeout waiting for next event" 23 | ) { 24 | return { 25 | pass: false, 26 | message: () => 27 | hint + "\n\nExpected stream to complete but it did not.", 28 | }; 29 | } else { 30 | throw error; 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/testing/matchers/toEmitAnything.ts: -------------------------------------------------------------------------------- 1 | import type { MatcherFunction } from "expect"; 2 | import type { ObservableStream } from "../internal/index.js"; 3 | import type { TakeOptions } from "../internal/ObservableStream.js"; 4 | 5 | export const toEmitAnything: MatcherFunction<[options?: TakeOptions]> = 6 | async function (actual, options) { 7 | const stream = actual as ObservableStream; 8 | const hint = this.utils.matcherHint("toEmitAnything", "stream", ""); 9 | 10 | try { 11 | const value = await stream.peek(options); 12 | 13 | return { 14 | pass: true, 15 | message: () => { 16 | return ( 17 | hint + 18 | "\n\nExpected stream not to emit anything but it did." + 19 | "\n\nReceived:\n" + 20 | this.utils.printReceived(value) 21 | ); 22 | }, 23 | }; 24 | } catch (error) { 25 | if ( 26 | error instanceof Error && 27 | error.message === "Timeout waiting for next event" 28 | ) { 29 | return { 30 | pass: false, 31 | message: () => 32 | hint + "\n\nExpected stream to emit an event but it did not.", 33 | }; 34 | } else { 35 | throw error; 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/testing/matchers/toEmitNext.ts: -------------------------------------------------------------------------------- 1 | import type { MatcherFunction } from "expect"; 2 | import type { ObservableStream } from "../internal/index.js"; 3 | import type { TakeOptions } from "../internal/ObservableStream.js"; 4 | 5 | export const toEmitNext: MatcherFunction<[options?: TakeOptions]> = 6 | async function (actual, options) { 7 | const stream = actual as ObservableStream; 8 | const hint = this.utils.matcherHint( 9 | this.isNot ? ".not.toEmitValue" : "toEmitValue", 10 | "stream", 11 | "expected" 12 | ); 13 | 14 | try { 15 | await stream.takeNext(options); 16 | 17 | return { 18 | pass: true, 19 | message: () => { 20 | return hint + "\n\nExpected stream not to emit a value but it did."; 21 | }, 22 | }; 23 | } catch (error) { 24 | if ( 25 | error instanceof Error && 26 | error.message === "Timeout waiting for next event" 27 | ) { 28 | return { 29 | pass: false, 30 | message: () => 31 | hint + "\n\nExpected stream to emit a value but it did not.", 32 | }; 33 | } else { 34 | throw error; 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/testing/matchers/toEqualApolloQueryResult.ts: -------------------------------------------------------------------------------- 1 | import { iterableEquality } from "@jest/expect-utils"; 2 | import type { MatcherFunction } from "expect"; 3 | import type { ApolloQueryResult } from "../../core/index.js"; 4 | 5 | export const toEqualApolloQueryResult: MatcherFunction< 6 | [queryResult: ApolloQueryResult] 7 | > = function (actual, expected) { 8 | const queryResult = actual as ApolloQueryResult; 9 | const hint = this.utils.matcherHint( 10 | this.isNot ? ".not.toEqualApolloQueryResult" : "toEqualApolloQueryResult", 11 | "queryResult", 12 | "expected", 13 | { isNot: this.isNot, promise: this.promise } 14 | ); 15 | 16 | const pass = this.equals( 17 | queryResult, 18 | expected, 19 | // https://github.com/jestjs/jest/blob/22029ba06b69716699254bb9397f2b3bc7b3cf3b/packages/expect/src/matchers.ts#L62-L67 20 | [...this.customTesters, iterableEquality], 21 | true 22 | ); 23 | 24 | return { 25 | pass, 26 | message: () => { 27 | if (pass) { 28 | return hint + `\n\nExpected: not ${this.utils.printExpected(expected)}`; 29 | } 30 | 31 | return ( 32 | hint + 33 | "\n\n" + 34 | this.utils.printDiffOrStringify( 35 | expected, 36 | queryResult, 37 | "Expected", 38 | "Received", 39 | true 40 | ) 41 | ); 42 | }, 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /src/testing/matchers/toEqualFetchResult.ts: -------------------------------------------------------------------------------- 1 | import { iterableEquality } from "@jest/expect-utils"; 2 | import type { MatcherFunction } from "expect"; 3 | import type { FetchResult } from "../../core/index.js"; 4 | 5 | export const toEqualFetchResult: MatcherFunction<[result: FetchResult]> = 6 | function (actual, expected) { 7 | const result = actual as FetchResult; 8 | const hint = this.utils.matcherHint( 9 | this.isNot ? ".not.toEqualFetchResult" : "toEqualFetchResult", 10 | "result", 11 | "expected", 12 | { isNot: this.isNot, promise: this.promise } 13 | ); 14 | 15 | const pass = this.equals( 16 | result, 17 | expected, 18 | // https://github.com/jestjs/jest/blob/22029ba06b69716699254bb9397f2b3bc7b3cf3b/packages/expect/src/matchers.ts#L62-L67 19 | [...this.customTesters, iterableEquality], 20 | true 21 | ); 22 | 23 | return { 24 | pass, 25 | message: () => { 26 | if (pass) { 27 | return ( 28 | hint + `\n\nExpected: not ${this.utils.printExpected(expected)}` 29 | ); 30 | } 31 | 32 | return ( 33 | hint + 34 | "\n\n" + 35 | this.utils.printDiffOrStringify( 36 | expected, 37 | result, 38 | "Expected", 39 | "Received", 40 | true 41 | ) 42 | ); 43 | }, 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | // `tsconfig.json` for the editor only 2 | // this config includes additional files (e.g. tests) that 3 | // we don't want to see hooked up to the main build 4 | // it can also add a few more types for that purpose 5 | { 6 | "compilerOptions": { 7 | "noEmit": true, 8 | "declaration": false, 9 | "declarationMap": false, 10 | "lib": ["DOM", "es2015", "esnext.asynciterable", "ES2021.WeakRef"], 11 | "types": [ 12 | "jest", 13 | "node", 14 | "./testing/matchers/index.d.ts", 15 | "@testing-library/react-render-stream/expect" 16 | ] 17 | }, 18 | "extends": "../tsconfig.json", 19 | "include": ["./**/*.ts", "./**/*.tsx"], 20 | "exclude": [] 21 | } 22 | -------------------------------------------------------------------------------- /src/utilities/caching/__tests__/sizes.test.ts: -------------------------------------------------------------------------------- 1 | import { expectTypeOf } from "expect-type"; 2 | import type { CacheSizes, defaultCacheSizes } from "../sizes"; 3 | 4 | test.skip("type tests", () => { 5 | expectTypeOf().toMatchTypeOf< 6 | keyof typeof defaultCacheSizes 7 | >(); 8 | expectTypeOf().toMatchTypeOf< 9 | keyof CacheSizes 10 | >(); 11 | }); 12 | -------------------------------------------------------------------------------- /src/utilities/caching/index.ts: -------------------------------------------------------------------------------- 1 | export { AutoCleanedStrongCache, AutoCleanedWeakCache } from "./caches.js"; 2 | export type { CacheSizes } from "./sizes.js"; 3 | export { cacheSizes, defaultCacheSizes } from "./sizes.js"; 4 | -------------------------------------------------------------------------------- /src/utilities/common/__tests__/canUse.ts: -------------------------------------------------------------------------------- 1 | import { canUseDOM, canUseLayoutEffect } from "../canUse"; 2 | 3 | describe("canUse* boolean constants", () => { 4 | // https://github.com/apollographql/apollo-client/pull/9675 5 | it("sets canUseDOM to true when using Jest in Node.js with jsdom", () => { 6 | expect(canUseDOM).toBe(true); 7 | }); 8 | it("sets canUseLayoutEffect to false when using Jest in Node.js with jsdom", () => { 9 | expect(canUseLayoutEffect).toBe(false); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/utilities/common/arrays.ts: -------------------------------------------------------------------------------- 1 | // A version of Array.isArray that works better with readonly arrays. 2 | export const isArray: (a: any) => a is any[] | readonly any[] = Array.isArray; 3 | 4 | export function isNonEmptyArray(value?: ArrayLike): value is Array { 5 | return Array.isArray(value) && value.length > 0; 6 | } 7 | -------------------------------------------------------------------------------- /src/utilities/common/cloneDeep.ts: -------------------------------------------------------------------------------- 1 | const { toString } = Object.prototype; 2 | 3 | /** 4 | * Deeply clones a value to create a new instance. 5 | */ 6 | export function cloneDeep(value: T): T { 7 | return cloneDeepHelper(value); 8 | } 9 | 10 | function cloneDeepHelper(val: T, seen?: Map): T { 11 | switch (toString.call(val)) { 12 | case "[object Array]": { 13 | seen = seen || new Map(); 14 | if (seen.has(val)) return seen.get(val); 15 | const copy: T & any[] = (val as any).slice(0); 16 | seen.set(val, copy); 17 | copy.forEach(function (child, i) { 18 | copy[i] = cloneDeepHelper(child, seen); 19 | }); 20 | return copy; 21 | } 22 | 23 | case "[object Object]": { 24 | seen = seen || new Map(); 25 | if (seen.has(val)) return seen.get(val); 26 | // High fidelity polyfills of Object.create and Object.getPrototypeOf are 27 | // possible in all JS environments, so we will assume they exist/work. 28 | const copy = Object.create(Object.getPrototypeOf(val)); 29 | seen.set(val, copy); 30 | Object.keys(val as T & Record).forEach((key) => { 31 | copy[key] = cloneDeepHelper((val as any)[key], seen); 32 | }); 33 | return copy; 34 | } 35 | 36 | default: 37 | return val; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/utilities/common/compact.ts: -------------------------------------------------------------------------------- 1 | import type { TupleToIntersection } from "./mergeDeep.js"; 2 | 3 | /** 4 | * Merges the provided objects shallowly and removes 5 | * all properties with an `undefined` value 6 | */ 7 | export function compact( 8 | ...objects: TArgs 9 | ): TupleToIntersection { 10 | const result = Object.create(null); 11 | 12 | objects.forEach((obj) => { 13 | if (!obj) return; 14 | Object.keys(obj).forEach((key) => { 15 | const value = (obj as any)[key]; 16 | if (value !== void 0) { 17 | result[key] = value; 18 | } 19 | }); 20 | }); 21 | 22 | return result; 23 | } 24 | -------------------------------------------------------------------------------- /src/utilities/common/errorHandling.ts: -------------------------------------------------------------------------------- 1 | import type { FetchResult } from "../../link/core/index.js"; 2 | import { isNonEmptyArray } from "./arrays.js"; 3 | import { isExecutionPatchIncrementalResult } from "./incrementalResult.js"; 4 | 5 | export function graphQLResultHasError(result: FetchResult): boolean { 6 | const errors = getGraphQLErrorsFromResult(result); 7 | return isNonEmptyArray(errors); 8 | } 9 | 10 | export function getGraphQLErrorsFromResult(result: FetchResult) { 11 | const graphQLErrors = 12 | isNonEmptyArray(result.errors) ? result.errors.slice(0) : []; 13 | 14 | if ( 15 | isExecutionPatchIncrementalResult(result) && 16 | isNonEmptyArray(result.incremental) 17 | ) { 18 | result.incremental.forEach((incrementalResult) => { 19 | if (incrementalResult.errors) { 20 | graphQLErrors.push(...incrementalResult.errors); 21 | } 22 | }); 23 | } 24 | return graphQLErrors; 25 | } 26 | -------------------------------------------------------------------------------- /src/utilities/common/makeUniqueId.ts: -------------------------------------------------------------------------------- 1 | const prefixCounts = new Map(); 2 | 3 | // These IDs won't be globally unique, but they will be unique within this 4 | // process, thanks to the counter, and unguessable thanks to the random suffix. 5 | export function makeUniqueId(prefix: string) { 6 | const count = prefixCounts.get(prefix) || 1; 7 | prefixCounts.set(prefix, count + 1); 8 | return `${prefix}:${count}:${Math.random().toString(36).slice(2)}`; 9 | } 10 | -------------------------------------------------------------------------------- /src/utilities/common/maybeDeepFreeze.ts: -------------------------------------------------------------------------------- 1 | import { isNonNullObject } from "./objects.js"; 2 | 3 | export function deepFreeze(value: any) { 4 | const workSet = new Set([value]); 5 | workSet.forEach((obj) => { 6 | if (isNonNullObject(obj) && shallowFreeze(obj) === obj) { 7 | Object.getOwnPropertyNames(obj).forEach((name) => { 8 | if (isNonNullObject(obj[name])) workSet.add(obj[name]); 9 | }); 10 | } 11 | }); 12 | return value; 13 | } 14 | 15 | function shallowFreeze(obj: T): T | null { 16 | if (__DEV__ && !Object.isFrozen(obj)) { 17 | try { 18 | Object.freeze(obj); 19 | } catch (e) { 20 | // Some types like Uint8Array and Node.js's Buffer cannot be frozen, but 21 | // they all throw a TypeError when you try, so we re-throw any exceptions 22 | // that are not TypeErrors, since that would be unexpected. 23 | if (e instanceof TypeError) return null; 24 | throw e; 25 | } 26 | } 27 | return obj; 28 | } 29 | 30 | export function maybeDeepFreeze(obj: T): T { 31 | if (__DEV__) { 32 | deepFreeze(obj); 33 | } 34 | return obj; 35 | } 36 | -------------------------------------------------------------------------------- /src/utilities/common/mergeOptions.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | QueryOptions, 3 | WatchQueryOptions, 4 | MutationOptions, 5 | OperationVariables, 6 | } from "../../core/index.js"; 7 | 8 | import { compact } from "./compact.js"; 9 | 10 | type OptionsUnion = 11 | | WatchQueryOptions 12 | | QueryOptions 13 | | MutationOptions; 14 | 15 | export function mergeOptions< 16 | TDefaultOptions extends Partial>, 17 | TOptions extends TDefaultOptions, 18 | >( 19 | defaults: TDefaultOptions | Partial | undefined, 20 | options: TOptions | Partial 21 | ): TOptions & TDefaultOptions { 22 | return compact( 23 | defaults, 24 | options, 25 | options.variables && { 26 | variables: compact({ 27 | ...(defaults && defaults.variables), 28 | ...options.variables, 29 | }), 30 | } 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/utilities/common/objects.ts: -------------------------------------------------------------------------------- 1 | export function isNonNullObject(obj: any): obj is Record { 2 | return obj !== null && typeof obj === "object"; 3 | } 4 | 5 | export function isPlainObject(obj: any): obj is Record { 6 | return ( 7 | obj !== null && 8 | typeof obj === "object" && 9 | (Object.getPrototypeOf(obj) === Object.prototype || 10 | Object.getPrototypeOf(obj) === null) 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/utilities/common/omitDeep.ts: -------------------------------------------------------------------------------- 1 | import type { DeepOmit } from "../types/DeepOmit.js"; 2 | import { isPlainObject } from "./objects.js"; 3 | 4 | export function omitDeep(value: T, key: K) { 5 | return __omitDeep(value, key); 6 | } 7 | 8 | function __omitDeep( 9 | value: T, 10 | key: K, 11 | known = new Map() 12 | ): DeepOmit { 13 | if (known.has(value)) { 14 | return known.get(value); 15 | } 16 | 17 | let modified = false; 18 | 19 | if (Array.isArray(value)) { 20 | const array: any[] = []; 21 | known.set(value, array); 22 | 23 | value.forEach((value, index) => { 24 | const result = __omitDeep(value, key, known); 25 | modified ||= result !== value; 26 | 27 | array[index] = result; 28 | }); 29 | 30 | if (modified) { 31 | return array as DeepOmit; 32 | } 33 | } else if (isPlainObject(value)) { 34 | const obj = Object.create(Object.getPrototypeOf(value)); 35 | known.set(value, obj); 36 | 37 | Object.keys(value).forEach((k) => { 38 | if (k === key) { 39 | modified = true; 40 | return; 41 | } 42 | 43 | const result = __omitDeep(value[k], key, known); 44 | modified ||= result !== value[k]; 45 | 46 | obj[k] = result; 47 | }); 48 | 49 | if (modified) { 50 | return obj; 51 | } 52 | } 53 | 54 | return value as DeepOmit; 55 | } 56 | -------------------------------------------------------------------------------- /src/utilities/common/stringifyForDisplay.ts: -------------------------------------------------------------------------------- 1 | import { makeUniqueId } from "./makeUniqueId.js"; 2 | 3 | export function stringifyForDisplay(value: any, space = 0): string { 4 | const undefId = makeUniqueId("stringifyForDisplay"); 5 | return JSON.stringify( 6 | value, 7 | (key, value) => { 8 | return value === void 0 ? undefId : value; 9 | }, 10 | space 11 | ) 12 | .split(JSON.stringify(undefId)) 13 | .join(""); 14 | } 15 | -------------------------------------------------------------------------------- /src/utilities/common/stripTypename.ts: -------------------------------------------------------------------------------- 1 | import { omitDeep } from "./omitDeep.js"; 2 | 3 | export function stripTypename(value: T) { 4 | return omitDeep(value, "__typename"); 5 | } 6 | -------------------------------------------------------------------------------- /src/utilities/globals/global.ts: -------------------------------------------------------------------------------- 1 | import { maybe } from "./maybe.js"; 2 | 3 | declare global { 4 | const __DEV__: boolean; // will be removed in `dist` by the `postprocessDist` script 5 | interface Window { 6 | __DEV__?: boolean; 7 | } 8 | } 9 | 10 | export default (maybe(() => globalThis) || 11 | maybe(() => window) || 12 | maybe(() => self) || 13 | maybe(() => global) || 14 | // We don't expect the Function constructor ever to be invoked at runtime, as 15 | // long as at least one of globalThis, window, self, or global is defined, so 16 | // we are under no obligation to make it easy for static analysis tools to 17 | // detect syntactic usage of the Function constructor. If you think you can 18 | // improve your static analysis to detect this obfuscation, think again. This 19 | // is an arms race you cannot win, at least not in JavaScript. 20 | maybe(function () { 21 | return maybe.constructor("return this")(); 22 | })) as typeof globalThis & Window; 23 | -------------------------------------------------------------------------------- /src/utilities/globals/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | invariant, 3 | newInvariantError, 4 | InvariantError, 5 | } from "./invariantWrappers.js"; 6 | 7 | export { maybe } from "./maybe.js"; 8 | export { default as global } from "./global.js"; 9 | export { invariant, newInvariantError, InvariantError }; 10 | 11 | /** 12 | * @deprecated we do not use this internally anymore, 13 | * it is just exported for backwards compatibility 14 | */ 15 | // this file is extempt from automatic `__DEV__` replacement 16 | // so we have to write it out here 17 | // @ts-ignore 18 | export const DEV = globalThis.__DEV__ !== false; 19 | export { DEV as __DEV__ }; 20 | -------------------------------------------------------------------------------- /src/utilities/globals/maybe.ts: -------------------------------------------------------------------------------- 1 | export function maybe(thunk: () => T): T | undefined { 2 | try { 3 | return thunk(); 4 | } catch {} 5 | } 6 | -------------------------------------------------------------------------------- /src/utilities/graphql/__tests__/storeUtils.ts: -------------------------------------------------------------------------------- 1 | import { getStoreKeyName } from "../storeUtils"; 2 | 3 | describe("getStoreKeyName", () => { 4 | it( 5 | "should return a deterministic version of the store key name no matter " + 6 | "which order the args object properties are in", 7 | () => { 8 | const validStoreKeyName = 9 | 'someField({"prop1":"value1","prop2":"value2"})'; 10 | let generatedStoreKeyName = getStoreKeyName("someField", { 11 | prop1: "value1", 12 | prop2: "value2", 13 | }); 14 | expect(generatedStoreKeyName).toEqual(validStoreKeyName); 15 | 16 | generatedStoreKeyName = getStoreKeyName("someField", { 17 | prop2: "value2", 18 | prop1: "value1", 19 | }); 20 | expect(generatedStoreKeyName).toEqual(validStoreKeyName); 21 | } 22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /src/utilities/graphql/operations.ts: -------------------------------------------------------------------------------- 1 | import type { DocumentNode } from "../../core/index.js"; 2 | import { getOperationDefinition } from "./getFromAST.js"; 3 | 4 | function isOperation( 5 | document: DocumentNode, 6 | operation: "query" | "mutation" | "subscription" 7 | ) { 8 | return getOperationDefinition(document)?.operation === operation; 9 | } 10 | 11 | export function isMutationOperation(document: DocumentNode) { 12 | return isOperation(document, "mutation"); 13 | } 14 | 15 | export function isQueryOperation(document: DocumentNode) { 16 | return isOperation(document, "query"); 17 | } 18 | 19 | export function isSubscriptionOperation(document: DocumentNode) { 20 | return isOperation(document, "subscription"); 21 | } 22 | -------------------------------------------------------------------------------- /src/utilities/graphql/print.ts: -------------------------------------------------------------------------------- 1 | import type { ASTNode } from "graphql"; 2 | import { print as origPrint } from "graphql"; 3 | import { 4 | AutoCleanedWeakCache, 5 | cacheSizes, 6 | defaultCacheSizes, 7 | } from "../caching/index.js"; 8 | import { registerGlobalCache } from "../caching/getMemoryInternals.js"; 9 | 10 | let printCache!: AutoCleanedWeakCache; 11 | export const print = Object.assign( 12 | (ast: ASTNode) => { 13 | let result = printCache.get(ast); 14 | 15 | if (!result) { 16 | result = origPrint(ast); 17 | printCache.set(ast, result); 18 | } 19 | return result; 20 | }, 21 | { 22 | reset() { 23 | printCache = new AutoCleanedWeakCache( 24 | cacheSizes.print || defaultCacheSizes.print 25 | ); 26 | }, 27 | } 28 | ); 29 | print.reset(); 30 | 31 | if (__DEV__) { 32 | registerGlobalCache("print", () => (printCache ? printCache.size : 0)); 33 | } 34 | -------------------------------------------------------------------------------- /src/utilities/observables/Observable.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Observer, 3 | Subscription as ObservableSubscription, 4 | Subscriber, 5 | } from "zen-observable-ts"; 6 | import { Observable } from "zen-observable-ts"; 7 | 8 | // This simplified polyfill attempts to follow the ECMAScript Observable 9 | // proposal (https://github.com/zenparsing/es-observable) 10 | import "symbol-observable"; 11 | 12 | export type { Observer, ObservableSubscription, Subscriber }; 13 | 14 | // The zen-observable package defines Observable.prototype[Symbol.observable] 15 | // when Symbol is supported, but RxJS interop depends on also setting this fake 16 | // '@@observable' string as a polyfill for Symbol.observable. 17 | const { prototype } = Observable; 18 | const fakeObsSymbol = "@@observable" as keyof typeof prototype; 19 | if (!prototype[fakeObsSymbol]) { 20 | // @ts-expect-error 21 | prototype[fakeObsSymbol] = function () { 22 | return this; 23 | }; 24 | } 25 | 26 | export { Observable }; 27 | -------------------------------------------------------------------------------- /src/utilities/observables/iteration.ts: -------------------------------------------------------------------------------- 1 | import type { Observer } from "./Observable.js"; 2 | 3 | export function iterateObserversSafely( 4 | observers: Set>, 5 | method: keyof Observer, 6 | argument?: A 7 | ) { 8 | // In case observers is modified during iteration, we need to commit to the 9 | // original elements, which also provides an opportunity to filter them down 10 | // to just the observers with the given method. 11 | const observersWithMethod: Observer[] = []; 12 | observers.forEach((obs) => obs[method] && observersWithMethod.push(obs)); 13 | observersWithMethod.forEach((obs) => (obs as any)[method](argument)); 14 | } 15 | -------------------------------------------------------------------------------- /src/utilities/promises/preventUnhandledRejection.ts: -------------------------------------------------------------------------------- 1 | export function preventUnhandledRejection(promise: Promise): Promise { 2 | promise.catch(() => {}); 3 | 4 | return promise; 5 | } 6 | -------------------------------------------------------------------------------- /src/utilities/subscriptions/shared.ts: -------------------------------------------------------------------------------- 1 | import { fallbackHttpConfig } from "../../link/http/selectHttpOptionsAndBody.js"; 2 | 3 | export type CreateMultipartSubscriptionOptions = { 4 | fetch?: WindowOrWorkerGlobalScope["fetch"]; 5 | headers?: Record; 6 | }; 7 | 8 | export function generateOptionsForMultipartSubscription( 9 | headers: Record 10 | ) { 11 | const options: { headers: Record; body?: string } = { 12 | ...fallbackHttpConfig.options, 13 | headers: { 14 | ...(headers || {}), 15 | ...fallbackHttpConfig.headers, 16 | accept: 17 | "multipart/mixed;boundary=graphql;subscriptionSpec=1.0,application/json", 18 | }, 19 | }; 20 | return options; 21 | } 22 | -------------------------------------------------------------------------------- /src/utilities/types/DeepOmit.ts: -------------------------------------------------------------------------------- 1 | import type { Primitive } from "./Primitive.js"; 2 | 3 | // DeepOmit primitives include functions since these are unmodified. 4 | type DeepOmitPrimitive = Primitive | Function; 5 | 6 | export type DeepOmitArray = { 7 | [P in keyof T]: DeepOmit; 8 | }; 9 | 10 | // Unfortunately there is one major flaw in this type: This will omit properties 11 | // from class instances in the return type even though our omitDeep helper 12 | // ignores class instances, therefore resulting in a type mismatch between 13 | // the return value and the runtime value. 14 | // 15 | // It is not currently possible with TypeScript to distinguish between plain 16 | // objects and class instances. 17 | // https://github.com/microsoft/TypeScript/issues/29063 18 | // 19 | // This should be fine as of the time of this writing until omitDeep gets 20 | // broader use since this utility is only used to strip __typename from 21 | // `variables`; a case in which class instances are invalid anyways. 22 | export type DeepOmit = 23 | T extends DeepOmitPrimitive ? T 24 | : { 25 | [P in keyof T as P extends K ? never : P]: T[P] extends infer TP ? 26 | TP extends DeepOmitPrimitive ? TP 27 | : TP extends any[] ? DeepOmitArray 28 | : DeepOmit 29 | : never; 30 | }; 31 | -------------------------------------------------------------------------------- /src/utilities/types/IsStrictlyAny.ts: -------------------------------------------------------------------------------- 1 | // Returns true if T is any, or false for any other type. 2 | // Inspired by https://stackoverflow.com/a/61625296/128454. 3 | export type IsStrictlyAny = 4 | UnionToIntersection> extends never ? true : false; 5 | 6 | // If (and only if) T is any, the union 'a' | 1 is returned here, representing 7 | // both branches of this conditional type. Only UnionForAny produces this 8 | // union type; all other inputs produce the 1 literal type. 9 | type UnionForAny = T extends never ? "a" : 1; 10 | 11 | // If that 'a' | 1 union is then passed to UnionToIntersection, the result 12 | // should be 'a' & 1, which TypeScript simplifies to the never type, since the 13 | // literal type 'a' and the literal type 1 are incompatible. More explanation of 14 | // this helper type: https://stackoverflow.com/a/50375286/62076. 15 | type UnionToIntersection = 16 | (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I 17 | : never; 18 | -------------------------------------------------------------------------------- /src/utilities/types/NoInfer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Helper type that allows using a type in a way that cannot be "widened" by inference on the value it is used on. 3 | 4 | This type was first suggested [in this Github discussion](https://github.com/microsoft/TypeScript/issues/14829#issuecomment-504042546). 5 | 6 | Example usage: 7 | ```ts 8 | export function useQuery< 9 | TData = any, 10 | TVariables extends OperationVariables = OperationVariables, 11 | >( 12 | query: DocumentNode | TypedDocumentNode, 13 | options: QueryHookOptions, NoInfer> = Object.create(null), 14 | ) 15 | ``` 16 | In this case, `TData` and `TVariables` should be inferred from `query`, but never widened from something in `options`. 17 | 18 | So, in this code example: 19 | ```ts 20 | declare const typedNode: TypedDocumentNode<{ foo: string}, { bar: number }> 21 | const { variables } = useQuery(typedNode, { variables: { bar: 4, nonExistingVariable: "string" } }); 22 | ``` 23 | Without the use of `NoInfer`, `variables` would now be of the type `{ bar: number, nonExistingVariable: "string" }`. 24 | With `NoInfer`, it will instead give an error on `nonExistingVariable`. 25 | */ 26 | export type NoInfer = [T][T extends any ? 0 : never]; 27 | -------------------------------------------------------------------------------- /src/utilities/types/OnlyRequiredProperties.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a new type that only contains the required properties from `T` 3 | */ 4 | export type OnlyRequiredProperties = { 5 | [K in keyof T as {} extends Pick ? never : K]: T[K]; 6 | }; 7 | -------------------------------------------------------------------------------- /src/utilities/types/Prettify.ts: -------------------------------------------------------------------------------- 1 | export type Prettify = { [K in keyof T]: T[K] } & {}; 2 | -------------------------------------------------------------------------------- /src/utilities/types/Primitive.ts: -------------------------------------------------------------------------------- 1 | // Matches any primitive value: https://developer.mozilla.org/en-US/docs/Glossary/Primitive. 2 | export type Primitive = 3 | | null 4 | | undefined 5 | | string 6 | | number 7 | | boolean 8 | | symbol 9 | | bigint; 10 | -------------------------------------------------------------------------------- /src/utilities/types/RemoveIndexSignature.ts: -------------------------------------------------------------------------------- 1 | export type RemoveIndexSignature = { 2 | [K in keyof T as string extends K ? never 3 | : number extends K ? never 4 | : symbol extends K ? never 5 | : K]: T[K]; 6 | }; 7 | -------------------------------------------------------------------------------- /src/utilities/types/TODO.ts: -------------------------------------------------------------------------------- 1 | /** @internal */ 2 | export type TODO = any; 3 | -------------------------------------------------------------------------------- /src/utilities/types/UnionToIntersection.ts: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/a/50375286/2012454 2 | export type UnionToIntersection = 3 | (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I 4 | : never; 5 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | export const version = "local"; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "strictNullChecks": true, 5 | "noUnusedParameters": false, 6 | "noUnusedLocals": true, 7 | "skipLibCheck": true, 8 | "moduleResolution": "node", 9 | "importHelpers": true, 10 | "sourceMap": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "target": "es5", 14 | "module": "es2015", 15 | "esModuleInterop": true, 16 | "experimentalDecorators": true, 17 | "outDir": "./dist", 18 | "lib": ["es2015", "esnext.asynciterable"], 19 | "jsx": "react", 20 | "strict": true 21 | }, 22 | "include": ["src/**/*.ts", "src/**/*.tsx"], 23 | "exclude": ["src/**/__tests__/**/*"] 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./src/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /tsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", 3 | 4 | // Inherit the TSDoc configuration for API Extractor 5 | "extends": ["@microsoft/api-extractor/extends/tsdoc-base.json"], 6 | 7 | "tagDefinitions": [ 8 | { 9 | "tagName": "@since", 10 | "syntaxKind": "block", 11 | "allowMultiple": false 12 | }, 13 | { 14 | "tagName": "@docGroup", 15 | "syntaxKind": "block", 16 | "allowMultiple": false 17 | } 18 | ], 19 | 20 | "supportForTags": { 21 | "@since": true, 22 | "@docGroup": true 23 | } 24 | } 25 | --------------------------------------------------------------------------------