From 5b1cd6ff82eb719adcd0cbf2e09b409d5d6eda11 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 28 Oct 2025 13:40:55 +0200 Subject: [PATCH 1/5] polish: unify our formatted and non-formatted execution result types via using a new generic type parameter with this pattern: export interface ExecutionResult< TData = ObjMap, TExtensions = ObjMap, TError extends GraphQLError | GraphQLFormattedError = GraphQLError, > { errors?: ReadonlyArray; data?: TData | null; extensions?: TExtensions; } export interface FormattedExecutionResult< TData = ObjMap, TExtensions = ObjMap, > extends ExecutionResult {} This should remove maintenance burden, as we explicitly say that the formatted types only differ from the unformatted in that they have a formatted error type. --- src/execution/types.ts | 117 +++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/src/execution/types.ts b/src/execution/types.ts index 638f1d3725..5fac9350fb 100644 --- a/src/execution/types.ts +++ b/src/execution/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-empty-object-type */ import type { BoxedPromiseOrValue } from '../jsutils/BoxedPromiseOrValue.js'; import type { ObjMap } from '../jsutils/ObjMap.js'; import type { Path } from '../jsutils/Path.js'; @@ -19,8 +20,9 @@ import type { export interface ExecutionResult< TData = ObjMap, TExtensions = ObjMap, + TError extends GraphQLError | GraphQLFormattedError = GraphQLError, > { - errors?: ReadonlyArray; + errors?: ReadonlyArray; data?: TData | null; extensions?: TExtensions; } @@ -28,29 +30,42 @@ export interface ExecutionResult< export interface FormattedExecutionResult< TData = ObjMap, TExtensions = ObjMap, -> { - errors?: ReadonlyArray; - data?: TData | null; - extensions?: TExtensions; -} +> extends ExecutionResult {} export interface ExperimentalIncrementalExecutionResults< TInitial = ObjMap, TSubsequent = unknown, TExtensions = ObjMap, + TError extends GraphQLError | GraphQLFormattedError = GraphQLError, > { - initialResult: InitialIncrementalExecutionResult; + initialResult: InitialIncrementalExecutionResult< + TInitial, + TExtensions, + TError + >; subsequentResults: AsyncGenerator< - SubsequentIncrementalExecutionResult, + SubsequentIncrementalExecutionResult, void, void >; } +export interface FormattedExperimentalIncrementalExecutionResults< + TInitial = ObjMap, + TSubsequent = unknown, + TExtensions = ObjMap, +> extends ExperimentalIncrementalExecutionResults< + TInitial, + TSubsequent, + TExtensions, + GraphQLFormattedError + > {} + export interface InitialIncrementalExecutionResult< TData = ObjMap, TExtensions = ObjMap, -> extends ExecutionResult { + TError extends GraphQLError | GraphQLFormattedError = GraphQLError, +> extends ExecutionResult { data: TData; pending: ReadonlyArray; hasNext: true; @@ -60,20 +75,20 @@ export interface InitialIncrementalExecutionResult< export interface FormattedInitialIncrementalExecutionResult< TData = ObjMap, TExtensions = ObjMap, -> extends FormattedExecutionResult { - data: TData; - pending: ReadonlyArray; - hasNext: boolean; - extensions?: TExtensions; -} +> extends InitialIncrementalExecutionResult< + TData, + TExtensions, + GraphQLFormattedError + > {} export interface SubsequentIncrementalExecutionResult< TData = unknown, TExtensions = ObjMap, + TError extends GraphQLError | GraphQLFormattedError = GraphQLError, > { pending?: ReadonlyArray; incremental?: ReadonlyArray>; - completed?: ReadonlyArray; + completed?: ReadonlyArray>; hasNext: boolean; extensions?: TExtensions; } @@ -81,23 +96,19 @@ export interface SubsequentIncrementalExecutionResult< export interface FormattedSubsequentIncrementalExecutionResult< TData = unknown, TExtensions = ObjMap, -> { - hasNext: boolean; - pending?: ReadonlyArray; - incremental?: ReadonlyArray>; - completed?: ReadonlyArray; - extensions?: TExtensions; -} - -interface ExecutionGroupResult> { - errors?: ReadonlyArray; - data: TData; -} +> extends SubsequentIncrementalExecutionResult< + TData, + TExtensions, + GraphQLFormattedError + > {} export interface IncrementalDeferResult< TData = ObjMap, TExtensions = ObjMap, -> extends ExecutionGroupResult { + TError extends GraphQLError | GraphQLFormattedError = GraphQLError, +> { + errors?: ReadonlyArray; + data: TData; id: string; subPath?: ReadonlyArray; extensions?: TExtensions; @@ -106,23 +117,15 @@ export interface IncrementalDeferResult< export interface FormattedIncrementalDeferResult< TData = ObjMap, TExtensions = ObjMap, -> { - errors?: ReadonlyArray; - data: TData; - id: string; - subPath?: ReadonlyArray; - extensions?: TExtensions; -} - -interface StreamItemsRecordResult> { - errors?: ReadonlyArray; - items: TData; -} +> extends IncrementalDeferResult {} export interface IncrementalStreamResult< TData = ReadonlyArray, TExtensions = ObjMap, -> extends StreamItemsRecordResult { + TError extends GraphQLError | GraphQLFormattedError = GraphQLError, +> { + errors?: ReadonlyArray; + items: TData; id: string; subPath?: ReadonlyArray; extensions?: TExtensions; @@ -131,13 +134,7 @@ export interface IncrementalStreamResult< export interface FormattedIncrementalStreamResult< TData = Array, TExtensions = ObjMap, -> { - errors?: ReadonlyArray; - items: TData; - id: string; - subPath?: ReadonlyArray; - extensions?: TExtensions; -} +> extends IncrementalStreamResult {} export type IncrementalResult> = | IncrementalDeferResult @@ -156,15 +153,15 @@ export interface PendingResult { label?: string; } -export interface CompletedResult { +export interface CompletedResult< + TError extends GraphQLError | GraphQLFormattedError = GraphQLError, +> { id: string; - errors?: ReadonlyArray; + errors?: ReadonlyArray; } -export interface FormattedCompletedResult { - id: string; - errors?: ReadonlyArray; -} +export interface FormattedCompletedResult + extends CompletedResult {} export function isPendingExecutionGroup( incrementalDataRecord: IncrementalDataRecord, @@ -185,7 +182,10 @@ export function isCompletedExecutionGroup( export interface SuccessfulExecutionGroup { pendingExecutionGroup: PendingExecutionGroup; path: Array; - result: ExecutionGroupResult; + result: { + errors?: ReadonlyArray; + data: ObjMap; + }; newDeferredFragmentRecords: ReadonlyArray | undefined; incrementalDataRecords: ReadonlyArray | undefined; errors?: never; @@ -266,7 +266,10 @@ export interface StreamRecord { export interface StreamItemsResult { streamRecord: StreamRecord; errors?: ReadonlyArray; - result?: StreamItemsRecordResult; + result?: { + errors?: ReadonlyArray; + items: ReadonlyArray; + }; newDeferredFragmentRecords?: | ReadonlyArray | undefined; From ae9b9600d31e8b37034176d2dbb8e34b4b4f3d28 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 28 Oct 2025 14:52:29 +0200 Subject: [PATCH 2/5] group the formatted types together --- src/execution/types.ts | 102 ++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/execution/types.ts b/src/execution/types.ts index 5fac9350fb..78cec2f575 100644 --- a/src/execution/types.ts +++ b/src/execution/types.ts @@ -27,11 +27,6 @@ export interface ExecutionResult< extensions?: TExtensions; } -export interface FormattedExecutionResult< - TData = ObjMap, - TExtensions = ObjMap, -> extends ExecutionResult {} - export interface ExperimentalIncrementalExecutionResults< TInitial = ObjMap, TSubsequent = unknown, @@ -50,17 +45,6 @@ export interface ExperimentalIncrementalExecutionResults< >; } -export interface FormattedExperimentalIncrementalExecutionResults< - TInitial = ObjMap, - TSubsequent = unknown, - TExtensions = ObjMap, -> extends ExperimentalIncrementalExecutionResults< - TInitial, - TSubsequent, - TExtensions, - GraphQLFormattedError - > {} - export interface InitialIncrementalExecutionResult< TData = ObjMap, TExtensions = ObjMap, @@ -72,15 +56,6 @@ export interface InitialIncrementalExecutionResult< extensions?: TExtensions; } -export interface FormattedInitialIncrementalExecutionResult< - TData = ObjMap, - TExtensions = ObjMap, -> extends InitialIncrementalExecutionResult< - TData, - TExtensions, - GraphQLFormattedError - > {} - export interface SubsequentIncrementalExecutionResult< TData = unknown, TExtensions = ObjMap, @@ -93,15 +68,6 @@ export interface SubsequentIncrementalExecutionResult< extensions?: TExtensions; } -export interface FormattedSubsequentIncrementalExecutionResult< - TData = unknown, - TExtensions = ObjMap, -> extends SubsequentIncrementalExecutionResult< - TData, - TExtensions, - GraphQLFormattedError - > {} - export interface IncrementalDeferResult< TData = ObjMap, TExtensions = ObjMap, @@ -114,11 +80,6 @@ export interface IncrementalDeferResult< extensions?: TExtensions; } -export interface FormattedIncrementalDeferResult< - TData = ObjMap, - TExtensions = ObjMap, -> extends IncrementalDeferResult {} - export interface IncrementalStreamResult< TData = ReadonlyArray, TExtensions = ObjMap, @@ -131,22 +92,10 @@ export interface IncrementalStreamResult< extensions?: TExtensions; } -export interface FormattedIncrementalStreamResult< - TData = Array, - TExtensions = ObjMap, -> extends IncrementalStreamResult {} - export type IncrementalResult> = | IncrementalDeferResult | IncrementalStreamResult; -export type FormattedIncrementalResult< - TData = unknown, - TExtensions = ObjMap, -> = - | FormattedIncrementalDeferResult - | FormattedIncrementalStreamResult; - export interface PendingResult { id: string; path: ReadonlyArray; @@ -160,6 +109,57 @@ export interface CompletedResult< errors?: ReadonlyArray; } +export interface FormattedExecutionResult< + TData = ObjMap, + TExtensions = ObjMap, +> extends ExecutionResult {} + +export interface FormattedExperimentalIncrementalExecutionResults< + TInitial = ObjMap, + TSubsequent = unknown, + TExtensions = ObjMap, +> extends ExperimentalIncrementalExecutionResults< + TInitial, + TSubsequent, + TExtensions, + GraphQLFormattedError + > {} + +export interface FormattedInitialIncrementalExecutionResult< + TData = ObjMap, + TExtensions = ObjMap, +> extends InitialIncrementalExecutionResult< + TData, + TExtensions, + GraphQLFormattedError + > {} + +export interface FormattedSubsequentIncrementalExecutionResult< + TData = unknown, + TExtensions = ObjMap, +> extends SubsequentIncrementalExecutionResult< + TData, + TExtensions, + GraphQLFormattedError + > {} + +export interface FormattedIncrementalDeferResult< + TData = ObjMap, + TExtensions = ObjMap, +> extends IncrementalDeferResult {} + +export interface FormattedIncrementalStreamResult< + TData = Array, + TExtensions = ObjMap, +> extends IncrementalStreamResult {} + +export type FormattedIncrementalResult< + TData = unknown, + TExtensions = ObjMap, +> = + | FormattedIncrementalDeferResult + | FormattedIncrementalStreamResult; + export interface FormattedCompletedResult extends CompletedResult {} From fd4d2930b094e7aba63e5836e218cd708009d420 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 28 Oct 2025 15:00:18 +0200 Subject: [PATCH 3/5] add last missing generic --- src/execution/types.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/execution/types.ts b/src/execution/types.ts index 78cec2f575..f538a482ea 100644 --- a/src/execution/types.ts +++ b/src/execution/types.ts @@ -62,7 +62,7 @@ export interface SubsequentIncrementalExecutionResult< TError extends GraphQLError | GraphQLFormattedError = GraphQLError, > { pending?: ReadonlyArray; - incremental?: ReadonlyArray>; + incremental?: ReadonlyArray>; completed?: ReadonlyArray>; hasNext: boolean; extensions?: TExtensions; @@ -92,9 +92,13 @@ export interface IncrementalStreamResult< extensions?: TExtensions; } -export type IncrementalResult> = - | IncrementalDeferResult - | IncrementalStreamResult; +export type IncrementalResult< + TData = unknown, + TExtensions = ObjMap, + TError extends GraphQLError | GraphQLFormattedError = GraphQLError, +> = + | IncrementalDeferResult + | IncrementalStreamResult; export interface PendingResult { id: string; @@ -156,9 +160,7 @@ export interface FormattedIncrementalStreamResult< export type FormattedIncrementalResult< TData = unknown, TExtensions = ObjMap, -> = - | FormattedIncrementalDeferResult - | FormattedIncrementalStreamResult; +> = IncrementalResult; export interface FormattedCompletedResult extends CompletedResult {} From 69f4d400a7b2a806f83c49b8d12a14b908989122 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 28 Oct 2025 22:10:32 +0200 Subject: [PATCH 4/5] adjust GraphQLError type so it implements GraphQLFormattedError --- src/error/GraphQLError.ts | 51 +++++++++++++++++------- src/error/__tests__/GraphQLError-test.ts | 5 +-- src/execution/types.ts | 16 ++++---- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/error/GraphQLError.ts b/src/error/GraphQLError.ts index a2404d6040..aba88b8402 100644 --- a/src/error/GraphQLError.ts +++ b/src/error/GraphQLError.ts @@ -51,7 +51,7 @@ export interface GraphQLErrorOptions { * and stack trace, it also includes information about the locations in a * GraphQL document and/or execution result that correspond to the Error. */ -export class GraphQLError extends Error { +export class GraphQLError extends Error implements GraphQLFormattedError { /** * An array of `{ line, column }` locations within the source GraphQL document * which correspond to this error. @@ -62,7 +62,7 @@ export class GraphQLError extends Error { * * Enumerable, and appears in the result of JSON.stringify(). */ - readonly locations: ReadonlyArray | undefined; + readonly locations?: ReadonlyArray; /** * An array describing the JSON-path into the execution response which @@ -70,12 +70,12 @@ export class GraphQLError extends Error { * * Enumerable, and appears in the result of JSON.stringify(). */ - readonly path: ReadonlyArray | undefined; + readonly path?: ReadonlyArray; /** * An array of GraphQL AST Nodes corresponding to this error. */ - readonly nodes: ReadonlyArray | undefined; + readonly nodes?: ReadonlyArray; /** * The source GraphQL document for the first location of this error. @@ -83,23 +83,23 @@ export class GraphQLError extends Error { * Note that if this Error represents more than one node, the source may not * represent nodes after the first node. */ - readonly source: Source | undefined; + readonly source?: Source; /** * An array of character offsets within the source GraphQL document * which correspond to this error. */ - readonly positions: ReadonlyArray | undefined; + readonly positions?: ReadonlyArray; /** * The original error thrown from a field resolver during execution. */ - readonly originalError: Error | undefined; + readonly originalError?: Error; /** * Extension fields to add to the formatted error. */ - readonly extensions: GraphQLErrorExtensions; + readonly extensions?: GraphQLErrorExtensions; constructor(message: string, options: GraphQLErrorOptions = {}) { const { nodes, source, positions, path, originalError, extensions } = @@ -108,13 +108,21 @@ export class GraphQLError extends Error { super(message); this.name = 'GraphQLError'; - this.path = path ?? undefined; - this.originalError = originalError ?? undefined; + if (path) { + this.path = path; + } + + if (originalError) { + this.originalError = originalError; + } // Compute list of blame nodes. - this.nodes = undefinedIfEmpty( + const resolvedNodes = undefinedIfEmpty( Array.isArray(nodes) ? nodes : nodes ? [nodes] : undefined, ); + if (resolvedNodes) { + this.nodes = resolvedNodes; + } const nodeLocations = undefinedIfEmpty( this.nodes @@ -123,19 +131,32 @@ export class GraphQLError extends Error { ); // Compute locations in the source for the given nodes/positions. - this.source = source ?? nodeLocations?.[0]?.source; + const resolvedSource = source ?? nodeLocations?.[0]?.source; + if (resolvedSource) { + this.source = resolvedSource; + } - this.positions = positions ?? nodeLocations?.map((loc) => loc.start); + const resolvedPositions = + positions ?? nodeLocations?.map((loc) => loc.start); + if (resolvedPositions) { + this.positions = resolvedPositions; + } - this.locations = + const resolvedLocations = positions && source ? positions.map((pos) => getLocation(source, pos)) : nodeLocations?.map((loc) => getLocation(loc.source, loc.start)); + if (resolvedLocations) { + this.locations = resolvedLocations; + } const originalExtensions = isObjectLike(originalError?.extensions) ? originalError?.extensions : undefined; - this.extensions = extensions ?? originalExtensions ?? Object.create(null); + const resolvedExtensions = extensions ?? originalExtensions; + if (resolvedExtensions) { + this.extensions = resolvedExtensions; + } // Only properties prescribed by the spec should be enumerable. // Keep the rest as non-enumerable. diff --git a/src/error/__tests__/GraphQLError-test.ts b/src/error/__tests__/GraphQLError-test.ts index 7cfda2a5a3..06c6a45b2a 100644 --- a/src/error/__tests__/GraphQLError-test.ts +++ b/src/error/__tests__/GraphQLError-test.ts @@ -111,7 +111,7 @@ describe('GraphQLError', () => { }); }); - it('converts node without location to undefined source, positions and locations', () => { + it('converts node without location to error without source, positions and locations', () => { const fieldNodeNoLocation = { ...fieldNode, loc: undefined, @@ -120,9 +120,6 @@ describe('GraphQLError', () => { const e = new GraphQLError('msg', { nodes: fieldNodeNoLocation }); expect(e).to.deep.include({ nodes: [fieldNodeNoLocation], - source: undefined, - positions: undefined, - locations: undefined, }); }); diff --git a/src/execution/types.ts b/src/execution/types.ts index f538a482ea..a1ae06d712 100644 --- a/src/execution/types.ts +++ b/src/execution/types.ts @@ -20,7 +20,7 @@ import type { export interface ExecutionResult< TData = ObjMap, TExtensions = ObjMap, - TError extends GraphQLError | GraphQLFormattedError = GraphQLError, + TError extends GraphQLFormattedError = GraphQLError, > { errors?: ReadonlyArray; data?: TData | null; @@ -31,7 +31,7 @@ export interface ExperimentalIncrementalExecutionResults< TInitial = ObjMap, TSubsequent = unknown, TExtensions = ObjMap, - TError extends GraphQLError | GraphQLFormattedError = GraphQLError, + TError extends GraphQLFormattedError = GraphQLError, > { initialResult: InitialIncrementalExecutionResult< TInitial, @@ -48,7 +48,7 @@ export interface ExperimentalIncrementalExecutionResults< export interface InitialIncrementalExecutionResult< TData = ObjMap, TExtensions = ObjMap, - TError extends GraphQLError | GraphQLFormattedError = GraphQLError, + TError extends GraphQLFormattedError = GraphQLError, > extends ExecutionResult { data: TData; pending: ReadonlyArray; @@ -59,7 +59,7 @@ export interface InitialIncrementalExecutionResult< export interface SubsequentIncrementalExecutionResult< TData = unknown, TExtensions = ObjMap, - TError extends GraphQLError | GraphQLFormattedError = GraphQLError, + TError extends GraphQLFormattedError = GraphQLError, > { pending?: ReadonlyArray; incremental?: ReadonlyArray>; @@ -71,7 +71,7 @@ export interface SubsequentIncrementalExecutionResult< export interface IncrementalDeferResult< TData = ObjMap, TExtensions = ObjMap, - TError extends GraphQLError | GraphQLFormattedError = GraphQLError, + TError extends GraphQLFormattedError = GraphQLError, > { errors?: ReadonlyArray; data: TData; @@ -83,7 +83,7 @@ export interface IncrementalDeferResult< export interface IncrementalStreamResult< TData = ReadonlyArray, TExtensions = ObjMap, - TError extends GraphQLError | GraphQLFormattedError = GraphQLError, + TError extends GraphQLFormattedError = GraphQLError, > { errors?: ReadonlyArray; items: TData; @@ -95,7 +95,7 @@ export interface IncrementalStreamResult< export type IncrementalResult< TData = unknown, TExtensions = ObjMap, - TError extends GraphQLError | GraphQLFormattedError = GraphQLError, + TError extends GraphQLFormattedError = GraphQLError, > = | IncrementalDeferResult | IncrementalStreamResult; @@ -107,7 +107,7 @@ export interface PendingResult { } export interface CompletedResult< - TError extends GraphQLError | GraphQLFormattedError = GraphQLError, + TError extends GraphQLFormattedError = GraphQLError, > { id: string; errors?: ReadonlyArray; From 382d063fbffdceb6609f0784943cb8e4cc0a60ae Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Wed, 29 Oct 2025 12:14:32 +0200 Subject: [PATCH 5/5] fix test --- src/error/__tests__/GraphQLError-test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/error/__tests__/GraphQLError-test.ts b/src/error/__tests__/GraphQLError-test.ts index 06c6a45b2a..08bedcf101 100644 --- a/src/error/__tests__/GraphQLError-test.ts +++ b/src/error/__tests__/GraphQLError-test.ts @@ -32,7 +32,6 @@ describe('GraphQLError', () => { expect(e).to.deep.include({ name: 'GraphQLError', message: 'msg', - extensions: {}, }); expect(e.stack).to.be.a('string'); });