UNPKG

@apollo/federation-internals

Version:
1,142 lines 113 kB
"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