@nestjs/microservices
Version:
Nest - modern, fast, powerful node.js web framework (@microservices)
285 lines (284 loc) • 12.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClientGrpcProxy = void 0;
const logger_service_1 = require("@nestjs/common/services/logger.service");
const load_package_util_1 = require("@nestjs/common/utils/load-package.util");
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
const rxjs_1 = require("rxjs");
const constants_1 = require("../constants");
const invalid_grpc_package_exception_1 = require("../errors/invalid-grpc-package.exception");
const invalid_grpc_service_exception_1 = require("../errors/invalid-grpc-service.exception");
const invalid_proto_definition_exception_1 = require("../errors/invalid-proto-definition.exception");
const helpers_1 = require("../helpers");
const client_proxy_1 = require("./client-proxy");
const GRPC_CANCELLED = 'Cancelled';
let grpcPackage = {};
let grpcProtoLoaderPackage = {};
/**
* @publicApi
*/
class ClientGrpcProxy extends client_proxy_1.ClientProxy {
get status() {
throw new Error('The "status" attribute is not supported by the gRPC transport');
}
constructor(options) {
super();
this.options = options;
this.logger = new logger_service_1.Logger(client_proxy_1.ClientProxy.name);
this.clients = new Map();
this.grpcClients = [];
this.url = this.getOptionsProp(options, 'url') || constants_1.GRPC_DEFAULT_URL;
const protoLoader = this.getOptionsProp(options, 'protoLoader') || constants_1.GRPC_DEFAULT_PROTO_LOADER;
grpcPackage = (0, load_package_util_1.loadPackage)('@grpc/grpc-js', ClientGrpcProxy.name, () => require('@grpc/grpc-js'));
grpcProtoLoaderPackage = (0, load_package_util_1.loadPackage)(protoLoader, ClientGrpcProxy.name, () => protoLoader === constants_1.GRPC_DEFAULT_PROTO_LOADER
? require('@grpc/proto-loader')
: require(protoLoader));
this.grpcClients = this.createClients();
}
getService(name) {
const grpcClient = this.getClientByServiceName(name);
const clientRef = this.getClient(name);
if (!clientRef) {
throw new invalid_grpc_service_exception_1.InvalidGrpcServiceException(name);
}
const protoMethods = Object.keys(clientRef[name].prototype);
const grpcService = {};
protoMethods.forEach(m => {
grpcService[m] = this.createServiceMethod(grpcClient, m);
});
return grpcService;
}
getClientByServiceName(name) {
return this.clients.get(name) || this.createClientByServiceName(name);
}
createClientByServiceName(name) {
const clientRef = this.getClient(name);
if (!clientRef) {
throw new invalid_grpc_service_exception_1.InvalidGrpcServiceException(name);
}
const channelOptions = this.options && this.options.channelOptions
? this.options.channelOptions
: {};
if (this.options && this.options.maxSendMessageLength) {
channelOptions['grpc.max_send_message_length'] =
this.options.maxSendMessageLength;
}
if (this.options && this.options.maxReceiveMessageLength) {
channelOptions['grpc.max_receive_message_length'] =
this.options.maxReceiveMessageLength;
}
if (this.options && this.options.maxMetadataSize) {
channelOptions['grpc.max_metadata_size'] = this.options.maxMetadataSize;
}
const keepaliveOptions = this.getKeepaliveOptions();
const options = {
...channelOptions,
...keepaliveOptions,
};
const credentials = this.options.credentials || grpcPackage.credentials.createInsecure();
const grpcClient = new clientRef[name](this.url, credentials, options);
this.clients.set(name, grpcClient);
return grpcClient;
}
getKeepaliveOptions() {
if (!(0, shared_utils_1.isObject)(this.options.keepalive)) {
return {};
}
const keepaliveKeys = {
keepaliveTimeMs: 'grpc.keepalive_time_ms',
keepaliveTimeoutMs: 'grpc.keepalive_timeout_ms',
keepalivePermitWithoutCalls: 'grpc.keepalive_permit_without_calls',
http2MaxPingsWithoutData: 'grpc.http2.max_pings_without_data',
http2MinTimeBetweenPingsMs: 'grpc.http2.min_time_between_pings_ms',
http2MinPingIntervalWithoutDataMs: 'grpc.http2.min_ping_interval_without_data_ms',
http2MaxPingStrikes: 'grpc.http2.max_ping_strikes',
};
const keepaliveOptions = {};
for (const [optionKey, optionValue] of Object.entries(this.options.keepalive)) {
const key = keepaliveKeys[optionKey];
if (key === undefined) {
continue;
}
keepaliveOptions[key] = optionValue;
}
return keepaliveOptions;
}
createServiceMethod(client, methodName) {
return client[methodName].responseStream
? this.createStreamServiceMethod(client, methodName)
: this.createUnaryServiceMethod(client, methodName);
}
createStreamServiceMethod(client, methodName) {
return (...args) => {
const isRequestStream = client[methodName].requestStream;
const stream = new rxjs_1.Observable(observer => {
let isClientCanceled = false;
let upstreamSubscription = null;
const upstreamSubjectOrData = args[0];
const maybeMetadata = args[1];
const isUpstreamSubject = upstreamSubjectOrData && (0, shared_utils_1.isFunction)(upstreamSubjectOrData.subscribe);
const call = isRequestStream && isUpstreamSubject
? client[methodName](maybeMetadata)
: client[methodName](...args);
if (isRequestStream && isUpstreamSubject) {
upstreamSubscription = upstreamSubjectOrData.subscribe((val) => call.write(val), (err) => call.emit('error', err), () => call.end());
}
call.on('data', (data) => observer.next(data));
call.on('error', (error) => {
if (error.details === GRPC_CANCELLED) {
call.destroy();
if (isClientCanceled) {
return;
}
}
observer.error(this.serializeError(error));
});
call.on('end', () => {
if (upstreamSubscription) {
upstreamSubscription.unsubscribe();
upstreamSubscription = null;
}
call.removeAllListeners();
observer.complete();
});
return () => {
if (upstreamSubscription) {
upstreamSubscription.unsubscribe();
upstreamSubscription = null;
}
if (call.finished) {
return undefined;
}
isClientCanceled = true;
call.cancel();
};
});
return stream;
};
}
createUnaryServiceMethod(client, methodName) {
return (...args) => {
const isRequestStream = client[methodName].requestStream;
const upstreamSubjectOrData = args[0];
const isUpstreamSubject = upstreamSubjectOrData && (0, shared_utils_1.isFunction)(upstreamSubjectOrData.subscribe);
if (isRequestStream && isUpstreamSubject) {
return new rxjs_1.Observable(observer => {
let isClientCanceled = false;
const callArgs = [
(error, data) => {
if (error) {
if (error.details === GRPC_CANCELLED || error.code === 1) {
call.destroy();
if (isClientCanceled) {
return;
}
}
return observer.error(this.serializeError(error));
}
observer.next(data);
observer.complete();
},
];
const maybeMetadata = args[1];
if (maybeMetadata) {
callArgs.unshift(maybeMetadata);
}
const call = client[methodName](...callArgs);
const upstreamSubscription = upstreamSubjectOrData.subscribe((val) => call.write(val), (err) => call.emit('error', err), () => call.end());
return () => {
upstreamSubscription.unsubscribe();
if (!call.finished) {
isClientCanceled = true;
call.cancel();
}
};
});
}
return new rxjs_1.Observable(observer => {
const call = client[methodName](...args, (error, data) => {
if (error) {
return observer.error(this.serializeError(error));
}
observer.next(data);
observer.complete();
});
return () => {
if (!call.finished) {
call.cancel();
}
};
});
};
}
createClients() {
const grpcContext = this.loadProto();
const packageOption = this.getOptionsProp(this.options, 'package');
const grpcPackages = [];
const packageNames = Array.isArray(packageOption)
? packageOption
: [packageOption];
for (const packageName of packageNames) {
const grpcPkg = this.lookupPackage(grpcContext, packageName);
if (!grpcPkg) {
const invalidPackageError = new invalid_grpc_package_exception_1.InvalidGrpcPackageException(packageName);
this.logger.error(invalidPackageError.message, invalidPackageError.stack);
throw invalidPackageError;
}
grpcPackages.push(grpcPkg);
}
return grpcPackages;
}
loadProto() {
try {
const packageDefinition = (0, helpers_1.getGrpcPackageDefinition)(this.options, grpcProtoLoaderPackage);
return grpcPackage.loadPackageDefinition(packageDefinition);
}
catch (err) {
const invalidProtoError = new invalid_proto_definition_exception_1.InvalidProtoDefinitionException(err.path);
const message = err && err.message ? err.message : invalidProtoError.message;
this.logger.error(message, invalidProtoError.stack);
throw invalidProtoError;
}
}
lookupPackage(root, packageName) {
/** Reference: https://github.com/kondi/rxjs-grpc */
let pkg = root;
if (packageName) {
for (const name of packageName.split('.')) {
pkg = pkg[name];
}
}
return pkg;
}
close() {
this.clients.forEach(client => {
if (client && (0, shared_utils_1.isFunction)(client.close)) {
client.close();
}
});
this.clients.clear();
this.grpcClients = [];
}
async connect() {
throw new Error('The "connect()" method is not supported in gRPC mode.');
}
send(pattern, data) {
throw new Error('Method is not supported in gRPC mode. Use ClientGrpc instead (learn more in the documentation).');
}
getClient(name) {
return this.grpcClients.find(client => Object.hasOwnProperty.call(client, name));
}
publish(packet, callback) {
throw new Error('Method is not supported in gRPC mode. Use ClientGrpc instead (learn more in the documentation).');
}
async dispatchEvent(packet) {
throw new Error('Method is not supported in gRPC mode. Use ClientGrpc instead (learn more in the documentation).');
}
on(event, callback) {
throw new Error('Method is not supported in gRPC mode.');
}
unwrap() {
throw new Error('Method is not supported in gRPC mode.');
}
}
exports.ClientGrpcProxy = ClientGrpcProxy;