Repository: badsyntax/grpc-js-typescript Branch: master Commit: 9daa97bbfd09 Files: 83 Total size: 92.9 KB Directory structure: gitextract_7hw2d8wn/ ├── .github/ │ ├── CODEOWNERS │ ├── dependabot.yml │ └── workflows/ │ └── test.yml ├── README.md └── examples/ ├── gradle-ts-protoc-gen/ │ ├── .gitattributes │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .vscode/ │ │ ├── extensions.json │ │ └── settings.json │ ├── README.md │ ├── build.gradle │ ├── client.ts │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── package.json │ ├── proto/ │ │ ├── example.proto │ │ ├── example_grpc_pb.d.ts │ │ └── example_pb.d.ts │ ├── server.ts │ ├── settings.gradle │ └── tsconfig.json ├── grpc-proto-loader/ │ ├── .gitignore │ ├── .npmrc │ ├── .nvmrc │ ├── .prettierignore │ ├── .vscode/ │ │ ├── extensions.json │ │ └── settings.json │ ├── README.md │ ├── client.ts │ ├── package.json │ ├── proto/ │ │ ├── example.proto │ │ ├── example.ts │ │ └── example_package/ │ │ ├── ClientMessage.ts │ │ ├── Example.ts │ │ └── ServerMessage.ts │ ├── server.ts │ └── tsconfig.json ├── grpc-web/ │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .vscode/ │ │ ├── extensions.json │ │ └── settings.json │ ├── README.md │ ├── client.ts │ ├── compile-proto.sh │ ├── envoy.yaml │ ├── index.html │ ├── package.json │ ├── proto/ │ │ └── chat.proto │ ├── server.ts │ ├── tsconfig.json │ └── webpack.config.ts ├── grpc_tools_node_protoc_ts/ │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .vscode/ │ │ ├── extensions.json │ │ └── settings.json │ ├── README.md │ ├── client.ts │ ├── compile-proto.sh │ ├── package.json │ ├── proto/ │ │ ├── example.proto │ │ ├── example_grpc_pb.d.ts │ │ └── example_pb.d.ts │ ├── server.ts │ └── tsconfig.json └── ts-protoc-gen/ ├── .gitignore ├── .npmrc ├── .prettierignore ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── README.md ├── client.ts ├── compile-proto.sh ├── package.json ├── proto/ │ ├── example.proto │ ├── example_grpc_pb.d.ts │ └── example_pb.d.ts ├── server.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/CODEOWNERS ================================================ * @badsyntax ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: github-actions directory: "/" schedule: interval: daily open-pull-requests-limit: 10 ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: push: branches: [master] pull_request: branches: [master] jobs: test: runs-on: ubuntu-20.04 name: Build examples steps: - uses: actions/checkout@v4.2.2 - uses: actions/setup-node@v3.4.1 with: node-version: 16 - uses: actions/setup-java@v4.5.0 with: java-version: 15 architecture: x64 distribution: zulu - name: Build gradle-ts-protoc-gen run: | cd examples/gradle-ts-protoc-gen npm i npm run build - name: Build grpc_tools_node_protoc_ts run: | cd examples/grpc_tools_node_protoc_ts npm i npm run build - name: Build grpc-proto-loader run: | cd examples/grpc-proto-loader npm i npm run build - name: Build grpc-web run: | cd examples/grpc-web npm i npm run build - name: Build ts-protoc-gen run: | cd examples/ts-protoc-gen npm i npm run build ================================================ FILE: README.md ================================================ # gRPC TypeScript [![Test](https://github.com/badsyntax/grpc-js-typescript/actions/workflows/test.yml/badge.svg)](https://github.com/badsyntax/grpc-js-typescript/actions/workflows/test.yml) This repo shows how to use gRPC with TypeScript & Node.js. In all cases, [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) is used as [`grpc`](https://www.npmjs.com/package/grpc) is now [effectively deprecated](https://grpc.io/blog/grpc-js-1.0/). TypeScript types are generated at build time from the proto files using different tools as demonstrated in this repo. ## Examples Each example is a separate application. Refer to the README in each project for more information. - [grpc-proto-loader](./examples/grpc-proto-loader) - [ts-protoc-gen](./examples/ts-protoc-gen) - [gradle-ts-protoc-gen](./examples/gradle-ts-protoc-gen) - [grpc_tools_node_protoc_ts](./examples/grpc_tools_node_protoc_ts) - [grpc-web](./examples/grpc-web) ## Type Parity Thanks to [this issue](https://github.com/agreatfool/grpc_tools_node_protoc_ts/issues/79) being fixed, we now have type parity across the different type generator packages. 🎉 ## Contributions Contributions I made to help with gRPC & TypeScript support: - https://github.com/grpc/grpc-node/pull/1368 - https://github.com/grpc/grpc-node/pull/1380 - https://github.com/grpc/grpc-node/pull/1387 - https://github.com/grpc/grpc-node/pull/1419 - https://github.com/grpc/grpc-node/pull/1454 - https://github.com/grpc/grpc-node/pull/1587 - https://github.com/grpc/grpc-node/pull/1590 - https://github.com/murgatroid99/grpc-node/pull/1 - https://github.com/murgatroid99/grpc-node/pull/2 - https://github.com/murgatroid99/grpc-node/pull/3 - https://github.com/improbable-eng/ts-protoc-gen/pull/236 - https://github.com/improbable-eng/ts-protoc-gen/pull/247 ================================================ FILE: examples/gradle-ts-protoc-gen/.gitattributes ================================================ # # https://help.github.com/articles/dealing-with-line-endings/ # # These are explicitly windows files and should use crlf *.bat text eol=crlf ================================================ FILE: examples/gradle-ts-protoc-gen/.gitignore ================================================ node_modules **/*.js !proto/*.js # Ignore Gradle project-specific cache directory .gradle # Ignore Gradle build output directory build # Eclipse files .settings .classpath .project ================================================ FILE: examples/gradle-ts-protoc-gen/.npmrc ================================================ registry=https://registry.npmjs.org/ ================================================ FILE: examples/gradle-ts-protoc-gen/.prettierignore ================================================ package-lock.json proto/*.ts ================================================ FILE: examples/gradle-ts-protoc-gen/.vscode/extensions.json ================================================ { "recommendations": [ "dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", "esbenp.prettier-vscode", "streetsidesoftware.code-spell-checker", "zxh404.vscode-proto3" ] } ================================================ FILE: examples/gradle-ts-protoc-gen/.vscode/settings.json ================================================ { "typescript.tsc.autoDetect": "off", "typescript.updateImportsOnFileMove.enabled": "always", "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "eslint.validate": ["javascript", "typescript"], "editor.formatOnSave": false, "editor.codeActionsOnSave": { "source.fixAll.eslint": true, "source.organizeImports": false }, "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "cSpell.language": "en-GB", "cSpell.words": [ "grpc" ], "java.configuration.updateBuildConfiguration": "automatic" } ================================================ FILE: examples/gradle-ts-protoc-gen/README.md ================================================ # gradle-ts-protoc-gen example This examples shows how to uses [Gradle](https://gradle.org/), the [proto compiler](https://www.npmjs.com/package/grpc-tools), [`ts-protoc-gen`](https://www.npmjs.com/package/ts-protoc-gen) & [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) to build a fully typed gRPC application that runs on Node.js. The [proto compiler](https://www.npmjs.com/package/grpc-tools) and [`ts-protoc-gen`](https://www.npmjs.com/package/ts-protoc-gen) compiler plugin are used to generate JavaScript & TypeScript files from the proto definitions. The [`protobuf-gradle-plugin`](https://github.com/google/protobuf-gradle-plugin) Gradle plugin is used to run the compiler from Gradle. [`google-protobuf`](https://www.npmjs.com/package/google-protobuf) & [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) are used at runtime. ## App layout - [package.json](./package.json) - Dependencies and node build scripts - [build.gradle](./build.gradle) - The Gradle build file - [proto/](./proto/) - Protobuf definitions and generated types - [server.ts](./server.ts) - The grpc server - [client.ts](./client.ts) - The grpc client ## Generating the Types Install dependencies: ```sh npm install # Apple M1 users should install for x64 due to grpc-tools not supporting arm64 npm i --target_arch=x64 ``` Use Gradle & [`ts-protoc-gen`](https://www.npmjs.com/package/ts-protoc-gen) to generate the TypeScript files: ```sh ./gradlew generateProto ``` This is aliased as a npm script: ```sh npm run build:proto ``` ### Running the App This simple app demonstrates the different gRPC calls you can perform. First generated the types and build the application files: ```sh npm run build ``` Start the server: ```sh npm run start:server ``` Now run the client by specifying which example you want to run: ```bash npm run start:client -- --unary npm run start:client -- --server-streaming npm run start:client -- --client-streaming npm run start:client -- --bidi-streaming ``` ================================================ FILE: examples/gradle-ts-protoc-gen/build.gradle ================================================ plugins { id 'java' id 'com.google.protobuf' version '0.8.16' id 'com.github.jlouns.cpe' version '0.5.0' } project.ext.set('protobufVersion', '3.16.0') project.ext.set('protocVersion', project.protobufVersion) def isWindows = System.getProperty('os.name').toLowerCase().contains('windows') repositories { mavenCentral() } sourceSets { main { proto { srcDir file('./proto') } } } protobuf { protoc { if (osdetector.os == 'osx') { // Required for arm64 support on MacOS artifact = "com.google.protobuf:protoc:${protocVersion}:osx-x86_64" } else { artifact = "com.google.protobuf:protoc:${protocVersion}" } } plugins { grpc { path = file( './node_modules/.bin/grpc_tools_node_protoc_plugin' + (isWindows ? '.cmd' : '') ) } ts { path = file( './node_modules/.bin/protoc-gen-ts' + (isWindows ? '.cmd' : '') ) } } generateProtoTasks { generateProto.finalizedBy copyGeneratedProto all().each { task -> task.plugins { grpc { outputSubDir = 'js' option 'grpc_js' } ts { option 'service=grpc-node,mode=grpc-js' } } task.builtins { remove java js { option 'import_style=commonjs' } } task.dependsOn npmInstall } } } task copyGeneratedProto(type: Copy) { from "$protobuf.generatedFilesBaseDir/main/ts", "$protobuf.generatedFilesBaseDir/main/js" into 'proto' } task npmInstall(type: CrossPlatformExec) { description 'Installs node dependencies' inputs.file('package-lock.json').withPathSensitivity(PathSensitivity.RELATIVE) outputs.dir('node_modules') outputs.cacheIf { true } commandLine 'npm', 'install' } ================================================ FILE: examples/gradle-ts-protoc-gen/client.ts ================================================ import * as grpc from '@grpc/grpc-js'; import { ExampleClient } from './proto/example_grpc_pb'; import { ClientMessage, ServerMessage } from './proto/example_pb'; const host = '0.0.0.0:9090'; const client = new ExampleClient(host, grpc.credentials.createInsecure()); const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 5); client.waitForReady(deadline, (error?: Error) => { if (error) { console.log(`Client connect error: ${error.message}`); } else { onClientReady(); } }); function onClientReady() { switch (process.argv[process.argv.length - 1]) { case '--unary': doUnaryCall(); break; case '--server-streaming': doServerStreamingCall(); break; case '--client-streaming': doClientStreamingCall(); break; case '--bidi-streaming': doBidirectionalStreamingCall(); break; default: throw new Error('Example not specified'); } } function doUnaryCall() { const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); client.unaryCall( clientMessage, (error: grpc.ServiceError | null, serverMessage?: ServerMessage) => { if (error) { console.error(error.message); } else if (serverMessage) { console.log( `(client) Got server message: ${serverMessage.getServerMessage()}` ); } } ); } function doServerStreamingCall() { const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); const stream = client.serverStreamingCall(clientMessage); stream.on('data', (serverMessage: ServerMessage) => { console.log( `(client) Got server message: ${serverMessage.getServerMessage()}` ); }); } function doClientStreamingCall() { const stream = client.clientStreamingCall( (error: grpc.ServiceError | null) => { if (error) { console.error(error.message); } } ); const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); stream.write(clientMessage); } function doBidirectionalStreamingCall() { const stream = client.bidirectionalStreamingCall(); // Server stream stream.on('data', (serverMessage: ServerMessage) => { console.log( `(client) Got server message: ${serverMessage.getServerMessage()}` ); }); // Client stream const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); stream.write(clientMessage); } ================================================ FILE: examples/gradle-ts-protoc-gen/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: examples/gradle-ts-protoc-gen/gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: examples/gradle-ts-protoc-gen/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: examples/gradle-ts-protoc-gen/package.json ================================================ { "name": "gradle-ts-protoc-gen", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "npm run build:typescript", "build:typescript": "tsc -p .", "build:proto": "./gradlew generateProto", "watch": "npm run build:typescript -- -w", "prewatch": "npm run build:proto", "prebuild": "npm run build:proto", "lint": "npm run lint:prettier && npm run lint:eslint", "lint:prettier": "prettier --check \"**/*.{ts,js,json,svg,md,yml}\"", "lint:eslint": "eslint . --ext .js,.ts", "lint:fix": "npm run lint:fix:prettier && npm run lint:eslint -- --fix", "lint:fix:prettier": "prettier --write '**/*.{ts,tsx,js,json,svg,md,yml}'", "start:server": "node server", "start:client": "node client" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.6.8", "google-protobuf": "^3.21.0" }, "devDependencies": { "@tsconfig/node12": "^1.0.11", "@types/google-protobuf": "^3.15.6", "@types/node": "^15.0.3", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", "grpc-tools": "^1.11.2", "prettier": "^2.7.1", "ts-protoc-gen": "^0.15.0", "typescript": "^4.7.4" }, "eslintConfig": { "ignorePatterns": [ "**/*.js", "proto/*.ts" ], "env": { "browser": false, "es6": true, "node": true }, "parserOptions": { "project": "./tsconfig.json", "ecmaVersion": 2018, "sourceType": "module" }, "extends": [ "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended" ] }, "prettier": { "singleQuote": true, "tabWidth": 2, "printWidth": 80, "useTabs": false } } ================================================ FILE: examples/gradle-ts-protoc-gen/proto/example.proto ================================================ 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) {} } ================================================ FILE: examples/gradle-ts-protoc-gen/proto/example_grpc_pb.d.ts ================================================ // GENERATED CODE -- DO NOT EDIT! // package: example_package // file: example.proto import * as example_pb from "./example_pb"; import * as grpc from "@grpc/grpc-js"; interface IExampleService extends grpc.ServiceDefinition { unaryCall: grpc.MethodDefinition; serverStreamingCall: grpc.MethodDefinition; clientStreamingCall: grpc.MethodDefinition; bidirectionalStreamingCall: grpc.MethodDefinition; } export const ExampleService: IExampleService; export interface IExampleServer extends grpc.UntypedServiceImplementation { unaryCall: grpc.handleUnaryCall; serverStreamingCall: grpc.handleServerStreamingCall; clientStreamingCall: grpc.handleClientStreamingCall; bidirectionalStreamingCall: grpc.handleBidiStreamingCall; } export class ExampleClient extends grpc.Client { constructor(address: string, credentials: grpc.ChannelCredentials, options?: object); unaryCall(argument: example_pb.ClientMessage, callback: grpc.requestCallback): grpc.ClientUnaryCall; unaryCall(argument: example_pb.ClientMessage, metadataOrOptions: grpc.Metadata | grpc.CallOptions | null, callback: grpc.requestCallback): grpc.ClientUnaryCall; unaryCall(argument: example_pb.ClientMessage, metadata: grpc.Metadata | null, options: grpc.CallOptions | null, callback: grpc.requestCallback): grpc.ClientUnaryCall; serverStreamingCall(argument: example_pb.ClientMessage, metadataOrOptions?: grpc.Metadata | grpc.CallOptions | null): grpc.ClientReadableStream; serverStreamingCall(argument: example_pb.ClientMessage, metadata?: grpc.Metadata | null, options?: grpc.CallOptions | null): grpc.ClientReadableStream; clientStreamingCall(callback: grpc.requestCallback): grpc.ClientWritableStream; clientStreamingCall(metadataOrOptions: grpc.Metadata | grpc.CallOptions | null, callback: grpc.requestCallback): grpc.ClientWritableStream; clientStreamingCall(metadata: grpc.Metadata | null, options: grpc.CallOptions | null, callback: grpc.requestCallback): grpc.ClientWritableStream; bidirectionalStreamingCall(metadataOrOptions?: grpc.Metadata | grpc.CallOptions | null): grpc.ClientDuplexStream; bidirectionalStreamingCall(metadata?: grpc.Metadata | null, options?: grpc.CallOptions | null): grpc.ClientDuplexStream; } ================================================ FILE: examples/gradle-ts-protoc-gen/proto/example_pb.d.ts ================================================ // package: example_package // file: example.proto import * as jspb from "google-protobuf"; export class ServerMessage extends jspb.Message { getServerMessage(): string; setServerMessage(value: string): void; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ServerMessage.AsObject; static toObject(includeInstance: boolean, msg: ServerMessage): ServerMessage.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; static serializeBinaryToWriter(message: ServerMessage, writer: jspb.BinaryWriter): void; static deserializeBinary(bytes: Uint8Array): ServerMessage; static deserializeBinaryFromReader(message: ServerMessage, reader: jspb.BinaryReader): ServerMessage; } export namespace ServerMessage { export type AsObject = { serverMessage: string, } } export class ClientMessage extends jspb.Message { getClientMessage(): string; setClientMessage(value: string): void; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ClientMessage.AsObject; static toObject(includeInstance: boolean, msg: ClientMessage): ClientMessage.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; static serializeBinaryToWriter(message: ClientMessage, writer: jspb.BinaryWriter): void; static deserializeBinary(bytes: Uint8Array): ClientMessage; static deserializeBinaryFromReader(message: ClientMessage, reader: jspb.BinaryReader): ClientMessage; } export namespace ClientMessage { export type AsObject = { clientMessage: string, } } ================================================ FILE: examples/gradle-ts-protoc-gen/server.ts ================================================ import * as grpc from '@grpc/grpc-js'; import { ExampleService, IExampleServer } from './proto/example_grpc_pb'; import { ClientMessage, ServerMessage } from './proto/example_pb'; const host = '0.0.0.0:9090'; const exampleServer: IExampleServer = { unaryCall( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData ) { if (call.request) { console.log( `(server) Got client message: ${call.request.getClientMessage()}` ); } const serverMessage = new ServerMessage(); serverMessage.setServerMessage('Message from server'); callback(null, serverMessage); }, serverStreamingCall( call: grpc.ServerWritableStream ) { const serverMessage = new ServerMessage(); serverMessage.setServerMessage('Message from server'); call.write(serverMessage); }, clientStreamingCall( call: grpc.ServerReadableStream ) { call.on('data', (clientMessage: ClientMessage) => { console.log( `(server) Got client message: ${clientMessage.getClientMessage()}` ); }); }, bidirectionalStreamingCall( call: grpc.ServerDuplexStream ) { call.on('data', (clientMessage: ClientMessage) => { console.log( `(server) Got client message: ${clientMessage.getClientMessage()}` ); }); const serverMessage = new ServerMessage(); serverMessage.setServerMessage('Message from server'); call.write(serverMessage); }, }; function getServer(): grpc.Server { const server = new grpc.Server(); server.addService(ExampleService, 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(); } } ); } ================================================ FILE: examples/gradle-ts-protoc-gen/settings.gradle ================================================ /* * This file was generated by the Gradle 'init' task. * * The settings file is used to specify which projects to include in your build. * * Detailed information about configuring a multi-project build in Gradle can be found * in the user manual at https://docs.gradle.org/6.6.1/userguide/multi_project_builds.html */ rootProject.name = 'gradle-ts-protoc-gen' ================================================ FILE: examples/gradle-ts-protoc-gen/tsconfig.json ================================================ { "extends": "@tsconfig/node12/tsconfig.json", "exclude": ["node_modules"] } ================================================ FILE: examples/grpc-proto-loader/.gitignore ================================================ node_modules generated *.js ================================================ FILE: examples/grpc-proto-loader/.npmrc ================================================ registry=https://registry.npmjs.org/ ================================================ FILE: examples/grpc-proto-loader/.nvmrc ================================================ 14 ================================================ FILE: examples/grpc-proto-loader/.prettierignore ================================================ package-lock.json generated ================================================ FILE: examples/grpc-proto-loader/.vscode/extensions.json ================================================ { "recommendations": [ "dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", "esbenp.prettier-vscode", "streetsidesoftware.code-spell-checker", "zxh404.vscode-proto3" ] } ================================================ FILE: examples/grpc-proto-loader/.vscode/settings.json ================================================ { "typescript.tsc.autoDetect": "off", "typescript.updateImportsOnFileMove.enabled": "always", "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "eslint.validate": ["javascript", "typescript"], "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "cSpell.language": "en-GB", "cSpell.words": ["grpc", "unary", "bidi"] } ================================================ FILE: examples/grpc-proto-loader/README.md ================================================ # grpc-proto-loader example This example shows how to use [`@grpc/proto-loader`](https://www.npmjs.com/package/@grpc/proto-loader) & [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) to build a fully typed CLI application that runs on Node.js. [`@grpc/proto-loader`](https://www.npmjs.com/package/@grpc/proto-loader) to used to [generate the types](https://github.com/grpc/grpc-node/pull/1474), as well as load the protobuf files at runtime using [`protobuf.js`](https://www.npmjs.com/package/protobufjs) instead of [`google-protobuf`](https://www.npmjs.com/package/google-protobuf). _No proto compiler is used. It's all JavaScript._ ## App layout - [package.json](./package.json) - Dependencies and build scripts - [proto/](./proto/) - Protobuf definitions and generated types - [server.ts](./server.ts) - The grpc server - [client.ts](./client.ts) - The grpc client ## Generating the Types Install dependencies: ```sh npm install ``` Use [`@grpc/proto-loader`](https://www.npmjs.com/package/@grpc/proto-loader) to generate the TypeScript files: ```sh $(npm bin)/proto-loader-gen-types --longs=String --enums=String --defaults --oneofs --grpcLib=@grpc/grpc-js --outDir=proto/ proto/*.proto ``` This is aliased as a npm script: ```sh npm run build:proto ``` ### Running the App This simple app demonstrates the different gRPC calls you can perform. First generated the types and build the application files: ```sh npm run build ``` Start the server: ```sh npm run start:server ``` Now run the client by specifying which example you want to run: ```bash npm run start:client -- --unary npm run start:client -- --server-streaming npm run start:client -- --client-streaming npm run start:client -- --bidi-streaming ``` ================================================ FILE: examples/grpc-proto-loader/client.ts ================================================ import * as grpc from '@grpc/grpc-js'; import * as protoLoader from '@grpc/proto-loader'; import { ProtoGrpcType } from './proto/example'; import { ServerMessage } from './proto/example_package/ServerMessage'; const host = '0.0.0.0:9090'; const packageDefinition = protoLoader.loadSync('./proto/example.proto', { longs: String, enums: String, defaults: true, oneofs: true, }); const proto = grpc.loadPackageDefinition( packageDefinition ) as unknown as ProtoGrpcType; const client = new proto.example_package.Example( host, grpc.credentials.createInsecure() ); const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 5); client.waitForReady(deadline, (error?: Error) => { if (error) { console.log(`Client connect error: ${error.message}`); } else { onClientReady(); } }); function onClientReady() { switch (process.argv[process.argv.length - 1]) { case '--unary': doUnaryCall(); break; case '--server-streaming': doServerStreamingCall(); break; case '--client-streaming': doClientStreamingCall(); break; case '--bidi-streaming': doBidirectionalStreamingCall(); break; default: throw new Error('Example not specified'); } } function doUnaryCall() { client.unaryCall( { clientMessage: 'Message from client', }, (error?: grpc.ServiceError | null, serverMessage?: ServerMessage) => { if (error) { console.error(error.message); } else if (serverMessage) { console.log( `(client) Got server message: ${serverMessage.serverMessage}` ); } } ); } function doServerStreamingCall() { const stream = client.serverStreamingCall({ clientMessage: 'Message from client', }); stream.on('data', (serverMessage: ServerMessage) => { console.log(`(client) Got server message: ${serverMessage.serverMessage}`); }); } function doClientStreamingCall() { const stream = client.clientStreamingCall((error?: grpc.ServiceError | null) => { if (error) { console.error(error.message); } }); stream.write({ clientMessage: 'Message from client', }); } function doBidirectionalStreamingCall() { const stream = client.bidirectionalStreamingCall(); // Server stream stream.on('data', (serverMessage: ServerMessage) => { console.log(`(client) Got server message: ${serverMessage.serverMessage}`); }); // Client stream stream.write({ clientMessage: 'Message from client', }); } ================================================ FILE: examples/grpc-proto-loader/package.json ================================================ { "name": "grpc-proto-loader-example", "version": "1.0.0", "description": "", "keywords": [], "author": "", "license": "ISC", "bugs": { "url": "https://github.com/badsyntax/grpc-js-types/issues" }, "repository": { "type": "git", "url": "https://github.com/badsyntax/grpc-js-types" }, "scripts": { "build": "npm run build:typescript", "build:typescript": "tsc -p .", "watch": "npm run build:typescript -- -w", "build:proto": "proto-loader-gen-types --longs=String --enums=String --defaults --oneofs --grpcLib=@grpc/grpc-js --outDir=proto/ proto/*.proto", "prewatch": "npm run build:proto", "prebuild": "npm run build:proto", "lint": "npm run lint:prettier && npm run lint:eslint", "lint:prettier": "prettier --check \"**/*.{ts,js,json,svg,md,yml}\"", "lint:eslint": "eslint . --ext .js,.ts", "lint:fix": "npm run lint:fix:prettier && npm run lint:eslint -- --fix", "lint:fix:prettier": "prettier --write '**/*.{ts,tsx,js,json,svg,md,yml}'", "start:server": "node server", "start:client": "node client" }, "dependencies": { "@grpc/grpc-js": "^1.6.8", "@grpc/proto-loader": "^0.7.0" }, "devDependencies": { "@tsconfig/node12": "^1.0.11", "@types/node": "^15.0.3", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", "prettier": "^2.7.1", "typescript": "^4.7.4" }, "eslintConfig": { "ignorePatterns": [ "**/*.js", "proto/**/*.ts" ], "env": { "browser": false, "es6": true, "node": true }, "parserOptions": { "project": "./tsconfig.json", "ecmaVersion": 2018, "sourceType": "module" }, "extends": [ "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended" ] }, "prettier": { "singleQuote": true, "tabWidth": 2, "printWidth": 80, "useTabs": false } } ================================================ FILE: examples/grpc-proto-loader/proto/example.proto ================================================ 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) {} } ================================================ FILE: examples/grpc-proto-loader/proto/example.ts ================================================ import type * as grpc from '@grpc/grpc-js'; import type { MessageTypeDefinition } from '@grpc/proto-loader'; import type { ExampleClient as _example_package_ExampleClient, ExampleDefinition as _example_package_ExampleDefinition } from './example_package/Example'; type SubtypeConstructor any, Subtype> = { new(...args: ConstructorParameters): Subtype; }; export interface ProtoGrpcType { example_package: { ClientMessage: MessageTypeDefinition Example: SubtypeConstructor & { service: _example_package_ExampleDefinition } ServerMessage: MessageTypeDefinition } } ================================================ FILE: examples/grpc-proto-loader/proto/example_package/ClientMessage.ts ================================================ // Original file: proto/example.proto export interface ClientMessage { 'clientMessage'?: (string); } export interface ClientMessage__Output { 'clientMessage': (string); } ================================================ FILE: examples/grpc-proto-loader/proto/example_package/Example.ts ================================================ // Original file: proto/example.proto import type * as grpc from '@grpc/grpc-js' import type { MethodDefinition } from '@grpc/proto-loader' import type { ClientMessage as _example_package_ClientMessage, ClientMessage__Output as _example_package_ClientMessage__Output } from '../example_package/ClientMessage'; import type { ServerMessage as _example_package_ServerMessage, ServerMessage__Output as _example_package_ServerMessage__Output } from '../example_package/ServerMessage'; export interface ExampleClient extends grpc.Client { bidirectionalStreamingCall(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_example_package_ClientMessage, _example_package_ServerMessage__Output>; bidirectionalStreamingCall(options?: grpc.CallOptions): grpc.ClientDuplexStream<_example_package_ClientMessage, _example_package_ServerMessage__Output>; bidirectionalStreamingCall(metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientDuplexStream<_example_package_ClientMessage, _example_package_ServerMessage__Output>; bidirectionalStreamingCall(options?: grpc.CallOptions): grpc.ClientDuplexStream<_example_package_ClientMessage, _example_package_ServerMessage__Output>; clientStreamingCall(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientWritableStream<_example_package_ClientMessage>; clientStreamingCall(metadata: grpc.Metadata, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientWritableStream<_example_package_ClientMessage>; clientStreamingCall(options: grpc.CallOptions, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientWritableStream<_example_package_ClientMessage>; clientStreamingCall(callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientWritableStream<_example_package_ClientMessage>; clientStreamingCall(metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientWritableStream<_example_package_ClientMessage>; clientStreamingCall(metadata: grpc.Metadata, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientWritableStream<_example_package_ClientMessage>; clientStreamingCall(options: grpc.CallOptions, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientWritableStream<_example_package_ClientMessage>; clientStreamingCall(callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientWritableStream<_example_package_ClientMessage>; serverStreamingCall(argument: _example_package_ClientMessage, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_example_package_ServerMessage__Output>; serverStreamingCall(argument: _example_package_ClientMessage, options?: grpc.CallOptions): grpc.ClientReadableStream<_example_package_ServerMessage__Output>; serverStreamingCall(argument: _example_package_ClientMessage, metadata: grpc.Metadata, options?: grpc.CallOptions): grpc.ClientReadableStream<_example_package_ServerMessage__Output>; serverStreamingCall(argument: _example_package_ClientMessage, options?: grpc.CallOptions): grpc.ClientReadableStream<_example_package_ServerMessage__Output>; unaryCall(argument: _example_package_ClientMessage, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientUnaryCall; unaryCall(argument: _example_package_ClientMessage, metadata: grpc.Metadata, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientUnaryCall; unaryCall(argument: _example_package_ClientMessage, options: grpc.CallOptions, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientUnaryCall; unaryCall(argument: _example_package_ClientMessage, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientUnaryCall; unaryCall(argument: _example_package_ClientMessage, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientUnaryCall; unaryCall(argument: _example_package_ClientMessage, metadata: grpc.Metadata, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientUnaryCall; unaryCall(argument: _example_package_ClientMessage, options: grpc.CallOptions, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientUnaryCall; unaryCall(argument: _example_package_ClientMessage, callback: grpc.requestCallback<_example_package_ServerMessage__Output>): grpc.ClientUnaryCall; } export interface ExampleHandlers extends grpc.UntypedServiceImplementation { bidirectionalStreamingCall: grpc.handleBidiStreamingCall<_example_package_ClientMessage__Output, _example_package_ServerMessage>; clientStreamingCall: grpc.handleClientStreamingCall<_example_package_ClientMessage__Output, _example_package_ServerMessage>; serverStreamingCall: grpc.handleServerStreamingCall<_example_package_ClientMessage__Output, _example_package_ServerMessage>; unaryCall: grpc.handleUnaryCall<_example_package_ClientMessage__Output, _example_package_ServerMessage>; } export interface ExampleDefinition extends grpc.ServiceDefinition { bidirectionalStreamingCall: MethodDefinition<_example_package_ClientMessage, _example_package_ServerMessage, _example_package_ClientMessage__Output, _example_package_ServerMessage__Output> clientStreamingCall: MethodDefinition<_example_package_ClientMessage, _example_package_ServerMessage, _example_package_ClientMessage__Output, _example_package_ServerMessage__Output> serverStreamingCall: MethodDefinition<_example_package_ClientMessage, _example_package_ServerMessage, _example_package_ClientMessage__Output, _example_package_ServerMessage__Output> unaryCall: MethodDefinition<_example_package_ClientMessage, _example_package_ServerMessage, _example_package_ClientMessage__Output, _example_package_ServerMessage__Output> } ================================================ FILE: examples/grpc-proto-loader/proto/example_package/ServerMessage.ts ================================================ // Original file: proto/example.proto export interface ServerMessage { 'serverMessage'?: (string); } export interface ServerMessage__Output { 'serverMessage': (string); } ================================================ FILE: examples/grpc-proto-loader/server.ts ================================================ 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, callback: grpc.sendUnaryData ) { if (call.request) { console.log(`(server) Got client message: ${call.request.clientMessage}`); } callback(null, { serverMessage: 'Message from server', }); }, serverStreamingCall( call: grpc.ServerWritableStream ) { call.write({ serverMessage: 'Message from server', }); }, clientStreamingCall( call: grpc.ServerReadableStream ) { call.on('data', (clientMessage: ClientMessage) => { console.log( `(server) Got client message: ${clientMessage.clientMessage}` ); }); }, bidirectionalStreamingCall( call: grpc.ServerDuplexStream ) { 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', { longs: String, enums: String, defaults: true, oneofs: true, }); 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(); } } ); } ================================================ FILE: examples/grpc-proto-loader/tsconfig.json ================================================ { "extends": "@tsconfig/node12/tsconfig.json", "exclude": ["node_modules"] } ================================================ FILE: examples/grpc-web/.gitignore ================================================ node_modules *.js proto/*.ts dist/ ================================================ FILE: examples/grpc-web/.npmrc ================================================ registry=https://registry.npmjs.org/ ================================================ FILE: examples/grpc-web/.prettierignore ================================================ package-lock.json dist/ ================================================ FILE: examples/grpc-web/.vscode/extensions.json ================================================ { "recommendations": [ "dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", "esbenp.prettier-vscode", "streetsidesoftware.code-spell-checker", "zxh404.vscode-proto3" ] } ================================================ FILE: examples/grpc-web/.vscode/settings.json ================================================ { "typescript.tsc.autoDetect": "off", "typescript.updateImportsOnFileMove.enabled": "always", "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "eslint.validate": ["javascript", "typescript"], "editor.formatOnSave": false, "editor.codeActionsOnSave": { "source.fixAll.eslint": true, "source.organizeImports": false }, "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "cSpell.language": "en-GB", "cSpell.words": ["grpc"] } ================================================ FILE: examples/grpc-web/README.md ================================================ # grpc-web This examples shows how to use the [proto compiler](https://www.npmjs.com/package/grpc-tools), [`ts-protoc-gen`](https://www.npmjs.com/package/ts-protoc-gen), [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js), [`protoc-gen-grpc-web`](https://www.npmjs.com/package/protoc-gen-grpc-web) & [`grpc-web`](https://www.npmjs.com/package/grpc-web) to build a fully typed gRPC CLI chat application that runs on Node.js and your browser. The [`ts-protoc-gen`](https://www.npmjs.com/package/ts-protoc-gen) compiler plugin is used to generate TypeScript files and the [`protoc-gen-grpc-web`](https://www.npmjs.com/package/protoc-gen-grpc-web) compiler plugin is used to generate the grpc-web files from the proto definitions. The generated code uses [`google-protobuf`](https://www.npmjs.com/package/google-protobuf) & [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) at runtime. ## App layout - [package.json](./package.json) - Dependencies and node build scripts - [compile-proto.sh](./compile-proto.sh) - The proto compiler script - [proto/](./proto/) - Protobuf definitions and generated types - [server.ts](./server.ts) - The grpc server - [client.ts](./client.ts) - The grpc client ## Generating the Types ```sh npm install # Apple M1 users should install for x64 due to grpc-tools not supporting arm64 npm install --target_arch=x64 ./compile-proto.sh ``` ### Running the app (Note, docker is required to run the envoy proxy.) Run the demo: ```bash npm install npm run build npm run start:proxy npm run start:server python3 -m http.server 8081 ``` Open http://localhost:8081/ in multiple browser windows and chat to yourself. Development workflow: ```bash npm run watch:typescript npm run watch:webpack npm run start:proxy npm run start:server python3 -m http.server 8081 ``` ## Credits The chat application idea is inspired from https://techblog.fexcofts.com/2018/07/20/grpc-nodejs-chat-example/ ================================================ FILE: examples/grpc-web/client.ts ================================================ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { RpcError as GrpcWebError } from 'grpc-web'; import { ChatClient } from './proto/chat_grpc_web_pb'; import { ClientMessage, ServerMessage } from './proto/chat_pb'; let user: string; window.addEventListener('DOMContentLoaded', () => { document.getElementById('submit-name')?.addEventListener( 'submit', async (e) => { e.preventDefault(); user = (document.getElementById('name') as HTMLFormElement)?.value; if (user) { document.getElementById('submit-message')!.style.display = 'block'; document.getElementById('submit-name')!.style.display = 'none'; initChat(user); } }, false ); }); async function initChat(user: string) { const client = new ChatClient('http://' + window.location.hostname + ':8080'); const clientMessage = new ClientMessage(); clientMessage.setUser(user); const stream = client.join(clientMessage); stream.on('data', (message: ServerMessage) => { const messageElement = document.createElement('div'); messageElement.innerText = `${message.getUser()}: ${message.getText()}`; document.getElementById('messages')!.appendChild(messageElement); }); function sendMessage(message: string) { const clientMessage = new ClientMessage(); clientMessage.setUser(user); clientMessage.setText(message); client.send( clientMessage, undefined, (err: GrpcWebError, response: ServerMessage) => { if (err) { console.error(err); } else { console.log('response', response.toObject()); (document.getElementById('message') as HTMLFormElement)!.value = ''; } } ); } document.getElementById('submit-message')?.addEventListener( 'submit', (e) => { e.preventDefault(); const message = (document.getElementById('message') as HTMLFormElement) ?.value; sendMessage(message); }, false ); } ================================================ FILE: examples/grpc-web/compile-proto.sh ================================================ #!/usr/bin/env bash OUT_DIR="." TS_OUT_DIR="." IN_DIR="./proto" PROTOC="$(npm bin)/grpc_tools_node_protoc" PROTOC_GEN_TS_PATH="$(npm bin)/protoc-gen-ts" PROTOC_GEN_GRPC_PATH="$(npm bin)/grpc_tools_node_protoc_plugin" PROTOC_GEN_GRPC_WEB_PATH="$(npm bin)/protoc-gen-grpc-web" # generate grpc-web files & types $PROTOC \ -I="./" \ --plugin=protoc-gen-grpc-web=${PROTOC_GEN_GRPC_WEB_PATH} \ --js_out=import_style=commonjs:$OUT_DIR \ --grpc-web_out=import_style=commonjs+dts,mode=grpcwebtext:$OUT_DIR \ "$IN_DIR"/*.proto # generate grpc-js files & types $PROTOC \ -I="./" \ --plugin=protoc-gen-ts=$PROTOC_GEN_TS_PATH \ --plugin=protoc-gen-grpc=${PROTOC_GEN_GRPC_PATH} \ --js_out=import_style=commonjs:$OUT_DIR \ --ts_out=service=grpc-node,mode=grpc-js:$TS_OUT_DIR \ --grpc_out=grpc_js:$OUT_DIR \ "$IN_DIR"/*.proto ================================================ FILE: examples/grpc-web/envoy.yaml ================================================ static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 8080 } filter_chains: - filters: - name: envoy.http_connection_manager config: codec_type: auto stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: { prefix: "/" } route: cluster: greeter_service max_grpc_timeout: 0s cors: allow_origin_string_match: - prefix: "*" allow_methods: GET, PUT, DELETE, POST, OPTIONS allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout max_age: "1728000" expose_headers: custom-header-1,grpc-status,grpc-message http_filters: - name: envoy.grpc_web - name: envoy.cors - name: envoy.router clusters: - name: greeter_service connect_timeout: 0.25s type: logical_dns http2_protocol_options: {} lb_policy: round_robin hosts: [{ socket_address: { address: host.docker.internal, port_value: 9090 }}] ================================================ FILE: examples/grpc-web/index.html ================================================ gRPC-Web Example

