graphql-fragment-normalizer
v0.3.1
Published
Schema-aware GraphQL fragment expansion and normalization for ASTs and GraphQL Code Generator.
Maintainers
Readme
graphql-fragment-normalizer
Normalizes GraphQL documents by expanding fragment spreads and inline fragments with schema-aware type checks.
This package can be used directly with GraphQL ASTs, or as a GraphQL Code Generator plugin that rewrites documents before later plugins generate their output.
You can try the library here: graphql-fragment-normalizer REPL
Motivation
GraphQL fragments are useful for colocating field selections, but some consumers work better with operations whose fragments have already been expanded. For example, generated document strings may need to be self-contained, or downstream tooling may need to inspect the final field selection without resolving fragment references itself.
graphql-fragment-normalizer expands fragments while using the schema to keep only selections that are valid for the current type. It can flatten compatible fragments, preserve type-narrowing selections as inline fragments, merge repeated field selections, and optionally keep named fragments when they should remain available for generated fragment types.
Packages such as @graphql-tools/relay-operation-optimizer already provide operation normalization, but they are designed for Relay-style optimization and can apply broader, more complex rewrites. This package focuses on a smaller transformation surface: expanding and normalizing fragments while keeping the resulting document close to the original operation shape.
Usage
Installing
npm install graphql-fragment-normalizer graphqlUsing the API
import { buildSchema, parse, print } from 'graphql';
import { expandFragments } from 'graphql-fragment-normalizer';
const schema = buildSchema(`
type Query {
user: User
}
type User {
id: ID!
name: String!
}
`);
const document = parse(`
query GetUser {
user {
...UserFields
}
}
fragment UserFields on User {
id
name
}
`);
const normalizedDocument = expandFragments(schema, document);
console.log(print(normalizedDocument));Output:
query GetUser {
user {
id
name
}
}By default, fragment definitions are removed from the returned document after the operations have been expanded.
Using with GraphQL Code Generator
Add the plugin graphql-fragment-normalizer/codegen-plugin before the plugin or preset that should receive normalized documents:
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: 'schema.graphql',
documents: ['src/**/*.graphql'],
generates: {
'src/generated/': {
preset: 'client',
plugins: [
{
'graphql-fragment-normalizer/codegen-plugin': {
preserveNarrowingFragments: true,
},
},
],
},
},
};
export default config;The Code Generator plugin mutates the loaded documents in place and returns no generated content by itself. It also collects fragments from all input document files, so operations can be normalized even when fragments are split into separate files.
For Code Generator usage, fragment definitions are preserved so that later plugins can still generate colocated fragment types.
The plugin is exported through the graphql-fragment-normalizer/codegen-plugin subpath. If you need to import the plugin function directly, use that subpath instead of the main package entry.
The options (configurations) for the plugin can be ExpandFragmentsOptions as described below.
Options
export interface ExpandFragmentsOptions {
readonly additionalFragments?: readonly FragmentDefinitionNode[];
readonly operationName?: string | null | undefined;
readonly preserveNarrowingFragments?: boolean;
readonly distributeAbstractFragments?: boolean;
readonly preserveNamedFragmentsUsedAtLeast?: number;
readonly fragmentDefinitionsMode?: 'drop' | 'normalize' | 'preserve';
readonly missingFragmentBehavior?: 'error' | 'warn' | 'ignore';
readonly typeRelationContext?: TypeRelationContext;
}additionalFragments: Fragment definitions supplied outside the document being expanded.operationName: Expands only the named operation. When omitted, all operations are expanded.preserveNarrowingFragments: Keeps fragments that narrow from the current type to a more specific type as inline fragments. Defaults totrue.distributeAbstractFragments: Emits abstract narrowing fragments as inline fragments for each reachable concrete object type. Defaults tofalse.preserveNamedFragmentsUsedAtLeast: Keeps named fragment spreads when a fragment is used at least this many times. Values less than or equal to0disable this behavior.fragmentDefinitionsMode: Controls inputFragmentDefinitionnodes in the returned document.dropremoves them after expansion,preservekeeps them unchanged, andnormalizekeeps and normalizes them. Defaults todrop.missingFragmentBehavior: Controls unresolved named fragment spreads.errorthrows,warnlogs a warning and omits the spread, andignoreomits it silently. Defaults toerror.typeRelationContext: Reusable type-relation cache created withcreateTypeRelationContext(schema).
The Code Generator plugin accepts the same options except additionalFragments, fragmentDefinitionsMode, and typeRelationContext, which are managed internally. (The plugin always sets fragmentDefinitionsMode to preserve to keep fragment definitions for following generating processes.)
APIs
import {
createTypeRelationContext,
expandFragments,
expandFragmentsInFragment,
type ExpandFragmentsOptions,
type FragmentDefinitionsMode,
type TypeRelationContext,
} from 'graphql-fragment-normalizer';expandFragments(schema, document, options?)
Expands fragment spreads and inline fragments inside a DocumentNode using schema type information.
const normalizedDocument = expandFragments(schema, document, {
fragmentDefinitionsMode: 'normalize',
preserveNamedFragmentsUsedAtLeast: 2,
});expandFragmentsInFragment(schema, fragment, options?)
Expands fragment spreads and inline fragments inside a single FragmentDefinitionNode.
const normalizedFragment = expandFragmentsInFragment(schema, fragment, {
additionalFragments,
});createTypeRelationContext(schema)
Creates a reusable cache for schema type relation checks. Reuse it when expanding multiple documents against the same schema.
const typeRelationContext = createTypeRelationContext(schema);
const first = expandFragments(schema, firstDocument, { typeRelationContext });
const second = expandFragments(schema, secondDocument, { typeRelationContext });Development Note
Parts of this package were developed with assistance from AI tools. The published code is reviewed, tested, and maintained by the package author.
