UNPKG

@protobuf-ts/grpc-transport

Version:

gRPC transport for clients generated by the protoc plugin "protobuf-ts"

205 lines (204 loc) 10 kB
import { ClientStreamingCall, Deferred, DeferredState, DuplexStreamingCall, mergeRpcOptions, RpcError, RpcOutputStreamController, ServerStreamingCall, UnaryCall } from "@protobuf-ts/runtime-rpc"; import { Client, status as GrpcStatus } from "@grpc/grpc-js"; import { assert } from "@protobuf-ts/runtime"; import { metadataFromGrpc, isServiceError, metadataToGrpc } from "./util"; export class GrpcTransport { constructor(defaultOptions) { this.defaultOptions = defaultOptions; this.client = new Client(defaultOptions.host, defaultOptions.channelCredentials, defaultOptions.clientOptions); } mergeOptions(options) { return mergeRpcOptions(this.defaultOptions, options); } pickCallOptions(options) { if (options.callOptions) { return options.callOptions; } const co = {}; if (typeof options.timeout === "number") { co.deadline = Date.now() + options.timeout; } else if (options.timeout) { co.deadline = options.timeout; } return co; } unary(method, input, options) { var _a; const opt = options, meta = (_a = opt.meta) !== null && _a !== void 0 ? _a : {}, gMeta = metadataToGrpc(meta, opt.metadataOptions, method.idempotency === "IDEMPOTENT"), defHeader = new Deferred(), defMessage = new Deferred(), defStatus = new Deferred(), defTrailer = new Deferred(), call = new UnaryCall(method, meta, input, defHeader.promise, defMessage.promise, defStatus.promise, defTrailer.promise); const gCall = this.client.makeUnaryRequest(`/${method.service.typeName}/${method.name}`, (value) => Buffer.from(method.I.toBinary(value, opt.binaryOptions)), (value) => method.O.fromBinary(value, opt.binaryOptions), input, gMeta, this.pickCallOptions(opt), (err, value) => { if (value) { defMessage.resolve(value); } if (err) { const e = new RpcError(err.details, GrpcStatus[err.code], metadataFromGrpc(err.metadata)); e.methodName = method.name; e.serviceName = method.service.typeName; defHeader.rejectPending(e); defMessage.rejectPending(e); defStatus.rejectPending(e); defTrailer.rejectPending(e); } }); if (opt.abort) { opt.abort.addEventListener('abort', ev => { gCall.cancel(); }); } gCall.addListener('metadata', val => { defHeader.resolve(metadataFromGrpc(val)); }); gCall.addListener('status', val => { // if we get a status (via trailer), but did not get a message, // we require that the status is an error status. if (defMessage.state === DeferredState.PENDING && val.code === GrpcStatus.OK) { const e = new RpcError('expected error status', GrpcStatus[GrpcStatus.DATA_LOSS]); e.methodName = method.name; e.serviceName = method.service.typeName; defMessage.rejectPending(e); defStatus.rejectPending(e); defTrailer.rejectPending(e); } else { defStatus.resolvePending({ code: GrpcStatus[val.code], detail: val.details }); defTrailer.resolvePending(metadataFromGrpc(val.metadata)); } }); return call; } serverStreaming(method, input, options) { var _a; const opt = options, meta = (_a = opt.meta) !== null && _a !== void 0 ? _a : {}, gMeta = metadataToGrpc(meta, opt.metadataOptions, method.idempotency === "IDEMPOTENT"), defHeader = new Deferred(), outStream = new RpcOutputStreamController(), defStatus = new Deferred(), defTrailer = new Deferred(), call = new ServerStreamingCall(method, meta, input, defHeader.promise, outStream, defStatus.promise, defTrailer.promise); const gCall = this.client.makeServerStreamRequest(`/${method.service.typeName}/${method.name}`, (value) => Buffer.from(method.I.toBinary(value, opt.binaryOptions)), (value) => method.O.fromBinary(value, opt.binaryOptions), input, gMeta, this.pickCallOptions(opt)); if (opt.abort) { opt.abort.addEventListener('abort', ev => { gCall.cancel(); }); } gCall.addListener('error', err => { const e = isServiceError(err) ? new RpcError(err.details, GrpcStatus[err.code], metadataFromGrpc(err.metadata)) : new RpcError(err.message); e.methodName = method.name; e.serviceName = method.service.typeName; defHeader.rejectPending(e); if (!outStream.closed) { outStream.notifyError(e); } defStatus.rejectPending(e); defTrailer.rejectPending(e); }); gCall.addListener('end', () => { if (!outStream.closed) { outStream.notifyComplete(); } }); gCall.addListener('data', arg1 => { assert(method.O.is(arg1)); outStream.notifyMessage(arg1); }); gCall.addListener('metadata', val => { defHeader.resolve(metadataFromGrpc(val)); }); gCall.addListener('status', val => { defStatus.resolvePending({ code: GrpcStatus[val.code], detail: val.details }); defTrailer.resolvePending(metadataFromGrpc(val.metadata)); }); return call; } clientStreaming(method, options) { var _a; const opt = options, meta = (_a = opt.meta) !== null && _a !== void 0 ? _a : {}, gMeta = metadataToGrpc(meta, opt.metadataOptions, method.idempotency === "IDEMPOTENT"), defHeader = new Deferred(), defMessage = new Deferred(), defStatus = new Deferred(), defTrailer = new Deferred(), gCall = this.client.makeClientStreamRequest(`/${method.service.typeName}/${method.name}`, (value) => Buffer.from(method.I.toBinary(value, opt.binaryOptions)), (value) => method.O.fromBinary(value, opt.binaryOptions), gMeta, this.pickCallOptions(opt), (err, value) => { if (value) { defMessage.resolve(value); } if (err) { const e = new RpcError(err.details, GrpcStatus[err.code], metadataFromGrpc(err.metadata)); e.methodName = method.name; e.serviceName = method.service.typeName; defHeader.rejectPending(e); defMessage.rejectPending(e); defStatus.rejectPending(e); defTrailer.rejectPending(e); } }), inStream = new GrpcInputStreamWrapper(gCall), call = new ClientStreamingCall(method, meta, inStream, defHeader.promise, defMessage.promise, defStatus.promise, defTrailer.promise); if (opt.abort) { opt.abort.addEventListener('abort', ev => { gCall.cancel(); }); } gCall.addListener('metadata', val => { defHeader.resolve(metadataFromGrpc(val)); }); gCall.addListener('status', val => { defStatus.resolvePending({ code: GrpcStatus[val.code], detail: val.details }); defTrailer.resolvePending(metadataFromGrpc(val.metadata)); }); return call; } duplex(method, options) { var _a; const opt = options, meta = (_a = opt.meta) !== null && _a !== void 0 ? _a : {}, gMeta = metadataToGrpc(meta, opt.metadataOptions, method.idempotency === "IDEMPOTENT"), defHeader = new Deferred(), outStream = new RpcOutputStreamController(), defStatus = new Deferred(), defTrailer = new Deferred(), gCall = this.client.makeBidiStreamRequest(`/${method.service.typeName}/${method.name}`, (value) => Buffer.from(method.I.toBinary(value, opt.binaryOptions)), (value) => method.O.fromBinary(value, opt.binaryOptions), gMeta, this.pickCallOptions(opt)), inStream = new GrpcInputStreamWrapper(gCall), call = new DuplexStreamingCall(method, meta, inStream, defHeader.promise, outStream, defStatus.promise, defTrailer.promise); if (opt.abort) { opt.abort.addEventListener('abort', ev => { gCall.cancel(); }); } gCall.addListener('error', err => { const e = isServiceError(err) ? new RpcError(err.details, GrpcStatus[err.code], metadataFromGrpc(err.metadata)) : new RpcError(err.message); e.methodName = method.name; e.serviceName = method.service.typeName; defHeader.rejectPending(e); if (!outStream.closed) { outStream.notifyError(e); } defStatus.rejectPending(e); defTrailer.rejectPending(e); }); gCall.addListener('end', () => { if (!outStream.closed) { outStream.notifyComplete(); } }); gCall.addListener('data', arg1 => { assert(method.O.is(arg1)); outStream.notifyMessage(arg1); }); gCall.addListener('metadata', val => { defHeader.resolve(metadataFromGrpc(val)); }); gCall.addListener('status', val => { defStatus.resolvePending({ code: GrpcStatus[val.code], detail: val.details }); defTrailer.resolvePending(metadataFromGrpc(val.metadata)); }); return call; } close() { this.client.close(); } } class GrpcInputStreamWrapper { constructor(inner) { this.inner = inner; } send(message) { return new Promise((resolve, reject) => { this.inner.write(message, resolve); // this.inner.end(message, resolve); }); } complete() { this.inner.end(); return Promise.resolve(undefined); } }