CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-graphql-tools--utils

Common package containing utils and types for GraphQL tools

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

utilities.mddocs/

Utilities

Helper functions for memoization, async handling, type guards, data structures, and performance monitoring. These utilities provide essential building blocks for efficient GraphQL operations and general-purpose data processing.

Capabilities

Memoization

Function memoization utilities for performance optimization with different argument counts.

/**
 * Memoize a function with one argument
 * @param fn - Function to memoize
 * @returns Memoized function
 */
function memoize1<TArgs, TReturn>(fn: (a1: TArgs) => TReturn): (a1: TArgs) => TReturn;

/**
 * Memoize a function with two arguments
 * @param fn - Function to memoize
 * @returns Memoized function
 */
function memoize2<TArgs1, TArgs2, TReturn>(
  fn: (a1: TArgs1, a2: TArgs2) => TReturn
): (a1: TArgs1, a2: TArgs2) => TReturn;

/**
 * Memoize a function with three arguments
 * @param fn - Function to memoize
 * @returns Memoized function
 */
function memoize3<TArgs1, TArgs2, TArgs3, TReturn>(
  fn: (a1: TArgs1, a2: TArgs2, a3: TArgs3) => TReturn
): (a1: TArgs1, a2: TArgs2, a3: TArgs3) => TReturn;

/**
 * Memoize a function with four arguments
 * @param fn - Function to memoize
 * @returns Memoized function
 */
function memoize4<TArgs1, TArgs2, TArgs3, TArgs4, TReturn>(
  fn: (a1: TArgs1, a2: TArgs2, a3: TArgs3, a4: TArgs4) => TReturn
): (a1: TArgs1, a2: TArgs2, a3: TArgs3, a4: TArgs4) => TReturn;

/**
 * Memoize a function with five arguments
 * @param fn - Function to memoize
 * @returns Memoized function
 */
function memoize5<TArgs1, TArgs2, TArgs3, TArgs4, TArgs5, TReturn>(
  fn: (a1: TArgs1, a2: TArgs2, a3: TArgs3, a4: TArgs4, a5: TArgs5) => TReturn
): (a1: TArgs1, a2: TArgs2, a3: TArgs3, a4: TArgs4, a5: TArgs5) => TReturn;

/**
 * Memoize first 2 of 4 arguments of a function
 * @param fn - Function to memoize
 * @returns Memoized function
 */
function memoize2of4<F extends (a1: any, a2: any, a3: any, a4: any) => any>(fn: F): F;

/**
 * Memoize first 2 of 5 arguments of a function
 * @param fn - Function to memoize
 * @returns Memoized function
 */
function memoize2of5<F extends (a1: any, a2: any, a3: any, a4: any, a5: any) => any>(fn: F): F;

Usage Examples:

import { memoize1, memoize2, memoize3 } from "@graphql-tools/utils";

// Memoize expensive type lookup
const getTypeFromSchema = memoize2(
  (schema: GraphQLSchema, typeName: string) => {
    console.log(`Looking up type: ${typeName}`);
    return schema.getType(typeName);
  }
);

// Multiple calls with same arguments only execute once
const userType1 = getTypeFromSchema(schema, 'User'); // Executes function
const userType2 = getTypeFromSchema(schema, 'User'); // Returns cached result

// Memoize field collection
const collectFieldsMemoized = memoize3(
  (schema: GraphQLSchema, selectionSet: SelectionSetNode, type: GraphQLObjectType) => {
    return collectFields(schema, {}, {}, type, selectionSet);
  }
);

// Memoize complex calculations
const calculateComplexMetric = memoize1((data: ComplexData) => {
  console.log('Calculating complex metric...');
  return performExpensiveCalculation(data);
});

Deep Merging

Intelligent deep merging for GraphQL objects and complex data structures.

/**
 * Deep merge multiple objects with intelligent GraphQL-aware merging
 * @param sources - Array of objects to merge
 * @returns Merged object
 */
