UNPKG

@graphql-mesh/utils

Version:
252 lines (251 loc) • 13 kB
import { getNamedType, isLeafType, Kind, print } from 'graphql'; import { batchDelegateToSchema } from '@graphql-tools/batch-delegate'; import { applySchemaTransforms, delegateToSchema } from '@graphql-tools/delegate'; import { buildOperationNodeForField, isDocumentNode, memoize1 } from '@graphql-tools/utils'; import { WrapQuery } from '@graphql-tools/wrap'; import { handleMaybePromise, iterateAsync } from '@whatwg-node/promise-helpers'; import { parseWithCache } from './parseAndPrintWithCache.js'; export const MESH_API_CONTEXT_SYMBOL = Symbol('isMeshAPIContext'); export function getInContextSDK(unifiedSchema, rawSources, logger, onDelegateHooks) { const inContextSDK = {}; const sourceMap = unifiedSchema.extensions.sourceMap; for (const rawSource of rawSources) { const rawSourceLogger = logger?.child({ source: rawSource.name }); const rawSourceContext = { rawSource, [MESH_API_CONTEXT_SYMBOL]: true, }; // TODO: Somehow rawSource reference got lost in somewhere let rawSourceSubSchemaConfig = rawSource; const stitchingInfo = unifiedSchema.extensions.stitchingInfo; if (stitchingInfo) { for (const [subschemaConfig, subschema] of stitchingInfo.subschemaMap) { if (subschemaConfig.name === rawSource.name) { rawSourceSubSchemaConfig = subschema; break; } } } // If there is a single source, there is no unifiedSchema const transformedSchema = sourceMap?.get(rawSource) || applySchemaTransforms(rawSource.schema, rawSource); const rootTypes = { query: transformedSchema.getQueryType(), mutation: transformedSchema.getMutationType(), subscription: transformedSchema.getSubscriptionType(), }; rawSourceLogger?.debug(`Generating In Context SDK`); for (const operationType in rootTypes) { const rootType = rootTypes[operationType]; if (rootType) { rawSourceContext[rootType.name] = {}; const rootTypeFieldMap = rootType.getFields(); for (const fieldName in rootTypeFieldMap) { const rootTypeField = rootTypeFieldMap[fieldName]; const inContextSdkLogger = rawSourceLogger?.child({ inContextSdk: `${rootType.name}.${fieldName}`, }); const namedReturnType = getNamedType(rootTypeField.type); const shouldHaveSelectionSet = !isLeafType(namedReturnType); rawSourceContext[rootType.name][fieldName] = ({ root, args, context, info = { fieldName, fieldNodes: [], returnType: namedReturnType, parentType: rootType, path: { typename: rootType.name, key: fieldName, prev: undefined, }, schema: transformedSchema, fragments: {}, rootValue: root, operation: { kind: Kind.OPERATION_DEFINITION, operation: operationType, selectionSet: { kind: Kind.SELECTION_SET, selections: [], }, }, variableValues: {}, }, selectionSet, key, argsFromKeys, valuesFromResults, autoSelectionSetWithDepth, dataLoaderOptions, }) => { inContextSdkLogger?.debug(`Called with`, { args, key, }); const commonDelegateOptions = { schema: rawSourceSubSchemaConfig, rootValue: root, operation: operationType, fieldName, context, transformedSchema, info, transforms: [], }; // If there isn't an extraction of a value if (typeof selectionSet !== 'function') { commonDelegateOptions.returnType = rootTypeField.type; } if (shouldHaveSelectionSet) { let selectionCount = 0; for (const fieldNode of info.fieldNodes) { if (fieldNode.selectionSet != null) { selectionCount += fieldNode.selectionSet.selections.length; } } if (selectionCount === 0) { if (!selectionSet) { if (autoSelectionSetWithDepth) { const operationNode = buildOperationNodeForField({ schema: transformedSchema, kind: operationType, depthLimit: autoSelectionSetWithDepth, field: fieldName, }); selectionSet = print(operationNode.selectionSet.selections[0].selectionSet); } else { throw new Error(`You have to provide 'selectionSet' for context.${rawSource.name}.${rootType.name}.${fieldName}`); } } commonDelegateOptions.info = { ...info, fieldNodes: [ { ...info.fieldNodes[0], selectionSet: { kind: Kind.SELECTION_SET, selections: [ { kind: Kind.FIELD, name: { kind: Kind.NAME, value: '__typename', }, }, ], }, }, ...info.fieldNodes.slice(1), ], }; } } if (key && argsFromKeys) { const batchDelegationOptions = { ...commonDelegateOptions, key, argsFromKeys, valuesFromResults, dataLoaderOptions, }; if (selectionSet) { const selectionSetFactory = normalizeSelectionSetParamOrFactory(selectionSet); const path = [fieldName]; const wrapQueryTransform = new WrapQuery(path, selectionSetFactory, identical); batchDelegationOptions.transforms = [wrapQueryTransform]; } const onDelegateHookDones = []; const onDelegatePayload = { ...batchDelegationOptions, sourceName: rawSource.name, typeName: rootType.name, fieldName, }; fixInfo(batchDelegationOptions.info, operationType); return handleMaybePromise(() => iterateAsync(onDelegateHooks, onDelegateHook => onDelegateHook(onDelegatePayload), onDelegateHookDones), () => handleIterationResult(batchDelegateToSchema, batchDelegationOptions, onDelegateHookDones)); } else { const regularDelegateOptions = { ...commonDelegateOptions, args, }; if (selectionSet) { const selectionSetFactory = normalizeSelectionSetParamOrFactory(selectionSet); const path = [fieldName]; const wrapQueryTransform = new WrapQuery(path, selectionSetFactory, valuesFromResults || identical); regularDelegateOptions.transforms = [wrapQueryTransform]; } const onDelegateHookDones = []; const onDelegatePayload = { ...regularDelegateOptions, sourceName: rawSource.name, typeName: rootType.name, fieldName, }; fixInfo(regularDelegateOptions.info, operationType); return handleMaybePromise(() => iterateAsync(onDelegateHooks, onDelegateHook => onDelegateHook(onDelegatePayload), onDelegateHookDones), () => handleIterationResult(delegateToSchema, regularDelegateOptions, onDelegateHookDones)); } }; } } } inContextSDK[rawSource.name] = rawSourceContext; } return inContextSDK; } function getSelectionSetFromDocumentNode(documentNode) { const operationDefinition = documentNode.definitions.find(definition => definition.kind === Kind.OPERATION_DEFINITION); if (!operationDefinition) { throw new Error('DocumentNode must contain an OperationDefinitionNode'); } return operationDefinition.selectionSet; } function normalizeSelectionSetParam(selectionSetParam) { if (typeof selectionSetParam === 'string') { const documentNode = parseWithCache(selectionSetParam); return getSelectionSetFromDocumentNode(documentNode); } if (isDocumentNode(selectionSetParam)) { return getSelectionSetFromDocumentNode(selectionSetParam); } return selectionSetParam; } const normalizeSelectionSetParamFactory = memoize1(function normalizeSelectionSetParamFactory(selectionSetParamFactory) { const memoizedSelectionSetFactory = memoize1(selectionSetParamFactory); return function selectionSetFactory(subtree) { const selectionSetParam = memoizedSelectionSetFactory(subtree); return normalizeSelectionSetParam(selectionSetParam); }; }); function normalizeSelectionSetParamOrFactory(selectionSetParamOrFactory) { if (typeof selectionSetParamOrFactory === 'function') { return normalizeSelectionSetParamFactory(selectionSetParamOrFactory); } return () => normalizeSelectionSetParam(selectionSetParamOrFactory); } function identical(val) { return val; } function handleIterationResult(delegateFn, delegateOptions, onDelegateHookDones) { return handleMaybePromise(() => delegateFn(delegateOptions), delegationResult => handleMaybePromise(() => iterateAsync(onDelegateHookDones, onDelegateHookDone => onDelegateHookDone({ result: delegationResult, setResult(newResult) { delegationResult = newResult; }, })), () => delegationResult)); } function fixInfo(info, operationType) { info.operation ||= { kind: Kind.OPERATION_DEFINITION, operation: operationType, selectionSet: { kind: Kind.SELECTION_SET, selections: [], }, }; info.operation.selectionSet ||= { kind: Kind.SELECTION_SET, selections: [], }; info.operation.selectionSet.selections ||= []; info.operation.selectionSet.selections[0] ||= { kind: Kind.FIELD, name: { kind: Kind.NAME, value: '__typename', }, }; info.operation.selectionSet.selections[0].arguments ||= []; }