From 09d38bf98b282e2f37fda03f43985e59dd472e2b Mon Sep 17 00:00:00 2001 From: Naor Peled Date: Mon, 27 Jan 2025 10:05:10 +0200 Subject: [PATCH 01/11] Update queries.mdx --- docs/source/data/queries.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/data/queries.mdx b/docs/source/data/queries.mdx index bd15ebf1a84..7eef344c6ad 100644 --- a/docs/source/data/queries.mdx +++ b/docs/source/data/queries.mdx @@ -439,6 +439,7 @@ This is the default fetch policy. Apollo Client executes the query _only_ against the cache. It never queries your server in this case. + A `cache-only` query throws an error if the cache does not contain data for all requested fields. @@ -469,6 +470,7 @@ Provides a fast response while also helping to keep cached data consistent with Apollo Client executes the full query against your GraphQL server, _without_ first checking the cache. The query's result _is_ stored in the cache. + Prioritizes consistency with server data, but can't provide a near-instantaneous response when cached data is available. From d43f629f8dee01fc0e3c23bdb6a7b1ab3dac5a83 Mon Sep 17 00:00:00 2001 From: Naor Peled Date: Mon, 27 Jan 2025 10:40:05 +0200 Subject: [PATCH 02/11] Update queries.mdx --- docs/source/data/queries.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/data/queries.mdx b/docs/source/data/queries.mdx index 7eef344c6ad..8c11a7cac6f 100644 --- a/docs/source/data/queries.mdx +++ b/docs/source/data/queries.mdx @@ -455,6 +455,7 @@ A `cache-only` query throws an error if the cache does not contain data for all Apollo Client executes the full query against both the cache _and_ your GraphQL server. The query automatically updates if the result of the server-side query modifies cached fields. + Provides a fast response while also helping to keep cached data consistent with server data. From 1c5e7951e382fe834aef881aab54f5e44047d0a3 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Mon, 27 Jan 2025 10:00:14 +0100 Subject: [PATCH 03/11] ci: add write permissions for issues in cleanup checks workflow --- .github/workflows/cleanup-checks.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cleanup-checks.yml b/.github/workflows/cleanup-checks.yml index 132f64ab792..7f91d6ce1db 100644 --- a/.github/workflows/cleanup-checks.yml +++ b/.github/workflows/cleanup-checks.yml @@ -13,6 +13,8 @@ jobs: github.event.review.state == 'APPROVED' && contains(github.event.pull_request.labels.*.name, 'auto-cleanup') == false runs-on: ubuntu-latest + permissions: + issues: write steps: - name: Checkout repo uses: actions/checkout@v4 From 63e308c3efb1b826f36ab7629994d5c69e3adce1 Mon Sep 17 00:00:00 2001 From: Maria Elisabeth Schreiber Date: Mon, 27 Jan 2025 09:20:05 -0700 Subject: [PATCH 04/11] Update apollo-link-subscriptions.md --- docs/source/api/link/apollo-link-subscriptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/api/link/apollo-link-subscriptions.md b/docs/source/api/link/apollo-link-subscriptions.md index 7dc4a71a537..313e33fab4a 100644 --- a/docs/source/api/link/apollo-link-subscriptions.md +++ b/docs/source/api/link/apollo-link-subscriptions.md @@ -33,7 +33,7 @@ const link = new GraphQLWsLink( The `GraphQLWsLink` constructor takes a single argument, which is a `Client` returned from the `graphql-ws` `createClient` function. -The `createClient` function can take many options, described in the [`graphql-ws` docs for `ClientOptions`](https://the-guild.dev/graphql/ws/docs/interfaces/client.ClientOptions). The one required option is `url`, which is the URL (typically starting with `ws://` or `wss://`, which are the equivalents of `http://` and `https://` respectively) to your WebSocket server. (Note that this differs from the [older link's URL option](./apollo-link-ws), which is named `uri` instead of `url`.) +The `createClient` function can take many options, described in the [`graphql-ws` docs for `ClientOptions`](https://the-guild.dev/graphql/ws/docs/client/interfaces/ClientOptions). The one required option is `url`, which is the URL (typically starting with `ws://` or `wss://`, which are the equivalents of `http://` and `https://` respectively) to your WebSocket server. (Note that this differs from the [older link's URL option](./apollo-link-ws), which is named `uri` instead of `url`.) ## Usage From 0ea97e5877e58441392c2e937a4f8933cb0f6edc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:21:28 -0700 Subject: [PATCH 05/11] Version Packages (#12293) Co-authored-by: github-actions[bot] --- .changeset/lemon-singers-pay.md | 5 ----- .changeset/ten-rules-stare.md | 5 ----- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) delete mode 100644 .changeset/lemon-singers-pay.md delete mode 100644 .changeset/ten-rules-stare.md diff --git a/.changeset/lemon-singers-pay.md b/.changeset/lemon-singers-pay.md deleted file mode 100644 index bf013dce813..00000000000 --- a/.changeset/lemon-singers-pay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@apollo/client": patch ---- - -Remove unused dependency `response-iterator` diff --git a/.changeset/ten-rules-stare.md b/.changeset/ten-rules-stare.md deleted file mode 100644 index f7d5ebdfa7b..00000000000 --- a/.changeset/ten-rules-stare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@apollo/client": patch ---- - -Fixes an issue where `client.watchFragment`/`useFragment` with `@includes` crashes when a separate cache update writes to the conditionally included fields. diff --git a/CHANGELOG.md b/CHANGELOG.md index 33b1eb2f84c..fb62dad7b77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # @apollo/client +## 3.12.8 + +### Patch Changes + +- [#12292](https://github.com/apollographql/apollo-client/pull/12292) [`3abd944`](https://github.com/apollographql/apollo-client/commit/3abd944e4adde5d94d91133f2bf6ed1c7744f8c5) Thanks [@phryneas](https://github.com/phryneas)! - Remove unused dependency `response-iterator` + +- [#12287](https://github.com/apollographql/apollo-client/pull/12287) [`bf313a3`](https://github.com/apollographql/apollo-client/commit/bf313a39d342a73dc3e9b3db9415c71c2573db3f) Thanks [@phryneas](https://github.com/phryneas)! - Fixes an issue where `client.watchFragment`/`useFragment` with `@includes` crashes when a separate cache update writes to the conditionally included fields. + ## 3.12.7 ### Patch Changes diff --git a/package-lock.json b/package-lock.json index 5a2cca35bcf..7f9ab4c9b58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@apollo/client", - "version": "3.12.7", + "version": "3.12.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@apollo/client", - "version": "3.12.7", + "version": "3.12.8", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index dd4d8f617ee..ca77d5bfbc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@apollo/client", - "version": "3.12.7", + "version": "3.12.8", "description": "A fully-featured caching GraphQL client.", "private": true, "keywords": [ From 7f71d7e769620501690bf55f4ce6488587b8ff65 Mon Sep 17 00:00:00 2001 From: Maria Elisabeth Schreiber Date: Mon, 27 Jan 2025 12:50:04 -0700 Subject: [PATCH 06/11] Update sidebar --- docs/source/_sidebar.yaml | 177 ++++++++++++++++++++++++++++++++++++++ docs/source/config.json | 107 ----------------------- 2 files changed, 177 insertions(+), 107 deletions(-) create mode 100644 docs/source/_sidebar.yaml delete mode 100644 docs/source/config.json diff --git a/docs/source/_sidebar.yaml b/docs/source/_sidebar.yaml new file mode 100644 index 00000000000..d26de2ac14a --- /dev/null +++ b/docs/source/_sidebar.yaml @@ -0,0 +1,177 @@ +switcher: + heading: "Apollo Client (Web)" + versions: + - label: v3 + latest: true + href: ./ + - label: v2 + href: ./v2 +items: + - label: Introduction + href: "." + - label: Why Apollo Client? + href: ./why-apollo + - label: Get started + href: ./get-started + - label: Changelog + href: https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md + - label: Fetching + children: + - label: Queries + href: ./data/queries + - label: Suspense + href: ./data/suspense + - label: Fragments + href: ./data/fragments + - label: Mutations + href: ./data/mutations + - label: Refetching + href: ./data/refetching + - label: Subscriptions + href: ./data/subscriptions + - label: Directives + href: ./data/directives + - label: Error handling + href: ./data/error-handling + - label: Document transforms + href: ./data/document-transforms + - label: Best practices + href: ./data/operation-best-practices + - label: Caching + children: + - label: Overview + href: ./caching/overview + - label: Configuration + href: ./caching/cache-configuration + - label: Reading and writing + href: ./caching/cache-interaction + - label: Garbage collection and eviction + href: ./caching/garbage-collection + - label: Customizing field behavior + href: ./caching/cache-field-behavior + - label: Memory Management + href: ./caching/memory-management + - label: Advanced topics + href: ./caching/advanced-topics + - label: Pagination + children: + - label: Overview + href: ./pagination/overview + - label: Core API + href: ./pagination/core-api + - label: Offset-based + href: ./pagination/offset-based + - label: Cursor-based + href: ./pagination/cursor-based + - label: keyArgs + href: ./pagination/key-args + - label: Local State + children: + - label: Overview + href: ./local-state/local-state-management + - label: Local-only fields + href: ./local-state/managing-state-with-field-policies + - label: Reactive variables + href: ./local-state/reactive-variables + - label: Client-side schema + href: ./local-state/client-side-schema + - label: Local resolvers + href: ./local-state/local-resolvers + - label: Development & Testing + children: + - label: Developer tools + href: ./development-testing/developer-tooling + - label: Using TypeScript + href: ./development-testing/static-typing + - label: Testing React components + href: ./development-testing/testing + - label: Schema-driven testing + href: ./development-testing/schema-driven-testing + - label: Mocking schema capabilities + href: ./development-testing/client-schema-mocking + - label: Reducing bundle size + href: ./development-testing/reducing-bundle-size + - label: Performance + children: + - label: Improving performance + href: ./performance/performance + - label: Optimistic mutation results + href: ./performance/optimistic-ui + - label: Server-side rendering + href: ./performance/server-side-rendering + - label: Compiling queries with Babel + href: ./performance/babel + - label: Integrations + children: + - label: Using Apollo Client with your view layer + href: ./integrations/integrations + - label: Integrating with React Native + href: ./integrations/react-native + - label: Loading queries with Webpack + href: ./integrations/webpack + - label: Networking + children: + - label: Basic HTTP networking + href: ./networking/basic-http-networking + - label: Advanced HTTP networking + href: ./networking/advanced-http-networking + - label: Authentication + href: ./networking/authentication + - label: Migrating + children: + - label: Migrating to Apollo Client 3.0 + href: ./migrating/apollo-client-3-migration + - label: Hooks migration guide + href: ./migrating/hooks-migration + - label: API Reference + children: + - label: Core + children: + - label: ApolloClient + href: ./api/core/ApolloClient + - label: InMemoryCache + href: ./api/cache/InMemoryCache + - label: ObservableQuery + href: ./api/core/ObservableQuery + - label: React + children: + - label: Hooks + href: ./api/react/hooks + - label: Preloading + href: ./api/react/preloading + - label: Testing + href: ./api/react/testing + - label: SSR + href: ./api/react/ssr + - label: Components (deprecated) + href: ./api/react/components + - label: HOC (deprecated) + href: ./api/react/hoc + - label: Apollo Link + children: + - label: Overview + href: ./api/link/introduction + - label: HTTP + href: ./api/link/apollo-link-http + - label: HTTP Batch + href: ./api/link/apollo-link-batch-http + - label: Context + href: ./api/link/apollo-link-context + - label: Error + href: ./api/link/apollo-link-error + - label: Persisted Queries + href: ./api/link/persisted-queries + - label: Remove Typename + href: ./api/link/apollo-link-remove-typename + - label: REST + href: ./api/link/apollo-link-rest + - label: Retry + href: ./api/link/apollo-link-retry + - label: Schema + href: ./api/link/apollo-link-schema + - label: Subscriptions (newer protocol) + href: ./api/link/apollo-link-subscriptions + - label: WebSocket (older protocol) + href: ./api/link/apollo-link-ws + - label: Community links + href: ./api/link/community-links diff --git a/docs/source/config.json b/docs/source/config.json deleted file mode 100644 index 5e85cecba5f..00000000000 --- a/docs/source/config.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "title": "Client (React)", - "version": "v3", - "algoliaFilters": [ - "docset:apollo-client", - "docset:apollo-server" - ], - "sidebar": { - "Introduction": "/", - "Why Apollo Client?": "/why-apollo", - "Get started": "/get-started", - "Changelog": "https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md", - "Fetching": { - "Queries": "/data/queries", - "Suspense": "/data/suspense", - "Mutations": "/data/mutations", - "Refetching": "/data/refetching", - "Subscriptions": "/data/subscriptions", - "Fragments": "/data/fragments", - "Directives": "/data/directives", - "Error handling": "/data/error-handling", - "Document transforms": "/data/document-transforms", - "Best practices": "/data/operation-best-practices" - }, - "Caching": { - "Overview": "/caching/overview", - "Configuration": "/caching/cache-configuration", - "Reading and writing": "/caching/cache-interaction", - "Garbage collection and eviction": "/caching/garbage-collection", - "Customizing field behavior": "/caching/cache-field-behavior", - "Memory Management": "/caching/memory-management", - "Advanced topics": "/caching/advanced-topics" - }, - "Pagination": { - "Overview": "/pagination/overview", - "Core API": "/pagination/core-api", - "Offset-based": "/pagination/offset-based", - "Cursor-based": "/pagination/cursor-based", - "keyArgs": "/pagination/key-args" - }, - "Local State": { - "Overview": "/local-state/local-state-management", - "Local-only fields": "/local-state/managing-state-with-field-policies", - "Reactive variables": "/local-state/reactive-variables", - "Client-side schema": "/local-state/client-side-schema", - "Local resolvers": "/local-state/local-resolvers" - }, - "Development & Testing": { - "Developer tools": "/development-testing/developer-tooling", - "Using TypeScript": "/development-testing/static-typing", - "Testing React components": "/development-testing/testing", - "Schema-driven testing": "/development-testing/schema-driven-testing", - "Mocking schema capabilities": "/development-testing/client-schema-mocking", - "Reducing bundle size": "/development-testing/reducing-bundle-size" - }, - "Performance": { - "Improving performance": "/performance/performance", - "Optimistic mutation results": "/performance/optimistic-ui", - "Server-side rendering": "/performance/server-side-rendering", - "Compiling queries with Babel": "/performance/babel" - }, - "Integrations": { - "Using Apollo Client with your view layer": "/integrations/integrations", - "Integrating with React Native": "/integrations/react-native", - "Loading queries with Webpack": "/integrations/webpack" - }, - "Networking": { - "Basic HTTP networking": "/networking/basic-http-networking", - "Advanced HTTP networking": "/networking/advanced-http-networking", - "Authentication": "/networking/authentication" - }, - "Migrating": { - "Migrating to Apollo Client 3.0": "/migrating/apollo-client-3-migration", - "Hooks migration guide": "/migrating/hooks-migration" - }, - "API Reference": { - "Core": { - "ApolloClient": "/api/core/ApolloClient", - "InMemoryCache": "/api/cache/InMemoryCache", - "ObservableQuery": "/api/core/ObservableQuery" - }, - "React": { - "Hooks": "/api/react/hooks", - "Preloading": "/api/react/preloading", - "Testing": "/api/react/testing", - "SSR": "/api/react/ssr", - "Components (deprecated)": "/api/react/components", - "HOC (deprecated)": "/api/react/hoc" - }, - "Apollo Link": { - "Overview": "/api/link/introduction", - "HTTP": "/api/link/apollo-link-http", - "HTTP Batch": "/api/link/apollo-link-batch-http", - "Context": "/api/link/apollo-link-context", - "Error": "/api/link/apollo-link-error", - "Persisted Queries": "/api/link/persisted-queries", - "Remove Typename": "/api/link/apollo-link-remove-typename", - "REST": "/api/link/apollo-link-rest", - "Retry": "/api/link/apollo-link-retry", - "Schema": "/api/link/apollo-link-schema", - "Subscriptions (newer protocol)": "/api/link/apollo-link-subscriptions", - "WebSocket (older protocol)": "/api/link/apollo-link-ws", - "Community links": "/api/link/community-links" - } - } - } -} From 45120e86b21f7591cc52b5f3a6e5c116f50951f2 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Wed, 29 Jan 2025 03:35:09 +1100 Subject: [PATCH 07/11] Update fragment doc with Codegen Client Preset support (#12290) --- docs/source/data/fragments.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/data/fragments.mdx b/docs/source/data/fragments.mdx index ee6ccd596f0..d8f4fdf4373 100644 --- a/docs/source/data/fragments.mdx +++ b/docs/source/data/fragments.mdx @@ -1135,7 +1135,7 @@ const config: CodegenConfig = { ##### With the `client-preset` -Support for the `@unmask` directive was introduced with `@graphql-codegen/client-preset` [v4.5.1](https://github.com/dotansimha/graphql-code-generator/releases/tag/release-1732308151614) +Support for the `@unmask` directive was introduced with `@graphql-codegen/client-preset` [v4.6.0](https://github.com/dotansimha/graphql-code-generator/releases/tag/release-1738065376043) You can't use the `client-preset` [Fragment Masking](https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#fragment-masking) and Apollo Client's data masking features simultaneously. @@ -1157,11 +1157,11 @@ To migrate from CodeGen's fragment masking feature to Apollo Client's data maski // ... // disables the incompatible GraphQL Codegen fragment masking feature fragmentMasking: false, + }, + config: { customDirectives: { apolloUnmask: true } - }, - config: { inlineFragmentTypes: "mask", } } From 68e29dd70551f0c9215f879ec20d07fb7c43a6a7 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Tue, 28 Jan 2025 19:18:59 +0100 Subject: [PATCH 08/11] de-flake tests (#12315) --- src/react/hooks/__tests__/useBackgroundQuery.test.tsx | 2 +- src/react/hooks/__tests__/useQuery.test.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx index 6ce0564cc63..a335ede0877 100644 --- a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx +++ b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx @@ -339,7 +339,7 @@ it("will resubscribe after disposed when mounting useReadQuery", async () => { } // Wait long enough for auto dispose to kick in - await wait(50); + await wait(80); expect(client.getObservableQueries().size).toBe(0); expect(client).not.toHaveSuspenseCacheEntryUsing(query); diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx index 355508a22e2..f8b3ae81343 100644 --- a/src/react/hooks/__tests__/useQuery.test.tsx +++ b/src/react/hooks/__tests__/useQuery.test.tsx @@ -2916,7 +2916,7 @@ describe("useQuery Hook", () => { const { takeSnapshot, unmount } = await renderHookToSnapshotStream( () => useQuery(query, { - pollInterval: 10, + pollInterval: 25, fetchPolicy: "cache-and-network", }), { @@ -2955,7 +2955,7 @@ describe("useQuery Hook", () => { expect(requestSpy).toHaveBeenCalledTimes(1); } - await wait(10); + await wait(25); { const result = await takeSnapshot(); From 86469a25abb72dbd68adff3554e3909036e77eee Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 28 Jan 2025 14:41:00 -0700 Subject: [PATCH 09/11] Don't throw on missing results with `cache.diff` and other changes (#12304) --- .api-reports/api-report-cache.api.md | 21 +- .api-reports/api-report-core.api.md | 19 +- .api-reports/api-report-masking.api.md | 17 +- .api-reports/api-report-react.api.md | 17 +- .api-reports/api-report-react_context.api.md | 17 +- .api-reports/api-report-react_hooks.api.md | 17 +- .api-reports/api-report-react_internal.api.md | 17 +- .api-reports/api-report-react_ssr.api.md | 17 +- .api-reports/api-report-testing.api.md | 17 +- .api-reports/api-report-testing_core.api.md | 17 +- .api-reports/api-report-testing_react.api.md | 17 +- .api-reports/api-report-utilities.api.md | 18 +- .api-reports/api-report.api.md | 19 +- .changeset/moody-lobsters-listen.md | 7 + .changeset/tall-cups-suffer.md | 13 ++ .changeset/yellow-cats-judge.md | 13 ++ .size-limits.json | 4 +- src/__tests__/client.ts | 4 +- src/__tests__/dataMasking.ts | 2 +- src/__tests__/optimistic.ts | 8 +- src/__tests__/refetchQueries.ts | 7 +- src/cache/core/__tests__/cache.ts | 2 +- src/cache/core/cache.ts | 27 ++- src/cache/core/types/DataProxy.ts | 21 +- src/cache/inmemory/__tests__/cache.ts | 12 +- .../inmemory/__tests__/diffAgainstStore.ts | 66 ++++-- src/cache/inmemory/__tests__/entityStore.ts | 98 +++++---- src/cache/inmemory/__tests__/policies.ts | 189 +++++++++++------- src/cache/inmemory/__tests__/readFromStore.ts | 57 +++--- src/cache/inmemory/__tests__/roundtrip.ts | 117 ++++++----- src/cache/inmemory/inMemoryCache.ts | 42 ++-- src/cache/inmemory/readFromStore.ts | 37 ++-- src/config/jest/areMissingFieldErrorsEqual.ts | 27 +++ src/config/jest/setup.ts | 7 +- src/core/ObservableQuery.ts | 4 +- src/core/QueryInfo.ts | 2 +- src/core/QueryManager.ts | 21 +- src/core/__tests__/ObservableQuery.ts | 4 +- src/core/__tests__/QueryManager/index.ts | 12 +- src/core/__tests__/fetchPolicies.ts | 12 +- src/react/hooks/useFragment.ts | 33 +-- 41 files changed, 667 insertions(+), 411 deletions(-) create mode 100644 .changeset/moody-lobsters-listen.md create mode 100644 .changeset/tall-cups-suffer.md create mode 100644 .changeset/yellow-cats-judge.md create mode 100644 src/config/jest/areMissingFieldErrorsEqual.ts diff --git a/.api-reports/api-report-cache.api.md b/.api-reports/api-report-cache.api.md index 8c362513806..2f9d8774962 100644 --- a/.api-reports/api-report-cache.api.md +++ b/.api-reports/api-report-cache.api.md @@ -24,7 +24,6 @@ export abstract class ApolloCache implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -254,9 +253,14 @@ interface DataMasking { export namespace DataProxy { // (undocumented) export type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -324,8 +328,6 @@ export interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -661,6 +663,10 @@ export class InMemoryCache extends ApolloCache { // (undocumented) readonly policies: Policies; // (undocumented) + read(options: Cache_2.ReadOptions & { + returnPartialData: true; + }): T | DeepPartial | null; + // (undocumented) read(options: Cache_2.ReadOptions): T | null; // (undocumented) release(rootId: string, optimistic?: boolean): number; @@ -1182,7 +1188,8 @@ interface WriteContext extends ReadMergeModifyContext { // Warnings were encountered during analysis: // -// src/cache/core/cache.ts:92:7 - (ae-forgotten-export) The symbol "MaybeMasked" needs to be exported by the entry point index.d.ts +// src/cache/core/cache.ts:91:7 - (ae-forgotten-export) The symbol "MaybeMasked" needs to be exported by the entry point index.d.ts +// src/cache/core/types/DataProxy.ts:152:9 - (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:93:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-core.api.md b/.api-reports/api-report-core.api.md index cf7e1713adc..7f465fcf739 100644 --- a/.api-reports/api-report-core.api.md +++ b/.api-reports/api-report-core.api.md @@ -39,7 +39,6 @@ export abstract class ApolloCache implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -506,9 +505,14 @@ export interface DataMasking { export namespace DataProxy { // (undocumented) export type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -576,8 +580,6 @@ export interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -1187,6 +1189,10 @@ export class InMemoryCache extends ApolloCache { // (undocumented) readonly policies: Policies; // (undocumented) + read(options: Cache_2.ReadOptions & { + returnPartialData: true; + }): T | DeepPartial | null; + // (undocumented) read(options: Cache_2.ReadOptions): T | null; // (undocumented) release(rootId: string, optimistic?: boolean): number; @@ -2506,6 +2512,7 @@ interface WriteContext extends ReadMergeModifyContext { // Warnings were encountered during analysis: // +// src/cache/core/types/DataProxy.ts:152:9 - (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:93:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-masking.api.md b/.api-reports/api-report-masking.api.md index 37019fcb7bc..04d1b0b7ec4 100644 --- a/.api-reports/api-report-masking.api.md +++ b/.api-reports/api-report-masking.api.md @@ -25,7 +25,6 @@ abstract class ApolloCache implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -235,9 +234,14 @@ export interface DataMasking { namespace DataProxy { // (undocumented) type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -317,8 +321,6 @@ interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -648,7 +650,8 @@ type WatchFragmentResult = { // Warnings were encountered during analysis: // -// src/cache/core/types/DataProxy.ts:147:7 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts +// src/cache/core/types/DataProxy.ts:152:9 - (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts +// src/cache/core/types/DataProxy.ts:154:7 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:101:3 - (ae-forgotten-export) The symbol "ReadFieldFunction" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:102:3 - (ae-forgotten-export) The symbol "CanReadFunction" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:103:3 - (ae-forgotten-export) The symbol "isReference" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-react.api.md b/.api-reports/api-report-react.api.md index b7c867abad5..9f6825a9892 100644 --- a/.api-reports/api-report-react.api.md +++ b/.api-reports/api-report-react.api.md @@ -35,7 +35,6 @@ abstract class ApolloCache implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -599,9 +598,14 @@ interface DataMasking { namespace DataProxy { // (undocumented) type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -681,8 +685,6 @@ interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -2511,7 +2513,8 @@ interface WatchQueryOptions implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -532,9 +531,14 @@ interface DataMasking { namespace DataProxy { // (undocumented) type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -614,8 +618,6 @@ interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -1913,7 +1915,8 @@ interface WatchQueryOptions implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -561,9 +560,14 @@ interface DataMasking { namespace DataProxy { // (undocumented) type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -643,8 +647,6 @@ interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -2338,7 +2340,8 @@ interface WatchQueryOptions implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -545,9 +544,14 @@ interface DataMasking { namespace DataProxy { // (undocumented) type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -627,8 +631,6 @@ interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -2401,7 +2403,8 @@ export function wrapQueryRef(inter // Warnings were encountered during analysis: // -// src/cache/core/types/DataProxy.ts:147:7 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts +// src/cache/core/types/DataProxy.ts:152:9 - (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts +// src/cache/core/types/DataProxy.ts:154:7 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:101:3 - (ae-forgotten-export) The symbol "ReadFieldFunction" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:102:3 - (ae-forgotten-export) The symbol "CanReadFunction" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:103:3 - (ae-forgotten-export) The symbol "isReference" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-react_ssr.api.md b/.api-reports/api-report-react_ssr.api.md index 4c786192520..9473257b4ef 100644 --- a/.api-reports/api-report-react_ssr.api.md +++ b/.api-reports/api-report-react_ssr.api.md @@ -34,7 +34,6 @@ abstract class ApolloCache implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -501,9 +500,14 @@ interface DataMasking { namespace DataProxy { // (undocumented) type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -583,8 +587,6 @@ interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -1901,7 +1903,8 @@ interface WatchQueryOptions implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -501,9 +500,14 @@ interface DataMasking { namespace DataProxy { // (undocumented) type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -583,8 +587,6 @@ interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -1916,7 +1918,8 @@ export function withWarningSpy(it: (...args: TArgs // Warnings were encountered during analysis: // -// src/cache/core/types/DataProxy.ts:147:7 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts +// src/cache/core/types/DataProxy.ts:152:9 - (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts +// src/cache/core/types/DataProxy.ts:154:7 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:101:3 - (ae-forgotten-export) The symbol "ReadFieldFunction" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:102:3 - (ae-forgotten-export) The symbol "CanReadFunction" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:103:3 - (ae-forgotten-export) The symbol "isReference" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-testing_core.api.md b/.api-reports/api-report-testing_core.api.md index 5c4c3372d07..d5f11ed5c49 100644 --- a/.api-reports/api-report-testing_core.api.md +++ b/.api-reports/api-report-testing_core.api.md @@ -33,7 +33,6 @@ abstract class ApolloCache implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -501,9 +500,14 @@ interface DataMasking { namespace DataProxy { // (undocumented) type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -583,8 +587,6 @@ interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -1916,7 +1918,8 @@ export function withWarningSpy(it: (...args: TArgs // Warnings were encountered during analysis: // -// src/cache/core/types/DataProxy.ts:147:7 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts +// src/cache/core/types/DataProxy.ts:152:9 - (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts +// src/cache/core/types/DataProxy.ts:154:7 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:101:3 - (ae-forgotten-export) The symbol "ReadFieldFunction" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:102:3 - (ae-forgotten-export) The symbol "CanReadFunction" needs to be exported by the entry point index.d.ts // src/cache/core/types/common.ts:103:3 - (ae-forgotten-export) The symbol "isReference" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-testing_react.api.md b/.api-reports/api-report-testing_react.api.md index 36697ad119c..ef5f9c65b67 100644 --- a/.api-reports/api-report-testing_react.api.md +++ b/.api-reports/api-report-testing_react.api.md @@ -34,7 +34,6 @@ abstract class ApolloCache implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -496,9 +495,14 @@ interface DataMasking { namespace DataProxy { // (undocumented) type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -578,8 +582,6 @@ interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -1864,7 +1866,8 @@ interface WatchQueryOptions implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -633,9 +632,14 @@ interface DataMasking { namespace DataProxy { // (undocumented) type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -1404,6 +1408,10 @@ class InMemoryCache extends ApolloCache { // (undocumented) readonly policies: Policies; // (undocumented) + read(options: Cache_2.ReadOptions & { + returnPartialData: true; + }): T | DeepPartial | null; + // (undocumented) read(options: Cache_2.ReadOptions): T | null; // (undocumented) release(rootId: string, optimistic?: boolean): number; @@ -2869,7 +2877,7 @@ interface WriteContext extends ReadMergeModifyContext { // Warnings were encountered during analysis: // -// src/cache/core/types/DataProxy.ts:147:7 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts +// src/cache/core/types/DataProxy.ts:154:7 - (ae-forgotten-export) The symbol "MissingFieldError" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:58:3 - (ae-forgotten-export) The symbol "TypePolicy" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report.api.md b/.api-reports/api-report.api.md index cf7e1713adc..7f465fcf739 100644 --- a/.api-reports/api-report.api.md +++ b/.api-reports/api-report.api.md @@ -39,7 +39,6 @@ export abstract class ApolloCache implements DataProxy { readonly assumeImmutableResults: boolean; // (undocumented) batch(options: Cache_2.BatchOptions): U; - // (undocumented) abstract diff(query: Cache_2.DiffOptions): Cache_2.DiffResult; // (undocumented) abstract evict(options: Cache_2.EvictOptions): boolean; @@ -506,9 +505,14 @@ export interface DataMasking { export namespace DataProxy { // (undocumented) export type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; fromOptimisticTransaction?: boolean; }; // (undocumented) @@ -576,8 +580,6 @@ export interface DataProxy { // @public (undocumented) type DeepPartial = T extends DeepPartialPrimitive ? T : T extends Map ? DeepPartialMap : T extends ReadonlyMap ? DeepPartialReadonlyMap : T extends Set ? DeepPartialSet : T extends ReadonlySet ? DeepPartialReadonlySet : T extends (...args: any[]) => unknown ? T | undefined : T extends object ? T extends (ReadonlyArray) ? TItem[] extends (T) ? readonly TItem[] extends T ? ReadonlyArray> : Array> : DeepPartialObject : DeepPartialObject : unknown; -// Warning: (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type DeepPartialMap = {} & Map, DeepPartial>; @@ -1187,6 +1189,10 @@ export class InMemoryCache extends ApolloCache { // (undocumented) readonly policies: Policies; // (undocumented) + read(options: Cache_2.ReadOptions & { + returnPartialData: true; + }): T | DeepPartial | null; + // (undocumented) read(options: Cache_2.ReadOptions): T | null; // (undocumented) release(rootId: string, optimistic?: boolean): number; @@ -2506,6 +2512,7 @@ interface WriteContext extends ReadMergeModifyContext { // Warnings were encountered during analysis: // +// src/cache/core/types/DataProxy.ts:152:9 - (ae-forgotten-export) The symbol "DeepPartial" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:93:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts // src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts diff --git a/.changeset/moody-lobsters-listen.md b/.changeset/moody-lobsters-listen.md new file mode 100644 index 00000000000..f5bba9ea703 --- /dev/null +++ b/.changeset/moody-lobsters-listen.md @@ -0,0 +1,7 @@ +--- +"@apollo/client": major +--- + +The `Cache.DiffResult` type is now a union type with better type safety for both complete and partial results. Checking `diff.complete` will now narrow the type of `result` depending on whether the value is `true` or `false`. + +When `true`, `diff.result` will be a non-null value equal to the `T` generic type. When `false`, `diff.result` now reports `result` as `DeepPartial | null` indicating that fields in the result may be missing (`DeepPartial`) or empty entirely (`null`). diff --git a/.changeset/tall-cups-suffer.md b/.changeset/tall-cups-suffer.md new file mode 100644 index 00000000000..3f3e84d72f0 --- /dev/null +++ b/.changeset/tall-cups-suffer.md @@ -0,0 +1,13 @@ +--- +"@apollo/client": major +--- + +### Changes for users of `InMemoryCache` + +`cache.diff` now returns `null` instead of an empty object (`{}`) when `returnPartialData` is `true` and the result is empty. + +If you use `cache.diff` directly with `returnPartialData: true`, you will need to check for `null` before accessing any other fields on the `result` property. A non-null value indicates that at least one field was present in the cache for the given query document. + +### Changes for third-party cache implementations + +The client now expects `cache.diff` to return `null` instead of an empty object when there is no data that can be fulfilled from the cache and `returnPartialData` is `true`. If your cache implementation returns an empty object, please update this to return `null`. diff --git a/.changeset/yellow-cats-judge.md b/.changeset/yellow-cats-judge.md new file mode 100644 index 00000000000..5aadd047277 --- /dev/null +++ b/.changeset/yellow-cats-judge.md @@ -0,0 +1,13 @@ +--- +"@apollo/client": major +--- + +### Changes for users of `InMemoryCache` + +`cache.diff` no longer throws when `returnPartialData` is set to `false` without a complete result. Instead, `cache.diff` will return `null` when it is unable to read a full cache result. + +If you use `cache.diff` directly with `returnPartialData: false`, remove the `try`/`catch` block and replace with a check for `null`. + +### Changes for third-party cache implementations + +The client now expects `cache.diff` to return `null` instead of throwing when the cache returns an incomplete result and `returnPartialData` is `false`. The internal `try`/`catch` blocks have been removed around `cache.diff`. If your cache implementation throws for incomplete results, please update this to return `null`. diff --git a/.size-limits.json b/.size-limits.json index e5f66a45631..594a84ef8f3 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 34273, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34391 + "dist/apollo-client.min.cjs": 34271, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34359 } diff --git a/src/__tests__/client.ts b/src/__tests__/client.ts index 7e99eed1c6a..75db3efb350 100644 --- a/src/__tests__/client.ts +++ b/src/__tests__/client.ts @@ -3099,7 +3099,7 @@ describe("@connection", () => { const cStream = watch(cQuery, "cache-only"); await expect(cStream).toEmitValue({ - data: {}, + data: undefined, loading: false, networkStatus: NetworkStatus.ready, partial: true, @@ -3147,7 +3147,7 @@ describe("@connection", () => { await expect(bStream).not.toEmitAnything(); await expect(abStream).not.toEmitAnything(); await expect(cStream).toEmitValue({ - data: {}, + data: undefined, loading: false, networkStatus: NetworkStatus.ready, partial: true, diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index 9c23822916b..89ea5ca3c0d 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -5838,7 +5838,7 @@ describe("client.mutate", () => { class TestCache extends ApolloCache { public diff(query: Cache.DiffOptions): DataProxy.DiffResult { - return {}; + return { result: null, complete: false }; } public evict(): boolean { diff --git a/src/__tests__/optimistic.ts b/src/__tests__/optimistic.ts index f2b20a94e84..ba06295882c 100644 --- a/src/__tests__/optimistic.ts +++ b/src/__tests__/optimistic.ts @@ -1945,8 +1945,8 @@ describe("optimistic mutation results", () => { expect(realisticDiffs).toEqual([ { complete: false, - missing: [expect.anything()], - result: {}, + missing: expect.anything(), + result: null, }, ]); @@ -2136,8 +2136,8 @@ describe("optimistic mutation results", () => { expect(realisticDiffs).toEqual([ { complete: false, - missing: [expect.anything()], - result: {}, + missing: expect.anything(), + result: null, }, { complete: true, diff --git a/src/__tests__/refetchQueries.ts b/src/__tests__/refetchQueries.ts index fbeb52c6012..0d344af5e3c 100644 --- a/src/__tests__/refetchQueries.ts +++ b/src/__tests__/refetchQueries.ts @@ -535,10 +535,10 @@ describe("client.refetchQueries", () => { onQueryUpdated(obs, diff) { if (obs === aObs) { expect(diff.complete).toBe(false); - expect(diff.result).toEqual({}); + expect(diff.result).toEqual(null); } else if (obs === abObs) { expect(diff.complete).toBe(false); - expect(diff.result).toEqual({}); + expect(diff.result).toEqual(null); } else { throw new Error( `unexpected ObservableQuery ${obs.queryId} with name ${obs.queryName}` @@ -548,8 +548,7 @@ describe("client.refetchQueries", () => { }, }); - sortObjects(activeResults); - expect(activeResults).toEqual([{}, {}]); + expect(activeResults).toEqual([null, null]); const stream = new ObservableStream(abObs); subs.push(stream as unknown as Subscription); diff --git a/src/cache/core/__tests__/cache.ts b/src/cache/core/__tests__/cache.ts index 59744928535..6bb84265775 100644 --- a/src/cache/core/__tests__/cache.ts +++ b/src/cache/core/__tests__/cache.ts @@ -10,7 +10,7 @@ class TestCache extends ApolloCache { } public diff(query: Cache.DiffOptions): DataProxy.DiffResult { - return {}; + return { result: null, complete: false }; } public evict(): boolean { diff --git a/src/cache/core/cache.ts b/src/cache/core/cache.ts index 8f443cbc319..5ce05c8c7ce 100644 --- a/src/cache/core/cache.ts +++ b/src/cache/core/cache.ts @@ -17,7 +17,6 @@ import { defaultCacheSizes, getFragmentDefinition, getFragmentQueryDocument, - mergeDeepArray, } from "../../utilities/index.js"; import type { DataProxy } from "./types/DataProxy.js"; import type { Cache } from "./types/Cache.js"; @@ -110,6 +109,18 @@ export abstract class ApolloCache implements DataProxy { public abstract write( write: Cache.WriteOptions ): Reference | undefined; + + /** + * Returns data read from the cache for a given query along with information + * about the cache result such as whether the result is complete and details + * about missing fields. + * + * Will return `complete` as `true` if it can fulfill the full cache result or + * `false` if not. When no data can be fulfilled from the cache, `null` is + * returned. When `returnPartialData` is `true`, non-null partial results are + * returned if it contains at least one field that can be fulfilled from the + * cache. + */ public abstract diff(query: Cache.DiffOptions): Cache.DiffResult; public abstract watch( watch: Cache.WatchOptions @@ -297,11 +308,17 @@ export abstract class ApolloCache implements DataProxy { ...diffOptions, immediate: true, callback: (diff) => { - const data = + let data = dataMasking ? maskFragment(diff.result, fragment, this, fragmentName) : diff.result; + // TODO: Remove this once `watchFragment` supports `null` as valid + // value emitted + if (data === null) { + data = {} as any; + } + if ( // Always ensure we deliver the first result latestDiff && @@ -323,12 +340,10 @@ export abstract class ApolloCache implements DataProxy { } as WatchFragmentResult; if (diff.missing) { - result.missing = mergeDeepArray( - diff.missing.map((error) => error.missing) - ); + result.missing = diff.missing.missing; } - latestDiff = { ...diff, result: data }; + latestDiff = { ...diff, result: data } as DataProxy.DiffResult; observer.next(result); }, }); diff --git a/src/cache/core/types/DataProxy.ts b/src/cache/core/types/DataProxy.ts index 746cd174a21..5ccf9ad679b 100644 --- a/src/cache/core/types/DataProxy.ts +++ b/src/cache/core/types/DataProxy.ts @@ -2,7 +2,7 @@ import type { DocumentNode } from "graphql"; // ignore-comment eslint-disable-li import type { TypedDocumentNode } from "@graphql-typed-document-node/core"; import type { MissingFieldError } from "./common.js"; -import type { Reference } from "../../../utilities/index.js"; +import type { DeepPartial, Reference } from "../../../utilities/index.js"; import type { Unmasked } from "../../../masking/index.js"; export namespace DataProxy { @@ -128,12 +128,19 @@ export namespace DataProxy { "data" > {} - export type DiffResult = { - result?: T; - complete?: boolean; - missing?: MissingFieldError[]; - fromOptimisticTransaction?: boolean; - }; + export type DiffResult = + | { + result: T; + complete: true; + missing?: never; + fromOptimisticTransaction?: boolean; + } + | { + result: DeepPartial | null; + complete: false; + missing?: MissingFieldError; + fromOptimisticTransaction?: boolean; + }; } /** diff --git a/src/cache/inmemory/__tests__/cache.ts b/src/cache/inmemory/__tests__/cache.ts index 2d426ed7207..f12fc01df21 100644 --- a/src/cache/inmemory/__tests__/cache.ts +++ b/src/cache/inmemory/__tests__/cache.ts @@ -10,7 +10,7 @@ import { isReference, DocumentNode, } from "../../../core"; -import { Cache } from "../../../cache"; +import { Cache, MissingFieldError } from "../../../cache"; import { InMemoryCache } from "../inMemoryCache"; import { InMemoryCacheConfig } from "../types"; @@ -1348,7 +1348,9 @@ describe("Cache", () => { query, optimistic: true, callback(diff) { - results.push(diff.result!); + if (diff.complete) { + results.push(diff.result); + } }, }); @@ -1620,7 +1622,7 @@ describe("Cache", () => { expect(abInfo.diffs.length).toBe(1); expect(last(abInfo.diffs)).toEqual({ complete: false, - missing: expect.any(Array), + missing: expect.any(MissingFieldError), result: { a: "ay", }, @@ -2338,7 +2340,9 @@ describe("InMemoryCache#broadcastWatches", function () { optimistic: true, immediate: true, callback(diff: Diff) { - addDiff(diff.result!.object.name, diff); + if (diff.complete) { + addDiff(diff.result.object.name, diff); + } }, }; diff --git a/src/cache/inmemory/__tests__/diffAgainstStore.ts b/src/cache/inmemory/__tests__/diffAgainstStore.ts index 02eba301d2d..3c071c32fa6 100644 --- a/src/cache/inmemory/__tests__/diffAgainstStore.ts +++ b/src/cache/inmemory/__tests__/diffAgainstStore.ts @@ -10,6 +10,7 @@ import { writeQueryToStore, withError, } from "./helpers"; +import { MissingFieldError } from "../../../core"; disableFragmentWarnings(); @@ -45,7 +46,7 @@ describe("diffing queries against the store", () => { }); expect(queryResult.complete).toEqual(false); - expect(queryResult.result).toEqual({}); + expect(queryResult.result).toEqual(null); } ); @@ -88,7 +89,7 @@ describe("diffing queries against the store", () => { }); expect(queryResult.complete).toEqual(false); - expect(queryResult.result).toEqual({}); + expect(queryResult.result).toEqual(null); } ); @@ -312,7 +313,7 @@ describe("diffing queries against the store", () => { }); }); - it("throws an error on a query with fields missing from matching named fragments", () => { + it("returns null result on a query with fields missing from matching named fragments", () => { const firstQuery = gql` query { person { @@ -352,13 +353,33 @@ describe("diffing queries against the store", () => { jedi } `; - expect(() => { - reader.diffQueryAgainstStore({ - store, - query: unionQuery, - returnPartialData: false, - }); - }).toThrow(); + + const { complete, result, missing } = reader.diffQueryAgainstStore({ + store, + query: unionQuery, + returnPartialData: false, + }); + + const missingFieldErrorMessage = `Can't find field 'address' on object ${JSON.stringify( + firstResult.person, + null, + 2 + )}`; + + expect(complete).toBe(false); + expect(result).toBe(null); + expect(missing).toEqual( + new MissingFieldError( + missingFieldErrorMessage, + { + person: { + address: missingFieldErrorMessage, + }, + }, + unionQuery, + {} + ) + ); }); it("returns available fields if returnPartialData is true", () => { @@ -460,13 +481,24 @@ describe("diffing queries against the store", () => { }, }); - expect(function () { - reader.diffQueryAgainstStore({ - store, - query: simpleQuery, - returnPartialData: false, - }); - }).toThrow(); + const { complete, result, missing } = reader.diffQueryAgainstStore({ + store, + query: simpleQuery, + returnPartialData: false, + }); + + const missingFieldErrorMessage = `Can't find field 'age' on Person:lukeId object`; + + expect(complete).toBe(false); + expect(result).toBeNull(); + expect(missing).toEqual( + new MissingFieldError( + missingFieldErrorMessage, + { people_one: { age: missingFieldErrorMessage } }, + simpleQuery, + {} + ) + ); }); it("will add a private id property", () => { diff --git a/src/cache/inmemory/__tests__/entityStore.ts b/src/cache/inmemory/__tests__/entityStore.ts index 72af8c300d7..497a065587c 100644 --- a/src/cache/inmemory/__tests__/entityStore.ts +++ b/src/cache/inmemory/__tests__/entityStore.ts @@ -1321,21 +1321,19 @@ describe("EntityStore", () => { result: { authorOfBook: tedWithoutHobby, }, - missing: [ - new MissingFieldError( - 'Can\'t find field \'hobby\' on Author:{"name":"Ted Chiang"} object', - { - publisherOfBook: - "Can't find field 'publisherOfBook' on ROOT_QUERY object", - authorOfBook: { - hobby: - 'Can\'t find field \'hobby\' on Author:{"name":"Ted Chiang"} object', - }, + missing: new MissingFieldError( + 'Can\'t find field \'hobby\' on Author:{"name":"Ted Chiang"} object', + { + publisherOfBook: + "Can't find field 'publisherOfBook' on ROOT_QUERY object", + authorOfBook: { + hobby: + 'Can\'t find field \'hobby\' on Author:{"name":"Ted Chiang"} object', }, - expect.anything(), // query - expect.anything() // variables - ), - ], + }, + expect.anything(), // query + expect.anything() // variables + ), }); cache.evict({ id: "ROOT_QUERY", fieldName: "authorOfBook" }); @@ -1862,9 +1860,18 @@ describe("EntityStore", () => { }) ).toBe(null); - expect(() => diff(queryWithAliases)).toThrow( - /Dangling reference to missing ABCs:.* object/ - ); + expect(diff(queryWithAliases)).toEqual({ + result: null, + complete: false, + missing: new MissingFieldError( + 'Dangling reference to missing ABCs:{"b":"bee","a":"ay","c":"see"} object', + { + abcs: 'Dangling reference to missing ABCs:{"b":"bee","a":"ay","c":"see"} object', + }, + queryWithAliases, + {} + ), + }); expect( cache.readQuery({ @@ -1872,9 +1879,18 @@ describe("EntityStore", () => { }) ).toBe(null); - expect(() => diff(queryWithoutAliases)).toThrow( - /Dangling reference to missing ABCs:.* object/ - ); + expect(diff(queryWithoutAliases)).toEqual({ + result: null, + complete: false, + missing: new MissingFieldError( + 'Dangling reference to missing ABCs:{"b":"bee","a":"ay","c":"see"} object', + { + abcs: 'Dangling reference to missing ABCs:{"b":"bee","a":"ay","c":"see"} object', + }, + queryWithoutAliases, + {} + ), + }); }); it("gracefully handles eviction amid optimistic updates", () => { @@ -1952,18 +1968,16 @@ describe("EntityStore", () => { expect(cache.evict({ id: authorId })).toBe(false); - const missing = [ - new MissingFieldError( - "Dangling reference to missing Author:2 object", - { - book: { - author: "Dangling reference to missing Author:2 object", - }, + const missing = new MissingFieldError( + "Dangling reference to missing Author:2 object", + { + book: { + author: "Dangling reference to missing Author:2 object", }, - expect.anything(), // query - expect.anything() // variables - ), - ]; + }, + expect.anything(), // query + expect.anything() // variables + ); expect( cache.diff({ @@ -2241,19 +2255,17 @@ describe("EntityStore", () => { isbn: "031648637X", }, }, - missing: [ - new MissingFieldError( - 'Can\'t find field \'title\' on Book:{"isbn":"031648637X"} object', - { - book: { - title: - 'Can\'t find field \'title\' on Book:{"isbn":"031648637X"} object', - }, + missing: new MissingFieldError( + 'Can\'t find field \'title\' on Book:{"isbn":"031648637X"} object', + { + book: { + title: + 'Can\'t find field \'title\' on Book:{"isbn":"031648637X"} object', }, - expect.anything(), // query - expect.anything() // variables - ), - ], + }, + expect.anything(), // query + expect.anything() // variables + ), }); expect(cache.extract()).toEqual({ diff --git a/src/cache/inmemory/__tests__/policies.ts b/src/cache/inmemory/__tests__/policies.ts index af4aefaf8e1..4de7947fe5a 100644 --- a/src/cache/inmemory/__tests__/policies.ts +++ b/src/cache/inmemory/__tests__/policies.ts @@ -2116,29 +2116,27 @@ describe("type policies", function () { ], }, complete: false, - missing: [ - new MissingFieldError( - `Can't find field 'result' on Job:{"name":"Job #${1}"} object`, - { - jobs: { - 0: { - result: - 'Can\'t find field \'result\' on Job:{"name":"Job #1"} object', - }, - 1: { - result: - 'Can\'t find field \'result\' on Job:{"name":"Job #2"} object', - }, - 2: { - result: - 'Can\'t find field \'result\' on Job:{"name":"Job #3"} object', - }, + missing: new MissingFieldError( + `Can't find field 'result' on Job:{"name":"Job #${1}"} object`, + { + jobs: { + 0: { + result: + 'Can\'t find field \'result\' on Job:{"name":"Job #1"} object', + }, + 1: { + result: + 'Can\'t find field \'result\' on Job:{"name":"Job #2"} object', + }, + 2: { + result: + 'Can\'t find field \'result\' on Job:{"name":"Job #3"} object', }, }, - expect.anything(), // query - expect.anything() // variables - ), - ], + }, + expect.anything(), // query + expect.anything() // variables + ), }); function setResult(jobNum: number) { @@ -2196,25 +2194,23 @@ describe("type policies", function () { ], }, complete: false, - missing: [ - new MissingFieldError( - `Can't find field 'result' on Job:{"name":"Job #${1}"} object`, - { - jobs: { - 0: { - result: - 'Can\'t find field \'result\' on Job:{"name":"Job #1"} object', - }, - 2: { - result: - 'Can\'t find field \'result\' on Job:{"name":"Job #3"} object', - }, + missing: new MissingFieldError( + `Can't find field 'result' on Job:{"name":"Job #${1}"} object`, + { + jobs: { + 0: { + result: + 'Can\'t find field \'result\' on Job:{"name":"Job #1"} object', + }, + 2: { + result: + 'Can\'t find field \'result\' on Job:{"name":"Job #3"} object', }, }, - expect.anything(), // query - expect.anything() // variables - ), - ], + }, + expect.anything(), // query + expect.anything() // variables + ), }); cache.writeQuery({ @@ -2282,25 +2278,23 @@ describe("type policies", function () { ], }, complete: false, - missing: [ - new MissingFieldError( - `Can't find field 'result' on Job:{"name":"Job #${1}"} object`, - { - jobs: { - 0: { - result: - 'Can\'t find field \'result\' on Job:{"name":"Job #1"} object', - }, - 2: { - result: - 'Can\'t find field \'result\' on Job:{"name":"Job #3"} object', - }, + missing: new MissingFieldError( + `Can't find field 'result' on Job:{"name":"Job #${1}"} object`, + { + jobs: { + 0: { + result: + 'Can\'t find field \'result\' on Job:{"name":"Job #1"} object', + }, + 2: { + result: + 'Can\'t find field \'result\' on Job:{"name":"Job #3"} object', }, }, - expect.anything(), // query - expect.anything() // variables - ), - ], + }, + expect.anything(), // query + expect.anything() // variables + ), }); setResult(1); @@ -3244,19 +3238,45 @@ describe("type policies", function () { }) ).toBe(null); - expect(() => - cache.diff({ - optimistic: true, - returnPartialData: false, - query: gql` + const diff = cache.diff({ + optimistic: true, + returnPartialData: false, + query: gql` + query { + me { + secret + } + } + `, + }); + + const missingFieldErrorMessage = `Can't find field 'secret' on object ${JSON.stringify( + { + __typename: "Person", + name: "Ben Newman", + }, + null, + 2 + )}`; + + expect(diff.complete).toBe(false); + expect(diff.result).toBeNull(); + expect(diff.missing).toEqual( + new MissingFieldError( + missingFieldErrorMessage, + { + me: { secret: missingFieldErrorMessage }, + }, + gql` query { me { secret } } `, - }) - ).toThrowError("Can't find field 'secret' "); + {} + ) + ); expect(secretReadAttempted).toBe(true); }); @@ -4596,9 +4616,18 @@ describe("type policies", function () { expect(read()).toBe(null); - expect(diff).toThrow( - /Dangling reference to missing Book:{"isbn":"156858217X"} object/ - ); + expect(diff()).toEqual({ + complete: false, + result: null, + missing: new MissingFieldError( + 'Dangling reference to missing Book:{"isbn":"156858217X"} object', + { + book: 'Dangling reference to missing Book:{"isbn":"156858217X"} object', + }, + query, + { isbn: "156858217X" } + ), + }); const stealThisData = { __typename: "Book", @@ -4732,14 +4761,32 @@ describe("type policies", function () { }); expect(read("0393354326")).toBe(null); - expect(() => diff("0393354326")).toThrow( - /Dangling reference to missing Book:{"isbn":"0393354326"} object/ - ); + expect(diff("0393354326")).toEqual({ + complete: false, + result: null, + missing: new MissingFieldError( + 'Dangling reference to missing Book:{"isbn":"0393354326"} object', + { + book: 'Dangling reference to missing Book:{"isbn":"0393354326"} object', + }, + query, + { isbn: "0393354326" } + ), + }); expect(read("156858217X")).toBe(null); - expect(() => diff("156858217X")).toThrow( - /Dangling reference to missing Book:{"isbn":"156858217X"} object/ - ); + expect(diff("156858217X")).toEqual({ + complete: false, + result: null, + missing: new MissingFieldError( + 'Dangling reference to missing Book:{"isbn":"156858217X"} object', + { + book: 'Dangling reference to missing Book:{"isbn":"156858217X"} object', + }, + query, + { isbn: "156858217X" } + ), + }); }); it("can force merging of unidentified non-normalized data", function () { diff --git a/src/cache/inmemory/__tests__/readFromStore.ts b/src/cache/inmemory/__tests__/readFromStore.ts index d936ad73734..4180ded67f6 100644 --- a/src/cache/inmemory/__tests__/readFromStore.ts +++ b/src/cache/inmemory/__tests__/readFromStore.ts @@ -623,7 +623,7 @@ describe("reading from the store", () => { expect(reader["executeSubSelectedArray"].size).toBe(1); }); - it("throws on a missing field", () => { + it("returns null on a missing field", () => { const result = { id: "abcd", stringField: "This is a string!", @@ -633,17 +633,17 @@ describe("reading from the store", () => { const store = defaultNormalizedCacheFactory({ ROOT_QUERY: result }); - expect(() => { - readQueryFromStore(reader, { - store, - query: gql` - { - stringField - missingField - } - `, - }); - }).toThrowError(/Can't find field 'missingField' on ROOT_QUERY object/); + const cacheResult = readQueryFromStore(reader, { + store, + query: gql` + { + stringField + missingField + } + `, + }); + + expect(cacheResult).toBeNull(); }); it("readQuery supports returnPartialData", () => { @@ -678,7 +678,7 @@ describe("reading from the store", () => { query: bQuery, returnPartialData: true, }) - ).toEqual({}); + ).toEqual(null); expect( cache.readQuery({ @@ -809,7 +809,7 @@ describe("reading from the store", () => { }, }); - expect(missing).toEqual([ + expect(missing).toEqual( new MissingFieldError( `Can't find field 'missing' on object ${JSON.stringify( { @@ -836,8 +836,8 @@ describe("reading from the store", () => { }, query, {} // variables - ), - ]); + ) + ); }); it("runs a nested query where the reference is null", () => { @@ -1409,21 +1409,20 @@ describe("reading from the store", () => { expect(diffChickens()).toEqual({ complete: false, - missing: [ - new MissingFieldError( - "Can't find field 'id' on object {}", - { - chickens: { - 1: { - id: "Can't find field 'id' on object {}", - inCoop: "Can't find field 'inCoop' on object {}", - }, + missing: new MissingFieldError( + "Can't find field 'id' on object {}", + { + chickens: { + 1: { + id: "Can't find field 'id' on object {}", + inCoop: "Can't find field 'inCoop' on object {}", }, }, - expect.anything(), // query - expect.anything() // variables - ), - ], + }, + expect.anything(), // query + expect.anything() // variables + ), + result: { chickens: [ { __typename: "Chicken", id: 1, inCoop: true }, diff --git a/src/cache/inmemory/__tests__/roundtrip.ts b/src/cache/inmemory/__tests__/roundtrip.ts index 5c6824cdc6e..8e589a6f042 100644 --- a/src/cache/inmemory/__tests__/roundtrip.ts +++ b/src/cache/inmemory/__tests__/roundtrip.ts @@ -20,7 +20,12 @@ function assertDeeplyFrozen(value: any, stack: any[] = []) { } } -function storeRoundtrip(query: DocumentNode, result: any, variables = {}) { +function storeRoundtrip( + query: DocumentNode, + result: any, + variables = {}, + expectedResult = result +) { const cache = new InMemoryCache({ possibleTypes: { Character: ["Jedi", "Droid"], @@ -44,7 +49,7 @@ function storeRoundtrip(query: DocumentNode, result: any, variables = {}) { }; const reconstructedResult = readQueryFromStore(reader, readOptions); - expect(reconstructedResult).toEqual(result); + expect(reconstructedResult).toEqual(expectedResult); // Make sure the result is identical if we haven't written anything new // to the store. https://github.com/apollographql/apollo-client/pull/3394 @@ -81,7 +86,7 @@ function storeRoundtrip(query: DocumentNode, result: any, variables = {}) { }); const deletedRootResult = readQueryFromStore(reader, readOptions); - expect(deletedRootResult).toEqual(result); + expect(deletedRootResult).toEqual(expectedResult); if (deletedRootResult === reconstructedResult) { // We don't expect the new result to be identical to the previous result, @@ -258,7 +263,9 @@ describe("roundtrip", () => { fortuneCookie @skip(if: true) } `, - {} + {}, + {}, + null ); }); @@ -317,33 +324,33 @@ describe("roundtrip", () => { // However, the user may have written this result with client.writeQuery. it("should throw an error on two of the same inline fragment types", () => { using _consoleSpies = spyOnConsole.takeSnapshots("error"); - expect(() => { - storeRoundtrip( - gql` - query { - all_people { - __typename - name - ... on Jedi { - side - } - ... on Jedi { - rank - } + storeRoundtrip( + gql` + query { + all_people { + __typename + name + ... on Jedi { + side + } + ... on Jedi { + rank } } - `, - { - all_people: [ - { - __typename: "Jedi", - name: "Luke Skywalker", - side: "bright", - }, - ], } - ); - }).toThrowError(/Can't find field 'rank' /); + `, + { + all_people: [ + { + __typename: "Jedi", + name: "Luke Skywalker", + side: "bright", + }, + ], + }, + {}, + null + ); }); it("should resolve fields it can on interface with non matching inline fragments", () => { @@ -458,37 +465,37 @@ describe("roundtrip", () => { it("should throw on error on two of the same spread fragment types", () => { using _consoleSpies = spyOnConsole.takeSnapshots("error"); - expect(() => { - storeRoundtrip( - gql` - fragment jediSide on Jedi { - side - } + storeRoundtrip( + gql` + fragment jediSide on Jedi { + side + } - fragment jediRank on Jedi { - rank - } + fragment jediRank on Jedi { + rank + } - query { - all_people { - __typename - name - ...jediSide - ...jediRank - } + query { + all_people { + __typename + name + ...jediSide + ...jediRank } - `, - { - all_people: [ - { - __typename: "Jedi", - name: "Luke Skywalker", - side: "bright", - }, - ], } - ); - }).toThrowError(/Can't find field 'rank' /); + `, + { + all_people: [ + { + __typename: "Jedi", + name: "Luke Skywalker", + side: "bright", + }, + ], + }, + {}, + null + ); }); it("should resolve on @include and @skip with inline fragments", () => { diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts index 7be5b921ab5..37237cc320b 100644 --- a/src/cache/inmemory/inMemoryCache.ts +++ b/src/cache/inmemory/inMemoryCache.ts @@ -14,8 +14,11 @@ import { equal } from "@wry/equality"; import { ApolloCache } from "../core/cache.js"; import type { Cache } from "../core/types/Cache.js"; -import { MissingFieldError } from "../core/types/common.js"; -import type { StoreObject, Reference } from "../../utilities/index.js"; +import type { + StoreObject, + Reference, + DeepPartial, +} from "../../utilities/index.js"; import { addTypenameToDocument, isReference, @@ -178,7 +181,13 @@ export class InMemoryCache extends ApolloCache { return (optimistic ? this.optimisticData : this.data).extract(); } - public read(options: Cache.ReadOptions): T | null { + public read( + options: Cache.ReadOptions & { returnPartialData: true } + ): T | DeepPartial | null; + + public read(options: Cache.ReadOptions): T | null; + + public read(options: Cache.ReadOptions): T | DeepPartial | null { const { // Since read returns data or null, without any additional metadata // about whether/where there might have been missing fields, the @@ -189,26 +198,13 @@ export class InMemoryCache extends ApolloCache { // specified explicitly. returnPartialData = false, } = options; - try { - return ( - this.storeReader.diffQueryAgainstStore({ - ...options, - store: options.optimistic ? this.optimisticData : this.data, - config: this.config, - returnPartialData, - }).result || null - ); - } catch (e) { - if (e instanceof MissingFieldError) { - // Swallow MissingFieldError and return null, so callers do not need to - // worry about catching "normal" exceptions resulting from incomplete - // cache data. Unexpected errors will be re-thrown. If you need more - // information about which fields were missing, use cache.diff instead, - // and examine diffResult.missing. - return null; - } - throw e; - } + + return this.storeReader.diffQueryAgainstStore({ + ...options, + store: options.optimistic ? this.optimisticData : this.data, + config: this.config, + returnPartialData, + }).result; } public write(options: Cache.WriteOptions): Reference | undefined { diff --git a/src/cache/inmemory/readFromStore.ts b/src/cache/inmemory/readFromStore.ts index e88113782e7..c14b8db1ea5 100644 --- a/src/cache/inmemory/readFromStore.ts +++ b/src/cache/inmemory/readFromStore.ts @@ -270,30 +270,29 @@ export class StoreReader { }, }); - let missing: MissingFieldError[] | undefined; + let missing: MissingFieldError | undefined; if (execResult.missing) { - // For backwards compatibility we still report an array of - // MissingFieldError objects, even though there will only ever be at most - // one of them, now that all missing field error messages are grouped - // together in the execResult.missing tree. - missing = [ - new MissingFieldError( - firstMissing(execResult.missing)!, - execResult.missing, - query, - variables - ), - ]; - if (!returnPartialData) { - throw missing[0]; - } + missing = new MissingFieldError( + firstMissing(execResult.missing)!, + execResult.missing, + query, + variables + ); } + const complete = !missing; + const { result } = execResult; + return { - result: execResult.result, - complete: !missing, + result: + complete || returnPartialData ? + Object.keys(result).length === 0 ? + null + : result + : null, + complete, missing, - }; + } as Cache.DiffResult; } public isFresh( diff --git a/src/config/jest/areMissingFieldErrorsEqual.ts b/src/config/jest/areMissingFieldErrorsEqual.ts new file mode 100644 index 00000000000..820ad4f2935 --- /dev/null +++ b/src/config/jest/areMissingFieldErrorsEqual.ts @@ -0,0 +1,27 @@ +import type { Tester } from "@jest/expect-utils"; +import { MissingFieldError } from "../../cache/index.js"; + +export const areMissingFieldErrorsEqual: Tester = function ( + a, + b, + customTesters +) { + const isAMissingFieldError = a instanceof MissingFieldError; + const isBMissingFieldError = b instanceof MissingFieldError; + + if (isAMissingFieldError && isBMissingFieldError) { + return ( + a.message === b.message && + this.equals(a.path, b.path, customTesters) && + this.equals(a.query, b.query, customTesters) && + this.equals(a.variables, b.variables, customTesters) && + this.equals(a.missing, b.missing, customTesters) + ); + } + + if (isAMissingFieldError === isBMissingFieldError) { + return; + } + + return false; +}; diff --git a/src/config/jest/setup.ts b/src/config/jest/setup.ts index 141d0e4132d..93b812ac8f2 100644 --- a/src/config/jest/setup.ts +++ b/src/config/jest/setup.ts @@ -8,6 +8,7 @@ import { loadErrorMessageHandler } from "../../dev/loadErrorMessageHandler.js"; import "../../testing/matchers/index.js"; import { areApolloErrorsEqual } from "./areApolloErrorsEqual.js"; import { areGraphQLErrorsEqual } from "./areGraphQlErrorsEqual.js"; +import { areMissingFieldErrorsEqual } from "./areMissingFieldErrorsEqual.js"; // Turn off warnings for repeated fragment names gql.disableFragmentWarnings(); @@ -35,4 +36,8 @@ if (!Symbol.asyncDispose) { } // @ts-ignore -expect.addEqualityTesters([areApolloErrorsEqual, areGraphQLErrorsEqual]); +expect.addEqualityTesters([ + areApolloErrorsEqual, + areGraphQLErrorsEqual, + areMissingFieldErrorsEqual, +]); diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts index 2f35f7731ba..8093105b603 100644 --- a/src/core/ObservableQuery.ts +++ b/src/core/ObservableQuery.ts @@ -281,7 +281,7 @@ export class ObservableQuery< result.data = diff.result; } - if (equal(result.data, {})) { + if (result.data === null) { result.data = void 0 as any; } @@ -1195,7 +1195,7 @@ function defaultSubscriptionObserverErrorCallback(error: ApolloError) { } export function logMissingFieldErrors( - missing: MissingFieldError[] | MissingTree | undefined + missing: MissingFieldError | MissingTree | undefined ) { if (__DEV__ && missing) { invariant.debug(`Missing cache result fields: %o`, missing); diff --git a/src/core/QueryInfo.ts b/src/core/QueryInfo.ts index 2c065972b78..b6794aa3c3b 100644 --- a/src/core/QueryInfo.ts +++ b/src/core/QueryInfo.ts @@ -173,7 +173,7 @@ export class QueryInfo { const oq = this.observableQuery; if (oq && oq.options.fetchPolicy === "no-cache") { - return { complete: false }; + return { result: null, complete: false }; } const diff = this.cache.diff(options); diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index 066dc137de9..bb0b7115de2 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -3,7 +3,6 @@ import { invariant, newInvariantError } from "../utilities/globals/index.js"; import type { DocumentNode } from "graphql"; // TODO(brian): A hack until this issue is resolved (https://github.com/graphql/graphql-js/issues/3356) type OperationTypeNode = any; -import { equal } from "@wry/equality"; import type { ApolloLink, FetchResult } from "../link/core/index.js"; import { execute } from "../link/core/index.js"; @@ -22,6 +21,7 @@ import { canonicalStringify } from "../cache/index.js"; import type { ObservableSubscription, ConcastSourcesArray, + DeepPartial, } from "../utilities/index.js"; import { getDefaultValues, @@ -1650,11 +1650,11 @@ export class QueryManager { ) => { const data = diff.result; - if (__DEV__ && !returnPartialData && !equal(data, {})) { + if (__DEV__ && !returnPartialData && data !== null) { logMissingFieldErrors(diff.missing); } - const fromData = (data: TData | undefined) => + const fromData = (data: TData | DeepPartial | undefined) => Observable.of({ data, loading: isNetworkRequestInFlight(networkStatus), @@ -1662,11 +1662,18 @@ export class QueryManager { ...(diff.complete ? null : { partial: true }), } as ApolloQueryResult); - if (data && this.getDocumentInfo(query).hasForcedResolvers) { + if (this.getDocumentInfo(query).hasForcedResolvers) { return this.localState .runResolvers({ document: query, - remoteResult: { data }, + // TODO: Update remoteResult to handle `null`. In v3 the `if` + // statement contained a check against `data`, but this value was + // always `{}` if nothing was in the cache, which meant the check + // above always succeeded when there were forced resolvers. Now that + // `data` is nullable, this `remoteResult` needs to be an empty + // object. Ideally we can pass in `null` here and the resolvers + // would be able to handle this the same way. + remoteResult: { data: data || ({} as any) }, context, variables, onlyRunForcedResolvers: true, @@ -1681,12 +1688,12 @@ export class QueryManager { if ( errorPolicy === "none" && networkStatus === NetworkStatus.refetch && - Array.isArray(diff.missing) + diff.missing ) { return fromData(void 0); } - return fromData(data); + return fromData(data || undefined); }; const cacheWriteBehavior = diff --git a/src/core/__tests__/ObservableQuery.ts b/src/core/__tests__/ObservableQuery.ts index 96ef174df4b..afd768a8ced 100644 --- a/src/core/__tests__/ObservableQuery.ts +++ b/src/core/__tests__/ObservableQuery.ts @@ -551,7 +551,7 @@ describe("ObservableQuery", () => { { const result = await stream.takeNext(); - expect(result.data).toEqual({}); + expect(result.data).toEqual(undefined); expect(result.loading).toBe(false); expect(result.networkStatus).toBe(NetworkStatus.ready); expect(timesFired).toBe(1); @@ -601,7 +601,7 @@ describe("ObservableQuery", () => { const result = await stream.takeNext(); expect(result.loading).toBe(false); - expect(result.data).toEqual({}); + expect(result.data).toEqual(undefined); expect(timesFired).toBe(0); } diff --git a/src/core/__tests__/QueryManager/index.ts b/src/core/__tests__/QueryManager/index.ts index 7555d632060..457aa276092 100644 --- a/src/core/__tests__/QueryManager/index.ts +++ b/src/core/__tests__/QueryManager/index.ts @@ -2138,7 +2138,7 @@ describe("QueryManager", () => { const stream2 = new ObservableStream(observable2); await expect(stream1).toEmitValue({ - data: {}, + data: undefined, loading: true, networkStatus: NetworkStatus.loading, partial: true, @@ -2217,14 +2217,14 @@ describe("QueryManager", () => { await expect(aStream).toEmitValue({ loading: true, networkStatus: NetworkStatus.loading, - data: {}, + data: undefined, partial: true, }); await expect(bStream).toEmitValue({ loading: true, networkStatus: NetworkStatus.loading, - data: {}, + data: undefined, partial: true, }); @@ -2313,7 +2313,7 @@ describe("QueryManager", () => { await expect(stream).toEmitValue({ loading: true, networkStatus: NetworkStatus.loading, - data: {}, + data: undefined, partial: true, }); @@ -2332,7 +2332,7 @@ describe("QueryManager", () => { await expect(stream).toEmitValue({ loading: true, networkStatus: NetworkStatus.loading, - data: {}, + data: undefined, partial: true, }); @@ -2357,7 +2357,7 @@ describe("QueryManager", () => { await expect(stream).toEmitValue({ loading: true, networkStatus: NetworkStatus.loading, - data: {}, + data: undefined, partial: true, }); diff --git a/src/core/__tests__/fetchPolicies.ts b/src/core/__tests__/fetchPolicies.ts index 8042f2712b4..f27e22dcf44 100644 --- a/src/core/__tests__/fetchPolicies.ts +++ b/src/core/__tests__/fetchPolicies.ts @@ -430,6 +430,8 @@ describe("no-cache", () => { await expect(stream).toEmitValue({ loading: true, networkStatus: NetworkStatus.setVariables, + // TODO: Since we did not set `returnPartialData` on `watchQuery`, this + // property should not be here. partial: true, }); @@ -501,7 +503,7 @@ describe("cache-first", () => { ); await expect(stream).toEmitValue({ - data: {}, + data: undefined, loading: true, networkStatus: NetworkStatus.loading, partial: true, @@ -710,9 +712,11 @@ describe("cache-and-network", function () { await observable.setVariables({ id: "2" }); await expect(stream).toEmitValue({ - data: {}, + data: undefined, loading: true, networkStatus: NetworkStatus.setVariables, + // TODO: This field should not be present since returnPartialData is not + // set. partial: true, }); @@ -739,9 +743,11 @@ describe("cache-and-network", function () { await observable.refetch({ id: "3" }); await expect(stream).toEmitValue({ - data: {}, + data: undefined, loading: true, networkStatus: NetworkStatus.setVariables, + // TODO: This field should not be present since returnPartialData is not + // set. partial: true, }); diff --git a/src/react/hooks/useFragment.ts b/src/react/hooks/useFragment.ts index 98cdfa6b274..3355c832980 100644 --- a/src/react/hooks/useFragment.ts +++ b/src/react/hooks/useFragment.ts @@ -1,6 +1,5 @@ import * as React from "rehackt"; import type { DeepPartial } from "../../utilities/index.js"; -import { mergeDeepArray } from "../../utilities/index.js"; import type { Cache, Reference, @@ -39,6 +38,8 @@ export interface UseFragmentOptions client?: ApolloClient; } +// TODO: Update this to return `null` when there is no data returned from the +// fragment. export type UseFragmentResult = | { data: MaybeMasked; @@ -91,9 +92,9 @@ function useFragment_( if (from === null) { return { result: diffToResult({ - result: {} as TData, + result: {}, complete: false, - }), + } as Cache.DiffResult), }; } @@ -107,14 +108,18 @@ function useFragment_( }); return { - result: diffToResult({ - ...diff, - result: client["queryManager"].maskFragment({ - fragment, - fragmentName, - data: diff.result, - }), - }), + result: diffToResult( + { + ...diff, + result: client["queryManager"].maskFragment({ + fragment, + fragmentName, + // TODO: Revert to `diff.result` once `useFragment` supports `null` as + // valid return value + data: diff.result === null ? {} : diff.result, + }), + } as Cache.DiffResult // TODO: Remove assertion + ), }; }, [client, stableOptions]); @@ -161,12 +166,12 @@ function diffToResult( diff: Cache.DiffResult ): UseFragmentResult { const result = { - data: diff.result!, + data: diff.result, complete: !!diff.complete, - } as UseFragmentResult; + } as UseFragmentResult; // TODO: Remove assertion once useFragment returns null if (diff.missing) { - result.missing = mergeDeepArray(diff.missing.map((error) => error.missing)); + result.missing = diff.missing.missing; } return result; From b69d07b568225da4cff8eb2ea09e3608badca0be Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Wed, 29 Jan 2025 09:40:16 +0100 Subject: [PATCH 10/11] some more knip reportings (#12314) --- knip.config.js | 2 +- src/testing/internal/renderHelpers.tsx | 53 -------------------------- src/testing/react/MockedProvider.tsx | 2 +- 3 files changed, 2 insertions(+), 55 deletions(-) diff --git a/knip.config.js b/knip.config.js index eb80892ab7d..1b13b391b80 100644 --- a/knip.config.js +++ b/knip.config.js @@ -29,7 +29,7 @@ const config = { "src/react/types/types.documentation.ts", "eslint-local-rules/index.js", ]), - project: ["src/**/*.ts", "config/*.[jt]s", "eslint-local-rules/*.[jt]s"], + project: ["src/**/*.ts{,x}", "config/*.[jt]s", "eslint-local-rules/*.[jt]s"], ignore: ["integration-tests/**/*", ".yalc/**/*"], ignoreBinaries: ["jq"], ignoreDependencies: [ diff --git a/src/testing/internal/renderHelpers.tsx b/src/testing/internal/renderHelpers.tsx index de744abff6c..077c2fdc1ae 100644 --- a/src/testing/internal/renderHelpers.tsx +++ b/src/testing/internal/renderHelpers.tsx @@ -1,20 +1,9 @@ import * as React from "react"; -import type { ReactElement } from "react"; -import { render } from "@testing-library/react"; -import type { Queries, RenderOptions, queries } from "@testing-library/react"; import type { ApolloClient } from "../../core/index.js"; import { ApolloProvider } from "../../react/index.js"; import type { MockedProviderProps } from "../react/MockedProvider.js"; import { MockedProvider } from "../react/MockedProvider.js"; -export interface RenderWithClientOptions< - Q extends Queries = typeof queries, - Container extends Element | DocumentFragment = HTMLElement, - BaseElement extends Element | DocumentFragment = Container, -> extends RenderOptions { - client: ApolloClient; -} - export function createClientWrapper( client: ApolloClient, Wrapper: React.JSXElementConstructor<{ @@ -32,31 +21,6 @@ export function createClientWrapper( }; } -export function renderWithClient< - Q extends Queries = typeof queries, - Container extends Element | DocumentFragment = HTMLElement, - BaseElement extends Element | DocumentFragment = Container, ->( - ui: ReactElement, - { - client, - wrapper, - ...renderOptions - }: RenderWithClientOptions -) { - return render(ui, { - ...renderOptions, - wrapper: createClientWrapper(client, wrapper), - }); -} - -export interface RenderWithMocksOptions< - Q extends Queries = typeof queries, - Container extends Element | DocumentFragment = HTMLElement, - BaseElement extends Element | DocumentFragment = Container, -> extends RenderOptions, - MockedProviderProps {} - export function createMockWrapper( renderOptions: MockedProviderProps, Wrapper: React.JSXElementConstructor<{ @@ -73,20 +37,3 @@ export function createMockWrapper( ); }; } - -export function renderWithMocks< - Q extends Queries = typeof queries, - Container extends Element | DocumentFragment = HTMLElement, - BaseElement extends Element | DocumentFragment = Container, ->( - ui: ReactElement, - { - wrapper, - ...renderOptions - }: RenderWithMocksOptions -) { - return render(ui, { - ...renderOptions, - wrapper: createMockWrapper(renderOptions, wrapper), - }); -} diff --git a/src/testing/react/MockedProvider.tsx b/src/testing/react/MockedProvider.tsx index fa06db3c324..c5a23dc0020 100644 --- a/src/testing/react/MockedProvider.tsx +++ b/src/testing/react/MockedProvider.tsx @@ -27,7 +27,7 @@ export interface MockedProviderProps { connectToDevTools?: boolean; } -export interface MockedProviderState { +interface MockedProviderState { client: ApolloClient; } From 2a623248905ff4dcdb728fe12ef43c60386525b2 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Wed, 29 Jan 2025 18:43:01 +0100 Subject: [PATCH 11/11] remove dependency on `mocha` from memory tests (#12320) --- scripts/memory/package-lock.json | 162 +++++++++++++++++++++++++ scripts/memory/package.json | 5 +- scripts/memory/{tests.js => tests.cts} | 6 +- scripts/memory/tsconfig.json | 12 ++ 4 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 scripts/memory/package-lock.json rename scripts/memory/{tests.js => tests.cts} (98%) create mode 100644 scripts/memory/tsconfig.json diff --git a/scripts/memory/package-lock.json b/scripts/memory/package-lock.json new file mode 100644 index 00000000000..884f248f804 --- /dev/null +++ b/scripts/memory/package-lock.json @@ -0,0 +1,162 @@ +{ + "name": "apollo-client-memory-tests", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "apollo-client-memory-tests", + "dependencies": { + "@apollo/client": "file:../../dist", + "graphql": "^16.0.0" + } + }, + "../../dist": { + "name": "@apollo/client", + "version": "3.12.8", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@wry/caches": "^1.0.0", + "@wry/equality": "^0.5.6", + "@wry/trie": "^0.5.0", + "graphql-tag": "^2.12.6", + "optimism": "^0.18.0", + "rehackt": "^0.1.0", + "symbol-observable": "^4.0.0", + "ts-invariant": "^0.10.3", + "tslib": "^2.3.0", + "zen-observable-ts": "^1.2.5" + }, + "devDependencies": { + "@arethetypeswrong/cli": "0.15.3", + "@ark/attest": "0.28.0", + "@babel/parser": "7.25.0", + "@changesets/changelog-github": "0.5.0", + "@changesets/cli": "2.27.7", + "@eslint/compat": "1.2.5", + "@eslint/eslintrc": "3.2.0", + "@eslint/js": "9.18.0", + "@graphql-tools/merge": "9.0.4", + "@graphql-tools/schema": "10.0.4", + "@graphql-tools/utils": "10.5.0", + "@jest/expect-utils": "29.7.0", + "@jest/globals": "29.7.0", + "@microsoft/api-extractor": "7.49.1", + "@microsoft/api-extractor-model": "7.30.2", + "@microsoft/tsdoc": "0.15.1", + "@rollup/plugin-node-resolve": "11.2.1", + "@size-limit/esbuild-why": "11.1.4", + "@size-limit/preset-small-lib": "11.1.4", + "@testing-library/dom": "10.4.0", + "@testing-library/jest-dom": "6.6.3", + "@testing-library/react": "16.1.0", + "@testing-library/react-render-stream": "2.0.0", + "@testing-library/user-event": "14.5.2", + "@tsconfig/node20": "20.1.4", + "@types/bytes": "3.1.4", + "@types/fetch-mock": "7.3.8", + "@types/glob": "8.1.0", + "@types/jest": "29.5.12", + "@types/lodash": "4.17.7", + "@types/node": "^22.10.7", + "@types/node-fetch": "2.6.11", + "@types/react": "19.0.0", + "@types/react-dom": "19.0.0", + "@types/relay-runtime": "14.1.24", + "@types/use-sync-external-store": "0.0.6", + "@typescript-eslint/eslint-plugin": "8.21.0", + "@typescript-eslint/parser": "8.21.0", + "@typescript-eslint/rule-tester": "8.21.0", + "@typescript-eslint/types": "8.21.0", + "@typescript-eslint/utils": "8.21.0", + "ast-types": "0.16.1", + "blob-polyfill": "7.0.20220408", + "bytes": "3.1.2", + "eslint": "9.18.0", + "eslint-import-resolver-typescript": "3.7.0", + "eslint-plugin-import": "npm:@phryneas/eslint-plugin-import@2.27.5-pr.2813.2817.199971c", + "eslint-plugin-local-rules": "3.0.2", + "eslint-plugin-react-compiler": "19.0.0-beta-decd7b8-20250118", + "eslint-plugin-react-hooks": "5.1.0", + "eslint-plugin-testing-library": "7.1.1", + "expect": "29.7.0", + "expect-type": "1.1.0", + "fetch-mock": "9.11.0", + "glob": "8.1.0", + "globals": "15.14.0", + "graphql": "16.9.0", + "graphql-17-alpha2": "npm:graphql@17.0.0-alpha.2", + "graphql-ws": "5.16.0", + "jest": "29.7.0", + "jest-environment-jsdom": "29.7.0", + "jest-junit": "16.0.0", + "jest-matcher-utils": "29.7.0", + "knip": "^5.42.2", + "lodash": "4.17.21", + "patch-package": "8.0.0", + "pkg-pr-new": "0.0.24", + "prettier": "3.1.1", + "react": "19.0.0", + "react-17": "npm:react@^17", + "react-18": "npm:react@^18", + "react-dom": "19.0.0", + "react-dom-17": "npm:react-dom@^17", + "react-dom-18": "npm:react-dom@^18", + "react-error-boundary": "4.0.13", + "recast": "0.23.9", + "rimraf": "5.0.9", + "rollup": "2.79.2", + "rollup-plugin-cleanup": "3.2.1", + "rollup-plugin-terser": "7.0.2", + "rxjs": "7.8.1", + "size-limit": "11.1.4", + "subscriptions-transport-ws": "0.11.0", + "terser": "5.31.3", + "ts-api-utils": "2.0.0", + "ts-jest": "29.2.3", + "ts-jest-resolver": "2.0.1", + "ts-morph": "25.0.0", + "ts-node": "10.9.2", + "tsx": "4.19.2", + "typedoc": "0.25.0", + "typescript": "^5.7.3", + "wait-for-observables": "1.0.3", + "web-streams-polyfill": "4.0.0" + }, + "peerDependencies": { + "graphql": "^15.0.0 || ^16.0.0", + "graphql-ws": "^5.5.5", + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc", + "react-dom": "^17.0.0 || ^18.0.0 || >=19.0.0-rc", + "subscriptions-transport-ws": "^0.9.0 || ^0.11.0" + }, + "peerDependenciesMeta": { + "graphql-ws": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "subscriptions-transport-ws": { + "optional": true + } + } + }, + "node_modules/@apollo/client": { + "resolved": "../../dist", + "link": true + }, + "node_modules/graphql": { + "version": "16.10.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", + "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + } + } +} diff --git a/scripts/memory/package.json b/scripts/memory/package.json index 1672c0abc81..d73d1a03f0c 100644 --- a/scripts/memory/package.json +++ b/scripts/memory/package.json @@ -2,13 +2,10 @@ "name": "apollo-client-memory-tests", "private": true, "scripts": { - "test": "mocha --exit -n expose-gc tests.js" + "test": "node --expose-gc --test tests.cts" }, "dependencies": { "@apollo/client": "file:../../dist", "graphql": "^16.0.0" - }, - "devDependencies": { - "mocha": "11.0.1" } } diff --git a/scripts/memory/tests.js b/scripts/memory/tests.cts similarity index 98% rename from scripts/memory/tests.js rename to scripts/memory/tests.cts index a764f4dac96..23ff95efca3 100644 --- a/scripts/memory/tests.js +++ b/scripts/memory/tests.cts @@ -1,4 +1,6 @@ -const assert = require("assert"); +const assert = require("node:assert"); +const { describe, it } = require("node:test"); + const { ApolloClient, InMemoryCache, @@ -318,7 +320,7 @@ describe("garbage collection", () => { assert.strictEqual(typeof getDataFromTree, "function"); const expectedKeys = new Set(["cache", "queryInfo1"]); - const renderPromisesSet = new Set(); + const renderPromisesSet = new Set(); const registry = makeRegistry((key) => { // By retaining the RenderPromises object in a Set in the scope of // this callback function, we artificially ensure the RenderPromises diff --git a/scripts/memory/tsconfig.json b/scripts/memory/tsconfig.json new file mode 100644 index 00000000000..7df4303890c --- /dev/null +++ b/scripts/memory/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "nodenext", + "allowJs": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "verbatimModuleSyntax": true, + "resolveJsonModule": true + } +}