diff --git a/README.md b/README.md index c0da71b..6baea93 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,66 @@ -# loopback4-extension-grpc +# gRPC Extension for LoopBack 4 [![Join the chat at https://gitter.im/strongloop/loopback4-extension-grpc](https://badges.gitter.im/strongloop/loopback4-extension-grpc.svg)](https://gitter.im/strongloop/loopback4-extension-grpc?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -gRPC Extension for LoopBack 4 - -## Summary -A gRPC extension for LoopBack Next. - ## Overview -TODO. +The `@loopback/grpc` component enables LoopBack 4 as a Grpc Server. Also it provides with a @grpc decorator to define your RPC Method implementations from your Application Controllers. ## Installation -Get started by either downloading this project or cloning it as follows: +Install the `@loopback/grpc` component in your LoopBack 4 Application. ```sh -$ git clone https://github.com/strongloop/loopback4-extension-grpc.git -$ cd loopback4-extension-grpc && npm install +$ npm install --save @loopback/grpc ``` -## Basic Usage -TODO - -### Components -TODO. - -### Controllers -TODO. +## Component Configuration +```js +import {Application} from '@loopback/core'; +import {GrpcComponent, GrpcConfig} from '@loopback/grpc'; +import {MyGreeter} from './MyGreeter'; -### Decorators -TODO. +const config: GrpcConfig = { + proto: './file.proto', + package: 'myawesomepackage' +} -### Mixins -TODO. +const app = new Application({ + components: [GrpcComponent], + grpc: config +}); -### Providers -TODO. +app.controller(MyGreeter}); -### Repositories -TODO. +await app.start(); +``` -### Project Layout +## Grpc Decorator +The `@loopback/grpc` component provides you with a handy decorator to implement GRPC Methods. + +```js +import {grpc} from '@loopback/grpc'; +// You create the following types according your own proto.file +import {Greeter, HelloRequest, HelloReply} from './types'; +/** + * @class MyGreeter + * @description Implements grpc proto service + **/ +export class MyGreeter implements Greeter { + // Tell LoopBack that this is a Service RPC implementation + @grpc() + SayHello(request: HelloRequest): HelloReply { + const reply: HelloReply = {message: 'Hello ' + request.name}; + return reply; + } +} +``` -#### `src/` -TODO. +## Contribute +Get started by either downloading this project or cloning it as follows: -#### `test/` -TODO. +```sh +$ git clone https://github.com/strongloop/loopback4-extension-grpc.git +$ cd loopback4-extension-grpc && npm install +``` ## Contributions - [Guidelines](https://github.com/strongloop/loopback-next/wiki/Contributing#guidelines) @@ -58,4 +73,4 @@ run `npm test` from the root folder. See [all contributors](https://github.com/strongloop/loopback4-extension-grpc/graphs/contributors). ## License -MIT +MIT \ No newline at end of file diff --git a/src/grpc.sequence.ts b/src/grpc.sequence.ts index 8bacac7..127fdba 100644 --- a/src/grpc.sequence.ts +++ b/src/grpc.sequence.ts @@ -1,6 +1,6 @@ import {inject} from '@loopback/context'; import {GrpcBindings} from './keys'; -import {UnaryCall, UnaryReply} from './types'; +import * as grpc from 'grpc'; /** * @interface GrpcSequenceInterface * @author Jonathan Casarrubias @@ -8,7 +8,7 @@ import {UnaryCall, UnaryReply} from './types'; * @description Interface that describes a GRPC Sequence */ export interface GrpcSequenceInterface { - unaryCall(request: UnaryCall): Promise; + unaryCall(request: grpc.ServerUnaryCall): Promise; } /** * @class GrpcSequence @@ -22,7 +22,7 @@ export class GrpcSequence implements GrpcSequenceInterface { @inject(GrpcBindings.GRPC_METHOD) protected method, ) {} - async unaryCall(call: UnaryCall): Promise { + async unaryCall(call: grpc.ServerUnaryCall): Promise { // Do something before call const reply = await this.method(call.request); // Do something after call diff --git a/src/grpc.server.ts b/src/grpc.server.ts index 908fa5e..a78aa7c 100644 --- a/src/grpc.server.ts +++ b/src/grpc.server.ts @@ -1,9 +1,8 @@ import {Application, CoreBindings, Server} from '@loopback/core'; import {Context, inject, Reflector} from '@loopback/context'; import {GrpcBindings} from './keys'; -import {GrpcServerInstance, UnaryReply, UnaryCall} from './types'; import {GrpcSequence} from './grpc.sequence'; -import * as grpcModule from 'grpc'; +import * as grpc from 'grpc'; const debug = require('debug')('loopback:grpc:server'); /** * @class GrpcServer @@ -27,7 +26,7 @@ export class GrpcServer extends Context implements Server { */ constructor( @inject(CoreBindings.APPLICATION_INSTANCE) protected app: Application, - @inject(GrpcBindings.GRPC_SERVER) protected server: GrpcServerInstance, + @inject(GrpcBindings.GRPC_SERVER) protected server: grpc.Server, @inject(GrpcBindings.HOST) protected host: string, @inject(GrpcBindings.PORT) protected port: string, @inject(GrpcBindings.PROTO_PROVIDER) protected protoProvider: any, @@ -49,7 +48,7 @@ export class GrpcServer extends Context implements Server { return new Promise((resolve, reject) => { this.server.bind( `${this.host}:${this.port}`, - grpcModule.ServerCredentials.createInsecure(), + grpc.ServerCredentials.createInsecure(), ); this.server.start(); resolve(); @@ -68,7 +67,7 @@ export class GrpcServer extends Context implements Server { if (!proto) { throw new Error(`Grpc Server: No proto file was provided.`); } - const handlers: {[key: string]: Function} = {}; + const handlers: {[key: string]: grpc.handleUnaryCall} = {}; const className = prototype.constructor.name || ''; // If this class is defined within the proto file // then we search for rpc methods and register handlers. @@ -101,9 +100,12 @@ export class GrpcServer extends Context implements Server { * @param prototype * @param methodName */ - private setupGrpcCall(prototype, methodName: string): Function { + private setupGrpcCall(prototype, methodName: string): grpc.handleUnaryCall { const context: Context = this; - return function(call: UnaryCall, callback: (err, value?) => void) { + return function( + call: grpc.ServerUnaryCall, + callback: (err, value?) => void, + ) { handleUnary().then( result => callback(null, result), error => { @@ -111,7 +113,7 @@ export class GrpcServer extends Context implements Server { callback(error); }, ); - async function handleUnary(): Promise { + async function handleUnary(): Promise { context.bind(GrpcBindings.CONTEXT).to(context); context.bind(GrpcBindings.GRPC_METHOD).to(prototype[methodName]); const sequence: GrpcSequence = await context.get( diff --git a/src/providers/server.provider.ts b/src/providers/server.provider.ts index 0ed1542..893291a 100644 --- a/src/providers/server.provider.ts +++ b/src/providers/server.provider.ts @@ -3,8 +3,6 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT //import {Provider} from '@loopback/context'; -import {GrpcServerInstance} from '../types'; -// Require gRPC Module import * as grpc from 'grpc'; /** * @class ServerProvider @@ -13,8 +11,8 @@ import * as grpc from 'grpc'; * @description This provider will return the GRPC Server */ export class ServerProvider { - private server: GrpcServerInstance = new grpc.Server(); - public value(): GrpcServerInstance { + private server: grpc.Server = new grpc.Server(); + public value(): grpc.Server { return this.server; } } diff --git a/src/types.ts b/src/types.ts index 8541ca8..cc37b75 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,23 +4,6 @@ // License text available at https://opensource.org/licenses/MIT // Types and interfaces exposed by the extension go here -export interface GrpcServerInstance { - addService: (service: string, handlers: {[key: string]: Function}) => void; - addProtoService: ( - service: string, - handlers: {[key: string]: Function}, - ) => void; - bind: (host: string, secure: boolean) => void; - start: (callback?: () => void) => void; - tryShutdown: (callback?: () => void) => void; - forceShutdown: (callback?: () => void) => void; - register: () => void; -} -export interface UnaryCall { - request: UnaryCall; -} -export interface UnaryRequest {} -export interface UnaryReply {} export interface GrpcConfig { host?: string; port?: number; diff --git a/test/acceptance/grpc.component.unit.ts b/test/acceptance/grpc.component.unit.ts index 433d2ad..1d14c12 100644 --- a/test/acceptance/grpc.component.unit.ts +++ b/test/acceptance/grpc.component.unit.ts @@ -5,7 +5,7 @@ import {expect} from '@loopback/testlab'; import {inject} from '@loopback/context'; import {Application} from '@loopback/core'; -import {GrpcSequenceInterface, GrpcConfig, UnaryCall, UnaryReply} from '../../'; +import {GrpcSequenceInterface, GrpcConfig} from '../../'; import {grpc} from '../../src/decorators/grpc.decorator'; import * as grpcModule from 'grpc'; import {GrpcComponent, GrpcBindings} from '../..'; @@ -66,7 +66,7 @@ describe('GrpcComponent', () => { @inject(GrpcBindings.CONTEXT) protected context, @inject(GrpcBindings.GRPC_METHOD) protected method, ) {} - async unaryCall(call: UnaryCall): Promise { + async unaryCall(call: grpcModule.ServerUnaryCall): Promise { // Do something before call const reply = await this.method(call.request); reply.message += ' Sequenced'; diff --git a/test/unit/providers/server.provider.unit.ts b/test/unit/providers/server.provider.unit.ts index 5f74c0c..bafb633 100644 --- a/test/unit/providers/server.provider.unit.ts +++ b/test/unit/providers/server.provider.unit.ts @@ -4,11 +4,12 @@ // License text available at https://opensource.org/licenses/MIT import {expect} from '@loopback/testlab'; -import {ServerProvider, GrpcServerInstance} from '../../..'; +import {ServerProvider} from '../../..'; +import * as grpc from 'grpc'; describe('ServerProvider', () => { it('returns a grpc singleton server', () => { - const server: GrpcServerInstance = new ServerProvider().value(); + const server: grpc.Server = new ServerProvider().value(); expect(server).to.be.an.Object(); expect(server.bind).to.be.a.Function(); expect(server.start).to.be.a.Function();