Skip to content

Commit 8ae6e4e

Browse files
authored
[Data masking] Fix aggressive warnings when using objects marked with @unmask(mode: "migrate") with cache.identify (#12116)
1 parent e5258e5 commit 8ae6e4e

File tree

10 files changed

+203
-100
lines changed

10 files changed

+203
-100
lines changed

.api-reports/api-report-cache.api.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,9 +1109,9 @@ interface WriteContext extends ReadMergeModifyContext {
11091109
// Warnings were encountered during analysis:
11101110
//
11111111
// src/cache/core/cache.ts:92:7 - (ae-forgotten-export) The symbol "MaybeMasked" needs to be exported by the entry point index.d.ts
1112-
// src/cache/inmemory/policies.ts:92:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
1113-
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
1114-
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
1112+
// src/cache/inmemory/policies.ts:93:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
1113+
// src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
1114+
// src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
11151115
// src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
11161116

11171117
// (No @packageDocumentation comment for this package)

.api-reports/api-report-core.api.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2432,9 +2432,9 @@ interface WriteContext extends ReadMergeModifyContext {
24322432

24332433
// Warnings were encountered during analysis:
24342434
//
2435-
// src/cache/inmemory/policies.ts:92:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
2436-
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
2437-
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
2435+
// src/cache/inmemory/policies.ts:93:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
2436+
// src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
2437+
// src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
24382438
// src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
24392439
// src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts
24402440
// src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts

.api-reports/api-report-utilities.api.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2792,11 +2792,11 @@ interface WriteContext extends ReadMergeModifyContext {
27922792
// Warnings were encountered during analysis:
27932793
//
27942794
// 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
2795-
// src/cache/inmemory/policies.ts:57:3 - (ae-forgotten-export) The symbol "TypePolicy" needs to be exported by the entry point index.d.ts
2796-
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
2797-
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
2798-
// src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "FieldReadFunction" needs to be exported by the entry point index.d.ts
2799-
// src/cache/inmemory/policies.ts:163:3 - (ae-forgotten-export) The symbol "FieldMergeFunction" needs to be exported by the entry point index.d.ts
2795+
// src/cache/inmemory/policies.ts:58:3 - (ae-forgotten-export) The symbol "TypePolicy" needs to be exported by the entry point index.d.ts
2796+
// src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
2797+
// src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
2798+
// src/cache/inmemory/policies.ts:163:3 - (ae-forgotten-export) The symbol "FieldReadFunction" needs to be exported by the entry point index.d.ts
2799+
// src/cache/inmemory/policies.ts:164:3 - (ae-forgotten-export) The symbol "FieldMergeFunction" needs to be exported by the entry point index.d.ts
28002800
// src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
28012801
// src/cache/inmemory/writeToStore.ts:65:7 - (ae-forgotten-export) The symbol "MergeTree" needs to be exported by the entry point index.d.ts
28022802
// src/core/LocalState.ts:71:3 - (ae-forgotten-export) The symbol "ApolloClient" needs to be exported by the entry point index.d.ts

.api-reports/api-report.api.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3143,9 +3143,9 @@ interface WriteContext extends ReadMergeModifyContext {
31433143

31443144
// Warnings were encountered during analysis:
31453145
//
3146-
// src/cache/inmemory/policies.ts:92:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
3147-
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
3148-
// src/cache/inmemory/policies.ts:161:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
3146+
// src/cache/inmemory/policies.ts:93:3 - (ae-forgotten-export) The symbol "FragmentMap" needs to be exported by the entry point index.d.ts
3147+
// src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeySpecifier" needs to be exported by the entry point index.d.ts
3148+
// src/cache/inmemory/policies.ts:162:3 - (ae-forgotten-export) The symbol "KeyArgsFunction" needs to be exported by the entry point index.d.ts
31493149
// src/cache/inmemory/types.ts:139:3 - (ae-forgotten-export) The symbol "KeyFieldsFunction" needs to be exported by the entry point index.d.ts
31503150
// src/core/ObservableQuery.ts:120:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts
31513151
// src/core/ObservableQuery.ts:121:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts

.changeset/early-bobcats-eat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/client": patch
3+
---
4+
5+
Prevent field accessor warnings when using `@unmask(mode: "migrate")` on objects that are passed into `cache.identify`.

.size-limits.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"dist/apollo-client.min.cjs": 41506,
3-
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34257
2+
"dist/apollo-client.min.cjs": 41516,
3+
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 34296
44
}

src/__tests__/dataMasking.ts

Lines changed: 133 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,73 @@ describe("client.watchQuery", () => {
11431143
}
11441144
});
11451145

1146+
// https://github.com/apollographql/apollo-client/issues/12043
1147+
test("does not warn when passing @unmask(mode: 'migrate') object to cache.identify", async () => {
1148+
using consoleSpy = spyOnConsole("warn");
1149+
1150+
type UserFieldsFragment = {
1151+
age: number;
1152+
} & { " $fragmentName"?: "UserFieldsFragment" };
1153+
1154+
interface Query {
1155+
currentUser: {
1156+
__typename: "User";
1157+
id: number;
1158+
name: string;
1159+
/** @deprecated */
1160+
age: number;
1161+
} & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } };
1162+
}
1163+
1164+
const query: MaskedDocumentNode<Query, never> = gql`
1165+
query UnmaskedQuery {
1166+
currentUser {
1167+
id
1168+
name
1169+
...UserFields @unmask(mode: "migrate")
1170+
}
1171+
}
1172+
1173+
fragment UserFields on User {
1174+
age
1175+
name
1176+
}
1177+
`;
1178+
1179+
const mocks = [
1180+
{
1181+
request: { query },
1182+
result: {
1183+
data: {
1184+
currentUser: {
1185+
__typename: "User",
1186+
id: 1,
1187+
name: "Test User",
1188+
age: 34,
1189+
},
1190+
},
1191+
},
1192+
delay: 20,
1193+
},
1194+
];
1195+
1196+
const client = new ApolloClient({
1197+
dataMasking: true,
1198+
cache: new InMemoryCache(),
1199+
link: new MockLink(mocks),
1200+
});
1201+
1202+
const observable = client.watchQuery({ query });
1203+
const stream = new ObservableStream(observable);
1204+
1205+
const { data } = await stream.takeNext();
1206+
1207+
const id = client.cache.identify(data.currentUser);
1208+
1209+
expect(consoleSpy.warn).not.toHaveBeenCalled();
1210+
expect(id).toEqual("User:1");
1211+
});
1212+
11461213
test("reads fragment by passing parent object to `from`", async () => {
11471214
type UserFieldsFragment = {
11481215
age: number;
@@ -3112,79 +3179,84 @@ describe("client.watchFragment", () => {
31123179
});
31133180
});
31143181

3115-
// FIXME: This broke with the changes in https://github.com/apollographql/apollo-client/pull/12114
3116-
// which ensure masking works with deferred payloads. Instead of fixing with
3117-
// #12114, it will be fixed with https://github.com/apollographql/apollo-client/issues/12043
3118-
// which will fix overagressive warnings.
3119-
test.failing(
3120-
"warns when accessing an unmasked field on a watched fragment while using @unmask with mode: 'migrate'",
3121-
async () => {
3122-
using consoleSpy = spyOnConsole("warn");
3182+
test("warns when accessing an unmasked field on a watched fragment while using @unmask with mode: 'migrate'", async () => {
3183+
using consoleSpy = spyOnConsole("warn");
31233184

3124-
type ProfileFieldsFragment = {
3125-
__typename: "User";
3126-
age: number;
3127-
name: string;
3128-
} & { " $fragmentName": "UserFieldsFragment" };
3185+
type ProfileFieldsFragment = {
3186+
__typename: "User";
3187+
age: number;
3188+
name: string;
3189+
} & { " $fragmentName": "UserFieldsFragment" };
31293190

3130-
type UserFieldsFragment = {
3131-
__typename: "User";
3132-
id: number;
3133-
name: string;
3134-
/** @deprecated */
3135-
age: number;
3136-
} & { " $fragmentName": "UserFieldsFragment" } & {
3137-
" $fragmentRefs": { ProfileFieldsFragment: ProfileFieldsFragment };
3138-
};
3191+
type UserFieldsFragment = {
3192+
__typename: "User";
3193+
id: number;
3194+
name: string;
3195+
/** @deprecated */
3196+
age: number;
3197+
} & { " $fragmentName": "UserFieldsFragment" } & {
3198+
" $fragmentRefs": { ProfileFieldsFragment: ProfileFieldsFragment };
3199+
};
31393200

3140-
const fragment: MaskedDocumentNode<UserFieldsFragment, never> = gql`
3141-
fragment UserFields on User {
3142-
id
3143-
name
3144-
...ProfileFields @unmask(mode: "migrate")
3145-
}
3201+
const fragment: MaskedDocumentNode<UserFieldsFragment, never> = gql`
3202+
fragment UserFields on User {
3203+
id
3204+
name
3205+
...ProfileFields @unmask(mode: "migrate")
3206+
}
31463207
3147-
fragment ProfileFields on User {
3148-
age
3149-
name
3150-
}
3151-
`;
3208+
fragment ProfileFields on User {
3209+
age
3210+
name
3211+
}
3212+
`;
31523213

3153-
const client = new ApolloClient({
3154-
dataMasking: true,
3155-
cache: new InMemoryCache(),
3156-
});
3214+
const client = new ApolloClient({
3215+
dataMasking: true,
3216+
cache: new InMemoryCache(),
3217+
});
31573218

3158-
const observable = client.watchFragment({
3159-
fragment,
3160-
fragmentName: "UserFields",
3161-
from: { __typename: "User", id: 1 },
3162-
});
3163-
const stream = new ObservableStream(observable);
3219+
client.writeFragment({
3220+
id: client.cache.identify({ __typename: "User", id: 1 }),
3221+
fragment,
3222+
fragmentName: "UserFields",
3223+
data: {
3224+
__typename: "User",
3225+
id: 1,
3226+
age: 30,
3227+
name: "Test User",
3228+
},
3229+
});
31643230

3165-
{
3166-
const { data } = await stream.takeNext();
3167-
data.__typename;
3168-
data.id;
3169-
data.name;
3231+
const observable = client.watchFragment({
3232+
fragment,
3233+
fragmentName: "UserFields",
3234+
from: { __typename: "User", id: 1 },
3235+
});
3236+
const stream = new ObservableStream(observable);
31703237

3171-
expect(consoleSpy.warn).not.toHaveBeenCalled();
3238+
{
3239+
const { data } = await stream.takeNext();
3240+
data.__typename;
3241+
data.id;
3242+
data.name;
31723243

3173-
data.age;
3244+
expect(consoleSpy.warn).not.toHaveBeenCalled();
31743245

3175-
expect(consoleSpy.warn).toHaveBeenCalledTimes(1);
3176-
expect(consoleSpy.warn).toHaveBeenCalledWith(
3177-
"Accessing unmasked field on %s at path '%s'. This field will not be available when masking is enabled. Please read the field from the fragment instead.",
3178-
"fragment 'UserFields'",
3179-
"age"
3180-
);
3246+
data.age;
31813247

3182-
// Ensure we only warn once
3183-
data.age;
3184-
expect(consoleSpy.warn).toHaveBeenCalledTimes(1);
3185-
}
3248+
expect(consoleSpy.warn).toHaveBeenCalledTimes(1);
3249+
expect(consoleSpy.warn).toHaveBeenCalledWith(
3250+
"Accessing unmasked field on %s at path '%s'. This field will not be available when masking is enabled. Please read the field from the fragment instead.",
3251+
"fragment 'UserFields'",
3252+
"age"
3253+
);
3254+
3255+
// Ensure we only warn once
3256+
data.age;
3257+
expect(consoleSpy.warn).toHaveBeenCalledTimes(1);
31863258
}
3187-
);
3259+
});
31883260

31893261
test("can lookup unmasked fragments from the fragment registry in watched fragments", async () => {
31903262
const fragments = createFragmentRegistry();

src/cache/inmemory/policies.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
keyArgsFnFromSpecifier,
5353
keyFieldsFnFromSpecifier,
5454
} from "./key-extractor.js";
55+
import { disableWarningsSlot } from "../../core/masking.js";
5556

5657
export type TypePolicies = {
5758
[__typename: string]: TypePolicy;
@@ -391,15 +392,18 @@ export class Policies {
391392

392393
const policy = typename && this.getTypePolicy(typename);
393394
let keyFn = (policy && policy.keyFn) || this.config.dataIdFromObject;
394-
while (keyFn) {
395-
const specifierOrId = keyFn({ ...object, ...storeObject }, context);
396-
if (isArray(specifierOrId)) {
397-
keyFn = keyFieldsFnFromSpecifier(specifierOrId);
398-
} else {
399-
id = specifierOrId;
400-
break;
395+
396+
disableWarningsSlot.withValue(true, () => {
397+
while (keyFn) {
398+
const specifierOrId = keyFn({ ...object, ...storeObject }, context);
399+
if (isArray(specifierOrId)) {
400+
keyFn = keyFieldsFnFromSpecifier(specifierOrId);
401+
} else {
402+
id = specifierOrId;
403+
break;
404+
}
401405
}
402-
}
406+
});
403407

404408
id = id ? String(id) : void 0;
405409
return context.keyObject ? [id, context.keyObject] : [id];

src/core/__tests__/masking.test.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2244,7 +2244,7 @@ describe("maskFragment", () => {
22442244

22452245
test("warns when accessing unmasked fields when using `@unmask` directive with mode 'migrate'", () => {
22462246
using _ = spyOnConsole("warn");
2247-
const query = gql`
2247+
const fragment = gql`
22482248
fragment UnmaskedFragment on User {
22492249
id
22502250
name
@@ -2258,18 +2258,22 @@ describe("maskFragment", () => {
22582258

22592259
const data = maskFragment(
22602260
deepFreeze({
2261-
currentUser: {
2262-
__typename: "User",
2263-
id: 1,
2264-
name: "Test User",
2265-
age: 30,
2266-
},
2261+
__typename: "User",
2262+
id: 1,
2263+
name: "Test User",
2264+
age: 30,
22672265
}),
2268-
query,
2266+
fragment,
22692267
new InMemoryCache(),
22702268
"UnmaskedFragment"
22712269
);
22722270

2271+
data.__typename;
2272+
data.id;
2273+
data.name;
2274+
2275+
expect(console.warn).not.toHaveBeenCalled();
2276+
22732277
data.age;
22742278

22752279
expect(console.warn).toHaveBeenCalledTimes(1);

0 commit comments

Comments
 (0)