David Carver
David Carver

Reputation: 43

How to generate the _grpc_pb.d.ts from a proto file for use with gRPC in a Node app?

Here is my npm run protoc, the line below will run:

./node_modules/protoc/protoc/bin/protoc --proto_path=proto --js_out=import_style=commonjs,binary:src/bin --grpc_out=src/bin --plugin=protoc-gen-grpc=node_modules/grpc-tools/bin/grpc_node_plugin --ts_out=service=true:src/bin proto/authentication_service.proto

And it generates the following files:

authentication_service_grpc_pb.js
authentication_service_pb.d.ts
authentication_service_pb.js
authentication_service_pb_service.d.ts
authentication_service_pb_service.js

At one time I was able to get it to generate a authentication_service_grpc_pb.d.ts but with the config I saved above it does not. Can anyone help with what I am missing? Thanks!

Upvotes: 4

Views: 3408

Answers (3)

WU Chris
WU Chris

Reputation: 1

I used https://www.npmjs.com/package/ts-protoc-gen to generate typescript definition. In my case, I needed to pass in service=grpc-node param to the --ts_out. Then I got the correct file generated. xxx_grpc_pb.d.ts

My full command is sth like

protoc --proto_path={{path to proto files}} \
  --plugin=protoc-gen-ts={{path to ts-protoc-gen plugin}} \
  --plugin=protoc-gen-grpc={{path to grpc-tools plugin}} \
  --plugin={{path to official js plugin downloaded from https://github.com/protocolbuffers/protobuf-javascript release}} \
  --js_out=import_style=commonjs,binary:{{generated file path}} \
  --grpc_out=grpc_js:{{generated file path}} \
  --ts_out=service=grpc-node,mode=grpc-js:{{generated file path}} \

Upvotes: 0

Petr Tripolsky
Petr Tripolsky

Reputation: 1613

All of that solutions are awful. Try that instead

Generation script

generate-dts.js

import { globSync } from "glob";
import { basename } from "path";
import { writeFileSync } from "fs";

import prettierSync from "@prettier/sync";

import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';

for (const protoPath of globSync("./proto/*.proto")) {
    
    const output = [];

    const namespaceName = basename(protoPath).replace(".proto", "");

    const packageDefinition = protoLoader.loadSync(protoPath);
    const proto = grpc.loadPackageDefinition(packageDefinition);

    const services = Object.entries(proto)
        .filter(([, value]) => value.service)
        .map(([key, value]) => [key, Object.keys(value.service)]);

    {
        output.push("declare namespace GRPC {");
        output.push(`namespace ${namespaceName} {`);
    }

    services.forEach(([className, methodList]) => {
        output.push(`interface ${className} {`);
        methodList.forEach((methodName) => {
            output.push(`${methodName}(...args: any): any;`);
        });
        output.push("}");
    });

    {
        output.push("}");
        output.push("}");
    }

    const typedef = prettierSync.format(output.join("\n"), {
        semi: true,
        endOfLine: "auto",
        trailingComma: "all",
        singleQuote: false,
        printWidth: 80,
        tabWidth: 2,
        parser: 'typescript',
    });

    writeFileSync(`./modules/remote-lib/types/${namespaceName}.d.ts`, typedef);
}

Output

service.d.ts


declare global {
  export namespace GRPC {
    export namespace service {
      export interface ServiceA {
        SendToServiceB(...args: any): any;
        SendToServiceBCCC(...args: any): any;
      }
      export interface ServiceB {
        ProcessRequest(...args: any): any;
      }
    }
  }
}

service.proto

syntax = "proto3";

// Message for communication between services
message Request {
  string message = 1;
}

message Response {
  string message = 1;
}

// Service A definition
service ServiceA {
  rpc SendToServiceB (Request) returns (Response);
  rpc SendToServiceBCCC (Request) returns (Response);
}

// Service B definition
service ServiceB {
  rpc ProcessRequest (Request) returns (Response);
}

Upvotes: 0

zamber
zamber

Reputation: 938

Take a look at the "How to use" section of the documentation and note that generating the d.ts codes is done with a different executable:

npm install grpc_tools_node_protoc_ts --save-dev

# generate js codes via grpc-tools
grpc_tools_node_protoc \
--js_out=import_style=commonjs,binary:./your_dest_dir \
--grpc_out=./your_dest_dir \
--plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` \
-I ./proto \
./your_proto_dir/*.proto

# generate d.ts codes
protoc \
--plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \
--ts_out=./your_dest_dir \
-I ./proto \
./your_proto_dir/*.proto

After writing this, that's not even the root of the problem (at least for this one particular generator). The executable in bin/ is protoc-gen-ts.

When you're trying out different stuff make sure to version-control your attempts and clean out the output directory to have a reproducible environment.

Given all of this my best guess is that the --ts-out and --js-out flags cancel each other out and you'll have to run the generator once for each output type. Verify by trying it out. As a bonus you could try finding out if there's a --verbose flag to make your life easier :).

Upvotes: 2

Related Questions