@apollo/federation-internals
Advanced tools
Comparing version 2.2.2 to 2.3.0-alpha.0
@@ -54,2 +54,3 @@ import { ConstArgumentNode, ASTNode, DirectiveLocation, ConstDirectiveNode, DocumentNode, GraphQLError, GraphQLSchema, Kind, TypeNode, VariableDefinitionNode, VariableNode, DirectiveDefinitionNode, DirectiveNode } from "graphql"; | ||
export declare function runtimeTypesIntersects(t1: CompositeType, t2: CompositeType): boolean; | ||
export declare function supertypes(type: CompositeType): readonly CompositeType[]; | ||
export declare function isConditionalDirective(directive: Directive<any, any> | DirectiveDefinition<any>): boolean; | ||
@@ -409,2 +410,3 @@ export declare const executableDirectiveLocations: DirectiveLocation[]; | ||
protected removeReferenceRecursive(ref: ObjectTypeReferencer): void; | ||
unionsWhereMember(): readonly UnionType[]; | ||
} | ||
@@ -411,0 +413,0 @@ export declare class InterfaceType extends FieldBasedType<InterfaceType, InterfaceTypeReferencer> { |
@@ -92,3 +92,2 @@ import { ASTNode, GraphQLError, GraphQLErrorOptions, GraphQLFormattedError } from "graphql"; | ||
EXTERNAL_MISSING_ON_BASE: ErrorCodeDefinition; | ||
INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH: ErrorCodeDefinition; | ||
INVALID_FIELD_SHARING: ErrorCodeDefinition; | ||
@@ -122,2 +121,5 @@ INVALID_SHAREABLE_USAGE: ErrorCodeDefinition; | ||
DIRECTIVE_COMPOSITION_ERROR: ErrorCodeDefinition; | ||
INTERFACE_OBJECT_USAGE_ERROR: ErrorCodeDefinition; | ||
INTERFACE_KEY_NOT_ON_IMPLEMENTATION: ErrorCodeDefinition; | ||
INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE: ErrorCodeDefinition; | ||
}; | ||
@@ -124,0 +126,0 @@ export declare const REMOVED_ERRORS: string[][]; |
@@ -132,3 +132,3 @@ "use strict"; | ||
const REQUIRES_MISSING_EXTERNAL = DIRECTIVE_FIELDS_MISSING_EXTERNAL.createCode('requires'); | ||
const DIRECTIVE_UNSUPPORTED_ON_INTERFACE = makeFederationDirectiveErrorCodeCategory('UNSUPPORTED_ON_INTERFACE', (directive) => `A \`@${directive}\` directive is used on an interface, which is not (yet) supported.`); | ||
const DIRECTIVE_UNSUPPORTED_ON_INTERFACE = makeFederationDirectiveErrorCodeCategory('UNSUPPORTED_ON_INTERFACE', (directive) => `A \`@${directive}\` directive is used on an interface, which is ${directive === 'key' ? 'only supported when @linking to federation 2.3+' : 'not (yet) supported'}.`); | ||
const KEY_UNSUPPORTED_ON_INTERFACE = DIRECTIVE_UNSUPPORTED_ON_INTERFACE.createCode('key'); | ||
@@ -176,3 +176,2 @@ const PROVIDES_UNSUPPORTED_ON_INTERFACE = DIRECTIVE_UNSUPPORTED_ON_INTERFACE.createCode('provides'); | ||
const EXTERNAL_MISSING_ON_BASE = makeCodeDefinition('EXTERNAL_MISSING_ON_BASE', 'A field is marked as `@external` in a subgraph but with no non-external declaration in any other subgraph.', { addedIn: FED1_CODE }); | ||
const INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH = makeCodeDefinition('INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH', 'For an interface field, some of its concrete implementations have @external or @requires and there is difference in those implementations return type (which is currently not supported; see https://github.com/apollographql/federation/issues/1257)'); | ||
const INVALID_FIELD_SHARING = makeCodeDefinition('INVALID_FIELD_SHARING', 'A field that is non-shareable in at least one subgraph is resolved by multiple subgraphs.'); | ||
@@ -203,2 +202,5 @@ const INVALID_SHAREABLE_USAGE = makeCodeDefinition('INVALID_SHAREABLE_USAGE', 'The `@shareable` federation directive is used in an invalid way.', { addedIn: '2.1.2' }); | ||
const DIRECTIVE_COMPOSITION_ERROR = makeCodeDefinition('DIRECTIVE_COMPOSITION_ERROR', 'Error when composing custom directives.', { addedIn: '2.1.0' }); | ||
const INTERFACE_OBJECT_USAGE_ERROR = makeCodeDefinition('INTERFACE_OBJECT_USAGE_ERROR', 'Error in the usage of the @interfaceObject directive.', { addedIn: '2.3.0' }); | ||
const INTERFACE_KEY_NOT_ON_IMPLEMENTATION = makeCodeDefinition('INTERFACE_KEY_NOT_ON_IMPLEMENTATION', 'A `@key` is defined on an interface type, but is not defined (or is not resolvable) on at least one of the interface implementations', { addedIn: '2.3.0' }); | ||
const INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE = makeCodeDefinition('INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE', 'A subgraph has a `@key` on an interface type, but that subgraph does not define an implementation (in the supergraph) of that interface', { addedIn: '2.3.0' }); | ||
exports.ERROR_CATEGORIES = { | ||
@@ -257,3 +259,2 @@ DIRECTIVE_FIELDS_MISSING_EXTERNAL, | ||
EXTERNAL_MISSING_ON_BASE, | ||
INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH, | ||
INVALID_FIELD_SHARING, | ||
@@ -287,2 +288,5 @@ INVALID_SHAREABLE_USAGE, | ||
DIRECTIVE_COMPOSITION_ERROR, | ||
INTERFACE_OBJECT_USAGE_ERROR, | ||
INTERFACE_KEY_NOT_ON_IMPLEMENTATION, | ||
INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE, | ||
}; | ||
@@ -300,13 +304,14 @@ const codeDefByCode = Object.values(exports.ERRORS).reduce((obj, codeDef) => { obj[codeDef.code] = codeDef; return obj; }, {}); | ||
['REQUIRES_USED_ON_BASE', 'As there is not type ownership anymore, there is also no particular limitation as to which subgraph can use a @requires.'], | ||
['DUPLICATE_SCALAR_DEFINITION', 'As duplicate scalar definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`'], | ||
['DUPLICATE_ENUM_DEFINITION', 'As duplicate enum definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`'], | ||
['DUPLICATE_ENUM_VALUE', 'As duplicate enum values is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`'], | ||
['ENUM_MISMATCH', 'Subgraph definitions for an enum are now merged by composition'], | ||
['DUPLICATE_SCALAR_DEFINITION', 'As duplicate scalar definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'], | ||
['DUPLICATE_ENUM_DEFINITION', 'As duplicate enum definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'], | ||
['DUPLICATE_ENUM_VALUE', 'As duplicate enum values is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'], | ||
['ENUM_MISMATCH', 'Subgraph definitions for an enum are now merged by composition.'], | ||
['VALUE_TYPE_NO_ENTITY', 'There is no strong different between entity and value types in the model (they are just usage pattern) and a type can have keys in one subgraph but not another.'], | ||
['VALUE_TYPE_UNION_TYPES_MISMATCH', 'Subgraph definitions for an union are now merged by composition'], | ||
['PROVIDES_FIELDS_SELECT_INVALID_TYPE', '@provides can now be used on field of interface, union and list types'], | ||
['RESERVED_FIELD_USED', 'This error was previously not correctly enforced: the _service and _entities, if present, were overridden; this is still the case'], | ||
['NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH', 'Since federation 2.1.0, the case this error used to cover is now a warning (with code `INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS`) instead of an error'], | ||
['REQUIRES_FIELDS_HAS_ARGS', 'Since federation 2.1.1, using fields with arguments in a @requires is fully supported'], | ||
['VALUE_TYPE_UNION_TYPES_MISMATCH', 'Subgraph definitions for an union are now merged by composition.'], | ||
['PROVIDES_FIELDS_SELECT_INVALID_TYPE', '@provides can now be used on field of interface, union and list types.'], | ||
['RESERVED_FIELD_USED', 'This error was previously not correctly enforced: the _service and _entities, if present, were overridden; this is still the case.'], | ||
['NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH', 'Since federation 2.1.0, the case this error used to cover is now a warning (with code `INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS`) instead of an error.'], | ||
['REQUIRES_FIELDS_HAS_ARGS', 'Since federation 2.1.1, using fields with arguments in a @requires is fully supported.'], | ||
['INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH', 'This error was thrown by a validation introduced to avoid running into a known runtime bug. Since federation 2.3, the underlying runtime bug has been addressed and the validation/limitation was no longer necessary and has been removed.'], | ||
]; | ||
//# sourceMappingURL=error.js.map |
@@ -142,3 +142,8 @@ "use strict"; | ||
const fieldDirective = joinSpec.fieldDirective(supergraph); | ||
const getSubgraph = (application) => graphEnumNameToSubgraphName.get(application.arguments().graph); | ||
const unionMemberDirective = joinSpec.unionMemberDirective(supergraph); | ||
const enumValueDirective = joinSpec.enumValueDirective(supergraph); | ||
const getSubgraph = (application) => { | ||
const graph = application.arguments().graph; | ||
return graph ? graphEnumNameToSubgraphName.get(graph) : undefined; | ||
}; | ||
let includeTypeInSubgraph = () => true; | ||
@@ -192,3 +197,7 @@ if (isFed1) { | ||
if (!subgraphType) { | ||
subgraphType = schema.addType((0, definitions_1.newNamedType)(type.kind, type.name)); | ||
const kind = args.isInterfaceObject ? 'ObjectType' : type.kind; | ||
subgraphType = schema.addType((0, definitions_1.newNamedType)(kind, type.name)); | ||
if (args.isInterfaceObject) { | ||
subgraphType.applyDirective('interfaceObject'); | ||
} | ||
} | ||
@@ -262,2 +271,5 @@ if (args.key) { | ||
const args = application.arguments(); | ||
if (!args.graph) { | ||
continue; | ||
} | ||
const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(args.graph)); | ||
@@ -299,3 +311,7 @@ const subgraphField = addSubgraphField(field, subgraph, args.type); | ||
for (const value of type.values) { | ||
subgraphEnum.addValue(value.name); | ||
const addValue = !enumValueDirective | ||
|| value.appliedDirectivesOf(enumValueDirective).some((d) => graphEnumNameToSubgraphName.get(d.arguments().graph) === subgraph.name); | ||
if (addValue) { | ||
subgraphEnum.addValue(value.name); | ||
} | ||
} | ||
@@ -311,4 +327,14 @@ } | ||
(0, utils_1.assert)((0, definitions_1.isUnionType)(subgraphUnion), () => `${subgraphUnion} should be an enum but found a ${subgraphUnion.kind}`); | ||
for (const memberType of type.types()) { | ||
const subgraphType = subgraph.schema.type(memberType.name); | ||
let membersInSubgraph; | ||
if (unionMemberDirective) { | ||
membersInSubgraph = type | ||
.appliedDirectivesOf(unionMemberDirective) | ||
.filter((d) => graphEnumNameToSubgraphName.get(d.arguments().graph) === subgraph.name) | ||
.map((d) => d.arguments().member); | ||
} | ||
else { | ||
membersInSubgraph = type.types().map((t) => t.name); | ||
} | ||
for (const memberTypeName of membersInSubgraph) { | ||
const subgraphType = subgraph.schema.type(memberTypeName); | ||
if (subgraphType) { | ||
@@ -315,0 +341,0 @@ subgraphUnion.addType(subgraphType); |
@@ -30,2 +30,3 @@ import { CompositeType, CoreFeature, Directive, DirectiveDefinition, FieldDefinition, InputFieldDefinition, InterfaceType, NamedType, ObjectType, ScalarType, Schema, SchemaBlueprint, SchemaConfig, SchemaElement, UnionType } from "./definitions"; | ||
isFieldShareable(field: FieldDefinition<any>): boolean; | ||
isInterfaceObjectType(type: NamedType): type is ObjectType; | ||
federationDirectiveNameInSchema(name: string): string; | ||
@@ -35,2 +36,3 @@ federationTypeNameInSchema(name: string): string; | ||
private getFederationDirective; | ||
private getPost20FederationDirective; | ||
keyDirective(): DirectiveDefinition<{ | ||
@@ -57,8 +59,7 @@ fields: any; | ||
}>; | ||
composeDirective(): DirectiveDefinition<{ | ||
composeDirective(): Post20FederationDirectiveDefinition<{ | ||
name: string; | ||
}> | FederationDirectiveNotDefinedInSchema<{ | ||
name: string; | ||
}>; | ||
inaccessibleDirective(): DirectiveDefinition<{}>; | ||
interfaceObjectDirective(): Post20FederationDirectiveDefinition<{}>; | ||
allFederationDirectives(): DirectiveDefinition[]; | ||
@@ -77,5 +78,9 @@ entityType(): UnionType | undefined; | ||
}; | ||
export declare type Post20FederationDirectiveDefinition<TApplicationArgs extends { | ||
[key: string]: any; | ||
}> = DirectiveDefinition<TApplicationArgs> | FederationDirectiveNotDefinedInSchema<TApplicationArgs>; | ||
export declare function isFederationDirectiveDefinedInSchema<TApplicationArgs extends { | ||
[key: string]: any; | ||
}>(definition: DirectiveDefinition<TApplicationArgs> | FederationDirectiveNotDefinedInSchema<TApplicationArgs>): definition is DirectiveDefinition<TApplicationArgs>; | ||
}>(definition: Post20FederationDirectiveDefinition<TApplicationArgs>): definition is DirectiveDefinition<TApplicationArgs>; | ||
export declare function hasAppliedDirective(type: NamedType, definition: Post20FederationDirectiveDefinition<any>): boolean; | ||
export declare class FederationBlueprint extends SchemaBlueprint { | ||
@@ -98,4 +103,6 @@ private readonly withRootTypeRenaming; | ||
export declare function setSchemaAsFed2Subgraph(schema: Schema): void; | ||
export declare const FEDERATION2_LINK_WITH_FULL_IMPORTS = "@link(url: \"https://specs.apollo.dev/federation/v2.2\", import: [\"@key\", \"@requires\", \"@provides\", \"@external\", \"@tag\", \"@extends\", \"@shareable\", \"@inaccessible\", \"@override\", \"@composeDirective\"])"; | ||
export declare function asFed2SubgraphDocument(document: DocumentNode): DocumentNode; | ||
export declare const FEDERATION2_LINK_WITH_FULL_IMPORTS = "@link(url: \"https://specs.apollo.dev/federation/v2.3\", import: [\"@key\", \"@requires\", \"@provides\", \"@external\", \"@tag\", \"@extends\", \"@shareable\", \"@inaccessible\", \"@override\", \"@composeDirective\", \"@interfaceObject\"])"; | ||
export declare function asFed2SubgraphDocument(document: DocumentNode, options?: { | ||
addAsSchemaExtension: boolean; | ||
}): DocumentNode; | ||
export declare function printSubgraphNames(names: string[]): string; | ||
@@ -106,2 +113,3 @@ export declare function federationMetadata(schema: Schema): FederationMetadata | undefined; | ||
export declare function isEntityType(type: NamedType): boolean; | ||
export declare function isInterfaceObjectType(type: NamedType): boolean; | ||
export declare function buildSubgraph(name: string, url: string, source: DocumentNode | string, withRootTypeRenaming?: boolean): Subgraph; | ||
@@ -108,0 +116,0 @@ export declare function newEmptyFederation2Schema(config?: SchemaConfig): Schema; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.removeInactiveProvidesAndRequires = exports.addSubgraphToError = exports.addSubgraphToASTNode = exports.Subgraph = exports.FEDERATION_OPERATION_FIELDS = exports.entitiesFieldName = exports.serviceFieldName = exports.FEDERATION_OPERATION_TYPES = exports.entityTypeSpec = exports.serviceTypeSpec = exports.anyTypeSpec = exports.Subgraphs = exports.subgraphsFromServiceList = exports.collectTargetFields = exports.parseFieldSetArgument = exports.newEmptyFederation2Schema = exports.buildSubgraph = exports.isEntityType = exports.isFederationField = exports.isFederationSubgraphSchema = exports.federationMetadata = exports.printSubgraphNames = exports.asFed2SubgraphDocument = exports.FEDERATION2_LINK_WITH_FULL_IMPORTS = exports.setSchemaAsFed2Subgraph = exports.FederationBlueprint = exports.isFederationDirectiveDefinedInSchema = exports.FederationMetadata = exports.collectUsedFields = exports.FEDERATION_UNNAMED_SUBGRAPH_NAME = exports.FEDERATION_RESERVED_SUBGRAPH_NAME = void 0; | ||
exports.removeInactiveProvidesAndRequires = exports.addSubgraphToError = exports.addSubgraphToASTNode = exports.Subgraph = exports.FEDERATION_OPERATION_FIELDS = exports.entitiesFieldName = exports.serviceFieldName = exports.FEDERATION_OPERATION_TYPES = exports.entityTypeSpec = exports.serviceTypeSpec = exports.anyTypeSpec = exports.Subgraphs = exports.subgraphsFromServiceList = exports.collectTargetFields = exports.parseFieldSetArgument = exports.newEmptyFederation2Schema = exports.buildSubgraph = exports.isInterfaceObjectType = exports.isEntityType = exports.isFederationField = exports.isFederationSubgraphSchema = exports.federationMetadata = exports.printSubgraphNames = exports.asFed2SubgraphDocument = exports.FEDERATION2_LINK_WITH_FULL_IMPORTS = exports.setSchemaAsFed2Subgraph = exports.FederationBlueprint = exports.hasAppliedDirective = exports.isFederationDirectiveDefinedInSchema = exports.FederationMetadata = exports.collectUsedFields = exports.FEDERATION_UNNAMED_SUBGRAPH_NAME = exports.FEDERATION_RESERVED_SUBGRAPH_NAME = void 0; | ||
const definitions_1 = require("./definitions"); | ||
@@ -152,3 +152,3 @@ const utils_1 = require("./utils"); | ||
} | ||
function validateAllFieldSet({ definition, targetTypeExtractor, errorCollector, metadata, isOnParentType = false, allowOnNonExternalLeafFields = false, allowFieldsWithArguments = false, onFields, }) { | ||
function validateAllFieldSet({ definition, targetTypeExtractor, errorCollector, metadata, isOnParentType = false, allowOnNonExternalLeafFields = false, allowFieldsWithArguments = false, allowOnInterface = false, onFields, }) { | ||
for (const application of definition.applications()) { | ||
@@ -158,3 +158,3 @@ const elt = application.parent; | ||
const parentType = isOnParentType ? type : elt.parent; | ||
if ((0, definitions_1.isInterfaceType)(parentType)) { | ||
if ((0, definitions_1.isInterfaceType)(parentType) && !allowOnInterface) { | ||
const code = error_1.ERROR_CATEGORIES.DIRECTIVE_UNSUPPORTED_ON_INTERFACE.get(definition.name); | ||
@@ -235,27 +235,45 @@ errorCollector.push(code.err(isOnParentType | ||
} | ||
function validateInterfaceRuntimeImplementationFieldsTypes(itf, metadata, errorCollector) { | ||
var _a; | ||
const requiresDirective = (_a = federationMetadata(itf.schema())) === null || _a === void 0 ? void 0 : _a.requiresDirective(); | ||
(0, utils_1.assert)(requiresDirective, 'Schema should be a federation subgraph, but @requires directive not found'); | ||
const runtimeTypes = itf.possibleRuntimeTypes(); | ||
for (const field of itf.fields()) { | ||
const withExternalOrRequires = []; | ||
const typeToImplems = new utils_1.MultiMap(); | ||
const nodes = []; | ||
for (const type of runtimeTypes) { | ||
const implemField = type.field(field.name); | ||
if (!implemField) | ||
continue; | ||
if (implemField.sourceAST) { | ||
nodes.push(implemField.sourceAST); | ||
function validateKeyOnInterfacesAreAlsoOnAllImplementations(metadata, errorCollector) { | ||
for (const itfType of metadata.schema.interfaceTypes()) { | ||
const implementations = itfType.possibleRuntimeTypes(); | ||
for (const keyApplication of itfType.appliedDirectivesOf(metadata.keyDirective())) { | ||
const fields = parseFieldSetArgument({ parentType: itfType, directive: keyApplication, validate: false }); | ||
const isResolvable = !(keyApplication.arguments().resolvable === false); | ||
const implementationsWithKeyButNotResolvable = new Array(); | ||
const implementationsMissingKey = new Array(); | ||
for (const type of implementations) { | ||
const matchingApp = type.appliedDirectivesOf(metadata.keyDirective()).find((app) => { | ||
const appFields = parseFieldSetArgument({ parentType: type, directive: app, validate: false }); | ||
return fields.equals(appFields); | ||
}); | ||
if (matchingApp) { | ||
if (isResolvable && matchingApp.arguments().resolvable === false) { | ||
implementationsWithKeyButNotResolvable.push(type); | ||
} | ||
} | ||
else { | ||
implementationsMissingKey.push(type); | ||
} | ||
} | ||
if (metadata.isFieldExternal(implemField) || implemField.hasAppliedDirective(requiresDirective)) { | ||
withExternalOrRequires.push(implemField); | ||
if (implementationsMissingKey.length > 0) { | ||
const typesString = (0, utils_1.printHumanReadableList)(implementationsMissingKey.map((i) => `"${i.coordinate}"`), { | ||
prefix: 'type', | ||
prefixPlural: 'types', | ||
}); | ||
errorCollector.push(error_1.ERRORS.INTERFACE_KEY_NOT_ON_IMPLEMENTATION.err(`Key ${keyApplication} on interface type "${itfType.coordinate}" is missing on implementation ${typesString}.`, { nodes: (0, definitions_1.sourceASTs)(...implementationsMissingKey) })); | ||
} | ||
const returnType = implemField.type; | ||
typeToImplems.add(returnType.toString(), implemField); | ||
else if (implementationsWithKeyButNotResolvable.length > 0) { | ||
const typesString = (0, utils_1.printHumanReadableList)(implementationsWithKeyButNotResolvable.map((i) => `"${i.coordinate}"`), { | ||
prefix: 'type', | ||
prefixPlural: 'types', | ||
}); | ||
errorCollector.push(error_1.ERRORS.INTERFACE_KEY_NOT_ON_IMPLEMENTATION.err(`Key ${keyApplication} on interface type "${itfType.coordinate}" should be resolvable on all implementation types, but is declared with argument "@key(resolvable:)" set to false in ${typesString}.`, { nodes: (0, definitions_1.sourceASTs)(...implementationsWithKeyButNotResolvable) })); | ||
} | ||
} | ||
if (withExternalOrRequires.length > 0 && typeToImplems.size > 1) { | ||
const typeToImplemsArray = [...typeToImplems.entries()]; | ||
errorCollector.push(error_1.ERRORS.INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH.err(`Some of the runtime implementations of interface field "${field.coordinate}" are marked @external or have a @require (${withExternalOrRequires.map(printFieldCoordinate)}) so all the implementations should use the same type (a current limitation of federation; see https://github.com/apollographql/federation/issues/1257), but ${formatFieldsToReturnType(typeToImplemsArray[0])} while ${(0, utils_1.joinStrings)(typeToImplemsArray.slice(1).map(formatFieldsToReturnType), ' and ')}.`, { nodes })); | ||
} | ||
} | ||
function validateInterfaceObjectsAreOnEntities(metadata, errorCollector) { | ||
for (const application of metadata.interfaceObjectDirective().applications()) { | ||
if (!isEntityType(application.parent)) { | ||
errorCollector.push(error_1.ERRORS.INTERFACE_OBJECT_USAGE_ERROR.err(`The @interfaceObject directive can only be applied to entity types but type "${application.parent.coordinate}" has no @key in this subgraph.`, { nodes: application.parent.sourceAST })); | ||
} | ||
@@ -290,6 +308,2 @@ } | ||
} | ||
const printFieldCoordinate = (f) => `"${f.coordinate}"`; | ||
function formatFieldsToReturnType([type, implems]) { | ||
return `${(0, utils_1.joinStrings)(implems.map(printFieldCoordinate))} ${implems.length == 1 ? 'has' : 'have'} type "${type}"`; | ||
} | ||
class FederationMetadata { | ||
@@ -356,2 +370,6 @@ constructor(schema) { | ||
} | ||
isInterfaceObjectType(type) { | ||
return (0, definitions_1.isObjectType)(type) | ||
&& hasAppliedDirective(type, this.interfaceObjectDirective()); | ||
} | ||
federationDirectiveNameInSchema(name) { | ||
@@ -392,2 +410,9 @@ if (this.isFed2Schema()) { | ||
} | ||
getPost20FederationDirective(name) { | ||
var _a; | ||
return (_a = this.getFederationDirective(name)) !== null && _a !== void 0 ? _a : { | ||
name, | ||
applications: () => new Array(), | ||
}; | ||
} | ||
keyDirective() { | ||
@@ -418,7 +443,3 @@ return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.KEY); | ||
composeDirective() { | ||
var _a; | ||
return (_a = this.getFederationDirective(federationSpec_1.FederationDirectiveName.COMPOSE_DIRECTIVE)) !== null && _a !== void 0 ? _a : { | ||
name: federationSpec_1.FederationDirectiveName.COMPOSE_DIRECTIVE, | ||
applications: () => new Array(), | ||
}; | ||
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.COMPOSE_DIRECTIVE); | ||
} | ||
@@ -428,2 +449,5 @@ inaccessibleDirective() { | ||
} | ||
interfaceObjectDirective() { | ||
return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.INTERFACE_OBJECT); | ||
} | ||
allFederationDirectives() { | ||
@@ -448,2 +472,6 @@ const baseDirectives = [ | ||
} | ||
const interfaceObjectDirective = this.interfaceObjectDirective(); | ||
if (isFederationDirectiveDefinedInSchema(interfaceObjectDirective)) { | ||
baseDirectives.push(interfaceObjectDirective); | ||
} | ||
return baseDirectives; | ||
@@ -481,2 +509,6 @@ } | ||
exports.isFederationDirectiveDefinedInSchema = isFederationDirectiveDefinedInSchema; | ||
function hasAppliedDirective(type, definition) { | ||
return isFederationDirectiveDefinedInSchema(definition) && type.hasAppliedDirective(definition); | ||
} | ||
exports.hasAppliedDirective = hasAppliedDirective; | ||
class FederationBlueprint extends definitions_1.SchemaBlueprint { | ||
@@ -560,2 +592,3 @@ constructor(withRootTypeRenaming) { | ||
allowOnNonExternalLeafFields: true, | ||
allowOnInterface: metadata.federationFeature().url.version.compareTo(new coreSpec_1.FeatureVersion(2, 3)) >= 0, | ||
onFields: field => { | ||
@@ -594,2 +627,4 @@ const type = (0, definitions_1.baseType)(field.type); | ||
validateAllExternalFieldsUsed(metadata, errorCollector); | ||
validateKeyOnInterfacesAreAlsoOnAllImplementations(metadata, errorCollector); | ||
validateInterfaceObjectsAreOnEntities(metadata, errorCollector); | ||
const tagDirective = metadata.tagDirective(); | ||
@@ -602,5 +637,2 @@ if (tagDirective) { | ||
} | ||
for (const itf of schema.interfaceTypes()) { | ||
validateInterfaceRuntimeImplementationFieldsTypes(itf, metadata, errorCollector); | ||
} | ||
for (const objectType of schema.objectTypes()) { | ||
@@ -696,26 +728,65 @@ validateShareableNotRepeatedOnSameDeclaration(objectType, metadata, errorCollector); | ||
exports.setSchemaAsFed2Subgraph = setSchemaAsFed2Subgraph; | ||
exports.FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.2", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective"])'; | ||
function asFed2SubgraphDocument(document) { | ||
const fed2LinkExtension = { | ||
kind: graphql_1.Kind.SCHEMA_EXTENSION, | ||
directives: [{ | ||
kind: graphql_1.Kind.DIRECTIVE, | ||
name: { kind: graphql_1.Kind.NAME, value: coreSpec_1.linkDirectiveDefaultName }, | ||
arguments: [{ | ||
kind: graphql_1.Kind.ARGUMENT, | ||
name: { kind: graphql_1.Kind.NAME, value: 'url' }, | ||
value: { kind: graphql_1.Kind.STRING, value: federationSpec.url.toString() } | ||
}, | ||
{ | ||
kind: graphql_1.Kind.ARGUMENT, | ||
name: { kind: graphql_1.Kind.NAME, value: 'import' }, | ||
value: { kind: graphql_1.Kind.LIST, values: federationSpec.directiveSpecs().map((spec) => ({ kind: graphql_1.Kind.STRING, value: `@${spec.name}` })) } | ||
}] | ||
}] | ||
}; | ||
return { | ||
kind: graphql_1.Kind.DOCUMENT, | ||
loc: document.loc, | ||
definitions: document.definitions.concat(fed2LinkExtension) | ||
}; | ||
exports.FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])'; | ||
function asFed2SubgraphDocument(document, options) { | ||
var _a, _b; | ||
const directiveToAdd = ({ | ||
kind: graphql_1.Kind.DIRECTIVE, | ||
name: { kind: graphql_1.Kind.NAME, value: coreSpec_1.linkDirectiveDefaultName }, | ||
arguments: [ | ||
{ | ||
kind: graphql_1.Kind.ARGUMENT, | ||
name: { kind: graphql_1.Kind.NAME, value: 'url' }, | ||
value: { kind: graphql_1.Kind.STRING, value: federationSpec.url.toString() } | ||
}, | ||
{ | ||
kind: graphql_1.Kind.ARGUMENT, | ||
name: { kind: graphql_1.Kind.NAME, value: 'import' }, | ||
value: { kind: graphql_1.Kind.LIST, values: federationSpec.directiveSpecs().map((spec) => ({ kind: graphql_1.Kind.STRING, value: `@${spec.name}` })) } | ||
} | ||
] | ||
}); | ||
if ((_a = options === null || options === void 0 ? void 0 : options.addAsSchemaExtension) !== null && _a !== void 0 ? _a : true) { | ||
return { | ||
kind: graphql_1.Kind.DOCUMENT, | ||
loc: document.loc, | ||
definitions: document.definitions.concat({ | ||
kind: graphql_1.Kind.SCHEMA_EXTENSION, | ||
directives: [directiveToAdd] | ||
}), | ||
}; | ||
} | ||
const existingSchemaDefinition = document.definitions.find((d) => d.kind == graphql_1.Kind.SCHEMA_DEFINITION); | ||
if (existingSchemaDefinition) { | ||
return { | ||
kind: graphql_1.Kind.DOCUMENT, | ||
loc: document.loc, | ||
definitions: document.definitions.filter((d) => d !== existingSchemaDefinition).concat([{ | ||
...existingSchemaDefinition, | ||
directives: [directiveToAdd].concat((_b = existingSchemaDefinition.directives) !== null && _b !== void 0 ? _b : []), | ||
}]), | ||
}; | ||
} | ||
else { | ||
const hasMutation = document.definitions.some((d) => d.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && d.name.value === 'Mutation'); | ||
const makeOpType = (opType, name) => ({ | ||
kind: graphql_1.Kind.OPERATION_TYPE_DEFINITION, | ||
operation: opType, | ||
type: { | ||
kind: graphql_1.Kind.NAMED_TYPE, | ||
name: { | ||
kind: graphql_1.Kind.NAME, | ||
value: name, | ||
} | ||
}, | ||
}); | ||
return { | ||
kind: graphql_1.Kind.DOCUMENT, | ||
loc: document.loc, | ||
definitions: document.definitions.concat({ | ||
kind: graphql_1.Kind.SCHEMA_DEFINITION, | ||
directives: [directiveToAdd], | ||
operationTypes: [makeOpType(graphql_1.OperationTypeNode.QUERY, 'Query')].concat(hasMutation ? makeOpType(graphql_1.OperationTypeNode.MUTATION, 'Mutation') : []), | ||
}), | ||
}; | ||
} | ||
} | ||
@@ -747,3 +818,3 @@ exports.asFed2SubgraphDocument = asFed2SubgraphDocument; | ||
function isEntityType(type) { | ||
if (type.kind !== "ObjectType") { | ||
if (!(0, definitions_1.isObjectType)(type) && !(0, definitions_1.isInterfaceType)(type)) { | ||
return false; | ||
@@ -755,2 +826,10 @@ } | ||
exports.isEntityType = isEntityType; | ||
function isInterfaceObjectType(type) { | ||
if (!(0, definitions_1.isObjectType)(type)) { | ||
return false; | ||
} | ||
const metadata = federationMetadata(type.schema()); | ||
return !!metadata && metadata.isInterfaceObjectType(type); | ||
} | ||
exports.isInterfaceObjectType = isInterfaceObjectType; | ||
function buildSubgraph(name, url, source, withRootTypeRenaming = true) { | ||
@@ -757,0 +836,0 @@ const buildOptions = { |
@@ -19,3 +19,4 @@ import { Schema } from "./definitions"; | ||
INACCESSIBLE = "inaccessible", | ||
COMPOSE_DIRECTIVE = "composeDirective" | ||
COMPOSE_DIRECTIVE = "composeDirective", | ||
INTERFACE_OBJECT = "interfaceObject" | ||
} | ||
@@ -22,0 +23,0 @@ export declare const FEDERATION1_TYPES: TypeSpecification[]; |
@@ -30,2 +30,3 @@ "use strict"; | ||
FederationDirectiveName["COMPOSE_DIRECTIVE"] = "composeDirective"; | ||
FederationDirectiveName["INTERFACE_OBJECT"] = "interfaceObject"; | ||
})(FederationDirectiveName = exports.FederationDirectiveName || (exports.FederationDirectiveName = {})); | ||
@@ -130,2 +131,8 @@ const fieldSetTypeSpec = (0, directiveAndTypeSpecification_1.createScalarTypeSpecification)({ name: FederationTypeName.FIELD_SET }); | ||
} | ||
if (version >= (new coreSpec_1.FeatureVersion(2, 3))) { | ||
this.registerDirective((0, directiveAndTypeSpecification_1.createDirectiveSpecification)({ | ||
name: FederationDirectiveName.INTERFACE_OBJECT, | ||
locations: [graphql_1.DirectiveLocation.OBJECT], | ||
})); | ||
} | ||
} | ||
@@ -165,4 +172,5 @@ registerDirective(spec) { | ||
.add(new FederationSpecDefinition(new coreSpec_1.FeatureVersion(2, 1))) | ||
.add(new FederationSpecDefinition(new coreSpec_1.FeatureVersion(2, 2))); | ||
.add(new FederationSpecDefinition(new coreSpec_1.FeatureVersion(2, 2))) | ||
.add(new FederationSpecDefinition(new coreSpec_1.FeatureVersion(2, 3))); | ||
(0, knownCoreFeatures_1.registerKnownFeature)(exports.FEDERATION_VERSIONS); | ||
//# sourceMappingURL=federationSpec.js.map |
@@ -23,2 +23,3 @@ import { GraphQLError } from 'graphql'; | ||
resolvable?: boolean; | ||
isInterfaceObject?: boolean; | ||
}>; | ||
@@ -30,3 +31,3 @@ implementsDirective(schema: Schema): DirectiveDefinition<{ | ||
fieldDirective(schema: Schema): DirectiveDefinition<{ | ||
graph: string; | ||
graph?: string; | ||
requires?: string; | ||
@@ -39,2 +40,9 @@ provides?: string; | ||
}>; | ||
unionMemberDirective(schema: Schema): DirectiveDefinition<{ | ||
graph: string; | ||
member: string; | ||
}> | undefined; | ||
enumValueDirective(schema: Schema): DirectiveDefinition<{ | ||
graph: string; | ||
}> | undefined; | ||
ownerDirective(schema: Schema): DirectiveDefinition<{ | ||
@@ -41,0 +49,0 @@ graph: string; |
@@ -43,6 +43,12 @@ "use strict"; | ||
joinType.addArgument('resolvable', new definitions_1.NonNullType(schema.booleanType()), true); | ||
if (this.version >= (new coreSpec_1.FeatureVersion(0, 3))) { | ||
joinType.addArgument('isInterfaceObject', new definitions_1.NonNullType(schema.booleanType()), false); | ||
} | ||
} | ||
const joinField = this.addDirective(schema, 'field').addLocations(graphql_1.DirectiveLocation.FIELD_DEFINITION, graphql_1.DirectiveLocation.INPUT_FIELD_DEFINITION); | ||
joinField.repeatable = true; | ||
joinField.addArgument('graph', new definitions_1.NonNullType(graphEnum)); | ||
const graphArgType = this.version >= (new coreSpec_1.FeatureVersion(0, 3)) | ||
? graphEnum | ||
: new definitions_1.NonNullType(graphEnum); | ||
joinField.addArgument('graph', graphArgType); | ||
joinField.addArgument('requires', joinFieldSet); | ||
@@ -62,2 +68,11 @@ joinField.addArgument('provides', joinFieldSet); | ||
} | ||
if (this.version >= (new coreSpec_1.FeatureVersion(0, 3))) { | ||
const joinUnionMember = this.addDirective(schema, 'unionMember').addLocations(graphql_1.DirectiveLocation.UNION); | ||
joinUnionMember.repeatable = true; | ||
joinUnionMember.addArgument('graph', new definitions_1.NonNullType(graphEnum)); | ||
joinUnionMember.addArgument('member', new definitions_1.NonNullType(schema.stringType())); | ||
const joinEnumValue = this.addDirective(schema, 'enumValue').addLocations(graphql_1.DirectiveLocation.ENUM_VALUE); | ||
joinEnumValue.repeatable = true; | ||
joinEnumValue.addArgument('graph', new definitions_1.NonNullType(graphEnum)); | ||
} | ||
if (this.isV01()) { | ||
@@ -128,2 +143,8 @@ const joinOwner = this.addDirective(schema, 'owner').addLocations(graphql_1.DirectiveLocation.OBJECT); | ||
} | ||
unionMemberDirective(schema) { | ||
return this.directive(schema, 'unionMember'); | ||
} | ||
enumValueDirective(schema) { | ||
return this.directive(schema, 'enumValue'); | ||
} | ||
ownerDirective(schema) { | ||
@@ -139,4 +160,5 @@ return this.directive(schema, 'owner'); | ||
.add(new JoinSpecDefinition(new coreSpec_1.FeatureVersion(0, 1))) | ||
.add(new JoinSpecDefinition(new coreSpec_1.FeatureVersion(0, 2))); | ||
.add(new JoinSpecDefinition(new coreSpec_1.FeatureVersion(0, 2))) | ||
.add(new JoinSpecDefinition(new coreSpec_1.FeatureVersion(0, 3))); | ||
(0, knownCoreFeatures_1.registerKnownFeature)(exports.JOIN_VERSIONS); | ||
//# sourceMappingURL=joinSpec.js.map |
@@ -29,2 +29,3 @@ import { DocumentNode, FieldNode, FragmentDefinitionNode, SelectionNode, SelectionSetNode } from "graphql"; | ||
withUpdatedDefinition(newDefinition: FieldDefinition<any>): Field<TArgs>; | ||
withUpdatedAlias(newAlias: string | undefined): Field<TArgs>; | ||
appliesTo(type: ObjectType | InterfaceType): boolean; | ||
@@ -48,2 +49,4 @@ selects(definition: FieldDefinition<any>, assumeValid?: boolean): boolean; | ||
withUpdatedSourceType(newSourceType: CompositeType): FragmentElement; | ||
withUpdatedCondition(newCondition: CompositeType | undefined): FragmentElement; | ||
withUpdatedTypes(newSourceType: CompositeType, newCondition: CompositeType | undefined): FragmentElement; | ||
updateForAddingTo(selectionSet: SelectionSet): FragmentElement; | ||
@@ -146,2 +149,7 @@ hasDefer(): boolean; | ||
selections(reversedOrder?: boolean): readonly Selection[]; | ||
fieldsInSet(): { | ||
path: string[]; | ||
field: FieldSelection; | ||
directParent: SelectionSet; | ||
}[]; | ||
usedVariables(): Variables; | ||
@@ -160,2 +168,3 @@ collectUsedFragmentNames(collector: Map<string, number>): void; | ||
add(selection: Selection): Selection; | ||
removeTopLevelField(responseName: string): boolean; | ||
addPath(path: OperationPath, onPathEnd?: (finalSelectionSet: SelectionSet | undefined) => void): void; | ||
@@ -187,2 +196,3 @@ addSelectionSetNode(node: SelectionSetNode | undefined, variableDefinitions: VariableDefinitions, fieldAccessor?: (type: CompositeType, fieldName: string) => FieldDefinition<any> | undefined): void; | ||
constructor(field: Field<any>, initialSelectionSet?: SelectionSet); | ||
get parentType(): CompositeType; | ||
protected us(): FieldSelection; | ||
@@ -202,2 +212,3 @@ key(): string; | ||
withUpdatedSubSelection(newSubSelection: SelectionSet | undefined): FieldSelection; | ||
withUpdatedField(newField: Field<any>): FieldSelection; | ||
equals(that: Selection): boolean; | ||
@@ -226,2 +237,3 @@ contains(that: Selection): boolean; | ||
abstract withUpdatedSubSelection(newSubSelection: SelectionSet | undefined): FragmentSelection; | ||
get parentType(): CompositeType; | ||
protected us(): FragmentSelection; | ||
@@ -228,0 +240,0 @@ protected validateDeferAndStream(): void; |
@@ -179,5 +179,9 @@ "use strict"; | ||
let errors = []; | ||
const subgraphsUsingInterfaceObject = []; | ||
for (const subgraph of inputs.values()) { | ||
if (subgraph.isFed2Subgraph()) { | ||
subgraphs.add(subgraph); | ||
if (subgraph.metadata().interfaceObjectDirective().applications().length > 0) { | ||
subgraphsUsingInterfaceObject.push(subgraph.name); | ||
} | ||
} | ||
@@ -196,2 +200,7 @@ else { | ||
} | ||
if (errors.length === 0 && subgraphsUsingInterfaceObject.length > 0) { | ||
const fed1Subgraphs = inputs.values().filter((s) => !s.isFed2Subgraph()).map((s) => s.name); | ||
errors = [error_1.ERRORS.INTERFACE_OBJECT_USAGE_ERROR.err('The @interfaceObject directive can only be used if all subgraphs have federation 2 subgraph schema (schema with a `@link` to "https://specs.apollo.dev/federation" version 2.0 or newer): ' | ||
+ `@interfaceObject is used in ${(0, federation_1.printSubgraphNames)(subgraphsUsingInterfaceObject)} but ${(0, federation_1.printSubgraphNames)(fed1Subgraphs)} ${fed1Subgraphs.length > 1 ? 'are not' : 'is not a'} federation 2 subgraph schema.`)]; | ||
} | ||
return errors.length === 0 ? { subgraphs, changes } : { errors }; | ||
@@ -198,0 +207,0 @@ } |
@@ -15,2 +15,3 @@ "use strict"; | ||
'https://specs.apollo.dev/join/v0.2', | ||
'https://specs.apollo.dev/join/v0.3', | ||
'https://specs.apollo.dev/tag/v0.1', | ||
@@ -17,0 +18,0 @@ 'https://specs.apollo.dev/tag/v0.2', |
{ | ||
"name": "@apollo/federation-internals", | ||
"version": "2.2.2", | ||
"version": "2.3.0-alpha.0", | ||
"description": "Apollo Federation internal utilities", | ||
@@ -35,3 +35,3 @@ "main": "dist/index.js", | ||
}, | ||
"gitHead": "33f401239ce457c2dd840f38e9c3aa9795cc1c14" | ||
"gitHead": "b723008e72e58277bb366eb176b9f06e1949eff8" | ||
} |
@@ -599,3 +599,2 @@ import { buildSupergraphSchema, extractSubgraphsFromSupergraph, InputObjectType } from ".."; | ||
test('throw meaningful error for invalid federation directive fieldSet', () => { | ||
@@ -602,0 +601,0 @@ const supergraph = ` |
@@ -205,1 +205,44 @@ import { FEDERATION2_LINK_WITH_FULL_IMPORTS } from '..'; | ||
}) | ||
test('reject @interfaceObject usage if not all subgraphs are fed2', () => { | ||
// Note that this test both validates the rejection of fed1 subgraph when @interfaceObject is used somewhere, but also | ||
// illustrate why we do so: fed1 schema can use @key on interface for backward compatibility, but it is ignored and | ||
// the schema upgrader removes them. Given that actual support for @key on interfaces is necesarry to make @interfaceObject | ||
// work, it would be really confusing to not reject the example below right away, since it "looks" like it the @key on | ||
// the interface in the 2nd subgraph should work, but it actually won't. | ||
const s1 = ` | ||
extend schema | ||
@link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key", "@interfaceObject"]) | ||
type Query { | ||
a: A | ||
} | ||
type A @key(fields: "id") @interfaceObject { | ||
id: String | ||
x: Int | ||
} | ||
`; | ||
const s2 = ` | ||
interface A @key(fields: "id") { | ||
id: String | ||
y: Int | ||
} | ||
type X implements A @key(fields: "id") { | ||
id: String | ||
y: Int | ||
} | ||
`; | ||
const subgraphs = new Subgraphs(); | ||
subgraphs.add(buildSubgraph('s1', 'http://s1', s1)); | ||
subgraphs.add(buildSubgraph('s2', 'http://s2', s2)); | ||
const res = upgradeSubgraphsIfNecessary(subgraphs); | ||
expect(res.errors?.map((e) => e.message)).toStrictEqual([ | ||
'The @interfaceObject directive can only be used if all subgraphs have federation 2 subgraph schema (schema with a `@link` to "https://specs.apollo.dev/federation" version 2.0 or newer): ' | ||
+ '@interfaceObject is used in subgraph "s1" but subgraph "s2" is not a federation 2 subgraph schema.' | ||
]); | ||
}) |
import { DocumentNode } from 'graphql'; | ||
import gql from 'graphql-tag'; | ||
import { Subgraph, errorCauses } from '..'; | ||
import { asFed2SubgraphDocument, buildSubgraph } from "../federation" | ||
import { Subgraph } from '..'; | ||
import { buildSubgraph } from "../federation" | ||
import { defaultPrintOptions, printSchema } from '../print'; | ||
import './matchers'; | ||
import { buildForErrors } from './testUtils'; | ||
// Builds the provided subgraph (using name 'S' for the subgraph) and, if the | ||
// subgraph is invalid/has errors, return those errors as a list of [code, message]. | ||
// If the subgraph is valid, return undefined. | ||
export function buildForErrors( | ||
subgraphDefs: DocumentNode, | ||
options?: { | ||
subgraphName?: string, | ||
asFed2?: boolean, | ||
} | ||
): [string, string][] | undefined { | ||
try { | ||
const doc = (options?.asFed2 ?? true) ? asFed2SubgraphDocument(subgraphDefs) : subgraphDefs; | ||
const name = options?.subgraphName ?? 'S'; | ||
buildSubgraph(name, `http://${name}`, doc).validate(); | ||
return undefined; | ||
} catch (e) { | ||
const causes = errorCauses(e); | ||
if (!causes) { | ||
throw e; | ||
} | ||
return causes.map((err) => [err.extensions.code as string, err.message]); | ||
} | ||
} | ||
describe('fieldset-based directives', () => { | ||
@@ -94,4 +70,7 @@ it('rejects field defined with arguments in @key', () => { | ||
it('rejects @key on interfaces', () => { | ||
it.each(['2.0', '2.1', '2.2'])('rejects @key on interfaces _in the %p spec_', (version) => { | ||
const subgraph = gql` | ||
extend schema | ||
@link(url: "https://specs.apollo.dev/federation/v${version}", import: ["@key"]) | ||
type Query { | ||
@@ -105,3 +84,3 @@ t: T | ||
` | ||
expect(buildForErrors(subgraph)).toStrictEqual([ | ||
expect(buildForErrors(subgraph, { asFed2: false })).toStrictEqual([ | ||
['KEY_UNSUPPORTED_ON_INTERFACE', '[S] Cannot use @key on interface "T": @key is not yet supported on interfaces'], | ||
@@ -564,30 +543,2 @@ ]); | ||
it('validates all implementations of interface field have same type if any has @external', () => { | ||
const subgraph = gql` | ||
type Query { | ||
is: [I!]! | ||
} | ||
interface I { | ||
f: Int | ||
} | ||
type T1 implements I { | ||
f: Int | ||
} | ||
type T2 implements I { | ||
f: Int! | ||
} | ||
type T3 implements I { | ||
id: ID! | ||
f: Int @external | ||
} | ||
`; | ||
expect(buildForErrors(subgraph)).toStrictEqual([ | ||
['INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH', '[S] Some of the runtime implementations of interface field "I.f" are marked @external or have a @require ("T3.f") so all the implementations should use the same type (a current limitation of federation; see https://github.com/apollographql/federation/issues/1257), but "T1.f" and "T3.f" have type "Int" while "T2.f" has type "Int!".'], | ||
]); | ||
}) | ||
describe('custom error message for misnamed directives', () => { | ||
@@ -1189,1 +1140,119 @@ it.each([ | ||
}); | ||
describe('@interfaceObject/@key on interfaces validation', () => { | ||
it('@key on interfaces require @key on all implementations', () => { | ||
const doc = gql` | ||
interface I @key(fields: "id1") @key(fields: "id2") { | ||
id1: ID! | ||
id2: ID! | ||
} | ||
type A implements I @key(fields: "id2") { | ||
id1: ID! | ||
id2: ID! | ||
a: Int | ||
} | ||
type B implements I @key(fields: "id1") @key(fields: "id2") { | ||
id1: ID! | ||
id2: ID! | ||
b: Int | ||
} | ||
type C implements I @key(fields: "id2") { | ||
id1: ID! | ||
id2: ID! | ||
c: Int | ||
} | ||
`; | ||
expect(buildForErrors(doc)).toStrictEqual([[ | ||
'INTERFACE_KEY_NOT_ON_IMPLEMENTATION', | ||
'[S] Key @key(fields: "id1") on interface type "I" is missing on implementation types "A" and "C".', | ||
]]); | ||
}); | ||
it('@key on interfaces with @key on some implementation non resolvable', () => { | ||
const doc = gql` | ||
interface I @key(fields: "id1") { | ||
id1: ID! | ||
} | ||
type A implements I @key(fields: "id1") { | ||
id1: ID! | ||
a: Int | ||
} | ||
type B implements I @key(fields: "id1") { | ||
id1: ID! | ||
b: Int | ||
} | ||
type C implements I @key(fields: "id1", resolvable: false) { | ||
id1: ID! | ||
c: Int | ||
} | ||
`; | ||
expect(buildForErrors(doc)).toStrictEqual([[ | ||
'INTERFACE_KEY_NOT_ON_IMPLEMENTATION', | ||
'[S] Key @key(fields: "id1") on interface type "I" should be resolvable on all implementation types, but is declared with argument "@key(resolvable:)" set to false in type "C".', | ||
]]); | ||
}); | ||
it('ensures order of fields in key does not matter', () => { | ||
const doc = gql` | ||
interface I @key(fields: "a b c") { | ||
a: Int | ||
b: Int | ||
c: Int | ||
} | ||
type A implements I @key(fields: "c b a") { | ||
a: Int | ||
b: Int | ||
c: Int | ||
} | ||
type B implements I @key(fields: "a c b") { | ||
a: Int | ||
b: Int | ||
c: Int | ||
} | ||
type C implements I @key(fields: "a b c") { | ||
a: Int | ||
b: Int | ||
c: Int | ||
} | ||
`; | ||
expect(buildForErrors(doc)).toBeUndefined(); | ||
}); | ||
// There is no meaningful way to make @interfaceObject work on a value type at the moment, because | ||
// if you have an @interfaceObject, some other subgraph needs to be able to resolve the concrete | ||
// type, and that imply that you have key to go to that other subgraph. | ||
// To be clear, the @key on the @interfaceObject technically con't need to be "resolvable", and the | ||
// difference between no key and a non-resolvable key is arguably more convention than a genuine | ||
// mechanical difference at the moment, but still a good idea to rely on that convention to help | ||
// catching obvious mistakes early. | ||
it('only allow @interfaceObject on entity types', () => { | ||
const doc = gql` | ||
# This one shouldn't raise an error | ||
type A @key(fields: "id", resolvable: false) @interfaceObject { | ||
id: ID! | ||
} | ||
# This one should | ||
type B @interfaceObject { | ||
x: Int | ||
} | ||
`; | ||
expect(buildForErrors(doc)).toStrictEqual([[ | ||
'INTERFACE_OBJECT_USAGE_ERROR', | ||
'[S] The @interfaceObject directive can only be applied to entity types but type "B" has no @key in this subgraph.' | ||
]]); | ||
}); | ||
}); |
@@ -6,6 +6,6 @@ import { | ||
import { parseOperation } from '../operations'; | ||
import { buildForErrors } from './subgraphValidation.test'; | ||
import gql from 'graphql-tag'; | ||
import { printSchema } from '../print'; | ||
import { valueEquals } from '../values'; | ||
import { buildForErrors } from './testUtils'; | ||
@@ -12,0 +12,0 @@ function parseSchema(schema: string): Schema { |
@@ -238,3 +238,3 @@ import { ASTNode, GraphQLError, GraphQLErrorOptions, GraphQLFormattedError } from "graphql"; | ||
'UNSUPPORTED_ON_INTERFACE', | ||
(directive) => `A \`@${directive}\` directive is used on an interface, which is not (yet) supported.`, | ||
(directive) => `A \`@${directive}\` directive is used on an interface, which is ${directive === 'key' ? 'only supported when @linking to federation 2.3+' : 'not (yet) supported'}.`, | ||
); | ||
@@ -399,7 +399,2 @@ | ||
const INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH = makeCodeDefinition( | ||
'INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH', | ||
'For an interface field, some of its concrete implementations have @external or @requires and there is difference in those implementations return type (which is currently not supported; see https://github.com/apollographql/federation/issues/1257)' | ||
); | ||
const INVALID_FIELD_SHARING = makeCodeDefinition( | ||
@@ -536,2 +531,21 @@ 'INVALID_FIELD_SHARING', | ||
const INTERFACE_OBJECT_USAGE_ERROR = makeCodeDefinition( | ||
'INTERFACE_OBJECT_USAGE_ERROR', | ||
'Error in the usage of the @interfaceObject directive.', | ||
{ addedIn: '2.3.0' }, | ||
); | ||
const INTERFACE_KEY_NOT_ON_IMPLEMENTATION = makeCodeDefinition( | ||
'INTERFACE_KEY_NOT_ON_IMPLEMENTATION', | ||
'A `@key` is defined on an interface type, but is not defined (or is not resolvable) on at least one of the interface implementations', | ||
{ addedIn: '2.3.0' }, | ||
); | ||
const INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE = makeCodeDefinition( | ||
'INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE', | ||
'A subgraph has a `@key` on an interface type, but that subgraph does not define an implementation (in the supergraph) of that interface', | ||
{ addedIn: '2.3.0' }, | ||
) | ||
export const ERROR_CATEGORIES = { | ||
@@ -591,3 +605,2 @@ DIRECTIVE_FIELDS_MISSING_EXTERNAL, | ||
EXTERNAL_MISSING_ON_BASE, | ||
INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH, | ||
INVALID_FIELD_SHARING, | ||
@@ -621,2 +634,5 @@ INVALID_SHAREABLE_USAGE, | ||
DIRECTIVE_COMPOSITION_ERROR, | ||
INTERFACE_OBJECT_USAGE_ERROR, | ||
INTERFACE_KEY_NOT_ON_IMPLEMENTATION, | ||
INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE, | ||
}; | ||
@@ -646,14 +662,16 @@ | ||
['DUPLICATE_SCALAR_DEFINITION', 'As duplicate scalar definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`'], | ||
['DUPLICATE_ENUM_DEFINITION', 'As duplicate enum definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`'], | ||
['DUPLICATE_ENUM_VALUE', 'As duplicate enum values is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`'], | ||
['DUPLICATE_SCALAR_DEFINITION', 'As duplicate scalar definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'], | ||
['DUPLICATE_ENUM_DEFINITION', 'As duplicate enum definitions is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'], | ||
['DUPLICATE_ENUM_VALUE', 'As duplicate enum values is invalid GraphQL, this will now be an error with code `INVALID_GRAPHQL`.'], | ||
['ENUM_MISMATCH', 'Subgraph definitions for an enum are now merged by composition'], | ||
['ENUM_MISMATCH', 'Subgraph definitions for an enum are now merged by composition.'], | ||
['VALUE_TYPE_NO_ENTITY', 'There is no strong different between entity and value types in the model (they are just usage pattern) and a type can have keys in one subgraph but not another.'], | ||
['VALUE_TYPE_UNION_TYPES_MISMATCH', 'Subgraph definitions for an union are now merged by composition'], | ||
['PROVIDES_FIELDS_SELECT_INVALID_TYPE', '@provides can now be used on field of interface, union and list types'], | ||
['RESERVED_FIELD_USED', 'This error was previously not correctly enforced: the _service and _entities, if present, were overridden; this is still the case'], | ||
['VALUE_TYPE_UNION_TYPES_MISMATCH', 'Subgraph definitions for an union are now merged by composition.'], | ||
['PROVIDES_FIELDS_SELECT_INVALID_TYPE', '@provides can now be used on field of interface, union and list types.'], | ||
['RESERVED_FIELD_USED', 'This error was previously not correctly enforced: the _service and _entities, if present, were overridden; this is still the case.'], | ||
['NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH', 'Since federation 2.1.0, the case this error used to cover is now a warning (with code `INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS`) instead of an error'], | ||
['REQUIRES_FIELDS_HAS_ARGS', 'Since federation 2.1.1, using fields with arguments in a @requires is fully supported'], | ||
['NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH', 'Since federation 2.1.0, the case this error used to cover is now a warning (with code `INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS`) instead of an error.'], | ||
['REQUIRES_FIELDS_HAS_ARGS', 'Since federation 2.1.1, using fields with arguments in a @requires is fully supported.'], | ||
['INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH', 'This error was thrown by a validation introduced to avoid running into a known runtime bug. Since federation 2.3, the underlying runtime bug has been addressed and the validation/limitation was no longer necessary and has been removed.'], | ||
]; |
@@ -204,4 +204,9 @@ import { | ||
const fieldDirective = joinSpec.fieldDirective(supergraph); | ||
const unionMemberDirective = joinSpec.unionMemberDirective(supergraph); | ||
const enumValueDirective = joinSpec.enumValueDirective(supergraph); | ||
const getSubgraph = (application: Directive<any, { graph: string }>) => graphEnumNameToSubgraphName.get(application.arguments().graph); | ||
const getSubgraph = (application: Directive<any, { graph?: string }>) => { | ||
const graph = application.arguments().graph; | ||
return graph ? graphEnumNameToSubgraphName.get(graph) : undefined; | ||
}; | ||
@@ -225,3 +230,3 @@ /* | ||
(f, name) => { | ||
const fieldApplications: Directive<any, { graph: string, requires?: string, provides?: string }>[] = f.appliedDirectivesOf(fieldDirective); | ||
const fieldApplications: Directive<any, { graph?: string, requires?: string, provides?: string }>[] = f.appliedDirectivesOf(fieldDirective); | ||
if (fieldApplications.length) { | ||
@@ -279,3 +284,7 @@ const application = fieldApplications.find((application) => getSubgraph(application) === name); | ||
if (!subgraphType) { | ||
subgraphType = schema.addType(newNamedType(type.kind, type.name)); | ||
const kind = args.isInterfaceObject ? 'ObjectType' : type.kind; | ||
subgraphType = schema.addType(newNamedType(kind, type.name)); | ||
if (args.isInterfaceObject) { | ||
subgraphType.applyDirective('interfaceObject'); | ||
} | ||
} | ||
@@ -359,2 +368,7 @@ if (args.key) { | ||
const args = application.arguments(); | ||
// We use a @join__field with no graph to indicates when a field in the supergraph does not come | ||
// directly from any subgraph and there is thus nothing to do to "extract" it. | ||
if (!args.graph) { | ||
continue; | ||
} | ||
const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(args.graph)!)!; | ||
@@ -401,4 +415,13 @@ const subgraphField = addSubgraphField(field, subgraph, args.type); | ||
assert(isEnumType(subgraphEnum), () => `${subgraphEnum} should be an enum but found a ${subgraphEnum.kind}`); | ||
for (const value of type.values) { | ||
subgraphEnum.addValue(value.name); | ||
// Before version 0.3 of the join spec (before `enumValueDirective`), we were not recording which subgraph defined which values, | ||
// and instead aded all values to all subgraphs (at least if the type existed there). | ||
const addValue = !enumValueDirective | ||
|| value.appliedDirectivesOf(enumValueDirective).some((d) => | ||
graphEnumNameToSubgraphName.get(d.arguments().graph) === subgraph.name | ||
); | ||
if (addValue) { | ||
subgraphEnum.addValue(value.name); | ||
} | ||
} | ||
@@ -408,5 +431,2 @@ } | ||
case 'UnionType': | ||
// TODO: Same as for enums. We need to know in which subgraph each member is defined. | ||
// But for now, we also add every members to all subgraphs (as long as the subgraph has both the union type | ||
// and the member in question). | ||
for (const subgraph of subgraphs) { | ||
@@ -418,4 +438,15 @@ const subgraphUnion = subgraph.schema.type(type.name); | ||
assert(isUnionType(subgraphUnion), () => `${subgraphUnion} should be an enum but found a ${subgraphUnion.kind}`); | ||
for (const memberType of type.types()) { | ||
const subgraphType = subgraph.schema.type(memberType.name); | ||
let membersInSubgraph: string[]; | ||
if (unionMemberDirective) { | ||
membersInSubgraph = type | ||
.appliedDirectivesOf(unionMemberDirective) | ||
.filter((d) => graphEnumNameToSubgraphName.get(d.arguments().graph) === subgraph.name) | ||
.map((d) => d.arguments().member); | ||
} else { | ||
// Before version 0.3 of the join spec, we were not recording which subgraph defined which members, | ||
// and instead aded all members to all subgraphs (at least if the type existed there). | ||
membersInSubgraph = type.types().map((t) => t.name); | ||
} | ||
for (const memberTypeName of membersInSubgraph) { | ||
const subgraphType = subgraph.schema.type(memberTypeName); | ||
if (subgraphType) { | ||
@@ -422,0 +453,0 @@ subgraphUnion.addType(subgraphType as ObjectType); |
@@ -38,2 +38,3 @@ import { | ||
COMPOSE_DIRECTIVE = 'composeDirective', | ||
INTERFACE_OBJECT = 'interfaceObject', | ||
} | ||
@@ -157,2 +158,9 @@ | ||
} | ||
if (version >= (new FeatureVersion(2, 3))) { | ||
this.registerDirective(createDirectiveSpecification({ | ||
name: FederationDirectiveName.INTERFACE_OBJECT, | ||
locations: [DirectiveLocation.OBJECT], | ||
})); | ||
} | ||
} | ||
@@ -200,4 +208,5 @@ | ||
.add(new FederationSpecDefinition(new FeatureVersion(2, 1))) | ||
.add(new FederationSpecDefinition(new FeatureVersion(2, 2))); | ||
.add(new FederationSpecDefinition(new FeatureVersion(2, 2))) | ||
.add(new FederationSpecDefinition(new FeatureVersion(2, 3))); | ||
registerKnownFeature(FEDERATION_VERSIONS); |
@@ -67,2 +67,6 @@ import { DirectiveLocation, GraphQLError } from 'graphql'; | ||
joinType.addArgument('resolvable', new NonNullType(schema.booleanType()), true); | ||
if (this.version >= (new FeatureVersion(0, 3))) { | ||
joinType.addArgument('isInterfaceObject', new NonNullType(schema.booleanType()), false); | ||
} | ||
} | ||
@@ -72,3 +76,9 @@ | ||
joinField.repeatable = true; | ||
joinField.addArgument('graph', new NonNullType(graphEnum)); | ||
// The `graph` argument used to be non-nullable, but @interfaceObject makes us add some field in | ||
// the supergraph that don't "directly" come from any subgraph (they indirectly are inherited from | ||
// an `@interfaceObject` type), and to indicate that, we use a `@join__field(graph: null)` annotation. | ||
const graphArgType = this.version >= (new FeatureVersion(0, 3)) | ||
? graphEnum | ||
: new NonNullType(graphEnum); | ||
joinField.addArgument('graph', graphArgType); | ||
joinField.addArgument('requires', joinFieldSet); | ||
@@ -92,2 +102,13 @@ joinField.addArgument('provides', joinFieldSet); | ||
if (this.version >= (new FeatureVersion(0, 3))) { | ||
const joinUnionMember = this.addDirective(schema, 'unionMember').addLocations(DirectiveLocation.UNION); | ||
joinUnionMember.repeatable = true; | ||
joinUnionMember.addArgument('graph', new NonNullType(graphEnum)); | ||
joinUnionMember.addArgument('member', new NonNullType(schema.stringType())); | ||
const joinEnumValue = this.addDirective(schema, 'enumValue').addLocations(DirectiveLocation.ENUM_VALUE); | ||
joinEnumValue.repeatable = true; | ||
joinEnumValue.addArgument('graph', new NonNullType(graphEnum)); | ||
} | ||
if (this.isV01()) { | ||
@@ -159,3 +180,3 @@ const joinOwner = this.addDirective(schema, 'owner').addLocations(DirectiveLocation.OBJECT); | ||
typeDirective(schema: Schema): DirectiveDefinition<{graph: string, key?: string, extension?: boolean, resolvable?: boolean}> { | ||
typeDirective(schema: Schema): DirectiveDefinition<{graph: string, key?: string, extension?: boolean, resolvable?: boolean, isInterfaceObject?: boolean}> { | ||
return this.directive(schema, 'type')!; | ||
@@ -169,3 +190,3 @@ } | ||
fieldDirective(schema: Schema): DirectiveDefinition<{ | ||
graph: string, | ||
graph?: string, | ||
requires?: string, | ||
@@ -181,2 +202,10 @@ provides?: string, | ||
unionMemberDirective(schema: Schema): DirectiveDefinition<{graph: string, member: string}> | undefined { | ||
return this.directive(schema, 'unionMember'); | ||
} | ||
enumValueDirective(schema: Schema): DirectiveDefinition<{graph: string}> | undefined { | ||
return this.directive(schema, 'enumValue'); | ||
} | ||
ownerDirective(schema: Schema): DirectiveDefinition<{graph: string}> | undefined { | ||
@@ -191,13 +220,13 @@ return this.directive(schema, 'owner'); | ||
// Note: This declare a no-yet-agreed-upon join spec v0.2, that: | ||
// 1. allows a repeatable join__field (join-spec#15). | ||
// 2. allows the 'key' argument of join__type to be optional (join-spec#13) | ||
// 3. relax conditions on join__type in general so as to not relate to the notion of owner (join-spec#16). | ||
// 4. has join__implements (join-spec#13) | ||
// The changes from join-spec#17 and join-spec#18 are not yet implemented, but probably should be or we may have bugs | ||
// due to the query planner having an invalid understanding of the subgraph services API. | ||
// The versions are as follows: | ||
// - 0.1: this is the version used by federation 1 composition. Federation 2 is still able to read supergraphs | ||
// using that verison for backward compatibility, but never writes this spec version is not expressive enough | ||
// for federation 2 in general. | ||
// - 0.2: this is the original version released with federation 2. | ||
// - 0.3: adds the `isInterfaceObject` argument to `@join__type`, and make the `graph` in `@join__field` skippable. | ||
export const JOIN_VERSIONS = new FeatureDefinitions<JoinSpecDefinition>(joinIdentity) | ||
.add(new JoinSpecDefinition(new FeatureVersion(0, 1))) | ||
.add(new JoinSpecDefinition(new FeatureVersion(0, 2))); | ||
.add(new JoinSpecDefinition(new FeatureVersion(0, 2))) | ||
.add(new JoinSpecDefinition(new FeatureVersion(0, 3))); | ||
registerKnownFeature(JOIN_VERSIONS); |
@@ -232,5 +232,9 @@ import { | ||
let errors: GraphQLError[] = []; | ||
const subgraphsUsingInterfaceObject = []; | ||
for (const subgraph of inputs.values()) { | ||
if (subgraph.isFed2Subgraph()) { | ||
subgraphs.add(subgraph); | ||
if (subgraph.metadata().interfaceObjectDirective().applications().length > 0) { | ||
subgraphsUsingInterfaceObject.push(subgraph.name); | ||
} | ||
} else { | ||
@@ -247,2 +251,11 @@ const otherSubgraphs = inputs.values().filter((s) => s.name !== subgraph.name); | ||
} | ||
if (errors.length === 0 && subgraphsUsingInterfaceObject.length > 0) { | ||
const fed1Subgraphs = inputs.values().filter((s) => !s.isFed2Subgraph()).map((s) => s.name); | ||
// Note that we exit this method early if everything is a fed2 schema, so we know at least one of them wasn't. | ||
errors = [ ERRORS.INTERFACE_OBJECT_USAGE_ERROR.err( | ||
'The @interfaceObject directive can only be used if all subgraphs have federation 2 subgraph schema (schema with a `@link` to "https://specs.apollo.dev/federation" version 2.0 or newer): ' | ||
+ `@interfaceObject is used in ${printSubgraphNames(subgraphsUsingInterfaceObject)} but ${printSubgraphNames(fed1Subgraphs)} ${fed1Subgraphs.length > 1 ? 'are not' : 'is not a'} federation 2 subgraph schema.`, | ||
)]; | ||
} | ||
return errors.length === 0 ? { subgraphs, changes } : { errors }; | ||
@@ -249,0 +262,0 @@ } |
@@ -14,2 +14,3 @@ import { DocumentNode, GraphQLError } from "graphql"; | ||
'https://specs.apollo.dev/join/v0.2', | ||
'https://specs.apollo.dev/join/v0.3', | ||
'https://specs.apollo.dev/tag/v0.1', | ||
@@ -16,0 +17,0 @@ 'https://specs.apollo.dev/tag/v0.2', |
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 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 too big to display
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
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
2034291
170
34173
2