function mergeDeep<T>(sources: Partial<T>[]): T;

Usage Examples:

import { mergeDeep } from "@graphql-tools/utils";

// Merge resolver objects
const baseResolvers = {
  Query: {
    user: () => ({ id: '1', name: 'John' }),
    posts: () => []
  }
};

const extendedResolvers = {
  Query: {
    posts: () => [{ id: '1', title: 'Hello' }],
    comments: () => []
  },
  User: {
    posts: (user) => findPostsByUser(user.id)
  }
};

const mergedResolvers = mergeDeep([baseResolvers, extendedResolvers]);
// Result: Query has user, posts (from extended), and comments
//         User resolver is added

// Merge schema configurations
const baseConfig = {
  typeDefs: baseTypes,
  resolvers: baseResolvers,
  options: {
    cacheControl: true
  }
};

const additionalConfig = {
  resolvers: additionalResolvers,
  options: {
    tracing: true,
    introspection: true
  }
};

const finalConfig = mergeDeep([baseConfig, additionalConfig]);
// Result: Combined typeDefs, merged resolvers, merged options

Async Utilities

Utilities for working with async iterators, observables, and promise handling.

/**
 * Type guard to check if a value is an async iterable
 * @param value - Value to check
 * @returns True if value is async iterable
 */
function isAsyncIterable(value: any): value is AsyncIterable<unknown>;

/**
 * Convert an Observable to an AsyncIterable
 * @param observable - Observable to convert
 * @returns AsyncIterable that yields observable values
 */
function observableToAsyncIterable<T>(observable: Observable<T>): AsyncIterable<T>;

/**
 * Add cancellation capability to async operations
 * @param asyncIterable - The async iterable to make cancellable
 * @param onCancel - Callback called when cancelled
 * @returns Cancellable async iterable
 */
function withCancel<T>(
  asyncIterable: AsyncIterable<T>,
  onCancel: () => void
): AsyncIterable<T>;

/**
 * Reduce an array with async operations, processing sequentially
 * @param values - Array of values to reduce
 * @param callback - Async reducer function
 * @param initial - Initial accumulator value
 * @returns Promise resolving to final accumulated value
 */
function promiseReduce<T, R>(
  values: T[],
  callback: (accumulator: R, currentValue: T, currentIndex: number) => Promise<R>,
  initial: R
): Promise<R>;

/**
 * Register a listener for abort signals with cleanup
 * @param signal - AbortSignal to listen to
 * @param listener - Function called when aborted
 * @returns Cleanup function to remove listener
 */
function registerAbortSignalListener(
  signal: AbortSignal,
  listener: () => void
): () => void;

Usage Examples:

import { 
  isAsyncIterable, 
  observableToAsyncIterable, 
  withCancel, 
  promiseReduce,
  registerAbortSignalListener 
} from "@graphql-tools/utils";

// Check for async iterable support
async function processData(data: unknown) {
  if (isAsyncIterable(data)) {
    for await (const item of data) {
      console.log('Processing item:', item);
    }
  } else {
    console.log('Data is not async iterable');
  }
}

// Convert observable to async iterable for GraphQL subscriptions
const observable = new Observable(subscriber => {
  let count = 0;
  const interval = setInterval(() => {
    subscriber.next({ count: count++ });
  }, 1000);
  
  return () => clearInterval(interval);
});

const asyncIterable = observableToAsyncIterable(observable);

// Add cancellation support
const cancellableIterable = withCancel(asyncIterable, () => {
  console.log('Subscription cancelled');
});

// Use in GraphQL subscription resolver
const subscriptionResolver = {
  Subscription: {
    counter: {
      subscribe: () => cancellableIterable
    }
  }
};

// Sequential async processing
const userIds = ['1', '2', '3', '4'];
const processedUsers = await promiseReduce(
  userIds,
  async (acc, userId) => {
    const user = await fetchUser(userId);
    return [...acc, { ...user, processed: true }];
  },
  []
);

