I am trying to use @grpc/proto-loader to do dynamic code generation of the protobuf files to implement a simple server but in Typescript.
I've gotten as far as
import { Server, loadPackageDefinition, ServerCredentials } from "grpc";
import { loadSync } from "@grpc/proto-loader";
const packageDefinition = loadSync(__dirname + "/protos/ArtifactUpload.proto");
const protoDescriptor = loadPackageDefinition(packageDefinition);
const impl = {
};
const server = new Server();
server.addService(protoDescriptor.ArtifactUpload, impl);
server.bind('0.0.0.0:50051', ServerCredentials.createInsecure());
server.start();
So I have two problems
protoDescriptor.XXX.service
however, there's no service
property in protoDescriptor.ArtifactUpload
impl
, the compiler also fails to compile.Since the Javascript example works, I am thinking that questions along the line of add new property in Typescript object may be able to add the necessary service type. However, I haven't had luck so far.
My Protobuf is
syntax = "proto3";
service ArtifactUpload {
rpc SignedUrlPutObject (UploadRequest) returns (SignedUrlPutObjectResponse) {}
}
message UploadRequest {
string message = 1;
}
message SignedUrlPutObjectResponse {
string reply = 1;
}
I got it working in the end as follows:
In package.json
I had the following:
{
...
"scripts": {
"start": "node index.js",
"build": "pbjs -t static-module -w commonjs -o protos.js protos/*.proto && pbts -o protos.d.ts protos.js && tsc",
},
"dependencies": {
"@grpc/proto-loader": "^0.5.5",
"google-protobuf": "^3.13.0",
"grpc": "^1.24.4",
"typescript": "^4.0.5"
},
"devDependencies": {
"@types/node": "^14.14.7",
"protobufjs": "^6.10.1"
}
}
import { Server, loadPackageDefinition, ServerCredentials, GrpcObject, ServiceDefinition, handleUnaryCall } from "grpc";
import { ISignedUrlPutObjectResponse, IUploadRequest, SignedUrlPutObjectResponse } from "./protos";
import { loadSync } from "@grpc/proto-loader";
const packageDefinition = loadSync(__dirname + "/protos/ArtifactUpload.proto");
interface IArtifactUpload {
signedUrlPutObject: handleUnaryCall<IUploadRequest, ISignedUrlPutObjectResponse>;
}
interface ServerDefinition extends GrpcObject {
service: any
}
interface ServerPackage extends GrpcObject {
[name: string]: ServerDefinition
}
const protoDescriptor = loadPackageDefinition(packageDefinition) as ServerPackage;
const server = new Server();
server.addService<IArtifactUpload>(protoDescriptor.ArtifactUpload.service, {
signedUrlPutObject(call, callback) {
console.log(call.request.message);
console.log(callback);
callback(null, SignedUrlPutObjectResponse.create({ reply: "hello " + call.request.message }));
}
});
server.bind('0.0.0.0:50051', ServerCredentials.createInsecure());
server.start();
I use protobufjs
to build some of the typings though they are mostly unused as it is not fully compatible with GRPC. However, it does save time with the request and response typings.
I still needed to create the server typings and apply it to the protoDescriptor
. Repeating it here for emphasis.
interface IArtifactUpload {
signedUrlPutObject(call: ServerUnaryCall<IUploadRequest>, callback: ArtifactUpload.SignedUrlPutObjectCallback): void;
}
interface ServerDefinition extends GrpcObject {
service: any;
}
interface ServerPackage extends GrpcObject {
[name: string]: ServerDefinition
}
I used any
for the service
as it was the only one that allowed me to avoid putting in anything specific to IArtifactUpload Ideally the typing for GrpcObject which at present is
export interface GrpcObject {
[name: string]: GrpcObject | typeof Client | ProtobufMessage;
}
should try to provide an object that represents the server.
I linked my solution to https://github.com/protobufjs/protobuf.js/issues/1017#issuecomment-725064230 in case there's a better way that I am missing.