From e832e68cf04a5d8a038cf8f59ab91aed7619ff74 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 15:56:16 -0700 Subject: [PATCH 01/21] Add tests to ensure warning is emitted with no-cache queries --- src/__tests__/dataMasking.ts | 287 +++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index dfe2988c0f3..b0e27e59a62 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -2317,6 +2317,293 @@ describe("client.watchQuery", () => { }, }); }); + + test("warns and returns masked result when used with no-cache fetch policy", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Query { + currentUser: { + __typename: "User"; + id: number; + name: string; + } & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } }; + } + + const query: MaskedDocumentNode = gql` + query MaskedQuery { + currentUser { + id + name + ...UserFields + } + } + + fragment UserFields on User { + age + } + `; + + const mocks = [ + { + request: { query }, + result: { + data: { + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const observable = client.watchQuery({ query, fetchPolicy: "no-cache" }); + const stream = new ObservableStream(observable); + + { + const { data } = await stream.takeNext(); + + expect(data).toEqual({ + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + }, + }); + } + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith( + 'Fragments masked by data masking when using fetch policy "no-cache" cannot be read by `watchFragment` or `useFragment`. Please add `@unmask` to the fragment to read the fragment data.' + ); + }); + + test("does not warn on no-cache queries when data masking is disabled", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Query { + currentUser: { + __typename: "User"; + id: number; + name: string; + } & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } }; + } + + const query: MaskedDocumentNode = gql` + query MaskedQuery { + currentUser { + id + name + ...UserFields + } + } + + fragment UserFields on User { + age + } + `; + + const mocks = [ + { + request: { query }, + result: { + data: { + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: false, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const observable = client.watchQuery({ query, fetchPolicy: "no-cache" }); + const stream = new ObservableStream(observable); + + { + const { data } = await stream.takeNext(); + + expect(data).toEqual({ + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }); + } + + expect(console.warn).not.toHaveBeenCalled(); + }); + + test("does not warn on no-cache queries when all fragments use `@unmask`", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Query { + currentUser: { + __typename: "User"; + id: number; + name: string; + } & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } }; + } + + const query: MaskedDocumentNode = gql` + query MaskedQuery { + currentUser { + id + name + ...UserFields @unmask + } + } + + fragment UserFields on User { + age + } + `; + + const mocks = [ + { + request: { query }, + result: { + data: { + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const observable = client.watchQuery({ query, fetchPolicy: "no-cache" }); + const stream = new ObservableStream(observable); + + { + const { data } = await stream.takeNext(); + + expect(data).toEqual({ + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }); + } + + expect(console.warn).not.toHaveBeenCalled(); + }); + + test("warns on no-cache queries when at least one fragment does not use `@unmask`", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Query { + currentUser: { + __typename: "User"; + id: number; + name: string; + } & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } }; + } + + const query: MaskedDocumentNode = gql` + query MaskedQuery { + currentUser { + id + name + ...UserFields @unmask + } + } + + fragment UserFields on User { + age + ...ProfileFields + } + + fragment ProfileFields on User { + username + } + `; + + const mocks = [ + { + request: { query }, + result: { + data: { + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + username: "testuser", + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const observable = client.watchQuery({ query, fetchPolicy: "no-cache" }); + const stream = new ObservableStream(observable); + + { + const { data } = await stream.takeNext(); + + expect(data).toEqual({ + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }); + } + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith( + 'Fragments masked by data masking when using fetch policy "no-cache" cannot be read by `watchFragment` or `useFragment`. Please add `@unmask` to the fragment to read the fragment data.' + ); + }); }); describe("client.watchFragment", () => { From 4b9ccb563ddb62f000385bb41eb918ab899710f1 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 16:14:48 -0700 Subject: [PATCH 02/21] Add implementation to warn for no-cache query --- src/core/ObservableQuery.ts | 2 ++ src/core/QueryManager.ts | 23 +++++++++++++++++++++++ src/core/masking.ts | 7 ++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts index d134b1989eb..d30197b416d 100644 --- a/src/core/ObservableQuery.ts +++ b/src/core/ObservableQuery.ts @@ -1140,6 +1140,8 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, data: this.queryManager.maskOperation({ document: this.query, data: result.data, + fetchPolicy: this.options.fetchPolicy, + queryId: this.queryId, }), } : result; diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index e499a3d78b7..db3dac0317b 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -51,6 +51,7 @@ import type { MutationOptions, ErrorPolicy, MutationFetchPolicy, + WatchQueryFetchPolicy, } from "./watchQueryOptions.js"; import { ObservableQuery, logMissingFieldErrors } from "./ObservableQuery.js"; import { NetworkStatus, isNetworkRequestInFlight } from "./networkStatus.js"; @@ -118,6 +119,8 @@ interface MaskFragmentOptions { interface MaskOperationOptions { document: DocumentNode; data: TData; + queryId?: string; + fetchPolicy?: WatchQueryFetchPolicy; } export interface QueryManagerOptions { @@ -135,6 +138,8 @@ export interface QueryManagerOptions { dataMasking: boolean; } +const noCacheWarningsByQueryId = new Set(); + export class QueryManager { public cache: ApolloCache; public link: ApolloLink; @@ -1559,6 +1564,24 @@ export class QueryManager { ): MaybeMasked { const { document, data } = options; + if (__DEV__) { + const { fetchPolicy, queryId } = options; + + if ( + this.dataMasking && + fetchPolicy === "no-cache" && + (!queryId || !noCacheWarningsByQueryId.has(queryId)) + ) { + if (queryId) { + noCacheWarningsByQueryId.add(queryId); + } + + invariant.warn( + 'Fragments masked by data masking when using fetch policy "no-cache" cannot be read by `watchFragment` or `useFragment`. Please add `@unmask` to the fragment to read the fragment data.' + ); + } + } + return ( this.dataMasking ? maskOperation(data, document, this.cache) diff --git a/src/core/masking.ts b/src/core/masking.ts index a6d288b9653..2094690354a 100644 --- a/src/core/masking.ts +++ b/src/core/masking.ts @@ -9,7 +9,12 @@ import { maybeDeepFreeze, } from "../utilities/index.js"; import type { FragmentMap } from "../utilities/index.js"; -import type { ApolloCache, DocumentNode, TypedDocumentNode } from "./index.js"; +import type { + ApolloCache, + DocumentNode, + TypedDocumentNode, + WatchQueryFetchPolicy, +} from "./index.js"; import { invariant } from "../utilities/globals/index.js"; import { equal } from "@wry/equality"; import { Slot } from "optimism"; From 63124bfd82d2213ce33b5926709bd2395cbf6362 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 16:36:50 -0700 Subject: [PATCH 03/21] Don't warn when query is fully unmasked --- src/core/QueryManager.ts | 10 ++++++---- src/utilities/graphql/fragments.ts | 24 ++++++++++++++++++++++++ src/utilities/index.ts | 1 + 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index db3dac0317b..981daaeb59e 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -13,6 +13,7 @@ import { hasDirectives, isExecutionPatchIncrementalResult, isExecutionPatchResult, + isFullyUnmaskedQuery, removeDirectivesFromDocument, } from "../utilities/index.js"; import type { Cache, ApolloCache } from "../cache/index.js"; @@ -138,8 +139,6 @@ export interface QueryManagerOptions { dataMasking: boolean; } -const noCacheWarningsByQueryId = new Set(); - export class QueryManager { public cache: ApolloCache; public link: ApolloLink; @@ -1559,6 +1558,8 @@ export class QueryManager { return results; } + private noCacheWarningsByQueryId = new Set(); + public maskOperation( options: MaskOperationOptions ): MaybeMasked { @@ -1570,10 +1571,11 @@ export class QueryManager { if ( this.dataMasking && fetchPolicy === "no-cache" && - (!queryId || !noCacheWarningsByQueryId.has(queryId)) + !isFullyUnmaskedQuery(document) && + (!queryId || !this.noCacheWarningsByQueryId.has(queryId)) ) { if (queryId) { - noCacheWarningsByQueryId.add(queryId); + this.noCacheWarningsByQueryId.add(queryId); } invariant.warn( diff --git a/src/utilities/graphql/fragments.ts b/src/utilities/graphql/fragments.ts index c4c0d721b5c..0099500a58b 100644 --- a/src/utilities/graphql/fragments.ts +++ b/src/utilities/graphql/fragments.ts @@ -1,5 +1,6 @@ import { invariant, newInvariantError } from "../globals/index.js"; +import { BREAK, visit } from "graphql"; import type { DocumentNode, FragmentDefinitionNode, @@ -143,3 +144,26 @@ export function getFragmentFromSelection( return null; } } + +export function isFullyUnmaskedQuery(document: DocumentNode) { + let isUnmasked = true; + + visit(document, { + FragmentSpread: (node) => { + if (!node.directives) { + isUnmasked = false; + return BREAK; + } + + isUnmasked &&= node.directives.some( + (directive) => directive.name.value === "unmask" + ); + + if (!isUnmasked) { + return BREAK; + } + }, + }); + + return isUnmasked; +} diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 59f2edb054f..87208bfab40 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -23,6 +23,7 @@ export { createFragmentMap, getFragmentQueryDocument, getFragmentFromSelection, + isFullyUnmaskedQuery, } from "./graphql/fragments.js"; export { From cb9496d4a0b9503cd4597f6875865ece222f8e40 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 16:38:39 -0700 Subject: [PATCH 04/21] Extract warning to a constant --- src/__tests__/dataMasking.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index b0e27e59a62..b18abc43a24 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -26,6 +26,9 @@ import { createFragmentRegistry } from "../cache/inmemory/fragmentRegistry"; import { isSubscriptionOperation } from "../utilities"; import { MaskedDocumentNode } from "../masking"; +const NO_CACHE_WARNING = + 'Fragments masked by data masking when using fetch policy "no-cache" cannot be read by `watchFragment` or `useFragment`. Please add `@unmask` to the fragment to read the fragment data.'; + describe("client.watchQuery", () => { test("masks queries when dataMasking is `true`", async () => { type UserFieldsFragment = { @@ -2384,9 +2387,7 @@ describe("client.watchQuery", () => { } expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith( - 'Fragments masked by data masking when using fetch policy "no-cache" cannot be read by `watchFragment` or `useFragment`. Please add `@unmask` to the fragment to read the fragment data.' - ); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); }); test("does not warn on no-cache queries when data masking is disabled", async () => { @@ -2600,9 +2601,7 @@ describe("client.watchQuery", () => { } expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith( - 'Fragments masked by data masking when using fetch policy "no-cache" cannot be read by `watchFragment` or `useFragment`. Please add `@unmask` to the fragment to read the fragment data.' - ); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); }); }); From 0552fc2f0463f1fcc264398fe41c67ce2241a361 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 16:43:47 -0700 Subject: [PATCH 05/21] Add no-cache warnings when using client.query --- src/__tests__/dataMasking.ts | 263 +++++++++++++++++++++++++++++++++++ src/core/QueryManager.ts | 7 +- 2 files changed, 269 insertions(+), 1 deletion(-) diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index b18abc43a24..c96a886fd7e 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -3909,6 +3909,269 @@ describe("client.query", () => { expect(errors).toEqual([{ message: "Could not determine age" }]); }); + + test("warns and returns masked result when used with no-cache fetch policy", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Query { + currentUser: { + __typename: "User"; + id: number; + name: string; + } & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } }; + } + + const query: MaskedDocumentNode = gql` + query MaskedQuery { + currentUser { + id + name + ...UserFields + } + } + + fragment UserFields on User { + age + } + `; + + const mocks = [ + { + request: { query }, + result: { + data: { + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const { data } = await client.query({ query, fetchPolicy: "no-cache" }); + + expect(data).toEqual({ + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + }, + }); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + }); + + test("does not warn on no-cache queries when data masking is disabled", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Query { + currentUser: { + __typename: "User"; + id: number; + name: string; + } & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } }; + } + + const query: MaskedDocumentNode = gql` + query MaskedQuery { + currentUser { + id + name + ...UserFields + } + } + + fragment UserFields on User { + age + } + `; + + const mocks = [ + { + request: { query }, + result: { + data: { + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: false, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const { data } = await client.query({ query, fetchPolicy: "no-cache" }); + + expect(data).toEqual({ + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }); + + expect(console.warn).not.toHaveBeenCalled(); + }); + + test("does not warn on no-cache queries when all fragments use `@unmask`", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Query { + currentUser: { + __typename: "User"; + id: number; + name: string; + } & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } }; + } + + const query: MaskedDocumentNode = gql` + query MaskedQuery { + currentUser { + id + name + ...UserFields @unmask + } + } + + fragment UserFields on User { + age + } + `; + + const mocks = [ + { + request: { query }, + result: { + data: { + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const { data } = await client.query({ query, fetchPolicy: "no-cache" }); + + expect(data).toEqual({ + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }); + + expect(console.warn).not.toHaveBeenCalled(); + }); + + test("warns on no-cache queries when at least one fragment does not use `@unmask`", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Query { + currentUser: { + __typename: "User"; + id: number; + name: string; + } & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } }; + } + + const query: MaskedDocumentNode = gql` + query MaskedQuery { + currentUser { + id + name + ...UserFields @unmask + } + } + + fragment UserFields on User { + age + ...ProfileFields + } + + fragment ProfileFields on User { + username + } + `; + + const mocks = [ + { + request: { query }, + result: { + data: { + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + username: "testuser", + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const { data } = await client.query({ query, fetchPolicy: "no-cache" }); + + expect(data).toEqual({ + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + }); }); describe("client.subscribe", () => { diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index 981daaeb59e..d9009f662e1 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -823,7 +823,12 @@ export class QueryManager { (result) => result && { ...result, - data: this.maskOperation({ document: query, data: result.data }), + data: this.maskOperation({ + document: query, + data: result.data, + fetchPolicy: options.fetchPolicy, + queryId, + }), } ) .finally(() => this.stopQuery(queryId)); From 40b23f3d7e83bb93d946d262240124dfa3ce7084 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 16:46:59 -0700 Subject: [PATCH 06/21] Remove unused import --- src/core/masking.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/core/masking.ts b/src/core/masking.ts index 2094690354a..a6d288b9653 100644 --- a/src/core/masking.ts +++ b/src/core/masking.ts @@ -9,12 +9,7 @@ import { maybeDeepFreeze, } from "../utilities/index.js"; import type { FragmentMap } from "../utilities/index.js"; -import type { - ApolloCache, - DocumentNode, - TypedDocumentNode, - WatchQueryFetchPolicy, -} from "./index.js"; +import type { ApolloCache, DocumentNode, TypedDocumentNode } from "./index.js"; import { invariant } from "../utilities/globals/index.js"; import { equal } from "@wry/equality"; import { Slot } from "optimism"; From fb49a697b40534ff15c413157968cfc88f9e6458 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 16:48:34 -0700 Subject: [PATCH 07/21] Add warning for client.subscribe --- src/__tests__/dataMasking.ts | 56 ++++++++++++++++++++++++++++++++++++ src/core/ApolloClient.ts | 1 + 2 files changed, 57 insertions(+) diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index c96a886fd7e..09c7c524788 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -4462,6 +4462,62 @@ describe("client.subscribe", () => { expect(data).toEqual({ addedComment: { __typename: "Comment", id: 1 } }); expect(errors).toEqual([{ message: "Could not get author" }]); }); + + test("warns and returns masked result when used with no-cache fetch policy", async () => { + using _ = spyOnConsole("warn"); + const subscription = gql` + subscription NewCommentSubscription { + addedComment { + id + ...CommentFields + } + } + + fragment CommentFields on Comment { + comment + author + } + `; + + const link = new MockSubscriptionLink(); + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link, + }); + + const observable = client.subscribe({ + query: subscription, + fetchPolicy: "no-cache", + }); + const stream = new ObservableStream(observable); + + link.simulateResult({ + result: { + data: { + addedComment: { + __typename: "Comment", + id: 1, + comment: "Test comment", + author: "Test User", + }, + }, + }, + }); + + const { data } = await stream.takeNext(); + + expect(data).toEqual({ + addedComment: { + __typename: "Comment", + id: 1, + }, + }); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + }); }); describe("observableQuery.subscribeToMore", () => { diff --git a/src/core/ApolloClient.ts b/src/core/ApolloClient.ts index a299b56e2f3..8d35ff30707 100644 --- a/src/core/ApolloClient.ts +++ b/src/core/ApolloClient.ts @@ -517,6 +517,7 @@ export class ApolloClient implements DataProxy { data: this.queryManager.maskOperation({ document: options.query, data: result.data, + fetchPolicy: options.fetchPolicy, }), })); } From 1765ee8929cc07b93b0b79d4a91e869c675f7152 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 16:52:44 -0700 Subject: [PATCH 08/21] Ensure mutations warn with no-cache fetch policy and data masking --- src/__tests__/dataMasking.ts | 66 ++++++++++++++++++++++++++++++++++++ src/core/QueryManager.ts | 1 + 2 files changed, 67 insertions(+) diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index 09c7c524788..6bbe4dbcbf7 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -5341,6 +5341,72 @@ describe("client.mutate", () => { expect(errors).toEqual([{ message: "Could not determine age" }]); }); + + test("warns and returns masked result when used with no-cache fetch policy", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Mutation { + updateUser: { + __typename: "User"; + id: number; + name: string; + } & { + " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment }; + }; + } + + const mutation: MaskedDocumentNode = gql` + mutation MaskedMutation { + updateUser { + id + name + ...UserFields + } + } + + fragment UserFields on User { + age + } + `; + + const mocks = [ + { + request: { query: mutation }, + result: { + data: { + updateUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const { data } = await client.mutate({ mutation, fetchPolicy: "no-cache" }); + + expect(data).toEqual({ + updateUser: { + __typename: "User", + id: 1, + name: "Test User", + }, + }); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + }); }); class TestCache extends ApolloCache { diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index d9009f662e1..cfdb82fec43 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -366,6 +366,7 @@ export class QueryManager { data: self.maskOperation({ document: mutation, data: storeResult.data, + fetchPolicy, }) as any, }); } From 5a3b9e529fbc943de1e987236b5900f950561693 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 16:56:45 -0700 Subject: [PATCH 09/21] Add additional tests for warnings with no-cache on client.mutate --- src/__tests__/dataMasking.ts | 210 +++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index 6bbe4dbcbf7..b1e00a82166 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -5407,6 +5407,216 @@ describe("client.mutate", () => { expect(console.warn).toHaveBeenCalledTimes(1); expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); }); + + test("does not warn on no-cache queries when data masking is disabled", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Mutation { + updateUser: { + __typename: "User"; + id: number; + name: string; + } & { + " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment }; + }; + } + + const mutation: MaskedDocumentNode = gql` + mutation MaskedMutation { + updateUser { + id + name + ...UserFields + } + } + + fragment UserFields on User { + age + } + `; + + const mocks = [ + { + request: { query: mutation }, + result: { + data: { + updateUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: false, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const { data } = await client.mutate({ mutation, fetchPolicy: "no-cache" }); + + expect(data).toEqual({ + updateUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }); + + expect(console.warn).not.toHaveBeenCalled(); + }); + + test("does not warn on no-cache queries when all fragments use `@unmask`", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" }; + + interface Mutation { + updateUser: { + __typename: "User"; + id: number; + name: string; + } & { + " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment }; + }; + } + + const mutation: MaskedDocumentNode = gql` + mutation MaskedMutation { + updateUser { + id + name + ...UserFields @unmask + } + } + + fragment UserFields on User { + age + } + `; + + const mocks = [ + { + request: { query: mutation }, + result: { + data: { + updateUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const { data } = await client.mutate({ mutation, fetchPolicy: "no-cache" }); + + expect(data).toEqual({ + updateUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }); + + expect(console.warn).not.toHaveBeenCalled(); + }); + + test("warns on no-cache queries when at least one fragment does not use `@unmask`", async () => { + using _ = spyOnConsole("warn"); + type UserFieldsFragment = { + age: number; + } & { " $fragmentName"?: "UserFieldsFragment" } & { + " $fragmentRefs"?: { ProfileFieldsFragment: ProfileFieldsFragment }; + }; + type ProfileFieldsFragment = { + username: number; + } & { " $fragmentName"?: "ProfileFieldsFragment" }; + + interface Mutation { + updateUser: { + __typename: "User"; + id: number; + name: string; + } & { + " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment }; + }; + } + + const mutation: MaskedDocumentNode = gql` + mutation MaskedMutation { + updateUser { + id + name + ...UserFields @unmask + } + } + + fragment UserFields on User { + age + ...ProfileFieldsFragment + } + + fragment ProfileFieldsFragment on User { + username + } + `; + + const mocks = [ + { + request: { query: mutation }, + result: { + data: { + updateUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + username: "testuser", + }, + }, + }, + }, + ]; + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link: new MockLink(mocks), + }); + + const { data } = await client.mutate({ mutation, fetchPolicy: "no-cache" }); + + expect(data).toEqual({ + updateUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + }); }); class TestCache extends ApolloCache { From d3aad6d55d496f75b85e5b623221faaa5738729e Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 17:00:07 -0700 Subject: [PATCH 10/21] Add additional tests for warnings with no-cache on client.subscribe --- src/__tests__/dataMasking.ts | 178 +++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index b1e00a82166..89c412641af 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -4518,6 +4518,184 @@ describe("client.subscribe", () => { expect(console.warn).toHaveBeenCalledTimes(1); expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); }); + + test("does not warn on no-cache queries when data masking is disabled", async () => { + using _ = spyOnConsole("warn"); + const subscription = gql` + subscription NewCommentSubscription { + addedComment { + id + ...CommentFields + } + } + + fragment CommentFields on Comment { + comment + author + } + `; + + const link = new MockSubscriptionLink(); + + const client = new ApolloClient({ + dataMasking: false, + cache: new InMemoryCache(), + link, + }); + + const observable = client.subscribe({ + query: subscription, + fetchPolicy: "no-cache", + }); + const stream = new ObservableStream(observable); + + link.simulateResult({ + result: { + data: { + addedComment: { + __typename: "Comment", + id: 1, + comment: "Test comment", + author: "Test User", + }, + }, + }, + }); + + const { data } = await stream.takeNext(); + + expect(data).toEqual({ + addedComment: { + __typename: "Comment", + id: 1, + comment: "Test comment", + author: "Test User", + }, + }); + + expect(console.warn).not.toHaveBeenCalled(); + }); + + test("does not warn on no-cache queries when all fragments use `@unmask`", async () => { + using _ = spyOnConsole("warn"); + const subscription = gql` + subscription NewCommentSubscription { + addedComment { + id + ...CommentFields @unmask + } + } + + fragment CommentFields on Comment { + comment + author + } + `; + + const link = new MockSubscriptionLink(); + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link, + }); + + const observable = client.subscribe({ + query: subscription, + fetchPolicy: "no-cache", + }); + const stream = new ObservableStream(observable); + + link.simulateResult({ + result: { + data: { + addedComment: { + __typename: "Comment", + id: 1, + comment: "Test comment", + author: "Test User", + }, + }, + }, + }); + + const { data } = await stream.takeNext(); + + expect(data).toEqual({ + addedComment: { + __typename: "Comment", + id: 1, + comment: "Test comment", + author: "Test User", + }, + }); + + expect(console.warn).not.toHaveBeenCalled(); + }); + + test("warns on no-cache queries when at least one fragment does not use `@unmask`", async () => { + using _ = spyOnConsole("warn"); + const subscription = gql` + subscription NewCommentSubscription { + addedComment { + id + ...CommentFields @unmask + } + } + + fragment CommentFields on Comment { + comment + author { + ...AuthorFields + } + } + + fragment AuthorFields on User { + name + } + `; + + const link = new MockSubscriptionLink(); + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache(), + link, + }); + + const observable = client.subscribe({ + query: subscription, + fetchPolicy: "no-cache", + }); + const stream = new ObservableStream(observable); + + link.simulateResult({ + result: { + data: { + addedComment: { + __typename: "Comment", + id: 1, + comment: "Test comment", + author: { __typename: "User", name: "Test User" }, + }, + }, + }, + }); + + const { data } = await stream.takeNext(); + + expect(data).toEqual({ + addedComment: { + __typename: "Comment", + id: 1, + comment: "Test comment", + author: { __typename: "User" }, + }, + }); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + }); }); describe("observableQuery.subscribeToMore", () => { From 75bc952d0c7ef5225feb14225ceea513d6440e8a Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 17:01:54 -0700 Subject: [PATCH 11/21] Update size limits and api report --- .api-reports/api-report-core.api.md | 8 ++++++-- .api-reports/api-report-react.api.md | 8 ++++++-- .api-reports/api-report-react_components.api.md | 12 ++++++++---- .api-reports/api-report-react_context.api.md | 12 ++++++++---- .api-reports/api-report-react_hoc.api.md | 12 ++++++++---- .api-reports/api-report-react_hooks.api.md | 8 ++++++-- .api-reports/api-report-react_internal.api.md | 8 ++++++-- .api-reports/api-report-react_ssr.api.md | 12 ++++++++---- .api-reports/api-report-testing.api.md | 12 ++++++++---- .api-reports/api-report-testing_core.api.md | 12 ++++++++---- .api-reports/api-report-utilities.api.md | 15 +++++++++++---- .api-reports/api-report.api.md | 8 ++++++-- .size-limits.json | 4 ++-- 13 files changed, 91 insertions(+), 40 deletions(-) diff --git a/.api-reports/api-report-core.api.md b/.api-reports/api-report-core.api.md index 3a31996bbd3..4aca27710a3 100644 --- a/.api-reports/api-report-core.api.md +++ b/.api-reports/api-report-core.api.md @@ -1365,6 +1365,10 @@ interface MaskOperationOptions { data: TData; // (undocumented) document: DocumentNode; + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -2438,8 +2442,8 @@ interface WriteContext extends ReadMergeModifyContext { // src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:408:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/link/http/selectHttpOptionsAndBody.ts:128:32 - (ae-forgotten-export) The symbol "HttpQueryOptions" 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 818587e9537..f4d55b87197 100644 --- a/.api-reports/api-report-react.api.md +++ b/.api-reports/api-report-react.api.md @@ -1110,6 +1110,10 @@ interface MaskOperationOptions { data: TData; // (undocumented) document: DocumentNode; + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -2449,8 +2453,8 @@ interface WatchQueryOptions { data: TData; // (undocumented) document: DocumentNode; + // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts + // + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -1164,8 +1170,6 @@ enum NetworkStatus { // @public (undocumented) interface NextFetchPolicyContext { - // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts - // // (undocumented) initialFetchPolicy: WatchQueryFetchPolicy; // (undocumented) @@ -1931,8 +1935,8 @@ interface WatchQueryOptions { data: TData; // (undocumented) document: DocumentNode; + // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts + // + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -1118,8 +1124,6 @@ enum NetworkStatus { // @public (undocumented) interface NextFetchPolicyContext { - // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts - // // (undocumented) initialFetchPolicy: WatchQueryFetchPolicy; // (undocumented) @@ -1851,8 +1855,8 @@ interface WatchQueryOptions { data: TData; // (undocumented) document: DocumentNode; + // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts + // + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -1157,8 +1163,6 @@ enum NetworkStatus { // @public (undocumented) interface NextFetchPolicyContext { - // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts - // // (undocumented) initialFetchPolicy: WatchQueryFetchPolicy; // (undocumented) @@ -1878,8 +1882,8 @@ export function withSubscription { data: TData; // (undocumented) document: DocumentNode; + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -2273,8 +2277,8 @@ interface WatchQueryOptions { data: TData; // (undocumented) document: DocumentNode; + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -2336,8 +2340,8 @@ export function wrapQueryRef(inter // src/core/LocalState.ts:46:5 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:408:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:175:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:204:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" 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 95c4d46cd9f..b84e7b8395a 100644 --- a/.api-reports/api-report-react_ssr.api.md +++ b/.api-reports/api-report-react_ssr.api.md @@ -953,6 +953,12 @@ interface MaskOperationOptions { data: TData; // (undocumented) document: DocumentNode; + // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts + // + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -1103,8 +1109,6 @@ enum NetworkStatus { // @public (undocumented) interface NextFetchPolicyContext { - // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts - // // (undocumented) initialFetchPolicy: WatchQueryFetchPolicy; // (undocumented) @@ -1836,8 +1840,8 @@ interface WatchQueryOptions { data: TData; // (undocumented) document: DocumentNode; + // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts + // + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -1224,8 +1230,6 @@ enum NetworkStatus { // @public (undocumented) interface NextFetchPolicyContext { - // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts - // // (undocumented) initialFetchPolicy: WatchQueryFetchPolicy; // (undocumented) @@ -1904,8 +1908,8 @@ export function withWarningSpy(it: (...args: TArgs // src/core/LocalState.ts:46:5 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:408:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:175:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:204:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" 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 621bb36846b..1587195cdcd 100644 --- a/.api-reports/api-report-testing_core.api.md +++ b/.api-reports/api-report-testing_core.api.md @@ -941,6 +941,12 @@ interface MaskOperationOptions { data: TData; // (undocumented) document: DocumentNode; + // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts + // + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -1179,8 +1185,6 @@ enum NetworkStatus { // @public (undocumented) interface NextFetchPolicyContext { - // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts - // // (undocumented) initialFetchPolicy: WatchQueryFetchPolicy; // (undocumented) @@ -1861,8 +1865,8 @@ export function withWarningSpy(it: (...args: TArgs // src/core/LocalState.ts:46:5 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:408:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:175:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:204:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-utilities.api.md b/.api-reports/api-report-utilities.api.md index 32893220155..5ce9d3673d8 100644 --- a/.api-reports/api-report-utilities.api.md +++ b/.api-reports/api-report-utilities.api.md @@ -1480,6 +1480,9 @@ export function isExecutionPatchResult(value: FetchResult): value is Execu // @public (undocumented) export function isField(selection: SelectionNode): selection is FieldNode; +// @public (undocumented) +export function isFullyUnmaskedQuery(document: DocumentNode): boolean; + // @public (undocumented) export function isInlineFragment(selection: SelectionNode): selection is InlineFragmentNode; @@ -1642,6 +1645,12 @@ interface MaskOperationOptions { data: TData; // (undocumented) document: DocumentNode; + // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts + // + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -1833,8 +1842,6 @@ enum NetworkStatus { // @public (undocumented) interface NextFetchPolicyContext { - // Warning: (ae-forgotten-export) The symbol "WatchQueryFetchPolicy" needs to be exported by the entry point index.d.ts - // // (undocumented) initialFetchPolicy: WatchQueryFetchPolicy; // (undocumented) @@ -2802,8 +2809,8 @@ interface WriteContext extends ReadMergeModifyContext { // src/core/LocalState.ts:71:3 - (ae-forgotten-export) The symbol "ApolloClient" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:408:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:175:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:204:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" 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 0a2c8f55e31..6862a8f0fb6 100644 --- a/.api-reports/api-report.api.md +++ b/.api-reports/api-report.api.md @@ -1546,6 +1546,10 @@ interface MaskOperationOptions { data: TData; // (undocumented) document: DocumentNode; + // (undocumented) + fetchPolicy?: WatchQueryFetchPolicy; + // (undocumented) + queryId?: string; } // @public (undocumented) @@ -3149,8 +3153,8 @@ interface WriteContext extends ReadMergeModifyContext { // src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:155:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:408:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/link/http/selectHttpOptionsAndBody.ts:128:32 - (ae-forgotten-export) The symbol "HttpQueryOptions" needs to be exported by the entry point index.d.ts // src/react/hooks/useBackgroundQuery.ts:38:3 - (ae-forgotten-export) The symbol "SubscribeToMoreFunction" needs to be exported by the entry point index.d.ts diff --git a/.size-limits.json b/.size-limits.json index c88de213411..f39c7e3c76e 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 41520, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34303 + "dist/apollo-client.min.cjs": 41564, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34354 } From a655b06ef6078a4e98771f37e03f520f2b072df7 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 17:06:44 -0700 Subject: [PATCH 12/21] Update snapshot export --- src/__tests__/__snapshots__/exports.ts.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/src/__tests__/__snapshots__/exports.ts.snap b/src/__tests__/__snapshots__/exports.ts.snap index 92acb14a1da..8aa0f7eedf2 100644 --- a/src/__tests__/__snapshots__/exports.ts.snap +++ b/src/__tests__/__snapshots__/exports.ts.snap @@ -465,6 +465,7 @@ Array [ "isExecutionPatchInitialResult", "isExecutionPatchResult", "isField", + "isFullyUnmaskedQuery", "isInlineFragment", "isMutationOperation", "isNonEmptyArray", From f14f97f93dc1a52766193f160477606cafe124f7 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 17:07:52 -0700 Subject: [PATCH 13/21] Rename isFullyUnmaskedQuery to isFullyUnmaskedOperation --- src/__tests__/__snapshots__/exports.ts.snap | 2 +- src/core/QueryManager.ts | 4 ++-- src/utilities/graphql/fragments.ts | 2 +- src/utilities/index.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/__tests__/__snapshots__/exports.ts.snap b/src/__tests__/__snapshots__/exports.ts.snap index 8aa0f7eedf2..7527cb36224 100644 --- a/src/__tests__/__snapshots__/exports.ts.snap +++ b/src/__tests__/__snapshots__/exports.ts.snap @@ -465,7 +465,7 @@ Array [ "isExecutionPatchInitialResult", "isExecutionPatchResult", "isField", - "isFullyUnmaskedQuery", + "isFullyUnmaskedOperation", "isInlineFragment", "isMutationOperation", "isNonEmptyArray", diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index cfdb82fec43..262d30f8efb 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -13,7 +13,7 @@ import { hasDirectives, isExecutionPatchIncrementalResult, isExecutionPatchResult, - isFullyUnmaskedQuery, + isFullyUnmaskedOperation, removeDirectivesFromDocument, } from "../utilities/index.js"; import type { Cache, ApolloCache } from "../cache/index.js"; @@ -1577,7 +1577,7 @@ export class QueryManager { if ( this.dataMasking && fetchPolicy === "no-cache" && - !isFullyUnmaskedQuery(document) && + !isFullyUnmaskedOperation(document) && (!queryId || !this.noCacheWarningsByQueryId.has(queryId)) ) { if (queryId) { diff --git a/src/utilities/graphql/fragments.ts b/src/utilities/graphql/fragments.ts index 0099500a58b..23ec0d853e8 100644 --- a/src/utilities/graphql/fragments.ts +++ b/src/utilities/graphql/fragments.ts @@ -145,7 +145,7 @@ export function getFragmentFromSelection( } } -export function isFullyUnmaskedQuery(document: DocumentNode) { +export function isFullyUnmaskedOperation(document: DocumentNode) { let isUnmasked = true; visit(document, { diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 87208bfab40..300cafb0d56 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -23,7 +23,7 @@ export { createFragmentMap, getFragmentQueryDocument, getFragmentFromSelection, - isFullyUnmaskedQuery, + isFullyUnmaskedOperation, } from "./graphql/fragments.js"; export { From ddc9893fd8454dcd6ecc40980fd7f16e6767161d Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Tue, 12 Nov 2024 17:10:29 -0700 Subject: [PATCH 14/21] Update size limits and api report --- .api-reports/api-report-utilities.api.md | 2 +- .size-limits.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.api-reports/api-report-utilities.api.md b/.api-reports/api-report-utilities.api.md index 5ce9d3673d8..75d2f60ab40 100644 --- a/.api-reports/api-report-utilities.api.md +++ b/.api-reports/api-report-utilities.api.md @@ -1481,7 +1481,7 @@ export function isExecutionPatchResult(value: FetchResult): value is Execu export function isField(selection: SelectionNode): selection is FieldNode; // @public (undocumented) -export function isFullyUnmaskedQuery(document: DocumentNode): boolean; +export function isFullyUnmaskedOperation(document: DocumentNode): boolean; // @public (undocumented) export function isInlineFragment(selection: SelectionNode): selection is InlineFragmentNode; diff --git a/.size-limits.json b/.size-limits.json index f39c7e3c76e..67f553873ef 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { "dist/apollo-client.min.cjs": 41564, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34354 + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34355 } From 3a26fd52cb9c123d43ac430ac34e4a921197697e Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 13 Nov 2024 09:48:28 -0700 Subject: [PATCH 15/21] Reword warning and provide more info --- src/__tests__/dataMasking.ts | 30 +++++++++++++++++++++--------- src/core/QueryManager.ts | 6 +++++- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index 89c412641af..70ba8a8ca44 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -27,7 +27,7 @@ import { isSubscriptionOperation } from "../utilities"; import { MaskedDocumentNode } from "../masking"; const NO_CACHE_WARNING = - 'Fragments masked by data masking when using fetch policy "no-cache" cannot be read by `watchFragment` or `useFragment`. Please add `@unmask` to the fragment to read the fragment data.'; + '[%s]: Fragments masked by data masking are inaccessible when using fetch policy "no-cache". Please add `@unmask` to each fragment spread to access the data.'; describe("client.watchQuery", () => { test("masks queries when dataMasking is `true`", async () => { @@ -2387,7 +2387,7 @@ describe("client.watchQuery", () => { } expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING, "MaskedQuery"); }); test("does not warn on no-cache queries when data masking is disabled", async () => { @@ -2601,7 +2601,7 @@ describe("client.watchQuery", () => { } expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING, "MaskedQuery"); }); }); @@ -3971,7 +3971,7 @@ describe("client.query", () => { }); expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING, "MaskedQuery"); }); test("does not warn on no-cache queries when data masking is disabled", async () => { @@ -4170,7 +4170,7 @@ describe("client.query", () => { }); expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING, "MaskedQuery"); }); }); @@ -4516,7 +4516,10 @@ describe("client.subscribe", () => { }); expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + expect(console.warn).toHaveBeenCalledWith( + NO_CACHE_WARNING, + "NewCommentSubscription" + ); }); test("does not warn on no-cache queries when data masking is disabled", async () => { @@ -4694,7 +4697,10 @@ describe("client.subscribe", () => { }); expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + expect(console.warn).toHaveBeenCalledWith( + NO_CACHE_WARNING, + "NewCommentSubscription" + ); }); }); @@ -5583,7 +5589,10 @@ describe("client.mutate", () => { }); expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + expect(console.warn).toHaveBeenCalledWith( + NO_CACHE_WARNING, + "MaskedMutation" + ); }); test("does not warn on no-cache queries when data masking is disabled", async () => { @@ -5793,7 +5802,10 @@ describe("client.mutate", () => { }); expect(console.warn).toHaveBeenCalledTimes(1); - expect(console.warn).toHaveBeenCalledWith(NO_CACHE_WARNING); + expect(console.warn).toHaveBeenCalledWith( + NO_CACHE_WARNING, + "MaskedMutation" + ); }); }); diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index 262d30f8efb..7485bf2d6bd 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -1585,7 +1585,11 @@ export class QueryManager { } invariant.warn( - 'Fragments masked by data masking when using fetch policy "no-cache" cannot be read by `watchFragment` or `useFragment`. Please add `@unmask` to the fragment to read the fragment data.' + '[%s]: Fragments masked by data masking are inaccessible when using fetch policy "no-cache". Please add `@unmask` to each fragment spread to access the data.', + getOperationName(document) ?? + `Unnamed ${ + getOperationDefinition(document)?.operation ?? "operation" + }` ); } } From 61c8a41f6cd06f77070b35b0b694eaae9eb5d627 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 13 Nov 2024 09:50:11 -0700 Subject: [PATCH 16/21] Minor refactor to isFullyUnmaskedOperation --- src/utilities/graphql/fragments.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/utilities/graphql/fragments.ts b/src/utilities/graphql/fragments.ts index 23ec0d853e8..5baffedf69d 100644 --- a/src/utilities/graphql/fragments.ts +++ b/src/utilities/graphql/fragments.ts @@ -150,14 +150,9 @@ export function isFullyUnmaskedOperation(document: DocumentNode) { visit(document, { FragmentSpread: (node) => { - if (!node.directives) { - isUnmasked = false; - return BREAK; - } - - isUnmasked &&= node.directives.some( - (directive) => directive.name.value === "unmask" - ); + isUnmasked = + !!node.directives && + node.directives.some((directive) => directive.name.value === "unmask"); if (!isUnmasked) { return BREAK; From 67aa4eb411a642ab5c4d7b806ef1a4dd0a6af024 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 13 Nov 2024 09:55:05 -0700 Subject: [PATCH 17/21] Rename queryId to id in maskOperation options --- src/core/ObservableQuery.ts | 2 +- src/core/QueryManager.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts index d30197b416d..2a70e6e9097 100644 --- a/src/core/ObservableQuery.ts +++ b/src/core/ObservableQuery.ts @@ -1141,7 +1141,7 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, document: this.query, data: result.data, fetchPolicy: this.options.fetchPolicy, - queryId: this.queryId, + id: this.queryId, }), } : result; diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index 7485bf2d6bd..a35ca75d016 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -120,7 +120,7 @@ interface MaskFragmentOptions { interface MaskOperationOptions { document: DocumentNode; data: TData; - queryId?: string; + id?: string; fetchPolicy?: WatchQueryFetchPolicy; } @@ -828,7 +828,7 @@ export class QueryManager { document: query, data: result.data, fetchPolicy: options.fetchPolicy, - queryId, + id: queryId, }), } ) @@ -1572,16 +1572,16 @@ export class QueryManager { const { document, data } = options; if (__DEV__) { - const { fetchPolicy, queryId } = options; + const { fetchPolicy, id } = options; if ( this.dataMasking && fetchPolicy === "no-cache" && !isFullyUnmaskedOperation(document) && - (!queryId || !this.noCacheWarningsByQueryId.has(queryId)) + (!id || !this.noCacheWarningsByQueryId.has(id)) ) { - if (queryId) { - this.noCacheWarningsByQueryId.add(queryId); + if (id) { + this.noCacheWarningsByQueryId.add(id); } invariant.warn( From 70f2d60d1009d325b83d54b672aed73ce054e697 Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 13 Nov 2024 10:00:33 -0700 Subject: [PATCH 18/21] Ensure warning is only called once per subscription --- src/__tests__/dataMasking.ts | 36 +++++++++++++++++++++++++++++++----- src/core/ApolloClient.ts | 3 +++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index 70ba8a8ca44..90ecaa89fbc 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -4506,15 +4506,41 @@ describe("client.subscribe", () => { }, }); - const { data } = await stream.takeNext(); + { + const { data } = await stream.takeNext(); - expect(data).toEqual({ - addedComment: { - __typename: "Comment", - id: 1, + expect(data).toEqual({ + addedComment: { + __typename: "Comment", + id: 1, + }, + }); + } + + link.simulateResult({ + result: { + data: { + addedComment: { + __typename: "Comment", + id: 2, + comment: "Test comment 2", + author: "Test User", + }, + }, }, }); + { + const { data } = await stream.takeNext(); + + expect(data).toEqual({ + addedComment: { + __typename: "Comment", + id: 2, + }, + }); + } + expect(console.warn).toHaveBeenCalledTimes(1); expect(console.warn).toHaveBeenCalledWith( NO_CACHE_WARNING, diff --git a/src/core/ApolloClient.ts b/src/core/ApolloClient.ts index 8d35ff30707..90f6fcff8bc 100644 --- a/src/core/ApolloClient.ts +++ b/src/core/ApolloClient.ts @@ -510,6 +510,8 @@ export class ApolloClient implements DataProxy { >( options: SubscriptionOptions ): Observable>> { + const id = `s${this.queryManager.generateQueryId()}`; + return this.queryManager .startGraphQLSubscription(options) .map((result) => ({ @@ -518,6 +520,7 @@ export class ApolloClient implements DataProxy { document: options.query, data: result.data, fetchPolicy: options.fetchPolicy, + id, }), })); } From 81c14b3bf881235631893e85d516484c0b0d16de Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 13 Nov 2024 10:01:29 -0700 Subject: [PATCH 19/21] Require id for maskOperation --- src/core/QueryManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index a35ca75d016..8bcd3fa307a 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -120,7 +120,7 @@ interface MaskFragmentOptions { interface MaskOperationOptions { document: DocumentNode; data: TData; - id?: string; + id: string; fetchPolicy?: WatchQueryFetchPolicy; } @@ -367,6 +367,7 @@ export class QueryManager { document: mutation, data: storeResult.data, fetchPolicy, + id: `m${mutationId}`, }) as any, }); } From f57d5a90d7a328ba1a177e6a1dbab1c269a3e52b Mon Sep 17 00:00:00 2001 From: Jerel Miller Date: Wed, 13 Nov 2024 10:05:45 -0700 Subject: [PATCH 20/21] Add prefix to operation id in maskOperation --- src/core/ApolloClient.ts | 2 +- src/core/QueryManager.ts | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/core/ApolloClient.ts b/src/core/ApolloClient.ts index 90f6fcff8bc..ce30f30e5c6 100644 --- a/src/core/ApolloClient.ts +++ b/src/core/ApolloClient.ts @@ -510,7 +510,7 @@ export class ApolloClient implements DataProxy { >( options: SubscriptionOptions ): Observable>> { - const id = `s${this.queryManager.generateQueryId()}`; + const id = this.queryManager.generateQueryId(); return this.queryManager .startGraphQLSubscription(options) diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index 8bcd3fa307a..87cea8d2b63 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -367,7 +367,7 @@ export class QueryManager { document: mutation, data: storeResult.data, fetchPolicy, - id: `m${mutationId}`, + id: mutationId, }) as any, }); } @@ -1574,23 +1574,21 @@ export class QueryManager { if (__DEV__) { const { fetchPolicy, id } = options; + const operationType = getOperationDefinition(document)?.operation; + const operationId = (operationType?.[0] ?? "o") + id; if ( this.dataMasking && fetchPolicy === "no-cache" && !isFullyUnmaskedOperation(document) && - (!id || !this.noCacheWarningsByQueryId.has(id)) + !this.noCacheWarningsByQueryId.has(operationId) ) { - if (id) { - this.noCacheWarningsByQueryId.add(id); - } + this.noCacheWarningsByQueryId.add(operationId); invariant.warn( '[%s]: Fragments masked by data masking are inaccessible when using fetch policy "no-cache". Please add `@unmask` to each fragment spread to access the data.', getOperationName(document) ?? - `Unnamed ${ - getOperationDefinition(document)?.operation ?? "operation" - }` + `Unnamed ${operationType ?? "operation"}` ); } } From 8a3fece9858bf4fbdc9c3741bb4a598319bf0a68 Mon Sep 17 00:00:00 2001 From: jerelmiller Date: Wed, 13 Nov 2024 17:08:16 +0000 Subject: [PATCH 21/21] Clean up Prettier, Size-limit, and Api-Extractor --- .api-reports/api-report-core.api.md | 4 ++-- .api-reports/api-report-react.api.md | 4 ++-- .api-reports/api-report-react_components.api.md | 4 ++-- .api-reports/api-report-react_context.api.md | 4 ++-- .api-reports/api-report-react_hoc.api.md | 4 ++-- .api-reports/api-report-react_hooks.api.md | 4 ++-- .api-reports/api-report-react_internal.api.md | 4 ++-- .api-reports/api-report-react_ssr.api.md | 4 ++-- .api-reports/api-report-testing.api.md | 4 ++-- .api-reports/api-report-testing_core.api.md | 4 ++-- .api-reports/api-report-utilities.api.md | 4 ++-- .api-reports/api-report.api.md | 4 ++-- .size-limits.json | 4 ++-- 13 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.api-reports/api-report-core.api.md b/.api-reports/api-report-core.api.md index 4aca27710a3..e46102d87c7 100644 --- a/.api-reports/api-report-core.api.md +++ b/.api-reports/api-report-core.api.md @@ -1368,7 +1368,7 @@ interface MaskOperationOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -2443,7 +2443,7 @@ interface WriteContext extends ReadMergeModifyContext { // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts // src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:414:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/link/http/selectHttpOptionsAndBody.ts:128:32 - (ae-forgotten-export) The symbol "HttpQueryOptions" 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 f4d55b87197..2422d04f284 100644 --- a/.api-reports/api-report-react.api.md +++ b/.api-reports/api-report-react.api.md @@ -1113,7 +1113,7 @@ interface MaskOperationOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -2454,7 +2454,7 @@ interface WatchQueryOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -1936,7 +1936,7 @@ interface WatchQueryOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -1856,7 +1856,7 @@ interface WatchQueryOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -1883,7 +1883,7 @@ export function withSubscription { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -2278,7 +2278,7 @@ interface WatchQueryOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -2341,7 +2341,7 @@ export function wrapQueryRef(inter // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts // src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:414:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:175:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:204:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" 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 b84e7b8395a..c93a3387a0a 100644 --- a/.api-reports/api-report-react_ssr.api.md +++ b/.api-reports/api-report-react_ssr.api.md @@ -958,7 +958,7 @@ interface MaskOperationOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -1841,7 +1841,7 @@ interface WatchQueryOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -1909,7 +1909,7 @@ export function withWarningSpy(it: (...args: TArgs // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts // src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:414:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:175:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:204:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" 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 1587195cdcd..9695c5890d4 100644 --- a/.api-reports/api-report-testing_core.api.md +++ b/.api-reports/api-report-testing_core.api.md @@ -946,7 +946,7 @@ interface MaskOperationOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -1866,7 +1866,7 @@ export function withWarningSpy(it: (...args: TArgs // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts // src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:414:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:175:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:204:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts diff --git a/.api-reports/api-report-utilities.api.md b/.api-reports/api-report-utilities.api.md index 75d2f60ab40..dd76c0acc5d 100644 --- a/.api-reports/api-report-utilities.api.md +++ b/.api-reports/api-report-utilities.api.md @@ -1650,7 +1650,7 @@ interface MaskOperationOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -2810,7 +2810,7 @@ interface WriteContext extends ReadMergeModifyContext { // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts // src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:414:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:175:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:204:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" 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 6862a8f0fb6..21a585568a5 100644 --- a/.api-reports/api-report.api.md +++ b/.api-reports/api-report.api.md @@ -1549,7 +1549,7 @@ interface MaskOperationOptions { // (undocumented) fetchPolicy?: WatchQueryFetchPolicy; // (undocumented) - queryId?: string; + id: string; } // @public (undocumented) @@ -3154,7 +3154,7 @@ interface WriteContext extends ReadMergeModifyContext { // src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts // src/core/QueryManager.ts:159:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:413:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:414:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:277:2 - (ae-forgotten-export) The symbol "UpdateQueryFn" needs to be exported by the entry point index.d.ts // src/link/http/selectHttpOptionsAndBody.ts:128:32 - (ae-forgotten-export) The symbol "HttpQueryOptions" needs to be exported by the entry point index.d.ts // src/react/hooks/useBackgroundQuery.ts:38:3 - (ae-forgotten-export) The symbol "SubscribeToMoreFunction" needs to be exported by the entry point index.d.ts diff --git a/.size-limits.json b/.size-limits.json index 67f553873ef..5a2f2b7fb59 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 41564, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34355 + "dist/apollo-client.min.cjs": 41580, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34368 }