@apollo/federation-internals
Advanced tools
Comparing version 2.0.2 to 2.0.3
# CHANGELOG for `@apollo/federation-internals` | ||
## 2.0.2-alpha.2 | ||
## 2.0.3 | ||
- Fix bug removing an enum type [PR #1813](https://github.com/apollographql/federation/pull/1813) | ||
- Fix bug with type extension of empty type definition [PR #1821](https://github.com/apollographql/federation/pull/1821) | ||
## 2.0.2-alpha.1 | ||
## 2.0.2 | ||
- Fix bug removing an enum type [PR #1813](https://github.com/apollographql/federation/pull/1813) | ||
- Fix `Schema.clone` when directive application happens before definition [PR #1785](https://github.com/apollographql/federation/pull/1785) | ||
@@ -13,5 +14,2 @@ - More helpful error message for errors encountered while reading supergraphs generated pre-federation 2 [PR #1796](https://github.com/apollographql/federation/pull/1796) | ||
- Prevent non-core-feature elements from being marked @inaccessible if referenced by core feature elements [PR #1769](https://github.com/apollographql/federation/pull/1769) | ||
## v2.0.2-alpha.0 | ||
- Improve fed1 schema support during composition [PR #1735](https://github.com/apollographql/federation/pull/1735) | ||
@@ -32,10 +30,3 @@ - Honor directive imports when directive name is spec name [PR #1720](https://github.com/apollographql/federation/pull/1720) | ||
## v2.0.0-preview.13 | ||
- Released in sync with other federation packages but no changes to this package. | ||
## v2.0.0-preview.12 | ||
- Released in sync with other federation packages but no changes to this package. | ||
## v2.0.0-preview.11 | ||
@@ -46,6 +37,2 @@ | ||
## v2.0.0-preview.10 | ||
- Released in sync with other federation packages but no changes to this package. | ||
## v2.0.0-preview.9 | ||
@@ -57,14 +44,2 @@ | ||
## v2.0.0-preview.8 | ||
- Released in sync with other federation packages but no changes to this package. | ||
## v2.0.0-preview.7 | ||
- Released in sync with other federation packages but no changes to this package. | ||
## v2.0.0-preview.6 | ||
- Released in sync with other federation packages but no changes to this package. | ||
## v2.0.0-preview.5 | ||
@@ -71,0 +46,0 @@ |
@@ -18,3 +18,16 @@ "use strict"; | ||
const schema = new definitions_1.Schema(options === null || options === void 0 ? void 0 : options.blueprint); | ||
const { directiveDefinitions, schemaDefinitions, schemaExtensions } = buildNamedTypeAndDirectivesShallow(documentNode, schema); | ||
const { directiveDefinitions, typeDefinitions, typeExtensions, schemaDefinitions, schemaExtensions, } = buildNamedTypeAndDirectivesShallow(documentNode, schema, errors); | ||
for (const typeNode of typeDefinitions) { | ||
if (typeNode.kind === graphql_1.Kind.ENUM_TYPE_DEFINITION) { | ||
buildEnumTypeValuesWithoutDirectiveApplications(typeNode, schema.type(typeNode.name.value)); | ||
} | ||
} | ||
for (const typeExtensionNode of typeExtensions) { | ||
if (typeExtensionNode.kind === graphql_1.Kind.ENUM_TYPE_EXTENSION) { | ||
const toExtend = schema.type(typeExtensionNode.name.value); | ||
const extension = toExtend.newExtension(); | ||
extension.sourceAST = typeExtensionNode; | ||
buildEnumTypeValuesWithoutDirectiveApplications(typeExtensionNode, schema.type(typeExtensionNode.name.value), extension); | ||
} | ||
} | ||
for (const directiveDefinitionNode of directiveDefinitions) { | ||
@@ -33,29 +46,11 @@ buildDirectiveDefinitionInnerWithoutDirectiveApplications(directiveDefinitionNode, schema.directive(directiveDefinitionNode.name.value), errors); | ||
} | ||
for (const definitionNode of documentNode.definitions) { | ||
switch (definitionNode.kind) { | ||
case 'OperationDefinition': | ||
case 'FragmentDefinition': | ||
errors.push(new graphql_1.GraphQLError("Invalid executable definition found while building schema", definitionNode)); | ||
continue; | ||
case 'ScalarTypeDefinition': | ||
case 'ObjectTypeDefinition': | ||
case 'InterfaceTypeDefinition': | ||
case 'UnionTypeDefinition': | ||
case 'EnumTypeDefinition': | ||
case 'InputObjectTypeDefinition': | ||
buildNamedTypeInner(definitionNode, schema.type(definitionNode.name.value), schema.blueprint, errors); | ||
break; | ||
case 'ScalarTypeExtension': | ||
case 'ObjectTypeExtension': | ||
case 'InterfaceTypeExtension': | ||
case 'UnionTypeExtension': | ||
case 'EnumTypeExtension': | ||
case 'InputObjectTypeExtension': | ||
const toExtend = schema.type(definitionNode.name.value); | ||
const extension = toExtend.newExtension(); | ||
extension.sourceAST = definitionNode; | ||
buildNamedTypeInner(definitionNode, toExtend, schema.blueprint, errors, extension); | ||
break; | ||
} | ||
for (const typeNode of typeDefinitions) { | ||
buildNamedTypeInner(typeNode, schema.type(typeNode.name.value), schema.blueprint, errors); | ||
} | ||
for (const typeExtensionNode of typeExtensions) { | ||
const toExtend = schema.type(typeExtensionNode.name.value); | ||
const extension = toExtend.newExtension(); | ||
extension.sourceAST = typeExtensionNode; | ||
buildNamedTypeInner(typeExtensionNode, toExtend, schema.blueprint, errors, extension); | ||
} | ||
if (errors.length > 0) { | ||
@@ -70,4 +65,6 @@ throw (0, definitions_1.ErrGraphQLValidationFailed)(errors); | ||
exports.buildSchemaFromAST = buildSchemaFromAST; | ||
function buildNamedTypeAndDirectivesShallow(documentNode, schema) { | ||
function buildNamedTypeAndDirectivesShallow(documentNode, schema, errors) { | ||
const directiveDefinitions = []; | ||
const typeDefinitions = []; | ||
const typeExtensions = []; | ||
const schemaDefinitions = []; | ||
@@ -77,4 +74,9 @@ const schemaExtensions = []; | ||
switch (definitionNode.kind) { | ||
case 'OperationDefinition': | ||
case 'FragmentDefinition': | ||
errors.push(new graphql_1.GraphQLError("Invalid executable definition found while building schema", definitionNode)); | ||
continue; | ||
case 'SchemaDefinition': | ||
schemaDefinitions.push(definitionNode); | ||
schema.schemaDefinition.preserveEmptyDefinition = true; | ||
break; | ||
@@ -90,2 +92,12 @@ case 'SchemaExtension': | ||
case 'InputObjectTypeDefinition': | ||
typeDefinitions.push(definitionNode); | ||
let type = schema.type(definitionNode.name.value); | ||
if (!type || type.isBuiltIn) { | ||
type = schema.addType((0, definitions_1.newNamedType)(withoutTrailingDefinition(definitionNode.kind), definitionNode.name.value)); | ||
} | ||
else if (type.preserveEmptyDefinition) { | ||
throw new graphql_1.GraphQLError(`There can be only one type named "${definitionNode.name.value}"`); | ||
} | ||
type.preserveEmptyDefinition = true; | ||
break; | ||
case 'ScalarTypeExtension': | ||
@@ -97,6 +109,10 @@ case 'ObjectTypeExtension': | ||
case 'InputObjectTypeExtension': | ||
typeExtensions.push(definitionNode); | ||
const existing = schema.type(definitionNode.name.value); | ||
if (!existing || existing.isBuiltIn) { | ||
if (!existing) { | ||
schema.addType((0, definitions_1.newNamedType)(withoutTrailingDefinition(definitionNode.kind), definitionNode.name.value)); | ||
} | ||
else if (existing.isBuiltIn) { | ||
throw new graphql_1.GraphQLError(`Cannot extend built-in type "${definitionNode.name.value}"`); | ||
} | ||
break; | ||
@@ -111,2 +127,4 @@ case 'DirectiveDefinition': | ||
directiveDefinitions, | ||
typeDefinitions, | ||
typeExtensions, | ||
schemaDefinitions, | ||
@@ -176,2 +194,9 @@ schemaExtensions, | ||
switch (definitionNode.kind) { | ||
case 'EnumTypeDefinition': | ||
case 'EnumTypeExtension': | ||
const enumType = type; | ||
for (const enumVal of (_a = definitionNode.values) !== null && _a !== void 0 ? _a : []) { | ||
buildAppliedDirectives(enumVal, enumType.value(enumVal.name.value), errors); | ||
} | ||
break; | ||
case 'ObjectTypeDefinition': | ||
@@ -182,3 +207,3 @@ case 'ObjectTypeExtension': | ||
const fieldBasedType = type; | ||
for (const fieldNode of (_a = definitionNode.fields) !== null && _a !== void 0 ? _a : []) { | ||
for (const fieldNode of (_b = definitionNode.fields) !== null && _b !== void 0 ? _b : []) { | ||
if (blueprint.ignoreParsedField(type, fieldNode.name.value)) { | ||
@@ -191,3 +216,3 @@ continue; | ||
} | ||
for (const itfNode of (_b = definitionNode.interfaces) !== null && _b !== void 0 ? _b : []) { | ||
for (const itfNode of (_c = definitionNode.interfaces) !== null && _c !== void 0 ? _c : []) { | ||
withNodeAttachedToError(() => { | ||
@@ -205,3 +230,3 @@ const itfName = itfNode.name.value; | ||
const unionType = type; | ||
for (const namedType of (_c = definitionNode.types) !== null && _c !== void 0 ? _c : []) { | ||
for (const namedType of (_d = definitionNode.types) !== null && _d !== void 0 ? _d : []) { | ||
withNodeAttachedToError(() => { | ||
@@ -216,14 +241,2 @@ const name = namedType.name.value; | ||
break; | ||
case 'EnumTypeDefinition': | ||
case 'EnumTypeExtension': | ||
const enumType = type; | ||
for (const enumVal of (_d = definitionNode.values) !== null && _d !== void 0 ? _d : []) { | ||
const v = enumType.addValue(enumVal.name.value); | ||
if (enumVal.description) { | ||
v.description = enumVal.description.value; | ||
} | ||
v.setOfExtension(extension); | ||
buildAppliedDirectives(enumVal, v, errors); | ||
} | ||
break; | ||
case 'InputObjectTypeDefinition': | ||
@@ -240,6 +253,21 @@ case 'InputObjectTypeExtension': | ||
buildAppliedDirectives(definitionNode, type, errors, extension); | ||
buildDescriptionAndSourceAST(definitionNode, type); | ||
} | ||
function buildEnumTypeValuesWithoutDirectiveApplications(definitionNode, type, extension) { | ||
var _a; | ||
const enumType = type; | ||
for (const enumVal of (_a = definitionNode.values) !== null && _a !== void 0 ? _a : []) { | ||
const v = enumType.addValue(enumVal.name.value); | ||
if (enumVal.description) { | ||
v.description = enumVal.description.value; | ||
} | ||
v.setOfExtension(extension); | ||
} | ||
buildDescriptionAndSourceAST(definitionNode, type); | ||
} | ||
function buildDescriptionAndSourceAST(definitionNode, dest) { | ||
if (definitionNode.description) { | ||
type.description = definitionNode.description.value; | ||
dest.description = definitionNode.description.value; | ||
} | ||
type.sourceAST = definitionNode; | ||
dest.sourceAST = definitionNode; | ||
} | ||
@@ -314,3 +342,3 @@ function buildFieldDefinitionInner(fieldNode, field, errors) { | ||
function buildDirectiveDefinitionInnerWithoutDirectiveApplications(directiveNode, directive, errors) { | ||
var _a, _b; | ||
var _a; | ||
for (const inputValueDef of (_a = directiveNode.arguments) !== null && _a !== void 0 ? _a : []) { | ||
@@ -322,4 +350,3 @@ buildArgumentDefinitionInner(inputValueDef, directive.addArgument(inputValueDef.name.value), errors, false); | ||
directive.addLocations(...locations); | ||
directive.description = (_b = directiveNode.description) === null || _b === void 0 ? void 0 : _b.value; | ||
directive.sourceAST = directiveNode; | ||
buildDescriptionAndSourceAST(directiveNode, directive); | ||
} | ||
@@ -326,0 +353,0 @@ function buildDirectiveApplicationsInDirectiveDefinition(directiveNode, directive, errors) { |
@@ -1,2 +0,2 @@ | ||
import { ConstArgumentNode, ASTNode, DirectiveLocation, ConstDirectiveNode, DocumentNode, GraphQLError, GraphQLSchema, TypeNode, VariableDefinitionNode, VariableNode } from "graphql"; | ||
import { ConstArgumentNode, ASTNode, DirectiveLocation, ConstDirectiveNode, DocumentNode, GraphQLError, GraphQLSchema, Kind, TypeNode, VariableDefinitionNode, VariableNode } from "graphql"; | ||
import { CoreImport, CoreSpecDefinition, FeatureUrl } from "./coreSpec"; | ||
@@ -64,2 +64,3 @@ import { MapWithCachedArrays } from "./utils"; | ||
export declare function runtimeTypesIntersects(t1: CompositeType, t2: CompositeType): boolean; | ||
export declare function isConditionalDirective(directive: Directive<any, any> | DirectiveDefinition<any>): boolean; | ||
export declare const executableDirectiveLocations: DirectiveLocation[]; | ||
@@ -152,2 +153,3 @@ export declare function typeToAST(type: Type): TypeNode; | ||
protected readonly _extensions: Set<Extension<TOwnType>>; | ||
preserveEmptyDefinition: boolean; | ||
constructor(name: string, isBuiltIn?: boolean); | ||
@@ -228,3 +230,3 @@ private addReferencer; | ||
private add; | ||
sourceFeature(element: DirectiveDefinition | NamedType): CoreFeature | undefined; | ||
sourceFeature(element: DirectiveDefinition | Directive | NamedType): CoreFeature | undefined; | ||
} | ||
@@ -257,3 +259,4 @@ export declare class Schema { | ||
toAPISchema(): Schema; | ||
toGraphQLJSSchema(isSubgraph?: boolean): GraphQLSchema; | ||
private emptyASTDefinitionsForExtensionsWithoutDefinition; | ||
toGraphQLJSSchema(): GraphQLSchema; | ||
get schemaDefinition(): SchemaDefinition; | ||
@@ -317,2 +320,3 @@ types(): readonly NamedType[]; | ||
protected readonly _extensions: Set<Extension<SchemaDefinition>>; | ||
preserveEmptyDefinition: boolean; | ||
roots(): readonly RootType[]; | ||
@@ -330,2 +334,4 @@ applyDirective<TApplicationArgs extends { | ||
addExtension(extension: Extension<SchemaDefinition>): Extension<SchemaDefinition>; | ||
hasExtensionElements(): boolean; | ||
hasNonExtensionElements(): boolean; | ||
private removeRootType; | ||
@@ -337,2 +343,3 @@ protected removeTypeReference(toRemove: NamedType): void; | ||
readonly kind: "ScalarType"; | ||
readonly astDefinitionKind = Kind.SCALAR_TYPE_DEFINITION; | ||
protected removeTypeReference(type: NamedType): void; | ||
@@ -377,2 +384,3 @@ protected hasNonExtensionInnerElements(): boolean; | ||
readonly kind: "ObjectType"; | ||
readonly astDefinitionKind = Kind.OBJECT_TYPE_DEFINITION; | ||
isRootType(): boolean; | ||
@@ -384,2 +392,3 @@ isQueryRootType(): boolean; | ||
readonly kind: "InterfaceType"; | ||
readonly astDefinitionKind = Kind.INTERFACE_TYPE_DEFINITION; | ||
allImplementations(): (ObjectType | InterfaceType)[]; | ||
@@ -397,2 +406,3 @@ possibleRuntimeTypes(): readonly ObjectType[]; | ||
readonly kind: "UnionType"; | ||
readonly astDefinitionKind = Kind.UNION_TYPE_DEFINITION; | ||
protected readonly _members: MapWithCachedArrays<string, UnionMember>; | ||
@@ -418,2 +428,3 @@ private _typenameField?; | ||
readonly kind: "EnumType"; | ||
readonly astDefinitionKind = Kind.ENUM_TYPE_DEFINITION; | ||
protected readonly _values: EnumValue[]; | ||
@@ -433,2 +444,3 @@ get values(): readonly EnumValue[]; | ||
readonly kind: "InputObjectType"; | ||
readonly astDefinitionKind = Kind.INPUT_OBJECT_TYPE_DEFINITION; | ||
private readonly _fields; | ||
@@ -584,2 +596,6 @@ private _cachedFieldsArray?; | ||
} | ||
export declare function sameDirectiveApplication(application1: Directive<any, any>, application2: Directive<any, any>): boolean; | ||
export declare function sameDirectiveApplications(applications1: Directive<any, any>[], applications2: Directive<any, any>[]): boolean; | ||
export declare function isDirectiveApplicationsSubset(applications: Directive<any, any>[], maybeSubset: Directive<any, any>[]): boolean; | ||
export declare function directiveApplicationsSubstraction(baseApplications: Directive<any, any>[], toRemove: Directive<any, any>[]): Directive<any, any>[]; | ||
export declare class Variable { | ||
@@ -586,0 +602,0 @@ readonly name: string; |
@@ -160,3 +160,3 @@ "use strict"; | ||
errors = errors.concat(error_1.ERRORS.TYPE_DEFINITION_INVALID.err({ | ||
message: `Invalid definition of type ${name}: expected values [${expectedValueNames}] but found [${actualValueNames}].`, | ||
message: `Invalid definition for type "${name}": expected values [${expectedValueNames.join(', ')}] but found [${actualValueNames.join(', ')}].`, | ||
nodes: existing.sourceAST | ||
@@ -163,0 +163,0 @@ })); |
@@ -138,2 +138,3 @@ import { CompositeType, CoreFeature, Directive, DirectiveDefinition, FieldDefinition, InputFieldDefinition, InterfaceType, NamedType, ObjectType, ScalarType, Schema, SchemaBlueprint, UnionType } from "./definitions"; | ||
private isPrintedType; | ||
private isPrintedDirectiveApplication; | ||
toString(basePrintOptions?: PrintOptions): string; | ||
@@ -140,0 +141,0 @@ } |
@@ -1005,5 +1005,24 @@ "use strict"; | ||
} | ||
if ((0, definitions_1.isObjectType)(t) && t.isQueryRootType() && t.fields().filter((f) => !isFederationField(f)).length === 0) { | ||
return false; | ||
} | ||
const core = this.schema.coreFeatures; | ||
return !core || ((_a = core.sourceFeature(t)) === null || _a === void 0 ? void 0 : _a.url.identity) !== coreSpec_1.linkIdentity; | ||
} | ||
isPrintedDirectiveApplication(d) { | ||
if (!this.schema.coreFeatures || d.name !== linkSpec.url.name) { | ||
return true; | ||
} | ||
const args = d.arguments(); | ||
let urlArg = undefined; | ||
if ('url' in args) { | ||
try { | ||
urlArg = coreSpec_1.FeatureUrl.parse(args['url']); | ||
} | ||
catch (e) { | ||
} | ||
} | ||
const isDefaultLinkToLink = (urlArg === null || urlArg === void 0 ? void 0 : urlArg.identity) === coreSpec_1.linkIdentity && Object.keys(args).length === 1; | ||
return !isDefaultLinkToLink; | ||
} | ||
toString(basePrintOptions = print_1.defaultPrintOptions) { | ||
@@ -1015,2 +1034,3 @@ return (0, print_1.printSchema)(this.schema, { | ||
fieldFilter: (f) => !isFederationField(f), | ||
directiveApplicationFilter: (d) => this.isPrintedDirectiveApplication(d), | ||
}); | ||
@@ -1017,0 +1037,0 @@ } |
@@ -20,2 +20,3 @@ export * from './definitions'; | ||
export * from './suggestions'; | ||
export * from './graphQLJSSchemaToAST'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -36,2 +36,3 @@ "use strict"; | ||
__exportStar(require("./suggestions"), exports); | ||
__exportStar(require("./graphQLJSSchemaToAST"), exports); | ||
//# sourceMappingURL=index.js.map |
import { DocumentNode, FieldNode, FragmentDefinitionNode, InlineFragmentNode, SelectionNode, SelectionSetNode } from "graphql"; | ||
import { DirectiveTargetElement, FieldDefinition, InterfaceType, ObjectType, Schema, SchemaRootKind, Variables, VariableDefinitions, CompositeType, NamedType } from "./definitions"; | ||
import { Directive, DirectiveTargetElement, FieldDefinition, InterfaceType, ObjectType, Schema, SchemaRootKind, Variables, VariableDefinitions, CompositeType, NamedType } from "./definitions"; | ||
declare abstract class AbstractOperationElement<T extends AbstractOperationElement<T>> extends DirectiveTargetElement<T> { | ||
@@ -45,2 +45,3 @@ private readonly variablesInElement; | ||
export declare function sameOperationPaths(p1: OperationPath, p2: OperationPath): boolean; | ||
export declare function conditionalDirectivesInOperationPath(path: OperationPath): Directive<any, any>[]; | ||
export declare function concatOperationPaths(head: OperationPath, tail: OperationPath): OperationPath; | ||
@@ -47,0 +48,0 @@ export declare type RootOperationPath = { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.operationToDocument = exports.parseSelectionSet = exports.parseOperation = exports.operationFromDocument = exports.FragmentSelection = exports.FieldSelection = exports.selectionSetOfPath = exports.selectionOfElement = exports.selectionSetOfElement = exports.allFieldDefinitionsInSelectionSet = exports.SelectionSet = exports.NamedFragments = exports.NamedFragmentDefinition = exports.selectionSetOf = exports.Operation = exports.concatOperationPaths = exports.sameOperationPaths = exports.FragmentElement = exports.Field = void 0; | ||
exports.operationToDocument = exports.parseSelectionSet = exports.parseOperation = exports.operationFromDocument = exports.FragmentSelection = exports.FieldSelection = exports.selectionSetOfPath = exports.selectionOfElement = exports.selectionSetOfElement = exports.allFieldDefinitionsInSelectionSet = exports.SelectionSet = exports.NamedFragments = exports.NamedFragmentDefinition = exports.selectionSetOf = exports.Operation = exports.concatOperationPaths = exports.conditionalDirectivesInOperationPath = exports.sameOperationPaths = exports.FragmentElement = exports.Field = void 0; | ||
const graphql_1 = require("graphql"); | ||
@@ -15,11 +15,3 @@ const definitions_1 = require("./definitions"); | ||
function haveSameDirectives(op1, op2) { | ||
if (op1.appliedDirectives.length != op2.appliedDirectives.length) { | ||
return false; | ||
} | ||
for (const thisDirective of op1.appliedDirectives) { | ||
if (!op2.appliedDirectives.some(thatDirective => thisDirective.name === thatDirective.name && (0, values_1.argumentsEquals)(thisDirective.arguments(), thatDirective.arguments()))) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
return (0, definitions_1.sameDirectiveApplications)(op1.appliedDirectives, op2.appliedDirectives); | ||
} | ||
@@ -203,2 +195,6 @@ class AbstractOperationElement extends definitions_1.DirectiveTargetElement { | ||
exports.sameOperationPaths = sameOperationPaths; | ||
function conditionalDirectivesInOperationPath(path) { | ||
return path.map((e) => e.appliedDirectives).flat().filter((d) => (0, definitions_1.isConditionalDirective)(d)); | ||
} | ||
exports.conditionalDirectivesInOperationPath = conditionalDirectivesInOperationPath; | ||
function concatOperationPaths(head, tail) { | ||
@@ -212,5 +208,7 @@ if (head.length === 0) { | ||
const lastOfHead = head[head.length - 1]; | ||
const firstOfTail = tail[0]; | ||
if (isUselessFollowupElement(lastOfHead, firstOfTail)) { | ||
const conditionals = conditionalDirectivesInOperationPath(head); | ||
let firstOfTail = tail[0]; | ||
while (firstOfTail && isUselessFollowupElement(lastOfHead, firstOfTail, conditionals)) { | ||
tail = tail.slice(1); | ||
firstOfTail = tail[0]; | ||
} | ||
@@ -220,3 +218,3 @@ return head.concat(tail); | ||
exports.concatOperationPaths = concatOperationPaths; | ||
function isUselessFollowupElement(first, followup) { | ||
function isUselessFollowupElement(first, followup, conditionals) { | ||
const typeOfFirst = first.kind === 'Field' | ||
@@ -228,3 +226,3 @@ ? (0, definitions_1.baseType)(first.definition.type) | ||
&& !!followup.typeCondition | ||
&& followup.appliedDirectives.length === 0 | ||
&& (followup.appliedDirectives.length === 0 || (0, definitions_1.isDirectiveApplicationsSubset)(conditionals, followup.appliedDirectives)) | ||
&& (0, types_1.sameType)(typeOfFirst, followup.typeCondition); | ||
@@ -535,3 +533,3 @@ } | ||
this._cachedSelections = undefined; | ||
return selection; | ||
return toAdd; | ||
} | ||
@@ -812,5 +810,18 @@ addPath(path) { | ||
const updatedField = this.field.updateForAddingTo(selectionSet); | ||
return this.field === updatedField | ||
? this.cloneIfFrozen() | ||
: new FieldSelection(updatedField, (_a = this.selectionSet) === null || _a === void 0 ? void 0 : _a.cloneIfFrozen()); | ||
if (this.field === updatedField) { | ||
return this.cloneIfFrozen(); | ||
} | ||
const updatedBaseType = (0, definitions_1.baseType)(updatedField.definition.type); | ||
let updatedSelectionSet; | ||
if (this.selectionSet && this.selectionSet.parentType !== updatedBaseType) { | ||
(0, utils_1.assert)((0, definitions_1.isCompositeType)(updatedBaseType), `Expected ${updatedBaseType.coordinate} to be composite but ${updatedBaseType.kind}`); | ||
updatedSelectionSet = new SelectionSet(updatedBaseType); | ||
for (const selection of this.selectionSet.selections()) { | ||
updatedSelectionSet.add(selection); | ||
} | ||
} | ||
else { | ||
updatedSelectionSet = (_a = this.selectionSet) === null || _a === void 0 ? void 0 : _a.cloneIfFrozen(); | ||
} | ||
return new FieldSelection(updatedField, updatedSelectionSet); | ||
} | ||
@@ -817,0 +828,0 @@ toSelectionNode() { |
@@ -1,2 +0,2 @@ | ||
import { DirectiveDefinition, FieldDefinition, NamedType, Schema, SchemaRootKind } from "./definitions"; | ||
import { Directive, DirectiveDefinition, FieldDefinition, NamedType, Schema, SchemaRootKind } from "./definitions"; | ||
export declare type PrintOptions = { | ||
@@ -14,2 +14,3 @@ indentString: string; | ||
fieldFilter: (f: FieldDefinition<any>) => boolean; | ||
directiveApplicationFilter: (d: Directive) => boolean; | ||
}; | ||
@@ -16,0 +17,0 @@ export declare const defaultPrintOptions: PrintOptions; |
@@ -15,2 +15,3 @@ "use strict"; | ||
fieldFilter: () => true, | ||
directiveApplicationFilter: () => true, | ||
}; | ||
@@ -82,5 +83,12 @@ function orderPrintedDefinitions(options) { | ||
} | ||
function appliedDirectives(element, options, extension) { | ||
let directives = forExtension(element.appliedDirectives, extension); | ||
if (options.directiveApplicationFilter) { | ||
directives = directives.filter(options.directiveApplicationFilter); | ||
} | ||
return directives; | ||
} | ||
function printSchemaDefinitionOrExtension(schemaDefinition, options, extension) { | ||
const roots = forExtension(schemaDefinition.roots(), extension); | ||
const directives = forExtension(schemaDefinition.appliedDirectives, extension); | ||
const directives = appliedDirectives(schemaDefinition, options, extension); | ||
if (!roots.length && !directives.length) { | ||
@@ -143,3 +151,3 @@ return undefined; | ||
function printScalarDefinitionOrExtension(type, options, extension) { | ||
const directives = forExtension(type.appliedDirectives, extension); | ||
const directives = appliedDirectives(type, options, extension); | ||
if (extension && !directives.length) { | ||
@@ -156,3 +164,3 @@ return undefined; | ||
function printFieldBasedTypeDefinitionOrExtension(kind, type, options, extension) { | ||
const directives = forExtension(type.appliedDirectives, extension); | ||
const directives = appliedDirectives(type, options, extension); | ||
const interfaces = forExtension(type.interfaceImplementations(), extension); | ||
@@ -163,3 +171,3 @@ let fields = forExtension(type.fields(), extension); | ||
} | ||
if (!directives.length && !interfaces.length && !fields.length) { | ||
if (!directives.length && !interfaces.length && !fields.length && (extension || !type.preserveEmptyDefinition)) { | ||
return undefined; | ||
@@ -172,9 +180,9 @@ } | ||
+ printAppliedDirectives(directives, options, true, fields.length > 0) | ||
+ (directives.length === 0 ? ' ' : '') | ||
+ (directives.length === 0 && fields.length > 0 ? ' ' : '') | ||
+ printFields(fields, options); | ||
} | ||
function printUnionDefinitionOrExtension(type, options, extension) { | ||
const directives = forExtension(type.appliedDirectives, extension); | ||
const directives = appliedDirectives(type, options, extension); | ||
const members = forExtension(type.members(), extension); | ||
if (!directives.length && !members.length) { | ||
if (!directives.length && !members.length && (extension || !type.preserveEmptyDefinition)) { | ||
return undefined; | ||
@@ -190,5 +198,5 @@ } | ||
function printEnumDefinitionOrExtension(type, options, extension) { | ||
const directives = forExtension(type.appliedDirectives, extension); | ||
const directives = appliedDirectives(type, options, extension); | ||
const values = forExtension(type.values, extension); | ||
if (!directives.length && !values.length) { | ||
if (!directives.length && !values.length && (extension || !type.preserveEmptyDefinition)) { | ||
return undefined; | ||
@@ -204,9 +212,9 @@ } | ||
+ printAppliedDirectives(directives, options, true, vals.length > 0) | ||
+ (directives.length === 0 ? ' ' : '') | ||
+ (directives.length === 0 && vals.length > 0 ? ' ' : '') | ||
+ printBlock(vals); | ||
} | ||
function printInputDefinitionOrExtension(type, options, extension) { | ||
const directives = forExtension(type.appliedDirectives, extension); | ||
const directives = appliedDirectives(type, options, extension); | ||
const fields = forExtension(type.fields(), extension); | ||
if (!directives.length && !fields.length) { | ||
if (!directives.length && !fields.length && (extension || !type.preserveEmptyDefinition)) { | ||
return undefined; | ||
@@ -218,3 +226,3 @@ } | ||
+ printAppliedDirectives(directives, options, true, fields.length > 0) | ||
+ (directives.length === 0 ? ' ' : '') | ||
+ (directives.length === 0 && fields.length > 0 ? ' ' : '') | ||
+ printFields(fields, options); | ||
@@ -226,3 +234,3 @@ } | ||
+ printField(f, options) | ||
+ printAppliedDirectives(f.appliedDirectives, options))); | ||
+ printAppliedDirectives(appliedDirectives(f, options), options))); | ||
} | ||
@@ -249,3 +257,3 @@ function printField(field, options) { | ||
function printArg(arg, options) { | ||
return `${arg}${printAppliedDirectives(arg.appliedDirectives, options)}`; | ||
return `${arg}${printAppliedDirectives(appliedDirectives(arg, options), options)}`; | ||
} | ||
@@ -252,0 +260,0 @@ function printBlock(items) { |
{ | ||
"name": "@apollo/federation-internals", | ||
"version": "2.0.2", | ||
"version": "2.0.3", | ||
"description": "Apollo Federation internal utilities", | ||
@@ -36,3 +36,3 @@ "main": "dist/index.js", | ||
}, | ||
"gitHead": "02bd93d0ed4f75f9cd59b84ddcb66a30babb1553" | ||
"gitHead": "cda3ff6399a820367eb43c2e9a81c77763a57bdd" | ||
} |
@@ -13,3 +13,3 @@ import { | ||
import { | ||
printSchema as printGraphQLjsSchema | ||
printSchema as printGraphQLjsSchema, | ||
} from 'graphql'; | ||
@@ -575,2 +575,118 @@ import { defaultPrintOptions, printSchema } from '../../dist/print'; | ||
describe('type extension where definition is empty', () => { | ||
it('works for object types', () => { | ||
const sdl = ` | ||
type Query { | ||
foo: Foo | ||
} | ||
type Foo | ||
extend type Foo { | ||
baz: String | ||
} | ||
`; | ||
const schema = buildSchema(sdl); | ||
expect(printSchema(schema)).toMatchString(sdl); | ||
expect(schema.type('Foo')?.hasNonExtensionElements()).toBeTruthy(); | ||
expect(schema.type('Foo')?.hasExtensionElements()).toBeTruthy(); | ||
}); | ||
it('works for union', () => { | ||
const sdl = ` | ||
type Query { | ||
foo: Foo | ||
} | ||
union Foo | ||
extend union Foo = Bar | ||
type Bar { | ||
x: Int | ||
} | ||
`; | ||
const schema = buildSchema(sdl); | ||
expect(printSchema(schema)).toMatchString(sdl); | ||
expect(schema.type('Foo')?.hasNonExtensionElements()).toBeTruthy(); | ||
expect(schema.type('Foo')?.hasExtensionElements()).toBeTruthy(); | ||
}); | ||
it('works for enum', () => { | ||
const sdl = ` | ||
type Query { | ||
foo: Foo | ||
} | ||
enum Foo | ||
extend enum Foo { | ||
Bar | ||
} | ||
`; | ||
const schema = buildSchema(sdl); | ||
expect(printSchema(schema)).toMatchString(sdl); | ||
expect(schema.type('Foo')?.hasNonExtensionElements()).toBeTruthy(); | ||
expect(schema.type('Foo')?.hasExtensionElements()).toBeTruthy(); | ||
}); | ||
it('works for input object types', () => { | ||
const sdl = ` | ||
type Query { | ||
foo: Int | ||
} | ||
input Foo | ||
extend input Foo { | ||
bar: Int | ||
} | ||
`; | ||
const schema = buildSchema(sdl); | ||
expect(printSchema(schema)).toMatchString(sdl); | ||
expect(schema.type('Foo')?.hasNonExtensionElements()).toBeTruthy(); | ||
expect(schema.type('Foo')?.hasExtensionElements()).toBeTruthy(); | ||
}); | ||
it('works for scalar type', () => { | ||
const sdl = ` | ||
type Query { | ||
foo: Int | ||
} | ||
scalar Foo | ||
extend scalar Foo | ||
@specifiedBy(url: "something") | ||
`; | ||
const schema = buildSchema(sdl); | ||
expect(printSchema(schema)).toMatchString(sdl); | ||
expect(schema.type('Foo')?.hasNonExtensionElements()).toBeTruthy(); | ||
expect(schema.type('Foo')?.hasExtensionElements()).toBeTruthy(); | ||
}); | ||
}) | ||
test('reject type defined multiple times', () => { | ||
const sdl = ` | ||
type Query { | ||
foo: Foo | ||
} | ||
type Foo { | ||
bar: String | ||
} | ||
type Foo { | ||
baz: String | ||
} | ||
`; | ||
expect(() => buildSchema(sdl).validate()).toThrow('There can be only one type named "Foo"'); | ||
}); | ||
test('default arguments for directives', () => { | ||
@@ -577,0 +693,0 @@ const sdl = ` |
@@ -95,3 +95,2 @@ import { FEDERATION2_LINK_WTH_FULL_IMPORTS } from '..'; | ||
schema | ||
@link(url: "https://specs.apollo.dev/link/v1.0") | ||
${FEDERATION2_LINK_WTH_FULL_IMPORTS} | ||
@@ -153,3 +152,2 @@ { | ||
schema | ||
@link(url: "https://specs.apollo.dev/link/v1.0") | ||
${FEDERATION2_LINK_WTH_FULL_IMPORTS} | ||
@@ -156,0 +154,0 @@ { |
@@ -685,2 +685,15 @@ import { DocumentNode } from 'graphql'; | ||
`, | ||
gql` | ||
extend schema | ||
@link(url: "https://specs.apollo.dev/federation/v2.0") | ||
type T { | ||
k: ID! | ||
} | ||
enum link__Purpose { | ||
EXECUTION | ||
SECURITY | ||
} | ||
`, | ||
]; | ||
@@ -866,2 +879,23 @@ | ||
it('errors on invalid definition for @link Purpose', () => { | ||
const doc = gql` | ||
extend schema | ||
@link(url: "https://specs.apollo.dev/federation/v2.0") | ||
type T { | ||
k: ID! | ||
} | ||
enum link__Purpose { | ||
EXECUTION | ||
RANDOM | ||
} | ||
`; | ||
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[ | ||
'TYPE_DEFINITION_INVALID', | ||
'[S] Invalid definition for type "Purpose": expected values [EXECUTION, SECURITY] but found [EXECUTION, RANDOM].', | ||
]]); | ||
}); | ||
it('allows any (non-scalar) type in redefinition when expected type is a scalar', () => { | ||
@@ -883,2 +917,26 @@ const doc = gql` | ||
}); | ||
it('allows defining a repeatable directive as non-repeatable but validates usages', () => { | ||
const doc = gql` | ||
type T @key(fields: "k1") @key(fields: "k2") { | ||
k1: ID! | ||
k2: ID! | ||
} | ||
directive @key(fields: String!) on OBJECT | ||
`; | ||
// Test for fed2 (with @key being @link-ed) | ||
expect(buildForErrors(doc)).toStrictEqual([[ | ||
'INVALID_GRAPHQL', | ||
'[S] The directive "@key" can only be used once at this location.', | ||
]]); | ||
// Test for fed1 | ||
expect(buildForErrors(doc, { asFed2: false })).toStrictEqual([[ | ||
'INVALID_GRAPHQL', | ||
'[S] The directive "@key" can only be used once at this location.', | ||
]]); | ||
}); | ||
}); | ||
@@ -885,0 +943,0 @@ |
@@ -22,2 +22,6 @@ import { | ||
Kind, | ||
TypeDefinitionNode, | ||
TypeExtensionNode, | ||
EnumTypeExtensionNode, | ||
EnumTypeDefinitionNode, | ||
} from "graphql"; | ||
@@ -53,2 +57,3 @@ import { Maybe } from "graphql/jsutils/Maybe"; | ||
errorCauses, | ||
NamedSchemaElement, | ||
} from "./definitions"; | ||
@@ -75,6 +80,49 @@ | ||
const schema = new Schema(options?.blueprint); | ||
// Building schema has to proceed in a particular order due to 2 main constraints: | ||
// 1. some elements can refer other elements even if the definition of those referenced elements appear later in the AST. | ||
// And in fact, definitions can be cyclic (a type having field whose type is themselves for instance). Which we | ||
// deal with by first adding empty definition for every type and directive name, because handling any of their content. | ||
// 2. we accept "incomplete" schema due to `@link` (incomplete in the sense of the graphQL spec). Indeed, `@link` is all | ||
// about importing definitions, but that mean that some element may be _reference_ in the AST without their _definition_ | ||
// being in the AST. So we need to ensure we "import" those definitions before we try to "build" references to them. | ||
// We do a first pass to add all empty types and directives definition. This ensure any reference on one of | ||
// those can be resolved in the 2nd pass, regardless of the order of the definitions in the AST. | ||
const { directiveDefinitions, schemaDefinitions, schemaExtensions } = buildNamedTypeAndDirectivesShallow(documentNode, schema); | ||
const { | ||
directiveDefinitions, | ||
typeDefinitions, | ||
typeExtensions, | ||
schemaDefinitions, | ||
schemaExtensions, | ||
} = buildNamedTypeAndDirectivesShallow(documentNode, schema, errors); | ||
// We then build the content of enum types, but excluding their directive _applications. The reason we do this | ||
// is that: | ||
// 1. we can (enum values are self-contained and cannot reference anything that may need to be imported first; this | ||
// is also why we skip directive applications at that point, as those _may_ reference something that hasn't been imported yet) | ||
// 2. this allows the code to handle better the case where the `link__Purpose` enum is provided in the AST despite the `@link` | ||
// _definition_ not being provided. And the reason that is true is that as we later _add_ the `@link` definition, we | ||
// will need to check if `link_Purpose` needs to be added or not, but when it is already present, we check it's definition | ||
// is the expected, but that check will unexpected fail if we haven't finished "building" said type definition. | ||
// Do note that we can only do that "early building" for scalar and enum types (and it happens that there is nothing to do | ||
// for scalar because they are the only types whose "content" don't reference other types (and again, for definitions | ||
// referencing other types, we need to import `@link`-ed definition first). Thankfully, the `@link` directive definition | ||
// only rely on a scalar (`Import`) and an enum (`Purpose`) type (if that ever changes, we may have to something more here | ||
// to be resilient to weirdly incomplete schema). | ||
for (const typeNode of typeDefinitions) { | ||
if (typeNode.kind === Kind.ENUM_TYPE_DEFINITION) { | ||
buildEnumTypeValuesWithoutDirectiveApplications(typeNode, schema.type(typeNode.name.value) as EnumType); | ||
} | ||
} | ||
for (const typeExtensionNode of typeExtensions) { | ||
if (typeExtensionNode.kind === Kind.ENUM_TYPE_EXTENSION) { | ||
const toExtend = schema.type(typeExtensionNode.name.value)!; | ||
const extension = toExtend.newExtension(); | ||
extension.sourceAST = typeExtensionNode; | ||
buildEnumTypeValuesWithoutDirectiveApplications(typeExtensionNode, schema.type(typeExtensionNode.name.value) as EnumType, extension); | ||
} | ||
} | ||
// We then deal with directive definition first. This is mainly for the sake of core schemas: the core schema | ||
@@ -111,29 +159,11 @@ // handling in `Schema` detects that the schema is a core one when it see the application of `@core(feature: ".../core/...")` | ||
for (const definitionNode of documentNode.definitions) { | ||
switch (definitionNode.kind) { | ||
case 'OperationDefinition': | ||
case 'FragmentDefinition': | ||
errors.push(new GraphQLError("Invalid executable definition found while building schema", definitionNode)); | ||
continue; | ||
case 'ScalarTypeDefinition': | ||
case 'ObjectTypeDefinition': | ||
case 'InterfaceTypeDefinition': | ||
case 'UnionTypeDefinition': | ||
case 'EnumTypeDefinition': | ||
case 'InputObjectTypeDefinition': | ||
buildNamedTypeInner(definitionNode, schema.type(definitionNode.name.value)!, schema.blueprint, errors); | ||
break; | ||
case 'ScalarTypeExtension': | ||
case 'ObjectTypeExtension': | ||
case 'InterfaceTypeExtension': | ||
case 'UnionTypeExtension': | ||
case 'EnumTypeExtension': | ||
case 'InputObjectTypeExtension': | ||
const toExtend = schema.type(definitionNode.name.value)!; | ||
const extension = toExtend.newExtension(); | ||
extension.sourceAST = definitionNode; | ||
buildNamedTypeInner(definitionNode, toExtend, schema.blueprint, errors, extension); | ||
break; | ||
} | ||
for (const typeNode of typeDefinitions) { | ||
buildNamedTypeInner(typeNode, schema.type(typeNode.name.value)!, schema.blueprint, errors); | ||
} | ||
for (const typeExtensionNode of typeExtensions) { | ||
const toExtend = schema.type(typeExtensionNode.name.value)!; | ||
const extension = toExtend.newExtension(); | ||
extension.sourceAST = typeExtensionNode; | ||
buildNamedTypeInner(typeExtensionNode, toExtend, schema.blueprint, errors, extension); | ||
} | ||
@@ -156,4 +186,6 @@ // Note: we could try calling `schema.validate()` regardless of errors building the schema and merge the resulting | ||
function buildNamedTypeAndDirectivesShallow(documentNode: DocumentNode, schema: Schema): { | ||
function buildNamedTypeAndDirectivesShallow(documentNode: DocumentNode, schema: Schema, errors: GraphQLError[]): { | ||
directiveDefinitions: DirectiveDefinitionNode[], | ||
typeDefinitions: TypeDefinitionNode[], | ||
typeExtensions: TypeExtensionNode[], | ||
schemaDefinitions: SchemaDefinitionNode[], | ||
@@ -163,2 +195,4 @@ schemaExtensions: SchemaExtensionNode[], | ||
const directiveDefinitions = []; | ||
const typeDefinitions = []; | ||
const typeExtensions = []; | ||
const schemaDefinitions = []; | ||
@@ -168,4 +202,9 @@ const schemaExtensions = []; | ||
switch (definitionNode.kind) { | ||
case 'OperationDefinition': | ||
case 'FragmentDefinition': | ||
errors.push(new GraphQLError("Invalid executable definition found while building schema", definitionNode)); | ||
continue; | ||
case 'SchemaDefinition': | ||
schemaDefinitions.push(definitionNode); | ||
schema.schemaDefinition.preserveEmptyDefinition = true; | ||
break; | ||
@@ -181,2 +220,26 @@ case 'SchemaExtension': | ||
case 'InputObjectTypeDefinition': | ||
typeDefinitions.push(definitionNode); | ||
let type = schema.type(definitionNode.name.value); | ||
// Note that the type may already exists due to an extension having been processed first, but we know we | ||
// have seen 2 definitions (which is invalid) if the definition has `preserverEmptyDefnition` already set | ||
// since it's only set for definitions, not extensions. | ||
// Also note that we allow to redefine built-ins. | ||
if (!type || type.isBuiltIn) { | ||
type = schema.addType(newNamedType(withoutTrailingDefinition(definitionNode.kind), definitionNode.name.value)); | ||
} else if (type.preserveEmptyDefinition) { | ||
// Note: we reuse the same error message than graphQL-js would output | ||
throw new GraphQLError(`There can be only one type named "${definitionNode.name.value}"`); | ||
} | ||
// It's possible for the type definition to be empty, because it is valid graphQL to have: | ||
// type Foo | ||
// | ||
// extend type Foo { | ||
// bar: Int | ||
// } | ||
// and we need a way to distinguish between the case above, and the case where only an extension is provided. | ||
// `preserveEmptyDefinition` serves that purpose. | ||
// Note that we do this even if the type was already existing because an extension could have been processed | ||
// first and have created the definition, but we still want to remember that the definition _does_ exists. | ||
type.preserveEmptyDefinition = true; | ||
break; | ||
case 'ScalarTypeExtension': | ||
@@ -188,7 +251,14 @@ case 'ObjectTypeExtension': | ||
case 'InputObjectTypeExtension': | ||
// Note that because of extensions, this may be called multiple times for the same type. | ||
// But at the same time, we want to allow redefining built-in types, because some users do it. | ||
typeExtensions.push(definitionNode); | ||
const existing = schema.type(definitionNode.name.value); | ||
if (!existing || existing.isBuiltIn) { | ||
// In theory, graphQL does not let you have an extension without a corresponding definition. However, | ||
// 1) this is validated later, so there is no real reason to do it here and | ||
// 2) we actually accept it for federation subgraph (due to federation 1 mostly as it's not strictly needed | ||
// for federation 22, but it is still supported to ease migration there too). | ||
// So if the type exists, we simply create it. However, we don't set `preserveEmptyDefinition` since it | ||
// is _not_ a definition. | ||
if (!existing) { | ||
schema.addType(newNamedType(withoutTrailingDefinition(definitionNode.kind), definitionNode.name.value)); | ||
} else if (existing.isBuiltIn) { | ||
throw new GraphQLError(`Cannot extend built-in type "${definitionNode.name.value}"`); | ||
} | ||
@@ -204,2 +274,4 @@ break; | ||
directiveDefinitions, | ||
typeDefinitions, | ||
typeExtensions, | ||
schemaDefinitions, | ||
@@ -306,2 +378,11 @@ schemaExtensions, | ||
switch (definitionNode.kind) { | ||
case 'EnumTypeDefinition': | ||
case 'EnumTypeExtension': | ||
// We built enum values earlier in the `buildEnumTypeValuesWithoutDirectiveApplications`, but as the name | ||
// of that method implies, we just need to finish building directive applications. | ||
const enumType = type as EnumType; | ||
for (const enumVal of definitionNode.values ?? []) { | ||
buildAppliedDirectives(enumVal, enumType.value(enumVal.name.value)!, errors); | ||
} | ||
break; | ||
case 'ObjectTypeDefinition': | ||
@@ -351,14 +432,2 @@ case 'ObjectTypeExtension': | ||
break; | ||
case 'EnumTypeDefinition': | ||
case 'EnumTypeExtension': | ||
const enumType = type as EnumType; | ||
for (const enumVal of definitionNode.values ?? []) { | ||
const v = enumType.addValue(enumVal.name.value); | ||
if (enumVal.description) { | ||
v.description = enumVal.description.value; | ||
} | ||
v.setOfExtension(extension); | ||
buildAppliedDirectives(enumVal, v, errors); | ||
} | ||
break; | ||
case 'InputObjectTypeDefinition': | ||
@@ -375,6 +444,29 @@ case 'InputObjectTypeExtension': | ||
buildAppliedDirectives(definitionNode, type, errors, extension); | ||
buildDescriptionAndSourceAST(definitionNode, type); | ||
} | ||
function buildEnumTypeValuesWithoutDirectiveApplications( | ||
definitionNode: EnumTypeDefinitionNode | EnumTypeExtensionNode, | ||
type: EnumType, | ||
extension?: Extension<any>, | ||
) { | ||
const enumType = type as EnumType; | ||
for (const enumVal of definitionNode.values ?? []) { | ||
const v = enumType.addValue(enumVal.name.value); | ||
if (enumVal.description) { | ||
v.description = enumVal.description.value; | ||
} | ||
v.setOfExtension(extension); | ||
} | ||
buildDescriptionAndSourceAST(definitionNode, type); | ||
} | ||
function buildDescriptionAndSourceAST<T extends NamedSchemaElement<T, Schema, unknown>>( | ||
definitionNode: DefinitionNode & NodeWithDescription, | ||
dest: T, | ||
) { | ||
if (definitionNode.description) { | ||
type.description = definitionNode.description.value; | ||
dest.description = definitionNode.description.value; | ||
} | ||
type.sourceAST = definitionNode; | ||
dest.sourceAST = definitionNode; | ||
} | ||
@@ -474,4 +566,3 @@ | ||
directive.addLocations(...locations); | ||
directive.description = directiveNode.description?.value; | ||
directive.sourceAST = directiveNode; | ||
buildDescriptionAndSourceAST(directiveNode, directive); | ||
} | ||
@@ -478,0 +569,0 @@ |
@@ -230,3 +230,3 @@ import { ASTNode, DirectiveLocation, GraphQLError } from "graphql"; | ||
errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({ | ||
message: `Invalid definition of type ${name}: expected values [${expectedValueNames}] but found [${actualValueNames}].`, | ||
message: `Invalid definition for type "${name}": expected values [${expectedValueNames.join(', ')}] but found [${actualValueNames.join(', ')}].`, | ||
nodes: existing.sourceAST | ||
@@ -233,0 +233,0 @@ })); |
@@ -20,1 +20,2 @@ export * from './definitions'; | ||
export * from './suggestions'; | ||
export * from './graphQLJSSchemaToAST'; |
@@ -44,2 +44,5 @@ import { | ||
NamedType, | ||
sameDirectiveApplications, | ||
isConditionalDirective, | ||
isDirectiveApplicationsSubset, | ||
} from "./definitions"; | ||
@@ -57,12 +60,3 @@ import { sameType } from "./types"; | ||
function haveSameDirectives<TElement extends OperationElement>(op1: TElement, op2: TElement): boolean { | ||
if (op1.appliedDirectives.length != op2.appliedDirectives.length) { | ||
return false; | ||
} | ||
for (const thisDirective of op1.appliedDirectives) { | ||
if (!op2.appliedDirectives.some(thatDirective => thisDirective.name === thatDirective.name && argumentsEquals(thisDirective.arguments(), thatDirective.arguments()))) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
return sameDirectiveApplications(op1.appliedDirectives, op2.appliedDirectives); | ||
} | ||
@@ -311,2 +305,9 @@ | ||
/** | ||
* Returns all the "conditional" directive applications (`@skip` and `@include`) in the provided path. | ||
*/ | ||
export function conditionalDirectivesInOperationPath(path: OperationPath): Directive<any, any>[] { | ||
return path.map((e) => e.appliedDirectives).flat().filter((d) => isConditionalDirective(d)); | ||
} | ||
export function concatOperationPaths(head: OperationPath, tail: OperationPath): OperationPath { | ||
@@ -322,5 +323,12 @@ // While this is mainly a simple array concatenation, we optimize slightly by recognizing if the | ||
const lastOfHead = head[head.length - 1]; | ||
const firstOfTail = tail[0]; | ||
if (isUselessFollowupElement(lastOfHead, firstOfTail)) { | ||
const conditionals = conditionalDirectivesInOperationPath(head); | ||
let firstOfTail = tail[0]; | ||
// Note that in practice, we may be able to eliminate a few elements at the beginning of the path | ||
// due do conditionals ('@skip' and '@include'). Indeed, a (tail) path crossing multiple conditions | ||
// may start with: [ ... on X @include(if: $c1), ... on X @ksip(if: $c2), (...)], but if `head` | ||
// already ends on type `X` _and_ both the conditions on `$c1` and `$c2` are alredy found on `head`, | ||
// then we can remove both fragments in `tail`. | ||
while (firstOfTail && isUselessFollowupElement(lastOfHead, firstOfTail, conditionals)) { | ||
tail = tail.slice(1); | ||
firstOfTail = tail[0]; | ||
} | ||
@@ -330,3 +338,3 @@ return head.concat(tail); | ||
function isUselessFollowupElement(first: OperationElement, followup: OperationElement): boolean { | ||
function isUselessFollowupElement(first: OperationElement, followup: OperationElement, conditionals: Directive<any, any>[]): boolean { | ||
const typeOfFirst = first.kind === 'Field' | ||
@@ -341,3 +349,3 @@ ? baseType(first.definition.type!) | ||
&& !!followup.typeCondition | ||
&& followup.appliedDirectives.length === 0 | ||
&& (followup.appliedDirectives.length === 0 || isDirectiveApplicationsSubset(conditionals, followup.appliedDirectives)) | ||
&& sameType(typeOfFirst, followup.typeCondition); | ||
@@ -775,3 +783,3 @@ } | ||
this._cachedSelections = undefined; | ||
return selection; | ||
return toAdd; | ||
} | ||
@@ -1138,5 +1146,23 @@ | ||
const updatedField = this.field.updateForAddingTo(selectionSet); | ||
return this.field === updatedField | ||
? this.cloneIfFrozen() | ||
: new FieldSelection(updatedField, this.selectionSet?.cloneIfFrozen()); | ||
if (this.field === updatedField) { | ||
return this.cloneIfFrozen(); | ||
} | ||
// We create a new selection that not only uses the updated field, but also ensures | ||
// the underlying selection set uses the updated field type as parent type. | ||
const updatedBaseType = baseType(updatedField.definition.type!); | ||
let updatedSelectionSet : SelectionSet | undefined; | ||
if (this.selectionSet && this.selectionSet.parentType !== updatedBaseType) { | ||
assert(isCompositeType(updatedBaseType), `Expected ${updatedBaseType.coordinate} to be composite but ${updatedBaseType.kind}`); | ||
updatedSelectionSet = new SelectionSet(updatedBaseType); | ||
// Note that re-adding every selection ensures that anything frozen will be cloned as needed, on top of handling any knock-down | ||
// effect of the type change. | ||
for (const selection of this.selectionSet.selections()) { | ||
updatedSelectionSet.add(selection); | ||
} | ||
} else { | ||
updatedSelectionSet = this.selectionSet?.cloneIfFrozen(); | ||
} | ||
return new FieldSelection(updatedField, updatedSelectionSet); | ||
} | ||
@@ -1143,0 +1169,0 @@ |
@@ -38,2 +38,3 @@ import { | ||
fieldFilter: (f: FieldDefinition<any>) => boolean, | ||
directiveApplicationFilter: (d: Directive) => boolean, | ||
} | ||
@@ -50,2 +51,3 @@ | ||
fieldFilter: () => true, | ||
directiveApplicationFilter: () => true, | ||
} | ||
@@ -130,2 +132,14 @@ | ||
function appliedDirectives( | ||
element: SchemaElement<any, any>, | ||
options: PrintOptions, | ||
extension?: Extension<any> | null, | ||
): readonly Directive[] { | ||
let directives = forExtension(element.appliedDirectives, extension); | ||
if (options.directiveApplicationFilter) { | ||
directives = directives.filter(options.directiveApplicationFilter); | ||
} | ||
return directives; | ||
} | ||
function printSchemaDefinitionOrExtension( | ||
@@ -137,3 +151,4 @@ schemaDefinition: SchemaDefinition, | ||
const roots = forExtension(schemaDefinition.roots(), extension); | ||
const directives = forExtension(schemaDefinition.appliedDirectives, extension); | ||
const directives = appliedDirectives(schemaDefinition, options, extension); | ||
if (!roots.length && !directives.length) { | ||
@@ -232,3 +247,3 @@ return undefined; | ||
function printScalarDefinitionOrExtension(type: ScalarType, options: PrintOptions, extension?: Extension<any> | null): string | undefined { | ||
const directives = forExtension(type.appliedDirectives, extension); | ||
const directives = appliedDirectives(type, options, extension); | ||
if (extension && !directives.length) { | ||
@@ -247,3 +262,3 @@ return undefined; | ||
function printFieldBasedTypeDefinitionOrExtension(kind: string, type: ObjectType | InterfaceType, options: PrintOptions, extension?: Extension<any> | null): string | undefined { | ||
const directives = forExtension<Directive<ObjectType | InterfaceType>>(type.appliedDirectives, extension); | ||
const directives = appliedDirectives(type, options, extension); | ||
const interfaces = forExtension<InterfaceImplementation<any>>(type.interfaceImplementations(), extension); | ||
@@ -254,3 +269,3 @@ let fields = forExtension<FieldDefinition<any>>(type.fields(), extension); | ||
} | ||
if (!directives.length && !interfaces.length && !fields.length) { | ||
if (!directives.length && !interfaces.length && !fields.length && (extension || !type.preserveEmptyDefinition)) { | ||
return undefined; | ||
@@ -263,3 +278,3 @@ } | ||
+ printAppliedDirectives(directives, options, true, fields.length > 0) | ||
+ (directives.length === 0 ? ' ' : '') | ||
+ (directives.length === 0 && fields.length > 0 ? ' ' : '') | ||
+ printFields(fields, options); | ||
@@ -269,5 +284,5 @@ } | ||
function printUnionDefinitionOrExtension(type: UnionType, options: PrintOptions, extension?: Extension<any> | null): string | undefined { | ||
const directives = forExtension(type.appliedDirectives, extension); | ||
const directives = appliedDirectives(type, options, extension); | ||
const members = forExtension(type.members(), extension); | ||
if (!directives.length && !members.length) { | ||
if (!directives.length && !members.length && (extension || !type.preserveEmptyDefinition)) { | ||
return undefined; | ||
@@ -284,5 +299,5 @@ } | ||
function printEnumDefinitionOrExtension(type: EnumType, options: PrintOptions, extension?: Extension<any> | null): string | undefined { | ||
const directives = forExtension(type.appliedDirectives, extension); | ||
const directives = appliedDirectives(type, options, extension); | ||
const values = forExtension(type.values, extension); | ||
if (!directives.length && !values.length) { | ||
if (!directives.length && !values.length && (extension || !type.preserveEmptyDefinition)) { | ||
return undefined; | ||
@@ -299,3 +314,3 @@ } | ||
+ printAppliedDirectives(directives, options, true, vals.length > 0) | ||
+ (directives.length === 0 ? ' ' : '') | ||
+ (directives.length === 0 && vals.length > 0 ? ' ' : '') | ||
+ printBlock(vals); | ||
@@ -305,5 +320,5 @@ } | ||
function printInputDefinitionOrExtension(type: InputObjectType, options: PrintOptions, extension?: Extension<any> | null): string | undefined { | ||
const directives = forExtension(type.appliedDirectives, extension); | ||
const directives = appliedDirectives(type, options, extension); | ||
const fields = forExtension(type.fields(), extension); | ||
if (!directives.length && !fields.length) { | ||
if (!directives.length && !fields.length && (extension || !type.preserveEmptyDefinition)) { | ||
return undefined; | ||
@@ -315,3 +330,3 @@ } | ||
+ printAppliedDirectives(directives, options, true, fields.length > 0) | ||
+ (directives.length === 0 ? ' ' : '') | ||
+ (directives.length === 0 && fields.length > 0 ? ' ' : '') | ||
+ printFields(fields, options); | ||
@@ -325,3 +340,3 @@ } | ||
+ printField(f, options) | ||
+ printAppliedDirectives(f.appliedDirectives, options))); | ||
+ printAppliedDirectives(appliedDirectives(f, options), options))); | ||
} | ||
@@ -357,3 +372,3 @@ | ||
function printArg(arg: ArgumentDefinition<any>, options: PrintOptions) { | ||
return `${arg}${printAppliedDirectives(arg.appliedDirectives, options)}`; | ||
return `${arg}${printAppliedDirectives(appliedDirectives(arg, options), options)}`; | ||
} | ||
@@ -360,0 +375,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1821059
169
30684