UNPKG

@theguild/federation-composition

Version:
165 lines (164 loc) 6.59 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SelectionResolver = exports.Selection = void 0; const graphql_1 = require("graphql"); const helpers_js_1 = require("../../../../subgraph/helpers.js"); const state_js_1 = require("../../../../utils/state.js"); class Selection { typeName; source; selectionSet; constructor(typeName, source, selectionSet) { this.typeName = typeName; this.source = source; this.selectionSet = selectionSet; } contains(typeName, fieldName) { return this._contains(typeName, fieldName, this.selectionSet); } equals(other) { if (this.typeName !== other.typeName) { return false; } if (this.source === other.source) { return true; } return this._selectionSetEqual(this.selectionSet, other.selectionSet); } _selectionSetEqual(selectionSet, otherSelectionSet) { if (selectionSet.length !== otherSelectionSet.length) { return false; } for (let i = 0; i < selectionSet.length; i++) { const selectionNode = selectionSet[i]; const otherSelectionNode = otherSelectionSet[i]; if (selectionNode.kind !== otherSelectionNode.kind) { return false; } if (selectionNode.typeName !== otherSelectionNode.typeName) { return false; } if (selectionNode.kind === 'field' && otherSelectionNode.kind === 'field' && selectionNode.fieldName !== otherSelectionNode.fieldName) { return false; } const areEqual = Array.isArray(selectionNode.selectionSet) && Array.isArray(otherSelectionNode.selectionSet) ? this._selectionSetEqual(selectionNode.selectionSet, otherSelectionNode.selectionSet) : selectionNode.selectionSet === otherSelectionNode.selectionSet; if (!areEqual) { return false; } } return true; } _contains(typeName, fieldName, selectionSet) { return selectionSet.some(s => (s.kind === 'field' && s.typeName === typeName && s.fieldName === fieldName) || (s.selectionSet ? this._contains(typeName, fieldName, s.selectionSet) : false)); } toString() { return this.source.replace(/\s+/g, ' '); } } exports.Selection = Selection; class SelectionResolver { supergraphState; cache = new Map(); constructor(supergraphState) { this.supergraphState = supergraphState; } resolve(typeName, keyFields) { const key = this.keyFactory(typeName, keyFields); if (this.cache.has(key)) { return this.cache.get(key); } const typeState = this.supergraphState.objectTypes.get(typeName) ?? this.supergraphState.interfaceTypes.get(typeName); if (!typeState) { throw new Error(`Expected an object/interface type when resolving keyFields of ${typeName}`); } const selectionSetNode = (0, helpers_js_1.parseFields)(keyFields); if (!selectionSetNode) { throw new Error(`Expected a selection set when resolving keyFields of ${typeName}`); } const fields = new Selection(typeName, keyFields, this.resolveSelectionSetNode(typeName, selectionSetNode)); this.cache.set(key, fields); return fields; } keyFactory(typeName, keyFields) { return `${typeName}/${keyFields}`; } resolveFieldNode(typeName, fieldNode, selectionSet) { if (fieldNode.name.value === '__typename') { return; } const typeState = this.supergraphState.objectTypes.get(typeName) ?? this.supergraphState.interfaceTypes.get(typeName); if (!typeState) { throw new Error(`Type "${typeName}" is not defined.`); } if (!typeState.fields.has(fieldNode.name.value)) { throw new Error(`Type "${typeName.toString()}" does not have field "${fieldNode.name.value}".`); } if (fieldNode.selectionSet) { const outputType = (0, state_js_1.stripTypeModifiers)(typeState.fields.get(fieldNode.name.value).type); selectionSet.push({ kind: 'field', fieldName: fieldNode.name.value, typeName, selectionSet: this.resolveSelectionSetNode(outputType, fieldNode.selectionSet), }); } else { selectionSet.push({ kind: 'field', typeName, fieldName: fieldNode.name.value, selectionSet: null, }); } } resolveInlineFragmentNode(fragmentNode, selectionSet) { if (!fragmentNode.typeCondition?.name.value) { throw new Error(`Inline fragment without type condition is not supported.`); } const typeName = fragmentNode.typeCondition.name.value; const typeState = this.supergraphState.objectTypes.get(typeName) ?? this.supergraphState.interfaceTypes.get(typeName); if (!typeState) { throw new Error(`Type "${typeName}" is not defined.`); } selectionSet.push({ kind: 'fragment', typeName, selectionSet: this.resolveSelectionSetNode(typeName, fragmentNode.selectionSet), }); } resolveSelectionSetNode(typeName, selectionSetNode, selectionSet = []) { for (const selection of selectionSetNode.selections) { if (selection.kind === graphql_1.Kind.FIELD) { this.resolveFieldNode(typeName, selection, selectionSet); } else if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT) { this.resolveInlineFragmentNode(selection, selectionSet); } else { throw new Error(`Fragment spread is not supported.`); } } return this.sort(selectionSet); } sort(selectionSet) { return selectionSet.sort((a, b) => { if (a.kind === b.kind) { return a.kind === 'field' && b.kind === 'field' ? `${a.typeName}.${a.fieldName}`.localeCompare(`${b.typeName}.${b.fieldName}`) : a.typeName.localeCompare(b.typeName); } return a.kind === 'field' ? -1 : 1; }); } } exports.SelectionResolver = SelectionResolver;