Comparing version 17.0.0-alpha.1.canary.pr.3658.null to 17.0.0-alpha.1.canary.pr.3659.5dba20aef36112d13569d5f296ef967383e60d0f
@@ -18,3 +18,3 @@ import type { Maybe } from '../jsutils/Maybe'; | ||
export interface GraphQLErrorOptions { | ||
nodes?: ReadonlyArray<ASTNode> | ASTNode | null; | ||
nodes?: ReadonlyArray<ASTNode> | ASTNode | null | undefined; | ||
source?: Maybe<Source>; | ||
@@ -21,0 +21,0 @@ positions?: Maybe<ReadonlyArray<number>>; |
@@ -9,2 +9,10 @@ import type { ObjMap } from '../jsutils/ObjMap'; | ||
import type { GraphQLSchema } from '../type/schema'; | ||
export interface PatchFields { | ||
label: string | undefined; | ||
fields: Map<string, ReadonlyArray<FieldNode>>; | ||
} | ||
export interface FieldsAndPatches { | ||
fields: Map<string, ReadonlyArray<FieldNode>>; | ||
patches: Array<PatchFields>; | ||
} | ||
/** | ||
@@ -27,3 +35,3 @@ * Given a selectionSet, collects all of the fields and returns them. | ||
selectionSet: SelectionSetNode, | ||
): Map<string, ReadonlyArray<FieldNode>>; | ||
): FieldsAndPatches; | ||
/** | ||
@@ -47,2 +55,2 @@ * Given an array of field nodes, collects all of the subfields of the passed | ||
fieldNodes: ReadonlyArray<FieldNode>, | ||
): Map<string, ReadonlyArray<FieldNode>>; | ||
): FieldsAndPatches; |
@@ -5,2 +5,3 @@ import { AccumulatorMap } from '../jsutils/AccumulatorMap.js'; | ||
import { | ||
GraphQLDeferDirective, | ||
GraphQLIncludeDirective, | ||
@@ -28,2 +29,3 @@ GraphQLSkipDirective, | ||
const fields = new AccumulatorMap(); | ||
const patches = []; | ||
collectFieldsImpl( | ||
@@ -36,5 +38,6 @@ schema, | ||
fields, | ||
patches, | ||
new Set(), | ||
); | ||
return fields; | ||
return { fields, patches }; | ||
} | ||
@@ -60,2 +63,7 @@ /** | ||
const visitedFragmentNames = new Set(); | ||
const subPatches = []; | ||
const subFieldsAndPatches = { | ||
fields: subFieldNodes, | ||
patches: subPatches, | ||
}; | ||
for (const node of fieldNodes) { | ||
@@ -70,2 +78,3 @@ if (node.selectionSet) { | ||
subFieldNodes, | ||
subPatches, | ||
visitedFragmentNames, | ||
@@ -75,3 +84,3 @@ ); | ||
} | ||
return subFieldNodes; | ||
return subFieldsAndPatches; | ||
} | ||
@@ -86,2 +95,3 @@ // eslint-disable-next-line max-params | ||
fields, | ||
patches, | ||
visitedFragmentNames, | ||
@@ -105,11 +115,31 @@ ) { | ||
} | ||
collectFieldsImpl( | ||
schema, | ||
fragments, | ||
variableValues, | ||
runtimeType, | ||
selection.selectionSet, | ||
fields, | ||
visitedFragmentNames, | ||
); | ||
const defer = getDeferValues(variableValues, selection); | ||
if (defer) { | ||
const patchFields = new AccumulatorMap(); | ||
collectFieldsImpl( | ||
schema, | ||
fragments, | ||
variableValues, | ||
runtimeType, | ||
selection.selectionSet, | ||
patchFields, | ||
patches, | ||
visitedFragmentNames, | ||
); | ||
patches.push({ | ||
label: defer.label, | ||
fields: patchFields, | ||
}); | ||
} else { | ||
collectFieldsImpl( | ||
schema, | ||
fragments, | ||
variableValues, | ||
runtimeType, | ||
selection.selectionSet, | ||
fields, | ||
patches, | ||
visitedFragmentNames, | ||
); | ||
} | ||
break; | ||
@@ -119,9 +149,9 @@ } | ||
const fragName = selection.name.value; | ||
if ( | ||
visitedFragmentNames.has(fragName) || | ||
!shouldIncludeNode(variableValues, selection) | ||
) { | ||
if (!shouldIncludeNode(variableValues, selection)) { | ||
continue; | ||
} | ||
visitedFragmentNames.add(fragName); | ||
const defer = getDeferValues(variableValues, selection); | ||
if (visitedFragmentNames.has(fragName) && !defer) { | ||
continue; | ||
} | ||
const fragment = fragments[fragName]; | ||
@@ -134,11 +164,33 @@ if ( | ||
} | ||
collectFieldsImpl( | ||
schema, | ||
fragments, | ||
variableValues, | ||
runtimeType, | ||
fragment.selectionSet, | ||
fields, | ||
visitedFragmentNames, | ||
); | ||
if (!defer) { | ||
visitedFragmentNames.add(fragName); | ||
} | ||
if (defer) { | ||
const patchFields = new AccumulatorMap(); | ||
collectFieldsImpl( | ||
schema, | ||
fragments, | ||
variableValues, | ||
runtimeType, | ||
fragment.selectionSet, | ||
patchFields, | ||
patches, | ||
visitedFragmentNames, | ||
); | ||
patches.push({ | ||
label: defer.label, | ||
fields: patchFields, | ||
}); | ||
} else { | ||
collectFieldsImpl( | ||
schema, | ||
fragments, | ||
variableValues, | ||
runtimeType, | ||
fragment.selectionSet, | ||
fields, | ||
patches, | ||
visitedFragmentNames, | ||
); | ||
} | ||
break; | ||
@@ -150,2 +202,19 @@ } | ||
/** | ||
* Returns an object containing the `@defer` arguments if a field should be | ||
* deferred based on the experimental flag, defer directive present and | ||
* not disabled by the "if" argument. | ||
*/ | ||
function getDeferValues(variableValues, node) { | ||
const defer = getDirectiveValues(GraphQLDeferDirective, node, variableValues); | ||
if (!defer) { | ||
return; | ||
} | ||
if (defer.if === false) { | ||
return; | ||
} | ||
return { | ||
label: typeof defer.label === 'string' ? defer.label : undefined, | ||
}; | ||
} | ||
/** | ||
* Determines if a field should be included based on the `@include` and `@skip` | ||
@@ -152,0 +221,0 @@ * directives, where `@skip` has higher precedence than `@include`. |
@@ -59,2 +59,3 @@ import type { Maybe } from '../jsutils/Maybe'; | ||
errors: Array<GraphQLError>; | ||
subsequentPayloads: Set<AsyncPayloadRecord>; | ||
} | ||
@@ -66,3 +67,5 @@ /** | ||
* - `data` is the result of a successful execution of the query. | ||
* - `hasNext` is true if a future payload is expected. | ||
* - `extensions` is reserved for adding non-standard properties. | ||
* - `incremental` is a list of the results from defer/stream directives. | ||
*/ | ||
@@ -85,2 +88,95 @@ export interface ExecutionResult< | ||
} | ||
export declare type ExperimentalExecuteIncrementallyResults< | ||
TData = ObjMap<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> = | ||
| { | ||
singleResult: ExecutionResult<TData, TExtensions>; | ||
} | ||
| { | ||
initialResult: InitialIncrementalExecutionResult<TData, TExtensions>; | ||
subsequentResults: AsyncGenerator< | ||
SubsequentIncrementalExecutionResult<TData, TExtensions>, | ||
void, | ||
void | ||
>; | ||
}; | ||
export interface InitialIncrementalExecutionResult< | ||
TData = ObjMap<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> extends ExecutionResult<TData, TExtensions> { | ||
hasNext: boolean; | ||
incremental?: ReadonlyArray<IncrementalResult<TData, TExtensions>>; | ||
extensions?: TExtensions; | ||
} | ||
export interface FormattedInitialIncrementalExecutionResult< | ||
TData = ObjMap<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> extends FormattedExecutionResult<TData, TExtensions> { | ||
hasNext: boolean; | ||
incremental?: ReadonlyArray<FormattedIncrementalResult<TData, TExtensions>>; | ||
extensions?: TExtensions; | ||
} | ||
export interface SubsequentIncrementalExecutionResult< | ||
TData = ObjMap<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> { | ||
hasNext: boolean; | ||
incremental?: ReadonlyArray<IncrementalResult<TData, TExtensions>>; | ||
extensions?: TExtensions; | ||
} | ||
export interface FormattedSubsequentIncrementalExecutionResult< | ||
TData = ObjMap<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> { | ||
hasNext: boolean; | ||
incremental?: ReadonlyArray<FormattedIncrementalResult<TData, TExtensions>>; | ||
extensions?: TExtensions; | ||
} | ||
export interface IncrementalDeferResult< | ||
TData = ObjMap<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> extends ExecutionResult<TData, TExtensions> { | ||
path?: ReadonlyArray<string | number>; | ||
label?: string; | ||
} | ||
export interface FormattedIncrementalDeferResult< | ||
TData = ObjMap<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> extends FormattedExecutionResult<TData, TExtensions> { | ||
path?: ReadonlyArray<string | number>; | ||
label?: string; | ||
} | ||
export interface IncrementalStreamResult< | ||
TData = Array<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> { | ||
errors?: ReadonlyArray<GraphQLError>; | ||
items?: TData | null; | ||
path?: ReadonlyArray<string | number>; | ||
label?: string; | ||
extensions?: TExtensions; | ||
} | ||
export interface FormattedIncrementalStreamResult< | ||
TData = Array<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> { | ||
errors?: ReadonlyArray<GraphQLFormattedError>; | ||
items?: TData | null; | ||
path?: ReadonlyArray<string | number>; | ||
label?: string; | ||
extensions?: TExtensions; | ||
} | ||
export declare type IncrementalResult< | ||
TData = ObjMap<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> = | ||
| IncrementalDeferResult<TData, TExtensions> | ||
| IncrementalStreamResult<TData, TExtensions>; | ||
export declare type FormattedIncrementalResult< | ||
TData = ObjMap<unknown>, | ||
TExtensions = ObjMap<unknown>, | ||
> = | ||
| FormattedIncrementalDeferResult<TData, TExtensions> | ||
| FormattedIncrementalStreamResult<TData, TExtensions>; | ||
export interface ExecutionArgs { | ||
@@ -108,2 +204,8 @@ schema: GraphQLSchema; | ||
* a GraphQLError will be thrown immediately explaining the invalid input. | ||
* | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* If an operation which would defer or stream data is executed with this | ||
* function, it will throw or resolve to an object containing an error instead. | ||
* Use `experimentalExecuteIncrementally` if you want to support incremental | ||
* delivery. | ||
*/ | ||
@@ -114,2 +216,17 @@ export declare function execute( | ||
/** | ||
* Implements the "Executing requests" section of the GraphQL specification, | ||
* including `@defer` and `@stream` as proposed in | ||
* https://github.com/graphql/graphql-spec/pull/742 | ||
* | ||
* This function returns a Promise of an ExperimentalExecuteIncrementallyResults | ||
* object. This object either contains a single ExecutionResult as | ||
* `singleResult`, or an `initialResult` and a stream of `subsequentResults`. | ||
* | ||
* If the arguments to this function do not result in a legal execution context, | ||
* a GraphQLError will be thrown immediately explaining the invalid input. | ||
*/ | ||
export declare function experimentalExecuteIncrementally( | ||
args: ExecutionArgs, | ||
): PromiseOrValue<ExperimentalExecuteIncrementallyResults>; | ||
/** | ||
* Also implements the "Executing requests" section of the GraphQL specification. | ||
@@ -173,7 +290,7 @@ * However, it guarantees to complete synchronously (or throw an error) assuming | ||
* If the client-provided arguments to this function do not result in a | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with | ||
* descriptive errors and no data will be returned. | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive | ||
* errors and no data will be returned. | ||
* | ||
* If the source stream could not be created due to faulty subscription | ||
* resolver logic or underlying systems, the promise will resolve to a single | ||
* If the source stream could not be created due to faulty subscription resolver | ||
* logic or underlying systems, the promise will resolve to a single | ||
* ExecutionResult containing `errors` and no `data`. | ||
@@ -184,3 +301,11 @@ * | ||
* | ||
* Accepts either an object with named arguments, or individual arguments. | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* If an operation which would defer or stream data is executed with this | ||
* function, each `InitialIncrementalExecutionResult` and | ||
* `SubsequentIncrementalExecutionResult` in the result stream will be replaced | ||
* with an `ExecutionResult` with a single error stating that defer/stream is | ||
* not supported. Use `experimentalSubscribeIncrementally` if you want to | ||
* support incremental delivery. | ||
* | ||
* Accepts an object with named arguments. | ||
*/ | ||
@@ -193,2 +318,47 @@ export declare function subscribe( | ||
/** | ||
* Implements the "Subscribe" algorithm described in the GraphQL specification, | ||
* including `@defer` and `@stream` as proposed in | ||
* https://github.com/graphql/graphql-spec/pull/742 | ||
* | ||
* Returns a Promise which resolves to either an AsyncIterator (if successful) | ||
* or an ExecutionResult (error). The promise will be rejected if the schema or | ||
* other arguments to this function are invalid, or if the resolved event stream | ||
* is not an async iterable. | ||
* | ||
* If the client-provided arguments to this function do not result in a | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive | ||
* errors and no data will be returned. | ||
* | ||
* If the source stream could not be created due to faulty subscription resolver | ||
* logic or underlying systems, the promise will resolve to a single | ||
* ExecutionResult containing `errors` and no `data`. | ||
* | ||
* If the operation succeeded, the promise resolves to an AsyncIterator, which | ||
* yields a stream of result representing the response stream. | ||
* | ||
* Each result may be an ExecutionResult with no `hasNext` (if executing the | ||
* event did not use `@defer` or `@stream`), or an | ||
* `InitialIncrementalExecutionResult` or `SubsequentIncrementalExecutionResult` | ||
* (if executing the event used `@defer` or `@stream`). In the case of | ||
* incremental execution results, each event produces a single | ||
* `InitialIncrementalExecutionResult` followed by one or more | ||
* `SubsequentIncrementalExecutionResult`s; all but the last have `hasNext: true`, | ||
* and the last has `hasNext: false`. There is no interleaving between results | ||
* generated from the same original event. | ||
* | ||
* Accepts an object with named arguments. | ||
*/ | ||
export declare function experimentalSubscribeIncrementally( | ||
args: ExecutionArgs, | ||
): PromiseOrValue< | ||
| AsyncGenerator< | ||
| ExecutionResult | ||
| InitialIncrementalExecutionResult | ||
| SubsequentIncrementalExecutionResult, | ||
void, | ||
void | ||
> | ||
| ExecutionResult | ||
>; | ||
/** | ||
* Implements the "CreateSourceEventStream" algorithm described in the | ||
@@ -224,1 +394,45 @@ * GraphQL specification, resolving the subscription source event stream. | ||
): PromiseOrValue<AsyncIterable<unknown> | ExecutionResult>; | ||
declare class DeferredFragmentRecord { | ||
type: 'defer'; | ||
errors: Array<GraphQLError>; | ||
label: string | undefined; | ||
path: Path | undefined; | ||
promise: Promise<void>; | ||
data: ObjMap<unknown> | null; | ||
parentContext: AsyncPayloadRecord | undefined; | ||
isCompleted: boolean; | ||
_exeContext: ExecutionContext; | ||
_resolve?: (arg: PromiseOrValue<ObjMap<unknown> | null>) => void; | ||
constructor(opts: { | ||
label: string | undefined; | ||
path: Path | undefined; | ||
parentContext: AsyncPayloadRecord | undefined; | ||
exeContext: ExecutionContext; | ||
}); | ||
addData(data: PromiseOrValue<ObjMap<unknown> | null>): void; | ||
} | ||
declare class StreamRecord { | ||
type: 'stream'; | ||
errors: Array<GraphQLError>; | ||
label: string | undefined; | ||
path: Path | undefined; | ||
items: Array<unknown> | null; | ||
promise: Promise<void>; | ||
parentContext: AsyncPayloadRecord | undefined; | ||
iterator: AsyncIterator<unknown> | undefined; | ||
isCompletedIterator?: boolean; | ||
isCompleted: boolean; | ||
_exeContext: ExecutionContext; | ||
_resolve?: (arg: PromiseOrValue<Array<unknown> | null>) => void; | ||
constructor(opts: { | ||
label: string | undefined; | ||
path: Path | undefined; | ||
iterator?: AsyncIterator<unknown>; | ||
parentContext: AsyncPayloadRecord | undefined; | ||
exeContext: ExecutionContext; | ||
}); | ||
addItems(items: PromiseOrValue<Array<unknown> | null>): void; | ||
setIsCompletedIterator(): void; | ||
} | ||
declare type AsyncPayloadRecord = DeferredFragmentRecord | StreamRecord; | ||
export {}; |
@@ -22,2 +22,3 @@ import { inspect } from '../jsutils/inspect.js'; | ||
} from '../type/definition.js'; | ||
import { GraphQLStreamDirective } from '../type/directives.js'; | ||
import { assertValidSchema } from '../type/validate.js'; | ||
@@ -28,4 +29,9 @@ import { | ||
} from './collectFields.js'; | ||
import { mapAsyncIterator } from './mapAsyncIterator.js'; | ||
import { getArgumentValues, getVariableValues } from './values.js'; | ||
import { flattenAsyncIterable } from './flattenAsyncIterable.js'; | ||
import { mapAsyncIterable } from './mapAsyncIterable.js'; | ||
import { | ||
getArgumentValues, | ||
getDirectiveValues, | ||
getVariableValues, | ||
} from './values.js'; | ||
/* eslint-disable max-params */ | ||
@@ -48,2 +54,4 @@ // This file contains a lot of such errors but we plan to refactor it anyway | ||
); | ||
const UNEXPECTED_MULTIPLE_PAYLOADS = | ||
'Executing this GraphQL operation would unexpectedly produce multiple payloads (due to @defer or @stream directive)'; | ||
/** | ||
@@ -58,4 +66,39 @@ * Implements the "Executing requests" section of the GraphQL specification. | ||
* a GraphQLError will be thrown immediately explaining the invalid input. | ||
* | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* If an operation which would defer or stream data is executed with this | ||
* function, it will throw or resolve to an object containing an error instead. | ||
* Use `experimentalExecuteIncrementally` if you want to support incremental | ||
* delivery. | ||
*/ | ||
export function execute(args) { | ||
const result = experimentalExecuteIncrementally(args); | ||
if (!isPromise(result)) { | ||
if ('singleResult' in result) { | ||
return result.singleResult; | ||
} | ||
throw new Error(UNEXPECTED_MULTIPLE_PAYLOADS); | ||
} | ||
return result.then((incrementalResult) => { | ||
if ('singleResult' in incrementalResult) { | ||
return incrementalResult.singleResult; | ||
} | ||
return { | ||
errors: [new GraphQLError(UNEXPECTED_MULTIPLE_PAYLOADS)], | ||
}; | ||
}); | ||
} | ||
/** | ||
* Implements the "Executing requests" section of the GraphQL specification, | ||
* including `@defer` and `@stream` as proposed in | ||
* https://github.com/graphql/graphql-spec/pull/742 | ||
* | ||
* This function returns a Promise of an ExperimentalExecuteIncrementallyResults | ||
* object. This object either contains a single ExecutionResult as | ||
* `singleResult`, or an `initialResult` and a stream of `subsequentResults`. | ||
* | ||
* If the arguments to this function do not result in a legal execution context, | ||
* a GraphQLError will be thrown immediately explaining the invalid input. | ||
*/ | ||
export function experimentalExecuteIncrementally(args) { | ||
// If a valid execution context cannot be created due to incorrect arguments, | ||
@@ -66,3 +109,3 @@ // a "Response" with only errors is returned. | ||
if (!('schema' in exeContext)) { | ||
return { errors: exeContext }; | ||
return { singleResult: { errors: exeContext } }; | ||
} | ||
@@ -87,13 +130,35 @@ return executeImpl(exeContext); | ||
return result.then( | ||
(data) => buildResponse(data, exeContext.errors), | ||
(data) => { | ||
const initialResult = buildResponse(data, exeContext.errors); | ||
if (exeContext.subsequentPayloads.size > 0) { | ||
return { | ||
initialResult: { | ||
...initialResult, | ||
hasNext: true, | ||
}, | ||
subsequentResults: yieldSubsequentPayloads(exeContext), | ||
}; | ||
} | ||
return { singleResult: initialResult }; | ||
}, | ||
(error) => { | ||
exeContext.errors.push(error); | ||
return buildResponse(null, exeContext.errors); | ||
return { singleResult: buildResponse(null, exeContext.errors) }; | ||
}, | ||
); | ||
} | ||
return buildResponse(result, exeContext.errors); | ||
const initialResult = buildResponse(result, exeContext.errors); | ||
if (exeContext.subsequentPayloads.size > 0) { | ||
return { | ||
initialResult: { | ||
...initialResult, | ||
hasNext: true, | ||
}, | ||
subsequentResults: yieldSubsequentPayloads(exeContext), | ||
}; | ||
} | ||
return { singleResult: initialResult }; | ||
} catch (error) { | ||
exeContext.errors.push(error); | ||
return buildResponse(null, exeContext.errors); | ||
return { singleResult: buildResponse(null, exeContext.errors) }; | ||
} | ||
@@ -107,8 +172,8 @@ } | ||
export function executeSync(args) { | ||
const result = execute(args); | ||
const result = experimentalExecuteIncrementally(args); | ||
// Assert that the execution was synchronous. | ||
if (isPromise(result)) { | ||
if (isPromise(result) || 'initialResult' in result) { | ||
throw new Error('GraphQL execution failed to complete synchronously.'); | ||
} | ||
return result; | ||
return result.singleResult; | ||
} | ||
@@ -198,2 +263,3 @@ /** | ||
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver, | ||
subsequentPayloads: new Set(), | ||
errors: [], | ||
@@ -222,3 +288,3 @@ }; | ||
} | ||
const rootFields = collectFields( | ||
const { fields: rootFields, patches } = collectFields( | ||
schema, | ||
@@ -231,7 +297,9 @@ fragments, | ||
const path = undefined; | ||
let result; | ||
switch (operation.operation) { | ||
case OperationTypeNode.QUERY: | ||
return executeFields(exeContext, rootType, rootValue, path, rootFields); | ||
result = executeFields(exeContext, rootType, rootValue, path, rootFields); | ||
break; | ||
case OperationTypeNode.MUTATION: | ||
return executeFieldsSerially( | ||
result = executeFieldsSerially( | ||
exeContext, | ||
@@ -243,7 +311,20 @@ rootType, | ||
); | ||
break; | ||
case OperationTypeNode.SUBSCRIPTION: | ||
// TODO: deprecate `subscribe` and move all logic here | ||
// Temporary solution until we finish merging execute and subscribe together | ||
return executeFields(exeContext, rootType, rootValue, path, rootFields); | ||
result = executeFields(exeContext, rootType, rootValue, path, rootFields); | ||
} | ||
for (const patch of patches) { | ||
const { label, fields: patchFields } = patch; | ||
executeDeferredFragment( | ||
exeContext, | ||
rootType, | ||
rootValue, | ||
patchFields, | ||
label, | ||
path, | ||
); | ||
} | ||
return result; | ||
} | ||
@@ -262,3 +343,3 @@ /** | ||
return promiseReduce( | ||
fields.entries(), | ||
fields, | ||
(results, [responseName, fieldNodes]) => { | ||
@@ -292,6 +373,13 @@ const fieldPath = addPath(path, responseName, parentType.name); | ||
*/ | ||
function executeFields(exeContext, parentType, sourceValue, path, fields) { | ||
function executeFields( | ||
exeContext, | ||
parentType, | ||
sourceValue, | ||
path, | ||
fields, | ||
asyncPayloadRecord, | ||
) { | ||
const results = Object.create(null); | ||
let containsPromise = false; | ||
for (const [responseName, fieldNodes] of fields.entries()) { | ||
for (const [responseName, fieldNodes] of fields) { | ||
const fieldPath = addPath(path, responseName, parentType.name); | ||
@@ -304,2 +392,3 @@ const result = executeField( | ||
fieldPath, | ||
asyncPayloadRecord, | ||
); | ||
@@ -328,3 +417,11 @@ if (result !== undefined) { | ||
*/ | ||
function executeField(exeContext, parentType, source, fieldNodes, path) { | ||
function executeField( | ||
exeContext, | ||
parentType, | ||
source, | ||
fieldNodes, | ||
path, | ||
asyncPayloadRecord, | ||
) { | ||
const errors = asyncPayloadRecord?.errors ?? exeContext.errors; | ||
const fieldName = fieldNodes[0].name.value; | ||
@@ -362,3 +459,11 @@ const fieldDef = exeContext.schema.getField(parentType, fieldName); | ||
completed = result.then((resolved) => | ||
completeValue(exeContext, returnType, fieldNodes, info, path, resolved), | ||
completeValue( | ||
exeContext, | ||
returnType, | ||
fieldNodes, | ||
info, | ||
path, | ||
resolved, | ||
asyncPayloadRecord, | ||
), | ||
); | ||
@@ -373,2 +478,3 @@ } else { | ||
result, | ||
asyncPayloadRecord, | ||
); | ||
@@ -381,3 +487,3 @@ } | ||
const error = locatedError(rawError, fieldNodes, pathToArray(path)); | ||
return handleFieldError(error, returnType, exeContext); | ||
return handleFieldError(error, returnType, errors); | ||
}); | ||
@@ -388,3 +494,3 @@ } | ||
const error = locatedError(rawError, fieldNodes, pathToArray(path)); | ||
return handleFieldError(error, returnType, exeContext); | ||
return handleFieldError(error, returnType, errors); | ||
} | ||
@@ -418,3 +524,3 @@ } | ||
} | ||
function handleFieldError(error, returnType, exeContext) { | ||
function handleFieldError(error, returnType, errors) { | ||
// If the field type is non-nullable, then it is resolved without any | ||
@@ -427,3 +533,3 @@ // protection from errors, however it still properly locates the error. | ||
// a null value for this field if one is encountered. | ||
exeContext.errors.push(error); | ||
errors.push(error); | ||
return null; | ||
@@ -452,3 +558,11 @@ } | ||
*/ | ||
function completeValue(exeContext, returnType, fieldNodes, info, path, result) { | ||
function completeValue( | ||
exeContext, | ||
returnType, | ||
fieldNodes, | ||
info, | ||
path, | ||
result, | ||
asyncPayloadRecord, | ||
) { | ||
// If result is an Error, throw a located error. | ||
@@ -468,2 +582,3 @@ if (result instanceof Error) { | ||
result, | ||
asyncPayloadRecord, | ||
); | ||
@@ -490,2 +605,3 @@ if (completed === null) { | ||
result, | ||
asyncPayloadRecord, | ||
); | ||
@@ -508,2 +624,3 @@ } | ||
result, | ||
asyncPayloadRecord, | ||
); | ||
@@ -520,2 +637,3 @@ } | ||
result, | ||
asyncPayloadRecord, | ||
); | ||
@@ -532,2 +650,114 @@ } | ||
/** | ||
* Returns an object containing the `@stream` arguments if a field should be | ||
* streamed based on the experimental flag, stream directive present and | ||
* not disabled by the "if" argument. | ||
*/ | ||
function getStreamValues(exeContext, fieldNodes, path) { | ||
// do not stream inner lists of multi-dimensional lists | ||
if (typeof path.key === 'number') { | ||
return; | ||
} | ||
// validation only allows equivalent streams on multiple fields, so it is | ||
// safe to only check the first fieldNode for the stream directive | ||
const stream = getDirectiveValues( | ||
GraphQLStreamDirective, | ||
fieldNodes[0], | ||
exeContext.variableValues, | ||
); | ||
if (!stream) { | ||
return; | ||
} | ||
if (stream.if === false) { | ||
return; | ||
} | ||
typeof stream.initialCount === 'number' || | ||
invariant(false, 'initialCount must be a number'); | ||
stream.initialCount >= 0 || | ||
invariant(false, 'initialCount must be a positive integer'); | ||
return { | ||
initialCount: stream.initialCount, | ||
label: typeof stream.label === 'string' ? stream.label : undefined, | ||
}; | ||
} | ||
/** | ||
* Complete a async iterator value by completing the result and calling | ||
* recursively until all the results are completed. | ||
*/ | ||
async function completeAsyncIteratorValue( | ||
exeContext, | ||
itemType, | ||
fieldNodes, | ||
info, | ||
path, | ||
iterator, | ||
asyncPayloadRecord, | ||
) { | ||
const errors = asyncPayloadRecord?.errors ?? exeContext.errors; | ||
const stream = getStreamValues(exeContext, fieldNodes, path); | ||
let containsPromise = false; | ||
const completedResults = []; | ||
let index = 0; | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
if ( | ||
stream && | ||
typeof stream.initialCount === 'number' && | ||
index >= stream.initialCount | ||
) { | ||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
executeStreamIterator( | ||
index, | ||
iterator, | ||
exeContext, | ||
fieldNodes, | ||
info, | ||
itemType, | ||
path, | ||
stream.label, | ||
asyncPayloadRecord, | ||
); | ||
break; | ||
} | ||
const fieldPath = addPath(path, index, undefined); | ||
try { | ||
// eslint-disable-next-line no-await-in-loop | ||
const { value, done } = await iterator.next(); | ||
if (done) { | ||
break; | ||
} | ||
try { | ||
// TODO can the error checking logic be consolidated with completeListValue? | ||
const completedItem = completeValue( | ||
exeContext, | ||
itemType, | ||
fieldNodes, | ||
info, | ||
fieldPath, | ||
value, | ||
asyncPayloadRecord, | ||
); | ||
if (isPromise(completedItem)) { | ||
containsPromise = true; | ||
} | ||
completedResults.push(completedItem); | ||
} catch (rawError) { | ||
completedResults.push(null); | ||
const error = locatedError( | ||
rawError, | ||
fieldNodes, | ||
pathToArray(fieldPath), | ||
); | ||
handleFieldError(error, itemType, errors); | ||
} | ||
} catch (rawError) { | ||
completedResults.push(null); | ||
const error = locatedError(rawError, fieldNodes, pathToArray(fieldPath)); | ||
handleFieldError(error, itemType, errors); | ||
break; | ||
} | ||
index += 1; | ||
} | ||
return containsPromise ? Promise.all(completedResults) : completedResults; | ||
} | ||
/** | ||
* Complete a list value by completing each item in the list with the | ||
@@ -543,3 +773,18 @@ * inner type | ||
result, | ||
asyncPayloadRecord, | ||
) { | ||
const itemType = returnType.ofType; | ||
const errors = asyncPayloadRecord?.errors ?? exeContext.errors; | ||
if (isAsyncIterable(result)) { | ||
const iterator = result[Symbol.asyncIterator](); | ||
return completeAsyncIteratorValue( | ||
exeContext, | ||
itemType, | ||
fieldNodes, | ||
info, | ||
path, | ||
iterator, | ||
asyncPayloadRecord, | ||
); | ||
} | ||
if (!isIterableObject(result)) { | ||
@@ -550,7 +795,10 @@ throw new GraphQLError( | ||
} | ||
const stream = getStreamValues(exeContext, fieldNodes, path); | ||
// This is specified as a simple map, however we're optimizing the path | ||
// where the list contains no Promises by avoiding creating another Promise. | ||
const itemType = returnType.ofType; | ||
let containsPromise = false; | ||
const completedResults = Array.from(result, (item, index) => { | ||
let previousAsyncPayloadRecord = asyncPayloadRecord; | ||
const completedResults = []; | ||
let index = 0; | ||
for (const item of result) { | ||
// No need to modify the info object containing the path, | ||
@@ -561,2 +809,20 @@ // since from here on it is not ever accessed by resolver functions. | ||
let completedItem; | ||
if ( | ||
stream && | ||
typeof stream.initialCount === 'number' && | ||
index >= stream.initialCount | ||
) { | ||
previousAsyncPayloadRecord = executeStreamField( | ||
itemPath, | ||
item, | ||
exeContext, | ||
fieldNodes, | ||
info, | ||
itemType, | ||
stream.label, | ||
previousAsyncPayloadRecord, | ||
); | ||
index++; | ||
continue; | ||
} | ||
if (isPromise(item)) { | ||
@@ -571,2 +837,3 @@ completedItem = item.then((resolved) => | ||
resolved, | ||
asyncPayloadRecord, | ||
), | ||
@@ -582,2 +849,3 @@ ); | ||
item, | ||
asyncPayloadRecord, | ||
); | ||
@@ -589,17 +857,21 @@ } | ||
// to take a second callback for the error case. | ||
return completedItem.then(undefined, (rawError) => { | ||
const error = locatedError( | ||
rawError, | ||
fieldNodes, | ||
pathToArray(itemPath), | ||
); | ||
return handleFieldError(error, itemType, exeContext); | ||
}); | ||
completedResults.push( | ||
completedItem.then(undefined, (rawError) => { | ||
const error = locatedError( | ||
rawError, | ||
fieldNodes, | ||
pathToArray(itemPath), | ||
); | ||
return handleFieldError(error, itemType, errors); | ||
}), | ||
); | ||
} else { | ||
completedResults.push(completedItem); | ||
} | ||
return completedItem; | ||
} catch (rawError) { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath)); | ||
return handleFieldError(error, itemType, exeContext); | ||
completedResults.push(handleFieldError(error, itemType, errors)); | ||
} | ||
}); | ||
index++; | ||
} | ||
return containsPromise ? Promise.all(completedResults) : completedResults; | ||
@@ -632,2 +904,3 @@ } | ||
result, | ||
asyncPayloadRecord, | ||
) { | ||
@@ -653,2 +926,3 @@ const resolveTypeFn = returnType.resolveType ?? exeContext.typeResolver; | ||
result, | ||
asyncPayloadRecord, | ||
), | ||
@@ -671,2 +945,3 @@ ); | ||
result, | ||
asyncPayloadRecord, | ||
); | ||
@@ -732,5 +1007,4 @@ } | ||
result, | ||
asyncPayloadRecord, | ||
) { | ||
// Collect sub-fields to execute to complete this value. | ||
const subFieldNodes = collectSubfields(exeContext, returnType, fieldNodes); | ||
// If there is an isTypeOf predicate function, call it with the | ||
@@ -746,8 +1020,9 @@ // current result. If isTypeOf returns false, then raise an error rather | ||
} | ||
return executeFields( | ||
return collectAndExecuteSubfields( | ||
exeContext, | ||
returnType, | ||
fieldNodes, | ||
path, | ||
result, | ||
path, | ||
subFieldNodes, | ||
asyncPayloadRecord, | ||
); | ||
@@ -760,3 +1035,10 @@ }); | ||
} | ||
return executeFields(exeContext, returnType, result, path, subFieldNodes); | ||
return collectAndExecuteSubfields( | ||
exeContext, | ||
returnType, | ||
fieldNodes, | ||
path, | ||
result, | ||
asyncPayloadRecord, | ||
); | ||
} | ||
@@ -769,2 +1051,38 @@ function invalidReturnTypeError(returnType, result, fieldNodes) { | ||
} | ||
function collectAndExecuteSubfields( | ||
exeContext, | ||
returnType, | ||
fieldNodes, | ||
path, | ||
result, | ||
asyncPayloadRecord, | ||
) { | ||
// Collect sub-fields to execute to complete this value. | ||
const { fields: subFieldNodes, patches: subPatches } = collectSubfields( | ||
exeContext, | ||
returnType, | ||
fieldNodes, | ||
); | ||
const subFields = executeFields( | ||
exeContext, | ||
returnType, | ||
result, | ||
path, | ||
subFieldNodes, | ||
asyncPayloadRecord, | ||
); | ||
for (const subPatch of subPatches) { | ||
const { label, fields: subPatchFieldNodes } = subPatch; | ||
executeDeferredFragment( | ||
exeContext, | ||
returnType, | ||
result, | ||
subPatchFieldNodes, | ||
label, | ||
path, | ||
asyncPayloadRecord, | ||
); | ||
} | ||
return subFields; | ||
} | ||
/** | ||
@@ -844,7 +1162,7 @@ * If a resolveType function is not given, then a default resolve behavior is | ||
* If the client-provided arguments to this function do not result in a | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with | ||
* descriptive errors and no data will be returned. | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive | ||
* errors and no data will be returned. | ||
* | ||
* If the source stream could not be created due to faulty subscription | ||
* resolver logic or underlying systems, the promise will resolve to a single | ||
* If the source stream could not be created due to faulty subscription resolver | ||
* logic or underlying systems, the promise will resolve to a single | ||
* ExecutionResult containing `errors` and no `data`. | ||
@@ -855,5 +1173,67 @@ * | ||
* | ||
* Accepts either an object with named arguments, or individual arguments. | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* If an operation which would defer or stream data is executed with this | ||
* function, each `InitialIncrementalExecutionResult` and | ||
* `SubsequentIncrementalExecutionResult` in the result stream will be replaced | ||
* with an `ExecutionResult` with a single error stating that defer/stream is | ||
* not supported. Use `experimentalSubscribeIncrementally` if you want to | ||
* support incremental delivery. | ||
* | ||
* Accepts an object with named arguments. | ||
*/ | ||
export function subscribe(args) { | ||
const maybePromise = experimentalSubscribeIncrementally(args); | ||
if (isPromise(maybePromise)) { | ||
return maybePromise.then((resultOrIterable) => | ||
isAsyncIterable(resultOrIterable) | ||
? mapAsyncIterable(resultOrIterable, ensureSingleExecutionResult) | ||
: resultOrIterable, | ||
); | ||
} | ||
return isAsyncIterable(maybePromise) | ||
? mapAsyncIterable(maybePromise, ensureSingleExecutionResult) | ||
: maybePromise; | ||
} | ||
function ensureSingleExecutionResult(result) { | ||
if ('hasNext' in result) { | ||
return { | ||
errors: [new GraphQLError(UNEXPECTED_MULTIPLE_PAYLOADS)], | ||
}; | ||
} | ||
return result; | ||
} | ||
/** | ||
* Implements the "Subscribe" algorithm described in the GraphQL specification, | ||
* including `@defer` and `@stream` as proposed in | ||
* https://github.com/graphql/graphql-spec/pull/742 | ||
* | ||
* Returns a Promise which resolves to either an AsyncIterator (if successful) | ||
* or an ExecutionResult (error). The promise will be rejected if the schema or | ||
* other arguments to this function are invalid, or if the resolved event stream | ||
* is not an async iterable. | ||
* | ||
* If the client-provided arguments to this function do not result in a | ||
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive | ||
* errors and no data will be returned. | ||
* | ||
* If the source stream could not be created due to faulty subscription resolver | ||
* logic or underlying systems, the promise will resolve to a single | ||
* ExecutionResult containing `errors` and no `data`. | ||
* | ||
* If the operation succeeded, the promise resolves to an AsyncIterator, which | ||
* yields a stream of result representing the response stream. | ||
* | ||
* Each result may be an ExecutionResult with no `hasNext` (if executing the | ||
* event did not use `@defer` or `@stream`), or an | ||
* `InitialIncrementalExecutionResult` or `SubsequentIncrementalExecutionResult` | ||
* (if executing the event used `@defer` or `@stream`). In the case of | ||
* incremental execution results, each event produces a single | ||
* `InitialIncrementalExecutionResult` followed by one or more | ||
* `SubsequentIncrementalExecutionResult`s; all but the last have `hasNext: true`, | ||
* and the last has `hasNext: false`. There is no interleaving between results | ||
* generated from the same original event. | ||
* | ||
* Accepts an object with named arguments. | ||
*/ | ||
export function experimentalSubscribeIncrementally(args) { | ||
// If a valid execution context cannot be created due to incorrect arguments, | ||
@@ -874,2 +1254,10 @@ // a "Response" with only errors is returned. | ||
} | ||
async function* ensureAsyncIterable(someExecutionResult) { | ||
if ('initialResult' in someExecutionResult) { | ||
yield someExecutionResult.initialResult; | ||
yield* someExecutionResult.subsequentResults; | ||
} else { | ||
yield someExecutionResult.singleResult; | ||
} | ||
} | ||
function mapSourceToResponse(exeContext, resultOrStream) { | ||
@@ -885,4 +1273,8 @@ if (!isAsyncIterable(resultOrStream)) { | ||
// "ExecuteQuery" algorithm, for which `execute` is also used. | ||
return mapAsyncIterator(resultOrStream, (payload) => | ||
executeImpl(buildPerEventExecutionContext(exeContext, payload)), | ||
return flattenAsyncIterable( | ||
mapAsyncIterable(resultOrStream, async (payload) => | ||
ensureAsyncIterable( | ||
await executeImpl(buildPerEventExecutionContext(exeContext, payload)), | ||
), | ||
), | ||
); | ||
@@ -949,3 +1341,3 @@ } | ||
} | ||
const rootFields = collectFields( | ||
const { fields: rootFields } = collectFields( | ||
schema, | ||
@@ -957,3 +1349,4 @@ fragments, | ||
); | ||
const [responseName, fieldNodes] = [...rootFields.entries()][0]; | ||
const firstRootField = rootFields.entries().next().value; | ||
const [responseName, fieldNodes] = firstRootField; | ||
const fieldName = fieldNodes[0].name.value; | ||
@@ -1012,1 +1405,383 @@ const fieldDef = schema.getField(rootType, fieldName); | ||
} | ||
function executeDeferredFragment( | ||
exeContext, | ||
parentType, | ||
sourceValue, | ||
fields, | ||
label, | ||
path, | ||
parentContext, | ||
) { | ||
const asyncPayloadRecord = new DeferredFragmentRecord({ | ||
label, | ||
path, | ||
parentContext, | ||
exeContext, | ||
}); | ||
let promiseOrData; | ||
try { | ||
promiseOrData = executeFields( | ||
exeContext, | ||
parentType, | ||
sourceValue, | ||
path, | ||
fields, | ||
asyncPayloadRecord, | ||
); | ||
if (isPromise(promiseOrData)) { | ||
promiseOrData = promiseOrData.then(null, (e) => { | ||
asyncPayloadRecord.errors.push(e); | ||
return null; | ||
}); | ||
} | ||
} catch (e) { | ||
asyncPayloadRecord.errors.push(e); | ||
promiseOrData = null; | ||
} | ||
asyncPayloadRecord.addData(promiseOrData); | ||
} | ||
function executeStreamField( | ||
path, | ||
item, | ||
exeContext, | ||
fieldNodes, | ||
info, | ||
itemType, | ||
label, | ||
parentContext, | ||
) { | ||
const asyncPayloadRecord = new StreamRecord({ | ||
label, | ||
path, | ||
parentContext, | ||
exeContext, | ||
}); | ||
let completedItem; | ||
let completedItems; | ||
try { | ||
try { | ||
if (isPromise(item)) { | ||
completedItem = item.then((resolved) => | ||
completeValue( | ||
exeContext, | ||
itemType, | ||
fieldNodes, | ||
info, | ||
path, | ||
resolved, | ||
asyncPayloadRecord, | ||
), | ||
); | ||
} else { | ||
completedItem = completeValue( | ||
exeContext, | ||
itemType, | ||
fieldNodes, | ||
info, | ||
path, | ||
item, | ||
asyncPayloadRecord, | ||
); | ||
} | ||
if (isPromise(completedItem)) { | ||
// Note: we don't rely on a `catch` method, but we do expect "thenable" | ||
// to take a second callback for the error case. | ||
completedItem = completedItem.then(undefined, (rawError) => { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(path)); | ||
return handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
}); | ||
} | ||
} catch (rawError) { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(path)); | ||
completedItems = handleFieldError( | ||
error, | ||
itemType, | ||
asyncPayloadRecord.errors, | ||
); | ||
} | ||
} catch (error) { | ||
asyncPayloadRecord.errors.push(error); | ||
asyncPayloadRecord.addItems(null); | ||
return asyncPayloadRecord; | ||
} | ||
if (isPromise(completedItem)) { | ||
completedItems = completedItem.then( | ||
(value) => [value], | ||
(error) => { | ||
asyncPayloadRecord.errors.push(error); | ||
return null; | ||
}, | ||
); | ||
} else { | ||
completedItems = [completedItem]; | ||
} | ||
asyncPayloadRecord.addItems(completedItems); | ||
return asyncPayloadRecord; | ||
} | ||
async function executeStreamIteratorItem( | ||
iterator, | ||
exeContext, | ||
fieldNodes, | ||
info, | ||
itemType, | ||
asyncPayloadRecord, | ||
fieldPath, | ||
) { | ||
let item; | ||
try { | ||
const { value, done } = await iterator.next(); | ||
if (done) { | ||
asyncPayloadRecord.setIsCompletedIterator(); | ||
return { done, value: undefined }; | ||
} | ||
item = value; | ||
} catch (rawError) { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(fieldPath)); | ||
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
// don't continue if iterator throws | ||
return { done: true, value }; | ||
} | ||
let completedItem; | ||
try { | ||
completedItem = completeValue( | ||
exeContext, | ||
itemType, | ||
fieldNodes, | ||
info, | ||
fieldPath, | ||
item, | ||
asyncPayloadRecord, | ||
); | ||
if (isPromise(completedItem)) { | ||
completedItem = completedItem.then(undefined, (rawError) => { | ||
const error = locatedError( | ||
rawError, | ||
fieldNodes, | ||
pathToArray(fieldPath), | ||
); | ||
return handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
}); | ||
} | ||
return { done: false, value: completedItem }; | ||
} catch (rawError) { | ||
const error = locatedError(rawError, fieldNodes, pathToArray(fieldPath)); | ||
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors); | ||
return { done: false, value }; | ||
} | ||
} | ||
async function executeStreamIterator( | ||
initialIndex, | ||
iterator, | ||
exeContext, | ||
fieldNodes, | ||
info, | ||
itemType, | ||
path, | ||
label, | ||
parentContext, | ||
) { | ||
let index = initialIndex; | ||
let previousAsyncPayloadRecord = parentContext ?? undefined; | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
const fieldPath = addPath(path, index, undefined); | ||
const asyncPayloadRecord = new StreamRecord({ | ||
label, | ||
path: fieldPath, | ||
parentContext: previousAsyncPayloadRecord, | ||
iterator, | ||
exeContext, | ||
}); | ||
const dataPromise = executeStreamIteratorItem( | ||
iterator, | ||
exeContext, | ||
fieldNodes, | ||
info, | ||
itemType, | ||
asyncPayloadRecord, | ||
fieldPath, | ||
); | ||
asyncPayloadRecord.addItems( | ||
dataPromise | ||
.then(({ value }) => value) | ||
.then( | ||
(value) => [value], | ||
(err) => { | ||
asyncPayloadRecord.errors.push(err); | ||
return null; | ||
}, | ||
), | ||
); | ||
try { | ||
// eslint-disable-next-line no-await-in-loop | ||
const { done } = await dataPromise; | ||
if (done) { | ||
break; | ||
} | ||
} catch (err) { | ||
// do nothing, error is already handled above | ||
} | ||
previousAsyncPayloadRecord = asyncPayloadRecord; | ||
index++; | ||
} | ||
} | ||
function getCompletedIncrementalResults(exeContext) { | ||
const incrementalResults = []; | ||
for (const asyncPayloadRecord of exeContext.subsequentPayloads) { | ||
const incrementalResult = {}; | ||
if (!asyncPayloadRecord.isCompleted) { | ||
continue; | ||
} | ||
exeContext.subsequentPayloads.delete(asyncPayloadRecord); | ||
if (isStreamPayload(asyncPayloadRecord)) { | ||
const items = asyncPayloadRecord.items; | ||
if (asyncPayloadRecord.isCompletedIterator) { | ||
// async iterable resolver just finished but there may be pending payloads | ||
continue; | ||
} | ||
incrementalResult.items = items; | ||
} else { | ||
const data = asyncPayloadRecord.data; | ||
incrementalResult.data = data ?? null; | ||
} | ||
incrementalResult.path = asyncPayloadRecord.path | ||
? pathToArray(asyncPayloadRecord.path) | ||
: []; | ||
if (asyncPayloadRecord.label) { | ||
incrementalResult.label = asyncPayloadRecord.label; | ||
} | ||
if (asyncPayloadRecord.errors.length > 0) { | ||
incrementalResult.errors = asyncPayloadRecord.errors; | ||
} | ||
incrementalResults.push(incrementalResult); | ||
} | ||
return incrementalResults; | ||
} | ||
function yieldSubsequentPayloads(exeContext) { | ||
let isDone = false; | ||
async function next() { | ||
if (isDone) { | ||
return { value: undefined, done: true }; | ||
} | ||
await Promise.race( | ||
Array.from(exeContext.subsequentPayloads).map((p) => p.promise), | ||
); | ||
if (isDone) { | ||
// a different call to next has exhausted all payloads | ||
return { value: undefined, done: true }; | ||
} | ||
const incremental = getCompletedIncrementalResults(exeContext); | ||
const hasNext = exeContext.subsequentPayloads.size > 0; | ||
if (!incremental.length && hasNext) { | ||
return next(); | ||
} | ||
if (!incremental.length && !hasNext) { | ||
isDone = true; | ||
return { | ||
value: { hasNext }, | ||
done: false, | ||
}; | ||
} | ||
if (!hasNext) { | ||
isDone = true; | ||
} | ||
return { | ||
value: { incremental, hasNext }, | ||
done: false, | ||
}; | ||
} | ||
function returnStreamIterators() { | ||
const promises = []; | ||
exeContext.subsequentPayloads.forEach((asyncPayloadRecord) => { | ||
if ( | ||
isStreamPayload(asyncPayloadRecord) && | ||
asyncPayloadRecord.iterator?.return | ||
) { | ||
promises.push(asyncPayloadRecord.iterator.return()); | ||
} | ||
}); | ||
return Promise.all(promises); | ||
} | ||
return { | ||
[Symbol.asyncIterator]() { | ||
return this; | ||
}, | ||
next, | ||
async return() { | ||
await returnStreamIterators(); | ||
isDone = true; | ||
return { value: undefined, done: true }; | ||
}, | ||
async throw(error) { | ||
await returnStreamIterators(); | ||
isDone = true; | ||
return Promise.reject(error); | ||
}, | ||
}; | ||
} | ||
class DeferredFragmentRecord { | ||
constructor(opts) { | ||
this.type = 'defer'; | ||
this.label = opts.label; | ||
this.path = opts.path; | ||
this.parentContext = opts.parentContext; | ||
this.errors = []; | ||
this._exeContext = opts.exeContext; | ||
this._exeContext.subsequentPayloads.add(this); | ||
this.isCompleted = false; | ||
this.data = null; | ||
this.promise = new Promise((resolve) => { | ||
this._resolve = (promiseOrValue) => { | ||
resolve(promiseOrValue); | ||
}; | ||
}).then((data) => { | ||
this.data = data; | ||
this.isCompleted = true; | ||
}); | ||
} | ||
addData(data) { | ||
const parentData = this.parentContext?.promise; | ||
if (parentData) { | ||
this._resolve?.(parentData.then(() => data)); | ||
return; | ||
} | ||
this._resolve?.(data); | ||
} | ||
} | ||
class StreamRecord { | ||
constructor(opts) { | ||
this.type = 'stream'; | ||
this.items = null; | ||
this.label = opts.label; | ||
this.path = opts.path; | ||
this.parentContext = opts.parentContext; | ||
this.iterator = opts.iterator; | ||
this.errors = []; | ||
this._exeContext = opts.exeContext; | ||
this._exeContext.subsequentPayloads.add(this); | ||
this.isCompleted = false; | ||
this.items = null; | ||
this.promise = new Promise((resolve) => { | ||
this._resolve = (promiseOrValue) => { | ||
resolve(promiseOrValue); | ||
}; | ||
}).then((items) => { | ||
this.items = items; | ||
this.isCompleted = true; | ||
}); | ||
} | ||
addItems(items) { | ||
const parentData = this.parentContext?.promise; | ||
if (parentData) { | ||
this._resolve?.(parentData.then(() => items)); | ||
return; | ||
} | ||
this._resolve?.(items); | ||
} | ||
setIsCompletedIterator() { | ||
this.isCompletedIterator = true; | ||
} | ||
} | ||
function isStreamPayload(asyncPayload) { | ||
return asyncPayload.type === 'stream'; | ||
} |
@@ -5,2 +5,3 @@ export { pathToArray as responsePathAsArray } from '../jsutils/Path'; | ||
execute, | ||
experimentalExecuteIncrementally, | ||
executeSync, | ||
@@ -10,2 +11,3 @@ defaultFieldResolver, | ||
subscribe, | ||
experimentalSubscribeIncrementally, | ||
} from './execute'; | ||
@@ -15,3 +17,14 @@ export type { | ||
ExecutionResult, | ||
ExperimentalExecuteIncrementallyResults, | ||
InitialIncrementalExecutionResult, | ||
SubsequentIncrementalExecutionResult, | ||
IncrementalDeferResult, | ||
IncrementalStreamResult, | ||
IncrementalResult, | ||
FormattedExecutionResult, | ||
FormattedInitialIncrementalExecutionResult, | ||
FormattedSubsequentIncrementalExecutionResult, | ||
FormattedIncrementalDeferResult, | ||
FormattedIncrementalStreamResult, | ||
FormattedIncrementalResult, | ||
} from './execute'; | ||
@@ -18,0 +31,0 @@ export { |
@@ -5,2 +5,3 @@ export { pathToArray as responsePathAsArray } from '../jsutils/Path.js'; | ||
execute, | ||
experimentalExecuteIncrementally, | ||
executeSync, | ||
@@ -10,2 +11,3 @@ defaultFieldResolver, | ||
subscribe, | ||
experimentalSubscribeIncrementally, | ||
} from './execute.js'; | ||
@@ -12,0 +14,0 @@ export { |
@@ -71,3 +71,3 @@ import type { Maybe } from '../jsutils/Maybe'; | ||
node: { | ||
readonly directives?: ReadonlyArray<DirectiveNode>; | ||
readonly directives?: ReadonlyArray<DirectiveNode> | undefined; | ||
}, | ||
@@ -74,0 +74,0 @@ variableValues?: Maybe<ObjMap<unknown>>, |
@@ -18,2 +18,4 @@ import type { Maybe } from './jsutils/Maybe'; | ||
* | ||
* This function does not support incremental delivery (`@defer` and `@stream`). | ||
* | ||
* Accepts either an object with named arguments, or individual arguments: | ||
@@ -20,0 +22,0 @@ * |
@@ -55,2 +55,4 @@ /** | ||
GraphQLSkipDirective, | ||
GraphQLDeferDirective, | ||
GraphQLStreamDirective, | ||
GraphQLDeprecatedDirective, | ||
@@ -205,2 +207,3 @@ GraphQLSpecifiedByDirective, | ||
isSelectionNode, | ||
isNullabilityAssertionNode, | ||
isValueNode, | ||
@@ -233,2 +236,6 @@ isConstValueNode, | ||
ArgumentNode, | ||
NullabilityAssertionNode, | ||
NonNullAssertionNode, | ||
ErrorBoundaryNode, | ||
ListNullabilityOperatorNode, | ||
ConstArgumentNode, | ||
@@ -284,2 +291,3 @@ FragmentSpreadNode, | ||
execute, | ||
experimentalExecuteIncrementally, | ||
executeSync, | ||
@@ -293,2 +301,3 @@ defaultFieldResolver, | ||
subscribe, | ||
experimentalSubscribeIncrementally, | ||
createSourceEventStream, | ||
@@ -299,3 +308,14 @@ } from './execution/index'; | ||
ExecutionResult, | ||
ExperimentalExecuteIncrementallyResults, | ||
InitialIncrementalExecutionResult, | ||
SubsequentIncrementalExecutionResult, | ||
IncrementalDeferResult, | ||
IncrementalStreamResult, | ||
IncrementalResult, | ||
FormattedExecutionResult, | ||
FormattedInitialIncrementalExecutionResult, | ||
FormattedSubsequentIncrementalExecutionResult, | ||
FormattedIncrementalDeferResult, | ||
FormattedIncrementalStreamResult, | ||
FormattedIncrementalResult, | ||
} from './execution/index'; | ||
@@ -302,0 +322,0 @@ export { |
@@ -60,2 +60,4 @@ /** | ||
GraphQLSkipDirective, | ||
GraphQLDeferDirective, | ||
GraphQLStreamDirective, | ||
GraphQLDeprecatedDirective, | ||
@@ -167,2 +169,3 @@ GraphQLSpecifiedByDirective, | ||
isSelectionNode, | ||
isNullabilityAssertionNode, | ||
isValueNode, | ||
@@ -179,2 +182,3 @@ isConstValueNode, | ||
execute, | ||
experimentalExecuteIncrementally, | ||
executeSync, | ||
@@ -188,2 +192,3 @@ defaultFieldResolver, | ||
subscribe, | ||
experimentalSubscribeIncrementally, | ||
createSourceEventStream, | ||
@@ -190,0 +195,0 @@ } from './execution/index.js'; |
@@ -9,6 +9,8 @@ /** | ||
export function promiseForObject(object) { | ||
return Promise.all(Object.values(object)).then((resolvedValues) => { | ||
const keys = Object.keys(object); | ||
const values = Object.values(object); | ||
return Promise.all(values).then((resolvedValues) => { | ||
const resolvedObject = Object.create(null); | ||
for (const [i, key] of Object.keys(object).entries()) { | ||
resolvedObject[key] = resolvedValues[i]; | ||
for (let i = 0; i < keys.length; ++i) { | ||
resolvedObject[keys[i]] = resolvedValues[i]; | ||
} | ||
@@ -15,0 +17,0 @@ return resolvedObject; |
@@ -137,3 +137,6 @@ import type { Kind } from './kinds'; | ||
| EnumTypeExtensionNode | ||
| InputObjectTypeExtensionNode; | ||
| InputObjectTypeExtensionNode | ||
| NonNullAssertionNode | ||
| ErrorBoundaryNode | ||
| ListNullabilityOperatorNode; | ||
/** | ||
@@ -158,3 +161,3 @@ * Utility type listing all nodes indexed by their kind. | ||
readonly kind: Kind.NAME; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly value: string; | ||
@@ -165,3 +168,3 @@ } | ||
readonly kind: Kind.DOCUMENT; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly definitions: ReadonlyArray<DefinitionNode>; | ||
@@ -178,7 +181,9 @@ } | ||
readonly kind: Kind.OPERATION_DEFINITION; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly operation: OperationTypeNode; | ||
readonly name?: NameNode; | ||
readonly variableDefinitions?: ReadonlyArray<VariableDefinitionNode>; | ||
readonly directives?: ReadonlyArray<DirectiveNode>; | ||
readonly name?: NameNode | undefined; | ||
readonly variableDefinitions?: | ||
| ReadonlyArray<VariableDefinitionNode> | ||
| undefined; | ||
readonly directives?: ReadonlyArray<DirectiveNode> | undefined; | ||
readonly selectionSet: SelectionSetNode; | ||
@@ -193,11 +198,11 @@ } | ||
readonly kind: Kind.VARIABLE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly variable: VariableNode; | ||
readonly type: TypeNode; | ||
readonly defaultValue?: ConstValueNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly defaultValue?: ConstValueNode | undefined; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
} | ||
export interface VariableNode { | ||
readonly kind: Kind.VARIABLE; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
@@ -207,3 +212,3 @@ } | ||
kind: Kind.SELECTION_SET; | ||
loc?: Location; | ||
loc?: Location | undefined; | ||
selections: ReadonlyArray<SelectionNode>; | ||
@@ -217,12 +222,32 @@ } | ||
readonly kind: Kind.FIELD; | ||
readonly loc?: Location; | ||
readonly alias?: NameNode; | ||
readonly loc?: Location | undefined; | ||
readonly alias?: NameNode | undefined; | ||
readonly name: NameNode; | ||
readonly arguments?: ReadonlyArray<ArgumentNode>; | ||
readonly directives?: ReadonlyArray<DirectiveNode>; | ||
readonly selectionSet?: SelectionSetNode; | ||
readonly arguments?: ReadonlyArray<ArgumentNode> | undefined; | ||
readonly nullabilityAssertion?: NullabilityAssertionNode | undefined; | ||
readonly directives?: ReadonlyArray<DirectiveNode> | undefined; | ||
readonly selectionSet?: SelectionSetNode | undefined; | ||
} | ||
export declare type NullabilityAssertionNode = | ||
| NonNullAssertionNode | ||
| ErrorBoundaryNode | ||
| ListNullabilityOperatorNode; | ||
export interface ListNullabilityOperatorNode { | ||
readonly kind: Kind.LIST_NULLABILITY_OPERATOR; | ||
readonly loc?: Location | undefined; | ||
readonly nullabilityAssertion?: NullabilityAssertionNode | undefined; | ||
} | ||
export interface NonNullAssertionNode { | ||
readonly kind: Kind.NON_NULL_ASSERTION; | ||
readonly loc?: Location | undefined; | ||
readonly nullabilityAssertion?: ListNullabilityOperatorNode | undefined; | ||
} | ||
export interface ErrorBoundaryNode { | ||
readonly kind: Kind.ERROR_BOUNDARY; | ||
readonly loc?: Location | undefined; | ||
readonly nullabilityAssertion?: ListNullabilityOperatorNode | undefined; | ||
} | ||
export interface ArgumentNode { | ||
readonly kind: Kind.ARGUMENT; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
@@ -233,3 +258,3 @@ readonly value: ValueNode; | ||
readonly kind: Kind.ARGUMENT; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
@@ -241,11 +266,11 @@ readonly value: ConstValueNode; | ||
readonly kind: Kind.FRAGMENT_SPREAD; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
readonly directives?: ReadonlyArray<DirectiveNode>; | ||
readonly directives?: ReadonlyArray<DirectiveNode> | undefined; | ||
} | ||
export interface InlineFragmentNode { | ||
readonly kind: Kind.INLINE_FRAGMENT; | ||
readonly loc?: Location; | ||
readonly typeCondition?: NamedTypeNode; | ||
readonly directives?: ReadonlyArray<DirectiveNode>; | ||
readonly loc?: Location | undefined; | ||
readonly typeCondition?: NamedTypeNode | undefined; | ||
readonly directives?: ReadonlyArray<DirectiveNode> | undefined; | ||
readonly selectionSet: SelectionSetNode; | ||
@@ -255,8 +280,10 @@ } | ||
readonly kind: Kind.FRAGMENT_DEFINITION; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
/** @deprecated variableDefinitions will be removed in v17.0.0 */ | ||
readonly variableDefinitions?: ReadonlyArray<VariableDefinitionNode>; | ||
readonly variableDefinitions?: | ||
| ReadonlyArray<VariableDefinitionNode> | ||
| undefined; | ||
readonly typeCondition: NamedTypeNode; | ||
readonly directives?: ReadonlyArray<DirectiveNode>; | ||
readonly directives?: ReadonlyArray<DirectiveNode> | undefined; | ||
readonly selectionSet: SelectionSetNode; | ||
@@ -286,3 +313,3 @@ } | ||
readonly kind: Kind.INT; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly value: string; | ||
@@ -292,3 +319,3 @@ } | ||
readonly kind: Kind.FLOAT; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly value: string; | ||
@@ -298,9 +325,9 @@ } | ||
readonly kind: Kind.STRING; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly value: string; | ||
readonly block?: boolean; | ||
readonly block?: boolean | undefined; | ||
} | ||
export interface BooleanValueNode { | ||
readonly kind: Kind.BOOLEAN; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly value: boolean; | ||
@@ -310,7 +337,7 @@ } | ||
readonly kind: Kind.NULL; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
} | ||
export interface EnumValueNode { | ||
readonly kind: Kind.ENUM; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly value: string; | ||
@@ -320,3 +347,3 @@ } | ||
readonly kind: Kind.LIST; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly values: ReadonlyArray<ValueNode>; | ||
@@ -326,3 +353,3 @@ } | ||
readonly kind: Kind.LIST; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly values: ReadonlyArray<ConstValueNode>; | ||
@@ -332,3 +359,3 @@ } | ||
readonly kind: Kind.OBJECT; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly fields: ReadonlyArray<ObjectFieldNode>; | ||
@@ -338,3 +365,3 @@ } | ||
readonly kind: Kind.OBJECT; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly fields: ReadonlyArray<ConstObjectFieldNode>; | ||
@@ -344,3 +371,3 @@ } | ||
readonly kind: Kind.OBJECT_FIELD; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
@@ -351,3 +378,3 @@ readonly value: ValueNode; | ||
readonly kind: Kind.OBJECT_FIELD; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
@@ -359,11 +386,11 @@ readonly value: ConstValueNode; | ||
readonly kind: Kind.DIRECTIVE; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
readonly arguments?: ReadonlyArray<ArgumentNode>; | ||
readonly arguments?: ReadonlyArray<ArgumentNode> | undefined; | ||
} | ||
export interface ConstDirectiveNode { | ||
readonly kind: Kind.DIRECTIVE; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
readonly arguments?: ReadonlyArray<ConstArgumentNode>; | ||
readonly arguments?: ReadonlyArray<ConstArgumentNode> | undefined; | ||
} | ||
@@ -374,3 +401,3 @@ /** Type Reference */ | ||
readonly kind: Kind.NAMED_TYPE; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
@@ -380,3 +407,3 @@ } | ||
readonly kind: Kind.LIST_TYPE; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly type: TypeNode; | ||
@@ -386,3 +413,3 @@ } | ||
readonly kind: Kind.NON_NULL_TYPE; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly type: NamedTypeNode | ListTypeNode; | ||
@@ -397,5 +424,5 @@ } | ||
readonly kind: Kind.SCHEMA_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly operationTypes: ReadonlyArray<OperationTypeDefinitionNode>; | ||
@@ -405,3 +432,3 @@ } | ||
readonly kind: Kind.OPERATION_TYPE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly operation: OperationTypeNode; | ||
@@ -420,73 +447,73 @@ readonly type: NamedTypeNode; | ||
readonly kind: Kind.SCALAR_TYPE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly name: NameNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
} | ||
export interface ObjectTypeDefinitionNode { | ||
readonly kind: Kind.OBJECT_TYPE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly name: NameNode; | ||
readonly interfaces?: ReadonlyArray<NamedTypeNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly fields?: ReadonlyArray<FieldDefinitionNode>; | ||
readonly interfaces?: ReadonlyArray<NamedTypeNode> | undefined; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly fields?: ReadonlyArray<FieldDefinitionNode> | undefined; | ||
} | ||
export interface FieldDefinitionNode { | ||
readonly kind: Kind.FIELD_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly name: NameNode; | ||
readonly arguments?: ReadonlyArray<InputValueDefinitionNode>; | ||
readonly arguments?: ReadonlyArray<InputValueDefinitionNode> | undefined; | ||
readonly type: TypeNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
} | ||
export interface InputValueDefinitionNode { | ||
readonly kind: Kind.INPUT_VALUE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly name: NameNode; | ||
readonly type: TypeNode; | ||
readonly defaultValue?: ConstValueNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly defaultValue?: ConstValueNode | undefined; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
} | ||
export interface InterfaceTypeDefinitionNode { | ||
readonly kind: Kind.INTERFACE_TYPE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly name: NameNode; | ||
readonly interfaces?: ReadonlyArray<NamedTypeNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly fields?: ReadonlyArray<FieldDefinitionNode>; | ||
readonly interfaces?: ReadonlyArray<NamedTypeNode> | undefined; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly fields?: ReadonlyArray<FieldDefinitionNode> | undefined; | ||
} | ||
export interface UnionTypeDefinitionNode { | ||
readonly kind: Kind.UNION_TYPE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly name: NameNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly types?: ReadonlyArray<NamedTypeNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly types?: ReadonlyArray<NamedTypeNode> | undefined; | ||
} | ||
export interface EnumTypeDefinitionNode { | ||
readonly kind: Kind.ENUM_TYPE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly name: NameNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly values?: ReadonlyArray<EnumValueDefinitionNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly values?: ReadonlyArray<EnumValueDefinitionNode> | undefined; | ||
} | ||
export interface EnumValueDefinitionNode { | ||
readonly kind: Kind.ENUM_VALUE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly name: NameNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
} | ||
export interface InputObjectTypeDefinitionNode { | ||
readonly kind: Kind.INPUT_OBJECT_TYPE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly name: NameNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly fields?: ReadonlyArray<InputValueDefinitionNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly fields?: ReadonlyArray<InputValueDefinitionNode> | undefined; | ||
} | ||
@@ -496,6 +523,6 @@ /** Directive Definitions */ | ||
readonly kind: Kind.DIRECTIVE_DEFINITION; | ||
readonly loc?: Location; | ||
readonly description?: StringValueNode; | ||
readonly loc?: Location | undefined; | ||
readonly description?: StringValueNode | undefined; | ||
readonly name: NameNode; | ||
readonly arguments?: ReadonlyArray<InputValueDefinitionNode>; | ||
readonly arguments?: ReadonlyArray<InputValueDefinitionNode> | undefined; | ||
readonly repeatable: boolean; | ||
@@ -510,5 +537,7 @@ readonly locations: ReadonlyArray<NameNode>; | ||
readonly kind: Kind.SCHEMA_EXTENSION; | ||
readonly loc?: Location; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly operationTypes?: ReadonlyArray<OperationTypeDefinitionNode>; | ||
readonly loc?: Location | undefined; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly operationTypes?: | ||
| ReadonlyArray<OperationTypeDefinitionNode> | ||
| undefined; | ||
} | ||
@@ -525,42 +554,42 @@ /** Type Extensions */ | ||
readonly kind: Kind.SCALAR_TYPE_EXTENSION; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
} | ||
export interface ObjectTypeExtensionNode { | ||
readonly kind: Kind.OBJECT_TYPE_EXTENSION; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
readonly interfaces?: ReadonlyArray<NamedTypeNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly fields?: ReadonlyArray<FieldDefinitionNode>; | ||
readonly interfaces?: ReadonlyArray<NamedTypeNode> | undefined; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly fields?: ReadonlyArray<FieldDefinitionNode> | undefined; | ||
} | ||
export interface InterfaceTypeExtensionNode { | ||
readonly kind: Kind.INTERFACE_TYPE_EXTENSION; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
readonly interfaces?: ReadonlyArray<NamedTypeNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly fields?: ReadonlyArray<FieldDefinitionNode>; | ||
readonly interfaces?: ReadonlyArray<NamedTypeNode> | undefined; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly fields?: ReadonlyArray<FieldDefinitionNode> | undefined; | ||
} | ||
export interface UnionTypeExtensionNode { | ||
readonly kind: Kind.UNION_TYPE_EXTENSION; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly types?: ReadonlyArray<NamedTypeNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly types?: ReadonlyArray<NamedTypeNode> | undefined; | ||
} | ||
export interface EnumTypeExtensionNode { | ||
readonly kind: Kind.ENUM_TYPE_EXTENSION; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly values?: ReadonlyArray<EnumValueDefinitionNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly values?: ReadonlyArray<EnumValueDefinitionNode> | undefined; | ||
} | ||
export interface InputObjectTypeExtensionNode { | ||
readonly kind: Kind.INPUT_OBJECT_TYPE_EXTENSION; | ||
readonly loc?: Location; | ||
readonly loc?: Location | undefined; | ||
readonly name: NameNode; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode>; | ||
readonly fields?: ReadonlyArray<InputValueDefinitionNode>; | ||
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined; | ||
readonly fields?: ReadonlyArray<InputValueDefinitionNode> | undefined; | ||
} |
@@ -64,4 +64,18 @@ /** | ||
SelectionSet: ['selections'], | ||
Field: ['alias', 'name', 'arguments', 'directives', 'selectionSet'], | ||
Field: [ | ||
'alias', | ||
'name', | ||
'arguments', | ||
'directives', | ||
'selectionSet', | ||
// Note: Client Controlled Nullability is experimental and may be changed | ||
// or removed in the future. | ||
'nullabilityAssertion', | ||
], | ||
Argument: ['name', 'value'], | ||
// Note: Client Controlled Nullability is experimental and may be changed | ||
// or removed in the future. | ||
ListNullabilityOperator: ['nullabilityAssertion'], | ||
NonNullAssertion: ['nullabilityAssertion'], | ||
ErrorBoundary: ['nullabilityAssertion'], | ||
FragmentSpread: ['name', 'directives'], | ||
@@ -68,0 +82,0 @@ InlineFragment: ['typeCondition', 'directives', 'selectionSet'], |
@@ -27,2 +27,6 @@ export { Source } from './source'; | ||
FieldNode, | ||
NullabilityAssertionNode, | ||
NonNullAssertionNode, | ||
ErrorBoundaryNode, | ||
ListNullabilityOperatorNode, | ||
ArgumentNode, | ||
@@ -81,2 +85,3 @@ ConstArgumentNode, | ||
isSelectionNode, | ||
isNullabilityAssertionNode, | ||
isValueNode, | ||
@@ -83,0 +88,0 @@ isConstValueNode, |
@@ -20,2 +20,3 @@ export { Source } from './source.js'; | ||
isSelectionNode, | ||
isNullabilityAssertionNode, | ||
isValueNode, | ||
@@ -22,0 +23,0 @@ isConstValueNode, |
@@ -14,2 +14,6 @@ /** | ||
ARGUMENT = 'Argument', | ||
/** Nullability Modifiers */ | ||
LIST_NULLABILITY_OPERATOR = 'ListNullabilityOperator', | ||
NON_NULL_ASSERTION = 'NonNullAssertion', | ||
ERROR_BOUNDARY = 'ErrorBoundary', | ||
/** Fragments */ | ||
@@ -16,0 +20,0 @@ FRAGMENT_SPREAD = 'FragmentSpread', |
@@ -15,2 +15,6 @@ /** | ||
Kind['ARGUMENT'] = 'Argument'; | ||
/** Nullability Modifiers */ | ||
Kind['LIST_NULLABILITY_OPERATOR'] = 'ListNullabilityOperator'; | ||
Kind['NON_NULL_ASSERTION'] = 'NonNullAssertion'; | ||
Kind['ERROR_BOUNDARY'] = 'ErrorBoundary'; | ||
/** Fragments */ | ||
@@ -17,0 +21,0 @@ Kind['FRAGMENT_SPREAD'] = 'FragmentSpread'; |
@@ -64,2 +64,3 @@ import { syntaxError } from '../error/syntaxError.js'; | ||
kind === TokenKind.BANG || | ||
kind === TokenKind.QUESTION_MARK || | ||
kind === TokenKind.DOLLAR || | ||
@@ -238,2 +239,9 @@ kind === TokenKind.AMP || | ||
return createToken(lexer, TokenKind.BRACE_R, position, position + 1); | ||
case 0x003f: // ? | ||
return createToken( | ||
lexer, | ||
TokenKind.QUESTION_MARK, | ||
position, | ||
position + 1, | ||
); | ||
// StringValue | ||
@@ -240,0 +248,0 @@ case 0x0022: // " |
@@ -31,2 +31,3 @@ import type { Maybe } from '../jsutils/Maybe'; | ||
NameNode, | ||
NullabilityAssertionNode, | ||
ObjectFieldNode, | ||
@@ -67,4 +68,12 @@ ObjectTypeDefinitionNode, | ||
*/ | ||
noLocation?: boolean; | ||
noLocation?: boolean | undefined; | ||
/** | ||
* Parser CPU and memory usage is linear to the number of tokens in a document | ||
* however in extreme cases it becomes quadratic due to memory exhaustion. | ||
* Parsing happens before validation so even invalid queries can burn lots of | ||
* CPU time and memory. | ||
* To prevent this you can set a maximum number of tokens allowed within a document. | ||
*/ | ||
maxTokens?: number | undefined; | ||
/** | ||
* @deprecated will be removed in the v17.0.0 | ||
@@ -84,3 +93,25 @@ * | ||
*/ | ||
allowLegacyFragmentVariables?: boolean; | ||
allowLegacyFragmentVariables?: boolean | undefined; | ||
/** | ||
* EXPERIMENTAL: | ||
* | ||
* If enabled, the parser will understand and parse Client Controlled Nullability | ||
* Designators contained in Fields. They'll be represented in the | ||
* `nullabilityAssertion` field of the FieldNode. | ||
* | ||
* The syntax looks like the following: | ||
* | ||
* ```graphql | ||
* { | ||
* nullableField! | ||
* nonNullableField? | ||
* nonNullableSelectionSet? { | ||
* childField! | ||
* } | ||
* } | ||
* ``` | ||
* Note: this feature is experimental and may change or be removed in the | ||
* future. | ||
*/ | ||
experimentalClientControlledNullability?: boolean | undefined; | ||
} | ||
@@ -93,3 +124,3 @@ /** | ||
source: string | Source, | ||
options?: ParseOptions, | ||
options?: ParseOptions | undefined, | ||
): DocumentNode; | ||
@@ -108,3 +139,3 @@ /** | ||
source: string | Source, | ||
options?: ParseOptions, | ||
options?: ParseOptions | undefined, | ||
): ValueNode; | ||
@@ -117,3 +148,3 @@ /** | ||
source: string | Source, | ||
options?: ParseOptions, | ||
options?: ParseOptions | undefined, | ||
): ConstValueNode; | ||
@@ -132,3 +163,3 @@ /** | ||
source: string | Source, | ||
options?: ParseOptions, | ||
options?: ParseOptions | undefined, | ||
): TypeNode; | ||
@@ -147,4 +178,5 @@ /** | ||
export declare class Parser { | ||
protected _options: Maybe<ParseOptions>; | ||
protected _options: ParseOptions; | ||
protected _lexer: Lexer; | ||
protected _tokenCounter: number; | ||
constructor(source: string | Source, options?: ParseOptions); | ||
@@ -224,2 +256,3 @@ /** | ||
parseField(): FieldNode; | ||
parseNullabilityAssertion(): NullabilityAssertionNode | undefined; | ||
/** | ||
@@ -499,3 +532,3 @@ * Arguments[Const] : ( Argument[?Const]+ ) | ||
T extends { | ||
loc?: Location; | ||
loc?: Location | undefined; | ||
}, | ||
@@ -564,2 +597,3 @@ >(startToken: Token, node: T): T; | ||
delimitedMany<T>(delimiterKind: TokenKind, parseFn: () => T): Array<T>; | ||
advanceLexer(): void; | ||
} |
@@ -73,6 +73,7 @@ import { syntaxError } from '../error/syntaxError.js'; | ||
export class Parser { | ||
constructor(source, options) { | ||
constructor(source, options = {}) { | ||
const sourceObj = isSource(source) ? source : new Source(source); | ||
this._lexer = new Lexer(sourceObj); | ||
this._options = options; | ||
this._tokenCounter = 0; | ||
} | ||
@@ -303,2 +304,5 @@ /** | ||
arguments: this.parseArguments(false), | ||
// Experimental support for Client Controlled Nullability changes | ||
// the grammar of Field: | ||
nullabilityAssertion: this.parseNullabilityAssertion(), | ||
directives: this.parseDirectives(false), | ||
@@ -310,2 +314,32 @@ selectionSet: this.peek(TokenKind.BRACE_L) | ||
} | ||
// TODO: add grammar comment after it finalizes | ||
parseNullabilityAssertion() { | ||
// Note: Client Controlled Nullability is experimental and may be changed or | ||
// removed in the future. | ||
if (this._options.experimentalClientControlledNullability !== true) { | ||
return undefined; | ||
} | ||
const start = this._lexer.token; | ||
let nullabilityAssertion; | ||
if (this.expectOptionalToken(TokenKind.BRACKET_L)) { | ||
const innerModifier = this.parseNullabilityAssertion(); | ||
this.expectToken(TokenKind.BRACKET_R); | ||
nullabilityAssertion = this.node(start, { | ||
kind: Kind.LIST_NULLABILITY_OPERATOR, | ||
nullabilityAssertion: innerModifier, | ||
}); | ||
} | ||
if (this.expectOptionalToken(TokenKind.BANG)) { | ||
nullabilityAssertion = this.node(start, { | ||
kind: Kind.NON_NULL_ASSERTION, | ||
nullabilityAssertion, | ||
}); | ||
} else if (this.expectOptionalToken(TokenKind.QUESTION_MARK)) { | ||
nullabilityAssertion = this.node(start, { | ||
kind: Kind.ERROR_BOUNDARY, | ||
nullabilityAssertion, | ||
}); | ||
} | ||
return nullabilityAssertion; | ||
} | ||
parseArguments(isConst) { | ||
@@ -366,3 +400,3 @@ const item = isConst ? this.parseConstArgument : this.parseArgument; | ||
// - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet | ||
if (this._options?.allowLegacyFragmentVariables === true) { | ||
if (this._options.allowLegacyFragmentVariables === true) { | ||
return this.node(start, { | ||
@@ -402,3 +436,3 @@ kind: Kind.FRAGMENT_DEFINITION, | ||
case TokenKind.INT: | ||
this._lexer.advance(); | ||
this.advanceLexer(); | ||
return this.node(token, { | ||
@@ -409,3 +443,3 @@ kind: Kind.INT, | ||
case TokenKind.FLOAT: | ||
this._lexer.advance(); | ||
this.advanceLexer(); | ||
return this.node(token, { | ||
@@ -419,3 +453,3 @@ kind: Kind.FLOAT, | ||
case TokenKind.NAME: | ||
this._lexer.advance(); | ||
this.advanceLexer(); | ||
switch (token.value) { | ||
@@ -464,3 +498,3 @@ case 'true': | ||
const token = this._lexer.token; | ||
this._lexer.advance(); | ||
this.advanceLexer(); | ||
return this.node(token, { | ||
@@ -1140,3 +1174,3 @@ kind: Kind.STRING, | ||
node(startToken, node) { | ||
if (this._options?.noLocation !== true) { | ||
if (this._options.noLocation !== true) { | ||
node.loc = new Location( | ||
@@ -1163,3 +1197,3 @@ startToken, | ||
if (token.kind === kind) { | ||
this._lexer.advance(); | ||
this.advanceLexer(); | ||
return token; | ||
@@ -1180,3 +1214,3 @@ } | ||
if (token.kind === kind) { | ||
this._lexer.advance(); | ||
this.advanceLexer(); | ||
return true; | ||
@@ -1193,3 +1227,3 @@ } | ||
if (token.kind === TokenKind.NAME && token.value === value) { | ||
this._lexer.advance(); | ||
this.advanceLexer(); | ||
} else { | ||
@@ -1210,3 +1244,3 @@ throw syntaxError( | ||
if (token.kind === TokenKind.NAME && token.value === value) { | ||
this._lexer.advance(); | ||
this.advanceLexer(); | ||
return true; | ||
@@ -1282,2 +1316,16 @@ } | ||
} | ||
advanceLexer() { | ||
const { maxTokens } = this._options; | ||
const token = this._lexer.advance(); | ||
if (maxTokens !== undefined && token.kind !== TokenKind.EOF) { | ||
++this._tokenCounter; | ||
if (this._tokenCounter > maxTokens) { | ||
throw syntaxError( | ||
this._lexer.source, | ||
token.start, | ||
`Document contains more that ${maxTokens} tokens. Parsing aborted.`, | ||
); | ||
} | ||
} | ||
} | ||
} | ||
@@ -1284,0 +1332,0 @@ /** |
@@ -6,2 +6,3 @@ import type { | ||
ExecutableDefinitionNode, | ||
NullabilityAssertionNode, | ||
SelectionNode, | ||
@@ -20,2 +21,5 @@ TypeDefinitionNode, | ||
export declare function isSelectionNode(node: ASTNode): node is SelectionNode; | ||
export declare function isNullabilityAssertionNode( | ||
node: ASTNode, | ||
): node is NullabilityAssertionNode; | ||
export declare function isValueNode(node: ASTNode): node is ValueNode; | ||
@@ -22,0 +26,0 @@ export declare function isConstValueNode(node: ASTNode): node is ConstValueNode; |
@@ -22,2 +22,9 @@ import { Kind } from './kinds.js'; | ||
} | ||
export function isNullabilityAssertionNode(node) { | ||
return ( | ||
node.kind === Kind.LIST_NULLABILITY_OPERATOR || | ||
node.kind === Kind.NON_NULL_ASSERTION || | ||
node.kind === Kind.ERROR_BOUNDARY | ||
); | ||
} | ||
export function isValueNode(node) { | ||
@@ -24,0 +31,0 @@ return ( |
@@ -45,4 +45,11 @@ import { printBlockString } from './blockString.js'; | ||
Field: { | ||
leave({ alias, name, arguments: args, directives, selectionSet }) { | ||
const prefix = wrap('', alias, ': ') + name; | ||
leave({ | ||
alias, | ||
name, | ||
arguments: args, | ||
nullabilityAssertion, | ||
directives, | ||
selectionSet, | ||
}) { | ||
const prefix = join([wrap('', alias, ': '), name], ''); | ||
let argsLine = prefix + wrap('(', join(args, ', '), ')'); | ||
@@ -52,6 +59,29 @@ if (argsLine.length > MAX_LINE_LENGTH) { | ||
} | ||
return join([argsLine, join(directives, ' '), selectionSet], ' '); | ||
return join([ | ||
argsLine, | ||
// Note: Client Controlled Nullability is experimental and may be | ||
// changed or removed in the future. | ||
nullabilityAssertion, | ||
wrap(' ', join(directives, ' ')), | ||
wrap(' ', selectionSet), | ||
]); | ||
}, | ||
}, | ||
Argument: { leave: ({ name, value }) => name + ': ' + value }, | ||
// Nullability Modifiers | ||
ListNullabilityOperator: { | ||
leave({ nullabilityAssertion }) { | ||
return join(['[', nullabilityAssertion, ']']); | ||
}, | ||
}, | ||
NonNullAssertion: { | ||
leave({ nullabilityAssertion }) { | ||
return join([nullabilityAssertion, '!']); | ||
}, | ||
}, | ||
ErrorBoundary: { | ||
leave({ nullabilityAssertion }) { | ||
return join([nullabilityAssertion, '?']); | ||
}, | ||
}, | ||
// Fragments | ||
@@ -58,0 +88,0 @@ FragmentSpread: { |
@@ -9,2 +9,3 @@ /** | ||
BANG = '!', | ||
QUESTION_MARK = '?', | ||
DOLLAR = '$', | ||
@@ -11,0 +12,0 @@ AMP = '&', |
@@ -10,2 +10,3 @@ /** | ||
TokenKind['BANG'] = '!'; | ||
TokenKind['QUESTION_MARK'] = '?'; | ||
TokenKind['DOLLAR'] = '$'; | ||
@@ -12,0 +13,0 @@ TokenKind['AMP'] = '&'; |
@@ -14,4 +14,4 @@ import type { ASTNode } from './ast'; | ||
interface EnterLeaveVisitor<TVisitedNode extends ASTNode> { | ||
readonly enter?: ASTVisitFn<TVisitedNode>; | ||
readonly leave?: ASTVisitFn<TVisitedNode>; | ||
readonly enter?: ASTVisitFn<TVisitedNode> | undefined; | ||
readonly leave?: ASTVisitFn<TVisitedNode> | undefined; | ||
} | ||
@@ -18,0 +18,0 @@ /** |
{ | ||
"name": "graphql", | ||
"version": "17.0.0-alpha.1.canary.pr.3658.null", | ||
"version": "17.0.0-alpha.1.canary.pr.3659.5dba20aef36112d13569d5f296ef967383e60d0f", | ||
"description": "A Query Language and Runtime which can target any service.", | ||
@@ -35,3 +35,3 @@ "license": "MIT", | ||
"publishConfig": { | ||
"tag": "canary-pr-3658" | ||
"tag": "canary-pr-3659" | ||
}, | ||
@@ -50,3 +50,3 @@ "type": "module", | ||
}, | ||
"deprecated": "You are using canary version build from https://github.com/graphql/graphql-js/pull/3658, no gurantees provided so please use your own discretion." | ||
"deprecated": "You are using canary version build from https://github.com/graphql/graphql-js/pull/3659, no gurantees provided so please use your own discretion." | ||
} |
@@ -328,7 +328,7 @@ import type { Maybe } from '../jsutils/Maybe'; | ||
/** Serializes an internal value to include in a response. */ | ||
serialize?: GraphQLScalarSerializer<TExternal>; | ||
serialize?: GraphQLScalarSerializer<TExternal> | undefined; | ||
/** Parses an externally provided value to use as an input. */ | ||
parseValue?: GraphQLScalarValueParser<TInternal>; | ||
parseValue?: GraphQLScalarValueParser<TInternal> | undefined; | ||
/** Parses an externally provided literal value to use as an input. */ | ||
parseLiteral?: GraphQLScalarLiteralParser<TInternal>; | ||
parseLiteral?: GraphQLScalarLiteralParser<TInternal> | undefined; | ||
extensions?: Maybe<Readonly<GraphQLScalarTypeExtensions>>; | ||
@@ -430,3 +430,3 @@ astNode?: Maybe<ScalarTypeDefinitionNode>; | ||
description?: Maybe<string>; | ||
interfaces?: ThunkReadonlyArray<GraphQLInterfaceType>; | ||
interfaces?: ThunkReadonlyArray<GraphQLInterfaceType> | undefined; | ||
fields: ThunkObjMap<GraphQLFieldConfig<TSource, TContext>>; | ||
@@ -499,5 +499,5 @@ isTypeOf?: Maybe<GraphQLIsTypeOfFn<TSource, TContext>>; | ||
type: GraphQLOutputType; | ||
args?: GraphQLFieldConfigArgumentMap; | ||
resolve?: GraphQLFieldResolver<TSource, TContext, TArgs>; | ||
subscribe?: GraphQLFieldResolver<TSource, TContext, TArgs>; | ||
args?: GraphQLFieldConfigArgumentMap | undefined; | ||
resolve?: GraphQLFieldResolver<TSource, TContext, TArgs> | undefined; | ||
subscribe?: GraphQLFieldResolver<TSource, TContext, TArgs> | undefined; | ||
deprecationReason?: Maybe<string>; | ||
@@ -539,4 +539,4 @@ extensions?: Maybe< | ||
args: ReadonlyArray<GraphQLArgument>; | ||
resolve?: GraphQLFieldResolver<TSource, TContext, TArgs>; | ||
subscribe?: GraphQLFieldResolver<TSource, TContext, TArgs>; | ||
resolve?: GraphQLFieldResolver<TSource, TContext, TArgs> | undefined; | ||
subscribe?: GraphQLFieldResolver<TSource, TContext, TArgs> | undefined; | ||
deprecationReason: Maybe<string>; | ||
@@ -610,3 +610,3 @@ extensions: Readonly<GraphQLFieldExtensions<TSource, TContext, TArgs>>; | ||
description?: Maybe<string>; | ||
interfaces?: ThunkReadonlyArray<GraphQLInterfaceType>; | ||
interfaces?: ThunkReadonlyArray<GraphQLInterfaceType> | undefined; | ||
fields: ThunkObjMap<GraphQLFieldConfig<TSource, TContext>>; | ||
@@ -613,0 +613,0 @@ /** |
@@ -68,2 +68,10 @@ import type { Maybe } from '../jsutils/Maybe'; | ||
/** | ||
* Used to conditionally defer fragments. | ||
*/ | ||
export declare const GraphQLDeferDirective: GraphQLDirective; | ||
/** | ||
* Used to conditionally stream list fields. | ||
*/ | ||
export declare const GraphQLStreamDirective: GraphQLDirective; | ||
/** | ||
* Constant string used for default reason for a deprecation. | ||
@@ -70,0 +78,0 @@ */ |
@@ -11,3 +11,3 @@ import { inspect } from '../jsutils/inspect.js'; | ||
} from './definition.js'; | ||
import { GraphQLBoolean, GraphQLString } from './scalars.js'; | ||
import { GraphQLBoolean, GraphQLInt, GraphQLString } from './scalars.js'; | ||
/** | ||
@@ -102,2 +102,50 @@ * Test if the given value is a GraphQL directive. | ||
/** | ||
* Used to conditionally defer fragments. | ||
*/ | ||
export const GraphQLDeferDirective = new GraphQLDirective({ | ||
name: 'defer', | ||
description: | ||
'Directs the executor to defer this fragment when the `if` argument is true or undefined.', | ||
locations: [ | ||
DirectiveLocation.FRAGMENT_SPREAD, | ||
DirectiveLocation.INLINE_FRAGMENT, | ||
], | ||
args: { | ||
if: { | ||
type: new GraphQLNonNull(GraphQLBoolean), | ||
description: 'Deferred when true or undefined.', | ||
defaultValue: true, | ||
}, | ||
label: { | ||
type: GraphQLString, | ||
description: 'Unique name', | ||
}, | ||
}, | ||
}); | ||
/** | ||
* Used to conditionally stream list fields. | ||
*/ | ||
export const GraphQLStreamDirective = new GraphQLDirective({ | ||
name: 'stream', | ||
description: | ||
'Directs the executor to stream plural fields when the `if` argument is true or undefined.', | ||
locations: [DirectiveLocation.FIELD], | ||
args: { | ||
if: { | ||
type: new GraphQLNonNull(GraphQLBoolean), | ||
description: 'Stream when true or undefined.', | ||
defaultValue: true, | ||
}, | ||
label: { | ||
type: GraphQLString, | ||
description: 'Unique name', | ||
}, | ||
initialCount: { | ||
defaultValue: 0, | ||
type: GraphQLInt, | ||
description: 'Number of items to return immediately', | ||
}, | ||
}, | ||
}); | ||
/** | ||
* Constant string used for default reason for a deprecation. | ||
@@ -104,0 +152,0 @@ */ |
@@ -116,2 +116,4 @@ export type { Path as ResponsePath } from '../jsutils/Path'; | ||
GraphQLSkipDirective, | ||
GraphQLDeferDirective, | ||
GraphQLStreamDirective, | ||
GraphQLDeprecatedDirective, | ||
@@ -118,0 +120,0 @@ GraphQLSpecifiedByDirective, |
@@ -76,2 +76,4 @@ export { | ||
GraphQLSkipDirective, | ||
GraphQLDeferDirective, | ||
GraphQLStreamDirective, | ||
GraphQLDeprecatedDirective, | ||
@@ -78,0 +80,0 @@ GraphQLSpecifiedByDirective, |
@@ -163,3 +163,3 @@ import type { Maybe } from '../jsutils/Maybe'; | ||
*/ | ||
assumeValid?: boolean; | ||
assumeValid?: boolean | undefined; | ||
} | ||
@@ -166,0 +166,0 @@ export interface GraphQLSchemaConfig extends GraphQLSchemaValidationOptions { |
@@ -144,3 +144,3 @@ import { inspect } from '../jsutils/inspect.js'; | ||
this._typeMap = Object.create(null); | ||
this._subTypeMap = Object.create(null); | ||
this._subTypeMap = new Map(); | ||
// Keep track of all implementations by interface name. | ||
@@ -228,21 +228,16 @@ this._implementationsMap = Object.create(null); | ||
isSubType(abstractType, maybeSubType) { | ||
let map = this._subTypeMap[abstractType.name]; | ||
if (map === undefined) { | ||
map = Object.create(null); | ||
let set = this._subTypeMap.get(abstractType); | ||
if (set === undefined) { | ||
if (isUnionType(abstractType)) { | ||
for (const type of abstractType.getTypes()) { | ||
map[type.name] = true; | ||
} | ||
set = new Set(abstractType.getTypes()); | ||
} else { | ||
const implementations = this.getImplementations(abstractType); | ||
for (const type of implementations.objects) { | ||
map[type.name] = true; | ||
} | ||
for (const type of implementations.interfaces) { | ||
map[type.name] = true; | ||
} | ||
set = new Set([ | ||
...implementations.objects, | ||
...implementations.interfaces, | ||
]); | ||
} | ||
this._subTypeMap[abstractType.name] = map; | ||
this._subTypeMap.set(abstractType, set); | ||
} | ||
return map[maybeSubType.name] !== undefined; | ||
return set.has(maybeSubType); | ||
} | ||
@@ -249,0 +244,0 @@ getDirectives() { |
@@ -95,3 +95,3 @@ import { AccumulatorMap } from '../jsutils/AccumulatorMap.js'; | ||
} | ||
for (const [rootType, operationTypes] of rootTypesMap.entries()) { | ||
for (const [rootType, operationTypes] of rootTypesMap) { | ||
if (operationTypes.length > 1) { | ||
@@ -243,3 +243,3 @@ const operationList = andList(operationTypes); | ||
function validateInterfaces(context, type) { | ||
const ifaceTypeNames = Object.create(null); | ||
const ifaceTypeNames = new Set(); | ||
for (const iface of type.getInterfaces()) { | ||
@@ -261,3 +261,3 @@ if (!isInterfaceType(iface)) { | ||
} | ||
if (ifaceTypeNames[iface.name]) { | ||
if (ifaceTypeNames.has(iface.name)) { | ||
context.reportError( | ||
@@ -269,3 +269,3 @@ `Type ${type.name} can only implement ${iface.name} once.`, | ||
} | ||
ifaceTypeNames[iface.name] = true; | ||
ifaceTypeNames.add(iface.name); | ||
validateTypeImplementsAncestors(context, type, iface); | ||
@@ -362,5 +362,5 @@ validateTypeImplementsInterface(context, type, iface); | ||
} | ||
const includedTypeNames = Object.create(null); | ||
const includedTypeNames = new Set(); | ||
for (const memberType of memberTypes) { | ||
if (includedTypeNames[memberType.name]) { | ||
if (includedTypeNames.has(memberType.name)) { | ||
context.reportError( | ||
@@ -372,3 +372,3 @@ `Union type ${union.name} can only include type ${memberType.name} once.`, | ||
} | ||
includedTypeNames[memberType.name] = true; | ||
includedTypeNames.add(memberType.name); | ||
if (!isObjectType(memberType)) { | ||
@@ -428,3 +428,3 @@ context.reportError( | ||
// are not redundantly reported. | ||
const visitedTypes = Object.create(null); | ||
const visitedTypes = new Set(); | ||
// Array of types nodes used to produce meaningful errors | ||
@@ -439,6 +439,6 @@ const fieldPath = []; | ||
function detectCycleRecursive(inputObj) { | ||
if (visitedTypes[inputObj.name]) { | ||
if (visitedTypes.has(inputObj)) { | ||
return; | ||
} | ||
visitedTypes[inputObj.name] = true; | ||
visitedTypes.add(inputObj); | ||
fieldPathIndexByTypeName[inputObj.name] = fieldPath.length; | ||
@@ -445,0 +445,0 @@ const fields = Object.values(inputObj.getFields()); |
@@ -12,3 +12,3 @@ import type { DocumentNode } from '../language/ast'; | ||
*/ | ||
assumeValidSDL?: boolean; | ||
assumeValidSDL?: boolean | undefined; | ||
} | ||
@@ -15,0 +15,0 @@ /** |
@@ -115,3 +115,3 @@ import { didYouMean } from '../jsutils/didYouMean.js'; | ||
let parseResult; | ||
// Scalars and Enums determine if a input value is valid via parseValue(), | ||
// Scalars and Enums determine if an input value is valid via parseValue(), | ||
// which can throw to indicate failure. If it throws, maintain a reference | ||
@@ -118,0 +118,0 @@ // to the original error. |
@@ -13,3 +13,3 @@ import type { DocumentNode } from '../language/ast'; | ||
*/ | ||
assumeValidSDL?: boolean; | ||
assumeValidSDL?: boolean | undefined; | ||
} | ||
@@ -16,0 +16,0 @@ /** |
@@ -0,1 +1,2 @@ | ||
import { AccumulatorMap } from '../jsutils/AccumulatorMap.js'; | ||
import { inspect } from '../jsutils/inspect.js'; | ||
@@ -7,6 +8,2 @@ import { invariant } from '../jsutils/invariant.js'; | ||
import { | ||
isTypeDefinitionNode, | ||
isTypeExtensionNode, | ||
} from '../language/predicates.js'; | ||
import { | ||
GraphQLEnumType, | ||
@@ -76,3 +73,8 @@ GraphQLInputObjectType, | ||
const typeDefs = []; | ||
const typeExtensionsMap = Object.create(null); | ||
const scalarExtensions = new AccumulatorMap(); | ||
const objectExtensions = new AccumulatorMap(); | ||
const interfaceExtensions = new AccumulatorMap(); | ||
const unionExtensions = new AccumulatorMap(); | ||
const enumExtensions = new AccumulatorMap(); | ||
const inputObjectExtensions = new AccumulatorMap(); | ||
// New directives and types are separate because a directives and types can | ||
@@ -84,28 +86,50 @@ // have the same name. For example, a type named "skip". | ||
const schemaExtensions = []; | ||
let isSchemaChanged = false; | ||
for (const def of documentAST.definitions) { | ||
if (def.kind === Kind.SCHEMA_DEFINITION) { | ||
schemaDef = def; | ||
} else if (def.kind === Kind.SCHEMA_EXTENSION) { | ||
schemaExtensions.push(def); | ||
} else if (isTypeDefinitionNode(def)) { | ||
typeDefs.push(def); | ||
} else if (isTypeExtensionNode(def)) { | ||
const extendedTypeName = def.name.value; | ||
const existingTypeExtensions = typeExtensionsMap[extendedTypeName]; | ||
typeExtensionsMap[extendedTypeName] = existingTypeExtensions | ||
? existingTypeExtensions.concat([def]) | ||
: [def]; | ||
} else if (def.kind === Kind.DIRECTIVE_DEFINITION) { | ||
directiveDefs.push(def); | ||
switch (def.kind) { | ||
case Kind.SCHEMA_DEFINITION: | ||
schemaDef = def; | ||
break; | ||
case Kind.SCHEMA_EXTENSION: | ||
schemaExtensions.push(def); | ||
break; | ||
case Kind.DIRECTIVE_DEFINITION: | ||
directiveDefs.push(def); | ||
break; | ||
// Type Definitions | ||
case Kind.SCALAR_TYPE_DEFINITION: | ||
case Kind.OBJECT_TYPE_DEFINITION: | ||
case Kind.INTERFACE_TYPE_DEFINITION: | ||
case Kind.UNION_TYPE_DEFINITION: | ||
case Kind.ENUM_TYPE_DEFINITION: | ||
case Kind.INPUT_OBJECT_TYPE_DEFINITION: | ||
typeDefs.push(def); | ||
break; | ||
// Type System Extensions | ||
case Kind.SCALAR_TYPE_EXTENSION: | ||
scalarExtensions.add(def.name.value, def); | ||
break; | ||
case Kind.OBJECT_TYPE_EXTENSION: | ||
objectExtensions.add(def.name.value, def); | ||
break; | ||
case Kind.INTERFACE_TYPE_EXTENSION: | ||
interfaceExtensions.add(def.name.value, def); | ||
break; | ||
case Kind.UNION_TYPE_EXTENSION: | ||
unionExtensions.add(def.name.value, def); | ||
break; | ||
case Kind.ENUM_TYPE_EXTENSION: | ||
enumExtensions.add(def.name.value, def); | ||
break; | ||
case Kind.INPUT_OBJECT_TYPE_EXTENSION: | ||
inputObjectExtensions.add(def.name.value, def); | ||
break; | ||
default: | ||
continue; | ||
} | ||
isSchemaChanged = true; | ||
} | ||
// If this document contains no new types, extensions, or directives then | ||
// return the same unmodified GraphQLSchema instance. | ||
if ( | ||
Object.keys(typeExtensionsMap).length === 0 && | ||
typeDefs.length === 0 && | ||
directiveDefs.length === 0 && | ||
schemaExtensions.length === 0 && | ||
schemaDef == null | ||
) { | ||
if (!isSchemaChanged) { | ||
return schemaConfig; | ||
@@ -205,3 +229,3 @@ } | ||
const config = type.toConfig(); | ||
const extensions = typeExtensionsMap[config.name] ?? []; | ||
const extensions = inputObjectExtensions.get(config.name) ?? []; | ||
return new GraphQLInputObjectType({ | ||
@@ -221,3 +245,3 @@ ...config, | ||
const config = type.toConfig(); | ||
const extensions = typeExtensionsMap[type.name] ?? []; | ||
const extensions = enumExtensions.get(type.name) ?? []; | ||
return new GraphQLEnumType({ | ||
@@ -234,3 +258,3 @@ ...config, | ||
const config = type.toConfig(); | ||
const extensions = typeExtensionsMap[config.name] ?? []; | ||
const extensions = scalarExtensions.get(config.name) ?? []; | ||
let specifiedByURL = config.specifiedByURL; | ||
@@ -248,3 +272,3 @@ for (const extensionNode of extensions) { | ||
const config = type.toConfig(); | ||
const extensions = typeExtensionsMap[config.name] ?? []; | ||
const extensions = objectExtensions.get(config.name) ?? []; | ||
return new GraphQLObjectType({ | ||
@@ -265,3 +289,3 @@ ...config, | ||
const config = type.toConfig(); | ||
const extensions = typeExtensionsMap[config.name] ?? []; | ||
const extensions = interfaceExtensions.get(config.name) ?? []; | ||
return new GraphQLInterfaceType({ | ||
@@ -282,3 +306,3 @@ ...config, | ||
const config = type.toConfig(); | ||
const extensions = typeExtensionsMap[config.name] ?? []; | ||
const extensions = unionExtensions.get(config.name) ?? []; | ||
return new GraphQLUnionType({ | ||
@@ -447,5 +471,5 @@ ...config, | ||
const name = astNode.name.value; | ||
const extensionASTNodes = typeExtensionsMap[name] ?? []; | ||
switch (astNode.kind) { | ||
case Kind.OBJECT_TYPE_DEFINITION: { | ||
const extensionASTNodes = objectExtensions.get(name) ?? []; | ||
const allNodes = [astNode, ...extensionASTNodes]; | ||
@@ -462,2 +486,3 @@ return new GraphQLObjectType({ | ||
case Kind.INTERFACE_TYPE_DEFINITION: { | ||
const extensionASTNodes = interfaceExtensions.get(name) ?? []; | ||
const allNodes = [astNode, ...extensionASTNodes]; | ||
@@ -474,2 +499,3 @@ return new GraphQLInterfaceType({ | ||
case Kind.ENUM_TYPE_DEFINITION: { | ||
const extensionASTNodes = enumExtensions.get(name) ?? []; | ||
const allNodes = [astNode, ...extensionASTNodes]; | ||
@@ -485,2 +511,3 @@ return new GraphQLEnumType({ | ||
case Kind.UNION_TYPE_DEFINITION: { | ||
const extensionASTNodes = unionExtensions.get(name) ?? []; | ||
const allNodes = [astNode, ...extensionASTNodes]; | ||
@@ -496,2 +523,3 @@ return new GraphQLUnionType({ | ||
case Kind.SCALAR_TYPE_DEFINITION: { | ||
const extensionASTNodes = scalarExtensions.get(name) ?? []; | ||
return new GraphQLScalarType({ | ||
@@ -506,2 +534,3 @@ name, | ||
case Kind.INPUT_OBJECT_TYPE_DEFINITION: { | ||
const extensionASTNodes = inputObjectExtensions.get(name) ?? []; | ||
const allNodes = [astNode, ...extensionASTNodes]; | ||
@@ -508,0 +537,0 @@ return new GraphQLInputObjectType({ |
@@ -5,2 +5,4 @@ export { validate } from './validate'; | ||
export { specifiedRules } from './specifiedRules'; | ||
export { DeferStreamDirectiveLabelRule } from './rules/DeferStreamDirectiveLabelRule'; | ||
export { DeferStreamDirectiveOnRootFieldRule } from './rules/DeferStreamDirectiveOnRootFieldRule'; | ||
export { ExecutableDefinitionsRule } from './rules/ExecutableDefinitionsRule'; | ||
@@ -23,2 +25,3 @@ export { FieldsOnCorrectTypeRule } from './rules/FieldsOnCorrectTypeRule'; | ||
export { SingleFieldSubscriptionsRule } from './rules/SingleFieldSubscriptionsRule'; | ||
export { StreamDirectiveOnListFieldRule } from './rules/StreamDirectiveOnListFieldRule'; | ||
export { UniqueArgumentNamesRule } from './rules/UniqueArgumentNamesRule'; | ||
@@ -25,0 +28,0 @@ export { UniqueDirectivesPerLocationRule } from './rules/UniqueDirectivesPerLocationRule'; |
@@ -5,2 +5,6 @@ export { validate } from './validate.js'; | ||
export { specifiedRules } from './specifiedRules.js'; | ||
// Spec Section: "Defer And Stream Directive Labels Are Unique" | ||
export { DeferStreamDirectiveLabelRule } from './rules/DeferStreamDirectiveLabelRule.js'; | ||
// Spec Section: "Defer And Stream Directives Are Used On Valid Root Field" | ||
export { DeferStreamDirectiveOnRootFieldRule } from './rules/DeferStreamDirectiveOnRootFieldRule.js'; | ||
// Spec Section: "Executable Definitions" | ||
@@ -40,2 +44,4 @@ export { ExecutableDefinitionsRule } from './rules/ExecutableDefinitionsRule.js'; | ||
export { SingleFieldSubscriptionsRule } from './rules/SingleFieldSubscriptionsRule.js'; | ||
// Spec Section: "Stream Directives Are Used On List Fields" | ||
export { StreamDirectiveOnListFieldRule } from './rules/StreamDirectiveOnListFieldRule.js'; | ||
// Spec Section: "Argument Uniqueness" | ||
@@ -42,0 +48,0 @@ export { UniqueArgumentNamesRule } from './rules/UniqueArgumentNamesRule.js'; |
@@ -20,21 +20,15 @@ import { didYouMean } from '../../jsutils/didYouMean.js'; | ||
export function KnownTypeNamesRule(context) { | ||
const schema = context.getSchema(); | ||
const existingTypesMap = schema ? schema.getTypeMap() : Object.create(null); | ||
const definedTypes = Object.create(null); | ||
for (const def of context.getDocument().definitions) { | ||
if (isTypeDefinitionNode(def)) { | ||
definedTypes[def.name.value] = true; | ||
} | ||
} | ||
const typeNames = [ | ||
const { definitions } = context.getDocument(); | ||
const existingTypesMap = context.getSchema()?.getTypeMap() ?? {}; | ||
const typeNames = new Set([ | ||
...Object.keys(existingTypesMap), | ||
...Object.keys(definedTypes), | ||
]; | ||
...definitions.filter(isTypeDefinitionNode).map((def) => def.name.value), | ||
]); | ||
return { | ||
NamedType(node, _1, parent, _2, ancestors) { | ||
const typeName = node.name.value; | ||
if (!existingTypesMap[typeName] && !definedTypes[typeName]) { | ||
if (!typeNames.has(typeName)) { | ||
const definitionNode = ancestors[2] ?? parent; | ||
const isSDL = definitionNode != null && isSDLNode(definitionNode); | ||
if (isSDL && standardTypeNames.includes(typeName)) { | ||
if (isSDL && standardTypeNames.has(typeName)) { | ||
return; | ||
@@ -44,3 +38,3 @@ } | ||
typeName, | ||
isSDL ? standardTypeNames.concat(typeNames) : typeNames, | ||
isSDL ? [...standardTypeNames, ...typeNames] : [...typeNames], | ||
); | ||
@@ -57,4 +51,4 @@ context.reportError( | ||
} | ||
const standardTypeNames = [...specifiedScalarTypes, ...introspectionTypes].map( | ||
(type) => type.name, | ||
const standardTypeNames = new Set( | ||
[...specifiedScalarTypes, ...introspectionTypes].map((type) => type.name), | ||
); | ||
@@ -61,0 +55,0 @@ function isSDLNode(value) { |
@@ -13,3 +13,3 @@ import { GraphQLError } from '../../error/GraphQLError.js'; | ||
// are not redundantly reported. | ||
const visitedFrags = Object.create(null); | ||
const visitedFrags = new Set(); | ||
// Array of AST nodes used to produce meaningful errors | ||
@@ -30,7 +30,7 @@ const spreadPath = []; | ||
function detectCycleRecursive(fragment) { | ||
if (visitedFrags[fragment.name.value]) { | ||
if (visitedFrags.has(fragment.name.value)) { | ||
return; | ||
} | ||
const fragmentName = fragment.name.value; | ||
visitedFrags[fragmentName] = true; | ||
visitedFrags.add(fragmentName); | ||
const spreadNodes = context.getFragmentSpreads(fragment.selectionSet); | ||
@@ -37,0 +37,0 @@ if (spreadNodes.length === 0) { |
@@ -11,29 +11,23 @@ import { GraphQLError } from '../../error/GraphQLError.js'; | ||
export function NoUndefinedVariablesRule(context) { | ||
let variableNameDefined = Object.create(null); | ||
return { | ||
OperationDefinition: { | ||
enter() { | ||
variableNameDefined = Object.create(null); | ||
}, | ||
leave(operation) { | ||
const usages = context.getRecursiveVariableUsages(operation); | ||
for (const { node } of usages) { | ||
const varName = node.name.value; | ||
if (variableNameDefined[varName] !== true) { | ||
context.reportError( | ||
new GraphQLError( | ||
operation.name | ||
? `Variable "$${varName}" is not defined by operation "${operation.name.value}".` | ||
: `Variable "$${varName}" is not defined.`, | ||
{ nodes: [node, operation] }, | ||
), | ||
); | ||
} | ||
OperationDefinition(operation) { | ||
const variableNameDefined = new Set( | ||
operation.variableDefinitions?.map((node) => node.variable.name.value), | ||
); | ||
const usages = context.getRecursiveVariableUsages(operation); | ||
for (const { node } of usages) { | ||
const varName = node.name.value; | ||
if (!variableNameDefined.has(varName)) { | ||
context.reportError( | ||
new GraphQLError( | ||
operation.name | ||
? `Variable "$${varName}" is not defined by operation "${operation.name.value}".` | ||
: `Variable "$${varName}" is not defined.`, | ||
{ nodes: [node, operation] }, | ||
), | ||
); | ||
} | ||
}, | ||
} | ||
}, | ||
VariableDefinition(node) { | ||
variableNameDefined[node.variable.name.value] = true; | ||
}, | ||
}; | ||
} |
@@ -11,7 +11,11 @@ import { GraphQLError } from '../../error/GraphQLError.js'; | ||
export function NoUnusedFragmentsRule(context) { | ||
const operationDefs = []; | ||
const fragmentNameUsed = new Set(); | ||
const fragmentDefs = []; | ||
return { | ||
OperationDefinition(node) { | ||
operationDefs.push(node); | ||
OperationDefinition(operation) { | ||
for (const fragment of context.getRecursivelyReferencedFragments( | ||
operation, | ||
)) { | ||
fragmentNameUsed.add(fragment.name.value); | ||
} | ||
return false; | ||
@@ -25,13 +29,5 @@ }, | ||
leave() { | ||
const fragmentNameUsed = Object.create(null); | ||
for (const operation of operationDefs) { | ||
for (const fragment of context.getRecursivelyReferencedFragments( | ||
operation, | ||
)) { | ||
fragmentNameUsed[fragment.name.value] = true; | ||
} | ||
} | ||
for (const fragmentDef of fragmentDefs) { | ||
const fragName = fragmentDef.name.value; | ||
if (fragmentNameUsed[fragName] !== true) { | ||
if (!fragmentNameUsed.has(fragName)) { | ||
context.reportError( | ||
@@ -38,0 +34,0 @@ new GraphQLError(`Fragment "${fragName}" is never used.`, { |
@@ -11,33 +11,26 @@ import { GraphQLError } from '../../error/GraphQLError.js'; | ||
export function NoUnusedVariablesRule(context) { | ||
let variableDefs = []; | ||
return { | ||
OperationDefinition: { | ||
enter() { | ||
variableDefs = []; | ||
}, | ||
leave(operation) { | ||
const variableNameUsed = Object.create(null); | ||
const usages = context.getRecursiveVariableUsages(operation); | ||
for (const { node } of usages) { | ||
variableNameUsed[node.name.value] = true; | ||
OperationDefinition(operation) { | ||
const usages = context.getRecursiveVariableUsages(operation); | ||
const variableNameUsed = new Set( | ||
usages.map(({ node }) => node.name.value), | ||
); | ||
// FIXME: https://github.com/graphql/graphql-js/issues/2203 | ||
/* c8 ignore next */ | ||
const variableDefinitions = operation.variableDefinitions ?? []; | ||
for (const variableDef of variableDefinitions) { | ||
const variableName = variableDef.variable.name.value; | ||
if (!variableNameUsed.has(variableName)) { | ||
context.reportError( | ||
new GraphQLError( | ||
operation.name | ||
? `Variable "$${variableName}" is never used in operation "${operation.name.value}".` | ||
: `Variable "$${variableName}" is never used.`, | ||
{ nodes: variableDef }, | ||
), | ||
); | ||
} | ||
for (const variableDef of variableDefs) { | ||
const variableName = variableDef.variable.name.value; | ||
if (variableNameUsed[variableName] !== true) { | ||
context.reportError( | ||
new GraphQLError( | ||
operation.name | ||
? `Variable "$${variableName}" is never used in operation "${operation.name.value}".` | ||
: `Variable "$${variableName}" is never used.`, | ||
{ nodes: variableDef }, | ||
), | ||
); | ||
} | ||
} | ||
}, | ||
} | ||
}, | ||
VariableDefinition(def) { | ||
variableDefs.push(def); | ||
}, | ||
}; | ||
} |
@@ -530,2 +530,12 @@ import { inspect } from '../../jsutils/inspect.js'; | ||
} | ||
// FIXME https://github.com/graphql/graphql-js/issues/2203 | ||
const directives1 = /* c8 ignore next */ node1.directives ?? []; | ||
const directives2 = /* c8 ignore next */ node2.directives ?? []; | ||
if (!sameStreams(directives1, directives2)) { | ||
return [ | ||
[responseName, 'they have differing stream directives'], | ||
[node1], | ||
[node2], | ||
]; | ||
} | ||
// The return type for each field. | ||
@@ -578,2 +588,18 @@ const type1 = def1?.type; | ||
} | ||
function getStreamDirective(directives) { | ||
return directives.find((directive) => directive.name.value === 'stream'); | ||
} | ||
function sameStreams(directives1, directives2) { | ||
const stream1 = getStreamDirective(directives1); | ||
const stream2 = getStreamDirective(directives2); | ||
if (!stream1 && !stream2) { | ||
// both fields do not have streams | ||
return true; | ||
} else if (stream1 && stream2) { | ||
// check if both fields have equivalent streams | ||
return stringifyArguments(stream1) === stringifyArguments(stream2); | ||
} | ||
// fields have a mix of stream and no stream | ||
return false; | ||
} | ||
// Two types conflict if both types could not apply to a value simultaneously. | ||
@@ -618,3 +644,3 @@ // Composite types are ignored as their individual field types will be compared | ||
const nodeAndDefs = Object.create(null); | ||
const fragmentNames = Object.create(null); | ||
const fragmentNames = new Set(); | ||
_collectFieldsAndFragmentNames( | ||
@@ -627,3 +653,3 @@ context, | ||
); | ||
const result = [nodeAndDefs, Object.keys(fragmentNames)]; | ||
const result = [nodeAndDefs, [...fragmentNames]]; | ||
cachedFieldsAndFragmentNames.set(selectionSet, result); | ||
@@ -677,3 +703,3 @@ return result; | ||
case Kind.FRAGMENT_SPREAD: | ||
fragmentNames[selection.name.value] = true; | ||
fragmentNames.add(selection.name.value); | ||
break; | ||
@@ -680,0 +706,0 @@ case Kind.INLINE_FRAGMENT: { |
@@ -28,3 +28,3 @@ import { GraphQLError } from '../../error/GraphQLError.js'; | ||
} | ||
const fields = collectFields( | ||
const { fields } = collectFields( | ||
schema, | ||
@@ -50,4 +50,3 @@ fragments, | ||
for (const fieldNodes of fields.values()) { | ||
const field = fieldNodes[0]; | ||
const fieldName = field.name.value; | ||
const fieldName = fieldNodes[0].name.value; | ||
if (fieldName.startsWith('__')) { | ||
@@ -54,0 +53,0 @@ context.reportError( |
@@ -0,1 +1,5 @@ | ||
// Spec Section: "Defer And Stream Directive Labels Are Unique" | ||
import { DeferStreamDirectiveLabelRule } from './rules/DeferStreamDirectiveLabelRule.js'; | ||
// Spec Section: "Defer And Stream Directives Are Used On Valid Root Field" | ||
import { DeferStreamDirectiveOnRootFieldRule } from './rules/DeferStreamDirectiveOnRootFieldRule.js'; | ||
// Spec Section: "Executable Definitions" | ||
@@ -44,2 +48,4 @@ import { ExecutableDefinitionsRule } from './rules/ExecutableDefinitionsRule.js'; | ||
import { SingleFieldSubscriptionsRule } from './rules/SingleFieldSubscriptionsRule.js'; | ||
// Spec Section: "Stream Directives Are Used On List Fields" | ||
import { StreamDirectiveOnListFieldRule } from './rules/StreamDirectiveOnListFieldRule.js'; | ||
import { UniqueArgumentDefinitionNamesRule } from './rules/UniqueArgumentDefinitionNamesRule.js'; | ||
@@ -95,2 +101,5 @@ // Spec Section: "Argument Uniqueness" | ||
UniqueDirectivesPerLocationRule, | ||
DeferStreamDirectiveOnRootFieldRule, | ||
DeferStreamDirectiveLabelRule, | ||
StreamDirectiveOnListFieldRule, | ||
KnownArgumentNamesRule, | ||
@@ -97,0 +106,0 @@ UniqueArgumentNamesRule, |
@@ -41,3 +41,5 @@ import { GraphQLError } from '../error/GraphQLError.js'; | ||
assertValidSchema(schema); | ||
const abortObj = Object.freeze({}); | ||
const abortError = new GraphQLError( | ||
'Too many validation errors, error limit reached. Validation aborted.', | ||
); | ||
const errors = []; | ||
@@ -50,9 +52,3 @@ const context = new ValidationContext( | ||
if (errors.length >= maxErrors) { | ||
errors.push( | ||
new GraphQLError( | ||
'Too many validation errors, error limit reached. Validation aborted.', | ||
), | ||
); | ||
// eslint-disable-next-line @typescript-eslint/no-throw-literal | ||
throw abortObj; | ||
throw abortError; | ||
} | ||
@@ -69,3 +65,5 @@ errors.push(error); | ||
} catch (e) { | ||
if (e !== abortObj) { | ||
if (e === abortError) { | ||
errors.push(abortError); | ||
} else { | ||
throw e; | ||
@@ -72,0 +70,0 @@ } |
@@ -64,3 +64,3 @@ import { Kind } from '../language/kinds.js'; | ||
fragments = []; | ||
const collectedNames = Object.create(null); | ||
const collectedNames = new Set(); | ||
const nodesToVisit = [operation.selectionSet]; | ||
@@ -71,4 +71,4 @@ let node; | ||
const fragName = spread.name.value; | ||
if (collectedNames[fragName] !== true) { | ||
collectedNames[fragName] = true; | ||
if (!collectedNames.has(fragName)) { | ||
collectedNames.add(fragName); | ||
const fragment = this.getFragment(fragName); | ||
@@ -75,0 +75,0 @@ if (fragment) { |
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
725744
266
22232