How do you to implement a GRPC server in TypeScript? - node.js

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
in the Javascript examples they use protoDescriptor.XXX.service however, there's no service property in protoDescriptor.ArtifactUpload
if I try to add implementation methods in 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;
}

[Updated on 14 May 2021]: TypeScript generation via #grpc/proto-loader is now released with version 0.6.0! I've updated my example here to reflect this. You can now install the latest version of proto loader with npm i #grpc/proto-loader which will contain the TS generation script. The instructions below are still valid.
You can use the proto-loader to generate types.
First, install the proto-loader:
npm i #grpc/proto-loader
You can then generate the types like so:
./node_modules/.bin/proto-loader-gen-types --longs=String --enums=String --defaults --oneofs --grpcLib=#grpc/grpc-js --outDir=proto/ proto/*.proto
Here's the proto file I use for this example:
syntax = "proto3";
package example_package;
message ServerMessage {
string server_message = 1;
}
message ClientMessage {
string client_message = 1;
}
service Example {
rpc unaryCall(ClientMessage) returns (ServerMessage) {}
rpc serverStreamingCall(ClientMessage) returns (stream ServerMessage) {}
rpc clientStreamingCall(stream ClientMessage) returns (ServerMessage) {}
rpc bidirectionalStreamingCall(stream ClientMessage) returns (stream ServerMessage) {}
}
Once the types are generated, you can consume them like so:
import * as grpc from '#grpc/grpc-js';
import * as protoLoader from '#grpc/proto-loader';
import { ProtoGrpcType } from './proto/example';
import { ClientMessage } from './proto/example_package/ClientMessage';
import { ExampleHandlers } from './proto/example_package/Example';
import { ServerMessage } from './proto/example_package/ServerMessage';
const host = '0.0.0.0:9090';
const exampleServer: ExampleHandlers = {
unaryCall(
call: grpc.ServerUnaryCall<ClientMessage, ServerMessage>,
callback: grpc.sendUnaryData<ServerMessage>
) {
if (call.request) {
console.log(`(server) Got client message: ${call.request.clientMessage}`);
}
callback(null, {
serverMessage: 'Message from server',
});
},
serverStreamingCall(
call: grpc.ServerWritableStream<ClientMessage, ServerMessage>
) {
call.write({
serverMessage: 'Message from server',
});
},
clientStreamingCall(
call: grpc.ServerReadableStream<ClientMessage, ServerMessage>
) {
call.on('data', (clientMessage: ClientMessage) => {
console.log(
`(server) Got client message: ${clientMessage.clientMessage}`
);
});
},
bidirectionalStreamingCall(
call: grpc.ServerDuplexStream<ClientMessage, ServerMessage>
) {
call.write({
serverMessage: 'Message from server',
});
call.on('data', (clientMessage: ClientMessage) => {
console.log(
`(server) Got client message: ${clientMessage.clientMessage}`
);
});
},
};
function getServer(): grpc.Server {
const packageDefinition = protoLoader.loadSync('./proto/example.proto');
const proto = (grpc.loadPackageDefinition(
packageDefinition
) as unknown) as ProtoGrpcType;
const server = new grpc.Server();
server.addService(proto.example_package.Example.service, exampleServer);
return server;
}
if (require.main === module) {
const server = getServer();
server.bindAsync(
host,
grpc.ServerCredentials.createInsecure(),
(err: Error | null, port: number) => {
if (err) {
console.error(`Server error: ${err.message}`);
} else {
console.log(`Server bound on port: ${port}`);
server.start();
}
}
);
}
I've created various examples of how to use gRPC with TypeScript here: https://github.com/badsyntax/grpc-js-typescript

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.

Related

Apollo subscriptions - Nextjs - Error: Observable cancelled prematurely at Concast.removeObserver