// Handle abort signals
const abortController = new AbortController();
const cleanup = registerAbortSignalListener(
  abortController.signal,
  () => {
    console.log('Operation was aborted');
    // Cleanup resources
  }
);

// Later: abortController.abort() will trigger cleanup

Type Guards

Type guard functions for runtime type checking and validation.

/**
 * Check if a value is a GraphQL DocumentNode
 * @param value - Value to check
 * @returns True if value is a DocumentNode
 */
function isDocumentNode(value: any): value is DocumentNode;

/**
 * Check if a value is object-like (object but not null)
 * @param value - Value to check
 * @returns True if value is object-like
 */
function isObjectLike(value: any): value is Record<string, any>;

/**
 * Check if a value is a Promise
 * @param value - Value to check  
 * @returns True if value is a Promise
 */
function isPromise(value: any): value is Promise<any>;

/**
 * Check if a value matches specified criteria
 * @param value - Value to test
 * @param criteria - Criteria to match against
 * @returns True if value matches criteria
 */
function valueMatchesCriteria(value: any, criteria: any): boolean;

Usage Examples:

import { 
  isDocumentNode, 
  isObjectLike, 
  isPromise, 
  valueMatchesCriteria 
} from "@graphql-tools/utils";

// Validate GraphQL documents
function processDocument(input: unknown) {
  if (isDocumentNode(input)) {
    console.log('Processing GraphQL document with', input.definitions.length, 'definitions');
    return input;
  }
  throw new Error('Invalid document provided');
}

// Safe object property access
function getProperty(obj: unknown, key: string) {
  if (isObjectLike(obj)) {
    return obj[key];
  }
  return undefined;
}

// Handle async/sync values uniformly
async function normalizeValue<T>(value: T | Promise<T>): Promise<T> {
  if (isPromise(value)) {
    return await value;
  }
  return value;
}

// Flexible value matching
const users = [
  { name: 'John', age: 30, active: true },
  { name: 'Jane', age: 25, active: false },
  { name: 'Bob', age: 35, active: true }
];

// Find users matching criteria
const activeAdults = users.filter(user => 
  valueMatchesCriteria(user, { active: true }) &&
  user.age >= 18
);

Data Structures

Specialized data structures for GraphQL operations and general use.

/**
 * Specialized Map for accumulating arrays of values by key
 */
class AccumulatorMap<T> extends Map<string, T[]> {
  /**
   * Add an item to the array for the specified key
   * @param key - Key to add item under
   * @param item - Item to add to the array
   */
  add(key: string, item: T): void;
  
  /**
   * Get all items for a key, returning empty array if key doesn't exist
   * @param key - Key to get items for
   * @returns Array of items for the key
   */
  get(key: string): T[];
}

Usage Examples:

import { AccumulatorMap } from "@graphql-tools/utils";

// Track field selections by type
const fieldSelections = new AccumulatorMap<string>();

// Add selections for different types
fieldSelections.add('User', 'id');
fieldSelections.add('User', 'name');
fieldSelections.add('User', 'email');
fieldSelections.add('Post', 'title');
fieldSelections.add('Post', 'content');

// Get all fields for a type
console.log('User fields:', fieldSelections.get('User')); 
// Result: ['id', 'name', 'email']

console.log('Post fields:', fieldSelections.get('Post')); 
// Result: ['title', 'content']

// Check if any fields selected for a type
if (fieldSelections.get('Comment').length === 0) {
  console.log('No fields selected for Comment type');
}

// Use in GraphQL resolver optimization
const resolveUser = (source, args, context, info) => {
  const selections = new AccumulatorMap<string>();
  
  // Collect all selected fields
  collectFields(info.schema, {}, {}, info.returnType, info.fieldNodes[0].selectionSet)
    .fields.forEach((fieldNodes, fieldName) => {
      selections.add('User', fieldName);
    });
  
  // Optimize database query based on selections
  const selectedFields = selections.get('User');
  return context.db.findUser(args.id, { fields: selectedFields });
};

