@apollo/federation-internals
Advanced tools
Comparing version 2.1.0-alpha.4 to 2.1.0
# CHANGELOG for `@apollo/federation-internals` | ||
## 2.1.0-alpha.4 | ||
## 2.1.0 | ||
- Update peer dependency `graphql` to `^16.5.0` to use `GraphQLErrorOptions` [PR #2060](https://github.com/apollographql/federation/pull/2060) | ||
## 2.1.0-alpha.3 | ||
- Don't require `@link` when using `@composeDirective` [PR #2046](https://github.com/apollographql/federation/pull/2046) | ||
- Add `@defer` support [PR #1958](https://github.com/apollographql/federation/pull/1958) | ||
## 2.1.0-alpha.2 | ||
- Add `@composeDirective` directive to specify directives that should be merged to the supergraph during composition [PR #1996](https://github.com/apollographql/federation/pull/1996). | ||
## 2.1.0-alpha.0 | ||
- Expand support for Node.js v18 [PR #1884](https://github.com/apollographql/federation/pull/1884) | ||
@@ -19,0 +10,0 @@ |
@@ -144,3 +144,3 @@ "use strict"; | ||
catch (e) { | ||
const causes = (0, definitions_1.errorCauses)(e); | ||
const causes = (0, error_1.errorCauses)(e); | ||
if (causes) { | ||
@@ -147,0 +147,0 @@ for (const cause of causes) { |
@@ -7,6 +7,3 @@ import { ASTNode, GraphQLError, StringValueNode } from "graphql"; | ||
export declare const linkDirectiveDefaultName = "link"; | ||
export declare const ErrCoreCheckFailed: (causes: Error[]) => import("@apollo/core-schema/dist/error").GraphQLErrorExt<"CheckFailed"> & { | ||
message: string; | ||
causes: Error[]; | ||
}; | ||
export declare const ErrCoreCheckFailed: (causes: GraphQLError[]) => GraphQLError; | ||
export declare const corePurposes: ("SECURITY" | "EXECUTION")[]; | ||
@@ -13,0 +10,0 @@ export declare type CorePurpose = typeof corePurposes[number]; |
@@ -8,3 +8,2 @@ "use strict"; | ||
const types_1 = require("./types"); | ||
const core_schema_1 = require("@apollo/core-schema"); | ||
const utils_1 = require("./utils"); | ||
@@ -19,6 +18,3 @@ const error_1 = require("./error"); | ||
exports.linkDirectiveDefaultName = 'link'; | ||
const ErrCoreCheckFailed = (causes) => (0, core_schema_1.err)('CheckFailed', { | ||
message: 'one or more checks failed', | ||
causes | ||
}); | ||
const ErrCoreCheckFailed = (causes) => (0, error_1.aggregateError)('CheckFailed', 'one or more checks failed', causes); | ||
exports.ErrCoreCheckFailed = ErrCoreCheckFailed; | ||
@@ -25,0 +21,0 @@ function buildError(message) { |
import { ConstArgumentNode, ASTNode, DirectiveLocation, ConstDirectiveNode, DocumentNode, GraphQLError, GraphQLSchema, Kind, TypeNode, VariableDefinitionNode, VariableNode, DirectiveDefinitionNode, DirectiveNode } from "graphql"; | ||
import { CoreImport, CoreSpecDefinition, FeatureUrl } from "./coreSpec"; | ||
import { MapWithCachedArrays } from "./utils"; | ||
import { GraphQLErrorExt } from "@apollo/core-schema/dist/error"; | ||
import { SDLValidationRule } from "graphql/validation/ValidationContext"; | ||
export declare const ErrGraphQLValidationFailed: (causes: GraphQLError[], message?: string) => GraphQLErrorExt<"GraphQLValidationFailed"> & { | ||
message: string; | ||
causes: GraphQLError[]; | ||
}; | ||
export declare const ErrGraphQLAPISchemaValidationFailed: (causes: GraphQLError[]) => GraphQLErrorExt<"GraphQLAPISchemaValidationFailed"> & { | ||
message: string; | ||
causes: GraphQLError[]; | ||
}; | ||
export declare function errorCauses(e: Error): GraphQLError[] | undefined; | ||
export declare function printGraphQLErrorsOrRethrow(e: Error): string; | ||
export declare function printErrors(errors: GraphQLError[]): string; | ||
export declare const ErrGraphQLValidationFailed: (causes: GraphQLError[], message?: string) => GraphQLError; | ||
export declare const ErrGraphQLAPISchemaValidationFailed: (causes: GraphQLError[]) => GraphQLError; | ||
export declare const typenameFieldName = "__typename"; | ||
@@ -79,3 +69,3 @@ export declare type QueryRootKind = 'query'; | ||
private readonly _schema; | ||
readonly appliedDirectives: Directive<T>[]; | ||
private _appliedDirectives; | ||
constructor(_schema: Schema); | ||
@@ -89,2 +79,3 @@ schema(): Schema; | ||
}>(definition: DirectiveDefinition<TApplicationArgs>): Directive<T, TApplicationArgs>[]; | ||
get appliedDirectives(): readonly Directive<T>[]; | ||
hasAppliedDirective(nameOrDefinition: string | DirectiveDefinition): boolean; | ||
@@ -127,4 +118,4 @@ applyDirective<TApplicationArgs extends { | ||
export declare abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>, TParent extends SchemaElement<any, any> | Schema> extends Element<TParent> { | ||
protected readonly _appliedDirectives: Directive<TOwnType>[]; | ||
protected _unappliedDirectives: UnappliedDirective[]; | ||
protected _appliedDirectives: Directive<TOwnType>[] | undefined; | ||
protected _unappliedDirectives: UnappliedDirective[] | undefined; | ||
description?: string; | ||
@@ -165,4 +156,4 @@ addUnappliedDirective({ nameOrDef, args, extension, directive }: UnappliedDirective): void; | ||
readonly isBuiltIn: boolean; | ||
protected readonly _referencers: Set<TReferencer>; | ||
protected readonly _extensions: Set<Extension<TOwnType>>; | ||
protected _referencers?: TReferencer[]; | ||
protected _extensions?: Extension<TOwnType>[]; | ||
preserveEmptyDefinition: boolean; | ||
@@ -174,3 +165,4 @@ constructor(name: string, isBuiltIn?: boolean); | ||
allChildElements(): Generator<NamedSchemaElement<any, TOwnType, any>, void, undefined>; | ||
extensions(): ReadonlySet<Extension<TOwnType>>; | ||
extensions(): readonly Extension<TOwnType>[]; | ||
hasExtension(extension: Extension<any>): boolean; | ||
newExtension(): Extension<TOwnType>; | ||
@@ -261,4 +253,8 @@ addExtension(extension: Extension<TOwnType>): Extension<TOwnType>; | ||
}; | ||
export declare type SchemaConfig = { | ||
cacheAST?: boolean; | ||
}; | ||
export declare class Schema { | ||
readonly blueprint: SchemaBlueprint; | ||
readonly config: SchemaConfig; | ||
private _schemaDefinition; | ||
@@ -274,3 +270,3 @@ private readonly _builtInTypes; | ||
private apiSchema?; | ||
constructor(blueprint?: SchemaBlueprint); | ||
constructor(blueprint?: SchemaBlueprint, config?: SchemaConfig); | ||
private canModifyBuiltIn; | ||
@@ -284,3 +280,2 @@ private runWithBuiltInModificationAllowed; | ||
private onModification; | ||
private forceSetCachedDocument; | ||
isCoreSchema(): boolean; | ||
@@ -293,2 +288,3 @@ get coreFeatures(): CoreFeatures | undefined; | ||
includeDefer?: boolean; | ||
includeStream?: boolean; | ||
}): GraphQLSchema; | ||
@@ -354,3 +350,3 @@ get schemaDefinition(): SchemaDefinition; | ||
protected readonly _roots: MapWithCachedArrays<SchemaRootKind, RootType>; | ||
protected readonly _extensions: Set<Extension<SchemaDefinition>>; | ||
protected _extensions: Extension<SchemaDefinition>[] | undefined; | ||
preserveEmptyDefinition: boolean; | ||
@@ -366,3 +362,4 @@ roots(): readonly RootType[]; | ||
setRoot(rootKind: SchemaRootKind, nameOrType: ObjectType | string): RootType; | ||
extensions(): ReadonlySet<Extension<SchemaDefinition>>; | ||
extensions(): Extension<SchemaDefinition>[]; | ||
hasExtension(extension: Extension<any>): boolean; | ||
newExtension(): Extension<SchemaDefinition>; | ||
@@ -392,3 +389,3 @@ addExtension(extension: Extension<SchemaDefinition>): Extension<SchemaDefinition>; | ||
declare abstract class FieldBasedType<T extends (ObjectType | InterfaceType) & NamedSchemaElement<T, Schema, R>, R> extends BaseNamedType<R, T> { | ||
private readonly _interfaceImplementations; | ||
private _interfaceImplementations; | ||
private readonly _fields; | ||
@@ -512,3 +509,3 @@ private _cachedNonBuiltInFields?; | ||
readonly kind: "FieldDefinition"; | ||
private readonly _args; | ||
private _args; | ||
private _extension?; | ||
@@ -578,6 +575,6 @@ constructor(name: string, isBuiltIn?: boolean); | ||
readonly kind: "DirectiveDefinition"; | ||
private readonly _args; | ||
private _args?; | ||
repeatable: boolean; | ||
private readonly _locations; | ||
private readonly _referencers; | ||
private _referencers?; | ||
constructor(name: string, isBuiltIn?: boolean); | ||
@@ -632,5 +629,5 @@ get coordinate(): string; | ||
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 function sameDirectiveApplications(applications1: readonly Directive<any, any>[], applications2: readonly Directive<any, any>[]): boolean; | ||
export declare function isDirectiveApplicationsSubset(applications: readonly Directive<any, any>[], maybeSubset: readonly Directive<any, any>[]): boolean; | ||
export declare function directiveApplicationsSubstraction(baseApplications: readonly Directive<any, any>[], toRemove: readonly Directive<any, any>[]): Directive<any, any>[]; | ||
export declare class Variable { | ||
@@ -637,0 +634,0 @@ readonly name: string; |
@@ -14,2 +14,6 @@ import { ASTNode, GraphQLError, GraphQLErrorOptions, GraphQLFormattedError } from "graphql"; | ||
export declare function extractGraphQLErrorOptions(e: GraphQLError): GraphQLErrorOptions; | ||
export declare function aggregateError(code: String, message: string, causes: GraphQLError[]): GraphQLError; | ||
export declare function errorCauses(e: Error): GraphQLError[] | undefined; | ||
export declare function printGraphQLErrorsOrRethrow(e: Error): string; | ||
export declare function printErrors(errors: GraphQLError[]): string; | ||
export declare type ErrorCodeCategory<TElement = string> = { | ||
@@ -49,2 +53,3 @@ get(element: TElement): ErrorCodeDefinition; | ||
TYPE_DEFINITION_INVALID: ErrorCodeDefinition; | ||
UNSUPPORTED_LINKED_FEATURE: ErrorCodeDefinition; | ||
UNKNOWN_FEDERATION_LINK_VERSION: ErrorCodeDefinition; | ||
@@ -51,0 +56,0 @@ UNKNOWN_LINK_VERSION: ErrorCodeDefinition; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.REMOVED_ERRORS = exports.ERRORS = exports.ERROR_CATEGORIES = exports.withModifiedErrorNodes = exports.withModifiedErrorMessage = exports.errorCodeDef = exports.errorCode = exports.extractGraphQLErrorOptions = void 0; | ||
exports.REMOVED_ERRORS = exports.ERRORS = exports.ERROR_CATEGORIES = exports.withModifiedErrorNodes = exports.withModifiedErrorMessage = exports.errorCodeDef = exports.errorCode = exports.printErrors = exports.printGraphQLErrorsOrRethrow = exports.errorCauses = exports.aggregateError = exports.extractGraphQLErrorOptions = void 0; | ||
const graphql_1 = require("graphql"); | ||
@@ -30,2 +30,46 @@ const utils_1 = require("./utils"); | ||
exports.extractGraphQLErrorOptions = extractGraphQLErrorOptions; | ||
class AggregateGraphQLError extends graphql_1.GraphQLError { | ||
constructor(code, message, causes, options) { | ||
super(message + '. Caused by:\n' + causes.map((c) => c.toString()).join('\n\n'), { | ||
...options, | ||
extensions: { code }, | ||
}); | ||
this.causes = causes; | ||
} | ||
toString() { | ||
let output = `[${this.extensions.code}] ${super.toString()}`; | ||
output += "\ncaused by:"; | ||
for (const cause of this.causes) { | ||
output += "\n\n - "; | ||
output += cause.toString().split("\n").join("\n "); | ||
} | ||
return output; | ||
} | ||
} | ||
function aggregateError(code, message, causes) { | ||
return new AggregateGraphQLError(code, message, causes); | ||
} | ||
exports.aggregateError = aggregateError; | ||
function errorCauses(e) { | ||
if (e instanceof AggregateGraphQLError) { | ||
return e.causes; | ||
} | ||
if (e instanceof graphql_1.GraphQLError) { | ||
return [e]; | ||
} | ||
return undefined; | ||
} | ||
exports.errorCauses = errorCauses; | ||
function printGraphQLErrorsOrRethrow(e) { | ||
const causes = errorCauses(e); | ||
if (!causes) { | ||
throw e; | ||
} | ||
return causes.map(e => e.toString()).join('\n\n'); | ||
} | ||
exports.printGraphQLErrorsOrRethrow = printGraphQLErrorsOrRethrow; | ||
function printErrors(errors) { | ||
return errors.map(e => e.toString()).join('\n\n'); | ||
} | ||
exports.printErrors = printErrors; | ||
const DEFAULT_METADATA = { addedIn: '2.0.0' }; | ||
@@ -80,2 +124,3 @@ const makeErrorCodeCategory = (extractCode, makeDescription, metadata = DEFAULT_METADATA) => ({ | ||
const TYPE_DEFINITION_INVALID = makeCodeDefinition('TYPE_DEFINITION_INVALID', 'A built-in or federation type has an invalid definition in the schema.'); | ||
const UNSUPPORTED_LINKED_FEATURE = makeCodeDefinition('UNSUPPORTED_LINKED_FEATURE', 'Indicates that a feature used in a @link is either unsupported or is used with unsupported options.'); | ||
const UNKNOWN_FEDERATION_LINK_VERSION = makeCodeDefinition('UNKNOWN_FEDERATION_LINK_VERSION', 'The version of federation in a @link directive on the schema is unknown.'); | ||
@@ -171,2 +216,3 @@ const UNKNOWN_LINK_VERSION = makeCodeDefinition('UNKNOWN_LINK_VERSION', 'The version of @link set on the schema is unknown.', { addedIn: '2.1.0' }); | ||
TYPE_DEFINITION_INVALID, | ||
UNSUPPORTED_LINKED_FEATURE, | ||
UNKNOWN_FEDERATION_LINK_VERSION, | ||
@@ -173,0 +219,0 @@ UNKNOWN_LINK_VERSION, |
@@ -16,2 +16,3 @@ "use strict"; | ||
const print_1 = require("./print"); | ||
const operations_1 = require("./operations"); | ||
const fs_1 = __importDefault(require("fs")); | ||
@@ -53,2 +54,82 @@ const path_1 = __importDefault(require("path")); | ||
} | ||
function collectFieldReachableTypesForSubgraph(supergraph, subgraphName, addReachableType, fieldInfoInSubgraph, typeInfoInSubgraph) { | ||
const seenTypes = new Set(); | ||
const stack = supergraph.schemaDefinition.roots().map((root) => root.type); | ||
for (const type of supergraph.types()) { | ||
const { isEntityWithKeyInSubgraph, typesInFederationDirectives } = typeInfoInSubgraph(type, subgraphName); | ||
if (isEntityWithKeyInSubgraph) { | ||
stack.push(type); | ||
} | ||
typesInFederationDirectives.forEach((t) => stack.push(t)); | ||
} | ||
while (stack.length > 0) { | ||
const type = stack.pop(); | ||
addReachableType(type); | ||
if (seenTypes.has(type.name)) { | ||
continue; | ||
} | ||
seenTypes.add(type.name); | ||
switch (type.kind) { | ||
case 'InterfaceType': | ||
type.allImplementations().forEach((t) => stack.push(t)); | ||
case 'ObjectType': | ||
type.interfaces().forEach((t) => stack.push(t)); | ||
for (const field of type.fields()) { | ||
const { isInSubgraph, typesInFederationDirectives } = fieldInfoInSubgraph(field, subgraphName); | ||
if (isInSubgraph) { | ||
field.arguments().forEach((arg) => stack.push((0, definitions_1.baseType)(arg.type))); | ||
stack.push((0, definitions_1.baseType)(field.type)); | ||
typesInFederationDirectives.forEach((t) => stack.push(t)); | ||
} | ||
} | ||
break; | ||
case 'InputObjectType': | ||
for (const field of type.fields()) { | ||
const { isInSubgraph, typesInFederationDirectives } = fieldInfoInSubgraph(field, subgraphName); | ||
if (isInSubgraph) { | ||
stack.push((0, definitions_1.baseType)(field.type)); | ||
typesInFederationDirectives.forEach((t) => stack.push(t)); | ||
} | ||
} | ||
break; | ||
case 'UnionType': | ||
type.members().forEach((m) => stack.push(m.type)); | ||
break; | ||
} | ||
} | ||
for (const directive of supergraph.directives()) { | ||
if (!directive.hasExecutableLocations()) { | ||
continue; | ||
} | ||
directive.arguments().forEach((arg) => stack.push((0, definitions_1.baseType)(arg.type))); | ||
} | ||
} | ||
function collectFieldReachableTypesForAllSubgraphs(supergraph, allSubgraphs, fieldInfoInSubgraph, typeInfoInSubgraph) { | ||
const reachableTypesBySubgraphs = new Map(); | ||
for (const subgraphName of allSubgraphs) { | ||
const reachableTypes = new Set(); | ||
collectFieldReachableTypesForSubgraph(supergraph, subgraphName, (t) => reachableTypes.add(t.name), fieldInfoInSubgraph, typeInfoInSubgraph); | ||
reachableTypesBySubgraphs.set(subgraphName, reachableTypes); | ||
} | ||
return reachableTypesBySubgraphs; | ||
} | ||
function typesUsedInFederationDirective(fieldSet, parentType) { | ||
if (!fieldSet) { | ||
return []; | ||
} | ||
const usedTypes = []; | ||
(0, operations_1.parseSelectionSet)({ | ||
parentType, | ||
source: fieldSet, | ||
fieldAccessor: (type, fieldName) => { | ||
const field = type.field(fieldName); | ||
if (field) { | ||
usedTypes.push((0, definitions_1.baseType)(field.type)); | ||
} | ||
return field; | ||
}, | ||
validate: false, | ||
}); | ||
return usedTypes; | ||
} | ||
function extractSubgraphsFromSupergraph(supergraph) { | ||
@@ -61,6 +142,45 @@ const [coreFeatures, joinSpec] = (0, supergraphs_1.validateSupergraph)(supergraph); | ||
const implementsDirective = joinSpec.implementsDirective(supergraph); | ||
const ownerDirective = joinSpec.ownerDirective(supergraph); | ||
const fieldDirective = joinSpec.fieldDirective(supergraph); | ||
const getSubgraph = (application) => graphEnumNameToSubgraphName.get(application.arguments().graph); | ||
let includeTypeInSubgraph = () => true; | ||
if (isFed1) { | ||
const reachableTypesBySubgraph = collectFieldReachableTypesForAllSubgraphs(supergraph, subgraphs.names(), (f, name) => { | ||
const fieldApplications = f.appliedDirectivesOf(fieldDirective); | ||
if (fieldApplications.length) { | ||
const application = fieldApplications.find((application) => getSubgraph(application) === name); | ||
if (application) { | ||
const args = application.arguments(); | ||
const typesInFederationDirectives = typesUsedInFederationDirective(args.provides, (0, definitions_1.baseType)(f.type)) | ||
.concat(typesUsedInFederationDirective(args.requires, f.parent)); | ||
return { isInSubgraph: true, typesInFederationDirectives }; | ||
} | ||
else { | ||
return { isInSubgraph: false, typesInFederationDirectives: [] }; | ||
} | ||
} | ||
else { | ||
const ownerApplications = ownerDirective ? f.parent.appliedDirectivesOf(ownerDirective) : []; | ||
return { isInSubgraph: !ownerApplications.length || getSubgraph(ownerApplications[0]) == name, typesInFederationDirectives: [] }; | ||
} | ||
}, (t, name) => { | ||
const typeApplications = t.appliedDirectivesOf(typeDirective); | ||
const application = typeApplications.find((application) => (application.arguments().key && (getSubgraph(application) === name))); | ||
if (application) { | ||
const typesInFederationDirectives = typesUsedInFederationDirective(application.arguments().key, t); | ||
return { isEntityWithKeyInSubgraph: true, typesInFederationDirectives }; | ||
} | ||
else { | ||
return { isEntityWithKeyInSubgraph: false, typesInFederationDirectives: [] }; | ||
} | ||
}); | ||
includeTypeInSubgraph = (t, name) => { var _a, _b; return (_b = (_a = reachableTypesBySubgraph.get(name)) === null || _a === void 0 ? void 0 : _a.has(t.name)) !== null && _b !== void 0 ? _b : false; }; | ||
} | ||
for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) { | ||
const typeApplications = type.appliedDirectivesOf(typeDirective); | ||
if (!typeApplications.length) { | ||
subgraphs.values().map(sg => sg.schema).forEach(schema => schema.addType((0, definitions_1.newNamedType)(type.kind, type.name))); | ||
subgraphs | ||
.values() | ||
.filter((sg) => includeTypeInSubgraph(type, sg.name)) | ||
.map(sg => sg.schema).forEach(schema => schema.addType((0, definitions_1.newNamedType)(type.kind, type.name))); | ||
} | ||
@@ -70,3 +190,3 @@ else { | ||
const args = application.arguments(); | ||
const subgraphName = graphEnumNameToSubgraphName.get(args.graph); | ||
const subgraphName = getSubgraph(application); | ||
const schema = subgraphs.get(subgraphName).schema; | ||
@@ -87,4 +207,2 @@ let subgraphType = schema.type(type.name); | ||
} | ||
const ownerDirective = joinSpec.ownerDirective(supergraph); | ||
const fieldDirective = joinSpec.fieldDirective(supergraph); | ||
for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) { | ||
@@ -140,3 +258,6 @@ switch (type.kind) { | ||
const subgraphField = addSubgraphField(field, subgraph, args.type); | ||
(0, utils_1.assert)(subgraphField, () => `Found join__field directive for graph ${subgraph.name} on field ${field.coordinate} but no corresponding join__type on ${type}`); | ||
if (!subgraphField) { | ||
(0, utils_1.assert)(!includeTypeInSubgraph(type, subgraph.name), () => `Found join__field directive for graph ${subgraph.name} on field ${field.coordinate} but no corresponding join__type on ${type}`); | ||
continue; | ||
} | ||
if (args.requires) { | ||
@@ -143,0 +264,0 @@ subgraphField.applyDirective(subgraph.metadata().requiresDirective(), { 'fields': args.requires }); |
@@ -1,2 +0,2 @@ | ||
import { CompositeType, CoreFeature, Directive, DirectiveDefinition, FieldDefinition, InputFieldDefinition, InterfaceType, NamedType, ObjectType, ScalarType, Schema, SchemaBlueprint, UnionType } from "./definitions"; | ||
import { CompositeType, CoreFeature, Directive, DirectiveDefinition, FieldDefinition, InputFieldDefinition, InterfaceType, NamedType, ObjectType, ScalarType, Schema, SchemaBlueprint, SchemaConfig, UnionType } from "./definitions"; | ||
import { SDLValidationRule } from "graphql/validation/ValidationContext"; | ||
@@ -90,3 +90,3 @@ import { ASTNode, DocumentNode, GraphQLError } from "graphql"; | ||
export declare function buildSubgraph(name: string, url: string, source: DocumentNode | string, withRootTypeRenaming?: boolean): Subgraph; | ||
export declare function newEmptyFederation2Schema(): Schema; | ||
export declare function newEmptyFederation2Schema(config?: SchemaConfig): Schema; | ||
export declare function parseFieldSetArgument({ parentType, directive, fieldAccessor, validate, }: { | ||
@@ -93,0 +93,0 @@ parentType: CompositeType; |
@@ -656,4 +656,4 @@ "use strict"; | ||
exports.buildSubgraph = buildSubgraph; | ||
function newEmptyFederation2Schema() { | ||
const schema = new definitions_1.Schema(new FederationBlueprint(true)); | ||
function newEmptyFederation2Schema(config) { | ||
const schema = new definitions_1.Schema(new FederationBlueprint(true), config); | ||
setSchemaAsFed2Subgraph(schema); | ||
@@ -784,3 +784,3 @@ return schema; | ||
catch (e) { | ||
const isGraphQLError = (0, definitions_1.errorCauses)(e) !== undefined; | ||
const isGraphQLError = (0, error_1.errorCauses)(e) !== undefined; | ||
if (!isGraphQLError || validate) { | ||
@@ -821,3 +821,3 @@ throw e; | ||
catch (e) { | ||
const causes = (0, definitions_1.errorCauses)(e); | ||
const causes = (0, error_1.errorCauses)(e); | ||
if (causes) { | ||
@@ -869,3 +869,3 @@ errors = errors.concat(causes); | ||
catch (e) { | ||
const causes = (0, definitions_1.errorCauses)(e); | ||
const causes = (0, error_1.errorCauses)(e); | ||
if (!causes) { | ||
@@ -1011,3 +1011,3 @@ throw e; | ||
function addSubgraphToError(e, subgraphName, errorCode) { | ||
const updatedCauses = (0, definitions_1.errorCauses)(e).map(cause => { | ||
const updatedCauses = (0, error_1.errorCauses)(e).map(cause => { | ||
var _a; | ||
@@ -1014,0 +1014,0 @@ const message = `[${subgraphName}] ${cause.message}`; |
@@ -149,3 +149,3 @@ import { DocumentNode, FieldNode, FragmentDefinitionNode, SelectionNode, SelectionSetNode } from "graphql"; | ||
mergeIn(selectionSet: SelectionSet): void; | ||
addAll(selections: Selection[]): SelectionSet; | ||
addAll(selections: readonly Selection[]): SelectionSet; | ||
add(selection: Selection): Selection; | ||
@@ -212,6 +212,6 @@ addPath(path: OperationPath): void; | ||
abstract withNormalizedDefer(normalizer: DeferNormalizer): FragmentSelection | SelectionSet; | ||
abstract updateForAddingTo(selectionSet: SelectionSet): FragmentSelection; | ||
protected us(): FragmentSelection; | ||
protected validateDeferAndStream(): void; | ||
usedVariables(): Variables; | ||
updateForAddingTo(selectionSet: SelectionSet): FragmentSelection; | ||
filter(predicate: (selection: Selection) => boolean): FragmentSelection | undefined; | ||
@@ -218,0 +218,0 @@ protected freezeInternals(): void; |
@@ -1111,21 +1111,2 @@ "use strict"; | ||
} | ||
updateForAddingTo(selectionSet) { | ||
var _a; | ||
const updatedFragment = this.element().updateForAddingTo(selectionSet); | ||
if (this.element() === updatedFragment) { | ||
return this.cloneIfFrozen(); | ||
} | ||
const updatedCastedType = updatedFragment.castedType(); | ||
let updatedSelectionSet; | ||
if (this.selectionSet.parentType !== updatedCastedType) { | ||
updatedSelectionSet = new SelectionSet(updatedCastedType); | ||
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 InlineFragmentSelection(updatedFragment, updatedSelectionSet); | ||
} | ||
filter(predicate) { | ||
@@ -1179,2 +1160,21 @@ if (!predicate(this)) { | ||
} | ||
updateForAddingTo(selectionSet) { | ||
var _a; | ||
const updatedFragment = this.element().updateForAddingTo(selectionSet); | ||
if (this.element() === updatedFragment) { | ||
return this.cloneIfFrozen(); | ||
} | ||
const updatedCastedType = updatedFragment.castedType(); | ||
let updatedSelectionSet; | ||
if (this.selectionSet.parentType !== updatedCastedType) { | ||
updatedSelectionSet = new SelectionSet(updatedCastedType); | ||
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 InlineFragmentSelection(updatedFragment, updatedSelectionSet); | ||
} | ||
get selectionSet() { | ||
@@ -1310,2 +1310,5 @@ return this._selectionSet; | ||
} | ||
updateForAddingTo(_selectionSet) { | ||
return this; | ||
} | ||
expandFragments(names, updateSelectionSetFragments = true) { | ||
@@ -1316,3 +1319,3 @@ if (names && !names.includes(this.namedFragment.name)) { | ||
const expandedSubSelections = this.selectionSet.expandFragments(names, updateSelectionSetFragments); | ||
return (0, types_1.sameType)(this._element.parentType, this.namedFragment.typeCondition) | ||
return (0, types_1.sameType)(this._element.parentType, this.namedFragment.typeCondition) && this._element.appliedDirectives.length === 0 | ||
? expandedSubSelections.selections() | ||
@@ -1319,0 +1322,0 @@ : new InlineFragmentSelection(this._element, expandedSubSelections); |
@@ -229,3 +229,3 @@ "use strict"; | ||
catch (e) { | ||
const causes = (0, definitions_1.errorCauses)(e); | ||
const causes = (0, error_1.errorCauses)(e); | ||
if (causes) { | ||
@@ -301,3 +301,3 @@ causes.forEach((c) => this.addError(c)); | ||
catch (e) { | ||
const errors = (0, definitions_1.errorCauses)(e); | ||
const errors = (0, error_1.errorCauses)(e); | ||
if (!errors) { | ||
@@ -312,3 +312,3 @@ throw e; | ||
for (const directive of [this.metadata.keyDirective(), this.metadata.requiresDirective(), this.metadata.providesDirective()]) { | ||
for (const application of directive.applications()) { | ||
for (const application of Array.from(directive.applications())) { | ||
const fields = application.arguments().fields; | ||
@@ -526,3 +526,3 @@ if (typeof fields !== 'string') { | ||
} | ||
for (const application of tagDirective.applications()) { | ||
for (const application of Array.from(tagDirective.applications())) { | ||
const element = application.parent; | ||
@@ -529,0 +529,0 @@ if (!(element instanceof definitions_1.FieldDefinition)) { |
import { DocumentNode } from "graphql"; | ||
import { CoreFeature, CoreFeatures, Schema } from "./definitions"; | ||
import { CoreFeatures, Schema } from "./definitions"; | ||
import { JoinSpecDefinition } from "./joinSpec"; | ||
export declare function ErrUnsupportedFeature(feature: CoreFeature): Error; | ||
export declare function ErrForUnsupported(core: CoreFeature, ...features: readonly CoreFeature[]): Error; | ||
export declare function buildSupergraphSchema(supergraphSdl: string | DocumentNode): [Schema, { | ||
@@ -7,0 +5,0 @@ name: string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isFed1Supergraph = exports.validateSupergraph = exports.buildSupergraphSchema = exports.ErrForUnsupported = exports.ErrUnsupportedFeature = void 0; | ||
const core_schema_1 = require("@apollo/core-schema"); | ||
exports.isFed1Supergraph = exports.validateSupergraph = exports.buildSupergraphSchema = void 0; | ||
const coreSpec_1 = require("./coreSpec"); | ||
const definitions_1 = require("./definitions"); | ||
const joinSpec_1 = require("./joinSpec"); | ||
@@ -20,19 +20,2 @@ const buildSchema_1 = require("./buildSchema"); | ||
]); | ||
function ErrUnsupportedFeature(feature) { | ||
return (0, core_schema_1.err)('UnsupportedFeature', { | ||
message: `feature ${feature.url} is for: ${feature.purpose} but is unsupported`, | ||
feature, | ||
nodes: feature.directive.sourceAST, | ||
}); | ||
} | ||
exports.ErrUnsupportedFeature = ErrUnsupportedFeature; | ||
function ErrForUnsupported(core, ...features) { | ||
return (0, core_schema_1.err)('ForUnsupported', { | ||
message: `the \`for:\` argument is unsupported by version ${core.url.version} ` + | ||
`of the core spec. Please upgrade to at least @core v0.2 (https://specs.apollo.dev/core/v0.2).`, | ||
features, | ||
nodes: [core.directive.sourceAST, ...features.map(f => f.directive.sourceAST)].filter(n => !!n) | ||
}); | ||
} | ||
exports.ErrForUnsupported = ErrForUnsupported; | ||
const coreVersionZeroDotOneUrl = coreSpec_1.FeatureUrl.parse('https://specs.apollo.dev/core/v0.1'); | ||
@@ -51,6 +34,10 @@ function buildSupergraphSchema(supergraphSdl) { | ||
const errors = []; | ||
if (coreFeatures.coreItself.url.equals(coreVersionZeroDotOneUrl)) { | ||
const coreItself = coreFeatures.coreItself; | ||
if (coreItself.url.equals(coreVersionZeroDotOneUrl)) { | ||
const purposefulFeatures = [...coreFeatures.allFeatures()].filter(f => f.purpose); | ||
if (purposefulFeatures.length > 0) { | ||
errors.push(ErrForUnsupported(coreFeatures.coreItself, ...purposefulFeatures)); | ||
errors.push(error_1.ERRORS.UNSUPPORTED_LINKED_FEATURE.err(`the \`for:\` argument is unsupported by version ${coreItself.url.version} ` + | ||
`of the core spec. Please upgrade to at least @core v0.2 (https://specs.apollo.dev/core/v0.2).`, { | ||
nodes: (0, definitions_1.sourceASTs)(coreItself.directive, ...purposefulFeatures.map(f => f.directive)) | ||
})); | ||
} | ||
@@ -61,3 +48,3 @@ } | ||
if (!SUPPORTED_FEATURES.has(feature.url.base.toString())) { | ||
errors.push(ErrUnsupportedFeature(feature)); | ||
errors.push(error_1.ERRORS.UNSUPPORTED_LINKED_FEATURE.err(`feature ${feature.url} is for: ${feature.purpose} but is unsupported`, { nodes: feature.directive.sourceAST })); | ||
} | ||
@@ -64,0 +51,0 @@ } |
@@ -24,3 +24,3 @@ export declare function assert(condition: any, message: string | (() => string)): asserts condition; | ||
private insertKeyInOrder; | ||
[Symbol.iterator](): Generator<V, void, unknown>; | ||
[Symbol.iterator](): Generator<NonNullable<V>, void, unknown>; | ||
} | ||
@@ -61,2 +61,3 @@ export declare function arrayEquals<T>(a: readonly T[], b: readonly T[], equalFct?: (e1: T, e2: T) => boolean): boolean; | ||
export declare const isDefined: <T>(t: T | undefined) => t is T; | ||
export declare function removeArrayElement<T>(element: T, array: T[]): boolean; | ||
//# sourceMappingURL=utils.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isDefined = exports.printHumanReadableList = exports.joinStrings = exports.validateStringContainsBoolean = exports.copyWitNewLength = exports.MapWithCachedArrays = exports.setValues = exports.mapEntries = exports.mapKeys = exports.mapValues = exports.firstOf = exports.arrayEquals = exports.OrderedMap = exports.SetMultiMap = exports.MultiMap = exports.assertUnreachable = exports.assert = void 0; | ||
exports.removeArrayElement = exports.isDefined = exports.printHumanReadableList = exports.joinStrings = exports.validateStringContainsBoolean = exports.copyWitNewLength = exports.MapWithCachedArrays = exports.setValues = exports.mapEntries = exports.mapKeys = exports.mapValues = exports.firstOf = exports.arrayEquals = exports.OrderedMap = exports.SetMultiMap = exports.MultiMap = exports.assertUnreachable = exports.assert = void 0; | ||
function assert(condition, message) { | ||
@@ -299,2 +299,13 @@ if (!condition) { | ||
exports.isDefined = isDefined; | ||
function removeArrayElement(element, array) { | ||
const index = array.indexOf(element); | ||
if (index >= 0) { | ||
array.splice(index, 1); | ||
return true; | ||
} | ||
else { | ||
return false; | ||
} | ||
} | ||
exports.removeArrayElement = removeArrayElement; | ||
//# sourceMappingURL=utils.js.map |
{ | ||
"name": "@apollo/federation-internals", | ||
"version": "2.1.0-alpha.4", | ||
"version": "2.1.0", | ||
"description": "Apollo Federation internal utilities", | ||
@@ -26,3 +26,2 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"@apollo/core-schema": "~0.3.0", | ||
"chalk": "^4.1.0", | ||
@@ -37,3 +36,3 @@ "js-levenshtein": "^1.1.6" | ||
}, | ||
"gitHead": "db61f574cc2ee95a53dac8b95a5a320f8caf6fbc" | ||
"gitHead": "6dec2f26af031434c56bea46a75330559dac5f5b" | ||
} |
import { DocumentNode, GraphQLError } from "graphql"; | ||
import gql from "graphql-tag"; | ||
import { buildSubgraph } from "../federation"; | ||
import { errorCauses } from "../definitions"; | ||
import { assert } from "../utils"; | ||
import { buildSchemaFromAST } from "../buildSchema"; | ||
import { removeAllCoreFeatures } from "../coreSpec"; | ||
import { errorCauses } from "../error"; | ||
@@ -9,0 +9,0 @@ function expectErrors( |
@@ -820,3 +820,3 @@ import { | ||
test('Conversion to graphQL-js schema can optionally include @defer definition', () => { | ||
test('Conversion to graphQL-js schema can optionally include @defer and/or @streams definition(s)', () => { | ||
const sdl = ` | ||
@@ -829,5 +829,4 @@ type Query { | ||
const graphqQLSchema = schema.toGraphQLJSSchema({ includeDefer: true }); | ||
expect(printGraphQLjsSchema(graphqQLSchema)).toMatchString(` | ||
directive @defer(label: String, if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT | ||
expect(printGraphQLjsSchema(schema.toGraphQLJSSchema({ includeDefer: true }))).toMatchString(` | ||
directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT | ||
@@ -838,2 +837,12 @@ type Query { | ||
`); | ||
expect(printGraphQLjsSchema(schema.toGraphQLJSSchema({ includeDefer: true, includeStream: true }))).toMatchString(` | ||
directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT | ||
directive @stream(label: String, initialCount: Int = 0, if: Boolean! = true) on FIELD | ||
type Query { | ||
x: Int | ||
} | ||
`); | ||
}); | ||
@@ -840,0 +849,0 @@ |
@@ -601,3 +601,2 @@ import { buildSupergraphSchema, extractSubgraphsFromSupergraph } from ".."; | ||
test('throw meaningful error for type erased from supergraph due to extending an entity without a key', () => { | ||
@@ -620,3 +619,4 @@ // Supergraph generated by fed1 composition from: | ||
// | ||
// type Other { | ||
// type Other @key(fields: "id") { | ||
// id: ID! | ||
// f: T @provides(fields: "x") | ||
@@ -626,4 +626,4 @@ // } | ||
// The issue of that schema is that `T` is referenced in `ServiceB`, but because it extends an entity type | ||
// without a key and has noly external field, ther is no remaining traces of it's definition in `ServiceB` | ||
// in the supergraph. As extract cannot make up the original definition out of thin air, it ends up erroring | ||
// without a key and has only external fields, there is no remaining traces of its definition in `ServiceB` | ||
// in the supergraph. As extraction cannot make up the original definition out of thin air, it ends up erroring | ||
// when extracting `Other.t` due to not knowing that type. | ||
@@ -648,3 +648,7 @@ const supergraph = ` | ||
type Other { | ||
type Other | ||
@join__owner(graph: SERVICEB) | ||
@join__type(graph: SERVICEB, key: "id") | ||
{ | ||
id: ID! @join__field(graph: SERVICEB) | ||
f: T @join__field(graph: SERVICEB, provides: "x") | ||
@@ -651,0 +655,0 @@ } |
@@ -170,3 +170,2 @@ import { FEDERATION2_LINK_WTH_FULL_IMPORTS } from '..'; | ||
test('remove tag on external field if found on definition', () => { | ||
@@ -173,0 +172,0 @@ const s1 = ` |
import { DocumentNode } from 'graphql'; | ||
import gql from 'graphql-tag'; | ||
import { Subgraph } from '..'; | ||
import { errorCauses } from '../definitions'; | ||
import { Subgraph, errorCauses } from '..'; | ||
import { asFed2SubgraphDocument, buildSubgraph } from "../federation" | ||
@@ -6,0 +5,0 @@ import { defaultPrintOptions, printSchema } from '../print'; |
@@ -55,6 +55,5 @@ import { | ||
ErrGraphQLValidationFailed, | ||
errorCauses, | ||
NamedSchemaElement, | ||
} from "./definitions"; | ||
import { ERRORS, withModifiedErrorNodes } from "./error"; | ||
import { ERRORS, errorCauses, withModifiedErrorNodes } from "./error"; | ||
@@ -61,0 +60,0 @@ function buildValue(value?: ValueNode): any { |
@@ -5,5 +5,4 @@ import { ASTNode, DirectiveLocation, GraphQLError, StringValueNode } from "graphql"; | ||
import { sameType } from "./types"; | ||
import { err } from '@apollo/core-schema'; | ||
import { assert, firstOf } from './utils'; | ||
import { ERRORS } from "./error"; | ||
import { aggregateError, ERRORS } from "./error"; | ||
import { valueToString } from "./values"; | ||
@@ -19,7 +18,3 @@ import { coreFeatureDefinitionIfKnown, registerKnownFeature } from "./knownCoreFeatures"; | ||
export const ErrCoreCheckFailed = (causes: Error[]) => | ||
err('CheckFailed', { | ||
message: 'one or more checks failed', | ||
causes | ||
}) | ||
export const ErrCoreCheckFailed = (causes: GraphQLError[]) => aggregateError('CheckFailed', 'one or more checks failed', causes); | ||
@@ -26,0 +21,0 @@ function buildError(message: string): Error { |
@@ -59,2 +59,58 @@ import { ASTNode, GraphQLError, GraphQLErrorOptions, GraphQLFormattedError } from "graphql"; | ||
class AggregateGraphQLError extends GraphQLError { | ||
constructor( | ||
code: String, | ||
message: string, | ||
readonly causes: GraphQLError[], | ||
options?: GraphQLErrorOptions, | ||
) { | ||
super( | ||
message + '. Caused by:\n' + causes.map((c) => c.toString()).join('\n\n'), | ||
{ | ||
...options, | ||
extensions: { code }, | ||
} | ||
); | ||
} | ||
toString() { | ||
let output = `[${this.extensions.code}] ${super.toString()}` | ||
output += "\ncaused by:"; | ||
for (const cause of this.causes) { | ||
output += "\n\n - "; | ||
output += cause.toString().split("\n").join("\n "); | ||
} | ||
return output; | ||
} | ||
} | ||
export function aggregateError(code: String, message: string, causes: GraphQLError[]): GraphQLError { | ||
return new AggregateGraphQLError(code, message, causes); | ||
} | ||
/** | ||
* Given an error, check if it is a graphQL error and potentially extract its causes if is aggregate. | ||
* If the error is not a graphQL error, undefined is returned. | ||
*/ | ||
export function errorCauses(e: Error): GraphQLError[] | undefined { | ||
if (e instanceof AggregateGraphQLError) { | ||
return e.causes; | ||
} | ||
if (e instanceof GraphQLError) { | ||
return [e]; | ||
} | ||
return undefined; | ||
} | ||
export function printGraphQLErrorsOrRethrow(e: Error): string { | ||
const causes = errorCauses(e); | ||
if (!causes) { | ||
throw e; | ||
} | ||
return causes.map(e => e.toString()).join('\n\n'); | ||
} | ||
export function printErrors(errors: GraphQLError[]): string { | ||
return errors.map(e => e.toString()).join('\n\n'); | ||
} | ||
/* | ||
@@ -148,2 +204,7 @@ * Most codes currently originate from the initial fed 2 release so we use this for convenience. | ||
const UNSUPPORTED_LINKED_FEATURE = makeCodeDefinition( | ||
'UNSUPPORTED_LINKED_FEATURE', | ||
'Indicates that a feature used in a @link is either unsupported or is used with unsupported options.', | ||
); | ||
const UNKNOWN_FEDERATION_LINK_VERSION = makeCodeDefinition( | ||
@@ -484,2 +545,3 @@ 'UNKNOWN_FEDERATION_LINK_VERSION', | ||
TYPE_DEFINITION_INVALID, | ||
UNSUPPORTED_LINKED_FEATURE, | ||
UNKNOWN_FEDERATION_LINK_VERSION, | ||
@@ -486,0 +548,0 @@ UNKNOWN_LINK_VERSION, |
@@ -37,2 +37,3 @@ import { | ||
import { printSchema } from "./print"; | ||
import { parseSelectionSet } from "./operations"; | ||
import fs from 'fs'; | ||
@@ -86,2 +87,110 @@ import path from 'path'; | ||
function collectFieldReachableTypesForSubgraph( | ||
supergraph: Schema, | ||
subgraphName: string, | ||
addReachableType: (t: NamedType) => void, | ||
fieldInfoInSubgraph: (f: FieldDefinition<any> | InputFieldDefinition, subgraphName: string) => { isInSubgraph: boolean, typesInFederationDirectives: NamedType[] }, | ||
typeInfoInSubgraph: (t: NamedType, subgraphName: string) => { isEntityWithKeyInSubgraph: boolean, typesInFederationDirectives: NamedType[] }, | ||
): void { | ||
const seenTypes = new Set<string>(); | ||
// The types reachable at "top-level" are both the root types, plus any entity type with a key in this subgraph. | ||
const stack = supergraph.schemaDefinition.roots().map((root) => root.type as NamedType) | ||
for (const type of supergraph.types()) { | ||
const { isEntityWithKeyInSubgraph, typesInFederationDirectives } = typeInfoInSubgraph(type, subgraphName); | ||
if (isEntityWithKeyInSubgraph) { | ||
stack.push(type); | ||
} | ||
typesInFederationDirectives.forEach((t) => stack.push(t)); | ||
} | ||
while (stack.length > 0) { | ||
const type = stack.pop()!; | ||
addReachableType(type); | ||
if (seenTypes.has(type.name)) { | ||
continue; | ||
} | ||
seenTypes.add(type.name); | ||
switch (type.kind) { | ||
// @ts-expect-error: we fall-through to ObjectType for fields and implemented interfaces. | ||
case 'InterfaceType': | ||
// If an interface if reachable, then all of its implementation are too (a field returning the interface could return any of the | ||
// implementation at runtime typically). | ||
type.allImplementations().forEach((t) => stack.push(t)); | ||
case 'ObjectType': | ||
type.interfaces().forEach((t) => stack.push(t)); | ||
for (const field of type.fields()) { | ||
const { isInSubgraph, typesInFederationDirectives } = fieldInfoInSubgraph(field, subgraphName); | ||
if (isInSubgraph) { | ||
field.arguments().forEach((arg) => stack.push(baseType(arg.type!))); | ||
stack.push(baseType(field.type!)); | ||
typesInFederationDirectives.forEach((t) => stack.push(t)); | ||
} | ||
} | ||
break; | ||
case 'InputObjectType': | ||
for (const field of type.fields()) { | ||
const { isInSubgraph, typesInFederationDirectives } = fieldInfoInSubgraph(field, subgraphName); | ||
if (isInSubgraph) { | ||
stack.push(baseType(field.type!)); | ||
typesInFederationDirectives.forEach((t) => stack.push(t)); | ||
} | ||
} | ||
break; | ||
case 'UnionType': | ||
type.members().forEach((m) => stack.push(m.type)); | ||
break; | ||
} | ||
} | ||
for (const directive of supergraph.directives()) { | ||
// In fed1 supergraphs, which is the only place this is called, only executable directive from subgraph only ever made | ||
// it to the supergraph. Skipping anything else saves us from worrying about supergraph-specific directives too. | ||
if (!directive.hasExecutableLocations()) { | ||
continue; | ||
} | ||
directive.arguments().forEach((arg) => stack.push(baseType(arg.type!))); | ||
} | ||
} | ||
function collectFieldReachableTypesForAllSubgraphs( | ||
supergraph: Schema, | ||
allSubgraphs: readonly string[], | ||
fieldInfoInSubgraph: (f: FieldDefinition<any> | InputFieldDefinition, subgraphName: string) => { isInSubgraph: boolean, typesInFederationDirectives: NamedType[] }, | ||
typeInfoInSubgraph: (t: NamedType, subgraphName: string) => { isEntityWithKeyInSubgraph: boolean, typesInFederationDirectives: NamedType[] }, | ||
): Map<string, Set<string>> { | ||
const reachableTypesBySubgraphs = new Map<string, Set<string>>(); | ||
for (const subgraphName of allSubgraphs) { | ||
const reachableTypes = new Set<string>(); | ||
collectFieldReachableTypesForSubgraph( | ||
supergraph, | ||
subgraphName, | ||
(t) => reachableTypes.add(t.name), | ||
fieldInfoInSubgraph, | ||
typeInfoInSubgraph, | ||
); | ||
reachableTypesBySubgraphs.set(subgraphName, reachableTypes); | ||
} | ||
return reachableTypesBySubgraphs; | ||
} | ||
function typesUsedInFederationDirective(fieldSet: string | undefined, parentType: CompositeType): NamedType[] { | ||
if (!fieldSet) { | ||
return []; | ||
} | ||
const usedTypes: NamedType[] = []; | ||
parseSelectionSet({ | ||
parentType, | ||
source: fieldSet, | ||
fieldAccessor: (type, fieldName) => { | ||
const field = type.field(fieldName); | ||
if (field) { | ||
usedTypes.push(baseType(field.type!)); | ||
} | ||
return field; | ||
}, | ||
validate: false, | ||
}); | ||
return usedTypes; | ||
} | ||
export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs { | ||
@@ -95,3 +204,58 @@ const [coreFeatures, joinSpec] = validateSupergraph(supergraph); | ||
const implementsDirective = joinSpec.implementsDirective(supergraph); | ||
const ownerDirective = joinSpec.ownerDirective(supergraph); | ||
const fieldDirective = joinSpec.fieldDirective(supergraph); | ||
const getSubgraph = (application: Directive<any, { graph: string }>) => graphEnumNameToSubgraphName.get(application.arguments().graph); | ||
/* | ||
* Fed2 supergraph have "provenance" information for all types and fields, so we can faithfully extract subgraph relatively easily. | ||
* For fed1 supergraph however, only entity types are marked with `@join__type` and `@join__field`. Which mean that for value types, | ||
* we cannot directly know in which subgraphs they were initially defined. One strategy consists in "extracting" value types into | ||
* all subgraphs blindly: functionally, having some unused types in an extracted subgraph schema does not matter much. However, adding | ||
* those useless types increases memory usage, and we've seen some case with lots of subgraphs and lots of value types where those | ||
* unused types balloon up memory usage (from 100MB to 1GB in one example; obviously, this is made worst by the fact that javascript | ||
* is pretty memory heavy in the first place). So to avoid that problem, for fed1 supergraph, we do a first pass where we collect | ||
* for all the subgraphs the set of types that are actually reachable in that subgraph. As we extract do the actual type extraction, | ||
* we use this to ignore non-reachable types for any given subgraph. | ||
*/ | ||
let includeTypeInSubgraph: (t: NamedType, name: string) => boolean = () => true; | ||
if (isFed1) { | ||
const reachableTypesBySubgraph = collectFieldReachableTypesForAllSubgraphs( | ||
supergraph, | ||
subgraphs.names(), | ||
(f, name) => { | ||
const fieldApplications: Directive<any, { graph: string, requires?: string, provides?: string }>[] = f.appliedDirectivesOf(fieldDirective); | ||
if (fieldApplications.length) { | ||
const application = fieldApplications.find((application) => getSubgraph(application) === name); | ||
if (application) { | ||
const args = application.arguments(); | ||
const typesInFederationDirectives = | ||
typesUsedInFederationDirective(args.provides, baseType(f.type!) as CompositeType) | ||
.concat(typesUsedInFederationDirective(args.requires, f.parent)); | ||
return { isInSubgraph: true, typesInFederationDirectives }; | ||
} else { | ||
return { isInSubgraph: false, typesInFederationDirectives: [] }; | ||
} | ||
} else { | ||
// No field application depends on the "owner" directive on the type. If we have no owner, then the | ||
// field is in all subgraph and we return true. Otherwise, the field is only in the owner subgraph. | ||
// In any case, the field cannot have a requires or provides | ||
const ownerApplications = ownerDirective ? f.parent.appliedDirectivesOf(ownerDirective) : []; | ||
return { isInSubgraph: !ownerApplications.length || getSubgraph(ownerApplications[0]) == name, typesInFederationDirectives: [] }; | ||
} | ||
}, | ||
(t, name) => { | ||
const typeApplications: Directive<any, { graph: string, key?: string}>[] = t.appliedDirectivesOf(typeDirective); | ||
const application = typeApplications.find((application) => (application.arguments().key && (getSubgraph(application) === name))); | ||
if (application) { | ||
const typesInFederationDirectives = typesUsedInFederationDirective(application.arguments().key, t as CompositeType); | ||
return { isEntityWithKeyInSubgraph: true, typesInFederationDirectives }; | ||
} else { | ||
return { isEntityWithKeyInSubgraph: false, typesInFederationDirectives: [] }; | ||
} | ||
}, | ||
); | ||
includeTypeInSubgraph = (t, name) => reachableTypesBySubgraph.get(name)?.has(t.name) ?? false; | ||
} | ||
// Next, we iterate on all types and add it to the proper subgraphs (along with any @key). | ||
@@ -103,9 +267,11 @@ // Note that we first add all types empty and populate the types next. This avoids having to care about the iteration | ||
if (!typeApplications.length) { | ||
// Imply the type is in all subgraphs (technically, some subgraphs may not have had this type, but adding it | ||
// in that case is harmless because it will be unreachable anyway). | ||
subgraphs.values().map(sg => sg.schema).forEach(schema => schema.addType(newNamedType(type.kind, type.name))); | ||
// Imply we don't know in which subgraph the type is, so we had it in all subgraph in which the type is reachable. | ||
subgraphs | ||
.values() | ||
.filter((sg) => includeTypeInSubgraph(type, sg.name)) | ||
.map(sg => sg.schema).forEach(schema => schema.addType(newNamedType(type.kind, type.name))); | ||
} else { | ||
for (const application of typeApplications) { | ||
const args = application.arguments(); | ||
const subgraphName = graphEnumNameToSubgraphName.get(args.graph)!; | ||
const subgraphName = getSubgraph(application)!; | ||
const schema = subgraphs.get(subgraphName)!.schema; | ||
@@ -128,4 +294,2 @@ // We can have more than one type directive for a given subgraph | ||
const ownerDirective = joinSpec.ownerDirective(supergraph); | ||
const fieldDirective = joinSpec.fieldDirective(supergraph); | ||
// We can now populate all those types (with relevant @provides and @requires on fields). | ||
@@ -189,3 +353,10 @@ for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) { | ||
const subgraphField = addSubgraphField(field, subgraph, args.type); | ||
assert(subgraphField, () => `Found join__field directive for graph ${subgraph.name} on field ${field.coordinate} but no corresponding join__type on ${type}`); | ||
if (!subgraphField) { | ||
// It's unlikely but possible that a fed1 supergraph has a `@provides` on a field of a value type, | ||
// and that value type is actually unreachable. Because we trim unreachable types for fed1 supergraph | ||
// (see comment on `includeTypeInSubgraph` above), it would mean we get `undefined` here. It's fine | ||
// however: the type is unreachable in this subgraph, so ignoring that field application is fine too. | ||
assert(!includeTypeInSubgraph(type, subgraph.name), () => `Found join__field directive for graph ${subgraph.name} on field ${field.coordinate} but no corresponding join__type on ${type}`); | ||
continue; | ||
} | ||
if (args.requires) { | ||
@@ -192,0 +363,0 @@ subgraphField.applyDirective(subgraph.metadata().requiresDirective(), {'fields': args.requires}); |
@@ -97,3 +97,3 @@ import { | ||
function definitionAndExtensions<T extends ExtendableElement>(element: {extensions(): ReadonlySet<Extension<T>>}, options: PrintOptions): (Extension<any> | null | undefined)[] { | ||
function definitionAndExtensions<T extends ExtendableElement>(element: {extensions(): readonly Extension<T>[]}, options: PrintOptions): (Extension<any> | null | undefined)[] { | ||
return options.mergeTypesAndExtensions ? [undefined] : [null, ...element.extensions()]; | ||
@@ -106,3 +106,3 @@ } | ||
function printDefinitionAndExtensions<T extends {extensions(): ReadonlySet<Extension<any>>}>( | ||
function printDefinitionAndExtensions<T extends {extensions(): readonly Extension<any>[]}>( | ||
t: T, | ||
@@ -109,0 +109,0 @@ options: PrintOptions, |
@@ -7,3 +7,3 @@ import { | ||
} from "graphql"; | ||
import { ERRORS } from "./error"; | ||
import { errorCauses, ERRORS } from "./error"; | ||
import { | ||
@@ -13,3 +13,2 @@ baseType, | ||
Directive, | ||
errorCauses, | ||
Extension, | ||
@@ -437,3 +436,4 @@ FieldDefinition, | ||
for (const directive of [this.metadata.keyDirective(), this.metadata.requiresDirective(), this.metadata.providesDirective()]) { | ||
for (const application of directive.applications()) { | ||
// Note that we may remove (to replace) some of the application we iterate on, so we need to copy the list we iterate on first. | ||
for (const application of Array.from(directive.applications())) { | ||
const fields = application.arguments().fields; | ||
@@ -703,3 +703,4 @@ if (typeof fields !== 'string') { | ||
for (const application of tagDirective.applications()) { | ||
// Copying the list we iterate on as we remove in the loop. | ||
for (const application of Array.from(tagDirective.applications())) { | ||
const element = application.parent; | ||
@@ -706,0 +707,0 @@ if (!(element instanceof FieldDefinition)) { |
@@ -1,5 +0,4 @@ | ||
import { ASTNode, DocumentNode } from "graphql"; | ||
import { err } from '@apollo/core-schema'; | ||
import { DocumentNode, GraphQLError } from "graphql"; | ||
import { ErrCoreCheckFailed, FeatureUrl, FeatureVersion } from "./coreSpec"; | ||
import { CoreFeature, CoreFeatures, Schema } from "./definitions"; | ||
import { CoreFeatures, Schema, sourceASTs } from "./definitions"; | ||
import { joinIdentity, JoinSpecDefinition, JOIN_VERSIONS } from "./joinSpec"; | ||
@@ -21,20 +20,2 @@ import { buildSchema, buildSchemaFromAST } from "./buildSchema"; | ||
export function ErrUnsupportedFeature(feature: CoreFeature): Error { | ||
return err('UnsupportedFeature', { | ||
message: `feature ${feature.url} is for: ${feature.purpose} but is unsupported`, | ||
feature, | ||
nodes: feature.directive.sourceAST, | ||
}); | ||
} | ||
export function ErrForUnsupported(core: CoreFeature, ...features: readonly CoreFeature[]): Error { | ||
return err('ForUnsupported', { | ||
message: | ||
`the \`for:\` argument is unsupported by version ${core.url.version} ` + | ||
`of the core spec. Please upgrade to at least @core v0.2 (https://specs.apollo.dev/core/v0.2).`, | ||
features, | ||
nodes: [core.directive.sourceAST, ...features.map(f => f.directive.sourceAST)].filter(n => !!n) as ASTNode[] | ||
}); | ||
} | ||
const coreVersionZeroDotOneUrl = FeatureUrl.parse('https://specs.apollo.dev/core/v0.1'); | ||
@@ -60,7 +41,14 @@ | ||
function checkFeatureSupport(coreFeatures: CoreFeatures) { | ||
const errors = []; | ||
if (coreFeatures.coreItself.url.equals(coreVersionZeroDotOneUrl)) { | ||
const errors: GraphQLError[] = []; | ||
const coreItself = coreFeatures.coreItself; | ||
if (coreItself.url.equals(coreVersionZeroDotOneUrl)) { | ||
const purposefulFeatures = [...coreFeatures.allFeatures()].filter(f => f.purpose) | ||
if (purposefulFeatures.length > 0) { | ||
errors.push(ErrForUnsupported(coreFeatures.coreItself, ...purposefulFeatures)); | ||
errors.push(ERRORS.UNSUPPORTED_LINKED_FEATURE.err( | ||
`the \`for:\` argument is unsupported by version ${coreItself.url.version} ` + | ||
`of the core spec. Please upgrade to at least @core v0.2 (https://specs.apollo.dev/core/v0.2).`, | ||
{ | ||
nodes: sourceASTs(coreItself.directive, ...purposefulFeatures.map(f => f.directive)) | ||
} | ||
)); | ||
} | ||
@@ -72,3 +60,6 @@ } | ||
if (!SUPPORTED_FEATURES.has(feature.url.base.toString())) { | ||
errors.push(ErrUnsupportedFeature(feature)); | ||
errors.push(ERRORS.UNSUPPORTED_LINKED_FEATURE.err( | ||
`feature ${feature.url} is for: ${feature.purpose} but is unsupported`, | ||
{ nodes: feature.directive.sourceAST }, | ||
)); | ||
} | ||
@@ -75,0 +66,0 @@ } |
@@ -397,1 +397,16 @@ /** | ||
export const isDefined = <T>(t: T | undefined): t is T => t === undefined ? false : true; | ||
/** | ||
* Removes the first occurrence of the provided element in the provided array, if said array contains said elements. | ||
* | ||
* @return whether the element was removed. | ||
*/ | ||
export function removeArrayElement<T>(element: T, array: T[]): boolean { | ||
const index = array.indexOf(element); | ||
if (index >= 0) { | ||
array.splice(index, 1); | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} |
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 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 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
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
1937095
3
32530
1
- Removed@apollo/core-schema@~0.3.0
- Removed@apollo/core-schema@0.3.0(transitive)
- Removed@protoplasm/recall@0.2.4(transitive)