@apollo/federation-internals
Advanced tools
Comparing version 2.1.0-alpha.2 to 2.1.0-alpha.3
# CHANGELOG for `@apollo/federation-internals` | ||
## 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). | ||
@@ -5,0 +11,0 @@ |
@@ -171,5 +171,15 @@ "use strict"; | ||
withNodeAttachedToError(() => { | ||
const d = element.applyDirective(directive.name.value, buildArgs(directive)); | ||
d.setOfExtension(extension); | ||
d.sourceAST = directive; | ||
if (element !== element.schema().schemaDefinition || directive.name.value === 'link' || !element.schema().blueprint.applyDirectivesAfterParsing()) { | ||
const d = element.applyDirective(directive.name.value, buildArgs(directive)); | ||
d.setOfExtension(extension); | ||
d.sourceAST = directive; | ||
} | ||
else { | ||
element.addUnappliedDirective({ | ||
extension, | ||
directive, | ||
args: buildArgs(directive), | ||
nameOrDef: directive.name.value, | ||
}); | ||
} | ||
}, directive, errors); | ||
@@ -176,0 +186,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { ConstArgumentNode, ASTNode, DirectiveLocation, ConstDirectiveNode, DocumentNode, GraphQLError, GraphQLSchema, Kind, TypeNode, VariableDefinitionNode, VariableNode } from "graphql"; | ||
import { ConstArgumentNode, ASTNode, DirectiveLocation, ConstDirectiveNode, DocumentNode, GraphQLError, GraphQLSchema, Kind, TypeNode, VariableDefinitionNode, VariableNode, DirectiveDefinitionNode, DirectiveNode } from "graphql"; | ||
import { CoreImport, CoreSpecDefinition, FeatureUrl } from "./coreSpec"; | ||
@@ -66,2 +66,5 @@ import { MapWithCachedArrays } from "./utils"; | ||
export declare const executableDirectiveLocations: DirectiveLocation[]; | ||
export declare function isExecutableDirectiveLocation(loc: DirectiveLocation): boolean; | ||
export declare const typeSystemDirectiveLocations: DirectiveLocation[]; | ||
export declare function isTypeSystemDirectiveLocation(loc: DirectiveLocation): boolean; | ||
export declare function typeToAST(type: Type): TypeNode; | ||
@@ -116,5 +119,14 @@ export declare function typeFromAST(schema: Schema, node: TypeNode): Type; | ||
} | ||
declare type UnappliedDirective = { | ||
nameOrDef: DirectiveDefinition<Record<string, any>> | string; | ||
args: Record<string, any>; | ||
extension?: Extension<any>; | ||
directive: DirectiveNode; | ||
}; | ||
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[]; | ||
description?: string; | ||
addUnappliedDirective({ nameOrDef, args, extension, directive }: UnappliedDirective): void; | ||
processUnappliedDirectives(): void; | ||
get appliedDirectives(): readonly Directive<TOwnType>[]; | ||
@@ -206,2 +218,3 @@ appliedDirectivesOf<TApplicationArgs extends { | ||
onUnknownDirectiveValidationError(_schema: Schema, _unknownDirectiveName: string, error: GraphQLError): GraphQLError; | ||
applyDirectivesAfterParsing(): boolean; | ||
} | ||
@@ -237,2 +250,11 @@ export declare const defaultSchemaBlueprint: SchemaBlueprint; | ||
} | ||
export declare type DeferDirectiveArgs = { | ||
label?: string; | ||
if?: boolean | Variable; | ||
}; | ||
export declare type StreamDirectiveArgs = { | ||
label?: string; | ||
initialCount: number; | ||
if?: boolean; | ||
}; | ||
export declare class Schema { | ||
@@ -265,3 +287,5 @@ readonly blueprint: SchemaBlueprint; | ||
private emptyASTDefinitionsForExtensionsWithoutDefinition; | ||
toGraphQLJSSchema(): GraphQLSchema; | ||
toGraphQLJSSchema(config?: { | ||
includeDefer?: boolean; | ||
}): GraphQLSchema; | ||
get schemaDefinition(): SchemaDefinition; | ||
@@ -300,14 +324,16 @@ types(): readonly NamedType[]; | ||
private getBuiltInDirective; | ||
includeDirective(schema: Schema): DirectiveDefinition<{ | ||
includeDirective(): DirectiveDefinition<{ | ||
if: boolean; | ||
}>; | ||
skipDirective(schema: Schema): DirectiveDefinition<{ | ||
skipDirective(): DirectiveDefinition<{ | ||
if: boolean; | ||
}>; | ||
deprecatedDirective(schema: Schema): DirectiveDefinition<{ | ||
deprecatedDirective(): DirectiveDefinition<{ | ||
reason?: string; | ||
}>; | ||
specifiedByDirective(schema: Schema): DirectiveDefinition<{ | ||
specifiedByDirective(): DirectiveDefinition<{ | ||
url: string; | ||
}>; | ||
deferDirective(): DirectiveDefinition<DeferDirectiveArgs>; | ||
streamDirective(): DirectiveDefinition<StreamDirectiveArgs>; | ||
elementByCoordinate(coordinate: string): NamedSchemaElement<any, any, any> | undefined; | ||
@@ -561,2 +587,4 @@ } | ||
removeLocations(...locations: DirectiveLocation[]): DirectiveDefinition; | ||
hasExecutableLocations(): boolean; | ||
hasTypeSystemLocations(): boolean; | ||
applications(): readonly Directive<SchemaElement<any, any>, TApplicationArgs>[]; | ||
@@ -568,2 +596,3 @@ private addReferencer; | ||
removeRecursive(): void; | ||
toAST(): DirectiveDefinitionNode; | ||
toString(): string; | ||
@@ -635,3 +664,9 @@ } | ||
export declare function newNamedType(kind: NamedTypeKind, name: string): NamedType; | ||
export declare function copyDirectiveDefinitionToSchema({ definition, schema, copyDirectiveApplicationsInArguments, locationFilter, }: { | ||
definition: DirectiveDefinition; | ||
schema: Schema; | ||
copyDirectiveApplicationsInArguments: boolean; | ||
locationFilter?: (loc: DirectiveLocation) => boolean; | ||
}): void; | ||
export {}; | ||
//# sourceMappingURL=definitions.d.ts.map |
@@ -184,2 +184,3 @@ "use strict"; | ||
} | ||
const allExecutableDirectives = supergraph.directives().filter((def) => def.hasExecutableLocations()); | ||
for (const subgraph of subgraphs) { | ||
@@ -206,2 +207,10 @@ if (isFed1) { | ||
} | ||
for (const definition of allExecutableDirectives) { | ||
(0, definitions_1.copyDirectiveDefinitionToSchema)({ | ||
definition, | ||
schema: subgraph.schema, | ||
copyDirectiveApplicationsInArguments: false, | ||
locationFilter: (loc) => (0, definitions_1.isExecutableDirectiveLocation)(loc), | ||
}); | ||
} | ||
} | ||
@@ -208,0 +217,0 @@ if (isFed1) { |
@@ -79,2 +79,3 @@ import { CompositeType, CoreFeature, Directive, DirectiveDefinition, FieldDefinition, InputFieldDefinition, InterfaceType, NamedType, ObjectType, ScalarType, Schema, SchemaBlueprint, UnionType } from "./definitions"; | ||
onUnknownDirectiveValidationError(schema: Schema, unknownDirectiveName: string, error: GraphQLError): GraphQLError; | ||
applyDirectivesAfterParsing(): boolean; | ||
} | ||
@@ -81,0 +82,0 @@ export declare function setSchemaAsFed2Subgraph(schema: Schema): void; |
@@ -433,3 +433,5 @@ "use strict"; | ||
onDirectiveDefinitionAndSchemaParsed(schema) { | ||
return completeSubgraphSchema(schema); | ||
const errors = completeSubgraphSchema(schema); | ||
schema.schemaDefinition.processUnappliedDirectives(); | ||
return errors; | ||
} | ||
@@ -529,2 +531,5 @@ onInvalidation(schema) { | ||
} | ||
applyDirectivesAfterParsing() { | ||
return true; | ||
} | ||
} | ||
@@ -531,0 +536,0 @@ exports.FederationBlueprint = FederationBlueprint; |
@@ -422,5 +422,4 @@ "use strict"; | ||
} | ||
const executableDirectiveLocationSet = new Set(definitions_1.executableDirectiveLocations); | ||
for (const directive of schema.allDirectives()) { | ||
const typeSystemLocations = directive.locations.filter((loc) => !executableDirectiveLocationSet.has(loc)); | ||
const typeSystemLocations = directive.locations.filter((loc) => (0, definitions_1.isTypeSystemDirectiveLocation)(loc)); | ||
if (hasBuiltInName(directive)) { | ||
@@ -427,0 +426,0 @@ const inaccessibleElements = fetchInaccessibleElementsDeep(directive); |
@@ -1,3 +0,4 @@ | ||
import { DocumentNode, FieldNode, FragmentDefinitionNode, InlineFragmentNode, SelectionNode, SelectionSetNode } from "graphql"; | ||
import { Directive, DirectiveTargetElement, FieldDefinition, InterfaceType, ObjectType, Schema, SchemaRootKind, Variables, VariableDefinitions, CompositeType } from "./definitions"; | ||
import { DocumentNode, FieldNode, FragmentDefinitionNode, SelectionNode, SelectionSetNode } from "graphql"; | ||
import { Directive, DirectiveTargetElement, FieldDefinition, InterfaceType, ObjectType, Schema, SchemaRootKind, Variables, VariableDefinitions, CompositeType, DeferDirectiveArgs, Variable } from "./definitions"; | ||
import { SetMultiMap } from "./utils"; | ||
declare abstract class AbstractOperationElement<T extends AbstractOperationElement<T>> extends DirectiveTargetElement<T> { | ||
@@ -28,2 +29,5 @@ private readonly variablesInElement; | ||
updateForAddingTo(selectionSet: SelectionSet): Field<TArgs>; | ||
hasDefer(): boolean; | ||
deferDirectiveArgs(): undefined; | ||
withoutDefer(): Field<TArgs>; | ||
equals(that: OperationElement): boolean; | ||
@@ -38,4 +42,10 @@ toString(): string; | ||
get parentType(): CompositeType; | ||
castedType(): CompositeType; | ||
withUpdatedSourceType(newSourceType: CompositeType): FragmentElement; | ||
updateForAddingTo(selectionSet: SelectionSet): FragmentElement; | ||
hasDefer(): boolean; | ||
hasStream(): boolean; | ||
deferDirectiveArgs(): DeferDirectiveArgs | undefined; | ||
withoutDefer(): FragmentElement | undefined; | ||
withNormalizedDefer(normalizer: DeferNormalizer): FragmentElement | undefined; | ||
equals(that: OperationElement): boolean; | ||
@@ -61,2 +71,9 @@ toString(): string; | ||
expandAllFragments(): Operation; | ||
withoutDefer(labelsToRemove?: Set<string>): Operation; | ||
withNormalizedDefer(): { | ||
operation: Operation; | ||
hasDefers: boolean; | ||
assignedDeferLabels: Set<string>; | ||
deferConditions: SetMultiMap<string, string>; | ||
}; | ||
toString(expandFragments?: boolean, prettyPrint?: boolean): string; | ||
@@ -102,2 +119,15 @@ } | ||
} | ||
declare class DeferNormalizer { | ||
private index; | ||
readonly assignedLabels: Set<string>; | ||
readonly deferConditions: SetMultiMap<string, string>; | ||
private readonly usedLabels; | ||
init(selectionSet: SelectionSet): { | ||
hasDefers: boolean; | ||
hasNonLabelledOrConditionalDefers: boolean; | ||
}; | ||
private nextLabel; | ||
newLabel(): string; | ||
registerCondition(label: string, condition: Variable): void; | ||
} | ||
export declare class SelectionSet extends Freezable<SelectionSet> { | ||
@@ -116,3 +146,7 @@ readonly parentType: CompositeType; | ||
expandFragments(names?: string[], updateSelectionSetFragments?: boolean): SelectionSet; | ||
private lazyMap; | ||
withoutDefer(labelsToRemove?: Set<string>): SelectionSet; | ||
withNormalizedDefer(normalizer: DeferNormalizer): SelectionSet; | ||
filter(predicate: (selection: Selection) => boolean): SelectionSet; | ||
withoutEmptyBranches(): SelectionSet | undefined; | ||
protected freezeInternals(): void; | ||
@@ -164,2 +198,4 @@ mergeIn(selectionSet: SelectionSet): void; | ||
namedFragments(): NamedFragments | undefined; | ||
withoutDefer(labelsToRemove?: Set<string>): FieldSelection; | ||
withNormalizedDefer(normalizer: DeferNormalizer): FieldSelection; | ||
clone(): FieldSelection; | ||
@@ -179,6 +215,9 @@ toString(expandFragments?: boolean, indent?: string): string; | ||
abstract validate(): void; | ||
abstract withoutDefer(labelsToRemove?: Set<string>): FragmentSelection | SelectionSet; | ||
abstract withNormalizedDefer(normalizer: DeferNormalizer): FragmentSelection | SelectionSet; | ||
protected us(): FragmentSelection; | ||
protected validateDeferAndStream(): void; | ||
usedVariables(): Variables; | ||
updateForAddingTo(selectionSet: SelectionSet): FragmentSelection; | ||
filter(predicate: (selection: Selection) => boolean): InlineFragmentSelection | undefined; | ||
filter(predicate: (selection: Selection) => boolean): FragmentSelection | undefined; | ||
protected freezeInternals(): void; | ||
@@ -189,17 +228,2 @@ equals(that: Selection): boolean; | ||
} | ||
declare class InlineFragmentSelection extends FragmentSelection { | ||
private readonly fragmentElement; | ||
private readonly _selectionSet; | ||
constructor(fragmentElement: FragmentElement, initialSelectionSet?: SelectionSet); | ||
key(): string; | ||
validate(): void; | ||
get selectionSet(): SelectionSet; | ||
namedFragments(): NamedFragments | undefined; | ||
element(): FragmentElement; | ||
toSelectionNode(): InlineFragmentNode; | ||
optimize(fragments: NamedFragments): FragmentSelection; | ||
expandFragments(names?: string[], updateSelectionSetFragments?: boolean): FragmentSelection; | ||
collectUsedFragmentNames(collector: Map<string, number>): void; | ||
toString(expandFragments?: boolean, indent?: string): string; | ||
} | ||
export declare function operationFromDocument(schema: Schema, document: DocumentNode, operationName?: string): Operation; | ||
@@ -206,0 +230,0 @@ export declare function parseOperation(schema: Schema, operation: string, operationName?: string): Operation; |
@@ -104,13 +104,23 @@ "use strict"; | ||
const fieldParent = this.definition.parent; | ||
if (selectionParent.name !== fieldParent.name) { | ||
if (this.name === definitions_1.typenameFieldName) { | ||
return this.withUpdatedDefinition(selectionParent.typenameField()); | ||
} | ||
validate(!(0, definitions_1.isUnionType)(selectionParent) | ||
if (selectionParent === fieldParent) { | ||
return this; | ||
} | ||
if (this.name === definitions_1.typenameFieldName) { | ||
return this.withUpdatedDefinition(selectionParent.typenameField()); | ||
} | ||
validate(selectionParent.name == fieldParent.name | ||
|| (!(0, definitions_1.isUnionType)(selectionParent) | ||
&& (((0, definitions_1.isInterfaceType)(fieldParent) && fieldParent.allImplementations().some(i => i.name == selectionParent.name)) | ||
|| ((0, definitions_1.isObjectType)(fieldParent) && fieldParent.name == selectionParent.name)), () => `Cannot add selection of field "${this.definition.coordinate}" to selection set of parent type "${selectionSet.parentType}"`); | ||
const fieldDef = selectionParent.field(this.name); | ||
validate(fieldDef, () => `Cannot add selection of field "${this.definition.coordinate}" to selection set of parent type "${selectionParent} (that does not declare that type)"`); | ||
return this.withUpdatedDefinition(fieldDef); | ||
} | ||
|| ((0, definitions_1.isObjectType)(fieldParent) && fieldParent.name == selectionParent.name))), () => `Cannot add selection of field "${this.definition.coordinate}" to selection set of parent type "${selectionSet.parentType}"`); | ||
const fieldDef = selectionParent.field(this.name); | ||
validate(fieldDef, () => `Cannot add selection of field "${this.definition.coordinate}" to selection set of parent type "${selectionParent}" (that does not declare that field)`); | ||
return this.withUpdatedDefinition(fieldDef); | ||
} | ||
hasDefer() { | ||
return false; | ||
} | ||
deferDirectiveArgs() { | ||
return undefined; | ||
} | ||
withoutDefer() { | ||
return this; | ||
@@ -150,4 +160,8 @@ } | ||
} | ||
castedType() { | ||
return this.typeCondition ? this.typeCondition : this.sourceType; | ||
} | ||
withUpdatedSourceType(newSourceType) { | ||
const newFragment = new FragmentElement(newSourceType, this.typeCondition); | ||
var _a; | ||
const newFragment = new FragmentElement(newSourceType, (_a = this.typeCondition) === null || _a === void 0 ? void 0 : _a.name); | ||
for (const directive of this.appliedDirectives) { | ||
@@ -168,2 +182,73 @@ newFragment.applyDirective(directive.definition, directive.arguments()); | ||
} | ||
hasDefer() { | ||
return this.hasAppliedDirective('defer'); | ||
} | ||
hasStream() { | ||
return this.hasAppliedDirective('stream'); | ||
} | ||
deferDirectiveArgs() { | ||
var _a; | ||
return (_a = this.appliedDirectivesOf(this.schema().deferDirective())[0]) === null || _a === void 0 ? void 0 : _a.arguments(); | ||
} | ||
withoutDefer() { | ||
const deferName = this.schema().deferDirective().name; | ||
const updatedDirectives = this.appliedDirectives.filter((d) => d.name !== deferName); | ||
if (!this.typeCondition && updatedDirectives.length === 0) { | ||
return undefined; | ||
} | ||
if (updatedDirectives.length === this.appliedDirectives.length) { | ||
return this; | ||
} | ||
const updated = new FragmentElement(this.sourceType, this.typeCondition); | ||
updatedDirectives.forEach((d) => updated.applyDirective(d.definition, d.arguments())); | ||
return updated; | ||
} | ||
withNormalizedDefer(normalizer) { | ||
const deferArgs = this.deferDirectiveArgs(); | ||
if (!deferArgs) { | ||
return this; | ||
} | ||
let newDeferArgs = undefined; | ||
let conditionVariable = undefined; | ||
if (deferArgs.if !== undefined) { | ||
if (typeof deferArgs.if === 'boolean') { | ||
if (deferArgs.if) { | ||
newDeferArgs = { | ||
...deferArgs, | ||
if: undefined, | ||
}; | ||
} | ||
else { | ||
return this.withoutDefer(); | ||
} | ||
} | ||
else { | ||
conditionVariable = deferArgs.if; | ||
} | ||
} | ||
let label = deferArgs.label; | ||
if (!label) { | ||
label = normalizer.newLabel(); | ||
if (newDeferArgs) { | ||
newDeferArgs.label = label; | ||
} | ||
else { | ||
newDeferArgs = { | ||
...deferArgs, | ||
label, | ||
}; | ||
} | ||
} | ||
if (conditionVariable) { | ||
normalizer.registerCondition(label, conditionVariable); | ||
} | ||
if (!newDeferArgs) { | ||
return this; | ||
} | ||
const updated = new FragmentElement(this.sourceType, this.typeCondition); | ||
const deferDirective = this.schema().deferDirective(); | ||
this.appliedDirectives.filter((d) => d.name !== deferDirective.name).forEach((d) => updated.applyDirective(d.definition, d.arguments())); | ||
updated.applyDirective(this.schema().deferDirective(), newDeferArgs); | ||
return updated; | ||
} | ||
equals(that) { | ||
@@ -263,2 +348,25 @@ var _a, _b; | ||
} | ||
withoutDefer(labelsToRemove) { | ||
(0, utils_1.assert)(!this.selectionSet.fragments || this.selectionSet.fragments.isEmpty(), 'Removing @defer currently only work on "expanded" selections (no named fragments)'); | ||
const updated = this.selectionSet.withoutDefer(labelsToRemove); | ||
return updated == this.selectionSet | ||
? this | ||
: new Operation(this.rootKind, updated, this.variableDefinitions, this.name); | ||
} | ||
withNormalizedDefer() { | ||
(0, utils_1.assert)(!this.selectionSet.fragments || this.selectionSet.fragments.isEmpty(), 'Assigning @defer lables currently only work on "expanded" selections (no named fragments)'); | ||
const normalizer = new DeferNormalizer(); | ||
const { hasDefers, hasNonLabelledOrConditionalDefers } = normalizer.init(this.selectionSet); | ||
let updatedOperation = this; | ||
if (hasNonLabelledOrConditionalDefers) { | ||
const updated = this.selectionSet.withNormalizedDefer(normalizer); | ||
updatedOperation = new Operation(this.rootKind, updated, this.variableDefinitions, this.name); | ||
} | ||
return { | ||
operation: updatedOperation, | ||
hasDefers, | ||
assignedDeferLabels: normalizer.assignedLabels, | ||
deferConditions: normalizer.deferConditions, | ||
}; | ||
} | ||
toString(expandFragments = false, prettyPrint = true) { | ||
@@ -415,2 +523,48 @@ return this.selectionSet.toOperationString(this.rootKind, this.variableDefinitions, this.name, expandFragments, prettyPrint); | ||
} | ||
class DeferNormalizer { | ||
constructor() { | ||
this.index = 0; | ||
this.assignedLabels = new Set(); | ||
this.deferConditions = new utils_1.SetMultiMap(); | ||
this.usedLabels = new Set(); | ||
} | ||
init(selectionSet) { | ||
let hasNonLabelledOrConditionalDefers = false; | ||
let hasDefers = false; | ||
const stack = selectionSet.selections().concat(); | ||
while (stack.length > 0) { | ||
const selection = stack.pop(); | ||
if (selection.kind === 'FragmentSelection') { | ||
const deferArgs = selection.element().deferDirectiveArgs(); | ||
if (deferArgs) { | ||
hasDefers = true; | ||
if (deferArgs.label) { | ||
this.usedLabels.add(deferArgs.label); | ||
} | ||
else { | ||
hasNonLabelledOrConditionalDefers = true; | ||
} | ||
} | ||
} | ||
if (selection.selectionSet) { | ||
selection.selectionSet.selections().forEach((s) => stack.push(s)); | ||
} | ||
} | ||
return { hasDefers, hasNonLabelledOrConditionalDefers }; | ||
} | ||
nextLabel() { | ||
return `qp__${this.index++}`; | ||
} | ||
newLabel() { | ||
let candidate = this.nextLabel(); | ||
while (this.usedLabels.has(candidate)) { | ||
candidate = this.nextLabel(); | ||
} | ||
this.assignedLabels.add(candidate); | ||
return candidate; | ||
} | ||
registerCondition(label, condition) { | ||
this.deferConditions.add(condition.name, label); | ||
} | ||
} | ||
class SelectionSet extends Freezable { | ||
@@ -501,12 +655,43 @@ constructor(parentType, fragments) { | ||
} | ||
filter(predicate) { | ||
const filtered = new SelectionSet(this.parentType, this.fragments); | ||
for (const selection of this.selections()) { | ||
const filteredSelection = selection.filter(predicate); | ||
if (filteredSelection) { | ||
filtered.add(filteredSelection); | ||
lazyMap(mapper) { | ||
let updatedSelections = undefined; | ||
const selections = this.selections(); | ||
for (let i = 0; i < selections.length; i++) { | ||
const selection = selections[i]; | ||
const updated = mapper(selection); | ||
if (updated !== selection && !updatedSelections) { | ||
updatedSelections = []; | ||
for (let j = 0; j < i; j++) { | ||
updatedSelections.push(selections[j]); | ||
} | ||
} | ||
if (!!updated && updatedSelections) { | ||
if (updated instanceof SelectionSet) { | ||
updated.selections().forEach((s) => updatedSelections.push(s)); | ||
} | ||
else { | ||
updatedSelections.push(updated); | ||
} | ||
} | ||
} | ||
return filtered; | ||
if (!updatedSelections) { | ||
return this; | ||
} | ||
return new SelectionSet(this.parentType, this.fragments).addAll(updatedSelections); | ||
} | ||
withoutDefer(labelsToRemove) { | ||
(0, utils_1.assert)(!this.fragments, 'Not yet supported'); | ||
return this.lazyMap((selection) => selection.withoutDefer(labelsToRemove)); | ||
} | ||
withNormalizedDefer(normalizer) { | ||
(0, utils_1.assert)(!this.fragments, 'Not yet supported'); | ||
return this.lazyMap((selection) => selection.withNormalizedDefer(normalizer)); | ||
} | ||
filter(predicate) { | ||
return this.lazyMap((selection) => selection.filter(predicate)); | ||
} | ||
withoutEmptyBranches() { | ||
const updated = this.filter((selection) => { var _a; return ((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.isEmpty()) !== true; }); | ||
return updated.isEmpty() ? undefined : updated; | ||
} | ||
freezeInternals() { | ||
@@ -796,3 +981,6 @@ for (const selection of this.selections()) { | ||
} | ||
return new FieldSelection(this.field, this.selectionSet.filter(predicate)); | ||
const updatedSelectionSet = this.selectionSet.filter(predicate); | ||
return this.selectionSet === updatedSelectionSet | ||
? this | ||
: new FieldSelection(this.field, updatedSelectionSet); | ||
} | ||
@@ -887,2 +1075,16 @@ freezeInternals() { | ||
} | ||
withoutDefer(labelsToRemove) { | ||
var _a; | ||
const updatedSubSelections = (_a = this.selectionSet) === null || _a === void 0 ? void 0 : _a.withoutDefer(labelsToRemove); | ||
return updatedSubSelections === this.selectionSet | ||
? this | ||
: new FieldSelection(this.field, updatedSubSelections); | ||
} | ||
withNormalizedDefer(normalizer) { | ||
var _a; | ||
const updatedSubSelections = (_a = this.selectionSet) === null || _a === void 0 ? void 0 : _a.withNormalizedDefer(normalizer); | ||
return updatedSubSelections === this.selectionSet | ||
? this | ||
: new FieldSelection(this.field, updatedSubSelections); | ||
} | ||
clone() { | ||
@@ -907,2 +1109,9 @@ if (!this.selectionSet) { | ||
} | ||
validateDeferAndStream() { | ||
if (this.element().hasDefer() || this.element().hasStream()) { | ||
const schemaDef = this.element().schema().schemaDefinition; | ||
const parentType = this.element().parentType; | ||
validate(schemaDef.rootType('mutation') !== parentType && schemaDef.rootType('subscription') !== parentType, () => { var _a; return `The @defer and @stream directives cannot be used on ${(_a = schemaDef.roots().filter((t) => t.type === parentType).pop()) === null || _a === void 0 ? void 0 : _a.rootKind} root type "${parentType}"`; }); | ||
} | ||
} | ||
usedVariables() { | ||
@@ -912,6 +1121,19 @@ return (0, definitions_1.mergeVariables)(this.element().variables(), this.selectionSet.usedVariables()); | ||
updateForAddingTo(selectionSet) { | ||
var _a; | ||
const updatedFragment = this.element().updateForAddingTo(selectionSet); | ||
return this.element() === updatedFragment | ||
? this.cloneIfFrozen() | ||
: new InlineFragmentSelection(updatedFragment, this.selectionSet.cloneIfFrozen()); | ||
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); | ||
} | ||
@@ -922,3 +1144,7 @@ filter(predicate) { | ||
} | ||
return new InlineFragmentSelection(this.element(), this.selectionSet.filter(predicate)); | ||
const selectionSet = this.selectionSet; | ||
const updatedSelectionSet = selectionSet.filter(predicate); | ||
return updatedSelectionSet === selectionSet | ||
? this | ||
: new InlineFragmentSelection(this.element(), updatedSelectionSet); | ||
} | ||
@@ -959,2 +1185,3 @@ freezeInternals() { | ||
validate() { | ||
this.validateDeferAndStream(); | ||
validate(!this.selectionSet.isEmpty(), () => `Invalid empty selection set for fragment "${this.element()}"`); | ||
@@ -1016,2 +1243,25 @@ this.selectionSet.validate(); | ||
} | ||
withoutDefer(labelsToRemove) { | ||
const updatedSubSelections = this.selectionSet.withoutDefer(labelsToRemove); | ||
const deferArgs = this.fragmentElement.deferDirectiveArgs(); | ||
const hasDeferToRemove = deferArgs && (!labelsToRemove || (deferArgs.label && labelsToRemove.has(deferArgs.label))); | ||
if (updatedSubSelections === this.selectionSet && !hasDeferToRemove) { | ||
return this; | ||
} | ||
const newFragment = hasDeferToRemove ? this.fragmentElement.withoutDefer() : this.fragmentElement; | ||
if (!newFragment) { | ||
return updatedSubSelections; | ||
} | ||
return new InlineFragmentSelection(newFragment, updatedSubSelections); | ||
} | ||
withNormalizedDefer(normalizer) { | ||
const newFragment = this.fragmentElement.withNormalizedDefer(normalizer); | ||
const updatedSubSelections = this.selectionSet.withNormalizedDefer(normalizer); | ||
if (!newFragment) { | ||
return updatedSubSelections; | ||
} | ||
return newFragment === this.fragmentElement && updatedSubSelections === this.selectionSet | ||
? this | ||
: new InlineFragmentSelection(newFragment, updatedSubSelections); | ||
} | ||
toString(expandFragments = true, indent) { | ||
@@ -1046,2 +1296,3 @@ return (indent !== null && indent !== void 0 ? indent : '') + this.fragmentElement + ' ' + this.selectionSet.toString(expandFragments, true, indent); | ||
validate() { | ||
this.validateDeferAndStream(); | ||
} | ||
@@ -1085,2 +1336,8 @@ toSelectionNode() { | ||
} | ||
withoutDefer(_labelsToRemove) { | ||
(0, utils_1.assert)(false, 'Unsupported, see `Operation.withoutDefer`'); | ||
} | ||
withNormalizedDefer(_normalizezr) { | ||
(0, utils_1.assert)(false, 'Unsupported, see `Operation.withAllDeferLabelled`'); | ||
} | ||
spreadDirectives() { | ||
@@ -1087,0 +1344,0 @@ return this._element.appliedDirectives.slice(this.namedFragment.appliedDirectives.length); |
@@ -21,3 +21,3 @@ import { Directive, DirectiveDefinition, FieldDefinition, NamedType, Schema, SchemaRootKind } from "./definitions"; | ||
export declare function printTypeDefinitionAndExtensions(type: NamedType, options?: PrintOptions): string[]; | ||
export declare function printDirectiveDefinition(directive: DirectiveDefinition, options: PrintOptions): string; | ||
export declare function printDirectiveDefinition(directive: DirectiveDefinition, options?: PrintOptions): string; | ||
//# sourceMappingURL=print.d.ts.map |
@@ -126,3 +126,3 @@ "use strict"; | ||
exports.printTypeDefinitionAndExtensions = printTypeDefinitionAndExtensions; | ||
function printDirectiveDefinition(directive, options) { | ||
function printDirectiveDefinition(directive, options = exports.defaultPrintOptions) { | ||
const locations = directive.locations.join(' | '); | ||
@@ -129,0 +129,0 @@ return `${printDescription(directive, options, null)}directive ${directive}${printArgs(directive.arguments(), options)}${directive.repeatable ? ' repeatable' : ''} on ${locations}`; |
@@ -5,3 +5,8 @@ export declare function assert(condition: any, message: string | (() => string)): asserts condition; | ||
add(key: K, value: V): this; | ||
addAll(otherMap: MultiMap<K, V>): this; | ||
} | ||
export declare class SetMultiMap<K, V> extends Map<K, Set<V>> { | ||
add(key: K, value: V): this; | ||
addAll(otherMap: SetMultiMap<K, V>): this; | ||
} | ||
export declare class OrderedMap<K, V> { | ||
@@ -52,3 +57,6 @@ private _keys; | ||
}): string; | ||
export declare type Concrete<Type> = { | ||
[Property in keyof Type]-?: Type[Property]; | ||
}; | ||
export declare const isDefined: <T>(t: T | undefined) => t is T; | ||
//# 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.MultiMap = exports.assertUnreachable = exports.assert = void 0; | ||
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) { | ||
@@ -25,4 +25,32 @@ if (!condition) { | ||
} | ||
addAll(otherMap) { | ||
for (const [k, vs] of otherMap.entries()) { | ||
for (const v of vs) { | ||
this.add(k, v); | ||
} | ||
} | ||
return this; | ||
} | ||
} | ||
exports.MultiMap = MultiMap; | ||
class SetMultiMap extends Map { | ||
add(key, value) { | ||
let values = this.get(key); | ||
if (!values) { | ||
values = new Set(); | ||
this.set(key, values); | ||
} | ||
values.add(value); | ||
return this; | ||
} | ||
addAll(otherMap) { | ||
for (const [k, vs] of otherMap.entries()) { | ||
for (const v of vs) { | ||
this.add(k, v); | ||
} | ||
} | ||
return this; | ||
} | ||
} | ||
exports.SetMultiMap = SetMultiMap; | ||
class OrderedMap { | ||
@@ -29,0 +57,0 @@ constructor(compareFn = OrderedMap.defaultCompareFn) { |
{ | ||
"name": "@apollo/federation-internals", | ||
"version": "2.1.0-alpha.2", | ||
"version": "2.1.0-alpha.3", | ||
"description": "Apollo Federation internal utilities", | ||
@@ -36,3 +36,3 @@ "main": "dist/index.js", | ||
}, | ||
"gitHead": "cd07551ceaf9223fe6cda3ee29e30dee97d3573e" | ||
"gitHead": "10ffec3f74faab2096f4514c7b2669cce9605e1c" | ||
} |
@@ -820,2 +820,20 @@ import { | ||
test('Conversion to graphQL-js schema can optionally include @defer definition', () => { | ||
const sdl = ` | ||
type Query { | ||
x: Int | ||
} | ||
`; | ||
const schema = parseSchema(sdl); | ||
const graphqQLSchema = schema.toGraphQLJSSchema({ includeDefer: true }); | ||
expect(printGraphQLjsSchema(graphqQLSchema)).toMatchString(` | ||
directive @defer(label: String, if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT | ||
type Query { | ||
x: Int | ||
} | ||
`); | ||
}); | ||
test('retrieving elements by coordinate', () => { | ||
@@ -822,0 +840,0 @@ const sdl = ` |
import { | ||
defaultRootName, | ||
Schema, | ||
SchemaRootKind, | ||
} from '../../dist/definitions'; | ||
@@ -7,2 +9,3 @@ import { buildSchema } from '../../dist/buildSchema'; | ||
import './matchers'; | ||
import { GraphQLError } from 'graphql'; | ||
@@ -345,1 +348,34 @@ function parseSchema(schema: string): Schema { | ||
}); | ||
describe('validations', () => { | ||
test.each([ | ||
{ directive: '@defer', rootKind: 'mutation' }, | ||
{ directive: '@defer', rootKind: 'subscription' }, | ||
{ directive: '@stream', rootKind: 'mutation' }, | ||
{ directive: '@stream', rootKind: 'subscription' }, | ||
])('reject $directive on $rootKind type', ({ directive, rootKind }) => { | ||
const schema = parseSchema(` | ||
type Query { | ||
x: String | ||
} | ||
type Mutation { | ||
x: String | ||
} | ||
type Subscription { | ||
x: String | ||
} | ||
`); | ||
expect(() => { | ||
parseOperation(schema, ` | ||
${rootKind} { | ||
... ${directive} { | ||
x | ||
} | ||
} | ||
`) | ||
}).toThrowError(new GraphQLError(`The @defer and @stream directives cannot be used on ${rootKind} root type "${defaultRootName(rootKind as SchemaRootKind)}"`)); | ||
}); | ||
}); |
@@ -103,3 +103,3 @@ import { | ||
// is also why we skip directive applications at that point, as those _may_ reference something that hasn't been imported yet) | ||
// 2. this allows the code to handle better the case where the `link__Purpose` enum is provided in the AST despite the `@link` | ||
// 2. this allows the code to handle better the case where the `link__Purpose` enum is provided in the AST despite the `@link` | ||
// _definition_ not being provided. And the reason that is true is that as we later _add_ the `@link` definition, we | ||
@@ -219,3 +219,3 @@ // will need to check if `link_Purpose` needs to be added or not, but when it is already present, we check it's definition | ||
// have seen 2 definitions (which is invalid) if the definition has `preserverEmptyDefnition` already set | ||
// since it's only set for definitions, not extensions. | ||
// since it's only set for definitions, not extensions. | ||
// Also note that we allow to redefine built-ins. | ||
@@ -337,5 +337,19 @@ if (!type || type.isBuiltIn) { | ||
() => { | ||
const d = element.applyDirective(directive.name.value, buildArgs(directive)); | ||
d.setOfExtension(extension); | ||
d.sourceAST = directive; | ||
/** | ||
* If we are at the schemaDefinition level of a federation schema, it's possible that some directives | ||
* will not be added until after the federation calls completeSchema. In that case, we want to wait | ||
* until after completeSchema is called before we try to apply those directives. | ||
*/ | ||
if (element !== element.schema().schemaDefinition || directive.name.value === 'link' || !element.schema().blueprint.applyDirectivesAfterParsing()) { | ||
const d = element.applyDirective(directive.name.value, buildArgs(directive)); | ||
d.setOfExtension(extension); | ||
d.sourceAST = directive; | ||
} else { | ||
element.addUnappliedDirective({ | ||
extension, | ||
directive, | ||
args: buildArgs(directive), | ||
nameOrDef: directive.name.value, | ||
}); | ||
} | ||
}, | ||
@@ -342,0 +356,0 @@ directive, |
import { | ||
baseType, | ||
CompositeType, | ||
copyDirectiveDefinitionToSchema, | ||
Directive, | ||
@@ -9,2 +10,3 @@ FieldDefinition, | ||
InterfaceType, | ||
isExecutableDirectiveLocation, | ||
isEnumType, | ||
@@ -238,2 +240,3 @@ isInterfaceType, | ||
const allExecutableDirectives = supergraph.directives().filter((def) => def.hasExecutableLocations()); | ||
for (const subgraph of subgraphs) { | ||
@@ -271,2 +274,19 @@ if (isFed1) { | ||
} | ||
// Lastly, we add all the "executable" directives from the supergraph to each subgraphs, as those may be part | ||
// of a query and end up in any subgraph fetches. We do this "last" to make sure that if one of the directive | ||
// use a type for an argument, that argument exists. | ||
// Note that we don't bother with non-executable directives at the moment since we've don't extract their | ||
// applications. It might become something we need later, but we don't so far. | ||
for (const definition of allExecutableDirectives) { | ||
// Note that we skip any potentially applied directives in the argument of the copied definition, because as said | ||
// in the comment above, we haven't copied type-system directives. And so far, we really don't care about those | ||
// applications. | ||
copyDirectiveDefinitionToSchema({ | ||
definition, | ||
schema: subgraph.schema, | ||
copyDirectiveApplicationsInArguments: false, | ||
locationFilter: (loc) => isExecutableDirectiveLocation(loc), | ||
}); | ||
} | ||
} | ||
@@ -273,0 +293,0 @@ |
@@ -9,3 +9,2 @@ import { CorePurpose, FeatureDefinition, FeatureDefinitions, FeatureUrl, FeatureVersion } from "./coreSpec"; | ||
ErrGraphQLAPISchemaValidationFailed, | ||
executableDirectiveLocations, | ||
FieldDefinition, | ||
@@ -21,2 +20,3 @@ InputFieldDefinition, | ||
isScalarType, | ||
isTypeSystemDirectiveLocation, | ||
isVariable, | ||
@@ -687,7 +687,4 @@ NamedType, | ||
const executableDirectiveLocationSet = new Set(executableDirectiveLocations); | ||
for (const directive of schema.allDirectives()) { | ||
const typeSystemLocations = directive.locations.filter((loc) => | ||
!executableDirectiveLocationSet.has(loc) | ||
); | ||
const typeSystemLocations = directive.locations.filter((loc) => isTypeSystemDirectiveLocation(loc)); | ||
if (hasBuiltInName(directive)) { | ||
@@ -694,0 +691,0 @@ // Built-in directives (and their descendants) aren't allowed to be |
@@ -203,3 +203,3 @@ import { | ||
export function printDirectiveDefinition(directive: DirectiveDefinition, options: PrintOptions): string { | ||
export function printDirectiveDefinition(directive: DirectiveDefinition, options: PrintOptions = defaultPrintOptions): string { | ||
const locations = directive.locations.join(' | '); | ||
@@ -206,0 +206,0 @@ return `${printDescription(directive, options, null)}directive ${directive}${printArgs(directive.arguments(), options)}${directive.repeatable ? ' repeatable' : ''} on ${locations}`; |
@@ -30,4 +30,34 @@ /** | ||
} | ||
addAll(otherMap: MultiMap<K, V>): this { | ||
for (const [k, vs] of otherMap.entries()) { | ||
for (const v of vs) { | ||
this.add(k, v); | ||
} | ||
} | ||
return this; | ||
} | ||
} | ||
export class SetMultiMap<K, V> extends Map<K, Set<V>> { | ||
add(key: K, value: V): this { | ||
let values = this.get(key); | ||
if (!values) { | ||
values = new Set<V>(); | ||
this.set(key, values); | ||
} | ||
values.add(value); | ||
return this; | ||
} | ||
addAll(otherMap: SetMultiMap<K, V>): this { | ||
for (const [k, vs] of otherMap.entries()) { | ||
for (const v of vs) { | ||
this.add(k, v); | ||
} | ||
} | ||
return this; | ||
} | ||
} | ||
/** | ||
@@ -359,2 +389,6 @@ * Generic OrderedMap class that can sort keys based on an arbitrary sorting function | ||
export type Concrete<Type> = { | ||
[Property in keyof Type]-?: Type[Property]; | ||
}; | ||
// for use with Array.filter | ||
@@ -361,0 +395,0 @@ // Example: |
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 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
1909976
32057