or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

caching-performance.mdcontext-types.mderror-handling.mdindex.mdlogging-system.mdplugin-system.mdrequest-processing.mdresult-processing.mdschema-management.mdserver-configuration.mdsubscription-system.md
tile.json

result-processing.mddocs/

Result Processing

Flexible result processing system supporting different response formats including regular JSON, Server-Sent Events for subscriptions, and multipart responses for file uploads with content negotiation.

Capabilities

Result Processors Plugin

Main plugin for handling result processing with different output formats.

/**
 * Result processors plugin for handling different response formats
 * @returns Plugin instance
 */
function useResultProcessors(): Plugin;

Result Processor Types

Core types and interfaces for result processing functionality.

/**
 * Result processor function type
 */
type ResultProcessor = (
  executionResult: ExecutionResultWithSerializer,
  fetchAPI: FetchAPI
) => Response | Promise<Response>;

/**
 * Input types for result processors
 */
type ResultProcessorInput =
  | ExecutionResult
  | AsyncIterable<ExecutionResult>
  | Response;

/**
 * Execution result with serializer support
 */
type ExecutionResultWithSerializer<TData = any, TExtensions = any> = ExecutionResult<TData, TExtensions> & {
  stringify?: (result: ExecutionResult) => string;
};

Regular Result Processing

Functions for processing standard JSON GraphQL responses.

/**
 * Process regular GraphQL execution results as JSON responses
 * @param input - Execution result to process
 * @param fetchAPI - Fetch API implementation
 * @returns HTTP response with JSON content
 */
function processRegularResult(
  input: ResultProcessorInput,
  fetchAPI: FetchAPI
): Response;

Server-Sent Events Processing

Functions for processing subscription results as Server-Sent Events.

/**
 * Get Server-Sent Events result processor for subscriptions
 * @returns Result processor for SSE format
 */
function getSSEProcessor(): ResultProcessor;

Multipart Result Processing

Functions for processing multipart responses with file attachments.

/**
 * Process multipart results for file uploads and complex responses
 * @param result - Result to process as multipart
 * @param fetchAPI - Fetch API implementation
 * @returns HTTP response with multipart content
 */
function processMultipartResult(
  result: ResultProcessorInput,
  fetchAPI: FetchAPI
): Response;

Result Stringification

Utility functions for serializing execution results to JSON strings.

/**
 * Stringify GraphQL execution result without internal properties
 * @param result - Execution result to stringify
 * @returns JSON string without internal properties
 */
function jsonStringifyResultWithoutInternals(result: ExecutionResult): string;

/**
 * Remove internal properties from result errors
 * @param result - Execution result to clean
 * @returns Execution result with cleaned errors
 */
function omitInternalsFromResultErrors(result: ExecutionResult): ExecutionResult;

Content Negotiation

Functions for handling Accept header content negotiation.

/**
 * Get media types from request Accept header in priority order
 * @param request - HTTP request object
 * @returns Array of media types in preference order
 */
function getMediaTypesForRequestInOrder(request: Request): string[];

/**
 * Check if requested media type matches processor media type
 * @param askedMediaType - Media type requested by client
 * @param processorMediaType - Media type supported by processor
 * @returns True if media types are compatible
 */
function isMatchingMediaType(
  askedMediaType: string,
  processorMediaType: string
): boolean;

Result Processing Hook Types

Hook types for customizing result processing behavior.

/**
 * Result processing hook type
 */
type OnResultProcess<TServerContext> = (
  payload: OnResultProcessEventPayload<TServerContext>
) => void | Promise<void>;

/**
 * Result processing event payload
 */
interface OnResultProcessEventPayload<TServerContext> {
  /** Result to be processed */
  result: ResultProcessorInput;
  /** Function to modify the result */
  setResult: (result: ResultProcessorInput) => void;
  /** Server context */
  serverContext?: TServerContext;
  /** Original HTTP request */
  request: Request;
  /** Fetch API implementation */
  fetchAPI: FetchAPI;
}

/**
 * Execution result hook type
 */
type OnExecutionResultHook<TServerContext> = (
  payload: OnExecutionResultEventPayload<TServerContext>
) => void | Promise<void>;

/**
 * Execution result event payload
 */
interface OnExecutionResultEventPayload<TServerContext> {
  /** Execution result from GraphQL engine */
  result: ExecutionResult;
  /** Function to modify the result */
  setResult: (result: ExecutionResult | AsyncIterable<ExecutionResult>) => void;
  /** Server context */
  serverContext?: TServerContext;
}

Usage Examples:

import { 
  createYoga,
  useResultProcessors,
  processRegularResult,
  getSSEProcessor,
  processMultipartResult,
  jsonStringifyResultWithoutInternals
} from 'graphql-yoga';

