UNPKG

@grpc.ts/nestjs-server

Version:
156 lines (150 loc) 5.5 kB
import { createServer } from '@grpc.ts/core'; export { GrpcTimestamp, Metadata, ServerCredentials, StatusBuilder, createMetadata, createSecureContext, dateToGrpcTimestamp, grpcTimestampToDate, status } from '@grpc.ts/core'; import { Logger } from '@nestjs/common'; import { isObservable, lastValueFrom } from 'rxjs'; const PAYLOAD_TYPE = 3; const CONTEXT_TYPE = 6; const GRPC_CALL_TYPE = 9; const ROUTE_ARGS_METADATA = '__routeArguments__'; const PATTERN_METADATA = 'microservices:pattern'; const CUSTOM_ROUTE_ARGS_METADATA = '__customRouteArgs__'; const PATTERN_HANDLER_METADATA = 'microservices:handler_type'; const DEFAULT_GRPC_CALLBACK_METADATA = { [`${CONTEXT_TYPE}:1`]: { index: 1, data: undefined, pipes: [] }, [`${GRPC_CALL_TYPE}:2`]: { index: 2, data: undefined, pipes: [] }, [`${PAYLOAD_TYPE}:0`]: { index: 0, data: undefined, pipes: [] }, [`${CUSTOM_ROUTE_ARGS_METADATA}:3`]: { index: 3, data: undefined, pipes: [], factory: (_data, context) => { return context.args[context.args.length - 1]; }, }, }; function GrpcUnaryMethod({ rpcName, ...rest }) { return (target, key, descriptor) => { Reflect.defineMetadata(PATTERN_METADATA, [ { ...rest, isGrpc: true, type: 'unary', rpcName: rpcName || key || descriptor.value.name, }, ], descriptor.value); Reflect.defineMetadata(PATTERN_HANDLER_METADATA, 1, descriptor.value); Reflect.defineMetadata(ROUTE_ARGS_METADATA, DEFAULT_GRPC_CALLBACK_METADATA, target.constructor, rpcName || key || descriptor.value.name); return descriptor; }; } async function createServers(options) { if (!Array.isArray(options)) { options = [options]; } const servers = await Promise.allSettled(options.map(async ({ serverName = '', ...opts }) => { const serverObj = await createServer(opts); return { serverObj, serverName, }; })); return servers.reduce((result, settledServer) => { if (settledServer.status === 'fulfilled') { const { serverName, serverObj } = settledServer.value; result[serverName] = serverObj; } return result; }, {}); } function omit(data, keys) { return Object.entries(data).reduce((result, [key, value]) => { if (!keys.includes(key)) { result[key] = value; } return result; }, {}); } function isObject(data) { return !(data instanceof Date) && !!data && typeof data === 'object'; } function sortedObj(obj) { return Object.keys(obj) .sort() .reduce((result, key) => { const value = obj[key]; if (Array.isArray(value)) { result[key] = value.map((v) => sortedObj(v)); } else if (isObject(value)) { result[key] = sortedObj(value); } else { result[key] = value; } return result; }, {}); } function normalizePattern(pattern) { return JSON.stringify(sortedObj(pattern)); } class GrpcServer { #options; #grpcServers = {}; #patterns = []; #logger = new Logger('gRPCServer'); #messageHandlers = new Map(); constructor(props) { this.#options = props; } static createService(params) { return { strategy: new GrpcServer(params), }; } addHandler(pattern, callback, isEventHandler = false, _extras = {}) { if (typeof pattern === 'object' && pattern.isGrpc) { pattern = omit(pattern, ['isGrpc']); this.#patterns.push(pattern); } const normalizedPattern = normalizePattern(pattern); callback.isEventHandler = isEventHandler; this.#messageHandlers.set(normalizedPattern, callback); } async listen(callback) { const patterns = this.#patterns; const handleMessage = this.#handleMessage.bind(this); await this.#initServers(this.#options); patterns.forEach(async (pattern) => { const { serverName = '', serviceName, rpcName, options } = pattern; const server = this.#grpcServers[serverName]; if (!server) { return this.#logger.error(`Unknown server ${serverName}`); } server.addUnaryHandler(serviceName, rpcName, async (request, metadata, call, callback) => { const message = await handleMessage(request, metadata, call, pattern, callback); if (isObservable(message)) { return lastValueFrom(message); } return message; }, options); }); callback(); } async close() { Object.values(this.#grpcServers).forEach((serverWrapper) => { serverWrapper.server.forceShutdown(); }); } async #handleMessage(request, metadata, call, pattern, callback) { const patternAsString = normalizePattern(pattern); const handler = this.#messageHandlers.get(patternAsString); if (!handler) { return this.#logger.error(`There is no matching event handler defined in the server. Event pattern: ${patternAsString}`); } return handler(request, metadata, call, callback); } async #initServers(options) { this.#grpcServers = await createServers(options); } } export { GrpcServer, GrpcUnaryMethod };