or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

ast-operations.mddirectives.mdexecution-resolution.mdfield-type-operations.mdindex.mdschema-transformation.mdutilities.md
tile.json

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);
}