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.
Main plugin for handling result processing with different output formats.
/**
* Result processors plugin for handling different response formats
* @returns Plugin instance
*/
function useResultProcessors(): Plugin;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;
};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;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;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;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;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;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()
]
});