Performance Monitoring

Tools for performance monitoring and debugging GraphQL operations.

/**
 * Create a debug timer for measuring execution duration
 * @param label - Label for the timer
 * @returns Function to call when operation completes
 */
function debugTimer(label: string): () => void;

/**
 * Object inspection utility with GraphQL-aware formatting
 * @param object - Object to inspect
 * @param options - Inspection options
 * @returns Formatted string representation
 */
function inspect(object: any, options?: InspectOptions): string;

interface InspectOptions {
  depth?: number;
  colors?: boolean;
  compact?: boolean;
  breakLength?: number;
}

Usage Examples:

import { debugTimer, inspect } from "@graphql-tools/utils";

// Measure resolver performance
const resolveUser = async (source, args, context, info) => {
  const timer = debugTimer(`User resolver for ID: ${args.id}`);
  
  try {
    const user = await context.db.findUser(args.id);
    return user;
  } finally {
    timer(); // Logs execution time
  }
};

// Measure schema transformation
const transformSchema = (schema) => {
  const timer = debugTimer('Schema transformation');
  
  const transformed = mapSchema(schema, {
    [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
      // Transform field
      return fieldConfig;
    }
  });
  
  timer(); // Logs transformation time
  return transformed;
};

// Debug complex objects
const debugResolver = (source, args, context, info) => {
  console.log('Resolver called with:');
  console.log('Args:', inspect(args, { depth: 3, colors: true }));
  console.log('Context keys:', inspect(Object.keys(context)));
  console.log('Info path:', inspect(info.path));
  
  return resolveData(args);
};

// Performance monitoring for field collection
const collectFieldsWithTiming = (schema, fragments, variables, type, selectionSet) => {
  const timer = debugTimer('Field collection');
  const result = collectFields(schema, fragments, variables, type, selectionSet);
  timer();
  
  console.log('Collected fields:', inspect(Object.keys(result.fields)));
  return result;
};

Helper Functions

Additional utility functions for common GraphQL operations.

/**
 * Get description from an object, handling both string and object formats
 * @param object - Object that may contain description
 * @returns Description string or undefined
 */
function descriptionFromObject(object: any): string | undefined;

/**
 * Get leading comment block from AST node
 * @param node - AST node to extract comment from
 * @returns Comment text or undefined
 */
function getLeadingCommentBlock(node: ASTNode): string | undefined;

/**
 * Get trailing comment block from AST node
 * @param node - AST node to extract comment from
 * @returns Comment text or undefined
 */
function getTrailingCommentBlock(node: ASTNode): string | undefined;

/**
 * Update an argument in a field configuration
 * @param fieldConfig - Field configuration to update
 * @param argName - Name of argument to update
 * @param argUpdate - New argument configuration
 * @returns Updated field configuration
 */
function updateArgument(
  fieldConfig: GraphQLFieldConfig<any, any>,
  argName: string,
  argUpdate: GraphQLArgumentConfig
): GraphQLFieldConfig<any, any>;

/**
 * Transform input values in schema recursively
 * @param schema - Schema to transform
 * @param transform - Transformation function
 * @returns Schema with transformed input values
 */
function transformInputValue(
  schema: GraphQLSchema,
  transform: (type: GraphQLInputType, value: any) => any
): GraphQLSchema;

Usage Examples:

import { 
  descriptionFromObject, 
  getLeadingCommentBlock, 
  updateArgument,
  transformInputValue 
} from "@graphql-tools/utils";

// Extract descriptions from various sources
const typeDescription = descriptionFromObject(typeDefinition);
console.log('Type description:', typeDescription);

// Extract comments from AST nodes
const comment = getLeadingCommentBlock(fieldDefinitionNode);
if (comment) {
  console.log('Field comment:', comment);
}

// Update field arguments
const updatedField = updateArgument(
  userField,
  'limit',
  {
    type: GraphQLInt,
    defaultValue: 10,
    description: 'Maximum number of results'
  }
);

