@vendit-dev/grpc-caller
Version:
An improved Node.js gRPC client
125 lines (108 loc) • 4.33 kB
JavaScript
const grpc = require("@grpc/grpc-js");
const _ = require("lodash");
const gi = require("grpc-inspect");
const create = require("grpc-create-metadata");
const createClient = require("./client");
const BaseRequest = require("./request");
const Response = require("./response");
const protoLoader = require("@grpc/proto-loader");
module.exports = caller;
/**
* Create client isntance.
* @param {String} host - The host to connect to
* @param {String|Object} proto Path to the protocol buffer definition file or
* Object specifying <code>file</code> to load and <code>load</code> options for proto loader.
* @param {String} name - In case of proto path the name of the service as defined in the proto definition.
* @param {Object} credentials - The credentials to use to connect. Defaults to `grpc.credentials.createInsecure()`
* @param {Object} options - Options to be passed to the gRPC client constructor
* @param {Object} options.retry - In addition to gRPC client constructor options, we accept a `retry` option.
* The retry option is identical to `async.retry` and is passed as is to it.
* This is used only for `UNARY` calls to add automatic retry capability.
* @param {Object} defaults - Metadata and Options that will be passed to every Request
* @returns {Object}
*
* @example <caption>Create client dynamically</caption>
* const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto')
* const client = caller('localhost:50051', PROTO_PATH, 'Greeter')
*
* @example <caption>With options</caption>
* const file = path.join(__dirname, 'helloworld.proto')
* const load = {
* // ... proto-loader load options
* }
* const client = caller('localhost:50051', { file, load }, 'Greeter')
*
* @example <caption>Create a static client</caption>
* const services = require('./static/helloworld_grpc_pb')
* const client = caller('localhost:50051', services.GreeterClient)
*
* @example <caption>Pass Options, Default Metadata and Interceptor options</caption>
* const metadata = { node_id: process.env.CLUSTER_NODE_ID };
* const credentials = grpc.credentials.createInsecure()
* const options = {
* interceptors = [ bestInterceptorEver ]
* }
* const client = caller('localhost:50051', PROTO_PATH, 'Greeter', credentials, options, {
* metadata: { foo: 'bar' }
* })
*
* // Now every call with that client will result
* // in invoking the interceptor and sending the default metadata
*
*/
function caller(host, proto, name, credentials, options, defaults) {
let Ctor;
if (_.isString(proto) || (_.isObject(proto) && proto.file)) {
let protoFilePath = proto;
let loadOptions = {};
if (_.isObject(proto) && proto.file) {
protoFilePath = proto.file;
loadOptions = proto.load || {};
}
const packageDefinition = protoLoader.loadSync(protoFilePath, loadOptions);
const loaded = grpc.loadPackageDefinition(packageDefinition);
const descriptor = gi(loaded);
if (!descriptor) {
throw new Error(`Error parsing protocol buffer`);
}
Ctor = descriptor.client(name);
if (!Ctor) {
throw new Error(
`Service name ${name} not found in protocol buffer definition`
);
}
} else if (_.isObject(proto)) {
Ctor = proto;
options = credentials;
defaults = options;
credentials = name;
}
const client = new Ctor(
host,
credentials || grpc.credentials.createInsecure(),
options
);
const { metadata: defaultMetadata, options: defaultOptions } = Object.assign(
{},
defaults
);
return wrap(client, defaultMetadata, defaultOptions);
}
function wrap(client, metadata, options) {
const GRPCCaller = createClient(Object.getPrototypeOf(client));
class Request extends BaseRequest {}
const instance = new GRPCCaller(client, metadata, options);
instance.Request = Request;
instance.Request.prototype.client = instance;
instance.Response = Response;
return instance;
}
/**
* Utility helper function to create <code>Metadata</code> object from plain Javascript object.
* See <code>grpc-create-metadata</code> module.
*/
caller.metadata = create;
/**
* Utility function that can be used to wrap an already constructed client instance.
*/
caller.wrap = wrap;