UNPKG

@graphql-tools/delegate

Version:

A set of utils for faster development of GraphQL tools

230 lines (229 loc) • 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultMergedResolver = void 0; const graphql_1 = require("graphql"); const utils_1 = require("@graphql-tools/utils"); const leftOver_js_1 = require("./leftOver.js"); const mergeFields_js_1 = require("./mergeFields.js"); const resolveExternalValue_js_1 = require("./resolveExternalValue.js"); const symbols_js_1 = require("./symbols.js"); /** * Resolver that knows how to: * a) handle aliases for proxied schemas * b) handle errors from proxied schemas * c) handle external to internal enum conversion */ function defaultMergedResolver(parent, args, context, info) { if (!parent) { return null; } const responseKey = (0, utils_1.getResponseKeyFromInfo)(info); // check to see if parent is not a proxied result, i.e. if parent resolver was manually overwritten // See https://github.com/ardatan/graphql-tools/issues/967 if (!(0, mergeFields_js_1.isExternalObject)(parent)) { return (0, graphql_1.defaultFieldResolver)(parent, args, context, info); } // If the parent is satisfied for the left over after a nested delegation, try to resolve it if (!Object.prototype.hasOwnProperty.call(parent, responseKey)) { const leftOver = (0, leftOver_js_1.getPlanLeftOverFromParent)(parent); // Add this field to the deferred fields if (leftOver) { let missingFieldNodes = leftOver.missingFieldsParentMap.get(parent); if (!missingFieldNodes) { missingFieldNodes = []; leftOver.missingFieldsParentMap.set(parent, missingFieldNodes); } missingFieldNodes.push(...info.fieldNodes.filter(fieldNode => leftOver.unproxiableFieldNodes.some(unproxiableFieldNode => unproxiableFieldNode === fieldNode))); let missingDeferredFields = leftOver.missingFieldsParentDeferredMap.get(parent); if (!missingDeferredFields) { missingDeferredFields = new Map(); leftOver.missingFieldsParentDeferredMap.set(parent, missingDeferredFields); } const deferred = (0, leftOver_js_1.createDeferred)(); missingDeferredFields.set(responseKey, deferred); return deferred.promise; } return undefined; } return handleResult(parent, responseKey, context, info); } exports.defaultMergedResolver = defaultMergedResolver; function handleResult(parent, responseKey, context, info) { const subschema = (0, mergeFields_js_1.getSubschema)(parent, responseKey); const data = parent[responseKey]; const unpathedErrors = (0, mergeFields_js_1.getUnpathedErrors)(parent); const resolvedData$ = (0, resolveExternalValue_js_1.resolveExternalValue)(data, unpathedErrors, subschema, context, info); const leftOver = (0, leftOver_js_1.getPlanLeftOverFromParent)(parent); // Handle possible deferred fields if any left over from the previous delegation plan is found if (leftOver) { if ((0, utils_1.isPromise)(resolvedData$)) { return resolvedData$.then(resolvedData => { parent[responseKey] = resolvedData; handleLeftOver(parent, context, info, leftOver); return resolvedData; }); } parent[responseKey] = resolvedData$; handleLeftOver(parent, context, info, leftOver); } return resolvedData$; } function handleLeftOver(parent, context, info, leftOver) { const stitchingInfo = info.schema.extensions?.['stitchingInfo']; if (stitchingInfo) { for (const possibleSubschema of leftOver.nonProxiableSubschemas) { const parentTypeName = info.parentType.name; const selectionSet = stitchingInfo.mergedTypes[parentTypeName].selectionSets.get(possibleSubschema); // Wait until the parent is flattened, then check if non proxiable subschemas are satisfied now, // then the deferred fields can be resolved if (selectionSet) { const flattenedParent$ = flattenPromise(parent); if ((0, utils_1.isPromise)(flattenedParent$)) { flattenedParent$.then(flattenedParent => { handleFlattenedParent(flattenedParent, possibleSubschema, selectionSet, leftOver, stitchingInfo, parentTypeName, context, info); }); } else { handleFlattenedParent(flattenedParent$, possibleSubschema, selectionSet, leftOver, stitchingInfo, parentTypeName, context, info); } } } } } function handleFlattenedParent(flattenedParent, possibleSubschema, selectionSet, leftOver, stitchingInfo, parentTypeName, context, info) { // If this subschema is satisfied now, try to resolve the deferred fields if (parentSatisfiedSelectionSet(flattenedParent, selectionSet)) { for (const [leftOverParent, missingFieldNodes] of leftOver.missingFieldsParentMap) { const resolver = stitchingInfo.mergedTypes[parentTypeName].resolvers.get(possibleSubschema); if (resolver) { try { // Extend the left over parent with missing fields Object.assign(leftOverParent, flattenedParent); const selectionSet = { kind: graphql_1.Kind.SELECTION_SET, selections: missingFieldNodes, }; const resolverResult$ = resolver(leftOverParent, context, info, possibleSubschema, selectionSet, info.parentType, info.parentType); // Resolve the deferred fields if they are resolved if ((0, utils_1.isPromise)(resolverResult$)) { resolverResult$.then(resolverResult => handleDeferredResolverResult(resolverResult, possibleSubschema, selectionSet, leftOverParent, leftOver, context, info)).catch(error => handleDeferredResolverFailure(leftOver, leftOverParent, error)); } else { handleDeferredResolverResult(resolverResult$, possibleSubschema, selectionSet, leftOverParent, leftOver, context, info); } } catch (error) { handleDeferredResolverFailure(leftOver, leftOverParent, error); } } } } } function handleDeferredResolverResult(resolverResult, possibleSubschema, selectionSet, leftOverParent, leftOver, context, info) { (0, mergeFields_js_1.handleResolverResult)(resolverResult, possibleSubschema, selectionSet, leftOverParent, leftOverParent[symbols_js_1.FIELD_SUBSCHEMA_MAP_SYMBOL], info, (0, graphql_1.responsePathAsArray)(info.path), leftOverParent[symbols_js_1.UNPATHED_ERRORS_SYMBOL]); const deferredFields = leftOver.missingFieldsParentDeferredMap.get(leftOverParent); if (deferredFields) { for (const [responseKey, deferred] of deferredFields) { // If the deferred field is resolved, resolve the deferred field if (Object.prototype.hasOwnProperty.call(resolverResult, responseKey)) { deferred.resolve(handleResult(leftOverParent, responseKey, context, info)); } } leftOver.missingFieldsParentDeferredMap.delete(leftOverParent); } } function handleDeferredResolverFailure(leftOver, leftOverParent, error) { const deferredFields = leftOver.missingFieldsParentDeferredMap.get(leftOverParent); if (deferredFields) { for (const [_responseKey, deferred] of deferredFields) { deferred.reject(error); } leftOver.missingFieldsParentDeferredMap.delete(leftOverParent); } } function parentSatisfiedSelectionSet(parent, selectionSet) { if (Array.isArray(parent)) { const subschemas = new Set(); for (const item of parent) { const satisfied = parentSatisfiedSelectionSet(item, selectionSet); if (satisfied === undefined) { return undefined; } for (const subschema of satisfied) { subschemas.add(subschema); } } return subschemas; } if (parent === null) { return new Set(); } if (parent === undefined) { return undefined; } const subschemas = new Set(); for (const selection of selectionSet.selections) { if (selection.kind === graphql_1.Kind.FIELD) { const responseKey = selection.alias?.value ?? selection.name.value; if (parent[responseKey] === undefined) { return undefined; } if ((0, mergeFields_js_1.isExternalObject)(parent)) { const subschema = (0, mergeFields_js_1.getSubschema)(parent, responseKey); if (subschema) { subschemas.add(subschema); } } if (parent[responseKey] === null) { continue; } if (selection.selectionSet != null) { const satisfied = parentSatisfiedSelectionSet(parent[responseKey], selection.selectionSet); if (satisfied === undefined) { return undefined; } for (const subschema of satisfied) { subschemas.add(subschema); } } } else if (selection.kind === graphql_1.Kind.INLINE_FRAGMENT) { const inlineSatisfied = parentSatisfiedSelectionSet(parent, selection.selectionSet); if (inlineSatisfied === undefined) { return undefined; } for (const subschema of inlineSatisfied) { subschemas.add(subschema); } } } return subschemas; } function flattenPromise(data) { if ((0, utils_1.isPromise)(data)) { return data.then(flattenPromise); } if (Array.isArray(data)) { return Promise.all(data.map(flattenPromise)); } if (data != null && typeof data === 'object') { const jobs = []; const newData = {}; for (const key in data) { const keyResult = flattenPromise(data[key]); if ((0, utils_1.isPromise)(keyResult)) { jobs.push(keyResult.then(resolvedKeyResult => { newData[key] = resolvedKeyResult; })); } else { newData[key] = keyResult; } } if (jobs.length) { return Promise.all(jobs).then(() => newData); } return newData; } return data; }