@graphql-tools/delegate
Version:
A set of utils for faster development of GraphQL tools
230 lines (229 loc) • 10.7 kB
JavaScript
;
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;
}