// Transform default values in schema
const schemaWithTransformedDefaults = transformInputValue(
  schema,
  (type, value) => {
    if (type === GraphQLString && typeof value === 'string') {
      return value.trim().toLowerCase();
    }
    return value;
  }
);

Additional Helper Functions

More utility functions for common GraphQL and general programming tasks.

/**
 * Checks if the given string is a valid URL
 * @param str - The string to validate as a URL
 * @returns Boolean indicating whether the string is a valid URL
 */
function isUrl(str: string): boolean;

/**
 * Ensure a value is an array
 * @param fns - Value that may be single item or array
 * @returns Array containing the value(s)
 */
function asArray<T>(fns: T | T[]): T[];

/**
 * Determines if a given input is a valid GraphQL document string
 * @param str - The input to validate as a GraphQL document
 * @returns Boolean indicating whether the input is a valid GraphQL document string
 */
function isDocumentString(str: any): boolean;

/**
 * Checks whether the string contains any path illegal characters
 * @param str - String to validate as a file path
 * @returns Boolean indicating if string is a valid path
 */
function isValidPath(str: any): boolean;

/**
 * Compare two strings alphabetically
 * @param a - First string to compare
 * @param b - Second string to compare
 * @returns -1, 0, or 1 based on comparison
 */
function compareStrings<A, B>(a: A, b: B): number;

/**
 * Convert AST node to string representation
 * @param a - AST node to convert
 * @returns String representation of the node
 */
function nodeToString(a: ASTNode): string;

/**
 * Compare two AST nodes
 * @param a - First AST node
 * @param b - Second AST node
 * @param customFn - Optional custom comparison function
 * @returns Comparison result
 */
function compareNodes(a: ASTNode, b: ASTNode, customFn?: (a: any, b: any) => number): number;

/**
 * Type guard to check if value is not null or undefined
 * @param input - Value to check
 * @returns True if value is not null/undefined
 */
function isSome<T>(input: T): input is Exclude<T, null | undefined>;

/**
 * Assert that value is not null or undefined
 * @param input - Value to assert
 * @param message - Optional error message
 * @throws Error if value is null or undefined
 */
function assertSome<T>(
  input: T, 
  message?: string
): asserts input is Exclude<T, null | undefined>;

/**
 * Check if a value is iterable
 * @param value - Value to check
 * @returns True if value is iterable
 */
function isIterableObject(value: unknown): value is Iterable<unknown>;

/**
 * Safe hasOwnProperty check
 * @param obj - Object to check property on
 * @param prop - Property name to check
 * @returns True if object has the property
 */
function hasOwnProperty(obj: unknown, prop: string): boolean;

/**
 * Get description node from an object
 * @param obj - Object that may contain description
 * @returns Description string node or undefined
 */
function getDescriptionNode(obj: any): StringValueNode | undefined;

Usage Examples:

import { 
  isUrl, 
  asArray, 
  isDocumentString, 
  isSome, 
  assertSome, 
  hasOwnProperty 
} from "@graphql-tools/utils";

// URL validation
if (isUrl("https://example.com/graphql")) {
  console.log("Valid GraphQL endpoint URL");
}

// Ensure array format
const resolvers = asArray(singleResolver); // Always returns array

// Validate GraphQL document strings
if (isDocumentString(userInput)) {
  const document = parse(userInput);
  console.log("Valid GraphQL document");
}

// Safe null checks
const user = getUser();
if (isSome(user)) {
  // TypeScript knows user is not null here
  console.log(user.name);
}

// Assertion with better error messages
assertSome(config.apiKey, "API key is required for authentication");

// Safe property access
if (hasOwnProperty(data, 'fieldName')) {
  console.log('Field exists:', data.fieldName);
}

docs

ast-operations.md

directives.md

execution-resolution.md

field-type-operations.md

index.md

schema-transformation.md

utilities.md

tile.json