diff --git a/package.json b/package.json index b1ed0f8..fc35ea6 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,19 @@ { "name": "@loopback/grpc", - "version": "1.0.0-alpha.2", + "version": "1.0.7", "description": "A gRPC extencion for LoopBack Next", "main": "index.js", "engines": { "node": ">=8" }, "scripts": { - "build": "lb-tsc es2017", + "build": "lb-tsc es2017 --outDir dist", "build:apidocs": "lb-apidocs", - "build:watch": "lb-tsc es2017 --watch", + "build:watch": "lb-tsc es2017 --outDir dist --watch", "clean": "lb-clean loopback-grpc*.tgz dist package api-docs", "prepublishOnly": "npm run build && npm run build:apidocs", "pretest": "npm run lint:fix && npm run clean && npm run build", - "test": "lb-mocha \"DIST/test/unit/**/*.js\" \"DIST/test/acceptance/**/*.js\"", + "test": "lb-mocha \"dist/test/unit/**/*.js\" \"dist/test/acceptance/**/*.js\"", "lint": "npm run prettier:check && npm run tslint", "lint:fix": "npm run prettier:fix && npm run tslint:fix", "tslint": "lb-tslint --project tsconfig.json", @@ -47,19 +47,19 @@ "compilers" ], "dependencies": { - "@loopback/context": "^4.0.0-alpha.30", - "@loopback/core": "^4.0.0-alpha.32", - "@loopback/metadata": "^4.0.0-alpha.9", - "@loopback/repository": "^4.0.0-alpha.28", - "@loopback/rest": "^4.0.0-alpha.24", - "@mean-expert/protoc-ts": "0.0.2", + "@loopback/context": "^1.0.0", + "@loopback/core": "^1.0.0", + "@loopback/metadata": "^1.0.0", + "@loopback/repository": "^1.0.0", + "@loopback/rest": "^1.0.0", + "@xanthous/protoc-ts": "^0.1.0", "glob": "^7.1.2", "grpc": "^1.6.6", "protobufjs": "^6.8.0" }, "devDependencies": { - "@loopback/build": "^4.0.0-alpha.13", - "@loopback/testlab": "^4.0.0-alpha.23", + "@loopback/build": "^1.0.0", + "@loopback/testlab": "^1.0.0", "@types/glob": "^5.0.35" } } diff --git a/src/grpc.sequence.ts b/src/grpc.sequence.ts index 456e508..dee08f3 100644 --- a/src/grpc.sequence.ts +++ b/src/grpc.sequence.ts @@ -31,4 +31,20 @@ export class GrpcSequence implements GrpcSequenceInterface { // Do something after call return reply; } + + // tslint:disable-next-line:no-any + async clientStreamingCall(clientStream: grpc.ServerReadableStream): Promise { + const reply = await this.controller[this.method](clientStream); + return reply; + } + + // tslint:disable-next-line:no-any + processServerStream(stream: grpc.ServerWriteableStream): void { + this.controller[this.method](stream); + } + + // tslint:disable-next-line:no-any + processBidiStream(stream: grpc.ServerDuplexStream): void { + this.controller[this.method](stream); + } } diff --git a/src/grpc.server.ts b/src/grpc.server.ts index 789bbd6..7f5b26e 100644 --- a/src/grpc.server.ts +++ b/src/grpc.server.ts @@ -1,17 +1,17 @@ import { Application, CoreBindings, + BindingKey, Server, ControllerClass, } from '@loopback/core'; import {MetadataInspector} from '@loopback/metadata'; -import {Context, inject, Constructor} from '@loopback/context'; +import {Context, inject} from '@loopback/context'; import {GRPC_METHODS} from './decorators/grpc.decorator'; import {GrpcBindings} from './keys'; import {GrpcSequence} from './grpc.sequence'; import {Config} from './types'; import * as grpc from 'grpc'; -import {Service} from 'protobufjs'; import {GrpcGenerator} from './grpc.generator'; import {BindingScope} from '@loopback/context/dist/src/binding'; const debug = require('debug')('loopback:grpc:server'); @@ -35,6 +35,9 @@ export class GrpcServer extends Context implements Server { * GRPCBindings.CONFIG). * */ + + listening: boolean = true; + constructor( @inject(CoreBindings.APPLICATION_INSTANCE) protected app: Application, @inject(GrpcBindings.GRPC_SERVER) protected server: grpc.Server, @@ -97,9 +100,10 @@ export class GrpcServer extends Context implements Server { // tslint:disable-next-line:no-any const serviceMeta = pkgMeta[config.SERVICE_NAME] as any; // tslint:disable-next-line:no-any - const serviceDef: grpc.ServiceDefinition = serviceMeta.service; + const serviceDef: grpc.ServiceDefinition = + { [methodName]: serviceMeta.service[methodName] }; this.server.addService(serviceDef, { - [config.METHOD_NAME]: this.setupGrpcCall(ctor, methodName), + [config.METHOD_NAME]: this.setupGrpcCall(ctor, methodName, config), }); } } @@ -107,41 +111,81 @@ export class GrpcServer extends Context implements Server { * @method setupGrpcCall * @author Miroslav Bajtos * @author Jonathan Casarrubias + * @author Simon Liang * @license MIT - * @param prototype + * @param ctor * @param methodName */ private setupGrpcCall( ctor: ControllerClass, methodName: string, + config: Config.Method // tslint:disable-next-line:no-any - ): grpc.handleUnaryCall, any> { + ): grpc.handleCall { const context: Context = this; - return function( - // tslint:disable-next-line:no-any - call: grpc.ServerUnaryCall, - // tslint:disable-next-line:no-any - callback: (err: any, value?: T) => void, - ) { - handleUnary().then( - result => callback(null, result), - error => { - debugger; - callback(error); - }, - ); - async function handleUnary(): Promise { - context.bind(GrpcBindings.CONTEXT).to(context); - context - .bind(GrpcBindings.GRPC_CONTROLLER) - .toClass(ctor) - .inScope(BindingScope.CONTEXT); - context.bind(GrpcBindings.GRPC_METHOD_NAME).to(methodName); - const sequence: GrpcSequence = await context.get( - GrpcBindings.GRPC_SEQUENCE, - ); - return sequence.unaryCall(call); + + context.bind(GrpcBindings.CONTEXT).to(context); + context + .bind(GrpcBindings.GRPC_CONTROLLER) + .toClass(ctor) + .inScope(BindingScope.CONTEXT); + context.bind(GrpcBindings.GRPC_METHOD_NAME).to(methodName); + const bindingKey: BindingKey = BindingKey.create(GrpcBindings.GRPC_SEQUENCE); + + if (config.REQUEST_STREAM) { + if (config.RESPONSE_STREAM) { + // bidi stream + return function( + // tslint:disable-next-line:no-any + bidiStream: grpc.ServerDuplexStream + ) { + context.get(bindingKey).then((sequence: GrpcSequence) => sequence.processBidiStream(bidiStream)); + }; + } else { + // client streaming + return function( + // tslint:disable-next-line:no-any + clientStream: grpc.ServerReadableStream, + // tslint:disable-next-line:no-any + callback: grpc.sendUnaryData + ) { + handleClientStream().then( + result => callback(null, result), + error => callback(error, null), + ); + async function handleClientStream(): Promise { + const sequence: GrpcSequence = await context.get(bindingKey); + return sequence.clientStreamingCall(clientStream); + } + } } - }; + } else { + if (config.RESPONSE_STREAM) { + // server streaming + return function( + // tslint:disable-next-line:no-any + serverStream: grpc.ServerWriteableStream + ) { + context.get(bindingKey).then((sequence: GrpcSequence) => sequence.processServerStream(serverStream)); + } + } else { + // unary call + return function( + // tslint:disable-next-line:no-any + call: grpc.ServerUnaryCall, + // tslint:disable-next-line:no-any + callback: grpc.sendUnaryData, + ) { + handleUnary().then( + result => callback(null, result), + error => callback(error, null), + ); + async function handleUnary(): Promise { + const sequence: GrpcSequence = await context.get(bindingKey); + return sequence.unaryCall(call); + } + }; + } + } } }