{"id":21076542,"url":"https://github.com/monojack/grpc-mod","last_synced_at":"2025-08-21T07:43:32.536Z","repository":{"id":57254518,"uuid":"117968037","full_name":"monojack/grpc-mod","owner":"monojack","description":"GRPC Client that mods requests/response and more","archived":false,"fork":false,"pushed_at":"2018-11-19T14:19:23.000Z","size":78,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-19T15:55:34.207Z","etag":null,"topics":["async","await","grpc-client","grpc-node","javascript","mod","observable","promise"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/monojack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-18T10:28:19.000Z","updated_at":"2024-01-25T14:27:29.000Z","dependencies_parsed_at":"2022-08-31T08:21:37.102Z","dependency_job_id":null,"html_url":"https://github.com/monojack/grpc-mod","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/monojack/grpc-mod","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fgrpc-mod","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fgrpc-mod/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fgrpc-mod/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fgrpc-mod/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/monojack","download_url":"https://codeload.github.com/monojack/grpc-mod/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monojack%2Fgrpc-mod/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271447018,"owners_count":24761423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","await","grpc-client","grpc-node","javascript","mod","observable","promise"],"created_at":"2024-11-19T19:28:59.748Z","updated_at":"2025-08-21T07:43:32.512Z","avatar_url":"https://github.com/monojack.png","language":"JavaScript","readme":"# **gRPC-Mod**\n\n[gRPC](https://grpc.io/) client that lets you manipulate the response objects and returns promises or observables for streams.\n\n\n[![Build Status](https://travis-ci.org/monojack/grpc-mod.svg?branch=master)](https://travis-ci.org/monojack/grpc-mod)\n[![npm version](https://img.shields.io/npm/v/grpc-mod.svg)](https://www.npmjs.com/package/grpc-mod)\n[![npm downloads](https://img.shields.io/npm/dm/grpc-mod.svg)](https://www.npmjs.com/package/grpc-mod)\n\n## Table of contents\n\n* [Installation](#installation)\n* [API by example](#api-by-example)\n  * [Enhancing the client](#enhancing-the-client)\n    * [GRPCModClient](#grpcmodclient)\n    * [mod](#mod)\n  * [Promises and observables](#promises-and-observables)\n    * [Unary requests](#unary-requests)\n    * [Server stream requests](#server-stream-requests)\n    * [Client stream requests](#client-stream-requests)\n    * [Bidirectional stream requests](#bidirectional-stream-requests)\n  * [Manipulating the response](#manipulating-the-response)\n    * [Provided mods](#provided-mods)\n      * [noDefaults](#nodefaults)\n      * [noNilProps](#nonilprops)\n      * [enforceNumber](#enforcenumber)\n    * [Creating custom mods](#creating-custom-mods)\n      * [getTypeSchema](#gettypeschema)\n      * [deepApplyMod](#deepapplymod)\n\n\n## Installation\n```sh\nnpm install grpc-mod\n```\n\n## API by example\n\nConsider the following `.proto` file\n\n```protobuf\nsyntax = \"proto3\";\n\npackage test;\n\nmessage GetRequest {}\n\nmessage GetOneRequest {\n  string id = 1;\n}\n\nmessage GetPageRequest {\n  int32 from = 1;\n  int32 count = 2;\n}\n\nmessage SaveTodoRequest {\n  string label = 1;\n}\n\nmessage TodoResponse {\n  string id = 1;\n  string label = 2;\n  int64 created = 3;\n  int64 completed = 4;\n}\n\nmessage TodoListResponse {\n  repeated TodoResponse todos = 1;\n}\n\n\nservice TestService {\n  rpc getTodo (GetOneRequest) returns (TodoResponse) {}\n  rpc getAllTodos (GetRequest) returns (stream TodoResponse) {}\n  rpc saveTodos (stream SaveTodoRequest) returns (TodoListResponse) {}\n  rpc getTodoPages (stream GetPageRequest) returns (stream TodoListResponse) {}\n}\n```\n\n### Enhancing the client\n\nThere are two ways to enhance the client\n  1. You either create a new client with **GRPCModClient**\n```js\nconst client = new GRPCModClient(config)\n```\n  2. enhance an existing one with the **mod** method\n```js\nmod(client)\n```\n\n##### *GRPCModClient*\n\u003e new GRPCModClient(config: { protoPath, packageName, serviceName, serverAddress, credentials [, options] } [, options])\n\n**config**: required - the configuration object containing information for instantiating the client. it takes the following props:\n  - `protoPath`: required - path to the `.proto` file\n  - `packageName`: required - name of the package\n  - `serviceName`: required - name of the service we want to instantiate the client from\n  - `serverAddress`: required - address of the server we want our client to connect to\n  - `credentials`: required - credentials needed for connecting to the server\n  - `options`: optional - options to apply to channel creation\n\n**options**: optional - the GRPCMod client options\n  - `noDefaults`: boolean: Nullifies the default values added to the response object (default is `false`) - [example](#nodefaults)\n  - `noNilProps`: boolean: Removes `null` or `undefined` values from the response object (default is `false`) - [example](#nonilprops)\n  - `enforceNumber`: boolean:  Turns all `int64` types into numbers (default is `false`) - [example](#enforcenumber)\n  - `mods`: function | [function] : one or a list of custom [mods](#creating-custom-mods)\n\n```js\nconst path = require('path')\nconst { credentials, } = require('grpc')\nconst { GRPCModClient, } = require('grpc-mod')\n\nconst PROTO_PATH = path.resolve(__dirname, '../pb/messages.proto')\nconst client = new GRPCModClient({\n  protoPath: PROTO_PATH,\n  packageName: 'test',\n  serviceName: 'TestService',\n  serverAddress: 'localhost:8080',\n  credentials: credentials.createInsecure(),\n})\n```\n\n##### *mod*\n\u003e mod(client [, schema] [, options])\n\n```js\nconst path = require('path')\nconst { load, credentials, } = require('grpc')\nconst { mod, } = require('grpc-mod')\n\nconst PROTO_PATH = path.resolve(__dirname, '../pb/messages.proto')\nconst proto = load(PROTO_PATH).test\n\nconst client = new proto.TestService('localhost:8080', credentials.createInsecure())\nmod(client)\n```\n\nThe ***mod*** method is most useful when you only want to make use of promises and observables. If you want to write mods that need to know about the type of any of the properties, you would need a schema. ***GRPCModClient*** takes care of generating a schema for you, but if you really want to just use ***mod***, for whatever reason, and still write mods that depend on a schema, you'd have to provide one yourself. You can either use [protocol-buffers-schema](https://github.com/mafintosh/protocol-buffers-schema) or just write it yourself but it has to resemble the model that ***protocol-buffers-schema*** returns\n\n\u003e***NOTE:*** Some of the provided mods also depend on a schema and if you're using the ***mod*** method to enhance your client and activate one of those mods without providing a schema, the method will throw an error.\n\n### Promises and observables\n#### Unary requests\n```js\n// Simple promise\nlet doc\nclient\n  .getTodo({ id: '5a54caf61bec98000f59dcbe' })\n  .then(response =\u003e {\n    doc = response\n  })\n  .catch(err =\u003e {\n    console.log(err)\n  })\n\n// Async/await\n\nlet doc\ntry {\n  doc = await client.getTodo({ id: '5a54caf61bec98000f59dcbe' })\n} catch (err) {\n  console.log(err)\n}\n```\n\n#### Server stream requests\n```js\n\n// Using observables\nconst observable = client.getAllTodos({})\n\nobservable.subscribe({\n  next: (data) =\u003e console.log(data),\n  error: (err) =\u003e console.log(err),\n  complete: () =\u003e console.log('complete')\n})\n\n// Converting to Promise\nconst docs = await client.getAllTodos({}).toPromise()\n// docs will be an array containing all the responses\n\n```\n\n#### Client stream requests\n\n```js\nconst todosToAdd = [...]\n\nconst stream = client.saveTodos()\n// the stream has a getPromise method which you can use to listen for the response\nconst promise = stream.getPromise()\n\nfor(const label in todosToAdd) {\n  sream.write({ label })\n}\nstream.end()\n\nconst docs = await promise // list of the added todos maybe?\n```\n\n#### Bidirectional stream requests\n```js\nconst stream = client.getTodoPages()\n\n// the stream has a getObservable method\nconst observable = stream.getObservable()\nobservable.subscribe({\n  next: data =\u003e renderTable(data.todos),\n  err: err =\u003e console.log(err)\n})\n\nfunction onPaginationSelect(from, count) {\n  stream.write({from, count })\n}\n```\n\n### Manipulating the response\n\nYou can manipulate the response by providing a configuration object as the second argument when creating or enhancing a client. You have access to a few mods provided by the library but you can always add your own. Keep in mind that when activating any of the provided mods, they will be executed before those you provide.\n\n#### Provided mods\n##### *noDefaults*\n\ngRPC will add a default value for any of the props that are not present on the response object. In a client environment you might not expect a `completed` prop on a todo object that is not completed, but gRPC will actually set it to `0`. Find out more about default values [here](https://developers.google.com/protocol-buffers/docs/proto3#default).\n\nThe `noDefaults` flag will turn all the default values to null. You can further remove these props completely with `noNilProps`\n\n```js\n...\nconst client = new GRPCModClient({\n  protoPath: PROTO_PATH,\n  packageName: 'test',\n  serviceName: 'TestService',\n  serverAddress: 'localhost:8080',\n  credentials: credentials.createInsecure(),\n}, {\n  noDefaults: true\n})\n\nconst doc = await client.getTodo({id: '5a54caf61bec98000f59dcbe'})\n\n// noDefaults: false =\u003e\n// {\n//  id: '5a54caf61bec98000f59dcbe',\n//  label: 'Do something',\n//  created: '1515862405277',\n//  completed: 0\n// }\n\n\n// noDefaults: true =\u003e\n// {\n//  id: '5a54caf61bec98000f59dcbe',\n//  label: 'Do something',\n//  created: '1515862405277',\n//  completed: null\n// }\n```\n\n##### *noNilProps*\n\nFollowing the `noDefaults` example, we can add `noNilProps: true` to our configuration to remove the null/undefined props completely.\n\n```js\n...\nconst client = new GRPCModClient({\n  ...\n}, {\n  noDefaults: true,\n  noNilProps: true\n})\n\nconst doc = await client.getTodo({id: '5a54caf61bec98000f59dcbe'})\n\n// noDefaults: true, noNilProps: true =\u003e\n// {\n//  id: '5a54caf61bec98000f59dcbe',\n//  label: 'Do something',\n//  created: '1515862405277'\n// }\n```\n\u003e***NOTE:*** You need to first turn the defaults to null with `noDefaults` so they will get excluded by `noNilProps`*\n\n##### *enforceNumber*\n\nYou might have noticed in the previous examples that `created` is a *string*. We asked for *int64* and it should be a *Long* object but what we ultimately want is a number. Read about this design [here](https://github.com/grpc/grpc/issues/7229)\n\nThe `enforceNumber` flag will turn all *int64* values to numbers. There might be more \"issues\" like this, but this is the one I bumped into and I seriously have no idea about any other data-types not suported by JS.\n\n```js\n...\nconst client = new GRPCModClient({\n  ...\n}, {\n  noDefaults: true,\n  noNilProps: true,\n  enforceNumber: true\n})\n\nconst doc = await client.getTodo({id: '5a54caf61bec98000f59dcbe'})\n\n// noDefaults: true, noNilProps: true =\u003e\n// {\n//  id: '5a54caf61bec98000f59dcbe',\n//  label: 'Do something',\n//  created: 1515862405277\n// }\n```\n\n#### Creating custom mods\nThe configuration object accepts a 'mods' prop where you can specify one or a list of mods that you build.\n\n```js\n...\nconst client = new GRPCModClient({\n  ...\n}, {\n  noDefaults: true,\n  noNilProps: true,\n  enforceNumber: true,\n  mods: myMod // or a list: [myMod2, myMod1]\n})\n```\nCustom mods get applied from right to left. So, if `myMod2` expects a response type returned by `myMod1`, you'd have to list them in the right order `[myMod2, myMod1]`. Also, any of the provided mods will run before yours.\n\n**Mods** are simple functions that take 2 arguments: the `response` object and a `schema` object. The `response` object is exactly what it says, the response you get from the server. The `schema` object is not the entire schema generated from the `.proto` file, but only the part relevant to the response and only the top level. E.g.:\n\n```js\nfunction myMod(response, schema) {\n  console.log(reponse)\n  // {\n  //  id: '5a54caf61bec98000f59dcbe',\n  //  label: 'Do something',\n  //  created: 1515862405277\n  // }\n\n  console.log(schema)\n  // { id:\n  //    { name: 'id',\n  //      type: 'string',\n  //      tag: 1,\n  //      map: null,\n  //      oneof: null,\n  //      required: false,\n  //      repeated: false,\n  //      options: {}, },\n  // label:\n  //    { name: 'label',\n  //      type: 'string',\n  //      tag: 2,\n  //      map: null,\n  //      oneof: null,\n  //      required: false,\n  //      repeated: false,\n  //      options: {}, },\n  // created:\n  //    { name: 'created',\n  //      type: 'int64',\n  //      tag: 3,\n  //      map: null,\n  //      oneof: null,\n  //      required: false,\n  //      repeated: false,\n  //      options: {}, },\n  // completed:\n  //    { name: 'completed',\n  //      type: 'int64',\n  //      tag: 4,\n  //      map: null,\n  //      oneof: null,\n  //      required: false,\n  //      repeated: false,\n  //      options: {},\n  //    },\n  // }\n}\n```\n\nThe schema is generated from the `.proto` file with [protocol-buffers-schema](https://github.com/mafintosh/protocol-buffers-schema) and is only relevant to the top level of the response object, meaning that if you have property of a custom type, the schema of that type will not be available. This prevents the need of generating deeply nested schemas and eventual circular dependencies, like in the case of a `User` type that has a field called `friends` being a repeated `User` type. Those friends would also be of type `User` and also have friends of their own and so on.\n\nOur `saveTodos` method is a client-stream request ant it resolves with a `TodoListResponse`. The `TodoListResponse` is an object with a `todos` property of type `repeated TodoResponse` (an array of todos).\n\n```js\nfunction myMod(response, schema) {\n  console.log(schema)\n  // { todos:\n  //    { name: 'todos',\n  //      type: 'TodoResponse',\n  //      tag: 1,\n  //      map: null,\n  //      oneof: null,\n  //      required: false,\n  //      repeated: true,\n  //      options: {},\n  //    },\n  // }\n}\n```\n\n##### *getTypeSchema*\n\nIf we want to modify all the todos in that list, we'd have to `map` over `data`. This is pretty trivial if we don't need to check the type of the todo's properties, but if we want to manipulate a value based on the type of that property, we'd need access to the `TodoResponse` schema.\n\nThere is, however, a method to retrieve the schema of a property's type, `getTypeSchema`. This method is part of the schema object of every type and we'll see how it works by exploring how you'd implement a mod similar to [enforceNumber](#enforcenumber).\n\n```js\nfunction enforceNumber(response, schema){\n  // response is { todos: [{...}, {...}, {...}] }\n\n  const todoSchema = schema['todos'].getTypeSchema()\n\n  const moddedList = response.todos.map(todo =\u003e {\n    // reduce the todo entries and create a new object\n    return Object.entries(todo).reduce((acc, [key, value]) =\u003e {\n      // if the current property is of type `int64` we parse it into a number\n      return {\n        ...acc,\n        [key]: todoSchema[key].type === 'int64' ? parseInt(value) : value\n      }\n    }, {})\n  })\n\n  // Return the modified response object,\n  // or you can just return the list, but I recommend you stick with the contract\n  return { todos: moddedList }\n}\n```\n\u003e***NOTE:*** This is not how `enforceNumber` is written. The actual implementation recursively handles nested response objects and are partially applied with the `schema` argument. You can read through the source if you're interested how it all works.\n\n##### *deepApplyMod*\n\u003e deepApplyMod(response [, schema], transformFn [, predicate])\n\nThis is a helper function, provided by 'grpc-mod' which you can use to apply mods to nested response objects.\n\nLet's say that we want to transform `created` and `completed` props from unix to date strings.\n\n```js\nfunction unixToDateString(response) {\n  return {\n    ...response,\n    created: new Date(response.created).toString(),\n    completed: new Date(response.completed).toString()\n  }\n}\n```\nAnd then we add this function to the mods. Easy, right? This is enough if we only ever get a `TodoResponse`, but in the case of a `TodoListResponse`, it won't get applied. Remember, `TodoListResponse` looks like:\n\n```js\n{ data: [TodoResponse, TodoResponse, TodoResponse, ...]}\n```\nso there will be no 'created' or 'completed' props. In fact, with the above mod, those would get added alongside `data`.\nWe'll use `deepApplyMod` to solve this issue, providing a transformation function and a predicate to only apply it to the `created` and `completed` keys.\n\nThe *transformation function* takes a single argument, and it's the value of the currently iterated property. The *predicate* is optional (default `true`), and it's a function that takes 2 arguments, the `key` and the `value` of the property.\n\n```js\nimport { deepApplyMod } from 'grpc-mod'\n\n// This is the transformation function that will get applied to the properties\nconst convertToDateString =(value) =\u003e new Date(value).toString()\n\n// We want to also provide a predicate to apply that transformation only to the `created` and `completed` keys\nconst isDateProp = (key, value) =\u003e ['created', 'completed'].includes(key)\n\nfunction unixToDateString(response, schema) {\n  return deepApplyMod(response, null, convertToDateString, isDateProp)\n}\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonojack%2Fgrpc-mod","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmonojack%2Fgrpc-mod","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonojack%2Fgrpc-mod/lists"}