Fragment masking provides type-safe fragment management with GraphQL Code Generator integration. Apollo Client's masking system ensures that components only access the fragment data they explicitly declare, improving type safety and component isolation.
Type utilities for enforcing fragment boundaries and ensuring type safety with masked fragments.
/**
* Type used with fragments to ensure parent objects contain the fragment spread
* Provides compile-time verification that fragments are properly included
*/
type FragmentType<TFragmentData> = TFragmentData & {
readonly __fragmentType: unique symbol;
};
/**
* Unwraps masked data into its unmasked type
* Removes masking constraints to access underlying data
*/
type Unmasked<TData> = TData extends FragmentType<infer U> ? U : TData;
/**
* Returns data as either masked or unmasked depending on masking configuration
* Allows conditional masking based on build-time or runtime settings
*/
type MaybeMasked<TData> = TData | Unmasked<TData>;Usage Example:
// Generated fragment types (by GraphQL Code Generator)
type UserFragment = {
__typename?: 'User';
id: string;
name: string;
} & { __fragmentType: 'UserFragment' };
// Component receives masked fragment
function UserCard({ user }: { user: FragmentType<UserFragment> }) {
// This would cause a TypeScript error - data is masked
// console.log(user.name); // Error!
// Must use unmasked utility or fragment masking function
const userData = useFragment(UserFragmentDoc, user);
console.log(userData.name); // ✅ Safe access
}Runtime functions for masking and unmasking fragment data.
/**
* Mask a fragment to enforce type boundaries
* @param fragment - Fragment document
* @param data - Data to mask
* @returns Masked fragment data
*/
function maskFragment<TData>(
fragment: DocumentNode,
data: TData
): FragmentType<TData>;
/**
* Mask an operation result to enforce fragment boundaries
* @param operation - Operation document
* @param data - Operation result data
* @returns Masked operation data
*/
function maskOperation<TData>(
operation: DocumentNode,
data: TData
): MaybeMasked<TData>;
/**
* Disable masking warnings for specific contexts
* @returns Slot for disabling warnings
*/
function disableWarningsSlot(): { disable(): void; restore(): void };Usage Example:
import { maskFragment, maskOperation } from "@apollo/client/masking";
import { gql } from "@apollo/client";
const USER_FRAGMENT = gql`
fragment UserInfo on User {
id
name
email
}
`;
// Mask fragment data for type safety
const maskedUser = maskFragment(USER_FRAGMENT, {
id: '1',
name: 'John Doe',
email: 'john@example.com'
});
// Mask operation result
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
...UserInfo
}
}
${USER_FRAGMENT}
`;
const maskedResult = maskOperation(GET_USER, queryResult.data);React hooks and utilities that work with fragment masking for type-safe component development.
/**
* Use fragment data with automatic unmasking
* @param fragment - Fragment document
* @param data - Masked fragment data
* @returns Unmasked fragment data
*/
function useFragment<TData>(
fragment: DocumentNode,
data: FragmentType<TData>
): TData;
/**
* Query hook with masking support
* @param query - Query document
* @param options - Query options
* @returns Query result with masked data
*/
function useQuery<TData, TVariables>(
query: DocumentNode,
options?: QueryHookOptions<TData, TVariables>
): QueryResult<MaybeMasked<TData>, TVariables>;Usage Example:
import { useFragment, useQuery } from "@apollo/client";
import { gql } from "@apollo/client";
const USER_FRAGMENT = gql`
fragment UserProfile on User {
id
name
avatar
email
}
`;
const GET_CURRENT_USER = gql`
query GetCurrentUser {
currentUser {
...UserProfile
}
}
${USER_FRAGMENT}
`;
function UserProfile() {
const { data, loading } = useQuery(GET_CURRENT_USER);
if (loading || !data) return <div>Loading...</div>;
return <UserCard user={data.currentUser} />;
}
function UserCard({ user }: { user: FragmentType<UserProfileFragment> }) {
// Safely access fragment data
const userData = useFragment(USER_FRAGMENT, user);
return (
<div>
<img src={userData.avatar} alt={userData.name} />
<h3>{userData.name}</h3>
<p>{userData.email}</p>
</div>
);
}Configuration and types for integrating with GraphQL Code Generator's masking features.
/**
* Configuration interface for GraphQL Code Generator masking integration
* Provides settings for automatic fragment type generation
*/
interface GraphQLCodegenDataMasking {
/** Enable fragment masking in generated types */
enabled: boolean;
/** Unmask fragments by default */
unmaskOnExport?: boolean;
/** Custom masking function name */
maskingFunctionName?: string;
}Configuration Example:
// codegen.yml
const config = {
schema: './schema.graphql',
documents: './src/**/*.{ts,tsx}',
generates: {
'./src/generated/': {
preset: 'client',
config: {
// Enable fragment masking
dataMasking: {
enabled: true,
unmaskOnExport: false,
maskingFunctionName: 'useFragment'
}
},
plugins: []
}
}
};Advanced patterns for working with masked fragments in complex applications.
/**
* Utility for conditionally unmasking fragments based on runtime conditions
* @param condition - Whether to unmask
* @param fragment - Fragment document
* @param data - Potentially masked data
* @returns Conditionally unmasked data
*/
function conditionalUnmask<TData>(
condition: boolean,
fragment: DocumentNode,
data: MaybeMasked<TData>
): TData | FragmentType<TData>;
/**
* Batch unmask multiple fragments
* @param fragments - Array of fragment/data pairs
* @returns Array of unmasked data
*/
function batchUnmask<T extends readonly any[]>(
fragments: { fragment: DocumentNode; data: FragmentType<T[number]> }[]
): Unmasked<T>;Usage Example:
// Conditional unmasking for development/debugging
function DebugUserCard({ user, debug }: {
user: FragmentType<UserFragment>;
debug: boolean;
}) {
const userData = conditionalUnmask(debug, USER_FRAGMENT, user);
if (debug) {
console.log('User data:', userData); // Full access in debug mode
}
// Use properly unmasked data
const safeUserData = useFragment(USER_FRAGMENT, user);
return <div>{safeUserData.name}</div>;
}
// Batch processing of multiple fragments
function UserList({ users }: { users: FragmentType<UserFragment>[] }) {
const fragmentPairs = users.map(user => ({
fragment: USER_FRAGMENT,
data: user
}));
const batchUnmasked = batchUnmask(fragmentPairs);
return (
<div>
{batchUnmasked.map(userData => (
<UserCard key={userData.id} userData={userData} />
))}
</div>
);
}Utilities for migrating to fragment masking and maintaining compatibility with existing code.
/**
* Legacy compatibility function for gradually adopting masking
* @param data - Data that may or may not be masked
* @returns Unmasked data for legacy compatibility
*/
function legacyUnmask<TData>(data: MaybeMasked<TData>): Unmasked<TData>;
/**
* Opt-in masking for specific components
* @param enabled - Whether masking is enabled for this context
* @returns Masking configuration object
*/
function optInMasking(enabled: boolean): {
maskFragment: typeof maskFragment;
maskOperation: typeof maskOperation;
};// ✅ Good: Co-locate fragments with components
const USER_CARD_FRAGMENT = gql`
fragment UserCard on User {
id
name
avatar
}
`;
function UserCard({ user }: { user: FragmentType<UserCardFragment> }) {
const userData = useFragment(USER_CARD_FRAGMENT, user);
return <div>{userData.name}</div>;
}
// ✅ Good: Compose fragments hierarchically
const USER_PROFILE_FRAGMENT = gql`
fragment UserProfile on User {
...UserCard
email
bio
}
${USER_CARD_FRAGMENT}
`;// ✅ Good: Use fragment types for props
interface UserCardProps {
user: FragmentType<UserCardFragment>;
}
// ❌ Bad: Don't access masked data directly
function BadUserCard({ user }: { user: FragmentType<UserCardFragment> }) {
return <div>{user.name}</div>; // TypeScript error!
}
// ✅ Good: Always unmask before using
function GoodUserCard({ user }: { user: FragmentType<UserCardFragment> }) {
const userData = useFragment(USER_CARD_FRAGMENT, user);
return <div>{userData.name}</div>; // ✅ Type safe
}