@grpc.ts/nestjs-server
Version:
NestJS package for server
156 lines (150 loc) • 5.5 kB
JavaScript
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 };