Turns GraphQLResolveInfo into a map of the requested fields
npx @tessl/cli install tessl/npm-graphql-fields@2.0.0GraphQL Fields is a JavaScript utility library that transforms GraphQLResolveInfo objects into structured field maps. It flattens fragments and handles directives (@include, @skip) to create clean field objects for optimization purposes, making it particularly valuable for performance optimization in GraphQL applications.
npm install graphql-fieldsconst graphqlFields = require('graphql-fields');For ES modules:
import graphqlFields from 'graphql-fields';const graphqlFields = require('graphql-fields');
// In a GraphQL resolver
const resolver = (root, args, context, info) => {
const requestedFields = graphqlFields(info);
console.log(requestedFields);
// Use field map to optimize data fetching
const topLevelFields = Object.keys(requestedFields);
return fetch(`/api/user?fields=${topLevelFields.join(',')}`);
};Transforms GraphQLResolveInfo into a structured field map, flattening all fragments and handling directives.
/**
* Transforms GraphQLResolveInfo into a structured field map
* @param {Object} info - GraphQL resolver info object containing fieldNodes/fieldASTs
* @param {Object} [obj={}] - Base object to merge results into
* @param {Object} [options={}] - Configuration options
* @param {boolean} [options.processArguments=false] - Enable argument processing
* @param {string[]} [options.excludedFields=[]] - Array of field names to exclude
* @returns {Object} Flattened map of requested fields
*/
function graphqlFields(info, obj, options);When enabled via options, extracts and processes field arguments into the result map.
const fieldsWithArgs = graphqlFields(info, {}, { processArguments: true });Usage Example:
// GraphQL query with arguments:
// user(id: "123") {
// profile(includePrivate: true) {
// firstName
// }
// }
const fields = graphqlFields(info, {}, { processArguments: true });
// Result:
// {
// "profile": {
// "firstName": {},
// "__arguments": [
// {
// "includePrivate": {
// "kind": "BooleanValue",
// "value": true
// }
// }
// ]
// }
// }Excludes specific fields from the result map, commonly used to filter out __typename.
const fieldsWithoutTypename = graphqlFields(info, {}, {
excludedFields: ['__typename']
});Usage Example:
// Exclude __typename from results
const cleanFields = graphqlFields(info, {}, {
excludedFields: ['__typename']
});Automatically resolves and flattens both inline fragments and named fragments.
Input Query:
{
user {
...UserProfile
profile {
firstName
}
}
}
fragment UserProfile on User {
id
email
profile {
lastName
}
}Output:
{
"profile": {
"firstName": {},
"lastName": {}
},
"email": {},
"id": {}
}Handles @include and @skip directives, excluding fields based on directive conditions.
Input Query:
{
user {
id
email @include(if: $includeEmail)
profile @skip(if: $skipProfile) {
firstName
}
}
}Behavior:
@include(if: false) are excluded@skip(if: true) are excluded/**
* @typedef {Object} GraphQLFieldsOptions
* @property {boolean} [processArguments=false] - Enable argument processing
* @property {string[]} [excludedFields=[]] - Array of field names to exclude
*/
/**
* @typedef {Object} ArgumentValue
* @property {string} kind - The GraphQL argument kind (e.g., 'StringValue', 'IntValue', 'Variable')
* @property {*} value - The resolved argument value
*/
/**
* @typedef {Object.<string, ArgumentValue>} ProcessedArgument
* Object mapping argument names to their processed values
*/Field Map Structure:
{ "fieldName": {} }{ "parentField": { "childField": {} } }{ "fieldName": { "__arguments": ProcessedArgument[] } }Use field maps to optimize REST API calls based on requested GraphQL fields:
const resolver = async (root, args, context, info) => {
const fields = graphqlFields(info);
const requestedFields = Object.keys(fields);
// Only fetch needed fields from REST API
const response = await fetch(
`/api/users/${args.id}?fields=${requestedFields.join(',')}`
);
return response.json();
};Optimize database queries by selecting only requested fields:
const resolver = async (root, args, context, info) => {
const fields = graphqlFields(info);
const hasProfile = 'profile' in fields;
const hasAddressBook = 'addressBook' in fields;
let query = 'SELECT id, email FROM users WHERE id = ?';
const joins = [];
if (hasProfile) {
joins.push('LEFT JOIN profiles ON users.id = profiles.user_id');
}
if (hasAddressBook) {
joins.push('LEFT JOIN address_books ON users.id = address_books.user_id');
}
if (joins.length > 0) {
query += ' ' + joins.join(' ');
}
return database.query(query, [args.id]);
};Analyze nested field requirements for complex data fetching:
const resolver = async (root, args, context, info) => {
const fields = graphqlFields(info);
// Check which profile fields are requested
if (fields.profile) {
const profileFields = Object.keys(fields.profile);
console.log('Requested profile fields:', profileFields);
// Conditional data fetching based on nested fields
if (profileFields.includes('displayName')) {
// Fetch display name specific data
}
if (profileFields.includes('email')) {
// Fetch email specific data
}
}
};The function handles malformed or missing input gracefully:
{} if no valid fields are found