The plugin distinguishes between two classes of directives:
- built-in
@includeand@skip; - custom directives, for which the user explicitly defines a policy via
directivePolicies.
The main principle is:
- if a selection is guaranteed not to appear in the result, it is removed from the declaration;
- if a selection may be present or absent at runtime, it becomes optional;
- if a directive does not affect the response shape, the declaration remains unchanged;
- when directives affect abstract fields, they may also change how fallback
__typenameis rendered.
For built-in directives, the plugin uses static interpretation whenever possible.
fragment UserCard on User {
id
email @skip(if: true)
name @include(if: true)
}Expected declaration:
export type UserCard = {
__typename?: 'User';
id: string;
name: string;
}Logic:
@skip(if: true)always excludesemailfrom the result;@include(if: true)does not change the shape and is treated as a regular field.
query UserCardQuery($withEmail: Boolean!) {
user {
...UserCard
}
}
fragment UserCard on User {
id
email @include(if: $withEmail)
}Expected declaration:
export type UserCard = {
__typename?: 'User';
id: string;
email?: string | null;
}Logic:
- the directive value is not known at generation time;
- the field may be absent in the runtime response;
- therefore
?:is used instead of only| null.
fragment UserCard on User {
id
...UserMeta @skip(if: $hideMeta)
}Expected declaration:
export type UserCard = {
__typename?: 'User';
id: string;
} & Partial<UserMeta>Logic:
- the entire fragment spread contribution may be absent;
- therefore the spread is rendered as
Partial<...>.
For abstract selections such as interfaces and unions, the plugin may synthesize fallback __typename values even when __typename is not selected explicitly.
Current behavior:
- for concrete object shapes, an aliased
__typenameselection such askind: __typenamesuppresses the synthesized fallback__typename; the alias is rendered as a regular field with a string-literal union value; - if there is no explicit
__typenameselection and the result splits into distinct concrete branches, branch-level fallback__typenameis rendered as required so the union stays discriminated; - if
__typenameis selected conditionally, or only in part of the branches, fallback__typenamestays optional; - if branch-specific rendering collapses to the same shape, the plugin merges those branches into a single object type and renders
__typenameas a union of possible string literals.
Reserved name rule:
- aliasing any non-
__typenamefield to the response name__typenameis rejected by the plugin because__typenameis reserved for typename-specific handling.
The plugin currently does not recover missing fragment definitions automatically.
When a configured document references a missing fragment definition, the plugin emits a warning that names:
- the missing fragment definition;
- the document that referenced it.
These warnings are diagnostics only. They do not add recovered fragments to the generated output.
The plugin does not try to infer custom directive semantics from the directive name. You need to define an explicit policy for them:
{
directivePolicies: {
mask: {
field: { effect: 'conditional' },
},
clientOnly: {
inlineFragment: { effect: 'exclude' },
},
opaque: {
field: { effect: 'override-type', type: 'OpaqueId' },
},
required: {
field: { effect: 'nonnull' },
},
review: {
field: { effect: 'warn', message: 'Manual review required' },
},
},
}Supported policies:
ignore: does not affect the result type;exclude: the selection is removed from the declaration;conditional: the selection is treated as runtime-conditional and becomes optional;nonnull: removes| nullfrom the field type;override-type: replaces the rendered field type with a custom TypeScript type;warn: emits a warning without changing the generated shape.
Policies can be defined:
- directly for the directive name;
- or per target kind:
field,fragmentSpread,inlineFragment.
fragment UserCard on User {
id @mask
}Config:
{
directivePolicies: {
mask: {
field: { effect: 'conditional' },
},
},
}Expected declaration:
export type UserCard = {
__typename?: 'User';
id?: string;
}fragment UserCard on User {
id
email @clientOnly
}Config:
{
directivePolicies: {
clientOnly: {
field: { effect: 'exclude' },
},
},
}Expected declaration:
export type UserCard = {
__typename?: 'User';
id: string;
}fragment GroupOwner on Group {
owner {
id
... on UserPayload @clientOnly {
__typename
}
}
}Config:
{
directivePolicies: {
clientOnly: {
inlineFragment: { effect: 'exclude' },
},
},
}Expected declaration:
export type GroupOwner = {
__typename?: 'Group';
owner: {
__typename?: 'UserPayload' | 'AdminPayload';
id: string;
};
}Logic:
- the inline fragment is removed from the model entirely;
- explicit
__typenamefrom that inline fragment is removed with it; - the remaining abstract field still keeps an optional fallback
__typenamebased on the possible runtime types ofowner.
fragment UserCard on User {
id @trace
}Config:
{
directivePolicies: {
trace: {
field: { effect: 'ignore' },
},
},
}Expected declaration:
export type UserCard = {
__typename?: 'User';
id: string;
}query GroupOwnerQuery($withTypeName: Boolean!) {
group {
...GroupOwner
}
}
fragment GroupOwner on Group {
owner {
id
... on UserPayload @include(if: $withTypeName) {
__typename
}
}
}Expected declaration:
export type GroupOwner = {
__typename?: 'Group';
owner: {
__typename?: 'UserPayload' | 'AdminPayload';
id: string;
};
}Logic:
- the inline fragment may or may not contribute
__typenameat runtime; - because the selection is conditional, the generated
__typenameon the abstract field must remain optional; - since both concrete branches render to the same final shape, the plugin collapses them into one object type instead of keeping a redundant union.
fragment UserCard on User {
id @opaque
}Config:
{
directivePolicies: {
opaque: {
field: { effect: 'override-type', type: 'OpaqueId' },
},
},
}Expected declaration:
export type UserCard = {
__typename?: 'User';
id: OpaqueId;
}fragment UserCard on User {
nickname @required
}Config:
{
directivePolicies: {
required: {
field: { effect: 'nonnull' },
},
},
}Expected declaration:
export type UserCard = {
__typename?: 'User';
nickname: string;
}fragment UserCard on User {
id @review
}Config:
{
directivePolicies: {
review: {
field: { effect: 'warn', message: 'Manual review required' },
},
},
}Expected declaration:
export type UserCard = {
__typename?: 'User';
id: string;
}Logic:
- the generated shape does not change;
- the plugin emits a warning during model building.