diff --git a/README.md b/README.md index e8302ed7..d1a30a31 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Ecosystem based on Artus.js - [https://www.artusjs.org](https://www.artusjs.org) | @artusx/plugin-log4js | [![NPM version](https://img.shields.io/npm/v/@artusx/plugin-log4js.svg?style=flat-square)](https://npmjs.org/package/@artusx/plugin-log4js) | | @artusx/plugin-nunjucks | [![NPM version](https://img.shields.io/npm/v/@artusx/plugin-nunjucks.svg?style=flat-square)](https://npmjs.org/package/@artusx/plugin-nunjucks) | | @artusx/plugin-schedule | [![NPM version](https://img.shields.io/npm/v/@artusx/plugin-schedule.svg?style=flat-square)](https://npmjs.org/package/@artusx/plugin-schedule) | +| @artusx/plugin-grpc | [![NPM version](https://img.shields.io/npm/v/@artusx/plugin-grpc.svg?style=flat-square)](https://npmjs.org/package/@artusx/plugin-grpc) | | @artusx/plugin-pptr | [![NPM version](https://img.shields.io/npm/v/@artusx/plugin-pptr.svg?style=flat-square)](https://npmjs.org/package/@artusx/plugin-pptr) | | @artusx/plugin-proxy | [![NPM version](https://img.shields.io/npm/v/@artusx/plugin-proxy.svg?style=flat-square)](https://npmjs.org/package/@artusx/plugin-proxy) | | @artusx/plugin-openai | [![NPM version](https://img.shields.io/npm/v/@artusx/plugin-openai.svg?style=flat-square)](https://npmjs.org/package/@artusx/plugin-openai) | diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index da3c1d52..4027dc8b 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -42,6 +42,52 @@ importers: specifier: ~5.3.3 version: 5.3.3 + ../../packages/apps/artusx-grpc: + dependencies: + '@artus/core': + specifier: ~2.2.3 + version: 2.2.3(reflect-metadata@0.1.14) + '@artusx/plugin-grpc': + specifier: workspace:* + version: link:../../plugins/grpc + '@artusx/utils': + specifier: workspace:* + version: link:../../libs/utils + reflect-metadata: + specifier: ^0.1.13 + version: 0.1.14 + tslib: + specifier: ^2.5.0 + version: 2.6.2 + devDependencies: + '@artusx/eslint-config': + specifier: workspace:* + version: link:../../../toolchains/eslint-config + '@artusx/tsconfig': + specifier: workspace:* + version: link:../../../toolchains/tsconfig + '@types/node': + specifier: ^18.11.17 + version: 18.19.24 + '@typescript-eslint/eslint-plugin': + specifier: ~6.19.1 + version: 6.19.1(eslint@8.56.0)(typescript@4.9.5) + eslint: + specifier: ~8.56.0 + version: 8.56.0 + eslint-plugin-import: + specifier: ~2.29.1 + version: 2.29.1(eslint@8.56.0) + nodemon: + specifier: ~3.0.2 + version: 3.0.3 + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@18.19.24)(typescript@4.9.5) + typescript: + specifier: ^4.9.4 + version: 4.9.5 + ../../packages/apps/artusx-koa: dependencies: '@artusx/core': @@ -351,6 +397,64 @@ importers: specifier: ^4.9.4 version: 4.9.5 + ../../packages/plugins/grpc: + dependencies: + '@artus/core': + specifier: ^2.x + version: 2.2.2(reflect-metadata@0.2.1) + '@artus/pipeline': + specifier: ^0.2 + version: 0.2.3 + '@grpc/grpc-js': + specifier: ~1.10.3 + version: 1.10.3 + '@grpc/proto-loader': + specifier: ~0.7.10 + version: 0.7.10 + lodash.get: + specifier: ~4.4.2 + version: 4.4.2 + devDependencies: + '@artusx/eslint-config': + specifier: workspace:* + version: link:../../../toolchains/eslint-config + '@artusx/tsconfig': + specifier: workspace:* + version: link:../../../toolchains/tsconfig + '@types/jest': + specifier: ~29.5.11 + version: 29.5.12 + '@types/lodash.get': + specifier: ~4.4.9 + version: 4.4.9 + '@types/node': + specifier: ^18.11.17 + version: 18.19.24 + '@typescript-eslint/eslint-plugin': + specifier: ~6.19.1 + version: 6.19.1(eslint@8.56.0)(typescript@4.9.5) + eslint: + specifier: ~8.56.0 + version: 8.56.0 + eslint-plugin-import: + specifier: ~2.29.1 + version: 2.29.1(eslint@8.56.0) + jest: + specifier: ~29.7.0 + version: 29.7.0(@types/node@18.19.24) + reflect-metadata: + specifier: ~0.2.1 + version: 0.2.1 + ts-jest: + specifier: ~29.1.2 + version: 29.1.2(jest@29.7.0)(typescript@4.9.5) + tslib: + specifier: ^2.5.0 + version: 2.6.2 + typescript: + specifier: ^4.9.4 + version: 4.9.5 + ../../packages/plugins/koa: dependencies: '@artus/core': @@ -1030,6 +1134,18 @@ packages: tslib: 2.6.2 dev: false + /@artus/core@2.2.3(reflect-metadata@0.1.14): + resolution: {integrity: sha512-o9PE9XftuYTuzV2mDoGeGNB/9kJGT6PxHNFxPE64HYl+oaXf8gh+YqH3+WBD3ZXc7XvE+IB/E7Y3s2K1GCtBpQ==} + peerDependencies: + reflect-metadata: ^0.1.13 + dependencies: + '@artus/injection': 0.5.3 + deepmerge: 4.3.1 + minimatch: 5.1.6 + reflect-metadata: 0.1.14 + tslib: 2.6.2 + dev: false + /@artus/eslint-config-artus@0.0.1: resolution: {integrity: sha512-jIQQ27IF3erxD6hBV1wUOMNjK0vQnTAFot9BYGyFjNkcVqLhBoJ5X8oEai3azEcRAf+L7KGf/3wQv6Pe4gqomA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1446,6 +1562,25 @@ packages: resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} dev: false + /@grpc/grpc-js@1.10.3: + resolution: {integrity: sha512-qiO9MNgYnwbvZ8MK0YLWbnGrNX3zTcj6/Ef7UHu5ZofER3e2nF3Y35GaPo9qNJJ/UJQKa4KL+z/F4Q8Q+uCdUQ==} + engines: {node: '>=12.10.0'} + dependencies: + '@grpc/proto-loader': 0.7.10 + '@js-sdsl/ordered-map': 4.4.2 + dev: false + + /@grpc/proto-loader@0.7.10: + resolution: {integrity: sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==} + engines: {node: '>=6'} + hasBin: true + dependencies: + lodash.camelcase: 4.3.0 + long: 5.2.3 + protobufjs: 7.2.6 + yargs: 17.7.2 + dev: false + /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -1729,6 +1864,10 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@js-sdsl/ordered-map@4.4.2: + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + dev: false + /@koa/bodyparser@5.0.0: resolution: {integrity: sha512-JEiZVe2e85qPOqA+Nw/SJC5fkFw3XSekh0RSoqz5F6lFYuhEspgqAb972rQRCJesv27QUsz96vU/Vb92wF1GUg==} engines: {node: '>= 16'} @@ -1883,6 +2022,49 @@ packages: - encoding dev: false + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: false + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: false + + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false + + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false + + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false + + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false + + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false + /@puppeteer/browsers@2.2.0: resolution: {integrity: sha512-MC7LxpcBtdfTbzwARXIkqGZ1Osn3nnZJlm+i0+VqHl72t//Xwl9wICrXT8BwtgC6s1xJNHsxOpvzISUqe92+sw==} engines: {node: '>=18'} @@ -2143,6 +2325,16 @@ packages: '@types/koa': 2.15.0 dev: true + /@types/lodash.get@4.4.9: + resolution: {integrity: sha512-J5dvW98sxmGnamqf+/aLP87PYXyrha9xIgc2ZlHl6OHMFR2Ejdxep50QfU0abO1+CH6+ugx+8wEUN1toImAinA==} + dependencies: + '@types/lodash': 4.17.0 + dev: true + + /@types/lodash@4.17.0: + resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} + dev: true + /@types/luxon@3.3.8: resolution: {integrity: sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==} @@ -6620,10 +6812,18 @@ packages: dependencies: p-locate: 5.0.0 + /lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + dev: false + /lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} dev: false + /lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + dev: false + /lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} dev: false @@ -7731,6 +7931,25 @@ packages: sisteransi: 1.0.5 dev: true + /protobufjs@7.2.6: + resolution: {integrity: sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.11.28 + long: 5.2.3 + dev: false + /proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} diff --git a/packages/apps/artusx-grpc/.eslintignore b/packages/apps/artusx-grpc/.eslintignore new file mode 100644 index 00000000..42aa3099 --- /dev/null +++ b/packages/apps/artusx-grpc/.eslintignore @@ -0,0 +1,4 @@ +node_modules +lib +dist +coverage \ No newline at end of file diff --git a/packages/apps/artusx-grpc/.eslintrc b/packages/apps/artusx-grpc/.eslintrc new file mode 100644 index 00000000..b29cd197 --- /dev/null +++ b/packages/apps/artusx-grpc/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": ["@artusx/eslint-config"], + "parserOptions": { + "project": "./tsconfig.json" + } +} diff --git a/packages/apps/artusx-grpc/.gitignore b/packages/apps/artusx-grpc/.gitignore new file mode 100644 index 00000000..a65b4177 --- /dev/null +++ b/packages/apps/artusx-grpc/.gitignore @@ -0,0 +1 @@ +lib diff --git a/packages/apps/artusx-grpc/.npmrc b/packages/apps/artusx-grpc/.npmrc new file mode 100644 index 00000000..862731de --- /dev/null +++ b/packages/apps/artusx-grpc/.npmrc @@ -0,0 +1,3 @@ +registry=https://registry.npmjs.org/ +always-auth=false +strict-ssl=false diff --git a/packages/apps/artusx-grpc/README.md b/packages/apps/artusx-grpc/README.md new file mode 100644 index 00000000..5f7c5a85 --- /dev/null +++ b/packages/apps/artusx-grpc/README.md @@ -0,0 +1 @@ +# artusx-grpc diff --git a/packages/apps/artusx-grpc/package.json b/packages/apps/artusx-grpc/package.json new file mode 100644 index 00000000..23f99ef5 --- /dev/null +++ b/packages/apps/artusx-grpc/package.json @@ -0,0 +1,38 @@ +{ + "name": "artusx-grpc", + "version": "0.0.0-dev.0", + "description": "grpc app powered by artusx", + "keywords": [ + "artusx" + ], + "author": "Suyi ", + "main": "dist/index.js", + "scripts": { + "_build": "npm run tsc", + "build": "", + "ci": "npm run lint", + "dev": "ARTUS_SERVER_ENV=development npx nodemon src/index.ts", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", + "start": "ARTUS_SERVER_ENV=production node dist/index.js", + "tsc": "rm -rf dist && tsc" + }, + "dependencies": { + "@artus/core": "~2.2.3", + "@artusx/plugin-grpc": "workspace:*", + "@artusx/utils": "workspace:*", + "reflect-metadata": "^0.1.13", + "tslib": "^2.5.0" + }, + "devDependencies": { + "@artusx/eslint-config": "workspace:*", + "@artusx/tsconfig": "workspace:*", + "@types/node": "^18.11.17", + "@typescript-eslint/eslint-plugin": "~6.19.1", + "eslint": "~8.56.0", + "eslint-plugin-import": "~2.29.1", + "nodemon": "~3.0.2", + "ts-node": "^10.9.1", + "typescript": "^4.9.4" + } +} diff --git a/packages/apps/artusx-grpc/src/bootstrap.ts b/packages/apps/artusx-grpc/src/bootstrap.ts new file mode 100644 index 00000000..8e8566b7 --- /dev/null +++ b/packages/apps/artusx-grpc/src/bootstrap.ts @@ -0,0 +1,11 @@ +import path from 'path'; +import { Application } from '@artusx/utils'; + +export const main = async () => { + const app = await Application.start({ + root: path.resolve(__dirname), + configDir: 'config', + }); + + return app; +}; diff --git a/packages/apps/artusx-grpc/src/config/config.default.ts b/packages/apps/artusx-grpc/src/config/config.default.ts new file mode 100644 index 00000000..2ceb05d0 --- /dev/null +++ b/packages/apps/artusx-grpc/src/config/config.default.ts @@ -0,0 +1,18 @@ +import path from 'path'; + +export default () => { + const grpc = { + protoList: [ + path.resolve(__dirname, '../protos/helloworld.proto'), + path.resolve(__dirname, '../protos/echo.proto'), + ], + server: { + host: '0.0.0.0', + port: '50051', + }, + }; + + return { + grpc, + }; +}; diff --git a/packages/apps/artusx-grpc/src/config/config.development.ts b/packages/apps/artusx-grpc/src/config/config.development.ts new file mode 100644 index 00000000..ff8b4c56 --- /dev/null +++ b/packages/apps/artusx-grpc/src/config/config.development.ts @@ -0,0 +1 @@ +export default {}; diff --git a/packages/apps/artusx-grpc/src/config/config.production.ts b/packages/apps/artusx-grpc/src/config/config.production.ts new file mode 100644 index 00000000..ff8b4c56 --- /dev/null +++ b/packages/apps/artusx-grpc/src/config/config.production.ts @@ -0,0 +1 @@ +export default {}; diff --git a/packages/apps/artusx-grpc/src/config/plugin.ts b/packages/apps/artusx-grpc/src/config/plugin.ts new file mode 100644 index 00000000..56f14a8e --- /dev/null +++ b/packages/apps/artusx-grpc/src/config/plugin.ts @@ -0,0 +1,6 @@ +export default { + grpc: { + enable: true, + package: '@artusx/plugin-grpc', + }, +}; diff --git a/packages/apps/artusx-grpc/src/index.ts b/packages/apps/artusx-grpc/src/index.ts new file mode 100644 index 00000000..dcbdcdc2 --- /dev/null +++ b/packages/apps/artusx-grpc/src/index.ts @@ -0,0 +1,3 @@ +import { main } from './bootstrap'; + +main(); diff --git a/packages/apps/artusx-grpc/src/lifecycle.ts b/packages/apps/artusx-grpc/src/lifecycle.ts new file mode 100644 index 00000000..898a5d26 --- /dev/null +++ b/packages/apps/artusx-grpc/src/lifecycle.ts @@ -0,0 +1,47 @@ +import assert from 'assert'; +import { Inject, ApplicationLifecycle, LifecycleHook, LifecycleHookUnit, ArtusInjectEnum } from '@artus/core'; +import { GRPCClient, GRPCConfig } from '@artusx/plugin-grpc'; +import { credentials } from '@artusx/plugin-grpc/types'; +import { ArtusXInjectEnum } from '@artusx/utils'; + +@LifecycleHookUnit() +export default class CustomLifecycle implements ApplicationLifecycle { + @Inject(ArtusInjectEnum.Config) + config: Record; + + @Inject(ArtusXInjectEnum.GRPC) + grpcClient: GRPCClient; + + @LifecycleHook() + async didReady() {} + + private async invoke() { + const config: GRPCConfig = this.config.grpc; + + assert(config?.server, 'config.server is required'); + const { host, port } = config?.server; + const serverUrl = `${host || 'localhost'}:${port}`; + + // greeter + const GreeterService = this.grpcClient.getService('helloworld', 'Greeter'); + assert(GreeterService, 'GreeterService is required'); + const GreeterClient = new GreeterService(serverUrl, credentials.createInsecure()); + GreeterClient.sayHello({ name: 'you' }, function (_err: Error, response: any) { + console.log('client:Greeting:sayHello', response); + }); + + // echo + const EchoService = this.grpcClient.getService('grpc.examples.echo', 'Echo'); + assert(EchoService, 'Service is required'); + const echoClient = new EchoService(serverUrl, credentials.createInsecure()); + + echoClient.UnaryEcho({ name: 'you' }, function (_err: Error, response: any) { + console.log('client:Echo:UnaryEcho', response); + }); + } + + @LifecycleHook() + public async willReady() { + await this.invoke(); + } +} diff --git a/packages/apps/artusx-grpc/src/protos/echo.proto b/packages/apps/artusx-grpc/src/protos/echo.proto new file mode 100644 index 00000000..4814217d --- /dev/null +++ b/packages/apps/artusx-grpc/src/protos/echo.proto @@ -0,0 +1,45 @@ +/* + * + * Copyright 2018 gRPC 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 + * + * http://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. + * + */ + + syntax = "proto3"; + + option go_package = "google.golang.org/grpc/examples/features/proto/echo"; + + package grpc.examples.echo; + + // EchoRequest is the request for echo. + message EchoRequest { + string message = 1; + } + + // EchoResponse is the response for echo. + message EchoResponse { + string message = 1; + } + + // Echo is the echo service. + service Echo { + // UnaryEcho is unary echo. + rpc UnaryEcho(EchoRequest) returns (EchoResponse) {} + // ServerStreamingEcho is server side streaming. + rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {} + // ClientStreamingEcho is client side streaming. + rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {} + // BidirectionalStreamingEcho is bidi streaming. + rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse) {} + } \ No newline at end of file diff --git a/packages/apps/artusx-grpc/src/protos/helloworld.proto b/packages/apps/artusx-grpc/src/protos/helloworld.proto new file mode 100644 index 00000000..501c543a --- /dev/null +++ b/packages/apps/artusx-grpc/src/protos/helloworld.proto @@ -0,0 +1,41 @@ +// Copyright 2015 gRPC 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 +// +// http://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. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} + + rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; + string from = 2; +} diff --git a/packages/apps/artusx-grpc/src/rpc/echo.ts b/packages/apps/artusx-grpc/src/rpc/echo.ts new file mode 100644 index 00000000..59467923 --- /dev/null +++ b/packages/apps/artusx-grpc/src/rpc/echo.ts @@ -0,0 +1,18 @@ +import { GRPC, GRPCHandler } from '@artusx/plugin-grpc'; + +@GRPC({ + packageName: 'grpc.examples.echo', + serviceName: 'Echo', +}) +export default class EchoService { + @GRPCHandler({ + enable: true, + }) + UnaryEcho(call: any, callback: any) { + console.log('server:Echo:UnaryEcho', call.request); + callback(null, { + message: 'Hello ' + call.request.name, + from: 'handler 222', + }); + } +} diff --git a/packages/apps/artusx-grpc/src/rpc/greeter.ts b/packages/apps/artusx-grpc/src/rpc/greeter.ts new file mode 100644 index 00000000..409956cb --- /dev/null +++ b/packages/apps/artusx-grpc/src/rpc/greeter.ts @@ -0,0 +1,18 @@ +import { GRPC, GRPCHandler } from '@artusx/plugin-grpc'; + +@GRPC({ + packageName: 'helloworld', + serviceName: 'Greeter', +}) +export default class GreeterService { + @GRPCHandler({ + enable: true, + }) + sayHello(call: any, callback: any) { + console.log('server:Greeting:sayHello', call.request); + callback(null, { + message: 'Hello ' + call.request.name, + from: 'handler 222', + }); + } +} diff --git a/packages/apps/artusx-grpc/src/rpc/greeter_second.ts b/packages/apps/artusx-grpc/src/rpc/greeter_second.ts new file mode 100644 index 00000000..6ab870a4 --- /dev/null +++ b/packages/apps/artusx-grpc/src/rpc/greeter_second.ts @@ -0,0 +1,18 @@ +import { GRPC, GRPCHandler } from '@artusx/plugin-grpc'; + +@GRPC({ + packageName: 'helloworld', + serviceName: 'Greeter', +}) +export default class GreeterSecondService { + @GRPCHandler({ + enable: true, + }) + sayBye(call: any, callback: any) { + console.log('server:Greeting:sayHello', call.request); + callback(null, { + message: 'Hello ' + call.request.name, + from: 'handler', + }); + } +} diff --git a/packages/apps/artusx-grpc/tsconfig.json b/packages/apps/artusx-grpc/tsconfig.json new file mode 100644 index 00000000..aa99379c --- /dev/null +++ b/packages/apps/artusx-grpc/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@artusx/tsconfig", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/libs/utils/src/constants.ts b/packages/libs/utils/src/constants.ts index bb1e615b..d14fe5f0 100644 --- a/packages/libs/utils/src/constants.ts +++ b/packages/libs/utils/src/constants.ts @@ -14,6 +14,7 @@ export enum ArtusXInjectEnum { Redis = 'ARTUSX_REDIS', Sequelize = 'ARTUSX_SEQUELIZE', + GRPC = 'ARTUSX_GRPC', PPTR = 'ARTUSX_PPTR', Proxy = 'ARTUSX_PROXY', OpenAI = 'ARTUS_OPENAI', diff --git a/packages/plugins/grpc/.eslintignore b/packages/plugins/grpc/.eslintignore new file mode 100644 index 00000000..42aa3099 --- /dev/null +++ b/packages/plugins/grpc/.eslintignore @@ -0,0 +1,4 @@ +node_modules +lib +dist +coverage \ No newline at end of file diff --git a/packages/plugins/grpc/.eslintrc b/packages/plugins/grpc/.eslintrc new file mode 100644 index 00000000..b29cd197 --- /dev/null +++ b/packages/plugins/grpc/.eslintrc @@ -0,0 +1,6 @@ +{ + "extends": ["@artusx/eslint-config"], + "parserOptions": { + "project": "./tsconfig.json" + } +} diff --git a/packages/plugins/grpc/.gitignore b/packages/plugins/grpc/.gitignore new file mode 100644 index 00000000..a65b4177 --- /dev/null +++ b/packages/plugins/grpc/.gitignore @@ -0,0 +1 @@ +lib diff --git a/packages/plugins/grpc/.npmrc b/packages/plugins/grpc/.npmrc new file mode 100644 index 00000000..862731de --- /dev/null +++ b/packages/plugins/grpc/.npmrc @@ -0,0 +1,3 @@ +registry=https://registry.npmjs.org/ +always-auth=false +strict-ssl=false diff --git a/packages/plugins/grpc/README.md b/packages/plugins/grpc/README.md new file mode 100644 index 00000000..abc14144 --- /dev/null +++ b/packages/plugins/grpc/README.md @@ -0,0 +1,138 @@ +# @artusx/plugin-grpc + +## Plugin + +```ts +export default { + grpc: { + enable: true, + package: '@artusx/plugin-grpc', + }, +}; +``` + +## Config + +```ts +export interface GRPCConfig { + protoList: string[]; + server?: { + host: string; + port: number; + }; +} +``` + +## Server + +GRPC Proto + +```proto +// Copyright 2015 gRPC 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 +// +// http://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. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} + + rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; + string from = 2; +} +``` + +GRPC Handler + +```ts +import { GRPC, GRPCHandler } from '@artusx/plugin-grpc'; + +@GRPC({ + packageName: 'helloworld', + serviceName: 'Greeter', +}) +export default class GreeterService { + @GRPCHandler({ + enable: true, + }) + sayHello(call: any, callback: any) { + console.log('server:Greeting:sayHello', call.request); + callback(null, { + message: 'Hello ' + call.request.name, + from: 'handler 222', + }); + } +} +``` + +## Client + +```ts +import assert from 'assert'; +import { Inject, ApplicationLifecycle, LifecycleHook, LifecycleHookUnit, ArtusInjectEnum } from '@artus/core'; +import { GRPCClient, GRPCConfig } from '@artusx/plugin-grpc'; +import { credentials } from '@artusx/plugin-grpc/types'; +import { ArtusXInjectEnum } from '@artusx/utils'; + +@LifecycleHookUnit() +export default class CustomLifecycle implements ApplicationLifecycle { + @Inject(ArtusInjectEnum.Config) + config: Record; + + @Inject(ArtusXInjectEnum.GRPC) + grpcClient: GRPCClient; + + @LifecycleHook() + async didReady() {} + + private async invoke() { + const config: GRPCConfig = this.config.grpc; + + assert(config?.server, 'config.server is required'); + const { host, port } = config?.server; + const serverUrl = `${host || 'localhost'}:${port}`; + + // greeter + const GreeterService = this.grpcClient.getService('helloworld', 'Greeter'); + assert(GreeterService, 'GreeterService is required'); + const GreeterClient = new GreeterService(serverUrl, credentials.createInsecure()); + GreeterClient.sayHello({ name: 'you' }, function (_err: Error, response: any) { + console.log('client:Greeting:sayHello', response); + }); + } + + @LifecycleHook() + public async willReady() { + await this.invoke(); + } +} +``` diff --git a/packages/plugins/grpc/jest.config.js b/packages/plugins/grpc/jest.config.js new file mode 100644 index 00000000..2361426b --- /dev/null +++ b/packages/plugins/grpc/jest.config.js @@ -0,0 +1,6 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverageFrom: ['/src/**/*.ts'], +}; diff --git a/packages/plugins/grpc/package.json b/packages/plugins/grpc/package.json new file mode 100644 index 00000000..828220db --- /dev/null +++ b/packages/plugins/grpc/package.json @@ -0,0 +1,72 @@ +{ + "name": "@artusx/plugin-grpc", + "version": "0.0.0-dev.0", + "description": "grpc plugin for artusx", + "keywords": [ + "artusx" + ], + "author": "Suyi ", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + }, + "./constants": { + "types": "./lib/constants.d.ts", + "default": "./lib/constants.js" + }, + "./types": { + "types": "./lib/types.d.ts", + "default": "./lib/types.js" + }, + "./client": { + "types": "./lib/client.d.ts", + "default": "./lib/client.js" + }, + "./lifecycle": { + "types": "./lib/lifecycle.d.ts", + "default": "./lib/lifecycle.js" + } + }, + "main": "lib/index.js", + "types": "lib/index.d.js", + "files": [ + "lib" + ], + "scripts": { + "build": "npm run tsc", + "cov": "jest --coverage --detectOpenHandles --testTimeout=15000", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", + "test": "jest --detectOpenHandles --testTimeout=15000", + "tsc": "rm -rf lib && tsc -p ./tsconfig.build.json" + }, + "dependencies": { + "@artus/core": "^2.x", + "@artus/pipeline": "^0.2", + "@grpc/grpc-js": "~1.10.3", + "@grpc/proto-loader": "~0.7.10", + "lodash.get": "~4.4.2" + }, + "devDependencies": { + "@artusx/eslint-config": "workspace:*", + "@artusx/tsconfig": "workspace:*", + "@types/jest": "~29.5.11", + "@types/lodash.get": "~4.4.9", + "@types/node": "^18.11.17", + "@typescript-eslint/eslint-plugin": "~6.19.1", + "eslint": "~8.56.0", + "eslint-plugin-import": "~2.29.1", + "jest": "~29.7.0", + "reflect-metadata": "~0.2.1", + "ts-jest": "~29.1.2", + "tslib": "^2.5.0", + "typescript": "^4.9.4" + }, + "peerDependencies": { + "reflect-metadata": "^0.1.13" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/plugins/grpc/src/client.ts b/packages/plugins/grpc/src/client.ts new file mode 100644 index 00000000..1d9999e5 --- /dev/null +++ b/packages/plugins/grpc/src/client.ts @@ -0,0 +1,112 @@ +import assert from 'assert'; +import get from 'lodash.get'; + +import { Injectable, ScopeEnum } from '@artus/core'; +import { ArtusXInjectEnum } from './constants'; +import * as grpc from '@grpc/grpc-js'; +import * as protoLoader from '@grpc/proto-loader'; + +import type { Server, GrpcObject } from '@grpc/grpc-js'; +import type { PackageDefinition } from '@grpc/proto-loader'; +import type { ArtusXGrpcServiceMap } from './types'; + +export interface GRPCConfig { + protoList: string[]; + server?: { + host: string; + port: number; + }; +} + +type InitServerCallback = (err: Error, port: number) => void; + +@Injectable({ + id: ArtusXInjectEnum.GRPC, + scope: ScopeEnum.SINGLETON, +}) +export default class GRPCClient { + private _server: Server; + private _config: GRPCConfig; + + private _packageObject: GrpcObject; + private _packageDefinition: PackageDefinition; + + get packageObject() { + return this._packageObject; + } + + get packageDefinition() { + return this._packageDefinition; + } + + get server() { + return this._server; + } + + getService(packageName: string, serviceName: string) { + const packageObject = this._packageObject; + if (!packageObject) { + return; + } + + const grpcObject = get(packageObject, packageName); + if (!grpcObject) { + return; + } + + return get(grpcObject, serviceName); + } + + async init(config: GRPCConfig) { + if (!config) { + return; + } + + const { protoList } = config; + const packageDefinition = protoLoader.loadSync(protoList, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true, + }); + + const packageObject = grpc.loadPackageDefinition(packageDefinition); + + this._config = config; + this._packageObject = packageObject; + this._packageDefinition = packageDefinition; + } + + async initServer(serviceMap: ArtusXGrpcServiceMap, callback: InitServerCallback = (_err, _port) => {}) { + assert(this._config?.server, 'server config is required'); + + const { host = '0.0.0.0', port = '50051' } = this._config?.server; + + const server = new grpc.Server(); + const serverUrl = `${host}:${port}`; + + for (const serviceIndex of Object.keys(serviceMap)) { + const [packageName, serviceName] = serviceIndex.split(':'); + if (!packageName || !serviceName) { + continue; + } + + const service = this.getService(packageName, serviceName); + + if (!service) { + continue; + } + + const handler = serviceMap[serviceIndex]; + + server.addService(service.service, handler); + } + + server.bindAsync(serverUrl, grpc.ServerCredentials.createInsecure(), callback); + + this._server = server; + } +} + +export { GRPCClient }; diff --git a/packages/plugins/grpc/src/config/config.default.ts b/packages/plugins/grpc/src/config/config.default.ts new file mode 100644 index 00000000..ff8b4c56 --- /dev/null +++ b/packages/plugins/grpc/src/config/config.default.ts @@ -0,0 +1 @@ +export default {}; diff --git a/packages/plugins/grpc/src/constants.ts b/packages/plugins/grpc/src/constants.ts new file mode 100644 index 00000000..85ec705f --- /dev/null +++ b/packages/plugins/grpc/src/constants.ts @@ -0,0 +1,3 @@ +export enum ArtusXInjectEnum { + GRPC = 'ARTUSX_GRPC', +} diff --git a/packages/plugins/grpc/src/decorator.ts b/packages/plugins/grpc/src/decorator.ts new file mode 100644 index 00000000..3ebaa97a --- /dev/null +++ b/packages/plugins/grpc/src/decorator.ts @@ -0,0 +1,32 @@ +import { addTag, Injectable, ScopeEnum } from '@artus/core'; + +export const GRPC_SERVICE_TAG = 'GRPC_SERVICE_TAG'; +export const GRPC_SERVICE_METADATA = Symbol.for('GRPC_SERVICE_METADATA'); +export const GRPC_HANDLER_METADATA = Symbol.for('GRPC_METHOD_METADATA'); + +import { ArtusxGrpcServiceMetadata, ArtusxGrpcHandlerMetadata } from './types'; + +/** + * GRPC service decorator + * @param options GRPCServiceOptions + * @example @GRPC() + * @returns void + */ +export function GRPC(options: ArtusxGrpcServiceMetadata) { + return (target: any) => { + const grpcServiceMetadata = options; + + Reflect.defineMetadata(GRPC_SERVICE_METADATA, grpcServiceMetadata, target); + addTag(GRPC_SERVICE_TAG, target); + Injectable({ scope: ScopeEnum.EXECUTION })(target); + }; +} + +export function GRPCHandler(options?: ArtusxGrpcHandlerMetadata) { + return (_target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor) => { + const grpcHandlerMetadata = options || {}; + + Reflect.defineMetadata(GRPC_HANDLER_METADATA, grpcHandlerMetadata, descriptor.value); + return descriptor; + }; +} diff --git a/packages/plugins/grpc/src/index.ts b/packages/plugins/grpc/src/index.ts new file mode 100644 index 00000000..c82ced3e --- /dev/null +++ b/packages/plugins/grpc/src/index.ts @@ -0,0 +1,3 @@ +export * from './client'; +export * from './lifecycle'; +export * from './decorator'; diff --git a/packages/plugins/grpc/src/lifecycle.ts b/packages/plugins/grpc/src/lifecycle.ts new file mode 100644 index 00000000..1b665985 --- /dev/null +++ b/packages/plugins/grpc/src/lifecycle.ts @@ -0,0 +1,95 @@ +import assert from 'assert'; +import { + ApplicationLifecycle, + ArtusApplication, + Inject, + ArtusInjectEnum, + LifecycleHookUnit, + LifecycleHook, +} from '@artus/core'; +import { ArtusXInjectEnum } from './constants'; +import GRPCClient, { GRPCConfig } from './client'; +import { GRPC_SERVICE_METADATA, GRPC_SERVICE_TAG, GRPC_HANDLER_METADATA } from './decorator'; +import { + ArtusxGrpcHandlerMetadata, + ArtusxGrpcServiceMetadata, + ArtusXGrpcHandleMap, + ArtusXGrpcServiceMap, +} from './types'; + +@LifecycleHookUnit() +export default class GRPCLifecycle implements ApplicationLifecycle { + @Inject(ArtusInjectEnum.Application) + app: ArtusApplication; + + get logger() { + return this.app.logger; + } + + get container() { + return this.app.container; + } + + private async loadService() { + const serviceClazzList = this.container.getInjectableByTag(GRPC_SERVICE_TAG); + + let serviceMap: ArtusXGrpcServiceMap = {}; + + for (const serviceClazz of serviceClazzList) { + const serviceMetadata: ArtusxGrpcServiceMetadata = Reflect.getMetadata( + GRPC_SERVICE_METADATA, + serviceClazz + ); + const service = this.container.get(serviceClazz) as any; + const serviceDescriptorList = Object.getOwnPropertyDescriptors(serviceClazz.prototype); + + const { packageName, serviceName } = serviceMetadata; + const serviceIndex = `${packageName}:${serviceName}`; + + let handlerMap: ArtusXGrpcHandleMap = {}; + for (const key of Object.keys(serviceDescriptorList)) { + const serviceDescriptor = serviceDescriptorList[key]; + if (!serviceDescriptor.value) { + continue; + } + + const handlerMetadata: ArtusxGrpcHandlerMetadata = + Reflect.getMetadata(GRPC_HANDLER_METADATA, serviceDescriptor.value) ?? undefined; + + if (!handlerMetadata || !handlerMetadata?.enable) { + continue; + } + + handlerMap[key] = service[key].bind(service); + } + + serviceMap[serviceIndex] = { + ...serviceMap[serviceIndex], + ...handlerMap, + }; + } + + return serviceMap; + } + + @LifecycleHook() + async willReady() { + const config: GRPCConfig = this.app.config.grpc; + + assert(config, 'grpc config is required'); + assert(config.protoList, 'grpc config.protoList is required'); + + const client = this.app.container.get(ArtusXInjectEnum.GRPC) as GRPCClient; + await client.init(config); + + if (config.server) { + const serviceMap = await this.loadService(); + await client.initServer(serviceMap, (err, port) => { + if (err != null) { + return console.error(err); + } + this.logger.info(`[gRPC] listening on ${port}`); + }); + } + } +} diff --git a/packages/plugins/grpc/src/meta.json b/packages/plugins/grpc/src/meta.json new file mode 100644 index 00000000..65f9815a --- /dev/null +++ b/packages/plugins/grpc/src/meta.json @@ -0,0 +1,3 @@ +{ + "name": "grpc" +} diff --git a/packages/plugins/grpc/src/types.ts b/packages/plugins/grpc/src/types.ts new file mode 100644 index 00000000..898242a5 --- /dev/null +++ b/packages/plugins/grpc/src/types.ts @@ -0,0 +1,15 @@ +import { UntypedServiceImplementation } from '@grpc/grpc-js'; + +export * from '@grpc/grpc-js'; + +export interface ArtusxGrpcServiceMetadata { + packageName: string; + serviceName: string; +} + +export interface ArtusxGrpcHandlerMetadata { + enable: boolean; +} + +export type ArtusXGrpcHandleMap = UntypedServiceImplementation; +export type ArtusXGrpcServiceMap = Record; diff --git a/packages/plugins/grpc/tsconfig.build.json b/packages/plugins/grpc/tsconfig.build.json new file mode 100644 index 00000000..57934f70 --- /dev/null +++ b/packages/plugins/grpc/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*.ts", "src/**/*.json"] +} diff --git a/packages/plugins/grpc/tsconfig.json b/packages/plugins/grpc/tsconfig.json new file mode 100644 index 00000000..671eae1a --- /dev/null +++ b/packages/plugins/grpc/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@artusx/tsconfig", + "compilerOptions": { + "baseUrl": ".", + "types": ["node", "jest", "reflect-metadata"], + "outDir": "lib" + }, + "include": ["src/**/*.ts", "src/**/*.json", "test/**/*.ts"] +} diff --git a/packages/plugins/koa/src/lifecycle.ts b/packages/plugins/koa/src/lifecycle.ts index 7f459ba3..6b85a8a3 100644 --- a/packages/plugins/koa/src/lifecycle.ts +++ b/packages/plugins/koa/src/lifecycle.ts @@ -109,22 +109,21 @@ export default class ApplicationHttpLifecycle implements ApplicationLifecycle { for (const controllerClazz of controllerClazzList) { const controllerMetadata = Reflect.getMetadata(CLASS_CONTROLLER_METADATA, controllerClazz); const controller = this.container.get(controllerClazz) as any; + const controllerDescriptorList = Object.getOwnPropertyDescriptors(controllerClazz.prototype); - const handlerDescriptorList = Object.getOwnPropertyDescriptors(controllerClazz.prototype); - - for (const key of Object.keys(handlerDescriptorList)) { - const handlerDescriptor = handlerDescriptorList[key]; + for (const key of Object.keys(controllerDescriptorList)) { + const controllerDescriptor = controllerDescriptorList[key]; // skip getter/setter - if (!handlerDescriptor.value) { + if (!controllerDescriptor.value) { continue; } const routeMetadataList: HTTPRouteMetadata[] = - Reflect.getMetadata(HTTP_ROUTER_METADATA, handlerDescriptor.value) ?? []; + Reflect.getMetadata(HTTP_ROUTER_METADATA, controllerDescriptor.value) ?? []; const middlewareMetadata: HTTPMiddlewareMetadata = - Reflect.getMetadata(HTTP_MIDDLEWARE_METADATA, handlerDescriptor.value) ?? []; + Reflect.getMetadata(HTTP_MIDDLEWARE_METADATA, controllerDescriptor.value) ?? []; if (routeMetadataList.length === 0) continue; diff --git a/rush.json b/rush.json index 414876cc..130e1dab 100644 --- a/rush.json +++ b/rush.json @@ -506,6 +506,11 @@ "projectFolder": "packages/apps/artusx-koa-bench", "tags": ["artusx-apps"] }, + { + "packageName": "artusx-grpc", + "projectFolder": "packages/apps/artusx-grpc", + "tags": ["artusx-apps"] + }, // plugins { "packageName": "@artusx/plugin-redis", @@ -597,6 +602,13 @@ "tags": ["artusx-plugins"], "shouldPublish": true, "versionPolicyName": "public" + }, + { + "packageName": "@artusx/plugin-grpc", + "projectFolder": "packages/plugins/grpc", + "tags": ["artusx-plugins"], + "shouldPublish": true, + "versionPolicyName": "public" } ] }