UNPKG

@graphql-mesh/transport-grpc

Version:
130 lines (129 loc) 4.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.toGrpcGraphQLError = toGrpcGraphQLError; exports.buildGrpcMetadata = buildGrpcMetadata; exports.addMetaDataToCall = addMetaDataToCall; exports.addExecutionLogicToScalar = addExecutionLogicToScalar; const tslib_1 = require("tslib"); const lodash_get_1 = tslib_1.__importDefault(require("lodash.get")); const string_interpolation_1 = require("@graphql-mesh/string-interpolation"); const utils_1 = require("@graphql-mesh/utils"); const utils_2 = require("@graphql-tools/utils"); const grpc_js_1 = require("@grpc/grpc-js"); function isBlob(input) { return input != null && input.stream instanceof Function; } function isGrpcServiceError(error) { return (typeof error === 'object' && error != null && typeof error.code === 'number' && typeof error.details === 'string'); } /** * Convert a gRPC `ServiceError` into a GraphQLError so gateways do not mask it. * Upstream details live under `extensions.grpc` (code, status, details, metadata). * Non-ServiceError values are returned unchanged. */ function toGrpcGraphQLError(error) { if (!isGrpcServiceError(error)) { return error; } const status = grpc_js_1.status[error.code] != null ? String(grpc_js_1.status[error.code]) : 'UNKNOWN'; // Do not set `originalError` to the raw ServiceError — Envelop's maskedErrors // walks originalError and would treat a non-GraphQLError root as unexpected. return (0, utils_2.createGraphQLError)(error.message || status, { extensions: { code: 'DOWNSTREAM_SERVICE_ERROR', grpc: { code: error.code, status, details: error.details, metadata: error.metadata?.getMap?.(), }, }, }); } function buildGrpcMetadata(metaData, resolverData) { if (!metaData) { return undefined; } const meta = new grpc_js_1.Metadata(); const entries = Array.isArray(metaData) ? Object.fromEntries(metaData) : metaData; for (const [key, value] of Object.entries(entries)) { let metaValue = value; if (Array.isArray(value)) { // Extract data from context metaValue = (0, lodash_get_1.default)(resolverData.context, value); } // Ensure that the metadata is compatible with what node-grpc expects if (typeof metaValue !== 'string' && !(metaValue instanceof Buffer)) { metaValue = JSON.stringify(metaValue); } if (typeof metaValue === 'string') { metaValue = string_interpolation_1.stringInterpolator.parse(metaValue, resolverData); } meta.add(key, metaValue); } return meta; } function addMetaDataToCall(callFn, input, resolverData, metaData, isResponseStream = false, requestTimeout) { const callFnArguments = []; if (!isBlob(input)) { callFnArguments.push(input); } const meta = buildGrpcMetadata(metaData, resolverData); const callOptions = {}; if (requestTimeout != null && requestTimeout > 0) { callOptions.deadline = Date.now() + requestTimeout; } const hasCallOptions = Object.keys(callOptions).length > 0; if (meta || hasCallOptions) { // grpc-js accepts (argument, metadata, options, callback) callFnArguments.push(meta ?? new grpc_js_1.Metadata()); if (hasCallOptions) { callFnArguments.push(callOptions); } } return new Promise((resolve, reject) => { const call = callFn(...callFnArguments, (error, response) => { if (error) { reject(toGrpcGraphQLError(error)); return; } resolve(response); }); if (isResponseStream) { let isCancelled = false; const responseStreamWithCancel = (0, utils_1.withCancel)(call, reason => { if (!isCancelled) { call.call?.cancelWithStatus(0, reason?.toString() || 'aborted'); isCancelled = true; } }); resolve(responseStreamWithCancel); if (isBlob(input)) { const blobStream = input.stream(); blobStream.pipe(call); } } }); } function addExecutionLogicToScalar(nonExecutableScalar, actualScalar) { Object.defineProperties(nonExecutableScalar, { serialize: { value: actualScalar.serialize, }, parseValue: { value: actualScalar.parseValue, }, parseLiteral: { value: actualScalar.parseLiteral, }, extensions: { value: { ...actualScalar.extensions, ...nonExecutableScalar.extensions, }, }, }); }