diff --git a/.api-reports/api-report-cache.api.md b/.api-reports/api-report-cache.api.md index 452c2e2e068..db5ac4718be 100644 --- a/.api-reports/api-report-cache.api.md +++ b/.api-reports/api-report-cache.api.md @@ -30,7 +30,7 @@ export abstract class ApolloCache implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -40,9 +40,7 @@ export abstract class ApolloCache implements DataProxy { // (undocumented) identify(object: StoreObject | Reference): string | undefined; // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // (undocumented) @@ -583,7 +581,7 @@ export class InMemoryCache extends ApolloCache { // (undocumented) extract(optimistic?: boolean): NormalizedCacheObject; // (undocumented) - protected fragmentMatches(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(options?: { resetResultCache?: boolean; @@ -596,6 +594,8 @@ export class InMemoryCache extends ApolloCache { // (undocumented) identify(object: StoreObject | Reference): string | undefined; // (undocumented) + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; + // (undocumented) readonly makeVar: typeof makeVar; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; @@ -704,13 +704,6 @@ export function makeReference(id: string): Reference; // @public (undocumented) export function makeVar(value: T): ReactiveVar; -// @public (undocumented) -export interface MaskFragmentOptions { - data: TData; - fragment: DocumentNode | TypedDocumentNode; - fragmentName?: string; -} - // @public (undocumented) export interface MergeInfo { // (undocumented) diff --git a/.api-reports/api-report-core.api.md b/.api-reports/api-report-core.api.md index 3771973942a..c905b809684 100644 --- a/.api-reports/api-report-core.api.md +++ b/.api-reports/api-report-core.api.md @@ -45,7 +45,7 @@ export abstract class ApolloCache implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -54,12 +54,8 @@ export abstract class ApolloCache implements DataProxy { getMemoryInternals?: typeof getApolloCacheMemoryInternals; // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // (undocumented) @@ -1113,7 +1109,7 @@ export class InMemoryCache extends ApolloCache { // (undocumented) extract(optimistic?: boolean): NormalizedCacheObject; // (undocumented) - protected fragmentMatches(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(options?: { resetResultCache?: boolean; @@ -1126,6 +1122,8 @@ export class InMemoryCache extends ApolloCache { // (undocumented) identify(object: StoreObject | Reference): string | undefined; // (undocumented) + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; + // (undocumented) readonly makeVar: typeof makeVar; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; @@ -1317,11 +1315,22 @@ export function makeVar(value: T): ReactiveVar; // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1857,8 +1866,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -2343,8 +2358,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:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:144:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:389:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:152:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:397:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:275: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 874aa41ca44..3833078e887 100644 --- a/.api-reports/api-report-react.api.md +++ b/.api-reports/api-report-react.api.md @@ -41,7 +41,7 @@ abstract class ApolloCache implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -52,12 +52,8 @@ abstract class ApolloCache implements DataProxy { // // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -1074,11 +1070,22 @@ type LocalStateOptions = { // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1635,8 +1642,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -2367,8 +2380,8 @@ interface WatchQueryOptions implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -52,12 +52,8 @@ abstract class ApolloCache implements DataProxy { // // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -935,11 +931,22 @@ type LocalStateOptions = { // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1449,8 +1456,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -1846,8 +1859,8 @@ interface WatchQueryOptions implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -51,12 +51,8 @@ abstract class ApolloCache implements DataProxy { // // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -932,11 +928,22 @@ type LocalStateOptions = { // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1377,8 +1384,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -1766,8 +1779,8 @@ interface WatchQueryOptions implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -51,12 +51,8 @@ abstract class ApolloCache implements DataProxy { // // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -939,11 +935,22 @@ type LocalStateOptions = { // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1422,8 +1429,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -1793,8 +1806,8 @@ export function withSubscription implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -50,12 +50,8 @@ abstract class ApolloCache implements DataProxy { // // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -1023,11 +1019,22 @@ type LocalStateOptions = { // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1504,8 +1511,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -2191,8 +2204,8 @@ interface WatchQueryOptions implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -50,12 +50,8 @@ abstract class ApolloCache implements DataProxy { // // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -1033,11 +1029,22 @@ type LocalStateOptions = { // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1555,8 +1562,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -2254,8 +2267,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:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:144:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:389:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:152:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:397:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:203:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:275: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 1d6c2847a90..15c9ebcf378 100644 --- a/.api-reports/api-report-react_ssr.api.md +++ b/.api-reports/api-report-react_ssr.api.md @@ -40,7 +40,7 @@ abstract class ApolloCache implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -51,12 +51,8 @@ abstract class ApolloCache implements DataProxy { // // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -917,11 +913,22 @@ type LocalStateOptions = { // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1362,8 +1369,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -1751,8 +1764,8 @@ interface WatchQueryOptions implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -51,12 +51,8 @@ abstract class ApolloCache implements DataProxy { // // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -906,11 +902,22 @@ type LocalStateOptions = { // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1443,8 +1450,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -1819,8 +1832,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:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:144:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:389:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:152:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:397:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:203:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:275: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 1d58d41f20e..65672566046 100644 --- a/.api-reports/api-report-testing_core.api.md +++ b/.api-reports/api-report-testing_core.api.md @@ -39,7 +39,7 @@ abstract class ApolloCache implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -50,12 +50,8 @@ abstract class ApolloCache implements DataProxy { // // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -905,11 +901,22 @@ type LocalStateOptions = { // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -1400,8 +1407,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -1776,8 +1789,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:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:144:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:389:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:152:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:397:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:203:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:275: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 df1df1fb86e..9b9718a06f6 100644 --- a/.api-reports/api-report-utilities.api.md +++ b/.api-reports/api-report-utilities.api.md @@ -56,7 +56,7 @@ abstract class ApolloCache implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -65,12 +65,8 @@ abstract class ApolloCache implements DataProxy { getMemoryInternals?: typeof getApolloCacheMemoryInternals; // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // Warning: (ae-forgotten-export) The symbol "Transaction" needs to be exported by the entry point index.d.ts @@ -1327,7 +1323,7 @@ class InMemoryCache extends ApolloCache { // (undocumented) extract(optimistic?: boolean): NormalizedCacheObject; // (undocumented) - protected fragmentMatches(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(options?: { resetResultCache?: boolean; @@ -1339,6 +1335,8 @@ class InMemoryCache extends ApolloCache { getMemoryInternals?: typeof getInMemoryCacheMemoryInternals; // (undocumented) identify(object: StoreObject | Reference): string | undefined; + // (undocumented) + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // Warning: (ae-forgotten-export) The symbol "makeVar" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1602,11 +1600,22 @@ function makeVar(value: T): ReactiveVar; // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) export function maybe(thunk: () => T): T | undefined; @@ -2168,8 +2177,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -2711,8 +2726,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:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:144:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:389:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:152:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:397:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/types.ts:174:3 - (ae-forgotten-export) The symbol "MutationQueryReducer" needs to be exported by the entry point index.d.ts // src/core/types.ts:203:5 - (ae-forgotten-export) The symbol "Resolver" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:275: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 e4513503f9e..c1b395ccdf1 100644 --- a/.api-reports/api-report.api.md +++ b/.api-reports/api-report.api.md @@ -47,7 +47,7 @@ export abstract class ApolloCache implements DataProxy { abstract evict(options: Cache_2.EvictOptions): boolean; abstract extract(optimistic?: boolean): TSerialized; // (undocumented) - protected fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches?(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(): string[]; // Warning: (ae-forgotten-export) The symbol "getApolloCacheMemoryInternals" needs to be exported by the entry point index.d.ts @@ -56,12 +56,8 @@ export abstract class ApolloCache implements DataProxy { getMemoryInternals?: typeof getApolloCacheMemoryInternals; // (undocumented) identify(object: StoreObject | Reference): string | undefined; - // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) - maskFragment(options: MaskFragmentOptions): TData; - // (undocumented) - maskOperation(document: DocumentNode, data: TData): TData; + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; // (undocumented) @@ -1247,7 +1243,7 @@ export class InMemoryCache extends ApolloCache { // (undocumented) extract(optimistic?: boolean): NormalizedCacheObject; // (undocumented) - protected fragmentMatches(fragment: InlineFragmentNode, typename: string): boolean; + fragmentMatches(fragment: InlineFragmentNode, typename: string): boolean; // (undocumented) gc(options?: { resetResultCache?: boolean; @@ -1260,6 +1256,8 @@ export class InMemoryCache extends ApolloCache { // (undocumented) identify(object: StoreObject | Reference): string | undefined; // (undocumented) + lookupFragment(fragmentName: string): FragmentDefinitionNode | null; + // (undocumented) readonly makeVar: typeof makeVar; // (undocumented) modify = Record>(options: Cache_2.ModifyOptions): boolean; @@ -1498,11 +1496,22 @@ export function makeVar(value: T): ReactiveVar; // @public (undocumented) interface MaskFragmentOptions { + // (undocumented) data: TData; - fragment: DocumentNode | TypedDocumentNode; + // (undocumented) + fragment: DocumentNode; + // (undocumented) fragmentName?: string; } +// @public (undocumented) +interface MaskOperationOptions { + // (undocumented) + data: TData; + // (undocumented) + document: DocumentNode; +} + // @public (undocumented) type MaybeAsync = T | PromiseLike; @@ -2208,8 +2217,14 @@ class QueryManager { onQueryUpdated?: OnQueryUpdated; keepRootFields?: boolean; }, cache?: ApolloCache): Promise>; + // Warning: (ae-forgotten-export) The symbol "MaskFragmentOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) maskFragment(options: MaskFragmentOptions): TData; + // Warning: (ae-forgotten-export) The symbol "MaskOperationOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + maskOperation(options: MaskOperationOptions): TData; // (undocumented) mutate, TCache extends ApolloCache>({ mutation, variables, optimisticResponse, updateQueries, refetchQueries, awaitRefetchQueries, update: updateWithProxyFn, onQueryUpdated, fetchPolicy, errorPolicy, keepRootFields, context, }: MutationOptions): Promise>; // (undocumented) @@ -3058,8 +3073,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:116:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts // src/core/ObservableQuery.ts:117:5 - (ae-forgotten-export) The symbol "QueryInfo" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:144:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts -// src/core/QueryManager.ts:389:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:152:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts +// src/core/QueryManager.ts:397:7 - (ae-forgotten-export) The symbol "UpdateQueries" needs to be exported by the entry point index.d.ts // src/core/watchQueryOptions.ts:275: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 ff40a03e55a..b2800c3acfe 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 41174, + "dist/apollo-client.min.cjs": 41255, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 33943 } diff --git a/src/__tests__/dataMasking.ts b/src/__tests__/dataMasking.ts index 27e9f19ebfd..d019ff1e74d 100644 --- a/src/__tests__/dataMasking.ts +++ b/src/__tests__/dataMasking.ts @@ -2,18 +2,21 @@ import { FragmentSpreadNode, Kind, visit } from "graphql"; import { ApolloCache, ApolloClient, + ApolloLink, Cache, DataProxy, DocumentTransform, FetchPolicy, gql, InMemoryCache, + Observable, Reference, TypedDocumentNode, } from "../core"; import { MockLink } from "../testing"; import { ObservableStream, spyOnConsole } from "../testing/internal"; import { invariant } from "../utilities/globals"; +import { createFragmentRegistry } from "../cache/inmemory/fragmentRegistry"; test("masks queries when dataMasking is `true`", async () => { interface Query { @@ -411,7 +414,9 @@ test("does not mask query when using a cache that does not support it", async () expect(console.warn).toHaveBeenCalledTimes(1); expect(console.warn).toHaveBeenCalledWith( - expect.stringContaining("This cache does not support data masking") + expect.stringContaining( + "The configured cache does not support data masking" + ) ); }); @@ -1300,6 +1305,67 @@ test("warns when passing parent object to `from` that is non-normalized", async } }); +test("can lookup unmasked fragments from the fragment registry in queries", async () => { + const fragments = createFragmentRegistry(); + + interface Query { + currentUser: { + __typename: "User"; + id: number; + name: string; + age: number; + }; + } + + const query: TypedDocumentNode = gql` + query MaskedQuery { + currentUser { + id + name + ...UserFields @unmask + } + } + `; + + fragments.register(gql` + fragment UserFields on User { + age + } + `); + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache({ fragments }), + link: new ApolloLink(() => { + return Observable.of({ + data: { + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }, + }); + }), + }); + + const stream = new ObservableStream(client.watchQuery({ query })); + + { + const { data } = await stream.takeNext(); + + expect(data).toEqual({ + currentUser: { + __typename: "User", + id: 1, + name: "Test User", + age: 30, + }, + }); + } +}); + test("masks watched fragments when dataMasking is `true`", async () => { type UserFieldsFragment = { __typename: "User"; @@ -2175,6 +2241,61 @@ test("warns when accessing an unmasked field on a watched fragment while using @ } }); +test("can lookup unmasked fragments from the fragment registry in watched fragments", async () => { + const fragments = createFragmentRegistry(); + + const profileFieldsFragment = gql` + fragment ProfileFields on User { + age + } + `; + + const userFieldsFragment = gql` + fragment UserFields on User { + id + ...ProfileFields @unmask + } + `; + + fragments.register(profileFieldsFragment); + + const client = new ApolloClient({ + dataMasking: true, + cache: new InMemoryCache({ fragments }), + }); + + client.writeFragment({ + fragment: userFieldsFragment, + fragmentName: "UserFields", + data: { + __typename: "User", + id: 1, + age: 30, + }, + }); + + const observable = client.watchFragment({ + fragment: userFieldsFragment, + fragmentName: "UserFields", + from: { __typename: "User", id: 1 }, + }); + + const stream = new ObservableStream(observable); + + { + const result = await stream.takeNext(); + + expect(result).toEqual({ + data: { + __typename: "User", + id: 1, + age: 30, + }, + complete: true, + }); + } +}); + class TestCache extends ApolloCache { public diff(query: Cache.DiffOptions): DataProxy.DiffResult { return {}; diff --git a/src/cache/core/__tests__/cache.ts b/src/cache/core/__tests__/cache.ts index 5732cc14d5e..59744928535 100644 --- a/src/cache/core/__tests__/cache.ts +++ b/src/cache/core/__tests__/cache.ts @@ -3,8 +3,6 @@ import { ApolloCache } from "../cache"; import { Cache, DataProxy } from "../.."; import { Reference } from "../../../utilities/graphql/storeUtils"; import { expectTypeOf } from "expect-type"; -import { spyOnConsole } from "../../../testing/internal"; -import type { InlineFragmentNode } from "graphql"; class TestCache extends ApolloCache { constructor() { @@ -164,143 +162,6 @@ describe("abstract cache", () => { }); }); - describe("maskDocument", () => { - it("warns on caches that don't implement the required interface and returns original data", () => { - using consoleSpy = spyOnConsole("warn"); - const query = gql` - query { - user { - __typename - id - ...UserFields - } - } - - fragment UserFields on User { - name - } - `; - const data = { - user: { __typename: "User", id: 1, name: "Mister Masked" }, - }; - const cache = new TestCache(); - - const result = cache.maskOperation(query, data); - - expect(result).toBe(data); - expect(consoleSpy.warn).toHaveBeenCalledTimes(1); - expect(consoleSpy.warn).toHaveBeenCalledWith( - expect.stringContaining("does not support data masking") - ); - }); - - it("returns masked query for caches that implement required interface", () => { - class MaskingCache extends TestCache { - protected fragmentMatches( - _fragment: InlineFragmentNode, - _typename: string - ): boolean { - return true; - } - } - - const cache = new MaskingCache(); - - const query = gql` - query { - user { - __typename - id - ...UserFields - } - } - - fragment UserFields on User { - name - } - `; - const data = { - user: { __typename: "User", id: 1, name: "Mister Masked" }, - }; - - const result = cache.maskOperation(query, data); - - expect(result).toEqual({ - user: { __typename: "User", id: 1 }, - }); - }); - }); - - describe("maskFragment", () => { - it("returns original data and warns on caches that don't implement the required interface", () => { - using consoleSpy = spyOnConsole("warn"); - - const fragment = gql` - fragment UserFields on User { - id - ...NameFields - } - - fragment NameFields on User { - name - } - `; - const data = { - __typename: "User", - id: 1, - name: "Mister Masked", - }; - const cache = new TestCache(); - - const result = cache.maskFragment({ fragment, data }); - - expect(result).toBe(data); - expect(consoleSpy.warn).toHaveBeenCalledTimes(1); - expect(consoleSpy.warn).toHaveBeenCalledWith( - expect.stringContaining("does not support data masking") - ); - }); - - it("returns masked fragment for caches that implement required interface", () => { - class MaskingCache extends TestCache { - protected fragmentMatches( - _fragment: InlineFragmentNode, - _typename: string - ): boolean { - return true; - } - } - - const cache = new MaskingCache(); - - const fragment = gql` - fragment UserFields on User { - __typename - id - ...NameFields - } - - fragment NameFields on User { - name - } - `; - - const data = { - __typename: "User", - id: 1, - name: "Mister Masked", - }; - - const result = cache.maskFragment({ - fragment, - data, - fragmentName: "UserFields", - }); - - expect(result).toEqual({ __typename: "User", id: 1 }); - }); - }); - describe("updateQuery", () => { it("runs the readQuery & writeQuery methods", () => { const test = new TestCache(); diff --git a/src/cache/core/cache.ts b/src/cache/core/cache.ts index 54a07c48c15..7cc13df2f94 100644 --- a/src/cache/core/cache.ts +++ b/src/cache/core/cache.ts @@ -1,4 +1,8 @@ -import type { DocumentNode, InlineFragmentNode } from "graphql"; +import type { + DocumentNode, + FragmentDefinitionNode, + InlineFragmentNode, +} from "graphql"; import { wrap } from "optimism"; import type { @@ -24,8 +28,8 @@ import type { } from "../../core/types.js"; import type { MissingTree } from "./types/common.js"; import { equalByQuery } from "../../core/equalByQuery.js"; -import { maskFragment, maskOperation } from "../../core/masking.js"; import { invariant } from "../../utilities/globals/index.js"; +import { maskFragment } from "../../core/masking.js"; export type Transaction = (c: ApolloCache) => void; @@ -74,31 +78,6 @@ export interface WatchFragmentOptions { optimistic?: boolean; } -export interface MaskFragmentOptions { - /** - * A GraphQL fragment document parsed into an AST with the `gql` - * template literal. - * - * @docGroup 1. Required options - */ - fragment: DocumentNode | TypedDocumentNode; - /** - * The raw, unmasked data that should be masked. - * - * @docGroup 1. Required options - */ - data: TData; - /** - * The name of the fragment defined in the fragment document. - * - * Required if the fragment document includes more than one fragment, - * optional otherwise. - * - * @docGroup 2. Cache options - */ - fragmentName?: string; -} - /** * Watched fragment results. */ @@ -170,11 +149,18 @@ export abstract class ApolloCache implements DataProxy { // If not implemented by a cache subclass, data masking will effectively be // disabled since we will not be able to accurately determine if a given type // condition for a union or interface matches a particular type. - protected fragmentMatches?( + public fragmentMatches?( fragment: InlineFragmentNode, typename: string ): boolean; + // Function used to lookup a fragment when a fragment definition is not part + // of the GraphQL document. This is useful for caches, such as InMemoryCache, + // that register fragments ahead of time so they can be referenced by name. + public lookupFragment(fragmentName: string): FragmentDefinitionNode | null { + return null; + } + // Transactional API // The batch method is intended to replace/subsume both performTransaction @@ -267,6 +253,7 @@ export abstract class ApolloCache implements DataProxy { } = options; const query = this.getFragmentDoc(fragment, fragmentName); const id = typeof from === "string" ? from : this.identify(from); + const dataMasking = !!(options as any)[Symbol.for("apollo.dataMasking")]; if (__DEV__) { const actualFragmentName = @@ -294,21 +281,22 @@ export abstract class ApolloCache implements DataProxy { return this.watch({ ...diffOptions, immediate: true, - callback(diff) { + callback: (diff) => { + const data = + dataMasking ? + maskFragment(diff.result, fragment, this, fragmentName) + : diff.result; + if ( // Always ensure we deliver the first result latestDiff && - equalByQuery( - query, - { data: latestDiff?.result }, - { data: diff.result } - ) + equalByQuery(query, { data: latestDiff?.result }, { data }) ) { return; } const result = { - data: diff.result as DeepPartial, + data, complete: !!diff.complete, } as WatchFragmentResult; @@ -318,7 +306,7 @@ export abstract class ApolloCache implements DataProxy { ); } - latestDiff = diff; + latestDiff = { ...diff, result: data }; observer.next(result); }, }); @@ -405,41 +393,6 @@ export abstract class ApolloCache implements DataProxy { }); } - public maskOperation(document: DocumentNode, data: TData) { - if (!this.fragmentMatches) { - if (__DEV__) { - invariant.warn( - "This cache does not support data masking which effectively disables it. Please use a cache that supports data masking or disable data masking to silence this warning." - ); - } - - return data; - } - - return maskOperation(data, document, this.fragmentMatches.bind(this)); - } - - public maskFragment(options: MaskFragmentOptions) { - const { data, fragment, fragmentName } = options; - - if (!this.fragmentMatches) { - if (__DEV__) { - invariant.warn( - "This cache does not support data masking which effectively disables it. Please use a cache that supports data masking or disable data masking to silence this warning." - ); - } - - return data; - } - - return maskFragment( - data, - fragment, - this.fragmentMatches.bind(this), - fragmentName - ); - } - /** * @experimental * @internal diff --git a/src/cache/index.ts b/src/cache/index.ts index 92a5315b6b7..1f55d8c16df 100644 --- a/src/cache/index.ts +++ b/src/cache/index.ts @@ -4,7 +4,6 @@ export type { Transaction, WatchFragmentOptions, WatchFragmentResult, - MaskFragmentOptions, } from "./core/cache.js"; export { ApolloCache } from "./core/cache.js"; export { Cache } from "./core/types/Cache.js"; diff --git a/src/cache/inmemory/inMemoryCache.ts b/src/cache/inmemory/inMemoryCache.ts index 9d6413b60a4..7be5b921ab5 100644 --- a/src/cache/inmemory/inMemoryCache.ts +++ b/src/cache/inmemory/inMemoryCache.ts @@ -3,7 +3,11 @@ import { invariant } from "../../utilities/globals/index.js"; // Make builtins like Map and Set safe to use with non-extensible objects. import "./fixPolyfills.js"; -import type { DocumentNode, InlineFragmentNode } from "graphql"; +import type { + DocumentNode, + FragmentDefinitionNode, + InlineFragmentNode, +} from "graphql"; import type { OptimisticWrapperFunction } from "optimism"; import { wrap } from "optimism"; import { equal } from "@wry/equality"; @@ -528,13 +532,17 @@ export class InMemoryCache extends ApolloCache { return this.addTypenameToDocument(this.addFragmentsToDocument(document)); } - protected fragmentMatches( + public fragmentMatches( fragment: InlineFragmentNode, typename: string ): boolean { return this.policies.fragmentMatches(fragment, typename); } + public lookupFragment(fragmentName: string): FragmentDefinitionNode | null { + return this.config.fragments?.lookup(fragmentName) || null; + } + protected broadcastWatches(options?: BroadcastOptions) { if (!this.txCount) { this.watches.forEach((c) => this.maybeBroadcastWatch(c, options)); diff --git a/src/core/ApolloClient.ts b/src/core/ApolloClient.ts index fd6127e930b..765ebd512c6 100644 --- a/src/core/ApolloClient.ts +++ b/src/core/ApolloClient.ts @@ -6,7 +6,7 @@ import type { FetchResult, GraphQLRequest } from "../link/core/index.js"; import { ApolloLink, execute } from "../link/core/index.js"; import type { ApolloCache, DataProxy, Reference } from "../cache/index.js"; import type { DocumentTransform } from "../utilities/index.js"; -import { Observable } from "../utilities/index.js"; +import type { Observable } from "../utilities/index.js"; import { version } from "../version.js"; import type { UriFunction } from "../link/http/index.js"; import { HttpLink } from "../link/http/index.js"; @@ -164,7 +164,6 @@ import type { WatchFragmentOptions, WatchFragmentResult, } from "../cache/core/cache.js"; -import { equalByQuery } from "./equalByQuery.js"; export { mergeOptions }; /** @@ -548,39 +547,9 @@ export class ApolloClient implements DataProxy { >( options: WatchFragmentOptions ): Observable> { - const { fragment, fragmentName } = options; - - const observable = this.cache.watchFragment(options); - let latestResult: WatchFragmentResult | undefined; - - return new Observable((observer) => { - const subscription = observable.subscribe({ - next: (result) => { - result.data = this.queryManager.maskFragment({ - fragment, - fragmentName, - data: result.data, - }); - - if ( - latestResult && - equalByQuery( - this.cache["getFragmentDoc"](fragment, fragmentName), - { data: latestResult.data }, - { data: result.data } - ) - ) { - return; - } - - latestResult = result; - observer.next(result); - }, - complete: observer.complete.bind(observer), - error: observer.error.bind(observer), - }); - - return () => subscription.unsubscribe(); + return this.cache.watchFragment({ + ...options, + [Symbol.for("apollo.dataMasking")]: this.queryManager.dataMasking, }); } diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts index 5fe352171f6..68cfade9b7c 100644 --- a/src/core/ObservableQuery.ts +++ b/src/core/ObservableQuery.ts @@ -1110,11 +1110,7 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`, } private maskQuery(data: TData) { - const { queryManager } = this; - - return queryManager.dataMasking ? - queryManager.cache.maskOperation(this.query, data) - : data; + return this.queryManager.maskOperation({ document: this.query, data }); } } diff --git a/src/core/QueryManager.ts b/src/core/QueryManager.ts index 450a72fdd6a..07e32e94abe 100644 --- a/src/core/QueryManager.ts +++ b/src/core/QueryManager.ts @@ -14,11 +14,7 @@ import { isExecutionPatchResult, removeDirectivesFromDocument, } from "../utilities/index.js"; -import type { - Cache, - ApolloCache, - MaskFragmentOptions, -} from "../cache/index.js"; +import type { Cache, ApolloCache } from "../cache/index.js"; import { canonicalStringify } from "../cache/index.js"; import type { @@ -108,6 +104,18 @@ interface TransformCacheEntry { import type { DefaultOptions } from "./ApolloClient.js"; import { Trie } from "@wry/trie"; import { AutoCleanedWeakCache, cacheSizes } from "../utilities/index.js"; +import { maskFragment, maskOperation } from "./masking.js"; + +interface MaskFragmentOptions { + fragment: DocumentNode; + data: TData; + fragmentName?: string; +} + +interface MaskOperationOptions { + document: DocumentNode; + data: TData; +} export interface QueryManagerOptions { cache: ApolloCache; @@ -1524,8 +1532,18 @@ export class QueryManager { return results; } + public maskOperation(options: MaskOperationOptions) { + const { document, data } = options; + + return this.dataMasking ? maskOperation(data, document, this.cache) : data; + } + public maskFragment(options: MaskFragmentOptions) { - return this.dataMasking ? this.cache.maskFragment(options) : options.data; + const { data, fragment, fragmentName } = options; + + return this.dataMasking ? + maskFragment(data, fragment, this.cache, fragmentName) + : data; } private fetchQueryByPolicy( diff --git a/src/core/__tests__/masking.test.ts b/src/core/__tests__/masking.test.ts index ce4cb237ae7..3eaa1bdb5cb 100644 --- a/src/core/__tests__/masking.test.ts +++ b/src/core/__tests__/masking.test.ts @@ -1,6 +1,5 @@ import { maskFragment, maskOperation } from "../masking.js"; import { InMemoryCache, gql } from "../index.js"; -import { InlineFragmentNode } from "graphql"; import { deepFreeze } from "../../utilities/common/maybeDeepFreeze.js"; import { InvariantError } from "../../utilities/globals/index.js"; import { spyOnConsole, withProdMode } from "../../testing/internal/index.js"; @@ -13,9 +12,7 @@ describe("maskOperation", () => { } `; - expect(() => - maskOperation({}, document, createFragmentMatcher(new InMemoryCache())) - ).toThrow( + expect(() => maskOperation({}, document, new InMemoryCache())).toThrow( new InvariantError( "Expected a parsed GraphQL document with a query, mutation, or subscription." ) @@ -34,7 +31,7 @@ describe("maskOperation", () => { {}, // @ts-expect-error document, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ) ).toThrow( new InvariantError( @@ -54,9 +51,7 @@ describe("maskOperation", () => { } `; - expect(() => - maskOperation({}, document, createFragmentMatcher(new InMemoryCache())) - ).toThrow( + expect(() => maskOperation({}, document, new InMemoryCache())).toThrow( new InvariantError("Ambiguous GraphQL document: contains 2 operations") ); }); @@ -76,7 +71,7 @@ describe("maskOperation", () => { const data = maskOperation( { foo: true, bar: true }, query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ foo: true }); @@ -99,7 +94,7 @@ describe("maskOperation", () => { const data = maskOperation( deepFreeze({ user: { __typename: "User", id: 1, name: "Test User" } }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ user: { __typename: "User", id: 1 } }); @@ -132,7 +127,7 @@ describe("maskOperation", () => { }, }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ @@ -171,7 +166,7 @@ describe("maskOperation", () => { }, }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ @@ -199,13 +194,13 @@ describe("maskOperation", () => { const frozenData = maskOperation( deepFreeze({ user: { __typename: "User", id: 1, name: "Test User" } }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); const nonFrozenData = maskOperation( { user: { __typename: "User", id: 1, name: "Test User" } }, query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(Object.isFrozen(frozenData)).toBe(true); @@ -234,7 +229,7 @@ describe("maskOperation", () => { ], }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ @@ -274,7 +269,7 @@ describe("maskOperation", () => { }, }, query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ @@ -318,7 +313,7 @@ describe("maskOperation", () => { }, }, query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ @@ -353,7 +348,7 @@ describe("maskOperation", () => { }, }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ @@ -395,7 +390,7 @@ describe("maskOperation", () => { }, }), query, - createFragmentMatcher(cache) + cache ); expect(data).toEqual({ @@ -468,7 +463,7 @@ describe("maskOperation", () => { }, }), query, - createFragmentMatcher(cache) + cache ); expect(data).toEqual({ @@ -511,7 +506,7 @@ describe("maskOperation", () => { ], }), query, - createFragmentMatcher(cache) + cache ); expect(data).toEqual({ @@ -545,7 +540,7 @@ describe("maskOperation", () => { }, }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ @@ -666,7 +661,7 @@ describe("maskOperation", () => { ], }), query, - createFragmentMatcher(cache) + cache ); expect(data).toEqual({ @@ -719,7 +714,7 @@ describe("maskOperation", () => { const data = maskOperation( deepFreeze({ user: { __typename: "User", id: 1, name: "Test User" } }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ @@ -801,11 +796,7 @@ describe("maskOperation", () => { const drink = { __typename: "Espresso" }; const originalData = deepFreeze({ user, post, authors, industries, drink }); - const data = maskOperation( - originalData, - query, - createFragmentMatcher(new InMemoryCache()) - ); + const data = maskOperation(originalData, query, new InMemoryCache()); expect(data).toEqual({ user: { @@ -856,11 +847,7 @@ describe("maskOperation", () => { }, }); - const data = maskOperation( - originalData, - query, - createFragmentMatcher(new InMemoryCache()) - ); + const data = maskOperation(originalData, query, new InMemoryCache()); expect(data).toBe(originalData); }); @@ -889,11 +876,7 @@ describe("maskOperation", () => { }, }); - const data = maskOperation( - queryData, - query, - createFragmentMatcher(new InMemoryCache()) - ); + const data = maskOperation(queryData, query, new InMemoryCache()); expect(data).toBe(queryData); }); @@ -987,11 +970,7 @@ describe("maskOperation", () => { ]; const originalData = deepFreeze({ user, post, authors, industries }); - const data = maskOperation( - originalData, - query, - createFragmentMatcher(new InMemoryCache()) - ); + const data = maskOperation(originalData, query, new InMemoryCache()); expect(data).toEqual({ user: { @@ -1066,18 +1045,14 @@ describe("maskOperation", () => { age: 30, }; - const fragmentMatcher = createFragmentMatcher(new InMemoryCache()); + const cache = new InMemoryCache(); - const data = maskOperation( - deepFreeze({ currentUser }), - query, - fragmentMatcher - ); + const data = maskOperation(deepFreeze({ currentUser }), query, cache); const dataFromAnonymous = maskOperation( { currentUser }, anonymousQuery, - fragmentMatcher + cache ); data.currentUser.age; @@ -1133,7 +1108,7 @@ describe("maskOperation", () => { }, }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); const age = data.currentUser.age; @@ -1158,8 +1133,6 @@ describe("maskOperation", () => { } `; - const fragmentMatcher = createFragmentMatcher(new InMemoryCache()); - const data = maskOperation( deepFreeze({ users: [ @@ -1168,7 +1141,7 @@ describe("maskOperation", () => { ], }), query, - fragmentMatcher + new InMemoryCache() ); data.users[0].age; @@ -1257,7 +1230,7 @@ describe("maskOperation", () => { }, }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ @@ -1338,7 +1311,7 @@ describe("maskOperation", () => { }, }), query, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ @@ -1424,12 +1397,10 @@ describe("maskOperation", () => { ], }; - const fragmentMatcher = createFragmentMatcher(new InMemoryCache()); - const data = maskOperation( deepFreeze({ currentUser }), query, - fragmentMatcher + new InMemoryCache() ); data.currentUser.age; @@ -1515,8 +1486,6 @@ describe("maskOperation", () => { } `; - const fragmentMatcher = createFragmentMatcher(new InMemoryCache()); - const data = maskOperation( deepFreeze({ currentUser: { @@ -1528,7 +1497,7 @@ describe("maskOperation", () => { }, }), query, - fragmentMatcher + new InMemoryCache() ); data.currentUser.age; @@ -1553,8 +1522,6 @@ describe("maskOperation", () => { } `; - const fragmentMatcher = createFragmentMatcher(new InMemoryCache()); - const data = maskOperation( deepFreeze({ currentUser: { @@ -1565,7 +1532,7 @@ describe("maskOperation", () => { }, }), query, - fragmentMatcher + new InMemoryCache() ); data.currentUser.age; @@ -1592,7 +1559,7 @@ describe("maskOperation", () => { onUserUpdated: { __typename: "User", id: 1, name: "Test User" }, }), subscription, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ onUserUpdated: { __typename: "User", id: 1 } }); @@ -1619,7 +1586,7 @@ describe("maskOperation", () => { const data = maskOperation( subscriptionData, subscription, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toBe(subscriptionData); @@ -1648,7 +1615,7 @@ describe("maskOperation", () => { const data = maskOperation( subscriptionData, subscription, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); data.onUserUpdated.name; @@ -1680,7 +1647,7 @@ describe("maskOperation", () => { updateUser: { __typename: "User", id: 1, name: "Test User" }, }), mutation, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ); expect(data).toEqual({ updateUser: { __typename: "User", id: 1 } }); @@ -1704,11 +1671,7 @@ describe("maskOperation", () => { updateUser: { __typename: "User", id: 1, name: "Test User" }, }); - const data = maskOperation( - mutationData, - mutation, - createFragmentMatcher(new InMemoryCache()) - ); + const data = maskOperation(mutationData, mutation, new InMemoryCache()); expect(data).toBe(mutationData); }); @@ -1733,11 +1696,7 @@ describe("maskOperation", () => { updateUser: { __typename: "User", id: 1, name: "Test User" }, }); - const data = maskOperation( - mutationData, - mutation, - createFragmentMatcher(new InMemoryCache()) - ); + const data = maskOperation(mutationData, mutation, new InMemoryCache()); data.updateUser.name; @@ -1766,7 +1725,7 @@ describe("maskFragment", () => { const data = maskFragment( deepFreeze({ __typename: "User", id: 1, age: 30 }), fragment, - createFragmentMatcher(new InMemoryCache()), + new InMemoryCache(), "UserFields" ); @@ -1794,7 +1753,7 @@ describe("maskFragment", () => { profile: { __typename: "Profile", age: 30 }, }), fragment, - createFragmentMatcher(new InMemoryCache()), + new InMemoryCache(), "UserFields" ); @@ -1826,7 +1785,7 @@ describe("maskFragment", () => { profile: { __typename: "Profile", age: 30 }, }), fragment, - createFragmentMatcher(new InMemoryCache()), + new InMemoryCache(), "UserFields" ); @@ -1837,7 +1796,7 @@ describe("maskFragment", () => { profile: { __typename: "Profile", age: 30 }, }, fragment, - createFragmentMatcher(new InMemoryCache()), + new InMemoryCache(), "UserFields" ); @@ -1858,7 +1817,7 @@ describe("maskFragment", () => { const data = maskFragment( deepFreeze({ __typename: "User", id: 1, age: 30 }), fragment, - createFragmentMatcher(new InMemoryCache()), + new InMemoryCache(), "UserFields" ); @@ -1881,7 +1840,7 @@ describe("maskFragment", () => { maskFragment( deepFreeze({ __typename: "User", id: 1, age: 30 }), fragment, - createFragmentMatcher(new InMemoryCache()) + new InMemoryCache() ) ).toThrow( new InvariantError( @@ -1906,7 +1865,7 @@ describe("maskFragment", () => { maskFragment( deepFreeze({ __typename: "User", id: 1, age: 30 }), fragment, - createFragmentMatcher(new InMemoryCache()), + new InMemoryCache(), "ProfileFields" ) ).toThrow( @@ -1996,7 +1955,7 @@ describe("maskFragment", () => { const data = maskFragment( user, fragment, - createFragmentMatcher(new InMemoryCache()), + new InMemoryCache(), "UserFields" ); @@ -2035,11 +1994,7 @@ describe("maskFragment", () => { const user = { __typename: "User", id: 1, age: 30 }; - const data = maskFragment( - deepFreeze(user), - fragment, - createFragmentMatcher(new InMemoryCache()) - ); + const data = maskFragment(deepFreeze(user), fragment, new InMemoryCache()); expect(data).toBe(user); }); @@ -2067,7 +2022,7 @@ describe("maskFragment", () => { const data = maskFragment( fragmentData, fragment, - createFragmentMatcher(new InMemoryCache()), + new InMemoryCache(), "UnmaskedFragment" ); @@ -2088,8 +2043,6 @@ describe("maskFragment", () => { } `; - const fragmentMatcher = createFragmentMatcher(new InMemoryCache()); - const data = maskFragment( deepFreeze({ currentUser: { @@ -2100,7 +2053,7 @@ describe("maskFragment", () => { }, }), query, - fragmentMatcher, + new InMemoryCache(), "UnmaskedFragment" ); @@ -2207,7 +2160,7 @@ describe("maskFragment", () => { const data = maskFragment( user, fragment, - createFragmentMatcher(new InMemoryCache()), + new InMemoryCache(), "UserFields" ); @@ -2274,7 +2227,7 @@ describe("maskFragment", () => { email: "test@example.com", }), fragment, - createFragmentMatcher(new InMemoryCache()), + new InMemoryCache(), "UnmaskedUser" ); @@ -2294,8 +2247,3 @@ describe("maskFragment", () => { ); }); }); - -function createFragmentMatcher(cache: InMemoryCache) { - return (node: InlineFragmentNode, typename: string) => - cache.policies.fragmentMatches(node, typename); -} diff --git a/src/core/masking.ts b/src/core/masking.ts index 64cae9275e5..ee82723e6b3 100644 --- a/src/core/masking.ts +++ b/src/core/masking.ts @@ -1,9 +1,5 @@ import { Kind } from "graphql"; -import type { - FragmentDefinitionNode, - InlineFragmentNode, - SelectionSetNode, -} from "graphql"; +import type { FragmentDefinitionNode, SelectionSetNode } from "graphql"; import { createFragmentMap, resultKeyNameFromField, @@ -13,27 +9,30 @@ import { maybeDeepFreeze, } from "../utilities/index.js"; import type { FragmentMap } from "../utilities/index.js"; -import type { DocumentNode, TypedDocumentNode } from "./index.js"; +import type { ApolloCache, DocumentNode, TypedDocumentNode } from "./index.js"; import { invariant } from "../utilities/globals/index.js"; -type MatchesFragmentFn = ( - fragmentNode: InlineFragmentNode, - typename: string -) => boolean; - interface MaskingContext { operationType: "query" | "mutation" | "subscription" | "fragment"; operationName: string | undefined; fragmentMap: FragmentMap; - matchesFragment: MatchesFragmentFn; + cache: ApolloCache; disableWarnings?: boolean; } export function maskOperation( data: TData, - document: TypedDocumentNode | DocumentNode, - matchesFragment: MatchesFragmentFn + document: DocumentNode | TypedDocumentNode, + cache: ApolloCache ): TData { + if (!cache.fragmentMatches) { + if (__DEV__) { + warnOnImproperCacheImplementation(); + } + + return data; + } + const definition = getOperationDefinition(document); invariant( @@ -45,7 +44,7 @@ export function maskOperation( operationType: definition.operation, operationName: definition.name?.value, fragmentMap: createFragmentMap(getFragmentDefinitions(document)), - matchesFragment, + cache, }; const [masked, changed] = maskSelectionSet( @@ -66,9 +65,17 @@ export function maskOperation( export function maskFragment( data: TData, document: TypedDocumentNode | DocumentNode, - matchesFragment: MatchesFragmentFn, + cache: ApolloCache, fragmentName?: string ): TData { + if (!cache.fragmentMatches) { + if (__DEV__) { + warnOnImproperCacheImplementation(); + } + + return data; + } + const fragments = document.definitions.filter( (node): node is FragmentDefinitionNode => node.kind === Kind.FRAGMENT_DEFINITION @@ -97,7 +104,7 @@ export function maskFragment( operationType: "fragment", operationName: fragment.name.value, fragmentMap: createFragmentMap(getFragmentDefinitions(document)), - matchesFragment, + cache, }; const [masked, changed] = maskSelectionSet( @@ -173,7 +180,7 @@ function maskSelectionSet( case Kind.INLINE_FRAGMENT: { if ( selection.typeCondition && - !context.matchesFragment(selection, data.__typename) + !context.cache.fragmentMatches!(selection, data.__typename) ) { return [memo, changed]; } @@ -194,7 +201,17 @@ function maskSelectionSet( ]; } case Kind.FRAGMENT_SPREAD: { - const fragment = context.fragmentMap[selection.name.value]; + const fragmentName = selection.name.value; + let fragment: FragmentDefinitionNode | null = + context.fragmentMap[fragmentName] || + (context.fragmentMap[fragmentName] = + context.cache.lookupFragment(fragmentName)!); + invariant( + fragment, + "Could not find fragment with name '%s'.", + fragmentName + ); + const mode = getFragmentMaskMode(selection); if (mode === "mask") { @@ -364,3 +381,9 @@ function addAccessorWarning( configurable: true, }); } + +function warnOnImproperCacheImplementation() { + invariant.warn( + "The configured cache does not support data masking which effectively disables it. Please use a cache that supports data masking or disable data masking to silence this warning." + ); +}