@protobuf-ts/grpc-backend
Version:
gRPC backend for servers generated by the protoc plugin "protobuf-ts"
297 lines (296 loc) • 12.2 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createContext = exports.mapService = exports.createDefinition = exports.adaptService = void 0;
const runtime_rpc_1 = require("@protobuf-ts/runtime-rpc");
const grpc = require("@grpc/grpc-js");
const runtime_1 = require("@protobuf-ts/runtime");
const util_1 = require("./util");
/**
* Create a grpc service definition and an implementation for @grpc/grpc-js.
*/
function adaptService(serviceInfo, serviceImplementation, options = {}) {
return [
createDefinition(serviceInfo),
mapService(serviceInfo, serviceImplementation, options)
];
}
exports.adaptService = adaptService;
/**
* Create a service definition for @grpc/grpc-js from service info.
*/
function createDefinition(serviceInfo) {
const def = {};
for (let mi of serviceInfo.methods) {
def[mi.localName] = {
path: `/${serviceInfo.typeName}/${mi.name}`,
originalName: mi.name,
requestStream: mi.clientStreaming,
responseStream: mi.serverStreaming,
responseDeserialize: bytes => mi.O.fromBinary(bytes),
requestDeserialize: bytes => mi.I.fromBinary(bytes),
responseSerialize: value => Buffer.from(mi.O.toBinary(value)),
requestSerialize: value => Buffer.from(mi.I.toBinary(value)),
};
}
return def;
}
exports.createDefinition = createDefinition;
/**
* Create a grpc service implementation that delegates to the given
* protobuf-ts service implementation.
*/
function mapService(si, service, options) {
var _a;
const grpcImp = {};
const ctxFactory = (_a = options.contextFactory) !== null && _a !== void 0 ? _a : createContext;
for (let mi of si.methods) {
runtime_1.assert(typeof service[mi.localName] == "function", `implementation is missing method ${mi.localName}()`);
const fn = service[mi.localName].bind(service);
if (mi.serverStreaming && mi.clientStreaming) {
grpcImp[mi.localName] = mapBidi(si, mi, ctxFactory, fn);
}
else if (mi.serverStreaming) {
grpcImp[mi.localName] = mapServerStreaming(si, mi, ctxFactory, fn);
}
else if (mi.clientStreaming) {
grpcImp[mi.localName] = mapClientStreaming(si, mi, ctxFactory, fn);
}
else {
grpcImp[mi.localName] = mapUnary(si, mi, ctxFactory, fn);
}
}
return grpcImp;
}
exports.mapService = mapService;
function createContext(serviceInfo, methodInfo, call) {
const deadlineGrpc = call.getDeadline();
const deadlineDate = typeof deadlineGrpc === 'number' ? new Date(deadlineGrpc) : deadlineGrpc;
const sendHeaders = (data) => call.sendMetadata(util_1.metadataToGrpc(data));
const ctx = new runtime_rpc_1.ServerCallContextController(methodInfo, util_1.metadataFromGrpc(call.metadata), deadlineDate, sendHeaders);
call.on('cancelled', () => ctx.notifyCancelled());
return ctx;
}
exports.createContext = createContext;
function mapUnary(serviceInfo, methodInfo, ctxFactory, method) {
return (call, callback) => __awaiter(this, void 0, void 0, function* () {
const context = ctxFactory(serviceInfo, methodInfo, call);
let response;
try {
response = yield method(call.request, context);
}
catch (e) {
if (e instanceof runtime_rpc_1.RpcError) {
const grpcStatusCode = util_1.rpcCodeToGrpc(e.code);
if (grpcStatusCode === undefined) {
return callback({
message: `invalid grpc status code ${e.code}`,
code: grpc.status.INTERNAL,
details: e.message,
metadata: util_1.metadataToGrpc(e.meta),
});
}
return callback({
code: grpcStatusCode,
details: e.message,
metadata: util_1.metadataToGrpc(e.meta),
});
}
return callback({
code: grpc.status.INTERNAL,
message: e.message,
name: e.name,
stack: e.stack
});
}
const grpcStatusCode = util_1.rpcCodeToGrpc(context.status.code);
if (grpcStatusCode === undefined) {
return callback({
code: grpc.status.INTERNAL,
message: `invalid grpc status code ${context.status.code}`,
details: context.status.detail,
});
}
if (grpcStatusCode !== grpc.status.OK) {
return callback({
code: grpcStatusCode,
details: context.status.detail,
});
}
callback(null, response, util_1.metadataToGrpc(context.trailers));
});
}
function mapServerStreaming(serviceInfo, methodInfo, ctxFactory, method) {
return (call) => __awaiter(this, void 0, void 0, function* () {
const context = ctxFactory(serviceInfo, methodInfo, call);
const responseStream = {
send(message) {
return new Promise((resolve, reject) => {
call.write(message, resolve);
});
},
complete() {
return Promise.resolve();
}
};
try {
yield method(call.request, responseStream, context);
}
catch (e) {
if (e instanceof runtime_rpc_1.RpcError) {
const grpcStatusCode = util_1.rpcCodeToGrpc(e.code);
if (grpcStatusCode === undefined) {
let d = new Error(`invalid grpc status code ${e.code}`);
d.code = grpc.status.INTERNAL; // this is picked up
// d.metadata = new grpc.Metadata(); // but this isn't
return call.destroy(d);
}
return call.destroy(e);
}
return call.destroy(e);
}
const grpcStatusCode = util_1.rpcCodeToGrpc(context.status.code);
if (grpcStatusCode === undefined) {
let d = new Error(`invalid grpc status code ${context.status.code}`);
d.code = grpc.status.INTERNAL; // this is picked up
// d.metadata = new grpc.Metadata(); // but this isn't
return call.destroy(d);
}
if (grpcStatusCode !== grpc.status.OK) {
let d = new Error(context.status.detail);
d.code = grpcStatusCode; // this is picked up
// d.metadata = new grpc.Metadata(); // but this isn't
return call.destroy(d);
}
call.end(util_1.metadataToGrpc(context.trailers));
});
}
function mapClientStreaming(serviceInfo, methodInfo, ctxFactory, method) {
return (call, callback) => __awaiter(this, void 0, void 0, function* () {
const context = ctxFactory(serviceInfo, methodInfo, call);
const requestStream = new runtime_rpc_1.RpcOutputStreamController();
call.on('data', args => {
runtime_1.assert(methodInfo.I.is(args));
requestStream.notifyMessage(args);
});
call.on('end', () => {
requestStream.notifyComplete();
});
call.on('error', e => {
requestStream.notifyError(e);
});
let response;
try {
response = yield method(requestStream, context);
}
catch (e) {
if (e instanceof runtime_rpc_1.RpcError) {
const grpcStatusCode = util_1.rpcCodeToGrpc(e.code);
if (grpcStatusCode === undefined) {
return callback({
message: `invalid grpc status code ${e.code}`,
code: grpc.status.INTERNAL,
details: e.message,
metadata: util_1.metadataToGrpc(e.meta),
});
}
return callback({
code: grpcStatusCode,
details: e.message,
metadata: util_1.metadataToGrpc(e.meta),
});
}
return callback({
code: grpc.status.INTERNAL,
message: e.message,
name: e.name,
stack: e.stack
});
}
const grpcStatusCode = util_1.rpcCodeToGrpc(context.status.code);
if (grpcStatusCode === undefined) {
return callback({
code: grpc.status.INTERNAL,
message: `invalid grpc status code ${context.status.code}`,
details: context.status.detail,
});
}
if (grpcStatusCode !== grpc.status.OK) {
return callback({
code: grpcStatusCode,
details: context.status.detail,
});
}
callback(null, response, util_1.metadataToGrpc(context.trailers));
});
}
function mapBidi(serviceInfo, methodInfo, ctxFactory, method) {
return (call) => __awaiter(this, void 0, void 0, function* () {
const context = ctxFactory(serviceInfo, methodInfo, call);
const requestStream = new runtime_rpc_1.RpcOutputStreamController();
call.on('data', args => {
runtime_1.assert(methodInfo.I.is(args));
if (!requestStream.closed) {
requestStream.notifyMessage(args);
}
});
call.on('end', () => {
if (!requestStream.closed) {
requestStream.notifyComplete();
}
});
call.on('error', e => {
if (!requestStream.closed) {
requestStream.notifyError(e);
}
});
const responseStream = {
send(message) {
return new Promise((resolve, reject) => {
call.write(message, resolve);
});
},
complete() {
return Promise.resolve();
}
};
try {
yield method(requestStream, responseStream, context);
}
catch (e) {
if (e instanceof runtime_rpc_1.RpcError) {
const grpcStatusCode = util_1.rpcCodeToGrpc(e.code);
if (grpcStatusCode === undefined) {
let d = new Error(`invalid grpc status code ${e.code}`);
d.code = grpc.status.INTERNAL; // this is picked up
// d.metadata = new grpc.Metadata(); // but this isn't
return call.destroy(d);
}
return call.destroy(e);
}
return call.destroy(e);
}
const grpcStatusCode = util_1.rpcCodeToGrpc(context.status.code);
if (grpcStatusCode === undefined) {
let d = new Error(`invalid grpc status code ${context.status.code}`);
d.code = grpc.status.INTERNAL; // this is picked up
// d.metadata = new grpc.Metadata(); // but this isn't
return call.destroy(d);
}
if (grpcStatusCode !== grpc.status.OK) {
let d = new Error(context.status.detail);
d.code = grpcStatusCode; // this is picked up
// d.metadata = new grpc.Metadata(); // but this isn't
return call.destroy(d);
}
call.end(util_1.metadataToGrpc(context.trailers));
});
}