Enter your name to join the chat

Chat
================================================ FILE: examples/grpc-web/package.json ================================================ { "name": "grpc-web-example", "version": "1.0.0", "description": "", "private": true, "scripts": { "build": "npm run build:typescript && npm run build:webpack", "build:typescript": "tsc -p .", "build:proto": "./compile-proto.sh", "build:webpack": "webpack ./client.js -c webpack.config.js", "watch:typescript": "npm run build:typescript -- -w", "watch:webpack": "npm run build:webpack -- -w", "prebuild": "npm run build:proto", "lint": "npm run lint:prettier && npm run lint:eslint", "lint:prettier": "prettier --check \"**/*.{ts,js,json,svg,md,yml}\"", "lint:eslint": "eslint . --ext .js,.ts", "lint:fix": "npm run lint:fix:prettier && npm run lint:eslint -- --fix", "lint:fix:prettier": "prettier --write '**/*.{ts,tsx,js,json,svg,md,yml}'", "start:server": "node server", "start:proxy": "docker run -d -p 8080:8080 -v \"$(pwd)\"/envoy.yaml:/etc/envoy/envoy.yaml:ro envoyproxy/envoy:v1.15.0" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@grpc/grpc-js": "^1.6.8", "@grpc/proto-loader": "^0.7.0", "@tsconfig/node12": "^1.0.11", "@types/google-protobuf": "^3.15.6", "@types/node": "^14.14.14", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", "google-protobuf": "^3.21.0", "grpc-tools": "^1.11.2", "grpc-web": "^1.3.1", "prettier": "^2.7.1", "prettier-plugin-organize-imports": "^3.0.0", "protoc-gen-grpc-web": "^1.4.0", "ts-protoc-gen": "^0.15.0", "typescript": "^4.7.4", "webpack": "^5.74.0", "webpack-cli": "^4.10.0" }, "eslintConfig": { "ignorePatterns": [ "**/*.js", "proto/*.ts", "generated" ], "env": { "browser": false, "es6": true, "node": true }, "parserOptions": { "project": "./tsconfig.json", "ecmaVersion": 2018, "sourceType": "module" }, "extends": [ "plugin:@typescript-eslint/recommended", "prettier/@typescript-eslint", "plugin:prettier/recommended", "plugin:import/errors", "plugin:import/warnings", "plugin:import/typescript" ] }, "prettier": { "singleQuote": true, "tabWidth": 2, "printWidth": 80, "useTabs": false } } ================================================ FILE: examples/grpc-web/proto/chat.proto ================================================ syntax = "proto3"; package chat_package; message ServerMessage { string user = 1; string text = 2; } message ClientMessage { string user = 1; string text = 2; } service Chat { rpc join(ClientMessage) returns (stream ServerMessage) {} rpc send(ClientMessage) returns (ServerMessage) {} } ================================================ FILE: examples/grpc-web/server.ts ================================================ import * as grpc from '@grpc/grpc-js'; import { ChatService, IChatServer } from './proto/chat_grpc_pb'; import { ClientMessage, ServerMessage } from './proto/chat_pb'; const users: grpc.ServerWritableStream[] = []; const messages: ClientMessage[] = []; function notifyChat(message: ServerMessage) { messages.push(message); users.forEach((user) => { user.write(message); }); } const chatServer: IChatServer = { join(call: grpc.ServerWritableStream): void { users.push(call); const serverMessage = new ServerMessage(); serverMessage.setUser('Server'); serverMessage.setText(`${call.request?.getUser()} joined`); notifyChat(serverMessage); }, send( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData ): void { if (call.request) { const serverMessage = new ServerMessage(); serverMessage.setUser(call.request.getUser()); serverMessage.setText(call.request.getText()); notifyChat(serverMessage); callback(null, serverMessage); } }, }; export function getServer(): grpc.Server { const server = new grpc.Server(); server.addService(ChatService, chatServer); return server; } if (require.main === module) { const server = getServer(); server.bindAsync( '0.0.0.0:9090', 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(); } } ); } ================================================ FILE: examples/grpc-web/tsconfig.json ================================================ { "extends": "@tsconfig/node12/tsconfig.json", "compilerOptions": { "lib": ["DOM"] }, "exclude": ["node_modules"] } ================================================ FILE: examples/grpc-web/webpack.config.ts ================================================ import * as path from 'path'; export default { mode: 'production', entry: { index: './client.js', }, output: { filename: '[name].js', chunkFilename: '[name].js', path: path.resolve(__dirname, 'dist'), }, }; ================================================ FILE: examples/grpc_tools_node_protoc_ts/.gitignore ================================================ node_modules **/*.js !proto/*.js ================================================ FILE: examples/grpc_tools_node_protoc_ts/.npmrc ================================================ registry=https://registry.npmjs.org/ ================================================ FILE: examples/grpc_tools_node_protoc_ts/.prettierignore ================================================ package-lock.json proto/*.ts ================================================ FILE: examples/grpc_tools_node_protoc_ts/.vscode/extensions.json ================================================ { "recommendations": [ "dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", "esbenp.prettier-vscode", "streetsidesoftware.code-spell-checker", "zxh404.vscode-proto3" ] } ================================================ FILE: examples/grpc_tools_node_protoc_ts/.vscode/settings.json ================================================ { "typescript.tsc.autoDetect": "off", "typescript.updateImportsOnFileMove.enabled": "always", "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "eslint.validate": ["javascript", "typescript"], "editor.formatOnSave": false, "editor.codeActionsOnSave": { "source.fixAll.eslint": true, "source.organizeImports": false }, "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "cSpell.language": "en-GB", "cSpell.words": [ "Unary", "grpc" ] } ================================================ FILE: examples/grpc_tools_node_protoc_ts/README.md ================================================ # grpc_tools_node_protoc_ts example This example shows how to use the [proto compiler](https://www.npmjs.com/package/grpc-tools), [`grpc_tools_node_protoc_ts`](https://www.npmjs.com/package/grpc_tools_node_protoc_ts) & [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) to build a fully typed gRPC application that runs on Node.js. The [proto compiler](https://www.npmjs.com/package/grpc-tools) and [`grpc_tools_node_protoc_ts`](https://www.npmjs.com/package/grpc_tools_node_protoc_ts) compiler plugin are used to generate JavaScript & TypeScript files from the proto definitions and [`google-protobuf`](https://www.npmjs.com/package/google-protobuf) & [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) is used at runtime. ## App layout - [package.json](./package.json) - Dependencies and node build scripts - [compile-proto.sh](./compile-proto.sh) - The proto compiler script - [server.ts](./server.ts) - The grpc server - [client.ts](./client.ts) - The grpc client ## Generating the Types Install dependencies: ```sh npm install # Apple M1 users should install for x64 due to grpc-tools not supporting arm64 npm i --target_arch=x64 ``` Use [`grpc_tools_node_protoc_ts`](https://www.npmjs.com/package/grpc_tools_node_protoc_ts) to generate the TypeScript files: ```sh ./compile-proto.sh ``` This is aliased as a npm script: ```sh npm run build:proto ``` ### Running the App This simple app demonstrates the different gRPC calls you can perform. First generated the types and build the application files: ```sh npm run build ``` Start the server: ```sh npm run start:server ``` Now run the client by specifying which example you want to run: ```bash npm run start:client -- --unary npm run start:client -- --server-streaming npm run start:client -- --client-streaming npm run start:client -- --bidi-streaming ``` ================================================ FILE: examples/grpc_tools_node_protoc_ts/client.ts ================================================ import * as grpc from '@grpc/grpc-js'; import { ExampleClient } from './proto/example_grpc_pb'; import { ClientMessage, ServerMessage } from './proto/example_pb'; const host = '0.0.0.0:9090'; const client = new ExampleClient(host, grpc.credentials.createInsecure()); const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 5); client.waitForReady(deadline, (error?: Error) => { if (error) { console.log(`Client connect error: ${error.message}`); } else { onClientReady(); } }); function onClientReady() { switch (process.argv[process.argv.length - 1]) { case '--unary': doUnaryCall(); break; case '--server-streaming': doServerStreamingCall(); break; case '--client-streaming': doClientStreamingCall(); break; case '--bidi-streaming': doBidirectionalStreamingCall(); break; default: throw new Error('Example not specified'); } } function doUnaryCall() { const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); client.unaryCall( clientMessage, (error: grpc.ServiceError | null, serverMessage?: ServerMessage) => { if (error) { console.error(error.message); } else if (serverMessage) { console.log( `(client) Got server message: ${serverMessage.getServerMessage()}` ); } } ); } function doServerStreamingCall() { const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); const stream = client.serverStreamingCall(clientMessage); stream.on('data', (serverMessage: ServerMessage) => { console.log( `(client) Got server message: ${serverMessage.getServerMessage()}` ); }); } function doClientStreamingCall() { const stream = client.clientStreamingCall( (error: grpc.ServiceError | null) => { if (error) { console.error(error.message); } } ); const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); stream.write(clientMessage); } function doBidirectionalStreamingCall() { const stream = client.bidirectionalStreamingCall(); // Server stream stream.on('data', (serverMessage: ServerMessage) => { console.log( `(client) Got server message: ${serverMessage.getServerMessage()}` ); }); // Client stream const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); stream.write(clientMessage); } ================================================ FILE: examples/grpc_tools_node_protoc_ts/compile-proto.sh ================================================ #!/usr/bin/env bash OUT_DIR="." TS_OUT_DIR="." IN_DIR="./proto" PROTOC="$(npm bin)/grpc_tools_node_protoc" PROTOC_GEN_TS_PATH="$(npm bin)/protoc-gen-ts" PROTOC_GEN_GRPC_PATH="$(npm bin)/grpc_tools_node_protoc_plugin" $PROTOC \ -I="./" \ --plugin=protoc-gen-ts=$PROTOC_GEN_TS_PATH \ --plugin=protoc-gen-grpc=${PROTOC_GEN_GRPC_PATH} \ --js_out=import_style=commonjs:$OUT_DIR \ --grpc_out=grpc_js:$OUT_DIR \ --ts_out=grpc_js:$TS_OUT_DIR \ "$IN_DIR"/*.proto ================================================ FILE: examples/grpc_tools_node_protoc_ts/package.json ================================================ { "name": "grpc-js-example", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "npm run build:typescript", "build:typescript": "tsc -p .", "build:proto": "./compile-proto.sh", "watch": "npm run build:typescript -- -w", "prewatch": "npm run build:proto", "prebuild": "npm run build:proto", "lint": "npm run lint:prettier && npm run lint:eslint", "lint:prettier": "prettier --check \"**/*.{ts,js,json,svg,md,yml}\"", "lint:eslint": "eslint . --ext .js,.ts", "lint:fix": "npm run lint:fix:prettier && npm run lint:eslint -- --fix", "lint:fix:prettier": "prettier --write '**/*.{ts,tsx,js,json,svg,md,yml}'", "start:server": "node server", "start:client": "node client" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.6.8", "google-protobuf": "^3.21.0" }, "devDependencies": { "@tsconfig/node12": "^1.0.11", "@types/google-protobuf": "^3.15.6", "@types/node": "^15.0.3", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", "grpc_tools_node_protoc_ts": "^5.3.2", "grpc-tools": "^1.11.2", "prettier": "^2.7.1", "typescript": "^4.7.4" }, "eslintConfig": { "ignorePatterns": [ "**/*.js", "proto/*.ts" ], "env": { "browser": false, "es6": true, "node": true }, "parserOptions": { "project": "./tsconfig.json", "ecmaVersion": 2018, "sourceType": "module" }, "extends": [ "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended" ] }, "prettier": { "singleQuote": true, "tabWidth": 2, "printWidth": 80, "useTabs": false } } ================================================ FILE: examples/grpc_tools_node_protoc_ts/proto/example.proto ================================================ 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) {} } ================================================ FILE: examples/grpc_tools_node_protoc_ts/proto/example_grpc_pb.d.ts ================================================ // package: example_package // file: proto/example.proto /* tslint:disable */ /* eslint-disable */ import * as grpc from "@grpc/grpc-js"; import * as proto_example_pb from "../proto/example_pb"; interface IExampleService extends grpc.ServiceDefinition { unaryCall: IExampleService_IunaryCall; serverStreamingCall: IExampleService_IserverStreamingCall; clientStreamingCall: IExampleService_IclientStreamingCall; bidirectionalStreamingCall: IExampleService_IbidirectionalStreamingCall; } interface IExampleService_IunaryCall extends grpc.MethodDefinition { path: "/example_package.Example/unaryCall"; requestStream: false; responseStream: false; requestSerialize: grpc.serialize; requestDeserialize: grpc.deserialize; responseSerialize: grpc.serialize; responseDeserialize: grpc.deserialize; } interface IExampleService_IserverStreamingCall extends grpc.MethodDefinition { path: "/example_package.Example/serverStreamingCall"; requestStream: false; responseStream: true; requestSerialize: grpc.serialize; requestDeserialize: grpc.deserialize; responseSerialize: grpc.serialize; responseDeserialize: grpc.deserialize; } interface IExampleService_IclientStreamingCall extends grpc.MethodDefinition { path: "/example_package.Example/clientStreamingCall"; requestStream: true; responseStream: false; requestSerialize: grpc.serialize; requestDeserialize: grpc.deserialize; responseSerialize: grpc.serialize; responseDeserialize: grpc.deserialize; } interface IExampleService_IbidirectionalStreamingCall extends grpc.MethodDefinition { path: "/example_package.Example/bidirectionalStreamingCall"; requestStream: true; responseStream: true; requestSerialize: grpc.serialize; requestDeserialize: grpc.deserialize; responseSerialize: grpc.serialize; responseDeserialize: grpc.deserialize; } export const ExampleService: IExampleService; export interface IExampleServer extends grpc.UntypedServiceImplementation { unaryCall: grpc.handleUnaryCall; serverStreamingCall: grpc.handleServerStreamingCall; clientStreamingCall: grpc.handleClientStreamingCall; bidirectionalStreamingCall: grpc.handleBidiStreamingCall; } export interface IExampleClient { unaryCall(request: proto_example_pb.ClientMessage, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientUnaryCall; unaryCall(request: proto_example_pb.ClientMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientUnaryCall; unaryCall(request: proto_example_pb.ClientMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientUnaryCall; serverStreamingCall(request: proto_example_pb.ClientMessage, options?: Partial): grpc.ClientReadableStream; serverStreamingCall(request: proto_example_pb.ClientMessage, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; clientStreamingCall(callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientWritableStream; clientStreamingCall(metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientWritableStream; clientStreamingCall(options: Partial, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientWritableStream; clientStreamingCall(metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientWritableStream; bidirectionalStreamingCall(): grpc.ClientDuplexStream; bidirectionalStreamingCall(options: Partial): grpc.ClientDuplexStream; bidirectionalStreamingCall(metadata: grpc.Metadata, options?: Partial): grpc.ClientDuplexStream; } export class ExampleClient extends grpc.Client implements IExampleClient { constructor(address: string, credentials: grpc.ChannelCredentials, options?: Partial); public unaryCall(request: proto_example_pb.ClientMessage, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientUnaryCall; public unaryCall(request: proto_example_pb.ClientMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientUnaryCall; public unaryCall(request: proto_example_pb.ClientMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientUnaryCall; public serverStreamingCall(request: proto_example_pb.ClientMessage, options?: Partial): grpc.ClientReadableStream; public serverStreamingCall(request: proto_example_pb.ClientMessage, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; public clientStreamingCall(callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientWritableStream; public clientStreamingCall(metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientWritableStream; public clientStreamingCall(options: Partial, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientWritableStream; public clientStreamingCall(metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: proto_example_pb.ServerMessage) => void): grpc.ClientWritableStream; public bidirectionalStreamingCall(options?: Partial): grpc.ClientDuplexStream; public bidirectionalStreamingCall(metadata?: grpc.Metadata, options?: Partial): grpc.ClientDuplexStream; } ================================================ FILE: examples/grpc_tools_node_protoc_ts/proto/example_pb.d.ts ================================================ // package: example_package // file: proto/example.proto /* tslint:disable */ /* eslint-disable */ import * as jspb from "google-protobuf"; export class ServerMessage extends jspb.Message { getServerMessage(): string; setServerMessage(value: string): ServerMessage; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ServerMessage.AsObject; static toObject(includeInstance: boolean, msg: ServerMessage): ServerMessage.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; static serializeBinaryToWriter(message: ServerMessage, writer: jspb.BinaryWriter): void; static deserializeBinary(bytes: Uint8Array): ServerMessage; static deserializeBinaryFromReader(message: ServerMessage, reader: jspb.BinaryReader): ServerMessage; } export namespace ServerMessage { export type AsObject = { serverMessage: string, } } export class ClientMessage extends jspb.Message { getClientMessage(): string; setClientMessage(value: string): ClientMessage; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ClientMessage.AsObject; static toObject(includeInstance: boolean, msg: ClientMessage): ClientMessage.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; static serializeBinaryToWriter(message: ClientMessage, writer: jspb.BinaryWriter): void; static deserializeBinary(bytes: Uint8Array): ClientMessage; static deserializeBinaryFromReader(message: ClientMessage, reader: jspb.BinaryReader): ClientMessage; } export namespace ClientMessage { export type AsObject = { clientMessage: string, } } ================================================ FILE: examples/grpc_tools_node_protoc_ts/server.ts ================================================ import * as grpc from '@grpc/grpc-js'; import { ExampleService, IExampleServer } from './proto/example_grpc_pb'; import { ClientMessage, ServerMessage } from './proto/example_pb'; const host = '0.0.0.0:9090'; const exampleServer: IExampleServer = { unaryCall( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData ) { if (call.request) { console.log( `(server) Got client message: ${call.request.getClientMessage()}` ); } const serverMessage = new ServerMessage(); serverMessage.setServerMessage('Message from server'); callback(null, serverMessage); }, serverStreamingCall( call: grpc.ServerWritableStream ) { const serverMessage = new ServerMessage(); serverMessage.setServerMessage('Message from server'); call.write(serverMessage); }, clientStreamingCall( call: grpc.ServerReadableStream ) { call.on('data', (clientMessage: ClientMessage) => { console.log( `(server) Got client message: ${clientMessage.getClientMessage()}` ); }); }, bidirectionalStreamingCall( call: grpc.ServerDuplexStream ) { call.on('data', (clientMessage: ClientMessage) => { console.log( `(server) Got client message: ${clientMessage.getClientMessage()}` ); }); const serverMessage = new ServerMessage(); serverMessage.setServerMessage('Message from server'); call.write(serverMessage); }, }; function getServer(): grpc.Server { const server = new grpc.Server(); server.addService(ExampleService, 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(); } } ); } ================================================ FILE: examples/grpc_tools_node_protoc_ts/tsconfig.json ================================================ { "extends": "@tsconfig/node12/tsconfig.json", "exclude": ["node_modules"] } ================================================ FILE: examples/ts-protoc-gen/.gitignore ================================================ node_modules *.js !proto/*.js ================================================ FILE: examples/ts-protoc-gen/.npmrc ================================================ registry=https://registry.npmjs.org/ ================================================ FILE: examples/ts-protoc-gen/.prettierignore ================================================ package-lock.json proto/*.ts ================================================ FILE: examples/ts-protoc-gen/.vscode/extensions.json ================================================ { "recommendations": [ "dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", "esbenp.prettier-vscode", "streetsidesoftware.code-spell-checker", "zxh404.vscode-proto3" ] } ================================================ FILE: examples/ts-protoc-gen/.vscode/settings.json ================================================ { "typescript.tsc.autoDetect": "off", "typescript.updateImportsOnFileMove.enabled": "always", "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "eslint.validate": ["javascript", "typescript"], "editor.formatOnSave": false, "editor.codeActionsOnSave": { "source.fixAll.eslint": true, "source.organizeImports": false }, "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "cSpell.language": "en-GB", "cSpell.words": ["grpc"] } ================================================ FILE: examples/ts-protoc-gen/README.md ================================================ # ts-protoc-gen This examples shows how to use the [proto compiler](https://www.npmjs.com/package/grpc-tools), [`ts-protoc-gen`](https://www.npmjs.com/package/ts-protoc-gen) & [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) to build a fully typed gRPC CLI application that runs on Node.js. The [proto compiler](https://www.npmjs.com/package/grpc-tools) and [`ts-protoc-gen`](https://www.npmjs.com/package/ts-protoc-gen) compiler plugin are used to generate JavaScript & TypeScript files from the proto definitions. [`google-protobuf`](https://www.npmjs.com/package/google-protobuf) & [`@grpc/grpc-js`](https://www.npmjs.com/package/@grpc/grpc-js) are used at runtime. ## App layout - [package.json](./package.json) - Dependencies and node build scripts - [compile-proto.sh](./compile-proto.sh) - The proto compiler script - [proto/](./proto/) - Protobuf definitions and generated types - [server.ts](./server.ts) - The grpc server - [client.ts](./client.ts) - The grpc client ## Generating the Types Install dependencies: ```sh npm install # Apple M1 users should install for x64 due to grpc-tools not supporting arm64 npm install --target_arch=x64 ``` Use [proto compiler](https://www.npmjs.com/package/grpc-tools) and [`ts-protoc-gen`](https://www.npmjs.com/package/ts-protoc-gen) to generate the TypeScript files: ```sh ./compile-proto.sh ``` This is aliased as a npm script: ```sh npm run build:proto ``` ### Running the App This simple app demonstrates the different gRPC calls you can perform. First generate the types and build the application files: ```sh npm run build ``` Start the server: ```sh npm run start:server ``` Now run the client by specifying which example you want to run: ```bash npm run start:client -- --unary npm run start:client -- --server-streaming npm run start:client -- --client-streaming npm run start:client -- --bidi-streaming ``` ================================================ FILE: examples/ts-protoc-gen/client.ts ================================================ import * as grpc from '@grpc/grpc-js'; import { ExampleClient } from './proto/example_grpc_pb'; import { ClientMessage, ServerMessage } from './proto/example_pb'; const host = '0.0.0.0:9090'; const client = new ExampleClient(host, grpc.credentials.createInsecure()); const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 5); client.waitForReady(deadline, (error?: Error) => { if (error) { console.log(`Client connect error: ${error.message}`); } else { onClientReady(); } }); function onClientReady() { switch (process.argv[process.argv.length - 1]) { case '--unary': doUnaryCall(); break; case '--server-streaming': doServerStreamingCall(); break; case '--client-streaming': doClientStreamingCall(); break; case '--bidi-streaming': doBidirectionalStreamingCall(); break; default: throw new Error('Example not specified'); } } function doUnaryCall() { const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); client.unaryCall( clientMessage, (error: grpc.ServiceError | null, serverMessage?: ServerMessage) => { if (error) { console.error(error.message); } else if (serverMessage) { console.log( `(client) Got server message: ${serverMessage.getServerMessage()}` ); } } ); } function doServerStreamingCall() { const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); const stream = client.serverStreamingCall(clientMessage); stream.on('data', (serverMessage: ServerMessage) => { console.log( `(client) Got server message: ${serverMessage.getServerMessage()}` ); }); } function doClientStreamingCall() { const stream = client.clientStreamingCall( (error: grpc.ServiceError | null) => { if (error) { console.error(error.message); } } ); const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); stream.write(clientMessage); } function doBidirectionalStreamingCall() { const stream = client.bidirectionalStreamingCall(); // Server stream stream.on('data', (serverMessage: ServerMessage) => { console.log( `(client) Got server message: ${serverMessage.getServerMessage()}` ); }); // Client stream const clientMessage = new ClientMessage(); clientMessage.setClientMessage('Message from client'); stream.write(clientMessage); } ================================================ FILE: examples/ts-protoc-gen/compile-proto.sh ================================================ #!/usr/bin/env bash OUT_DIR="." TS_OUT_DIR="." IN_DIR="./proto" PROTOC="$(npm bin)/grpc_tools_node_protoc" PROTOC_GEN_TS_PATH="$(npm bin)/protoc-gen-ts" PROTOC_GEN_GRPC_PATH="$(npm bin)/grpc_tools_node_protoc_plugin" $PROTOC \ -I="./" \ --plugin=protoc-gen-ts=$PROTOC_GEN_TS_PATH \ --plugin=protoc-gen-grpc=$PROTOC_GEN_GRPC_PATH \ --js_out=import_style=commonjs:$OUT_DIR \ --grpc_out=grpc_js:$OUT_DIR \ --ts_out=service=grpc-node,mode=grpc-js:$TS_OUT_DIR \ "$IN_DIR"/*.proto ================================================ FILE: examples/ts-protoc-gen/package.json ================================================ { "name": "grpc-js-example", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "npm run build:typescript", "build:typescript": "tsc -p .", "build:proto": "./compile-proto.sh", "watch": "npm run build:typescript -- -w", "prewatch": "npm run build:proto", "prebuild": "npm run build:proto", "lint": "npm run lint:prettier && npm run lint:eslint", "lint:prettier": "prettier --check \"**/*.{ts,js,json,svg,md,yml}\"", "lint:eslint": "eslint . --ext .js,.ts", "lint:fix": "npm run lint:fix:prettier && npm run lint:eslint -- --fix", "lint:fix:prettier": "prettier --write '**/*.{ts,tsx,js,json,svg,md,yml}'", "start:server": "node server", "start:client": "node client" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.6.8", "google-protobuf": "^3.21.0" }, "devDependencies": { "@tsconfig/node12": "^1.0.11", "@types/google-protobuf": "^3.15.6", "@types/node": "^14.14.14", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", "grpc-tools": "^1.11.2", "prettier": "^2.7.1", "ts-protoc-gen": "^0.15.0", "typescript": "^4.7.4" }, "eslintConfig": { "ignorePatterns": [ "**/*.js", "proto/*.ts" ], "env": { "browser": false, "es6": true, "node": true }, "parserOptions": { "project": "./tsconfig.json", "ecmaVersion": 2018, "sourceType": "module" }, "extends": [ "plugin:@typescript-eslint/recommended", "prettier/@typescript-eslint", "plugin:prettier/recommended" ] }, "prettier": { "singleQuote": true, "tabWidth": 2, "printWidth": 80, "useTabs": false } } ================================================ FILE: examples/ts-protoc-gen/proto/example.proto ================================================ 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) {} } ================================================ FILE: examples/ts-protoc-gen/proto/example_grpc_pb.d.ts ================================================ // GENERATED CODE -- DO NOT EDIT! // package: example_package // file: proto/example.proto import * as proto_example_pb from "../proto/example_pb"; import * as grpc from "@grpc/grpc-js"; interface IExampleService extends grpc.ServiceDefinition { unaryCall: grpc.MethodDefinition; serverStreamingCall: grpc.MethodDefinition; clientStreamingCall: grpc.MethodDefinition; bidirectionalStreamingCall: grpc.MethodDefinition; } export const ExampleService: IExampleService; export interface IExampleServer extends grpc.UntypedServiceImplementation { unaryCall: grpc.handleUnaryCall; serverStreamingCall: grpc.handleServerStreamingCall; clientStreamingCall: grpc.handleClientStreamingCall; bidirectionalStreamingCall: grpc.handleBidiStreamingCall; } export class ExampleClient extends grpc.Client { constructor(address: string, credentials: grpc.ChannelCredentials, options?: object); unaryCall(argument: proto_example_pb.ClientMessage, callback: grpc.requestCallback): grpc.ClientUnaryCall; unaryCall(argument: proto_example_pb.ClientMessage, metadataOrOptions: grpc.Metadata | grpc.CallOptions | null, callback: grpc.requestCallback): grpc.ClientUnaryCall; unaryCall(argument: proto_example_pb.ClientMessage, metadata: grpc.Metadata | null, options: grpc.CallOptions | null, callback: grpc.requestCallback): grpc.ClientUnaryCall; serverStreamingCall(argument: proto_example_pb.ClientMessage, metadataOrOptions?: grpc.Metadata | grpc.CallOptions | null): grpc.ClientReadableStream; serverStreamingCall(argument: proto_example_pb.ClientMessage, metadata?: grpc.Metadata | null, options?: grpc.CallOptions | null): grpc.ClientReadableStream; clientStreamingCall(callback: grpc.requestCallback): grpc.ClientWritableStream; clientStreamingCall(metadataOrOptions: grpc.Metadata | grpc.CallOptions | null, callback: grpc.requestCallback): grpc.ClientWritableStream; clientStreamingCall(metadata: grpc.Metadata | null, options: grpc.CallOptions | null, callback: grpc.requestCallback): grpc.ClientWritableStream; bidirectionalStreamingCall(metadataOrOptions?: grpc.Metadata | grpc.CallOptions | null): grpc.ClientDuplexStream; bidirectionalStreamingCall(metadata?: grpc.Metadata | null, options?: grpc.CallOptions | null): grpc.ClientDuplexStream; } ================================================ FILE: examples/ts-protoc-gen/proto/example_pb.d.ts ================================================ // package: example_package // file: proto/example.proto import * as jspb from "google-protobuf"; export class ServerMessage extends jspb.Message { getServerMessage(): string; setServerMessage(value: string): void; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ServerMessage.AsObject; static toObject(includeInstance: boolean, msg: ServerMessage): ServerMessage.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; static serializeBinaryToWriter(message: ServerMessage, writer: jspb.BinaryWriter): void; static deserializeBinary(bytes: Uint8Array): ServerMessage; static deserializeBinaryFromReader(message: ServerMessage, reader: jspb.BinaryReader): ServerMessage; } export namespace ServerMessage { export type AsObject = { serverMessage: string, } } export class ClientMessage extends jspb.Message { getClientMessage(): string; setClientMessage(value: string): void; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ClientMessage.AsObject; static toObject(includeInstance: boolean, msg: ClientMessage): ClientMessage.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; static serializeBinaryToWriter(message: ClientMessage, writer: jspb.BinaryWriter): void; static deserializeBinary(bytes: Uint8Array): ClientMessage; static deserializeBinaryFromReader(message: ClientMessage, reader: jspb.BinaryReader): ClientMessage; } export namespace ClientMessage { export type AsObject = { clientMessage: string, } } ================================================ FILE: examples/ts-protoc-gen/server.ts ================================================ import * as grpc from '@grpc/grpc-js'; import { ExampleService, IExampleServer } from './proto/example_grpc_pb'; import { ClientMessage, ServerMessage } from './proto/example_pb'; const host = '0.0.0.0:9090'; const exampleServer: IExampleServer = { unaryCall( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData ) { if (call.request) { console.log( `(server) Got client message: ${call.request.getClientMessage()}` ); } const serverMessage = new ServerMessage(); serverMessage.setServerMessage('Message from server'); callback(null, serverMessage); }, serverStreamingCall( call: grpc.ServerWritableStream ) { const serverMessage = new ServerMessage(); serverMessage.setServerMessage('Message from server'); call.write(serverMessage); }, clientStreamingCall( call: grpc.ServerReadableStream ) { call.on('data', (clientMessage: ClientMessage) => { console.log( `(server) Got client message: ${clientMessage.getClientMessage()}` ); }); }, bidirectionalStreamingCall( call: grpc.ServerDuplexStream ) { call.on('data', (clientMessage: ClientMessage) => { console.log( `(server) Got client message: ${clientMessage.getClientMessage()}` ); }); const serverMessage = new ServerMessage(); serverMessage.setServerMessage('Message from server'); call.write(serverMessage); }, }; function getServer(): grpc.Server { const server = new grpc.Server(); server.addService(ExampleService, 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(); } } ); } ================================================ FILE: examples/ts-protoc-gen/tsconfig.json ================================================ { "extends": "@tsconfig/node12/tsconfig.json", "exclude": ["node_modules"] }