I am trying to use apollo/graphql subscription in my nextjs project, my graphql server is placed in external nextjs service,I can work with queries and mutation without any problem but when I use an implementation of useSubscription I get the following error:
"Error: Observable cancelled prematurely
at Concast.removeObserver (webpack-internal:///../../node_modules/#apollo/client/utilities/observables/Concast.js:118:33)
at eval (webpack-internal:///../../node_modules/#apollo/client/utilities/observables/Concast.js:21:47)
at cleanupSubscription (webpack-internal:///../../node_modules/zen-observable-ts/module.js:92:7)
at Subscription.unsubscribe (webpack-internal:///../../node_modules/zen-observable-ts/module.js:207:7)
at cleanupSubscription (webpack-internal:///../../node_modules/zen-observable-ts/module.js:97:21)
at Subscription.unsubscribe (webpack-internal:///../../node_modules/zen-observable-ts/module.js:207:7)
at eval (webpack-internal:///../../node_modules/#apollo/client/react/hooks/useSubscription.js:106:26)
at safelyCallDestroy (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:22763:5)
at commitHookEffectListUnmount (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:22927:11)
at invokePassiveEffectUnmountInDEV (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:24998:13)
at invokeEffectsInDev (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:27137:11)
at commitDoubleInvokeEffectsInDEV (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:27110:7)
at flushPassiveEffectsImpl (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:26860:5)
at flushPassiveEffects (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:26796:14)
at eval (webpack-internal:///../../node_modules/react-dom/cjs/react-dom.development.js:26592:9)
at workLoop (webpack-internal:///../../node_modules/scheduler/cjs/scheduler.development.js:266:34)
at flushWork (webpack-internal:///../../node_modules/scheduler/cjs/scheduler.development.js:239:14)
at MessagePort.performWorkUntilDeadline (webpack-internal:///../../node_modules/scheduler/cjs/scheduler.development.js:533:21)"
I know that the subscriptions server is working right because I can to listening from apollo studio and I have created a spa with create-react-app and it works fine
I have used:
Server:
"apollo-server-express": "^3.6.7"
"graphql-ws": "^5.7.0"
Client
"next": "^12.1.5"
"#apollo/client": "^3.5.10"
"graphql-ws": "^5.7.0"
Hook implementation
const room = useSubscription(
gql`
subscription onRoomAdded($roomAddedId: ID!) {
roomAdded(id: $roomAddedId) {
id
name
}
}
`
);
Client implementation
import { ApolloClient, HttpLink, InMemoryCache, split } from '#apollo/client';
import { GraphQLWsLink } from '#apollo/client/link/subscriptions';
import { getMainDefinition } from '#apollo/client/utilities';
import { createClient } from 'graphql-ws';
import fetch from 'isomorphic-fetch';
const HOST = 'http://localhost:3001/graphql';
const HOST_WS = 'ws://localhost:3001/graphql';
const isServer = typeof window === 'undefined';
if (isServer) {
global.fetch = fetch;
}
const httpLink = new HttpLink({
uri: HOST,
});
const link = isServer
? httpLink
: split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
new GraphQLWsLink(
createClient({
url: HOST_WS,
})
),
httpLink
);
const client = new ApolloClient({
ssrMode: isServer,
link,
cache: new InMemoryCache(),
});
export default client;
any idea about the problem? I think the problem could be that NextJS only works with subscriptions-transport-ws but in the official apollo documentation indicates that the new official way is to use graphql-ws the other library is unmaintained already
UPDATE!
I have checked that the subscriptions are working right in production build, I'm investigating how to implement in development process. any suggestions are welcome.
If it is working in production, but in not in dev, you may have the same issue I had with my React SPA: StrictMode and double rendering as described in this github issue.
So far I have found 2 ways to make it work:
remove StrictMode
subscribe with vanilla JS instead ofuseSubscription
const ON_USER_ADDED = gql`
subscription OnUserAdded {
userAdded {
name
id
}
}
`;
const subscribe = () => {
client.subscribe({
query: ON_USER_ADDED,
}).subscribe({
next(data) {
console.log('data', data);
},
complete(){
console.log('complete');
},
error(err) {
console.log('error', err);
}
})
};

How to make kuzzle-device-manager plugin API actions works?

I successfully installed and loaded kuzzle-device-manager in the backend file:
import { Backend } from 'kuzzle';
import { DeviceManagerPlugin } from 'kuzzle-device-manager';
const app = new Backend('playground');
console.log(app.config);
const deviceManager = new DeviceManagerPlugin();
const mappings = {
updatedAt: { type: 'date' },
payloadUuid: { type: 'keyword' },
value: { type: 'float' }
}
deviceManager.devices.registerMeasure('humidity', mappings)
app.plugin.use(deviceManager)
app.start()
.then(async () => {
// Interact with Kuzzle API to create a new index if it does not already exist
console.log(' started!');
})
.catch(console.error);
But when i try to use controllers from that plugin for example device-manager/device with create action i get an error output.
Here is my "client" code in js:
const { Kuzzle, WebSocket } = require("kuzzle-sdk")
const kuzzle = new Kuzzle(
new WebSocket('KUZZLE_IP')
)
kuzzle.on('networkError', error => {
console.error('Network Error: ', error);
})
const run = async () => {
try {
// Connects to the Kuzzle server
await kuzzle.connect();
// Creates an index
const result = await kuzzle.query({
index: "nyc-open-data",
controller: "device-manager/device",
action: "create",
body: {
model: "model-1234",
reference: "reference-1234"
}
}, {
queuable: false
})
console.log(result)
} catch (error) {
console.error(error.message);
} finally {
kuzzle.disconnect();
}
};
run();
And the result log:
API action "device-manager/device":"create" not found
Note: The nyc-open-data index exists and is empty.
We apologize for this mistake in the documentation, the device-manager/device:create method is not available because the plugin is using auto-provisioning until the v2.
You should send a payload to your decoder, the plugin will automatically provision the device if it does not exists https://docs.kuzzle.io/official-plugins/device-manager/1/guides/decoders/#receive-payloads

VSCode Extension with LSP in C#, how to terminate the LSP dll?

I have a very simple extension that loads a DLL that will contain my LSP project.
namespace SimpleLsp
{
class Program
{
static async Task Main(string[] args)
{
var server = await OmniSharp.Extensions.LanguageServer.Server.LanguageServer
.From(
options => options
.WithInput(Console.OpenStandardInput())
.WithOutput(Console.OpenStandardOutput())
).ConfigureAwait(false);
await server.WaitForExit;
}
}
}
and the TypeScript extension
import * as vscode from 'vscode';
import path = require('path');
import { workspace } from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind,
} from "vscode-languageclient/node";
let client: LanguageClient;
export function activate(context: vscode.ExtensionContext) {
console.log('Congratulations, your extension "simplelspextension" is now active!');
const extensionPath = vscode.extensions.getExtension("PauloAboimPinto.simplelspextension")?.extensionPath as string;
const libsFolder = path.join(extensionPath, "libs");
const dllPath = path.join(libsFolder, "simpleLsp.dll");
let serverOptions: ServerOptions = {
run: {
command: "dotnet",
args: [dllPath],
transport: TransportKind.pipe
},
debug: {
command: "dotnet",
args: [dllPath],
transport: TransportKind.pipe,
runtime: ""
}
};
let clientOptions: LanguageClientOptions = {
documentSelector: [
{
pattern: "**/*.xaml",
},
{
pattern: "**/*.axaml",
},
{
pattern: "**/*.csproj",
},
],
synchronize: {
// Notify the server about file changes to '.clientrc files contained in the workspace
fileEvents: workspace.createFileSystemWatcher('**/.axaml')
}
};
client = new LanguageClient(
"simpleLsp",
"Simple Language Server Protocol",
serverOptions,
clientOptions
);
let disposableLsp = client.start();
context.subscriptions.push(disposableLsp);
}
// this method is called when your extension is deactivated
export function deactivate() {
if (!client) {
return undefined;
}
return client.stop();
}
During deactivation I'm sending the stop command to the client, expecting the execution DLL to be terminated, but it's not.
When I close the VSCode I would expect the simpleLsp.dll to be terminated but it's not and I have to terminate it manually.
What I'm missing or doing wrong here?
How I can terminate the execution of the DLL that contains the LSP?
thanks in advance

Error: Cannot use namespace 'Observable' as a type. (apollo-link, typescript)

i am trying to create a graphql subcription with hasura, follow this example: Boilerplate to test GraphQL subscriptions using nodejs, just added types for trypescript, if i run it directly with nodemon works fine, and gets me the data i want but if try to make a tsc i get the following errors:
and my code for the subscription is:
/***** Setup a GraphQL subscription observable ******************************/
import { DocumentNode, execute, GraphQLRequest } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import ws from 'ws';
const getWsClient = (wsURL: string) => {
const client = new SubscriptionClient(
wsURL, {
reconnect: true, connectionParams: () => {
return {
headers: {
'x-hasura-admin-secret': process.env.HASURA_GRAPHQL_ADMIN_SECRET
}
}
}
}, ws
);
return client;
};
// wsURL: GraphQL endpoint
// query: GraphQL query (use gql`` from the 'graphql-tag' library)
// variables: Query variables object
const createSubscription = (wsURL: string, query: DocumentNode, variables: Record<string, any>) => {
const link = new WebSocketLink(getWsClient(wsURL));
return execute(link, { query, variables } as GraphQLRequest);
};
export default createSubscription
I already updated typescript for
"nodemon": "^2.0.15",
"ts-node": "^10.2.1",
"typescript": "^4.4.2"
does any one have any ideas?
Thanks in advance.

How do you generate typings for protobuf files for use with GRPC?

I am trying to use GRPC with TypeScript, and I am trying to make sure I have all the types set (rather than just adding my own mapping or using any.
I've gotten as far as with problems I am experiencing noted in the comments.
import { Server, loadPackageDefinition } from "grpc";
import { loadSync } from "#grpc/proto-loader";
const packageDefinition = loadSync(__dirname + "/protos/artifact.proto");
const artifacts = loadPackageDefinition(packageDefinition).artifacts;
// what are the types here?
function SignedUrlPutObject(call, callback) {
}
const server = new Server();
// There's no ArtifactUpload defined in artifacts
server.addService(artifacts.ArtifactUpload.service, { SignedUrlPutObject })
Another approach I tried was to use pbjs and pbts.
"protobuf": "pbjs -t static-module -w commonjs -o protos.js protos/artifact-upload.proto && pbts -o protos.d.ts protos.js",
This generated the typings file, but I can't get it to work with grpc. Here's a github issue I found that may be related https://github.com/protobufjs/protobuf.js/issues/1381
There's 3 main tools you can use:
ts-protoc-gen
#grpc/proto-loader
grpc_tools_node_protoc_ts
I recommend using proto-loader:
npm i #grpc/proto-loader
You can then generate the types like so:
./node_modules/.bin/proto-loader-gen-types --longs=String --enums=String --defaults --oneofs --grpcLib=#grpc/grpc-js --outDir=proto/ proto/*.proto
Here's the proto file I use for this example:
syntax = "proto3";
package example_package;
message ServerMessage {
string server_message = 1;
}
message ClientMessage {
string client_message = 1;
}
service Example {
rpc unaryCall(ClientMessage) returns (ServerMessage) {}
rpc serverStreamingCall(ClientMessage) returns (stream ServerMessage) {}
rpc clientStreamingCall(stream ClientMessage) returns (ServerMessage) {}
rpc bidirectionalStreamingCall(stream ClientMessage) returns (stream ServerMessage) {}
}
Once the types are generated, you can consume them like so:
import * as grpc from '#grpc/grpc-js';
import * as protoLoader from '#grpc/proto-loader';
import { ProtoGrpcType } from './proto/example';
import { ClientMessage } from './proto/example_package/ClientMessage';
import { ExampleHandlers } from './proto/example_package/Example';
import { ServerMessage } from './proto/example_package/ServerMessage';
const host = '0.0.0.0:9090';
const exampleServer: ExampleHandlers = {
unaryCall(
call: grpc.ServerUnaryCall<ClientMessage, ServerMessage>,
callback: grpc.sendUnaryData<ServerMessage>
) {
if (call.request) {
console.log(`(server) Got client message: ${call.request.clientMessage}`);
}
callback(null, {
serverMessage: 'Message from server',
});
},
serverStreamingCall(
call: grpc.ServerWritableStream<ClientMessage, ServerMessage>
) {
call.write({
serverMessage: 'Message from server',
});
},
clientStreamingCall(
call: grpc.ServerReadableStream<ClientMessage, ServerMessage>
) {
call.on('data', (clientMessage: ClientMessage) => {
console.log(
`(server) Got client message: ${clientMessage.clientMessage}`
);
});
},
bidirectionalStreamingCall(
call: grpc.ServerDuplexStream<ClientMessage, ServerMessage>
) {
call.write({
serverMessage: 'Message from server',
});
call.on('data', (clientMessage: ClientMessage) => {
console.log(
`(server) Got client message: ${clientMessage.clientMessage}`
);
});
},
};
function getServer(): grpc.Server {
const packageDefinition = protoLoader.loadSync('./proto/example.proto');
const proto = (grpc.loadPackageDefinition(
packageDefinition
) as unknown) as ProtoGrpcType;
const server = new grpc.Server();
server.addService(proto.example_package.Example.service, exampleServer);
return server;
}
if (require.main === module) {
const server = getServer();
server.bindAsync(
host,
grpc.ServerCredentials.createInsecure(),
(err: Error | null, port: number) => {
if (err) {
console.error(`Server error: ${err.message}`);
} else {
console.log(`Server bound on port: ${port}`);
server.start();
}
}
);
}
I've created various examples of how to use gRPC with TypeScript here: https://github.com/badsyntax/grpc-js-types

Resources