// Basic result processing setup
const yoga = createYoga({
  schema: mySchema,
  plugins: [
    useResultProcessors()
  ]
});

// Custom result processor for CSV format
function createCSVProcessor(): ResultProcessor {
  return (executionResult, fetchAPI) => {
    if (executionResult.data && Array.isArray(executionResult.data.items)) {
      const csv = convertToCSV(executionResult.data.items);
      return new fetchAPI.Response(csv, {
        headers: {
          'Content-Type': 'text/csv',
          'Content-Disposition': 'attachment; filename="data.csv"'
        }
      });
    }
    
    // Fall back to regular processing
    return processRegularResult(executionResult, fetchAPI);
  };
}

// Server with custom result processing
const customResultYoga = createYoga({
  schema: mySchema,
  plugins: [
    {
      onResultProcess({ result, setResult, request, fetchAPI }) {
        const acceptHeader = request.headers.get('accept');
        
        if (acceptHeader?.includes('text/csv')) {
          const processor = createCSVProcessor();
          const response = processor(result as ExecutionResult, fetchAPI);
          setResult(response);
        }
      }
    },
    useResultProcessors()
  ]
});

// Subscription handling with SSE
const subscriptionYoga = createYoga({
  schema: subscriptionSchema,
  plugins: [
    {
      onResultProcess({ result, setResult, request, fetchAPI }) {
        const acceptHeader = request.headers.get('accept');
        
        if (acceptHeader?.includes('text/event-stream')) {
          const processor = getSSEProcessor();
          const response = processor(result as ExecutionResult, fetchAPI);
          setResult(response);
        }
      }
    },
    useResultProcessors()
  ]
});

// File upload with multipart responses
const uploadYoga = createYoga({
  schema: uploadSchema,
  multipart: true,
  plugins: [
    {
      onResultProcess({ result, setResult, request, fetchAPI }) {
        // Check if result contains file data
        if (hasFileData(result)) {
          const response = processMultipartResult(result, fetchAPI);
          setResult(response);
        }
      }
    },
    useResultProcessors()
  ]
});

// Custom result transformation
const transformYoga = createYoga({
  schema: mySchema,
  plugins: [
    {
      onExecutionResult({ result, setResult }) {
        // Add metadata to all responses
        const transformedResult = {
          ...result,
          extensions: {
            ...result.extensions,
            timestamp: new Date().toISOString(),
            version: '1.0.0'
          }
        };
        setResult(transformedResult);
      },
      
      onResultProcess({ result, setResult, request }) {
        // Custom serialization
        if (typeof result === 'object' && 'data' in result) {
          const cleanResult = omitInternalsFromResultErrors(result);
          const jsonString = jsonStringifyResultWithoutInternals(cleanResult);
          
          const response = new Response(jsonString, {
            headers: {
              'Content-Type': 'application/json',
              'X-Request-ID': request.headers.get('x-request-id') || 'unknown'
            }
          });
          
          setResult(response);
        }
      }
    },
    useResultProcessors()
  ]
});

// Content negotiation example
function createContentNegotiationPlugin(): Plugin {
  return {
    onResultProcess({ result, setResult, request, fetchAPI }) {
      const mediaTypes = getMediaTypesForRequestInOrder(request);
      
      for (const mediaType of mediaTypes) {
        if (isMatchingMediaType(mediaType, 'application/json')) {
          const response = processRegularResult(result, fetchAPI);
          setResult(response);
          return;
        }
        
        if (isMatchingMediaType(mediaType, 'text/event-stream')) {
          const processor = getSSEProcessor();
          const response = processor(result as ExecutionResult, fetchAPI);
          setResult(response);
          return;
        }
        
        if (isMatchingMediaType(mediaType, 'multipart/mixed')) {
          const response = processMultipartResult(result, fetchAPI);
          setResult(response);
          return;
        }
      }
      
      // Default to JSON
      const response = processRegularResult(result, fetchAPI);
      setResult(response);
    }
  };
}

// Error handling in result processing
const errorHandlingYoga = createYoga({
  schema: mySchema,
  plugins: [
    {
      onExecutionResult({ result, setResult }) {
        if (result.errors) {
          // Log errors
          console.error('GraphQL execution errors:', result.errors);
          
          // Transform errors for client
          const transformedResult = {
            ...result,
            errors: result.errors.map(error => ({
              message: error.message,
              locations: error.locations,
              path: error.path,
              // Remove sensitive error details
              extensions: {
                code: error.extensions?.code || 'INTERNAL_ERROR'
              }
            }))
          };
          
          setResult(transformedResult);
        }
      }
    },
    useResultProcessors()
  ]
});