UNPKG

@graphql-tools/wrap

Version:

A set of utils for faster development of GraphQL tools

212 lines (211 loc) • 9.41 kB
import { Kind, GraphQLNonNull, } from 'graphql'; import { appendObjectFields, selectObjectFields, modifyObjectFields, relocatedError, } from '@graphql-tools/utils'; import { defaultMergedResolver } from '@graphql-tools/delegate'; import MapFields from './MapFields.js'; import { defaultCreateProxyingResolver } from '../generateProxyingResolvers.js'; export default class WrapFields { constructor(outerTypeName, wrappingFieldNames, wrappingTypeNames, fieldNames, prefix = 'gqtld') { this.outerTypeName = outerTypeName; this.wrappingFieldNames = wrappingFieldNames; this.wrappingTypeNames = wrappingTypeNames; this.numWraps = wrappingFieldNames.length; this.fieldNames = fieldNames; const remainingWrappingFieldNames = this.wrappingFieldNames.slice(); const outerMostWrappingFieldName = remainingWrappingFieldNames.shift(); if (outerMostWrappingFieldName == null) { throw new Error(`Cannot wrap fields, no wrapping field name provided.`); } this.transformer = new MapFields({ [outerTypeName]: { [outerMostWrappingFieldName]: (fieldNode, fragments, transformationContext) => hoistFieldNodes({ fieldNode, path: remainingWrappingFieldNames, fieldNames, fragments, transformationContext: transformationContext, prefix, }), }, }, { [outerTypeName]: (value, context) => dehoistValue(value, context), }, (errors, context) => dehoistErrors(errors, context)); } transformSchema(originalWrappingSchema, subschemaConfig) { var _a, _b, _c, _d; const fieldNames = this.fieldNames; const targetFieldConfigMap = selectObjectFields(originalWrappingSchema, this.outerTypeName, !fieldNames ? () => true : fieldName => fieldNames.includes(fieldName)); const newTargetFieldConfigMap = Object.create(null); for (const fieldName in targetFieldConfigMap) { const field = targetFieldConfigMap[fieldName]; const newField = { ...field, resolve: defaultMergedResolver, }; newTargetFieldConfigMap[fieldName] = newField; } let wrapIndex = this.numWraps - 1; let wrappingTypeName = this.wrappingTypeNames[wrapIndex]; let wrappingFieldName = this.wrappingFieldNames[wrapIndex]; let newSchema = appendObjectFields(originalWrappingSchema, wrappingTypeName, newTargetFieldConfigMap); for (wrapIndex--; wrapIndex > -1; wrapIndex--) { const nextWrappingTypeName = this.wrappingTypeNames[wrapIndex]; newSchema = appendObjectFields(newSchema, nextWrappingTypeName, { [wrappingFieldName]: { type: new GraphQLNonNull(newSchema.getType(wrappingTypeName)), resolve: defaultMergedResolver, }, }); wrappingTypeName = nextWrappingTypeName; wrappingFieldName = this.wrappingFieldNames[wrapIndex]; } const wrappingRootField = this.outerTypeName === ((_a = originalWrappingSchema.getQueryType()) === null || _a === void 0 ? void 0 : _a.name) || this.outerTypeName === ((_b = originalWrappingSchema.getMutationType()) === null || _b === void 0 ? void 0 : _b.name); let resolve; if (wrappingRootField) { const targetSchema = subschemaConfig.schema; const operation = this.outerTypeName === ((_c = targetSchema.getQueryType()) === null || _c === void 0 ? void 0 : _c.name) ? 'query' : 'mutation'; const createProxyingResolver = (_d = subschemaConfig.createProxyingResolver) !== null && _d !== void 0 ? _d : defaultCreateProxyingResolver; resolve = createProxyingResolver({ subschemaConfig, operation: operation, fieldName: wrappingFieldName, }); } else { resolve = defaultMergedResolver; } [newSchema] = modifyObjectFields(newSchema, this.outerTypeName, fieldName => !!newTargetFieldConfigMap[fieldName], { [wrappingFieldName]: { type: new GraphQLNonNull(newSchema.getType(wrappingTypeName)), resolve, }, }); return this.transformer.transformSchema(newSchema, subschemaConfig); } transformRequest(originalRequest, delegationContext, transformationContext) { transformationContext.nextIndex = 0; transformationContext.paths = Object.create(null); return this.transformer.transformRequest(originalRequest, delegationContext, transformationContext); } transformResult(originalResult, delegationContext, transformationContext) { return this.transformer.transformResult(originalResult, delegationContext, transformationContext); } } function collectFields(selectionSet, fragments, fields = [], visitedFragmentNames = {}) { if (selectionSet != null) { for (const selection of selectionSet.selections) { switch (selection.kind) { case Kind.FIELD: fields.push(selection); break; case Kind.INLINE_FRAGMENT: collectFields(selection.selectionSet, fragments, fields, visitedFragmentNames); break; case Kind.FRAGMENT_SPREAD: { const fragmentName = selection.name.value; if (!visitedFragmentNames[fragmentName]) { visitedFragmentNames[fragmentName] = true; collectFields(fragments[fragmentName].selectionSet, fragments, fields, visitedFragmentNames); } break; } default: // unreachable break; } } } return fields; } function aliasFieldNode(fieldNode, str) { return { ...fieldNode, alias: { kind: Kind.NAME, value: str, }, }; } function hoistFieldNodes({ fieldNode, fieldNames, path, fragments, transformationContext, prefix, index = 0, wrappingPath = [], }) { const alias = fieldNode.alias != null ? fieldNode.alias.value : fieldNode.name.value; let newFieldNodes = []; if (index < path.length) { const pathSegment = path[index]; for (const possibleFieldNode of collectFields(fieldNode.selectionSet, fragments)) { if (possibleFieldNode.name.value === pathSegment) { const newWrappingPath = wrappingPath.concat([alias]); newFieldNodes = newFieldNodes.concat(hoistFieldNodes({ fieldNode: possibleFieldNode, fieldNames, path, fragments, transformationContext, prefix, index: index + 1, wrappingPath: newWrappingPath, })); } } } else { for (const possibleFieldNode of collectFields(fieldNode.selectionSet, fragments)) { if (!fieldNames || fieldNames.includes(possibleFieldNode.name.value)) { const nextIndex = transformationContext.nextIndex; transformationContext.nextIndex++; const indexingAlias = `__${prefix}${nextIndex}__`; transformationContext.paths[indexingAlias] = { pathToField: wrappingPath.concat([alias]), alias: possibleFieldNode.alias != null ? possibleFieldNode.alias.value : possibleFieldNode.name.value, }; newFieldNodes.push(aliasFieldNode(possibleFieldNode, indexingAlias)); } } } return newFieldNodes; } export function dehoistValue(originalValue, context) { if (originalValue == null) { return originalValue; } const newValue = Object.create(null); for (const alias in originalValue) { let obj = newValue; const path = context.paths[alias]; if (path == null) { newValue[alias] = originalValue[alias]; continue; } const pathToField = path.pathToField; const fieldAlias = path.alias; for (const key of pathToField) { obj = obj[key] = obj[key] || Object.create(null); } obj[fieldAlias] = originalValue[alias]; } return newValue; } function dehoistErrors(errors, context) { if (errors === undefined) { return undefined; } return errors.map(error => { const originalPath = error.path; if (originalPath == null) { return error; } let newPath = []; for (const pathSegment of originalPath) { if (typeof pathSegment !== 'string') { newPath.push(pathSegment); continue; } const path = context.paths[pathSegment]; if (path == null) { newPath.push(pathSegment); continue; } newPath = newPath.concat(path.pathToField, [path.alias]); } return relocatedError(error, newPath); }); }