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..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'); }); @@ -111,7 +110,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 +119,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 638f1d3725..a1ae06d712 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,17 +20,9 @@ import type { export interface ExecutionResult< TData = ObjMap, TExtensions = ObjMap, + TError extends GraphQLFormattedError = GraphQLError, > { - errors?: ReadonlyArray; - data?: TData | null; - extensions?: TExtensions; -} - -export interface FormattedExecutionResult< - TData = ObjMap, - TExtensions = ObjMap, -> { - errors?: ReadonlyArray; + errors?: ReadonlyArray; data?: TData | null; extensions?: TExtensions; } @@ -38,10 +31,15 @@ export interface ExperimentalIncrementalExecutionResults< TInitial = ObjMap, TSubsequent = unknown, TExtensions = ObjMap, + TError extends GraphQLFormattedError = GraphQLError, > { - initialResult: InitialIncrementalExecutionResult; + initialResult: InitialIncrementalExecutionResult< + TInitial, + TExtensions, + TError + >; subsequentResults: AsyncGenerator< - SubsequentIncrementalExecutionResult, + SubsequentIncrementalExecutionResult, void, void >; @@ -50,105 +48,57 @@ export interface ExperimentalIncrementalExecutionResults< export interface InitialIncrementalExecutionResult< TData = ObjMap, TExtensions = ObjMap, -> extends ExecutionResult { + TError extends GraphQLFormattedError = GraphQLError, +> extends ExecutionResult { data: TData; pending: ReadonlyArray; hasNext: true; extensions?: TExtensions; } -export interface FormattedInitialIncrementalExecutionResult< - TData = ObjMap, - TExtensions = ObjMap, -> extends FormattedExecutionResult { - data: TData; - pending: ReadonlyArray; - hasNext: boolean; - extensions?: TExtensions; -} - export interface SubsequentIncrementalExecutionResult< TData = unknown, TExtensions = ObjMap, + TError extends GraphQLFormattedError = GraphQLError, > { pending?: ReadonlyArray; - incremental?: ReadonlyArray>; - completed?: ReadonlyArray; + incremental?: ReadonlyArray>; + completed?: ReadonlyArray>; hasNext: boolean; extensions?: TExtensions; } -export interface FormattedSubsequentIncrementalExecutionResult< - TData = unknown, - TExtensions = ObjMap, -> { - hasNext: boolean; - pending?: ReadonlyArray; - incremental?: ReadonlyArray>; - completed?: ReadonlyArray; - extensions?: TExtensions; -} - -interface ExecutionGroupResult> { - errors?: ReadonlyArray; - data: TData; -} - export interface IncrementalDeferResult< TData = ObjMap, TExtensions = ObjMap, -> extends ExecutionGroupResult { - id: string; - subPath?: ReadonlyArray; - extensions?: TExtensions; -} - -export interface FormattedIncrementalDeferResult< - TData = ObjMap, - TExtensions = ObjMap, + TError extends GraphQLFormattedError = GraphQLError, > { - errors?: ReadonlyArray; + errors?: ReadonlyArray; data: TData; id: string; subPath?: ReadonlyArray; extensions?: TExtensions; } -interface StreamItemsRecordResult> { - errors?: ReadonlyArray; - items: TData; -} - export interface IncrementalStreamResult< TData = ReadonlyArray, TExtensions = ObjMap, -> extends StreamItemsRecordResult { - id: string; - subPath?: ReadonlyArray; - extensions?: TExtensions; -} - -export interface FormattedIncrementalStreamResult< - TData = Array, - TExtensions = ObjMap, + TError extends GraphQLFormattedError = GraphQLError, > { - errors?: ReadonlyArray; + errors?: ReadonlyArray; items: TData; id: string; subPath?: ReadonlyArray; extensions?: TExtensions; } -export type IncrementalResult> = - | IncrementalDeferResult - | IncrementalStreamResult; - -export type FormattedIncrementalResult< +export type IncrementalResult< TData = unknown, TExtensions = ObjMap, + TError extends GraphQLFormattedError = GraphQLError, > = - | FormattedIncrementalDeferResult - | FormattedIncrementalStreamResult; + | IncrementalDeferResult + | IncrementalStreamResult; export interface PendingResult { id: string; @@ -156,15 +106,64 @@ export interface PendingResult { label?: string; } -export interface CompletedResult { +export interface CompletedResult< + TError extends GraphQLFormattedError = GraphQLError, +> { id: string; - errors?: ReadonlyArray; + errors?: ReadonlyArray; } -export interface FormattedCompletedResult { - id: string; - 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, +> = IncrementalResult; + +export interface FormattedCompletedResult + extends CompletedResult {} export function isPendingExecutionGroup( incrementalDataRecord: IncrementalDataRecord, @@ -185,7 +184,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 +268,10 @@ export interface StreamRecord { export interface StreamItemsResult { streamRecord: StreamRecord; errors?: ReadonlyArray; - result?: StreamItemsRecordResult; + result?: { + errors?: ReadonlyArray; + items: ReadonlyArray; + }; newDeferredFragmentRecords?: | ReadonlyArray | undefined;