{"_id":"@ayonli/grpc-async","_rev":"2-66c00c046b199c54a3fba0fe856117ad","name":"@ayonli/grpc-async","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@ayonli/grpc-async","version":"0.1.0","keywords":["gRPC","async"],"author":{"name":"A-yon Lee","email":"the@ayon.li"},"license":"MIT","_id":"@ayonli/grpc-async@0.1.0","maintainers":[{"name":"ayonli","email":"the@ayon.li"}],"homepage":"https://github.com/ayonli/grpc-async#readme","bugs":{"url":"https://github.com/ayonli/grpc-async/issues"},"dist":{"shasum":"fb504bcbd6e78c83094ca6e7e7dba74a3c8f2d09","tarball":"https://registry.npmjs.org/@ayonli/grpc-async/-/grpc-async-0.1.0.tgz","fileCount":12,"integrity":"sha512-yQuOSleKTzFixdN3zfPkrKYqHOgM7UXO+/xLDbOBxUi3bCr1UdlLzdtdtuZJA9KRcmNAsgjZPRzBThfZNb2bjQ==","signatures":[{"sig":"MEUCIDNn4s3agIz/Mii6fsGGqd5G5L43w+8CYtJFmkXZ7mzCAiEArVo9txmlCIRFXAcyc3Geyfbrmb3+nh6PJfDwlLdb54M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":70267},"main":"index.js","types":"index.d.ts","engines":{"node":">=14"},"gitHead":"b553f56686bf6b0740b133f933c9b22bce874502","scripts":{"test":"mocha -r ts-node/register test/index.ts","start":"node server.js","protoc:go":"protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative examples/Greeter.proto","postpublish":"ls *.js | xargs rm -f && ls *.js.map | xargs rm -f && ls *.d.ts | xargs rm -f","prepublishOnly":"tsc"},"_npmUser":{"name":"ayonli","email":"the@ayon.li"},"repository":{"url":"git+https://github.com/ayonli/grpc-async.git","type":"git"},"_npmVersion":"9.5.0","description":"A gRPC wrapper for Node.js with async functions","directories":{},"_nodeVersion":"18.14.2","dependencies":{"tslib":"^2.6.1","js-magic":"^1.3.0"},"_hasShrinkwrap":false,"devDependencies":{"dotry":"^1.4.0","mocha":"^10.2.0","ts-node":"^10.9.1","typescript":"^5.1.6","@types/node":"^20.4.5","@types/mocha":"^10.0.1","@grpc/grpc-js":"^1.8.21","@grpc/proto-loader":"^0.7.8"},"peerDependencies":{"@grpc/grpc-js":"^1.8.21","@grpc/proto-loader":"^0.7.8"},"_npmOperationalInternal":{"tmp":"tmp/grpc-async_0.1.0_1693078704352_0.20165910161027223","host":"s3://npm-registry-packages"}},"0.1.1":{"name":"@ayonli/grpc-async","version":"0.1.1","keywords":["gRPC","async"],"author":{"name":"A-yon Lee","email":"the@ayon.li"},"license":"MIT","_id":"@ayonli/grpc-async@0.1.1","maintainers":[{"name":"ayonli","email":"the@ayon.li"}],"homepage":"https://github.com/ayonli/grpc-async#readme","bugs":{"url":"https://github.com/ayonli/grpc-async/issues"},"dist":{"shasum":"accae72044621d1f36a1630b4142a4a7ad7be71a","tarball":"https://registry.npmjs.org/@ayonli/grpc-async/-/grpc-async-0.1.1.tgz","fileCount":13,"integrity":"sha512-rrdybVSHt2mVKn8L52KHHhPGskiXIv1a8ZeTDOsVkn9Bp4knCufdj2od/SKjiS8CdWgydvsjNdLaqIOp5+pRLg==","signatures":[{"sig":"MEUCIGu06YDncwhgT7+EER4UyqavQu+fU5L2cZc5I4IujVBRAiEAqyWpuyJx63iQ14f+RniQUKCyVtjTdzR/IsDJ1+aqT+A=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":93529},"engines":{"node":">=14"},"exports":{"bun":"./index.ts","types":"./index.ts","import":"./esm/index.js","require":"./cjs/index.js"},"gitHead":"62ec9d0205ea2fb32de21d667fc10b3f8f3472b8","scripts":{"test":"mocha -r ts-node/register test/index.ts","build":"npm run build:esm && npm run build:cjs","build:cjs":"tsc --module commonjs --outDir cjs -p tsconfig.json","build:esm":"tsc --module esnext --outDir esm -p tsconfig.json","protoc:go":"protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative examples/Greeter.proto","prepublishOnly":"npm run build"},"_npmUser":{"name":"ayonli","email":"the@ayon.li"},"repository":{"url":"git+https://github.com/ayonli/grpc-async.git","type":"git"},"_npmVersion":"9.5.0","description":"A gRPC wrapper for Node.js with async functions","directories":{},"_nodeVersion":"18.14.2","dependencies":{"js-magic":"^1.4.2"},"_hasShrinkwrap":false,"devDependencies":{"mocha":"^10.2.0","tslib":"^2.6.2","ts-node":"^10.9.1","typescript":"^5.1.6","@types/node":"^20.4.5","@types/mocha":"^10.0.1","@ayonli/jsext":"^0.5.2","@grpc/grpc-js":"^1.8.21","@grpc/proto-loader":"^0.7.8"},"peerDependencies":{"@grpc/grpc-js":"^1.8.21","@grpc/proto-loader":"^0.7.8"},"_npmOperationalInternal":{"tmp":"tmp/grpc-async_0.1.1_1695385294135_0.04203491121081315","host":"s3://npm-registry-packages"}},"0.1.2":{"name":"@ayonli/grpc-async","version":"0.1.2","description":"A gRPC wrapper for Node.js with async functions","main":"./cjs/index.js","module":"./esm/index.js","types":"./cjs/index.d.ts","exports":{"bun":"./index.ts","import":"./esm/index.js","require":"./cjs/index.js","types":"./cjs/index.d.ts"},"scripts":{"build":"tsc --module esnext --outDir esm && tsc --module commonjs --outDir cjs --declaration && node postbuild.js","prepack":"npm run build","test":"mocha -r ts-node/register test/index.ts"},"repository":{"type":"git","url":"git+https://github.com/ayonli/grpc-async.git"},"keywords":["gRPC","async"],"author":{"name":"A-yon Lee","email":"the@ayon.li"},"license":"MIT","bugs":{"url":"https://github.com/ayonli/grpc-async/issues"},"homepage":"https://github.com/ayonli/grpc-async#readme","dependencies":{"js-magic":"^1.4.2"},"devDependencies":{"@ayonli/jsext":"^0.9.55","@grpc/grpc-js":"^1.8.21","@grpc/proto-loader":"^0.7.8","@types/mocha":"^10.0.1","@types/node":"^20.4.5","mocha":"^10.2.0","ts-node":"^10.9.1","tslib":"^2.6.2","typescript":"^5.1.6"},"peerDependencies":{"@grpc/grpc-js":"^1.8.21","@grpc/proto-loader":"^0.7.8"},"engines":{"node":">=14"},"_id":"@ayonli/grpc-async@0.1.2","gitHead":"78b1a537a0bf84c65eb18c476530f2b34f2836f3","_nodeVersion":"22.0.0","_npmVersion":"10.5.1","dist":{"integrity":"sha512-jPAsLxeOCag9ngDStXhkgDnEDP8nLdfUHFbAmsS5tHzAd6yhyYCuOj+wubqqdLIb5IObry3EzmKgIrslIom3aQ==","shasum":"eaac17c13339d3c2540e8ff13c00f67c5bad5155","tarball":"https://registry.npmjs.org/@ayonli/grpc-async/-/grpc-async-0.1.2.tgz","fileCount":17,"unpackedSize":101659,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCIh6muB4wIq80GCGGmRK0TazqB+nRLG70iqGrnKhjpfAIgFb+BNUVOdYM+kKJ+DzFzeeLDltsvFg+0mOHbEEFcbgE="}]},"_npmUser":{"name":"ayonli","email":"the@ayon.li"},"directories":{},"maintainers":[{"name":"ayonli","email":"the@ayon.li"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/grpc-async_0.1.2_1722769675385_0.36042885241617806"},"_hasShrinkwrap":false}},"time":{"created":"2023-08-26T19:38:24.256Z","modified":"2024-08-04T11:07:55.728Z","0.1.0":"2023-08-26T19:38:24.558Z","0.1.1":"2023-09-22T12:21:34.368Z","0.1.2":"2024-08-04T11:07:55.564Z"},"bugs":{"url":"https://github.com/ayonli/grpc-async/issues"},"author":{"name":"A-yon Lee","email":"the@ayon.li"},"license":"MIT","homepage":"https://github.com/ayonli/grpc-async#readme","keywords":["gRPC","async"],"repository":{"type":"git","url":"git+https://github.com/ayonli/grpc-async.git"},"description":"A gRPC wrapper for Node.js with async functions","maintainers":[{"name":"ayonli","email":"the@ayon.li"}],"readme":"# gRPC Async\n\nA gRPC wrapper for Node.js with async functions.\n\nThe design of [@grpc/grpc-js](https://www.npmjs.com/package/@grpc/grpc-js) uses\na traditional Node.js callback design for function implementations, which, in\nnowadays programming world, is painful. So this little library wraps the\nasync/await functionality into both the gRPC server and the client.\n\n*(NOTE: this package only supports dynamic generated version of gRPC functions*\n*since the static generated version lacks typing support.)*\n\n## Prerequisites\n\n- [Node.js](https://nodejs.org) v14+\n- [@grpc/grpc-js](https://www.npmjs.com/package/@grpc/grpc-js) and [@grpc/proto-loader](https://www.npmjs.com/package/@grpc/proto-loader)\n- For server-side code, if using [TypeScript](https://www.typescriptlang.org/),\n    make sure the compiling target is `es2018` or higher which outputs native\n    async generator functions.\n\n## Install\n\nIn order to install this package, we must install **@grpc/grpc-js** and\n**@grpc/proto-loader** as well.\n\n```sh\nnpm i @grpc/grpc-js @grpc/proto-loader @ayonli/grpc-async\n```\n\n## Examples\n\nThe following examples first show the traditional way of implementations and\nthen show the async way implementations, so that we can compare how simple it is\nin the new way.\n\n### Traditional Way\n\n```ts\nimport * as protoLoader from '@grpc/proto-loader';\nimport {\n    loadPackageDefinition,\n    GrpcObject,\n    ServiceClientConstructor,\n    Server,\n    ServerUnaryCall,\n    ServerWritableStream,\n    ServerReadableStream,\n    ServerDuplexStream,\n    ServerCredentials,\n    credentials,\n    ClientReadableStream,\n    ClientWritableStream,\n    ClientDuplexStream\n} from \"@grpc/grpc-js\"\n\nconst PROTO_PATH = __dirname + '/examples/Greeter.proto';\nconst SERVER_ADDRESS = \"localhost:50051\";\n\nconst packageDefinition = protoLoader.loadSync(PROTO_PATH, {\n    longs: String,\n    enums: String,\n    defaults: true,\n    oneofs: true\n});\nconst examples = loadPackageDefinition(packageDefinition).examples as GrpcObject;\nconst Greeter = examples.Greeter as ServiceClientConstructor;\n\ntype Request = {\n    name: string;\n};\n\ntype Response = {\n    message: string;\n};\n\n// ==== server ====\nconst server = new Server();\nserver.addService(Greeter.service, {\n    sayHello: (\n        call: ServerUnaryCall<Request, Response>,\n        callback: (err: Error, reply: Response) => void\n    ) => {\n        const { name } = call.request;\n        callback(null, { message: \"Hello, \" + name } as Response);\n    },\n    sayHelloStreamReply: (call: ServerWritableStream<Request, Response>) => {\n        const { name } = call.request;\n        call.write({ message: \"Hello 1: \" + name } as Response);\n        call.write({ message: \"Hello 2: \" + name } as Response);\n        call.write({ message: \"Hello 3: \" + name } as Response);\n        call.end();\n    },\n    sayHelloStreamRequest: (call: ServerReadableStream<Request, Response>, callback) => {\n        const names: string[] = [];\n\n        call.on(\"data\", ({ name }: Request) => {\n            names.push(name);\n        }).once(\"end\", () => {\n            callback(null, { message: \"Hello, \" + names.join(\", \") } as Response);\n        }).once(\"error\", (err) => {\n            callback(err, void 0);\n        });\n    },\n    sayHelloDuplex: (call: ServerDuplexStream<Request, Response>) => {\n        call.on(\"data\", ({ name }: Request) => {\n            call.write({ message: \"Hello, \" + name });\n        });\n    }\n});\n\nserver.bindAsync(SERVER_ADDRESS, ServerCredentials.createInsecure(), () => {\n    server.start();\n});\n// ==== server ====\n\n// ==== client ====\nconst client = new Greeter(SERVER_ADDRESS, credentials.createInsecure());\n\n// Calling #waitForReady() is required since at this point the server may not be\n// available yet.\nclient.waitForReady(Date.now() + 5000, (_: Error) => {\n    client.sayHello({ name: \"World\" } as Request, (err: Error, reply: Response) => {\n        if (err) {\n            console.error(err);\n        } else {\n            console.log(reply); // { message: \"Hello, World\" }\n        }\n    });\n\n    const streamReplyCall: ClientReadableStream<Response> = client.sayHelloStreamReply({\n        name: \"World\",\n    } as Request);\n    streamReplyCall.on(\"data\", (reply: Response) => {\n        console.log(reply);\n        // { message: \"Hello 1: World\" }\n        // { message: \"Hello 2: World\" }\n        // { message: \"Hello 3: World\" }\n    }).on(\"error\", err => {\n        console.error(err);\n    });\n\n    const streamRequestCall: ClientWritableStream<Request> = client.sayHelloStreamRequest(\n        (err: Error, reply: Response) => {\n            if (err) {\n                console.error(err);\n            } else {\n                console.log(reply); // { message: \"Hello, Mr. World, Mrs. World\" }\n\n                // THINK: what should we do with the **reply**? If our code\n                // logic is from top to bottom, but we get the reply above the\n                // logic.\n            }\n        }\n    );\n    streamRequestCall.write({ name: \"Mr. World\" } as Request);\n    streamRequestCall.write({ name: \"Mrs. World\" } as Request);\n    streamRequestCall.end();\n\n    const duplexCall: ClientDuplexStream<Request, Response> = client.sayHelloDuplex();\n    duplexCall.on(\"data\", (reply: Response) => {\n        console.log(reply);\n        // { message: \"Hello, Mr. World\" }\n        // { message: \"Hello, Mrs. World\" }\n    });\n    duplexCall.write({ name: \"Mr. World\" });\n    duplexCall.write({ name: \"Mrs. World\" });\n    duplexCall.end();\n});\n// ==== client ====\n```\n\n### Async Way\n\n```ts\nimport * as protoLoader from '@grpc/proto-loader';\nimport {\n    loadPackageDefinition,\n    GrpcObject,\n    ServiceClientConstructor,\n    Server,\n    ServerCredentials,\n    credentials\n} from \"@grpc/grpc-js\";\nimport {\n    serve,\n    connect,\n    ServerReadableStream,\n    ServerDuplexStream\n} from \"@ayonli/grpc-async\";\n\nconst PROTO_PATH = __dirname + '/examples/Greeter.proto';\nconst SERVER_ADDRESS = \"localhost:50051\";\n\nconst packageDefinition = protoLoader.loadSync(PROTO_PATH, {\n    keepCase: true,\n    longs: String,\n    enums: String,\n    defaults: true,\n    oneofs: true\n});\nconst examples = loadPackageDefinition(packageDefinition).examples as GrpcObject;\n\ntype Request = {\n    name: string;\n};\n\ntype Response = {\n    message: string;\n};\n\nclass Greeter {\n    async sayHello({ name }: Request) {\n        return { message: 'Hello ' + name } as Response;\n    }\n\n    async *sayHelloStreamReply({ name }: Request) {\n        yield { message: `Hello 1: ${name}` } as Response;\n        yield { message: `Hello 2: ${name}` } as Response;\n        yield { message: `Hello 3: ${name}` } as Response;\n    }\n\n    async sayHelloStreamRequest(stream: ServerReadableStream<Request, Response>) {\n        const names: string[] = [];\n\n        for await (const { name } of stream) {\n            names.push(name);\n        }\n\n        return await this.sayHello({ name: names.join(\", \") });\n    }\n\n    async *sayHelloDuplex(stream: ServerDuplexStream<Request, Response>) {\n        for await (const req of stream) {\n            yield await this.sayHello(req);\n        }\n    }\n}\n\n// ==== server ====\nconst server = new Server()\n\nserve(server, examples.Greeter as ServiceClientConstructor, new Greeter());\n\nserver.bindAsync(SERVER_ADDRESS, ServerCredentials.createInsecure(), () => {\n    server.start();\n});\n// ==== server ====\n\n// ==== client ====\nconst client = connect<Greeter>(\n    examples.Greeter as ServiceClientConstructor,\n    SERVER_ADDRESS,\n    credentials.createInsecure());\n\n(async () => {\n    const reply = await client.sayHello({ name: \"World\" });\n    console.log(reply); // { message: \"Hello, World\" }\n})().catch(console.error);\n\n(async () => {\n    for await (const reply of client.sayHelloStreamReply({ name: \"World\" })) {\n        console.log(reply);\n        // { message: \"Hello 1: World\" }\n        // { message: \"Hello 2: World\" }\n        // { message: \"Hello 3: World\" }\n    }\n})().catch(console.error);\n\n(async () => {\n    const call = client.sayHelloStreamRequest();\n    call.write({ name: \"Mr. World\" });\n    call.write({ name: \"Mrs. World\" });\n\n    const reply = await call.returns();\n    console.log(reply); // { message: \"Hello, Mr. World, Mrs. World\" }\n})().catch(console.error);\n\n(async () => {\n    const call = client.sayHelloDuplex();\n    let counter = 0;\n\n    call.write({ name: \"Mr. World\" });\n    call.write({ name: \"Mrs. World\" });\n\n    for await (const reply of call) {\n        console.log(reply);\n        // { message: \"Hello, Mr. World\" }\n        // { message: \"Hello, Mrs. World\" }\n\n        if (++counter === 2) {\n            call.end(); // this will cause the iterator to close\n        }\n    }\n})().catch(console.error);\n// ==== client ====\n```\n\n*We can see more about the examples in the [examples](./examples) folder.*\n\n#### Recap\n\nSee the major differences here?\n\n**On the server**\n\n1. Instead of calling the `server.addService()` function to register the\n    implementation, we use the `serve()` utility function, which supports\n    native async (and async generator) functions.\n2. Instead of just using an object literal as the service implementation, we\n    define a class as implementation that honors the design in the `.proto` file.\n3. Instead of accessing the `request` from the `call` context argument, we\n    receive the data directly from the function's argument, which honor the same\n    design in the `.proto` file.\n4. For unary calls, instead of calling the `callback()` to send the response, we\n    simply return it from the function, which also honor the same design in the\n    `.proto` file.\n5. For stream reply calls, instead of calling `call.write()` to send the\n    response, we take advantage of the native `yield` expression, which\n    generates results overtime.\n6. For stream request calls, instead of listening to the `data` and `end` events,\n    we use the `for await` statement to receive the requests sent by the client.\n7. For duplex calls, instead of listening to the `data` event for requests and\n    calling `call.write()` to send back response, we use the `for await`\n    statement and the `yield` expression which are more straightforward.\n\n**On the client**\n\n1. Instead of creating the instance via a `new` expression, we use `connect()`\n    utility function to generate the instance, which resolves RPC functions with\n    native async support.\n2. We use the type `Greeter` on the `connect()` function so it can produce\n    correct methods in TypeScript that could helps us reduce errors in our code.\n3. There is no need for the `waitForReady()` since it's handled automatically\n    inside the function call.\n4. For unary calls, instead of passing a callback function to retrieve the\n    response, we use the `await` expression to get the result.\n5. For stream reply calls, instead of listening to the `data` and `end` events,\n    we use the `for await` statement to receive the responses yielded by the\n    server.\n6. For stream request calls, instead using a callback function to receive the\n    response, we use the `call.returns()` to retrieve the response just where\n    we need it.\n7. For duplex calls, instead of listening to the `data` event for responses,\n    again, we use the `for await` statement to receive the responses yielded by\n    the server.\n\n## LoadBalancer\n\nOther than using `connect()` to connect to a certain server, we can use\n`new LoadBalancer()` to connect to multiple servers at once and leverage calls\nwith a programmatic client-side load balancer.\n\nUnlike the traditional load balancer which uses a DNS resolver that assumes our\nprogram runs on different machines or virtual machines, this new load balancer\nallows us to run the server in the same machine but in many processes/instances,\nand we can programmatically control how our traffic is routed to different\nserver instances on demand.\n\n```ts\nimport { LoadBalancer } from \"@ayonli/grpc-async\";\n// ...\n\n// Imagine we have three server instances run on the same server (localhost).\nconst balancer = new LoadBalancer(examples.Greeter as ServiceClientConstructor, [\n    { address: \"localhost:50051\", credentials: credentials.createInsecure() },\n    { address: \"localhost:50052\", credentials: credentials.createInsecure() },\n    { address: \"localhost:50053\", credentials: credentials.createInsecure() }\n]);\n\n(async () => {\n    // Be default, the load balancer uses round-robin algorithm for routing, so\n    // this call happens on the first server instance,\n    const reply1 = await balancer.getInstance().sayHello({ name: \"World\" });\n\n    // this call happens on the second server instance.\n    const reply2 = await balancer.getInstance().sayHello({ name: \"World\" });\n\n    // this call happens on the third server instance.\n    const reply3 = await balancer.getInstance().sayHello({ name: \"World\" });\n\n    // this call happens on the first server instance.\n    const reply4 = await balancer.getInstance().sayHello({ name: \"World\" });\n})();\n\n// We can define the route resolver to achieve custom load balancing strategy.\nimport hash from \"string-hash\"; // assuming this package exists\nconst balancer2 = new LoadBalancer(examples.Greeter as ServiceClientConstructor, [\n    { address: \"localhost:50051\", credentials: credentials.createInsecure() },\n    { address: \"localhost:50052\", credentials: credentials.createInsecure() },\n    { address: \"localhost:50053\", credentials: credentials.createInsecure() }\n], (ctx) => {\n    const addresses: string[] = ctx.servers.map(item => item.address);\n\n    if (typeof ctx.params === \"string\") {\n        if (addresses.includes(ctx.params)) {\n            return ctx.params; // explicitly use a server instance\n        } else {\n            // route by hash\n            const id: number = hash(ctx.params);\n            return addresses[id % addresses.length];\n        }\n    } else if (typeof ctx.params === \"number\") {\n        return addresses[ctx.params % addresses.length];\n    } else if (typeof ctx.params === \"object\") {\n        // This algorithm guarantees the same param structure passed to the\n        // `getInstance()` returns the same service instance.\n        const id: number = hash(String(Object.keys(ctx.params ?? {}).sort()));\n        return addresses[id % addresses.length];\n    } else {\n        // use round-robin\n        return addresses[ctx.acc % addresses.length];\n    }\n});\n\n(async () => {\n    // These two calls will happen on the same server instance since they have\n    // the same route param structure:\n    const req1: Request = { name: \"Mr. World\" };\n    const reply1 = await balancer2.getInstance(req).sayHello(req);\n\n    const req2: Request = { name: \"Mrs. World\" };\n    const reply2 = await balancer2.getInstance(req).sayHello(req);\n\n    // This call happens on the first server since we explicitly set the server\n    // address to use:\n    const req3: Request = { name: \"Mrs. World\" };\n    const reply3 = await balancer2.getInstance(\"localhost:50051\").sayHello(req);\n})();\n```\n\n## ConnectionManager\n\nConnectionManager provides a place to manage all clients and retrieve instances\nvia a general approach.\n\nA client or a load balancer always binds a specific service client constructor\nand is a scoped variable, if we are going to use them across our program in\ndifferent places, it would very painful and may cause recursive import problem.\n\nThe connection manager, however, is a central place and a single variable, we\ncan assign it to the global namespace and use it to retrieve service instances\nanywhere we want without worrying how to import them.\n\nFor example:\n\n```ts\nimport { ConnectionManager } from \"@ayonli/grpc-async\";\n// ...\n\ndeclare global {\n    const services: ConnectionManager;\n}\n\n// @ts-ignore\nconst manager = global[\"services\"] = new ConnectionManager();\n\nmanager.register(client);\n// Or\nmanager.register(balancer);\n\n// and use it anywhere\nconst ins = services.getInstanceOf<Greeter>(\"examples.Greeter\");\nconst result = await ins.sayHello({ name: \"World\" });\n```\n\n**Further more**, we can extend our `services` via chaining syntax, make our code\neven more cleaner and elegant.\n\n```ts\nimport { ConnectionManager, ServiceClient } from \"@ayonli/grpc-async\";\n// ...\n\ndeclare global {\n    // Instead of defining `services` as global constant, we define it as a\n    // namespace which contains sub namespaces that corresponds the package name\n    // in the .proto file.\n    namespace services.examples {\n        const Greeter: ServiceClient<Greeter>;\n    }\n}\n\nconst manager = new ConnectionManager();\n\nmanager.register(client);\n// Or\nmanager.register(balancer);\n\n// @ts-ignore\nglobal[\"services\"] = manager.useChainingSyntax();\n\n// and use it anywhere\nconst result = await services.examples.Greeter.sayHello({ name: \"World\" });\n```\n\nFor more information about the `LoadBalancer` and the `ConnectionManager`, please\nrefer to the [source code](./client.ts) of their definition. They are the\nenhancement part of this package that aims to provide straightforward usage of\ngRPC in a project with distributed system design.\n\n## API\n\nApart from the functions and classes, for better TypeScript support, this package\nalso rewrites some of the interfaces/types seen in the **@grpc/grpc-js** library,\nI'll list them all as follows:\n\n```ts\nimport * as grpc from \"@grpc/grpc-js\";\n\nexport declare function serve<T>(\n    server: grpc.Server,\n    service: grpc.ServiceClientConstructor | grpc.ServiceDefinition<T>,\n    instance: T\n): void;\n\nexport declare function unserve<T>(\n    server: grpc.Server,\n    service: grpc.ServiceClientConstructor | grpc.ServiceDefinition<T>\n): void;\n\nexport declare function connect<T>(\n    service: grpc.ServiceClientConstructor,\n    address: string,\n    credentials: grpc.ChannelCredentials,\n    options?: Partial<grpc.ChannelOptions> & {\n        connectTimeout?: number; // default 120_000 ms\n    }\n): ServiceClient<T>;\n\nexport type ServerWritableStream<Req, Res> = grpc.ServerWritableStream<Req, Res>;\n\nexport type ServerReadableStream<Req, Res = void> = grpc.ServerReadableStream<Req, Res> & AsyncIterable<Req>;\n\nexport type ServerDuplexStream<Req, Res> = grpc.ServerDuplexStream<Req, Res> & AsyncIterable<Req>;\n\nexport type ClientWritableStream<Req, Res> = grpc.ClientWritableStream<Req> & {\n    returns(): Promise<Res>;\n};\n\nexport type ClientReadableStream<Res> = grpc.ClientReadableStream<Res> & AsyncIterable<Res>;\n\nexport type ClientDuplexStream<Req, Res> = grpc.ClientDuplexStream<Req, Res> & AsyncIterable<Res>;\n\nexport type UnaryFunction<Req, Res> = (req: Req, metadata?: grpc.Metadata) => Promise<Res>;\n\nexport type StreamResponseFunction<Req, Res> = (req: Req, metadata?: grpc.Metadata) => AsyncGenerator<Res, void, unknown>;\n\nexport type StreamRequestFunction<Req, Res> = (stream: ServerReadableStream<Req>) => Promise<Res>;\n\nexport type DuplexFunction<Req, Res> = (stream: ServerDuplexStream<Req, Res>) => AsyncGenerator<Res, void, unknown>;\n\nexport type ClientMethods<T extends object> = {\n    [K in keyof T]: T[K] extends DuplexFunction<infer Req, infer Res> ? (metadata?: grpc.Metadata) => ClientDuplexStream<Req, Res>\n    : T[K] extends StreamRequestFunction<infer Req, infer Res> ? (metadata?: grpc.Metadata) => ClientWritableStream<Req, Res>\n    : T[K] extends StreamResponseFunction<infer Req, infer Res> ? (req: Req, metadata?: grpc.Metadata) => AsyncGenerator<Res, void, unknown>\n    : T[K] extends UnaryFunction<infer Req, infer Res> ? (req: Req, metadata?: grpc.Metadata) => Promise<Res>\n    : T[K];\n};\n\nexport type ServiceClient<T extends object> = Omit<grpc.Client, \"waitForReady\"> & {\n    waitForReady(deadline?: Date | number): Promise<void>;\n    waitForReady(deadline: Date | number, callback: (err: Error) => void): void;\n} & ClientMethods<T>;\n\nexport type ServerConfig = {\n    address: string;\n    credentials: grpc.ChannelCredentials,\n    options?: Partial<grpc.ChannelOptions> & { connectTimeout?: number; };\n};\n\nexport declare class LoadBalancer<T extends object, P extends any = any> {\n    readonly service: grpc.ServiceClientConstructor;\n\n    /**\n     * @param target \n     * @param servers The server configurations used to create service client.\n     * @param routeResolver Custom route resolver used to implement load\n     *  balancing algorithms, if not provided, a default round-robin algorithm\n     *  is used. The function takes a context object and returns an address\n     *  filtered from the `ctx.servers`.\n     */\n    constructor(\n        service: grpc.ServiceClientConstructor,\n        servers: ServerConfig[],\n        routeResolver?: ((ctx: {\n            service: grpc.ServiceClientConstructor;\n            servers: (ServerConfig & { state: grpc.connectivityState; })[];\n            /**\n             * The route params passed when calling the `getInstance()` function, we\n             * can use this object to calculate the desired route address.\n             */\n            params: P | null;\n            acc: number;\n        }) => string)\n    );\n\n    /**\n     * Dynamically add server configurations at runtime, this is useful when we \n     * need to implement some kind of service discovery strategy.\n     */\n    addServer(server: ServerConfig): boolean;\n\n    /**\n     * Dynamically remove server configurations at runtime, this is useful when we \n     * need to implement some kind of service discovery strategy.\n     */\n    removeServer(address: string): boolean;\n\n    /**\n     * Retrieves an instance of the service client.\n     * \n     * @param routeParams If a custom `routeResolver` is provided when initiating\n     *  the load balancer, this argument will be passed to the function for route\n     *  calculation, otherwise, it has no effect.\n     * @returns \n     */\n    getInstance(routeParams?: P): ServiceClient<T>;\n\n    /** Closes all the connection. */\n    close(): void;\n}\n\nexport type ChainingProxyInterface = ServiceClient<any> | {\n    [nsp: string]: ChainingProxyInterface;\n};\n\nexport declare class ConnectionManager {\n    register(target: ServiceClient<any> | LoadBalancer<any>): boolean;\n\n    /**\n     * @param target If the target is a string, it is the full name of the\n     *  service (includes the package name, concatenated with `.`).\n     */\n    deregister(target: string | ServiceClient<any> | LoadBalancer<any>, closeConnection?: boolean): boolean;\n\n    /**\n     * @param target If the target is a string, it is the full name of the\n     *  service (includes the package name, concatenated with `.`).\n     * @param routeParams If a custom `routeResolver` is provided when initiating\n     *  the load balancer, this argument will be passed to the function for route\n     *  calculation, otherwise, it has no effect.\n     * @throws If the target service is not registered, a ReferenceError will be\n     *  thrown.\n     */\n    getInstanceOf<T extends object>(\n        target: string | ServiceClient<T> | LoadBalancer<T>\n    ): ServiceClient<T>;\n    getInstanceOf<T extends object, P extends any = any>(\n        target: string | ServiceClient<T> | LoadBalancer<T>,\n        routeParams: P\n    ): ServiceClient<T>;\n\n    /** Closes all the connections of all proxies. */\n    close(): void;\n\n    /**\n     * Instead of calling `#getInstanceOf()` to retrieve the service client,\n     * this function allows us to use chaining syntax to dynamically generated\n     * namespaces and client constructors that can be used as a syntax sugar.\n     * @example\n     *  // Instead of doing this:\n     *  const ins = manager.getInstanceOf<Greeter>(\"examples.Greeter\");\n     *  const result = await ins.sayHello({ name: \"World\" });\n     * \n     *  // We do this:\n     *  const services = manager.useChainingSyntax();\n     *  const result = await services.examples.Greeter.sayHello({ name: \"World\" });\n     * @param rootNsp If set, the namespace will start from the given name.\n     *  Usually leave blank or set to the package name in the proto file.\n     * @example\n     *  const examples = manager.useChainingSyntax(\"examples\");\n     *  const result = await examples.Greeter.sayHello({ name: \"World\" });\n     */\n    useChainingSyntax(rootNsp?: string): ChainingProxyInterface;\n}\n```\n","readmeFilename":"README.md"}