Package: gaia.py
gaiaGaia, the very framework to makegRPCservices. Gaia defines a definitely intuitive way to write gRPC services.Handle Custom ErrorsgRPCdoes NOT provide an formal way to handle errors, even lack of documentation, whilegaiawill do it for you.Manage.protofilesgaiaallows us to share proto files between server and clients.gaiasharesgPRCprotobuf files by wrapping them into an npm package and publishing the npm tarball to npm registry.Eggjs compatible pluginsgaiasupports to useegg pluginsto extend your applications.Restful API service made easygaiaprovides a convenient way to define restful API routings upon the existing gRPC services.gaiasupports bothproto2andproto3.Install$npmigaiaTable of ContentsAPIsClientServerHow gaia makes.protofiles sharable and portable?Create the client ofhelloImport.protofiles fromhelloMore aboutincludeDirsHow to Write agaiaServerPackages and name resolutionthisobject of the controller methodsReusing other controllersUsing external servicesUsing pluginsSynopsisconst{Server,Client,resolvePackage}=require('gaia')constroot=path.join(__dirname,'example','hello')To make better understanding the usage ofgaia,the example below is based on the demo in theexample/hellodirectory.Start server:newServer(root).listen(50051)Run client:const{// service GreeterGreeter}=newClient(root).connect('localhost:50051')construn=async()=>{const{message}=awaitGreeter.sayHello({name:'world'})console.log(message)}run()// Hello worldAPIsnew Client(root)Creates the gaia client.rootpaththe root path to load the client fromclient.connect(host):Connects to the gRPC server and returns the service methodshoststringthe server host to connect to which includes the server hostname and port and whose pattern is<hostname>:<port>new Server(root, serverConfig?)rootpaththe root path to load the server fromserverConfig?ServerConfig={}server configurationsinterfaceServerConfig{// Defines where to load controllerscontroller_root?:string='controller'plugins?:Array<Plugin>services?:Services}interfacePackage{// The root path of the packagepath?:string// The package name of the packagepackage?:string// Either path or package should be defined.}interfacePluginextendsPackage{// Configurations for the pluginconfig:object}interfaceServiceextendsPackage{// the host param of `client.connect(host)`host:string}interfaceServices{[name:string]:Service}server.listen(port): thisportnumberthe port which gRPC server will listen to.Start the gaia server.server.kill()Forcibly shut down the gRPC serverawait server.close()Gracefully shut down the serverresolvePackage(id: string): stringidstringpackage idReturns the root path of the packagenewClient(resolvePackage('foo')).connect(host)Howgaiamakes.protofiles sharable and portable?gaiatakes full advantage of npm packages to share proto files.A minimungaiaservice portable, as well as servicehelloor packagehello, could be:/path/to/hello/
  |-- proto/
  |       |-- hello.proto
  |-- package.jsonAnd inproto/hello.proto:syntax="proto3";serviceGreeter{rpcSayHello(HelloRequest)returns(HelloReply){}}messageHelloRequest{stringname=1;}messageHelloReply{stringmessage=1;}package.json{"name":"hello","gaia":{...}}The theoptionalfield"gaia"of package.json follows the schema:interfaceFieldGaia{// Tells `gaia` which properties of error should be// - collected, serialized and transmitted to the clients.// - or deseriialized from server// `errorProps` defaults to `['code', 'message']`// if the server throws an `error`, by default, gaia will collect// - `error.code`,// - `error.message`// and send them to its clients, while other properties will be omitted.errorProps?:Array<string>=['code','message']// Specifies where to load proto files.// `protoPath` should be a relative path to `root`protoPath?:string='proto'// Proto filenames inside `protoPath`.// If not specified, gaia will search all `*.proto` files inside `protoPath`.protos?:Array<string>|string='*.proto'// See section #import-proto-files-from-hello belowprotoDependencies?:Array<string>=[]}Apparently, packagehellohas everything we need to create a client agent for servicehello.And packagehellois language-independent which only contains proto files and client configurations.Create the client ofhelloAssume that we have a new projectfoo, and wenpm install hello./path/to/foo/
  |-- proto/
  |        |-- foo.proto
  |-- node_modules/
  |              |-- hello/
  |-- package.jsonThen if thehelloservice is already running on port8000, we could create a hello client by following lines:const{Client}=require('gaia')const{Greeter}=newClient('/path/to/foo/node_modules/hello').connect('localhost:8000')Import.protofiles fromhelloSince projectfoo, as we introduced above, has a dependencyhello, we could import.protofiles from packagehello.in/path/to/foo/proto/foo.proto:syntax="proto3";// We could install a package and import things from it// as well as we do in JavaScript es modules. Oh yeah! 😆import"hello/proto/hello.proto"serviceFooGreeter{// We could reuse message types from package `hello`rpcSayHello(HelloRequest)returns(HelloReply){}}In order to do that, we need to declare thathellois agaiadependency offooby adding some fields in package.json:{"name":"foo","gaia":{// So that we could import .proto files from package `hello`"protoDependencies":[// We have to add "hello" here."hello"]},"dependencies":{// This is generated by `npm install`"hello":"^1.0.0"}}Andgaiawill manage the--proto_paths (includeDirs) for you, so that gRPC Protobuf Loader will know where to search and import.protofilesMore aboutincludeDirsgaiarecursively parses theprotoDependenciesof projectfoo, and itsprotoDependency'sprotoDependenciesto generate theoptions.includeDirsoption for@grpc/proto-loaderHow to Write agaiaServerTake the projecthellowhich introduced above for example.Since we define aGreeterservice inhello.proto, we must implement the corresponding controller by ourselves.Service controllers should be defined in directory/path/to/hello/controllerwhich can be changed with by configcontroller_root.We must provide aGreeter.jsin that directory./path/to/hello/
  |-- controller/
  |            |-- Greeter.jsinGreeter.js, there should be an async/sync method namedSayHelloinexportsbecause we defined aSayHellorpc method in serviceGreeterexports.sayHello=({name})=>({message:`Hello${name}`})Packages and name resolutionFirst the innermost package scope is searched, then the next-innermost, and so on, and at last the service name.Assume that we have the following protocol buffer.packagefoo.bar;serviceBaz{rpcQuux(Req)returns(Res){}}Then in directorycontroller_root, we need to create a JavaScript filefoo/bar/Baz.jswhoseexportshas aQuuxmethod.thisobject of the controller methodsThere are several properties could be access bythisobject of the controller methods.Reusing other controllersWe could access other controller methods bythis.controller[namespace0][namespace1]...[serviceName][methodName]For example, we could access theQuuxmethod byexports.OtherMethodsOfSomeService=asyncfunction(request){constdata=awaitthis.controller.foo.bar.Baz.Quux(request)// ...returnsomething}Using external servicesIf we provideserverConfig.servicesfor servernewServer('/path/to/service/foo',{...otherConfig,services:{hello:{// 'hello' is a gaia serverpackage:'hello'}}}).listen(port)Then, client of the service'hello'could be accessed from the service controller of foo by:exports.Quux=asyncfunction({name}){const{message}=awaitthis.service.hello.SayHello({name})return{property:message}}Using pluginsLicenseMIT