@apollo/federation-internals
Version:
Apollo Federation internal utilities
1,142 lines • 113 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasSelectionWithPredicate = exports.operationToDocument = exports.parseOperationAST = exports.parseSelectionSet = exports.parseOperation = exports.operationFromDocument = exports.FragmentSpreadSelection = exports.FragmentSelection = exports.FieldSelection = exports.selectionOfElement = exports.selectionSetOfElement = exports.selectionSetOf = exports.allFieldDefinitionsInSelectionSet = exports.MutableSelectionSet = exports.SelectionSetUpdates = exports.SelectionSet = exports.ContainsResult = exports.NamedFragments = exports.NamedFragmentDefinition = exports.Operation = exports.concatOperationPaths = exports.conditionalDirectivesInOperationPath = exports.sameOperationPaths = exports.operationPathToStringPath = exports.FragmentElement = exports.Field = exports.DEFAULT_MIN_USAGES_TO_OPTIMIZE = void 0;
const graphql_1 = require("graphql");
const definitions_1 = require("./definitions");
const federation_1 = require("./federation");
const error_1 = require("./error");
const types_1 = require("./types");
const utils_1 = require("./utils");
const values_1 = require("./values");
const uuid_1 = require("uuid");
exports.DEFAULT_MIN_USAGES_TO_OPTIMIZE = 2;
function validate(condition, message, sourceAST) {
if (!condition) {
throw error_1.ERRORS.INVALID_GRAPHQL.err(message(), { nodes: sourceAST });
}
}
function haveSameDirectives(op1, op2) {
return (0, definitions_1.sameDirectiveApplications)(op1.appliedDirectives, op2.appliedDirectives);
}
class AbstractOperationElement extends definitions_1.DirectiveTargetElement {
constructor(schema, directives) {
super(schema, directives);
}
collectVariables(collector) {
this.collectVariablesInElement(collector);
this.collectVariablesInAppliedDirectives(collector);
}
rebaseOnOrError(parentType) {
return this.rebaseOn({ parentType, errorIfCannotRebase: true });
}
addAttachment(key, value) {
if (!this.attachments) {
this.attachments = new Map();
}
this.attachments.set(key, value);
}
getAttachment(key) {
var _a;
return (_a = this.attachments) === null || _a === void 0 ? void 0 : _a.get(key);
}
copyAttachmentsTo(elt) {
if (this.attachments) {
for (const [k, v] of this.attachments.entries()) {
elt.addAttachment(k, v);
}
}
}
keyForDirectives() {
return this.appliedDirectives.map((d) => keyForDirective(d)).join(' ');
}
}
class Field extends AbstractOperationElement {
constructor(definition, args, directives, alias) {
super(definition.schema(), directives);
this.definition = definition;
this.args = args;
this.alias = alias;
this.kind = 'Field';
}
collectVariablesInElement(collector) {
if (this.args) {
collector.collectInArguments(this.args);
}
}
get name() {
return this.definition.name;
}
argumentValue(name) {
return this.args ? this.args[name] : undefined;
}
responseName() {
return this.alias ? this.alias : this.name;
}
key() {
return this.responseName() + this.keyForDirectives();
}
asPathElement() {
return this.responseName();
}
get parentType() {
return this.definition.parent;
}
isLeafField() {
return (0, definitions_1.isLeafType)(this.baseType());
}
baseType() {
return (0, definitions_1.baseType)(this.definition.type);
}
copy() {
const newField = new Field(this.definition, this.args, this.appliedDirectives, this.alias);
this.copyAttachmentsTo(newField);
return newField;
}
withUpdatedArguments(newArgs) {
const newField = new Field(this.definition, { ...this.args, ...newArgs }, this.appliedDirectives, this.alias);
this.copyAttachmentsTo(newField);
return newField;
}
withUpdatedDefinition(newDefinition) {
const newField = new Field(newDefinition, this.args, this.appliedDirectives, this.alias);
this.copyAttachmentsTo(newField);
return newField;
}
withUpdatedAlias(newAlias) {
const newField = new Field(this.definition, this.args, this.appliedDirectives, newAlias);
this.copyAttachmentsTo(newField);
return newField;
}
withUpdatedDirectives(newDirectives) {
const newField = new Field(this.definition, this.args, newDirectives, this.alias);
this.copyAttachmentsTo(newField);
return newField;
}
argumentsToNodes() {
if (!this.args) {
return undefined;
}
const entries = Object.entries(this.args);
if (entries.length === 0) {
return undefined;
}
return entries.map(([n, v]) => {
return {
kind: graphql_1.Kind.ARGUMENT,
name: { kind: graphql_1.Kind.NAME, value: n },
value: (0, values_1.valueToAST)(v, this.definition.argument(n).type),
};
});
}
selects(definition, assumeValid = false, variableDefinitions, contextualArguments) {
(0, utils_1.assert)(assumeValid || variableDefinitions, 'Must provide variable definitions if validation is needed');
if (definition === this.definition) {
return true;
}
if (this.name !== definition.name) {
return false;
}
for (const argDef of definition.arguments()) {
const appliedValue = this.argumentValue(argDef.name);
if (appliedValue === undefined) {
if (argDef.defaultValue === undefined && !(0, definitions_1.isNullableType)(argDef.type) && (!contextualArguments || !(contextualArguments === null || contextualArguments === void 0 ? void 0 : contextualArguments.includes(argDef.name)))) {
return false;
}
}
else {
if (!assumeValid && !(0, values_1.isValidValue)(appliedValue, argDef, variableDefinitions)) {
return false;
}
}
}
if (!assumeValid && this.args) {
for (const [name, value] of Object.entries(this.args)) {
if (value !== null && definition.argument(name) === undefined) {
return false;
}
}
}
return true;
}
validate(variableDefinitions, validateContextualArgs) {
var _a;
validate(this.name === this.definition.name, () => `Field name "${this.name}" cannot select field "${this.definition.coordinate}: name mismatch"`);
for (const argDef of this.definition.arguments()) {
const appliedValue = this.argumentValue(argDef.name);
let isContextualArg = false;
const schema = this.definition.schema();
const fromContextDirective = (_a = (0, federation_1.federationMetadata)(schema)) === null || _a === void 0 ? void 0 : _a.fromContextDirective();
if (fromContextDirective && (0, federation_1.isFederationDirectiveDefinedInSchema)(fromContextDirective)) {
isContextualArg = argDef.appliedDirectivesOf(fromContextDirective).length > 0;
}
if (appliedValue === undefined) {
validate((isContextualArg && !validateContextualArgs) || argDef.defaultValue !== undefined || (0, definitions_1.isNullableType)(argDef.type), () => `Missing mandatory value for argument "${argDef.name}" of field "${this.definition.coordinate}" in selection "${this}"`);
}
else {
validate((isContextualArg && !validateContextualArgs) || (0, values_1.isValidValue)(appliedValue, argDef, variableDefinitions), () => `Invalid value ${(0, values_1.valueToString)(appliedValue)} for argument "${argDef.coordinate}" of type ${argDef.type}`);
}
}
if (this.args) {
for (const [name, value] of Object.entries(this.args)) {
validate(value === null || this.definition.argument(name) !== undefined, () => `Unknown argument "${name}" in field application of "${this.name}"`);
}
}
}
rebaseOn({ parentType, errorIfCannotRebase }) {
const fieldParent = this.definition.parent;
if (parentType === fieldParent) {
return this;
}
if (this.name === definitions_1.typenameFieldName) {
if ((0, definitions_1.possibleRuntimeTypes)(parentType).some((runtimeType) => (0, federation_1.isInterfaceObjectType)(runtimeType))) {
validate(!errorIfCannotRebase, () => `Cannot add selection of field "${this.definition.coordinate}" to selection set of parent type "${parentType}" that is potentially an interface object type at runtime`);
return undefined;
}
else {
return this.withUpdatedDefinition(parentType.typenameField());
}
}
const fieldDef = parentType.field(this.name);
const canRebase = this.canRebaseOn(parentType) && fieldDef;
if (!canRebase) {
validate(!errorIfCannotRebase, () => `Cannot add selection of field "${this.definition.coordinate}" to selection set of parent type "${parentType}"`);
return undefined;
}
return this.withUpdatedDefinition(fieldDef);
}
canRebaseOn(parentType) {
const fieldParentType = this.definition.parent;
return parentType.name === fieldParentType.name
|| (0, definitions_1.isInterfaceType)(fieldParentType)
|| (0, federation_1.isInterfaceObjectType)(fieldParentType);
}
typeIfAddedTo(parentType) {
var _a, _b, _c;
const fieldParentType = this.definition.parent;
if (parentType == fieldParentType) {
return this.definition.type;
}
if (this.name === definitions_1.typenameFieldName) {
return (_a = parentType.typenameField()) === null || _a === void 0 ? void 0 : _a.type;
}
const returnType = this.canRebaseOn(parentType)
? (_b = parentType.field(this.name)) === null || _b === void 0 ? void 0 : _b.type
: undefined;
const fromContextDirective = (_c = (0, federation_1.federationMetadata)(parentType.schema())) === null || _c === void 0 ? void 0 : _c.fromContextDirective();
if (fromContextDirective && (0, federation_1.isFederationDirectiveDefinedInSchema)(fromContextDirective)) {
const fieldInParent = parentType.field(this.name);
if (fieldInParent && fieldInParent.arguments()
.some(arg => arg.appliedDirectivesOf(fromContextDirective).length > 0 && (!this.args || this.args[arg.name] === undefined))) {
return undefined;
}
}
return returnType;
}
hasDefer() {
return false;
}
deferDirectiveArgs() {
return undefined;
}
withoutDefer() {
return this;
}
equals(that) {
if (this === that) {
return true;
}
return that.kind === 'Field'
&& this.name === that.name
&& this.alias === that.alias
&& (this.args ? that.args && (0, values_1.argumentsEquals)(this.args, that.args) : !that.args)
&& haveSameDirectives(this, that);
}
toString() {
const alias = this.alias ? this.alias + ': ' : '';
const entries = this.args ? Object.entries(this.args) : [];
const args = entries.length === 0
? ''
: '(' + entries.map(([n, v]) => { var _a; return `${n}: ${(0, values_1.valueToString)(v, (_a = this.definition.argument(n)) === null || _a === void 0 ? void 0 : _a.type)}`; }).join(', ') + ')';
return alias + this.name + args + this.appliedDirectivesToString();
}
}
exports.Field = Field;
function keyForDirective(directive, directivesNeverEqualToThemselves = ['defer']) {
if (directivesNeverEqualToThemselves.includes(directive.name)) {
return (0, uuid_1.v1)();
}
const entries = Object.entries(directive.arguments()).filter(([_, v]) => v !== undefined);
entries.sort(([n1], [n2]) => n1.localeCompare(n2));
const args = entries.length == 0 ? '' : '(' + entries.map(([n, v]) => `${n}: ${(0, values_1.valueToString)(v, directive.argumentType(n))}`).join(', ') + ')';
return `@${directive.name}${args}`;
}
class FragmentElement extends AbstractOperationElement {
constructor(sourceType, typeCondition, directives) {
super(sourceType.schema(), directives);
this.sourceType = sourceType;
this.kind = 'FragmentElement';
this.typeCondition = typeCondition !== undefined && typeof typeCondition === 'string'
? this.schema().type(typeCondition)
: typeCondition;
}
collectVariablesInElement(_) {
}
get parentType() {
return this.sourceType;
}
key() {
if (!this.computedKey) {
this.computedKey = '...' + (this.typeCondition ? ' on ' + this.typeCondition.name : '') + this.keyForDirectives();
}
return this.computedKey;
}
castedType() {
return this.typeCondition ? this.typeCondition : this.sourceType;
}
asPathElement() {
const condition = this.typeCondition;
return condition ? `... on ${condition}` : undefined;
}
withUpdatedSourceType(newSourceType) {
return this.withUpdatedTypes(newSourceType, this.typeCondition);
}
withUpdatedCondition(newCondition) {
return this.withUpdatedTypes(this.sourceType, newCondition);
}
withUpdatedTypes(newSourceType, newCondition) {
const newFragment = new FragmentElement(newSourceType, newCondition === null || newCondition === void 0 ? void 0 : newCondition.name, this.appliedDirectives);
this.copyAttachmentsTo(newFragment);
return newFragment;
}
withUpdatedDirectives(newDirectives) {
const newFragment = new FragmentElement(this.sourceType, this.typeCondition, newDirectives);
this.copyAttachmentsTo(newFragment);
return newFragment;
}
rebaseOn({ parentType, errorIfCannotRebase }) {
const fragmentParent = this.parentType;
const typeCondition = this.typeCondition;
if (parentType === fragmentParent) {
return this;
}
const { canRebase, rebasedCondition } = this.canRebaseOn(parentType);
if (!canRebase) {
validate(!errorIfCannotRebase, () => `Cannot add fragment of condition "${typeCondition}" (runtimes: [${(0, definitions_1.possibleRuntimeTypes)(typeCondition)}]) to parent type "${parentType}" (runtimes: ${(0, definitions_1.possibleRuntimeTypes)(parentType)})`);
return undefined;
}
return this.withUpdatedTypes(parentType, rebasedCondition);
}
canRebaseOn(parentType) {
if (!this.typeCondition) {
return { canRebase: true, rebasedCondition: undefined };
}
const rebasedCondition = parentType.schema().type(this.typeCondition.name);
if (!rebasedCondition || !(0, definitions_1.isCompositeType)(rebasedCondition) || !(0, definitions_1.runtimeTypesIntersects)(parentType, rebasedCondition)) {
return { canRebase: false };
}
return { canRebase: true, rebasedCondition };
}
castedTypeIfAddedTo(parentType) {
if (parentType == this.parentType) {
return this.castedType();
}
const { canRebase, rebasedCondition } = this.canRebaseOn(parentType);
return canRebase ? (rebasedCondition ? rebasedCondition : parentType) : undefined;
}
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);
this.copyAttachmentsTo(updated);
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 deferDirective = this.schema().deferDirective();
const updatedDirectives = this.appliedDirectives
.filter((d) => d.name !== deferDirective.name)
.concat(new definitions_1.Directive(deferDirective.name, newDeferArgs));
const updated = new FragmentElement(this.sourceType, this.typeCondition, updatedDirectives);
this.copyAttachmentsTo(updated);
return updated;
}
equals(that) {
var _a, _b;
if (this === that) {
return true;
}
return that.kind === 'FragmentElement'
&& ((_a = this.typeCondition) === null || _a === void 0 ? void 0 : _a.name) === ((_b = that.typeCondition) === null || _b === void 0 ? void 0 : _b.name)
&& haveSameDirectives(this, that);
}
toString() {
return '...' + (this.typeCondition ? ' on ' + this.typeCondition : '') + this.appliedDirectivesToString();
}
}
exports.FragmentElement = FragmentElement;
function operationPathToStringPath(path) {
return path
.filter((p) => !(p.kind === 'FragmentElement' && !p.typeCondition))
.map((p) => { var _a; return p.kind === 'Field' ? p.responseName() : `... on ${(_a = p.typeCondition) === null || _a === void 0 ? void 0 : _a.coordinate}`; });
}
exports.operationPathToStringPath = operationPathToStringPath;
function sameOperationPaths(p1, p2) {
if (p1 === p2) {
return true;
}
if (p1.length !== p2.length) {
return false;
}
for (let i = 0; i < p1.length; i++) {
if (!p1[i].equals(p2[i])) {
return false;
}
}
return true;
}
exports.sameOperationPaths = sameOperationPaths;
function conditionalDirectivesInOperationPath(path) {
return path.map((e) => e.appliedDirectives).flat().filter((d) => (0, definitions_1.isConditionalDirective)(d));
}
exports.conditionalDirectivesInOperationPath = conditionalDirectivesInOperationPath;
function concatOperationPaths(head, tail) {
if (head.length === 0) {
return tail;
}
if (tail.length === 0) {
return head;
}
const lastOfHead = head[head.length - 1];
const conditionals = conditionalDirectivesInOperationPath(head);
let firstOfTail = tail[0];
while (firstOfTail && isUselessFollowupElement(lastOfHead, firstOfTail, conditionals)) {
tail = tail.slice(1);
firstOfTail = tail[0];
}
return head.concat(tail);
}
exports.concatOperationPaths = concatOperationPaths;
function isUselessFollowupElement(first, followup, conditionals) {
const typeOfFirst = first.kind === 'Field'
? first.baseType()
: first.typeCondition;
return !!typeOfFirst
&& followup.kind === 'FragmentElement'
&& !!followup.typeCondition
&& (followup.appliedDirectives.length === 0 || (0, definitions_1.isDirectiveApplicationsSubset)(conditionals, followup.appliedDirectives))
&& (0, types_1.isSubtype)(followup.typeCondition, typeOfFirst);
}
function computeFragmentsDependents(fragments) {
const reverseDeps = new utils_1.SetMultiMap();
for (const fragment of fragments.definitions()) {
for (const dependency of fragment.fragmentUsages().keys()) {
reverseDeps.add(dependency, fragment.name);
}
}
return reverseDeps;
}
function clearKeptFragments(usages, fragments, minUsagesToOptimize) {
let toCheck = Array.from(usages.entries()).filter(([_, count]) => count >= minUsagesToOptimize).map(([name, _]) => name);
while (toCheck.length > 0) {
const newToCheck = [];
for (const name of toCheck) {
usages.delete(name);
const ownUsages = fragments.get(name).fragmentUsages();
for (const [otherName, otherCount] of ownUsages.entries()) {
const prevCount = usages.get(otherName);
if (prevCount !== undefined) {
const newCount = prevCount + otherCount;
usages.set(otherName, newCount);
if (prevCount < minUsagesToOptimize && newCount >= minUsagesToOptimize) {
newToCheck.push(otherName);
}
}
}
}
toCheck = newToCheck;
}
}
function computeFragmentsToKeep(selectionSet, fragments, minUsagesToOptimize) {
const usages = new Map();
selectionSet.collectUsedFragmentNames(usages);
if (usages.size === 0) {
return null;
}
for (const fragment of fragments.definitions()) {
if (usages.get(fragment.name) === undefined) {
usages.set(fragment.name, 0);
}
}
const reverseDependencies = computeFragmentsDependents(fragments);
const toExpand = new Set;
let shouldContinue = true;
while (shouldContinue) {
shouldContinue = false;
clearKeptFragments(usages, fragments, minUsagesToOptimize);
for (const name of (0, utils_1.mapKeys)(usages)) {
const count = usages.get(name);
if (count === 0) {
continue;
}
if (count >= minUsagesToOptimize) {
shouldContinue = true;
break;
}
const fragmentsUsingName = reverseDependencies.get(name);
if (!fragmentsUsingName || [...fragmentsUsingName].every((fragName) => toExpand.has(fragName) || !usages.get(fragName))) {
toExpand.add(name);
usages.delete(name);
shouldContinue = true;
const nameUsages = fragments.get(name).fragmentUsages();
for (const [otherName, otherCount] of nameUsages.entries()) {
const prev = usages.get(otherName);
if (prev !== undefined) {
usages.set(otherName, prev + count * otherCount);
}
}
}
}
}
for (const name of usages.keys()) {
toExpand.add(name);
}
return toExpand.size === 0 ? fragments : fragments.filter((f) => !toExpand.has(f.name));
}
class Operation extends definitions_1.DirectiveTargetElement {
constructor(schema, rootKind, selectionSet, variableDefinitions, fragments, name, directives = []) {
super(schema, directives);
this.rootKind = rootKind;
this.selectionSet = selectionSet;
this.variableDefinitions = variableDefinitions;
this.fragments = fragments;
this.name = name;
}
withUpdatedSelectionSet(newSelectionSet) {
if (this.selectionSet === newSelectionSet) {
return this;
}
return new Operation(this.schema(), this.rootKind, newSelectionSet, this.variableDefinitions, this.fragments, this.name, this.appliedDirectives);
}
collectUndefinedVariablesFromFragments(fragments) {
const collector = new definitions_1.VariableCollector();
for (const namedFragment of fragments.definitions()) {
namedFragment.selectionSet.usedVariables().forEach(v => {
if (!this.variableDefinitions.definition(v)) {
collector.add(v);
}
});
}
return collector.variables();
}
withUpdatedSelectionSetAndFragments(newSelectionSet, newFragments, allAvailableVariables) {
if (this.selectionSet === newSelectionSet && newFragments === this.fragments) {
return this;
}
let newVariableDefinitions = this.variableDefinitions;
if (allAvailableVariables && newFragments) {
const undefinedVariables = this.collectUndefinedVariablesFromFragments(newFragments);
if (undefinedVariables.length > 0) {
newVariableDefinitions = new definitions_1.VariableDefinitions();
newVariableDefinitions.addAll(this.variableDefinitions);
newVariableDefinitions.addAll(allAvailableVariables.filter(undefinedVariables));
}
}
return new Operation(this.schema(), this.rootKind, newSelectionSet, newVariableDefinitions, newFragments, this.name, this.appliedDirectives);
}
optimize(fragments, minUsagesToOptimize = exports.DEFAULT_MIN_USAGES_TO_OPTIMIZE, allAvailableVariables) {
(0, utils_1.assert)(minUsagesToOptimize >= 1, `Expected 'minUsagesToOptimize' to be at least 1, but got ${minUsagesToOptimize}`);
if (!fragments || fragments.isEmpty()) {
return this;
}
let optimizedSelection = this.selectionSet.optimize(fragments);
if (optimizedSelection === this.selectionSet) {
return this;
}
let finalFragments = computeFragmentsToKeep(optimizedSelection, fragments, minUsagesToOptimize);
if (finalFragments !== null && (finalFragments === null || finalFragments === void 0 ? void 0 : finalFragments.size) !== fragments.size) {
optimizedSelection = optimizedSelection.expandFragments(finalFragments);
optimizedSelection = optimizedSelection.normalize({ parentType: optimizedSelection.parentType });
if (finalFragments) {
let beforeRemoval;
do {
beforeRemoval = finalFragments;
const usages = new Map();
optimizedSelection.collectUsedFragmentNames(usages);
finalFragments.collectUsedFragmentNames(usages);
finalFragments = finalFragments.filter((f) => { var _a; return ((_a = usages.get(f.name)) !== null && _a !== void 0 ? _a : 0) > 0; });
} while (finalFragments && finalFragments.size < beforeRemoval.size);
}
}
return this.withUpdatedSelectionSetAndFragments(optimizedSelection, finalFragments !== null && finalFragments !== void 0 ? finalFragments : undefined, allAvailableVariables);
}
generateQueryFragments() {
const [minimizedSelectionSet, fragments] = this.selectionSet.minimizeSelectionSet();
return new Operation(this.schema(), this.rootKind, minimizedSelectionSet, this.variableDefinitions, fragments, this.name, this.appliedDirectives);
}
expandAllFragments() {
const expanded = this.selectionSet.expandFragments();
return this.withUpdatedSelectionSetAndFragments(expanded.normalize({ parentType: expanded.parentType }), undefined);
}
normalize() {
return this.withUpdatedSelectionSet(this.selectionSet.normalize({ parentType: this.selectionSet.parentType }));
}
withoutDefer(labelsToRemove) {
return this.withUpdatedSelectionSet(this.selectionSet.withoutDefer(labelsToRemove));
}
withNormalizedDefer() {
const normalizer = new DeferNormalizer();
const { hasDefers, hasNonLabelledOrConditionalDefers } = normalizer.init(this.selectionSet);
let updatedOperation = this;
if (hasNonLabelledOrConditionalDefers) {
updatedOperation = this.withUpdatedSelectionSet(this.selectionSet.withNormalizedDefer(normalizer));
}
return {
operation: updatedOperation,
hasDefers,
assignedDeferLabels: normalizer.assignedLabels,
deferConditions: normalizer.deferConditions,
};
}
collectDefaultedVariableValues() {
const defaultedVariableValues = Object.create(null);
for (const { variable, defaultValue } of this.variableDefinitions.definitions()) {
if (defaultValue !== undefined) {
defaultedVariableValues[variable.name] = defaultValue;
}
}
return defaultedVariableValues;
}
toString(expandFragments = false, prettyPrint = true) {
return this.selectionSet.toOperationString(this.rootKind, this.variableDefinitions, this.fragments, this.name, this.appliedDirectives, expandFragments, prettyPrint);
}
}
exports.Operation = Operation;
class NamedFragmentDefinition extends definitions_1.DirectiveTargetElement {
constructor(schema, name, typeCondition, directives) {
super(schema, directives);
this.name = name;
this.typeCondition = typeCondition;
this.expandedSelectionSetsAtTypesCache = new Map();
}
setSelectionSet(selectionSet) {
(0, utils_1.assert)(!this._selectionSet, 'Attempting to set the selection set of a fragment definition already built');
(0, utils_1.assert)(selectionSet.parentType === this.typeCondition, `Fragment selection set parent is ${selectionSet.parentType} differs from the fragment condition type ${this.typeCondition}`);
this._selectionSet = selectionSet;
return this;
}
get selectionSet() {
(0, utils_1.assert)(this._selectionSet, () => `Trying to access fragment definition ${this.name} before it is fully built`);
return this._selectionSet;
}
withUpdatedSelectionSet(newSelectionSet) {
return new NamedFragmentDefinition(this.schema(), this.name, this.typeCondition).setSelectionSet(newSelectionSet);
}
fragmentUsages() {
if (!this._fragmentUsages) {
this._fragmentUsages = new Map();
this.selectionSet.collectUsedFragmentNames(this._fragmentUsages);
}
return this._fragmentUsages;
}
collectUsedFragmentNames(collector) {
const usages = this.fragmentUsages();
for (const [name, count] of usages.entries()) {
const prevCount = collector.get(name);
collector.set(name, prevCount ? prevCount + count : count);
}
}
collectVariables(collector) {
this.selectionSet.collectVariables(collector);
this.collectVariablesInAppliedDirectives(collector);
}
toFragmentDefinitionNode() {
return {
kind: graphql_1.Kind.FRAGMENT_DEFINITION,
name: {
kind: graphql_1.Kind.NAME,
value: this.name
},
typeCondition: {
kind: graphql_1.Kind.NAMED_TYPE,
name: {
kind: graphql_1.Kind.NAME,
value: this.typeCondition.name
}
},
selectionSet: this.selectionSet.toSelectionSetNode()
};
}
canApplyDirectlyAtType(type) {
if ((0, types_1.sameType)(type, this.typeCondition)) {
return true;
}
if (!(0, definitions_1.isAbstractType)(this.typeCondition)) {
return false;
}
const conditionRuntimes = (0, definitions_1.possibleRuntimeTypes)(this.typeCondition);
const typeRuntimes = (0, definitions_1.possibleRuntimeTypes)(type);
if (conditionRuntimes.length < typeRuntimes.length
|| !typeRuntimes.every((t1) => conditionRuntimes.some((t2) => (0, types_1.sameType)(t1, t2)))) {
return false;
}
return (0, definitions_1.isObjectType)(type) || (0, definitions_1.isUnionType)(this.typeCondition);
}
expandedSelectionSet() {
if (!this._expandedSelectionSet) {
this._expandedSelectionSet = this.selectionSet.expandFragments();
}
return this._expandedSelectionSet;
}
expandedSelectionSetAtType(type) {
let cached = this.expandedSelectionSetsAtTypesCache.get(type.name);
if (!cached) {
cached = this.computeExpandedSelectionSetAtType(type);
this.expandedSelectionSetsAtTypesCache.set(type.name, cached);
}
return cached;
}
computeExpandedSelectionSetAtType(type) {
const expandedSelectionSet = this.expandedSelectionSet();
const selectionSet = expandedSelectionSet.normalize({ parentType: type });
if (!(0, definitions_1.isObjectType)(this.typeCondition)) {
const validator = FieldsConflictValidator.build(expandedSelectionSet);
return { selectionSet, validator };
}
const trimmed = expandedSelectionSet.minus(selectionSet);
const validator = trimmed.isEmpty() ? undefined : FieldsConflictValidator.build(trimmed);
return { selectionSet, validator };
}
includes(otherFragment) {
if (this.name === otherFragment) {
return false;
}
if (!this._includedFragmentNames) {
this._includedFragmentNames = this.computeIncludedFragmentNames();
}
return this._includedFragmentNames.has(otherFragment);
}
computeIncludedFragmentNames() {
const included = new Set();
for (const selection of this.selectionSet.selections()) {
if (selection instanceof FragmentSpreadSelection) {
included.add(selection.namedFragment.name);
}
}
return included;
}
toString(indent) {
return `fragment ${this.name} on ${this.typeCondition}${this.appliedDirectivesToString()} ${this.selectionSet.toString(false, true, indent)}`;
}
}
exports.NamedFragmentDefinition = NamedFragmentDefinition;
class NamedFragments {
constructor() {
this.fragments = new utils_1.MapWithCachedArrays();
}
isEmpty() {
return this.size === 0;
}
get size() {
return this.fragments.size;
}
names() {
return this.fragments.keys();
}
add(fragment) {
if (this.fragments.has(fragment.name)) {
throw error_1.ERRORS.INVALID_GRAPHQL.err(`Duplicate fragment name '${fragment}'`);
}
this.fragments.set(fragment.name, fragment);
}
addIfNotExist(fragment) {
if (!this.fragments.has(fragment.name)) {
this.fragments.set(fragment.name, fragment);
}
}
maybeApplyingDirectlyAtType(type) {
return this.fragments.values().filter(f => f.canApplyDirectlyAtType(type));
}
get(name) {
return this.fragments.get(name);
}
has(name) {
return this.fragments.has(name);
}
definitions() {
return this.fragments.values();
}
collectUsedFragmentNames(collector) {
for (const fragment of this.definitions()) {
fragment.collectUsedFragmentNames(collector);
}
}
map(mapper) {
const mapped = new NamedFragments();
for (const def of this.fragments.values()) {
mapped.fragments.set(def.name, mapper(def));
}
return mapped;
}
mapInDependencyOrder(mapper) {
const fragmentsMap = new Map();
for (const fragment of this.definitions()) {
fragmentsMap.set(fragment.name, {
fragment,
dependsOn: Array.from(fragment.fragmentUsages().keys()),
});
}
const removedFragments = new Set();
const mappedFragments = new NamedFragments();
while (fragmentsMap.size > 0) {
for (const [name, info] of fragmentsMap) {
if (info.dependsOn.every((n) => mappedFragments.has(n) || removedFragments.has(n))) {
const mapped = mapper(info.fragment, mappedFragments);
fragmentsMap.delete(name);
if (!mapped) {
removedFragments.add(name);
}
else {
mappedFragments.add(mapped);
}
break;
}
}
}
return mappedFragments.isEmpty() ? undefined : mappedFragments;
}
mapToExpandedSelectionSets(mapper) {
return this.mapInDependencyOrder((fragment, newFragments) => {
const mappedSelectionSet = mapper(fragment.selectionSet.expandFragments().normalize({ parentType: fragment.typeCondition }));
if (!mappedSelectionSet) {
return undefined;
}
const reoptimizedSelectionSet = mappedSelectionSet.optimize(newFragments);
return fragment.withUpdatedSelectionSet(reoptimizedSelectionSet);
});
}
rebaseOn(schema) {
return this.mapInDependencyOrder((fragment, newFragments) => {
const rebasedType = schema.type(fragment.selectionSet.parentType.name);
if (!rebasedType || !(0, definitions_1.isCompositeType)(rebasedType)) {
return undefined;
}
let rebasedSelection = fragment.selectionSet.rebaseOn({ parentType: rebasedType, fragments: newFragments, errorIfCannotRebase: false });
rebasedSelection = rebasedSelection.normalize({ parentType: rebasedType });
return rebasedSelection.isWorthUsing()
? new NamedFragmentDefinition(schema, fragment.name, rebasedType).setSelectionSet(rebasedSelection)
: undefined;
});
}
filter(predicate) {
return this.mapInDependencyOrder((fragment, newFragments) => {
if (predicate(fragment)) {
const updatedSelectionSet = fragment.selectionSet.expandFragments(newFragments);
return updatedSelectionSet === fragment.selectionSet
? fragment
: fragment.withUpdatedSelectionSet(updatedSelectionSet.normalize({ parentType: updatedSelectionSet.parentType }));
}
else {
return undefined;
}
});
}
validate(variableDefinitions) {
for (const fragment of this.fragments.values()) {
fragment.selectionSet.validate(variableDefinitions);
}
}
toFragmentDefinitionNodes() {
return this.definitions().map(f => f.toFragmentDefinitionNode());
}
toString(indent) {
return this.definitions().map(f => f.toString(indent)).join('\n\n');
}
}
exports.NamedFragments = NamedFragments;
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 || deferArgs.if !== undefined) {
hasNonLabelledOrConditionalDefers = true;
}
if (deferArgs.label) {
this.usedLabels.add(deferArgs.label);
}
}
}
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);
}
}
var ContainsResult;
(function (ContainsResult) {
ContainsResult[ContainsResult["NOT_CONTAINED"] = 0] = "NOT_CONTAINED";
ContainsResult[ContainsResult["STRICTLY_CONTAINED"] = 1] = "STRICTLY_CONTAINED";
ContainsResult[ContainsResult["EQUAL"] = 2] = "EQUAL";
})(ContainsResult || (exports.ContainsResult = ContainsResult = {}));
class SelectionSet {
constructor(parentType, keyedSelections = new Map()) {
this.parentType = parentType;
this._keyedSelections = keyedSelections;
this._selections = (0, utils_1.mapValues)(keyedSelections);
}
minimizeSelectionSet(namedFragments = new NamedFragments(), seenSelections = new Map()) {
const minimizedSelectionSet = this.lazyMap((selection) => {
var _a;
if (selection.kind === 'FragmentSelection' && selection.element.typeCondition && selection.element.appliedDirectives.length === 0
&& selection.selectionSet && selection.selectionSet.isWorthUsing()) {
const mockHashCode = `on${selection.element.typeCondition}` + selection.selectionSet.selections().length;
const equivalentSelectionSetCandidates = seenSelections.get(mockHashCode);
if (equivalentSelectionSetCandidates) {
const match = equivalentSelectionSetCandidates.find(([candidateSet]) => candidateSet.equals(selection.selectionSet));
if (match) {
return new FragmentSpreadSelection(this.parentType, namedFragments, match[1], []);
}
}
const [minimizedSelectionSet] = selection.selectionSet.minimizeSelectionSet(namedFragments, seenSelections);
const updatedEquivalentSelectionSetCandidates = seenSelections.get(mockHashCode);
const fragmentDefinition = new NamedFragmentDefinition(this.parentType.schema(), `_generated_${mockHashCode}_${(_a = updatedEquivalentSelectionSetCandidates === null || updatedEquivalentSelectionSetCandidates === void 0 ? void 0 : updatedEquivalentSelectionSetCandidates.length) !== null && _a !== void 0 ? _a : 0}`, selection.element.typeCondition).setSelectionSet(minimizedSelectionSet);
namedFragments.add(fragmentDefinition);
if (updatedEquivalentSelectionSetCandidates) {
updatedEquivalentSelectionSetCandidates.push([selection.selectionSet, fragmentDefinition]);
}
else {
seenSelections.set(mockHashCode, [[selection.selectionSet, fragmentDefinition]]);
}
return new FragmentSpreadSelection(this.parentType, namedFragments, fragmentDefinition, []);
}
if (selection.selectionSet) {
selection = selection.withUpdatedSelectionSet(selection.selectionSet.minimizeSelectionSet(namedFragments, seenSelections)[0]);
}
return selection;
});
return [minimizedSelectionSet, namedFragments];
}
selectionsInReverseOrder() {
const length = this._selections.length;
const reversed = new Array(length);
for (let i = 0; i < length; i++) {
reversed[i] = this._selections[length - i - 1];
}
return reversed;
}
selections() {
return this._selections;
}
hasTopLevelTypenameField() {
return this._keyedSelections.has(definitions_1.typenameFieldName);
}
withoutTopLevelTypenameField() {
if (!this.hasTopLevelTypenameField) {
return this;
}
const newKeyedSelections = new Map();
for (const [key, selection] of this._keyedSelections) {
if (key !== definitions_1.typenameFieldName) {
newKeyedSelections.set(key, selection);
}
}
return new SelectionSet(this.parentType, newKeyedSelections);
}
fieldsInSet() {
const fields = new Array();
for (const selection of this.selections()) {
if (selection.kind === 'FieldSelection') {
fields.push({ path: [], field: selection });
}
else {
const condition = selection.element.typeCondition;
const header = condition ? [`... on ${condition}`] : [];
for (const { path, field } of selection.selectionSet.fieldsInSet()) {
fields.push({ path: header.concat(path), field });
}
}
}
return fields;
}
fieldsByResponseName() {
const byResponseName = new utils_1.MultiMap();
this.collectFieldsByResponseName(byResponseName);
return byResponseName;
}
collectFieldsByResponseName(collector) {
for (const selection of this.selections()) {
if (selection.kind === 'FieldSelection') {
collector.add(selection.element.responseName(), selection);
}
else {
selection.selectionSet.collectFieldsByResponseName(collector);
}
}
}
usedVariables() {
const collector = new definitions_1.VariableCollector();
this.collectVariables(collector);
return collector.variables();
}
collectVariables(collector) {
for (const selection of this.selections()) {
selection.collectVariables(collector);
}
}
collectUsedFragmentNames(collector) {
for (const selection of this.selections()) {
selection.collectUsedFragmentNames(collector);
}
}
optimize(fragments) {
if (!fragments || fragments.isEmpty()) {
return this;
}
const wrapped = new InlineFragmentSelection(new FragmentElement(this.parentType, this.parentType), this);
const validator = FieldsConflictMultiBranchValidator.ofInitial(FieldsConflictValidator.build(this));
const optimized = wrapped.optimize(fragments, validator);
return optimized instanceof FragmentSpreadSelection
? selectionSetOf(this.parentType, optimized)
: optimized.selectionSet;
}
optimizeSelections(fragments, validator) {
return this.lazyMap((selection) => selection.optimize(fragments, validator));
}
expandFragments(updatedFragments) {
return this.lazyMap((selection) => selection.expandFragments(updatedFragments));
}
normalize({ parentType, recursive }) {
return this.lazyMap((selection) => selection.normalize({ parentType, recursive }), { parentType });
}
lazyMap(mapper, options) {
var _a;
const selections = this.selections();
let updatedSelections = undefined;
for (let i = 0; i < selections.length; i++) {
const selection = selections[i];
const updated = mapper(selection);
if (updated !== selection && !updatedSelections) {
updatedSelections = new SelectionSetUpdates();
for (let j = 0; j < i; j++) {
updatedSelections.add(selections[j]);
}
}
if (!!updated && updatedSelections) {
updatedSelections.add(updated);
}
}
if (!updatedSelections) {
return this;
}
return updatedSelections.toSelectionSet((_a = options === null || options === void 0 ? void 0 : options.parentType) !== null && _a !== void 0 ? _a : this.parentType);
}
withoutDefer(labelsToRemove) {
return this.lazyMap((selection) => selection.withoutDefer(labelsToRemove));
}
withNormalizedDefer(normalizer) {
return this.lazyMap((selection) => selection.withNormalizedDefer(normalizer));
}
hasDefer() {
return this.selections().some((s) => s.hasDefer());
}
filter(predicate) {
return this.lazyMap((selection) => predicate(selection) ? selection : undefined);
}
filterRecursiveDepthFirst(predicate) {
return this.lazyMap((selection) => selection.filterRecursiveDepthFirst(predicate));
}
withoutEmptyBranches() {
const updated = this.filterRecursiveDepthFirst((selection) => { var _a; return ((_a = selection.selectionSet) === null || _a === void 0 ? void 0 : _a.isEmpty()) !== true; });
return updated.isEmpty() ? undefined : updated;
}
rebaseOn({ parentType, fragments, errorIfCannotRebase, }) {
if (this.parentType === parentType) {
return this;
}
const newSelections = new Map();
for (const selection of this.selections()) {
const rebasedSelection = selection.rebaseOn({ parentType, fragments, errorIfCannotRebase });
if (rebase