From 216039b4e2088c8bfdf7dfc49f095f3e187cce88 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Tue, 19 Dec 2023 00:21:53 +0800 Subject: [PATCH 01/30] feat: refactor init --- .github/dependabot.yml | 8 + .github/workflows/{tests.yml => build.yml} | 2 +- .github/workflows/publish.yml | 35 + README.md | 2 +- README_CN.md | 2 +- jest.config.ts | 16 + package.json | 48 +- pnpm-lock.yaml | 2475 +++++++++++++++-- src/config/defaultChannelOptions.ts | 23 - src/config/defaultLoadOptions.ts | 11 - src/index.ts | 238 -- src/proxy/clientProxy.ts | 297 -- src/proxy/serverProxy.ts | 291 -- src/schema/loader.ts | 37 - src/schema/server.ts | 13 - src/util/compose.ts | 35 - src/util/iterator.ts | 187 -- src/util/prefixingDefinition.ts | 20 - test/benchmark/README.md | 109 - test/benchmark/benchmark.sh | 15 - test/benchmark/helloworld.proto | 15 - test/benchmark/server-grpcity.js | 23 - test/benchmark/server-grpcjs.js | 30 - test/certs/ca.crt | 30 - test/certs/ca.key | 54 - test/certs/client.crt | 30 - test/certs/client.csr | 27 - test/certs/client.key | 51 - test/certs/server.crt | 30 - test/certs/server.csr | 27 - test/certs/server.key | 51 - test/client.js | 75 - test/index.test.js | 199 -- test/protos/test/helloworld/helloworld.proto | 21 - .../test/helloworld/model/message.proto | 14 - test/script/genCert.sh | 45 - test/server.js | 120 - test/stream/client-v2.js | 66 - test/stream/client.js | 78 - test/stream/server-v2.js | 79 - test/stream/server.js | 83 - test/stream/stream.proto | 14 - tsconfig.cjs.json | 9 + tsconfig.esm.json | 8 + tsconfig.json | 19 +- 45 files changed, 2328 insertions(+), 2734 deletions(-) create mode 100644 .github/dependabot.yml rename .github/workflows/{tests.yml => build.yml} (93%) create mode 100644 .github/workflows/publish.yml create mode 100644 jest.config.ts delete mode 100644 src/config/defaultChannelOptions.ts delete mode 100644 src/config/defaultLoadOptions.ts delete mode 100644 src/proxy/clientProxy.ts delete mode 100644 src/proxy/serverProxy.ts delete mode 100644 src/schema/loader.ts delete mode 100644 src/schema/server.ts delete mode 100644 src/util/compose.ts delete mode 100644 src/util/iterator.ts delete mode 100644 src/util/prefixingDefinition.ts delete mode 100644 test/benchmark/README.md delete mode 100644 test/benchmark/benchmark.sh delete mode 100644 test/benchmark/helloworld.proto delete mode 100755 test/benchmark/server-grpcity.js delete mode 100644 test/benchmark/server-grpcjs.js delete mode 100644 test/certs/ca.crt delete mode 100644 test/certs/ca.key delete mode 100644 test/certs/client.crt delete mode 100644 test/certs/client.csr delete mode 100644 test/certs/client.key delete mode 100644 test/certs/server.crt delete mode 100644 test/certs/server.csr delete mode 100644 test/certs/server.key delete mode 100755 test/client.js delete mode 100755 test/index.test.js delete mode 100755 test/protos/test/helloworld/helloworld.proto delete mode 100755 test/protos/test/helloworld/model/message.proto delete mode 100644 test/script/genCert.sh delete mode 100755 test/server.js delete mode 100644 test/stream/client-v2.js delete mode 100644 test/stream/client.js delete mode 100644 test/stream/server-v2.js delete mode 100644 test/stream/server.js delete mode 100644 test/stream/stream.proto create mode 100644 tsconfig.cjs.json create mode 100644 tsconfig.esm.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ad1abbd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: monthly + open-pull-requests-limit: 15 + versioning-strategy: widen diff --git a/.github/workflows/tests.yml b/.github/workflows/build.yml similarity index 93% rename from .github/workflows/tests.yml rename to .github/workflows/build.yml index af968dd..d1694e7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: node-version: [16.x, 18.x, 20.x] - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest, macos-latest] steps: - name: Checkout master diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..f65ed04 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,35 @@ +name: publish + +on: + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout master + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' + cache: 'pnpm' + + - name: Install Dependencies + run: pnpm i + + - name: Build + run: pnpm build + + - name: Publish + run: pnpm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index 1ce742d..4320533 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# gRPCity ![build-status](https://github.com/chakhsu/grpcity/actions/workflows/tests.yml/badge.svg) ![npm](https://img.shields.io/npm/v/grpcity) ![license](https://img.shields.io/npm/l/grpcity) ![code-style](https://img.shields.io/badge/code_style-standard-brightgreen.svg) +# gRPCity ![build-status](https://github.com/chakhsu/grpcity/actions/workflows/build.yml/badge.svg) ![npm](https://img.shields.io/npm/v/grpcity) ![license](https://img.shields.io/npm/l/grpcity) ![code-style](https://img.shields.io/badge/code_style-standard-brightgreen.svg) [English](./README.md) | [简体中文](./README_CN.md) diff --git a/README_CN.md b/README_CN.md index 7d2d5ad..7e2bfaa 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,4 +1,4 @@ -# gRPCity ![build-status](https://github.com/chakhsu/grpcity/actions/workflows/tests.yml/badge.svg) ![npm](https://img.shields.io/npm/v/grpcity) ![license](https://img.shields.io/npm/l/grpcity) ![code-style](https://img.shields.io/badge/code_style-standard-brightgreen.svg) +# gRPCity ![build-status](https://github.com/chakhsu/grpcity/actions/workflows/build.yml/badge.svg) ![npm](https://img.shields.io/npm/v/grpcity) ![license](https://img.shields.io/npm/l/grpcity) ![code-style](https://img.shields.io/badge/code_style-standard-brightgreen.svg) [English](./README.md) | [简体中文](./README_CN.md) diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000..d150770 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,16 @@ +import type { Config } from 'jest' + +const config: Config = { + roots: ['test'], + testPathIgnorePatterns: ['node_modules', 'lib'], + preset: 'ts-jest', + testEnvironment: 'node', + testTimeout: 15000, + reporters: ['default', 'github-actions'], + collectCoverage: true, + coverageDirectory: 'coverage', + coverageReporters: ['lcov', 'text'], + coveragePathIgnorePatterns: ['node_modules', 'lib'] +} + +export default config diff --git a/package.json b/package.json index 44d54a1..c11e881 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grpcity", - "version": "1.3.1", + "version": "2.0.0", "description": "A powerful and complete gRPC framework for Node.js", "author": "Chakhsu.Lau", "license": "MIT", @@ -8,6 +8,7 @@ "grpcity", "grpc.js", "grpc", + "protobuf", "microservice", "framework", "typescript" @@ -20,8 +21,17 @@ "bugs": { "url": "https://github.com/chakhsu/grpcity/issues" }, - "main": "lib/index.js", - "types": "lib/index.d.ts", + "exports": { + ".": { + "types": "./lib/types/index.d.ts", + "import": "./lib/mjs/index.js", + "require": "./lib/esm/index.js" + }, + "./package.json": "./package.json" + }, + "main": "lib/cjs/index.js", + "module": "lib/esm/index.js", + "types": "lib/types/index.d.ts", "files": [ "lib", "README_CN.md" @@ -30,33 +40,39 @@ "node": ">=16" }, "scripts": { - "clear": "rimraf lib", - "test": "mocha --exit --extension .test.js --recursive test", - "build": "pnpm clear && tsc -P tsconfig.json", + "clear": "rimraf lib && rimraf coverage", + "patch:cjs-type": "echo '{ \"type\": \"commonjs\" }' >> lib/cjs/package.json", + "build:cjs": "tsc -P tsconfig.cjs.json && pnpm patch:cjs-type", + "patch:esm-type": "echo '{ \"type\": \"module\" }' >> lib/esm/package.json", + "patch:esm-js": "tsc-esm-fix --tsconfig=tsconfig.esm.json", + "build:esm": "tsc -P tsconfig.esm.json && pnpm patch:esm-js && pnpm patch:esm-type", + "build": "pnpm clear && pnpm build:cjs && pnpm build:esm", "lint:prettier": "prettier --cache --check --ignore-path .gitignore --ignore-path .prettierignore .", "prettier": "pnpm lint:prettier --write", - "prepare": "husky install" + "prepare": "husky install", + "test": "jest" }, "lint-staged": { "*.{ts,js,md,json,yaml}": "prettier --write" }, "dependencies": { - "@grpc/grpc-js": "^1.9.12", + "@grpc/grpc-js": "^1.9.13", "@grpc/proto-loader": "^0.7.10", - "joi": "^17.11.0", "lodash": "^4.17.21", "protobufjs": "^7.2.5" }, "devDependencies": { - "@tsconfig/node14": "^14.1.0", + "@types/jest": "^29.5.11", "@types/lodash": "^4.14.202", - "@types/node": "^20.10.2", - "chai": "^4.3.10", + "@types/node": "^20.10.5", "husky": "^8.0.3", - "lint-staged": "^15.1.0", - "mocha": "^10.2.0", - "prettier": "3.1.0", + "jest": "^29.7.0", + "lint-staged": "^15.2.0", + "prettier": "3.1.1", "rimraf": "^5.0.5", - "typescript": "^5.3.2" + "ts-jest": "^29.1.1", + "ts-node": "^10.9.2", + "tsc-esm-fix": "^2.20.18", + "typescript": "^5.3.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0568086..c777d2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@grpc/grpc-js': - specifier: ^1.9.12 - version: 1.9.12 + specifier: ^1.9.13 + version: 1.9.13 '@grpc/proto-loader': specifier: ^0.7.10 version: 0.7.10 @@ -25,41 +25,394 @@ devDependencies: '@tsconfig/node14': specifier: ^14.1.0 version: 14.1.0 + '@types/jest': + specifier: ^29.5.11 + version: 29.5.11 '@types/lodash': specifier: ^4.14.202 version: 4.14.202 '@types/node': - specifier: ^20.10.2 - version: 20.10.2 - chai: - specifier: ^4.3.10 - version: 4.3.10 + specifier: ^20.10.5 + version: 20.10.5 husky: specifier: ^8.0.3 version: 8.0.3 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) lint-staged: - specifier: ^15.1.0 - version: 15.1.0 - mocha: - specifier: ^10.2.0 - version: 10.2.0 + specifier: ^15.2.0 + version: 15.2.0 prettier: - specifier: 3.1.0 - version: 3.1.0 + specifier: 3.1.1 + version: 3.1.1 rimraf: specifier: ^5.0.5 version: 5.0.5 + ts-jest: + specifier: ^29.1.1 + version: 29.1.1(@babel/core@7.23.6)(jest@29.7.0)(typescript@5.3.3) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.10.5)(typescript@5.3.3) + tsc-esm-fix: + specifier: ^2.20.18 + version: 2.20.18 typescript: - specifier: ^5.3.2 - version: 5.3.2 + specifier: ^5.3.3 + version: 5.3.3 packages: - /@grpc/grpc-js@1.9.12: - resolution: { integrity: sha512-Um5MBuge32TS3lAKX02PGCnFM4xPT996yLgZNb5H03pn6NyJ4Iwn5YcPq6Jj9yxGRk7WOgaZFtVRH5iTdYBeUg== } + /@ampproject/remapping@2.2.1: + resolution: { integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== } + engines: { node: '>=6.0.0' } + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + dev: true + + /@babel/code-frame@7.23.5: + resolution: { integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/highlight': 7.23.4 + chalk: 2.4.2 + dev: true + + /@babel/compat-data@7.23.5: + resolution: { integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== } + engines: { node: '>=6.9.0' } + dev: true + + /@babel/core@7.23.6: + resolution: { integrity: sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw== } + engines: { node: '>=6.9.0' } + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.23.5 + '@babel/generator': 7.23.6 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.6) + '@babel/helpers': 7.23.6 + '@babel/parser': 7.23.6 + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.6 + '@babel/types': 7.23.6 + convert-source-map: 2.0.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/generator@7.23.6: + resolution: { integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/types': 7.23.6 + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + jsesc: 2.5.2 + dev: true + + /@babel/helper-compilation-targets@7.23.6: + resolution: { integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/compat-data': 7.23.5 + '@babel/helper-validator-option': 7.23.5 + browserslist: 4.22.2 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: true + + /@babel/helper-environment-visitor@7.22.20: + resolution: { integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== } + engines: { node: '>=6.9.0' } + dev: true + + /@babel/helper-function-name@7.23.0: + resolution: { integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/template': 7.22.15 + '@babel/types': 7.23.6 + dev: true + + /@babel/helper-hoist-variables@7.22.5: + resolution: { integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/types': 7.23.6 + dev: true + + /@babel/helper-module-imports@7.22.15: + resolution: { integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/types': 7.23.6 + dev: true + + /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.6): + resolution: { integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.20 + dev: true + + /@babel/helper-plugin-utils@7.22.5: + resolution: { integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== } + engines: { node: '>=6.9.0' } + dev: true + + /@babel/helper-simple-access@7.22.5: + resolution: { integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/types': 7.23.6 + dev: true + + /@babel/helper-split-export-declaration@7.22.6: + resolution: { integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/types': 7.23.6 + dev: true + + /@babel/helper-string-parser@7.23.4: + resolution: { integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== } + engines: { node: '>=6.9.0' } + dev: true + + /@babel/helper-validator-identifier@7.22.20: + resolution: { integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== } + engines: { node: '>=6.9.0' } + dev: true + + /@babel/helper-validator-option@7.23.5: + resolution: { integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== } + engines: { node: '>=6.9.0' } + dev: true + + /@babel/helpers@7.23.6: + resolution: { integrity: sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.6 + '@babel/types': 7.23.6 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/highlight@7.23.4: + resolution: { integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/helper-validator-identifier': 7.22.20 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: true + + /@babel/parser@7.23.6: + resolution: { integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== } + engines: { node: '>=6.0.0' } + hasBin: true + dependencies: + '@babel/types': 7.23.6 + dev: true + + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.6): + resolution: { integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.6): + resolution: { integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.6): + resolution: { integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.6): + resolution: { integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.6): + resolution: { integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.6): + resolution: { integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.6): + resolution: { integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.6): + resolution: { integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.6): + resolution: { integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.6): + resolution: { integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.6): + resolution: { integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.6): + resolution: { integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.6): + resolution: { integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.6): + resolution: { integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ== } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/template@7.22.15: + resolution: { integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/code-frame': 7.23.5 + '@babel/parser': 7.23.6 + '@babel/types': 7.23.6 + dev: true + + /@babel/traverse@7.23.6: + resolution: { integrity: sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/code-frame': 7.23.5 + '@babel/generator': 7.23.6 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.23.6 + '@babel/types': 7.23.6 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/types@7.23.6: + resolution: { integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== } + engines: { node: '>=6.9.0' } + dependencies: + '@babel/helper-string-parser': 7.23.4 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 + dev: true + + /@bcoe/v8-coverage@0.2.3: + resolution: { integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== } + dev: true + + /@cspotcode/source-map-support@0.8.1: + resolution: { integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== } + engines: { node: '>=12' } + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + + /@grpc/grpc-js@1.9.13: + resolution: { integrity: sha512-OEZZu9v9AA+7/tghMDE8o5DAMD5THVnwSqDWuh7PPYO5287rTyqy0xEHT6/e4pbqSrhyLPdQFsam4TwFQVVIIw== } engines: { node: ^8.13.0 || >=10.10.0 } dependencies: '@grpc/proto-loader': 0.7.10 - '@types/node': 20.10.2 + '@types/node': 20.10.5 dev: false /@grpc/proto-loader@0.7.10: @@ -95,6 +448,294 @@ packages: wrap-ansi-cjs: /wrap-ansi@7.0.0 dev: true + /@istanbuljs/load-nyc-config@1.1.0: + resolution: { integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== } + engines: { node: '>=8' } + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + dev: true + + /@istanbuljs/schema@0.1.3: + resolution: { integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== } + engines: { node: '>=8' } + dev: true + + /@jest/console@29.7.0: + resolution: { integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + dev: true + + /@jest/core@29.7.0(ts-node@10.9.2): + resolution: { integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /@jest/environment@29.7.0: + resolution: { integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + jest-mock: 29.7.0 + dev: true + + /@jest/expect-utils@29.7.0: + resolution: { integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + jest-get-type: 29.6.3 + dev: true + + /@jest/expect@29.7.0: + resolution: { integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/fake-timers@29.7.0: + resolution: { integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.10.5 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: true + + /@jest/globals@29.7.0: + resolution: { integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/reporters@29.7.0: + resolution: { integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.20 + '@types/node': 20.10.5 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.0 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.1 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.6 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/schemas@29.6.3: + resolution: { integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jest/source-map@29.6.3: + resolution: { integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jridgewell/trace-mapping': 0.3.20 + callsites: 3.1.0 + graceful-fs: 4.2.11 + dev: true + + /@jest/test-result@29.7.0: + resolution: { integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + dev: true + + /@jest/test-sequencer@29.7.0: + resolution: { integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + dev: true + + /@jest/transform@29.7.0: + resolution: { integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@babel/core': 7.23.6 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.20 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.5 + pirates: 4.0.6 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/types@29.6.3: + resolution: { integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.10.5 + '@types/yargs': 17.0.32 + chalk: 4.1.2 + dev: true + + /@jridgewell/gen-mapping@0.3.3: + resolution: { integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== } + engines: { node: '>=6.0.0' } + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.20 + dev: true + + /@jridgewell/resolve-uri@3.1.1: + resolution: { integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== } + engines: { node: '>=6.0.0' } + dev: true + + /@jridgewell/set-array@1.1.2: + resolution: { integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== } + engines: { node: '>=6.0.0' } + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: { integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== } + dev: true + + /@jridgewell/trace-mapping@0.3.20: + resolution: { integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== } + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@jridgewell/trace-mapping@0.3.9: + resolution: { integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== } + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: { integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== } + engines: { node: '>= 8' } + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: { integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== } + engines: { node: '>= 8' } + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: { integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== } + engines: { node: '>= 8' } + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.16.0 + dev: true + /@pkgjs/parseargs@0.11.0: resolution: { integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== } engines: { node: '>=14' } @@ -159,29 +800,146 @@ packages: resolution: { integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== } dev: false + /@sinclair/typebox@0.27.8: + resolution: { integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== } + dev: true + + /@sinonjs/commons@3.0.0: + resolution: { integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== } + dependencies: + type-detect: 4.0.8 + dev: true + + /@sinonjs/fake-timers@10.3.0: + resolution: { integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== } + dependencies: + '@sinonjs/commons': 3.0.0 + dev: true + + /@tsconfig/node10@1.0.9: + resolution: { integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== } + dev: true + + /@tsconfig/node12@1.0.11: + resolution: { integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== } + dev: true + + /@tsconfig/node14@1.0.3: + resolution: { integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== } + dev: true + /@tsconfig/node14@14.1.0: resolution: { integrity: sha512-VmsCG04YR58ciHBeJKBDNMWWfYbyP8FekWVuTlpstaUPlat1D0x/tXzkWP7yCMU0eSz9V4OZU0LBWTFJ3xZf6w== } dev: true + /@tsconfig/node16@1.0.4: + resolution: { integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== } + dev: true + + /@types/babel__core@7.20.5: + resolution: { integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== } + dependencies: + '@babel/parser': 7.23.6 + '@babel/types': 7.23.6 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.4 + dev: true + + /@types/babel__generator@7.6.8: + resolution: { integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== } + dependencies: + '@babel/types': 7.23.6 + dev: true + + /@types/babel__template@7.4.4: + resolution: { integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== } + dependencies: + '@babel/parser': 7.23.6 + '@babel/types': 7.23.6 + dev: true + + /@types/babel__traverse@7.20.4: + resolution: { integrity: sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA== } + dependencies: + '@babel/types': 7.23.6 + dev: true + + /@types/graceful-fs@4.1.9: + resolution: { integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== } + dependencies: + '@types/node': 20.10.5 + dev: true + + /@types/istanbul-lib-coverage@2.0.6: + resolution: { integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== } + dev: true + + /@types/istanbul-lib-report@3.0.3: + resolution: { integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== } + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + dev: true + + /@types/istanbul-reports@3.0.4: + resolution: { integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== } + dependencies: + '@types/istanbul-lib-report': 3.0.3 + dev: true + + /@types/jest@29.5.11: + resolution: { integrity: sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== } + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + dev: true + /@types/lodash@4.14.202: resolution: { integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== } dev: true - /@types/node@20.10.2: - resolution: { integrity: sha512-37MXfxkb0vuIlRKHNxwCkb60PNBpR94u4efQuN4JgIAm66zfCDXGSAFCef9XUWFovX2R1ok6Z7MHhtdVXXkkIw== } + /@types/node@20.10.5: + resolution: { integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== } dependencies: undici-types: 5.26.5 - /ansi-colors@4.1.1: - resolution: { integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== } - engines: { node: '>=6' } + /@types/stack-utils@2.0.3: + resolution: { integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== } dev: true - /ansi-escapes@5.0.0: - resolution: { integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA== } - engines: { node: '>=12' } + /@types/yargs-parser@21.0.3: + resolution: { integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== } + dev: true + + /@types/yargs@17.0.32: + resolution: { integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== } + dependencies: + '@types/yargs-parser': 21.0.3 + dev: true + + /acorn-walk@8.3.1: + resolution: { integrity: sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw== } + engines: { node: '>=0.4.0' } + dev: true + + /acorn@8.11.2: + resolution: { integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== } + engines: { node: '>=0.4.0' } + hasBin: true + dev: true + + /ansi-escapes@4.3.2: + resolution: { integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== } + engines: { node: '>=8' } dependencies: - type-fest: 1.4.0 + type-fest: 0.21.3 + dev: true + + /ansi-escapes@6.2.0: + resolution: { integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw== } + engines: { node: '>=14.16' } + dependencies: + type-fest: 3.13.1 dev: true /ansi-regex@5.0.1: @@ -193,12 +951,24 @@ packages: engines: { node: '>=12' } dev: true + /ansi-styles@3.2.1: + resolution: { integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== } + engines: { node: '>=4' } + dependencies: + color-convert: 1.9.3 + dev: true + /ansi-styles@4.3.0: resolution: { integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== } engines: { node: '>=8' } dependencies: color-convert: 2.0.1 + /ansi-styles@5.2.0: + resolution: { integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== } + engines: { node: '>=10' } + dev: true + /ansi-styles@6.2.1: resolution: { integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== } engines: { node: '>=12' } @@ -212,21 +982,90 @@ packages: picomatch: 2.3.1 dev: true - /argparse@2.0.1: - resolution: { integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== } + /arg@4.1.3: + resolution: { integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== } dev: true - /assertion-error@1.1.0: - resolution: { integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== } + /argparse@1.0.10: + resolution: { integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== } + dependencies: + sprintf-js: 1.0.3 dev: true - /balanced-match@1.0.2: - resolution: { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } + /babel-jest@29.7.0(@babel/core@7.23.6): + resolution: { integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@babel/core': 7.23.6 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.23.6) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color dev: true - /binary-extensions@2.2.0: - resolution: { integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== } + /babel-plugin-istanbul@6.1.1: + resolution: { integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== } engines: { node: '>=8' } + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-jest-hoist@29.6.3: + resolution: { integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@babel/template': 7.22.15 + '@babel/types': 7.23.6 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.4 + dev: true + + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.6): + resolution: { integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== } + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.6 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.6) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.23.6) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.6) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.6) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.6) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.6) + dev: true + + /babel-preset-jest@29.6.3(@babel/core@7.23.6): + resolution: { integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.6 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.6) + dev: true + + /balanced-match@1.0.2: + resolution: { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } dev: true /brace-expansion@1.1.11: @@ -249,8 +1088,42 @@ packages: fill-range: 7.0.1 dev: true - /browser-stdout@1.3.1: - resolution: { integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== } + /browserslist@4.22.2: + resolution: { integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + dependencies: + caniuse-lite: 1.0.30001570 + electron-to-chromium: 1.4.614 + node-releases: 2.0.14 + update-browserslist-db: 1.0.13(browserslist@4.22.2) + dev: true + + /bs-logger@0.2.6: + resolution: { integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== } + engines: { node: '>= 6' } + dependencies: + fast-json-stable-stringify: 2.1.0 + dev: true + + /bser@2.1.1: + resolution: { integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== } + dependencies: + node-int64: 0.4.0 + dev: true + + /buffer-from@1.1.2: + resolution: { integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== } + dev: true + + /callsites@3.1.0: + resolution: { integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } + engines: { node: '>=6' } + dev: true + + /camelcase@5.3.1: + resolution: { integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== } + engines: { node: '>=6' } dev: true /camelcase@6.3.0: @@ -258,17 +1131,17 @@ packages: engines: { node: '>=10' } dev: true - /chai@4.3.10: - resolution: { integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== } + /caniuse-lite@1.0.30001570: + resolution: { integrity: sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== } + dev: true + + /chalk@2.4.2: + resolution: { integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== } engines: { node: '>=4' } dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.3 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 dev: true /chalk@4.1.2: @@ -284,25 +1157,18 @@ packages: engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } dev: true - /check-error@1.0.3: - resolution: { integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== } - dependencies: - get-func-name: 2.0.2 + /char-regex@1.0.2: + resolution: { integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== } + engines: { node: '>=10' } dev: true - /chokidar@3.5.3: - resolution: { integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== } - engines: { node: '>= 8.10.0' } - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 + /ci-info@3.9.0: + resolution: { integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== } + engines: { node: '>=8' } + dev: true + + /cjs-module-lexer@1.2.3: + resolution: { integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== } dev: true /cli-cursor@4.0.0: @@ -312,20 +1178,12 @@ packages: restore-cursor: 4.0.0 dev: true - /cli-truncate@3.1.0: - resolution: { integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + /cli-truncate@4.0.0: + resolution: { integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA== } + engines: { node: '>=18' } dependencies: slice-ansi: 5.0.0 - string-width: 5.1.2 - dev: true - - /cliui@7.0.4: - resolution: { integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== } - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 + string-width: 7.0.0 dev: true /cliui@8.0.1: @@ -335,7 +1193,21 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: false + + /co@4.6.0: + resolution: { integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== } + engines: { iojs: '>= 1.0.0', node: '>= 0.12.0' } + dev: true + + /collect-v8-coverage@1.0.2: + resolution: { integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== } + dev: true + + /color-convert@1.9.3: + resolution: { integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== } + dependencies: + color-name: 1.1.3 + dev: true /color-convert@2.0.1: resolution: { integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== } @@ -343,6 +1215,10 @@ packages: dependencies: color-name: 1.1.4 + /color-name@1.1.3: + resolution: { integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== } + dev: true + /color-name@1.1.4: resolution: { integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== } @@ -359,6 +1235,33 @@ packages: resolution: { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } dev: true + /convert-source-map@2.0.0: + resolution: { integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== } + dev: true + + /create-jest@29.7.0(@types/node@20.10.5)(ts-node@10.9.2): + resolution: { integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + hasBin: true + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /create-require@1.1.1: + resolution: { integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== } + dev: true + /cross-spawn@7.0.3: resolution: { integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== } engines: { node: '>= 8' } @@ -368,7 +1271,7 @@ packages: which: 2.0.2 dev: true - /debug@4.3.4(supports-color@8.1.1): + /debug@4.3.4: resolution: { integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== } engines: { node: '>=6.0' } peerDependencies: @@ -378,30 +1281,61 @@ packages: optional: true dependencies: ms: 2.1.2 - supports-color: 8.1.1 dev: true - /decamelize@4.0.0: - resolution: { integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== } - engines: { node: '>=10' } + /dedent@1.5.1: + resolution: { integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== } + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true dev: true - /deep-eql@4.1.3: - resolution: { integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== } - engines: { node: '>=6' } - dependencies: - type-detect: 4.0.8 + /deepmerge@4.3.1: + resolution: { integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== } + engines: { node: '>=0.10.0' } + dev: true + + /detect-newline@3.1.0: + resolution: { integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== } + engines: { node: '>=8' } + dev: true + + /diff-sequences@29.6.3: + resolution: { integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } dev: true - /diff@5.0.0: - resolution: { integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== } + /diff@4.0.2: + resolution: { integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== } engines: { node: '>=0.3.1' } dev: true + /dir-glob@3.0.1: + resolution: { integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== } + engines: { node: '>=8' } + dependencies: + path-type: 4.0.0 + dev: true + /eastasianwidth@0.2.0: resolution: { integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== } dev: true + /electron-to-chromium@1.4.614: + resolution: { integrity: sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ== } + dev: true + + /emittery@0.13.1: + resolution: { integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== } + engines: { node: '>=12' } + dev: true + + /emoji-regex@10.3.0: + resolution: { integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== } + dev: true + /emoji-regex@8.0.0: resolution: { integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== } @@ -409,32 +1343,107 @@ packages: resolution: { integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== } dev: true + /error-ex@1.3.2: + resolution: { integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== } + dependencies: + is-arrayish: 0.2.1 + dev: true + /escalade@3.1.1: resolution: { integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== } engines: { node: '>=6' } - /escape-string-regexp@4.0.0: - resolution: { integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== } - engines: { node: '>=10' } + /escape-string-regexp@1.0.5: + resolution: { integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== } + engines: { node: '>=0.8.0' } + dev: true + + /escape-string-regexp@2.0.0: + resolution: { integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== } + engines: { node: '>=8' } + dev: true + + /esprima@4.0.1: + resolution: { integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== } + engines: { node: '>=4' } + hasBin: true dev: true /eventemitter3@5.0.1: resolution: { integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== } dev: true - /execa@8.0.1: - resolution: { integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== } - engines: { node: '>=16.17' } + /execa@5.1.1: + resolution: { integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== } + engines: { node: '>=10' } + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + + /execa@8.0.1: + resolution: { integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== } + engines: { node: '>=16.17' } + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + + /exit@0.1.2: + resolution: { integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== } + engines: { node: '>= 0.8.0' } + dev: true + + /expect@29.7.0: + resolution: { integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + dev: true + + /fast-glob@3.3.2: + resolution: { integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== } + engines: { node: '>=8.6.0' } + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify@2.1.0: + resolution: { integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== } + dev: true + + /fastq@1.16.0: + resolution: { integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA== } + dependencies: + reusify: 1.0.4 + dev: true + + /fb-watchman@2.0.2: + resolution: { integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== } dependencies: - cross-spawn: 7.0.3 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.1.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 + bser: 2.1.1 dev: true /fill-range@7.0.1: @@ -444,19 +1453,14 @@ packages: to-regex-range: 5.0.1 dev: true - /find-up@5.0.0: - resolution: { integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== } - engines: { node: '>=10' } + /find-up@4.1.0: + resolution: { integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== } + engines: { node: '>=8' } dependencies: - locate-path: 6.0.0 + locate-path: 5.0.0 path-exists: 4.0.0 dev: true - /flat@5.0.2: - resolution: { integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== } - hasBin: true - dev: true - /foreground-child@3.1.1: resolution: { integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== } engines: { node: '>=14' } @@ -465,6 +1469,15 @@ packages: signal-exit: 4.1.0 dev: true + /fs-extra@11.2.0: + resolution: { integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== } + engines: { node: '>=14.14' } + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + dev: true + /fs.realpath@1.0.0: resolution: { integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== } dev: true @@ -477,12 +1490,32 @@ packages: dev: true optional: true + /function-bind@1.1.2: + resolution: { integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== } + dev: true + + /gensync@1.0.0-beta.2: + resolution: { integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== } + engines: { node: '>=6.9.0' } + dev: true + /get-caller-file@2.0.5: resolution: { integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== } engines: { node: 6.* || 8.* || >= 10.* } - /get-func-name@2.0.2: - resolution: { integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== } + /get-east-asian-width@1.2.0: + resolution: { integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA== } + engines: { node: '>=18' } + dev: true + + /get-package-type@0.1.0: + resolution: { integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== } + engines: { node: '>=8.0.0' } + dev: true + + /get-stream@6.0.1: + resolution: { integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== } + engines: { node: '>=10' } dev: true /get-stream@8.0.1: @@ -520,14 +1553,50 @@ packages: path-is-absolute: 1.0.1 dev: true + /globals@11.12.0: + resolution: { integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== } + engines: { node: '>=4' } + dev: true + + /globby@13.2.2: + resolution: { integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.0 + merge2: 1.4.1 + slash: 4.0.0 + dev: true + + /graceful-fs@4.2.11: + resolution: { integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== } + dev: true + + /has-flag@3.0.0: + resolution: { integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== } + engines: { node: '>=4' } + dev: true + /has-flag@4.0.0: resolution: { integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== } engines: { node: '>=8' } dev: true - /he@1.2.0: - resolution: { integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== } - hasBin: true + /hasown@2.0.0: + resolution: { integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== } + engines: { node: '>= 0.4' } + dependencies: + function-bind: 1.1.2 + dev: true + + /html-escaper@2.0.2: + resolution: { integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== } + dev: true + + /human-signals@2.1.0: + resolution: { integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== } + engines: { node: '>=10.17.0' } dev: true /human-signals@5.0.0: @@ -541,6 +1610,25 @@ packages: hasBin: true dev: true + /ignore@5.3.0: + resolution: { integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== } + engines: { node: '>= 4' } + dev: true + + /import-local@3.1.0: + resolution: { integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== } + engines: { node: '>=8' } + hasBin: true + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + dev: true + + /imurmurhash@0.1.4: + resolution: { integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== } + engines: { node: '>=0.8.19' } + dev: true + /inflight@1.0.6: resolution: { integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== } dependencies: @@ -552,11 +1640,14 @@ packages: resolution: { integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== } dev: true - /is-binary-path@2.1.0: - resolution: { integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== } - engines: { node: '>=8' } + /is-arrayish@0.2.1: + resolution: { integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== } + dev: true + + /is-core-module@2.13.1: + resolution: { integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== } dependencies: - binary-extensions: 2.2.0 + hasown: 2.0.0 dev: true /is-extglob@2.1.1: @@ -573,6 +1664,18 @@ packages: engines: { node: '>=12' } dev: true + /is-fullwidth-code-point@5.0.0: + resolution: { integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA== } + engines: { node: '>=18' } + dependencies: + get-east-asian-width: 1.2.0 + dev: true + + /is-generator-fn@2.1.0: + resolution: { integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== } + engines: { node: '>=6' } + dev: true + /is-glob@4.0.3: resolution: { integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== } engines: { node: '>=0.10.0' } @@ -585,8 +1688,8 @@ packages: engines: { node: '>=0.12.0' } dev: true - /is-plain-obj@2.1.0: - resolution: { integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== } + /is-stream@2.0.1: + resolution: { integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== } engines: { node: '>=8' } dev: true @@ -595,13 +1698,67 @@ packages: engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } dev: true - /is-unicode-supported@0.1.0: - resolution: { integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== } + /isexe@2.0.0: + resolution: { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + dev: true + + /istanbul-lib-coverage@3.2.2: + resolution: { integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== } + engines: { node: '>=8' } + dev: true + + /istanbul-lib-instrument@5.2.1: + resolution: { integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== } + engines: { node: '>=8' } + dependencies: + '@babel/core': 7.23.6 + '@babel/parser': 7.23.6 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-lib-instrument@6.0.1: + resolution: { integrity: sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== } + engines: { node: '>=10' } + dependencies: + '@babel/core': 7.23.6 + '@babel/parser': 7.23.6 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-lib-report@3.0.1: + resolution: { integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== } engines: { node: '>=10' } + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 dev: true - /isexe@2.0.0: - resolution: { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + /istanbul-lib-source-maps@4.0.1: + resolution: { integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== } + engines: { node: '>=10' } + dependencies: + debug: 4.3.4 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports@3.1.6: + resolution: { integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== } + engines: { node: '>=8' } + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 dev: true /jackspeak@2.3.6: @@ -613,6 +1770,417 @@ packages: '@pkgjs/parseargs': 0.11.0 dev: true + /jest-changed-files@29.7.0: + resolution: { integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + dev: true + + /jest-circus@29.7.0: + resolution: { integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.1 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.0.4 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + + /jest-cli@29.7.0(@types/node@20.10.5)(ts-node@10.9.2): + resolution: { integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /jest-config@29.7.0(@types/node@20.10.5)(ts-node@10.9.2): + resolution: { integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.23.6 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + babel-jest: 29.7.0(@babel/core@7.23.6) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.0 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + ts-node: 10.9.2(@types/node@20.10.5)(typescript@5.3.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + + /jest-diff@29.7.0: + resolution: { integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + dev: true + + /jest-docblock@29.7.0: + resolution: { integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + detect-newline: 3.1.0 + dev: true + + /jest-each@29.7.0: + resolution: { integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + dev: true + + /jest-environment-node@29.7.0: + resolution: { integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: true + + /jest-get-type@29.6.3: + resolution: { integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dev: true + + /jest-haste-map@29.7.0: + resolution: { integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 20.10.5 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /jest-leak-detector@29.7.0: + resolution: { integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + dev: true + + /jest-matcher-utils@29.7.0: + resolution: { integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + dev: true + + /jest-message-util@29.7.0: + resolution: { integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@babel/code-frame': 7.23.5 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + dev: true + + /jest-mock@29.7.0: + resolution: { integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + jest-util: 29.7.0 + dev: true + + /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + resolution: { integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== } + engines: { node: '>=6' } + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 29.7.0 + dev: true + + /jest-regex-util@29.6.3: + resolution: { integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dev: true + + /jest-resolve-dependencies@29.7.0: + resolution: { integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-resolve@29.7.0: + resolution: { integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.8 + resolve.exports: 2.0.2 + slash: 3.0.0 + dev: true + + /jest-runner@29.7.0: + resolution: { integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-runtime@29.7.0: + resolution: { integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + chalk: 4.1.2 + cjs-module-lexer: 1.2.3 + collect-v8-coverage: 1.0.2 + glob: 7.2.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-snapshot@29.7.0: + resolution: { integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@babel/core': 7.23.6 + '@babel/generator': 7.23.6 + '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.6) + '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.6) + '@babel/types': 7.23.6 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.6) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-util@29.7.0: + resolution: { integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + dev: true + + /jest-validate@29.7.0: + resolution: { integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + dev: true + + /jest-watcher@29.7.0: + resolution: { integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.10.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + dev: true + + /jest-worker@29.7.0: + resolution: { integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@types/node': 20.10.5 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + dev: true + + /jest@29.7.0(@types/node@20.10.5)(ts-node@10.9.2): + resolution: { integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /joi@17.11.0: resolution: { integrity: sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ== } dependencies: @@ -623,29 +2191,72 @@ packages: '@sideway/pinpoint': 2.0.0 dev: false - /js-yaml@4.1.0: - resolution: { integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== } + /js-tokens@4.0.0: + resolution: { integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== } + dev: true + + /js-yaml@3.14.1: + resolution: { integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== } + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + dev: true + + /jsesc@2.5.2: + resolution: { integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== } + engines: { node: '>=4' } + hasBin: true + dev: true + + /json-parse-even-better-errors@2.3.1: + resolution: { integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== } + dev: true + + /json5@2.2.3: + resolution: { integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== } + engines: { node: '>=6' } hasBin: true + dev: true + + /jsonfile@6.1.0: + resolution: { integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== } dependencies: - argparse: 2.0.1 + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + + /kleur@3.0.3: + resolution: { integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== } + engines: { node: '>=6' } + dev: true + + /leven@3.1.0: + resolution: { integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== } + engines: { node: '>=6' } + dev: true + + /lilconfig@3.0.0: + resolution: { integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g== } + engines: { node: '>=14' } dev: true - /lilconfig@2.1.0: - resolution: { integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== } - engines: { node: '>=10' } + /lines-and-columns@1.2.4: + resolution: { integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== } dev: true - /lint-staged@15.1.0: - resolution: { integrity: sha512-ZPKXWHVlL7uwVpy8OZ7YQjYDAuO5X4kMh0XgZvPNxLcCCngd0PO5jKQyy3+s4TL2EnHoIXIzP1422f/l3nZKMw== } + /lint-staged@15.2.0: + resolution: { integrity: sha512-TFZzUEV00f+2YLaVPWBWGAMq7So6yQx+GG8YRMDeOEIf95Zn5RyiLMsEiX4KTNl9vq/w+NqRJkLA1kPIo15ufQ== } engines: { node: '>=18.12.0' } hasBin: true dependencies: chalk: 5.3.0 commander: 11.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 execa: 8.0.1 - lilconfig: 2.1.0 - listr2: 7.0.2 + lilconfig: 3.0.0 + listr2: 8.0.0 micromatch: 4.0.5 pidtree: 0.6.0 string-argv: 0.3.2 @@ -654,71 +2265,101 @@ packages: - supports-color dev: true - /listr2@7.0.2: - resolution: { integrity: sha512-rJysbR9GKIalhTbVL2tYbF2hVyDnrf7pFUZBwjPaMIdadYHmeT+EVi/Bu3qd7ETQPahTotg2WRCatXwRBW554g== } - engines: { node: '>=16.0.0' } + /listr2@8.0.0: + resolution: { integrity: sha512-u8cusxAcyqAiQ2RhYvV7kRKNLgUvtObIbhOX2NCXqvp1UU32xIg5CT22ykS2TPKJXZWJwtK3IKLiqAGlGNE+Zg== } + engines: { node: '>=18.0.0' } dependencies: - cli-truncate: 3.1.0 + cli-truncate: 4.0.0 colorette: 2.0.20 eventemitter3: 5.0.1 - log-update: 5.0.1 + log-update: 6.0.0 rfdc: 1.3.0 - wrap-ansi: 8.1.0 + wrap-ansi: 9.0.0 dev: true - /locate-path@6.0.0: - resolution: { integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== } - engines: { node: '>=10' } + /locate-path@5.0.0: + resolution: { integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== } + engines: { node: '>=8' } dependencies: - p-locate: 5.0.0 + p-locate: 4.1.0 dev: true /lodash.camelcase@4.3.0: resolution: { integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== } dev: false + /lodash.memoize@4.1.2: + resolution: { integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== } + dev: true + /lodash@4.17.21: resolution: { integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== } dev: false - /log-symbols@4.1.0: - resolution: { integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== } - engines: { node: '>=10' } - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - dev: true - - /log-update@5.0.1: - resolution: { integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw== } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + /log-update@6.0.0: + resolution: { integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw== } + engines: { node: '>=18' } dependencies: - ansi-escapes: 5.0.0 + ansi-escapes: 6.2.0 cli-cursor: 4.0.0 - slice-ansi: 5.0.0 + slice-ansi: 7.1.0 strip-ansi: 7.1.0 - wrap-ansi: 8.1.0 + wrap-ansi: 9.0.0 dev: true /long@5.2.3: resolution: { integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== } dev: false - /loupe@2.3.7: - resolution: { integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== } - dependencies: - get-func-name: 2.0.2 - dev: true - /lru-cache@10.1.0: resolution: { integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== } engines: { node: 14 || >=16.14 } dev: true + /lru-cache@5.1.1: + resolution: { integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== } + dependencies: + yallist: 3.1.1 + dev: true + + /lru-cache@6.0.0: + resolution: { integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== } + engines: { node: '>=10' } + dependencies: + yallist: 4.0.0 + dev: true + + /make-dir@4.0.0: + resolution: { integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== } + engines: { node: '>=10' } + dependencies: + semver: 7.5.4 + dev: true + + /make-error@1.3.6: + resolution: { integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== } + dev: true + + /makeerror@1.0.12: + resolution: { integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== } + dependencies: + tmpl: 1.0.5 + dev: true + + /meow@12.1.1: + resolution: { integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw== } + engines: { node: '>=16.10' } + dev: true + /merge-stream@2.0.0: resolution: { integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== } dev: true + /merge2@1.4.1: + resolution: { integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== } + engines: { node: '>= 8' } + dev: true + /micromatch@4.0.5: resolution: { integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== } engines: { node: '>=8.6' } @@ -743,13 +2384,6 @@ packages: brace-expansion: 1.1.11 dev: true - /minimatch@5.0.1: - resolution: { integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== } - engines: { node: '>=10' } - dependencies: - brace-expansion: 2.0.1 - dev: true - /minimatch@9.0.3: resolution: { integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== } engines: { node: '>=16 || 14 >=14.17' } @@ -762,46 +2396,20 @@ packages: engines: { node: '>=16 || 14 >=14.17' } dev: true - /mocha@10.2.0: - resolution: { integrity: sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== } - engines: { node: '>= 14.0.0' } - hasBin: true - dependencies: - ansi-colors: 4.1.1 - browser-stdout: 1.3.1 - chokidar: 3.5.3 - debug: 4.3.4(supports-color@8.1.1) - diff: 5.0.0 - escape-string-regexp: 4.0.0 - find-up: 5.0.0 - glob: 7.2.0 - he: 1.2.0 - js-yaml: 4.1.0 - log-symbols: 4.1.0 - minimatch: 5.0.1 - ms: 2.1.3 - nanoid: 3.3.3 - serialize-javascript: 6.0.0 - strip-json-comments: 3.1.1 - supports-color: 8.1.1 - workerpool: 6.2.1 - yargs: 16.2.0 - yargs-parser: 20.2.4 - yargs-unparser: 2.0.0 - dev: true - /ms@2.1.2: resolution: { integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== } dev: true - /ms@2.1.3: - resolution: { integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== } + /natural-compare@1.4.0: + resolution: { integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== } dev: true - /nanoid@3.3.3: - resolution: { integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } - hasBin: true + /node-int64@0.4.0: + resolution: { integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== } + dev: true + + /node-releases@2.0.14: + resolution: { integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== } dev: true /normalize-path@3.0.0: @@ -809,6 +2417,13 @@ packages: engines: { node: '>=0.10.0' } dev: true + /npm-run-path@4.0.1: + resolution: { integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== } + engines: { node: '>=8' } + dependencies: + path-key: 3.1.1 + dev: true + /npm-run-path@5.1.0: resolution: { integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } @@ -836,6 +2451,13 @@ packages: mimic-fn: 4.0.0 dev: true + /p-limit@2.3.0: + resolution: { integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== } + engines: { node: '>=6' } + dependencies: + p-try: 2.2.0 + dev: true + /p-limit@3.1.0: resolution: { integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== } engines: { node: '>=10' } @@ -843,11 +2465,26 @@ packages: yocto-queue: 0.1.0 dev: true - /p-locate@5.0.0: - resolution: { integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== } - engines: { node: '>=10' } + /p-locate@4.1.0: + resolution: { integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== } + engines: { node: '>=8' } dependencies: - p-limit: 3.1.0 + p-limit: 2.3.0 + dev: true + + /p-try@2.2.0: + resolution: { integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== } + engines: { node: '>=6' } + dev: true + + /parse-json@5.2.0: + resolution: { integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== } + engines: { node: '>=8' } + dependencies: + '@babel/code-frame': 7.23.5 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 dev: true /path-exists@4.0.0: @@ -870,6 +2507,10 @@ packages: engines: { node: '>=12' } dev: true + /path-parse@1.0.7: + resolution: { integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== } + dev: true + /path-scurry@1.10.1: resolution: { integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== } engines: { node: '>=16 || 14 >=14.17' } @@ -878,8 +2519,13 @@ packages: minipass: 7.0.4 dev: true - /pathval@1.1.1: - resolution: { integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== } + /path-type@4.0.0: + resolution: { integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== } + engines: { node: '>=8' } + dev: true + + /picocolors@1.0.0: + resolution: { integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== } dev: true /picomatch@2.3.1: @@ -893,12 +2539,41 @@ packages: hasBin: true dev: true - /prettier@3.1.0: - resolution: { integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw== } + /pirates@4.0.6: + resolution: { integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== } + engines: { node: '>= 6' } + dev: true + + /pkg-dir@4.2.0: + resolution: { integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== } + engines: { node: '>=8' } + dependencies: + find-up: 4.1.0 + dev: true + + /prettier@3.1.1: + resolution: { integrity: sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw== } engines: { node: '>=14' } hasBin: true dev: true + /pretty-format@29.7.0: + resolution: { integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + + /prompts@2.4.2: + resolution: { integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== } + engines: { node: '>= 6' } + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + dev: true + /protobufjs@7.2.5: resolution: { integrity: sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== } engines: { node: '>=12.0.0' } @@ -914,27 +2589,52 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.10.2 + '@types/node': 20.10.5 long: 5.2.3 dev: false - /randombytes@2.1.0: - resolution: { integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== } - dependencies: - safe-buffer: 5.2.1 + /pure-rand@6.0.4: + resolution: { integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== } dev: true - /readdirp@3.6.0: - resolution: { integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== } - engines: { node: '>=8.10.0' } - dependencies: - picomatch: 2.3.1 + /queue-microtask@1.2.3: + resolution: { integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== } + dev: true + + /react-is@18.2.0: + resolution: { integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== } dev: true /require-directory@2.1.1: resolution: { integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== } engines: { node: '>=0.10.0' } + /resolve-cwd@3.0.0: + resolution: { integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== } + engines: { node: '>=8' } + dependencies: + resolve-from: 5.0.0 + dev: true + + /resolve-from@5.0.0: + resolution: { integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== } + engines: { node: '>=8' } + dev: true + + /resolve.exports@2.0.2: + resolution: { integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== } + engines: { node: '>=10' } + dev: true + + /resolve@1.22.8: + resolution: { integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== } + hasBin: true + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + /restore-cursor@4.0.0: resolution: { integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } @@ -943,6 +2643,11 @@ packages: signal-exit: 3.0.7 dev: true + /reusify@1.0.4: + resolution: { integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== } + engines: { iojs: '>=1.0.0', node: '>=0.10.0' } + dev: true + /rfdc@1.3.0: resolution: { integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== } dev: true @@ -955,14 +2660,23 @@ packages: glob: 10.3.10 dev: true - /safe-buffer@5.2.1: - resolution: { integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== } + /run-parallel@1.2.0: + resolution: { integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== } + dependencies: + queue-microtask: 1.2.3 + dev: true + + /semver@6.3.1: + resolution: { integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== } + hasBin: true dev: true - /serialize-javascript@6.0.0: - resolution: { integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== } + /semver@7.5.4: + resolution: { integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== } + engines: { node: '>=10' } + hasBin: true dependencies: - randombytes: 2.1.0 + lru-cache: 6.0.0 dev: true /shebang-command@2.0.0: @@ -986,6 +2700,20 @@ packages: engines: { node: '>=14' } dev: true + /sisteransi@1.0.5: + resolution: { integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== } + dev: true + + /slash@3.0.0: + resolution: { integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== } + engines: { node: '>=8' } + dev: true + + /slash@4.0.0: + resolution: { integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== } + engines: { node: '>=12' } + dev: true + /slice-ansi@5.0.0: resolution: { integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== } engines: { node: '>=12' } @@ -994,11 +2722,50 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true + /slice-ansi@7.1.0: + resolution: { integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg== } + engines: { node: '>=18' } + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + dev: true + + /source-map-support@0.5.13: + resolution: { integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== } + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.6.1: + resolution: { integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== } + engines: { node: '>=0.10.0' } + dev: true + + /sprintf-js@1.0.3: + resolution: { integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== } + dev: true + + /stack-utils@2.0.6: + resolution: { integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== } + engines: { node: '>=10' } + dependencies: + escape-string-regexp: 2.0.0 + dev: true + /string-argv@0.3.2: resolution: { integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== } engines: { node: '>=0.6.19' } dev: true + /string-length@4.0.2: + resolution: { integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== } + engines: { node: '>=10' } + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + dev: true + /string-width@4.2.3: resolution: { integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== } engines: { node: '>=8' } @@ -1016,6 +2783,15 @@ packages: strip-ansi: 7.1.0 dev: true + /string-width@7.0.0: + resolution: { integrity: sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw== } + engines: { node: '>=18' } + dependencies: + emoji-regex: 10.3.0 + get-east-asian-width: 1.2.0 + strip-ansi: 7.1.0 + dev: true + /strip-ansi@6.0.1: resolution: { integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== } engines: { node: '>=8' } @@ -1029,6 +2805,16 @@ packages: ansi-regex: 6.0.1 dev: true + /strip-bom@4.0.0: + resolution: { integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== } + engines: { node: '>=8' } + dev: true + + /strip-final-newline@2.0.0: + resolution: { integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== } + engines: { node: '>=6' } + dev: true + /strip-final-newline@3.0.0: resolution: { integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== } engines: { node: '>=12' } @@ -1039,6 +2825,13 @@ packages: engines: { node: '>=8' } dev: true + /supports-color@5.5.0: + resolution: { integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== } + engines: { node: '>=4' } + dependencies: + has-flag: 3.0.0 + dev: true + /supports-color@7.2.0: resolution: { integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== } engines: { node: '>=8' } @@ -1053,6 +2846,29 @@ packages: has-flag: 4.0.0 dev: true + /supports-preserve-symlinks-flag@1.0.0: + resolution: { integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== } + engines: { node: '>= 0.4' } + dev: true + + /test-exclude@6.0.0: + resolution: { integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== } + engines: { node: '>=8' } + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.0 + minimatch: 3.1.2 + dev: true + + /tmpl@1.0.5: + resolution: { integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== } + dev: true + + /to-fast-properties@2.0.0: + resolution: { integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== } + engines: { node: '>=4' } + dev: true + /to-regex-range@5.0.1: resolution: { integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== } engines: { node: '>=8.0' } @@ -1060,18 +2876,104 @@ packages: is-number: 7.0.0 dev: true + /ts-jest@29.1.1(@babel/core@7.23.6)(jest@29.7.0)(typescript@5.3.3): + resolution: { integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.23.6 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.5.4 + typescript: 5.3.3 + yargs-parser: 21.1.1 + dev: true + + /ts-node@10.9.2(@types/node@20.10.5)(typescript@5.3.3): + resolution: { integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== } + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.10.5 + acorn: 8.11.2 + acorn-walk: 8.3.1 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.3.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /tsc-esm-fix@2.20.18: + resolution: { integrity: sha512-oPH79Z1AZxau4kc/Jw+LYVqs9NDTwsbyWwE57fVUgmwoPgxhGAwDTv0CjDEdMO1Aczg3EH1bLdsQmsoaJib4Tg== } + engines: { node: '>=16.0.0' } + hasBin: true + dependencies: + fs-extra: 11.2.0 + globby: 13.2.2 + json5: 2.2.3 + meow: 12.1.1 + tslib: 2.6.2 + dev: true + + /tslib@2.6.2: + resolution: { integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== } + dev: true + /type-detect@4.0.8: resolution: { integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== } engines: { node: '>=4' } dev: true - /type-fest@1.4.0: - resolution: { integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== } + /type-fest@0.21.3: + resolution: { integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== } engines: { node: '>=10' } dev: true - /typescript@5.3.2: - resolution: { integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== } + /type-fest@3.13.1: + resolution: { integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== } + engines: { node: '>=14.16' } + dev: true + + /typescript@5.3.3: + resolution: { integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== } engines: { node: '>=14.17' } hasBin: true dev: true @@ -1079,6 +2981,41 @@ packages: /undici-types@5.26.5: resolution: { integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== } + /universalify@2.0.1: + resolution: { integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== } + engines: { node: '>= 10.0.0' } + dev: true + + /update-browserslist-db@1.0.13(browserslist@4.22.2): + resolution: { integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== } + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.22.2 + escalade: 3.1.1 + picocolors: 1.0.0 + dev: true + + /v8-compile-cache-lib@3.0.1: + resolution: { integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== } + dev: true + + /v8-to-istanbul@9.2.0: + resolution: { integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== } + engines: { node: '>=10.12.0' } + dependencies: + '@jridgewell/trace-mapping': 0.3.20 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + dev: true + + /walker@1.0.8: + resolution: { integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== } + dependencies: + makeerror: 1.0.12 + dev: true + /which@2.0.2: resolution: { integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== } engines: { node: '>= 8' } @@ -1087,10 +3024,6 @@ packages: isexe: 2.0.0 dev: true - /workerpool@6.2.1: - resolution: { integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== } - dev: true - /wrap-ansi@7.0.0: resolution: { integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== } engines: { node: '>=10' } @@ -1108,51 +3041,47 @@ packages: strip-ansi: 7.1.0 dev: true + /wrap-ansi@9.0.0: + resolution: { integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q== } + engines: { node: '>=18' } + dependencies: + ansi-styles: 6.2.1 + string-width: 7.0.0 + strip-ansi: 7.1.0 + dev: true + /wrappy@1.0.2: resolution: { integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== } dev: true + /write-file-atomic@4.0.2: + resolution: { integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== } + engines: { node: ^12.13.0 || ^14.15.0 || >=16.0.0 } + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + dev: true + /y18n@5.0.8: resolution: { integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== } engines: { node: '>=10' } + /yallist@3.1.1: + resolution: { integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== } + dev: true + + /yallist@4.0.0: + resolution: { integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== } + dev: true + /yaml@2.3.4: resolution: { integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== } engines: { node: '>= 14' } dev: true - /yargs-parser@20.2.4: - resolution: { integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== } - engines: { node: '>=10' } - dev: true - /yargs-parser@21.1.1: resolution: { integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== } engines: { node: '>=12' } - dev: false - - /yargs-unparser@2.0.0: - resolution: { integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== } - engines: { node: '>=10' } - dependencies: - camelcase: 6.3.0 - decamelize: 4.0.0 - flat: 5.0.2 - is-plain-obj: 2.1.0 - dev: true - - /yargs@16.2.0: - resolution: { integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== } - engines: { node: '>=10' } - dependencies: - cliui: 7.0.4 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.4 - dev: true /yargs@17.7.2: resolution: { integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== } @@ -1165,7 +3094,11 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - dev: false + + /yn@3.1.1: + resolution: { integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== } + engines: { node: '>=6' } + dev: true /yocto-queue@0.1.0: resolution: { integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== } diff --git a/src/config/defaultChannelOptions.ts b/src/config/defaultChannelOptions.ts deleted file mode 100644 index 13f727c..0000000 --- a/src/config/defaultChannelOptions.ts +++ /dev/null @@ -1,23 +0,0 @@ -// gRPC channel options -// Doc: https://grpc.github.io/grpc/core/group__grpc__arg__keys.html -// Doc: https://github.com/grpc/grpc-node/blob/master/packages/grpc-js/src/channel-options.ts -import { ChannelOptions } from '@grpc/grpc-js' - -export const defaultChannelOptions: ChannelOptions = { - 'grpc.min_reconnect_backoff_ms': 1000, - 'grpc.max_reconnect_backoff_ms': 10000, - 'grpc.grpclb_call_timeout_ms': 5000, - 'grpc.keepalive_timeout_ms': 20 * 1000, - 'grpc.keepalive_time_ms': 120 * 1000, - 'grpc.keepalive_permit_without_calls': 1, - 'grpc.enable_retries': 1, - 'grpc.service_config': JSON.stringify({ - retryPolicy: { - maxAttempts: 4, - initialBackoff: '0.1s', - maxBackoff: '1s', - backoffMultiplier: 2, - retryableStatusCodes: ['UNAVAILABLE'] - } - }) -} diff --git a/src/config/defaultLoadOptions.ts b/src/config/defaultLoadOptions.ts deleted file mode 100644 index 63eb666..0000000 --- a/src/config/defaultLoadOptions.ts +++ /dev/null @@ -1,11 +0,0 @@ -// GRPC protos loader options -// Doc: https://www.npmjs.com/package/@grpc/proto-loader -import { Options } from '@grpc/proto-loader' - -export const defaultLoadOptions: Options = { - keepCase: true, - longs: String, - enums: String, - defaults: false, - oneofs: true -} diff --git a/src/index.ts b/src/index.ts index 0b87924..e69de29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,238 +0,0 @@ -import assert from 'node:assert' -import * as grpc from '@grpc/grpc-js' -import * as protoLoader from '@grpc/proto-loader' -import * as protobuf from 'protobufjs' -import * as Descriptor from 'protobufjs/ext/descriptor' -import * as _ from 'lodash' -import * as Joi from 'joi' - -import loaderSchemas from './schema/loader' -import prefixingDefinition from './util/prefixingDefinition' -import { defaultChannelOptions } from './config/defaultChannelOptions' -import { defaultLoadOptions } from './config/defaultLoadOptions' -import clientProxy from './proxy/clientProxy' -import ServerProxy from './proxy/serverProxy' - -class GrpcLoader { - private _protoFiles: any[] - private _clientMap: Map - private _clientAddrMap: Map - private _types: any - private _packagePrefix?: string - private _appName?: string - private _packageDefinition: any - private _isDev?: boolean - private _reflectedRoot: any - private _insecureCredentials?: grpc.ChannelCredentials - private _initDefaultClient?: boolean - - constructor(protoFileOptions: any) { - Joi.assert(protoFileOptions, loaderSchemas.constructor, 'new GrpcLoader() params Error') - - this._protoFiles = Array.isArray(protoFileOptions) ? protoFileOptions : [protoFileOptions] - this._clientMap = new Map() - this._clientAddrMap = new Map() - } - - async init({ services = undefined, isDev = false, packagePrefix = '', loadOptions = {}, channelOptions = {}, appName }: any = {}) { - Joi.assert({ services, loadOptions, isDev, channelOptions, appName }, loaderSchemas.init, 'GrpcLoader.init() params Error') - - if (this._types) { - return - } - - try { - loadOptions = Object.assign({}, defaultLoadOptions, loadOptions) - this._isDev = isDev - this._packagePrefix = packagePrefix - this._appName = appName - - loadOptions.includeDirs = this._protoFiles.map((p) => p.location).concat(loadOptions.includeDirs || []) - const files = this._protoFiles.reduce((result, p) => { - if (p.files && p.files.length > 0) { - result.push(...p.files) - } - return result - }, []) - - const packageDefinition = await protoLoader.load(files, loadOptions) - - if (this._packagePrefix) { - this._packageDefinition = prefixingDefinition(packageDefinition, packagePrefix) - } else { - this._packageDefinition = packageDefinition - } - - this._types = grpc.loadPackageDefinition(this._packageDefinition) - } catch (err) { - throw err - } - - if (services) { - await this.initClients({ services, channelOptions }) - } - } - - async initClients({ services, channelOptions = {}, credentials = undefined }: any) { - Joi.assert({ services, channelOptions }, loaderSchemas.initClients, 'GrpcLoader.initClients() Options Error') - - if (this._initDefaultClient) { - return - } - - if (!this._packageDefinition) { - await this.init() - } - - const serviceNames = Object.keys(services) - serviceNames.forEach((name) => { - const isDefaultClient = true - const addr = _.isString(services[name]) ? services[name] : services[name].host + ':' + services[name].port - this._makeClient(isDefaultClient, name, addr, credentials, channelOptions) - }) - - this._initDefaultClient = true - } - - closeClients() { - this._clientMap.forEach((client, key) => { - if (client && typeof client.close === 'function') { - client.close() - } - }) - this._clientMap.clear() - this._clientAddrMap.clear() - this._initDefaultClient = false - } - - makeCredentials(rootCerts?: any, privateKey?: any, certChain?: any, verifyOptions?: any) { - if (rootCerts && privateKey && certChain) { - return grpc.credentials.createSsl(rootCerts, privateKey, certChain, verifyOptions) - } else { - if (!this._insecureCredentials) { - this._insecureCredentials = grpc.credentials.createInsecure() - } - return this._insecureCredentials - } - } - - service(name: string) { - assert(this._types, 'Must called init() first. proto file has not been loaded.') - const fullName = this._isDev ? `${this._packagePrefix}.${name}` : name - const service = _.get(this._types, `${fullName}.service`) - assert(service, `Cannot find service with name: ${fullName}, please check if the protos file is configured incorrectly or if the corresponding proto file is missing.`) - return service - } - - type(name: string) { - assert(this._types, 'Must called init() first. proto file has not been loaded.') - const fullName = this._isDev ? `${this._packagePrefix}.${name}` : name - const type = _.get(this._types, `${fullName}`) - assert(type, `Cannot find type with name: ${fullName}, please check if the protos file is configured incorrectly or if the corresponding proto file is missing.`) - return type - } - - message(name: string) { - let root = this._reflectedRoot - - if (root) { - const found = root.lookupType(name) - if (found) { - return found - } - } - - const descriptor = this.type(name).fileDescriptorProtos.map((proto: any) => Descriptor.FileDescriptorProto.decode(proto)) - root = (protobuf.Root as protobuf.RootConstructor).fromDescriptor({ - file: descriptor - }) - - this._reflectedRoot = root - - return root.lookupType(name) - } - - client(name: string, { host = undefined, port = undefined, timeout = undefined, credentials = undefined, channelOptions = {} }: any = {}) { - const isDefaultClient = !(host && port) - const addr = `${host}:${port}` - const cacheKeyPrefix = isDefaultClient ? 'defaultAddr' : addr.replace(/\./g, '-') - const cacheKey = `proxy.${cacheKeyPrefix}.${name}.${timeout}` - - if (this._clientMap.has(cacheKey)) { - return this._clientMap.get(cacheKey) - } else { - const client = this._makeClient(isDefaultClient, name, addr, credentials, channelOptions) - const appName = this._appName - const proxy = clientProxy._proxy(client, { timeout }, appName) - this._clientMap.set(cacheKey, proxy) - return proxy - } - } - - realClient(name: string, { host = undefined, port = undefined, credentials = undefined, channelOptions = {} }: any = {}) { - const isDefaultClient = !(host && port) - const client = this._makeClient(isDefaultClient, name, `${host}:${port}`, credentials, channelOptions) - return client - } - - clientWithoutCache(name: string, { addr, timeout = undefined, credentials = undefined, channelOptions = {} }: any = {}) { - const client = this._makeClientWithoutCache(false, name, addr, credentials, channelOptions) - const appName = this._appName - const proxy = clientProxy._proxy(client, { timeout }, appName) - return proxy - } - - private _makeClient(isDefaultClient: boolean, name: string, addr: string, credentials: any, channelOptions: any = {}) { - const ctBool = !!credentials - const cacheKeyPrefix = isDefaultClient ? 'defaultAddr' : addr.replace(/\./g, '-') - const cacheKeyWithCt = `${cacheKeyPrefix}.${name}.${ctBool}` - const cacheKey = `${cacheKeyPrefix}.${name}` - - if (this._clientMap.has(cacheKey)) { - return this._clientMap.get(cacheKey) - } else if (this._clientMap.has(cacheKeyWithCt)) { - return this._clientMap.get(cacheKeyWithCt) - } else { - let cacheAddr: string = addr - if (addr === 'undefined:undefined') { - cacheAddr = this._clientAddrMap.get(name) || addr - } - const client = this._makeClientWithoutCache(isDefaultClient, name, cacheAddr, credentials, channelOptions) - this._clientAddrMap.set(name, cacheAddr) - this._clientMap.set(cacheKey, client) - return client - } - } - - private _makeClientWithoutCache(isDefaultClient: boolean, name: string, addr: string, credentials: any, channelOptions: any = {}) { - channelOptions = Object.assign({}, defaultChannelOptions, channelOptions) - - const ServiceProto = this.type(name) - const client = new ServiceProto(addr, credentials || this.makeCredentials(), channelOptions) - return client - } - - makeMetadata(initialValues: any) { - assert(this._types, 'Must called init() first. proto file has not been loaded.') - const meta = new grpc.Metadata() - if (typeof initialValues === 'object') { - Object.entries(initialValues).forEach(([key, value]: [string, any]) => { - if (Array.isArray(value)) { - value.map((v) => meta.add(key, _.isString(v) ? v : Buffer.from(v))) - } else { - meta.add(key, _.isString(value) ? value : Buffer.from(value)) - } - }) - } - return meta - } - - initServer(...args: any[]) { - assert(this._types, 'Must called init() first. proto file has not been loaded.') - const server = new ServerProxy() - return server._init(this, ...args) - } -} - -export default GrpcLoader -module.exports = GrpcLoader diff --git a/src/proxy/clientProxy.ts b/src/proxy/clientProxy.ts deleted file mode 100644 index 0c95eb5..0000000 --- a/src/proxy/clientProxy.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { Metadata, MetadataValue, UntypedServiceImplementation } from '@grpc/grpc-js' -import * as os from 'node:os' -import iterator from '../util/iterator' - -class ClientProxy { - private _getFuncStreamWay(func: any): { - requestStream: boolean - responseStream: boolean - } { - const { requestStream, responseStream } = func - return { requestStream, responseStream } - } - - private _prepareMetadata(metadata: Metadata | Record, options: Record, basicMeta: Record): [Metadata, Record] { - if (metadata instanceof Metadata) { - options = { ...options } - } else { - options = { ...metadata } - metadata = new Metadata() - } - - if (basicMeta.hostname) { - metadata.add('x-client-hostname', basicMeta.hostname as MetadataValue) - } - - if (basicMeta.appName) { - metadata.add('x-client-app-name', basicMeta.appName as MetadataValue) - } - - return [metadata, options] - } - - private _handlerError(err: any, basicMeta: Record) { - const newError = new Error() as { - name: string - code: string - message: string - stack: string - } - - newError.name = 'GrpcClientError' - newError.code = err.code - newError.message = `${basicMeta.fullServiceName} (${err.message})` - - const stacks = newError.stack!.split('\n') - newError.stack = [stacks[0], ...stacks.slice(2), ' ...', ...(err.stack!.split('\n').slice(1, 3) as string[])].join('\n') - - return newError - } - - private _setDeadline(options: { deadline?: Date; timeout?: number }, defaultOptions: Record, basicMeta: { fullServiceName?: string }): { deadline?: Date } { - if (!options.deadline) { - const timeout = options.timeout || defaultOptions.timeout - const deadline = new Date(Date.now() + (timeout as number)) - options.deadline = deadline - delete options.timeout - } - return options - } - - private _promisifyUnaryMethod(client: UntypedServiceImplementation, func: any, defaultOptions: Record, basicMeta: Record): any { - const asyncUnaryMethod = async (request: any, metadata: Metadata, options: Record): Promise => { - if (typeof options === 'function') { - throw new Error('gRPCity: AsyncFunction should not contain a callback function') - } else if (typeof metadata === 'function') { - throw new Error('gRPCity: AsyncFunction should not contain a callback function') - } - - ;[metadata, options] = this._prepareMetadata(metadata, options, basicMeta) - options = this._setDeadline(options, defaultOptions, basicMeta) - - return new Promise((resolve, reject) => { - const result: { response?: any; metadata?: any; status?: any } = {} - const argumentsList: Array = [request, metadata, options] - argumentsList.push((err: any, response: any) => { - if (err) { - reject(this._handlerError(err, basicMeta)) - } - result.response = response - }) - - const call = func.apply(client, argumentsList) - - call.on('metadata', (metadata: any) => { - result.metadata = metadata - }) - call.on('status', (status: any) => { - result.status = status - resolve(result) - }) - }) - } - return asyncUnaryMethod - } - - private _promisifyClientStreamMethod(client: UntypedServiceImplementation, func: any, defaultOptions: Record, basicMeta: Record): any { - const clientStreamMethod = (metadata: Metadata, options: Record): any => { - if (typeof options === 'function') { - throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') - } else if (typeof metadata === 'function') { - throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') - } - - ;[metadata, options] = this._prepareMetadata(metadata, options, basicMeta) - options = this._setDeadline(options, defaultOptions, basicMeta) - - const result: { response?: any; metadata?: any; status?: any } = {} - - const argumentsList: Array = [metadata, options] - argumentsList.push((err: any, response: any) => { - if (err) { - throw this._handlerError(err, basicMeta) - } - result.response = response - }) - - const call = func.apply(client, argumentsList) - - call.writeAll = (messages: any[]) => { - if (Array.isArray(messages)) { - messages.forEach((message) => { - call.write(message) - }) - } - } - call.writeEnd = async () => { - call.end() - await new Promise((resolve, reject) => { - call.on('metadata', (metadata: any) => { - result.metadata = metadata - }) - call.on('status', (status: any) => { - result.status = status - resolve() - }) - }) - return result - } - - return call - } - - return clientStreamMethod - } - - private _promisifyServerStreamMethod(client: UntypedServiceImplementation, func: any, defaultOptions: Record, basicMeta: { fullServiceName?: string }): any { - const serverStreamMethod = (request: any, metadata: Metadata, options: Record): any => { - if (typeof options === 'function') { - throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') - } else if (typeof metadata === 'function') { - throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') - } - - ;[metadata, options] = this._prepareMetadata(metadata, options, basicMeta) - options = this._setDeadline(options, defaultOptions, basicMeta) - - const call = func.apply(client, [request, metadata, options]) - - call.on('error', (err: Error) => { - throw this._handlerError(err, basicMeta) - }) - - const result: { metadata?: any; status?: any } = {} - call.readAll = () => { - call.on('metadata', (metadata: any) => { - result.metadata = metadata - }) - call.on('status', (status: any) => { - result.status = status - }) - return iterator(call, 'data', { - resolutionEvents: ['status', 'end'] - }) - } - call.readEnd = () => { - return result - } - - return call - } - - return serverStreamMethod - } - - private _promisifyDuplexStreamMethod(client: UntypedServiceImplementation, func: any, defaultOptions: Record, basicMeta: { fullServiceName?: string }): any { - const duplexStreamMethod = (metadata: Metadata, options: Record): any => { - if (typeof options === 'function') { - throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') - } else if (typeof metadata === 'function') { - throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') - } - - ;[metadata, options] = this._prepareMetadata(metadata, options, basicMeta) - options = this._setDeadline(options, defaultOptions, basicMeta) - - const call = func.apply(client, [metadata, options]) - - call.writeAll = (messages: any[]) => { - if (Array.isArray(messages)) { - messages.forEach((message) => { - call.write(message) - }) - } - } - call.writeEnd = call.end - - call.on('error', (err: Error) => { - throw this._handlerError(err, basicMeta) - }) - - const result: { metadata?: any; status?: any } = {} - call.readAll = () => { - call.on('metadata', (metadata: any) => { - result.metadata = metadata - }) - call.on('status', (status: any) => { - result.status = status - }) - return iterator(call, 'data', { - resolutionEvents: ['status', 'end'] - }) - } - call.readEnd = () => { - return result - } - - return call - } - - return duplexStreamMethod - } - - private _keepCallbackMethod(client: UntypedServiceImplementation, func: any): (...argumentsList: any[]) => any { - const callbackMethod = (...argumentsList: any[]) => { - return func.apply(client, argumentsList) - } - return callbackMethod - } - - _proxy(client: UntypedServiceImplementation, defaultOptions: Record = {}, appName?: string): any { - defaultOptions = defaultOptions || {} - defaultOptions.timeout = defaultOptions.timeout || 1000 * 10 - - const prototype = Object.getPrototypeOf(client) - - const methodNames: any = Object.keys(prototype) - .filter((key) => prototype[key] && prototype[key].path) - .reduce((names: any, key) => { - names[key.toUpperCase()] = prototype[key].path - return names - }, {}) - - const basicMeta: Record = { - hostname: os.hostname(), - appName - } - - const target = Object.entries(prototype).reduce( - (target: any, [name, func]) => { - if (name !== 'constructor' && typeof func === 'function') { - basicMeta.fullServiceName = `${methodNames[name.toUpperCase()]}` - - const { requestStream, responseStream } = this._getFuncStreamWay(func) - - if (!requestStream && !responseStream) { - // promisify unary method - target[name] = this._promisifyUnaryMethod(client, func, defaultOptions, basicMeta) - } - - // stream - if (requestStream && !responseStream) { - // promisify only client stream method - target[name] = this._promisifyClientStreamMethod(client, func, defaultOptions, basicMeta) - } - if (!requestStream && responseStream) { - // promisify only server stream method - target[name] = this._promisifyServerStreamMethod(client, func, defaultOptions, basicMeta) - } - if (requestStream && responseStream) { - // promisify duplex stream method - target[name] = this._promisifyDuplexStreamMethod(client, func, defaultOptions, basicMeta) - } - - // keep callback method - target.call[name] = this._keepCallbackMethod(client, func) - } - - return target - }, - { call: {} } - ) - - return target - } -} - -export default new ClientProxy() diff --git a/src/proxy/serverProxy.ts b/src/proxy/serverProxy.ts deleted file mode 100644 index b4ae5a6..0000000 --- a/src/proxy/serverProxy.ts +++ /dev/null @@ -1,291 +0,0 @@ -import assert from 'node:assert' -import * as util from 'node:util' -import * as grpc from '@grpc/grpc-js' -import * as _ from 'lodash' -import * as Joi from 'joi' -import serverSchemas from '../schema/server' -import iterator from '../util/iterator' -import { compose, MiddlewareFunction } from '../util/compose' - -class ServerProxy { - private _middleware: MiddlewareFunction[] - - private _loader?: any - private _server?: grpc.Server - private _insecureServerCredentials?: grpc.ServerCredentials - - constructor() { - this._middleware = [] - } - - _init(loader: any, ...args: any[]): this { - if (!this._loader) { - this._loader = loader - } - if (!this._server) { - this._server = new grpc.Server(...args) - } - return this - } - - async listen(addr: any, credentials: grpc.ServerCredentials | undefined = undefined): Promise { - assert(this._server, 'must be first init() server before server listen()') - Joi.assert(addr, serverSchemas.address, 'server listen() params Error') - - const url = _.isString(addr) ? addr : `${addr.host}:${addr.port}` - const bindPort = await new Promise((resolve, reject) => { - this._server!.bindAsync(url, credentials || this.makeServerCredentials(), (err, result) => (err ? reject(err) : resolve(result))) - }) - const port = addr.port ? addr.port : Number(addr.match(/:(\d+)/)![1]) - assert(bindPort === port, 'server bind port not to be right') - - this._server!.start() - } - - async shutdown(): Promise { - if (!this._server) { - return - } - - await new Promise((resolve, reject) => { - this._server!.tryShutdown((err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - - delete this._server - delete this._loader - } - - forceShutdown(): void { - if (!this._server) { - return - } - - this._server!.forceShutdown() - delete this._server - delete this._loader - } - - makeServerCredentials(rootCerts?: Buffer, keyCertPairs?: grpc.KeyCertPair[], checkClientCertificate?: boolean): grpc.ServerCredentials { - if (rootCerts && keyCertPairs) { - return grpc.ServerCredentials.createSsl(rootCerts, keyCertPairs, checkClientCertificate) - } else { - if (!this._insecureServerCredentials) { - this._insecureServerCredentials = grpc.ServerCredentials.createInsecure() - } - return this._insecureServerCredentials - } - } - - addService(name: string, implementation: any, { exclude = [], inherit }: { exclude?: string[]; inherit?: any } = {}): void { - const service = this._loader.service(name) - - const options: any = { exclude, inherit, _implementationType: {} } - Object.keys(service).forEach((key) => { - const { requestStream, responseStream } = service[key] - options._implementationType[service[key].originalName] = { - requestStream, - responseStream - } - }) - - this._server!.addService(service, this._callbackify(implementation, options)) - } - - removeService(name: string): void { - assert(this._server, 'must be first init() server before server removeService()') - this._server!.removeService(this._loader.service(name)) - } - - addMiddleware(...args: MiddlewareFunction[]): void { - assert(args.length >= 1, 'server addMiddleware() takes at least one argument.') - if (args.length === 1) { - if (Array.isArray(args[0])) { - args[0].forEach((fn) => { - this._use(fn) - }) - } else { - this._use(args[0]) - } - } else { - args.forEach((fn) => { - this._use(fn) - }) - } - } - - private _use(fn: MiddlewareFunction): void { - if (typeof fn !== 'function') throw new TypeError('grpcity loader server middleware must be a function!') - this._middleware.push(fn) - } - - private _callbackify(target: any, { exclude = [], inherit, _implementationType }: { exclude?: string[]; inherit?: any; _implementationType: any }): any { - assert(typeof target === 'object', 'Must callbackify an object') - assert(Array.isArray(exclude), 'options.exclude must be an array of strings') - - const protoPropertyNames = Object.getOwnPropertyNames(Object.getPrototypeOf({})) - exclude.push(...protoPropertyNames) - - const allPropertyNames = [ - ...new Set([...Object.keys(target), ...Object.getOwnPropertyNames(Object.getPrototypeOf(target)), ...(inherit && inherit.prototype ? Object.getOwnPropertyNames(inherit.prototype) : [])]) - ] - - const methods: { [key: string]: any } = {} - for (const key of allPropertyNames) { - const fn = target[key] - if (typeof fn === 'function' && key !== 'constructor' && !exclude.includes(key)) { - if (util.types.isAsyncFunction(fn)) { - const eglWrapFunction = this._proxy(target, key, _implementationType[key]) - methods[key] = eglWrapFunction - } else { - methods[key] = fn - } - } - } - - return methods - } - - private _proxy(target: any, key: string, options: any = {}): any { - const { requestStream, responseStream } = options - - const fn = compose(this._middleware) - - // unary - if (!requestStream && !responseStream) { - return this._callUnaryProxyMethod(target, key, fn) - } - // client stream - if (requestStream && !responseStream) { - return this._callClientStreamProxyMethod(target, key, fn) - } - // server stream - if (!requestStream && responseStream) { - return this._callServerStreamProxyMethod(target, key, fn) - } - // duplex stream - if (requestStream && responseStream) { - return this._callDuplexStreamProxyMethod(target, key, fn) - } - } - - private _createContext(call: any): any { - return { - // TODO: maybe need more details - // method: target.constructor.name + '.' + key, - path: call.call.handler.path || '', - request: call.request, - metadata: call.metadata.clone() - } - } - - private _callUnaryProxyMethod(target: any, key: string, composeFunc: Function): grpc.handleUnaryCall { - return (call, callback) => { - const ctx = this._createContext(call) - - Promise.resolve().then(async () => { - const handleResponse = async () => { - ctx.response = await target[key](call) - } - await composeFunc(ctx, handleResponse).catch((err: Error) => { - callback(this._createInternalErrorStatus(err)) - }) - callback(null, ctx.response) - }) - } - } - - private _callClientStreamProxyMethod(target: any, key: string, composeFunc: Function): any { - return (call: any, callback: Function) => { - const ctx = this._createContext(call) - - call.readAll = () => { - return iterator(call, 'data', { - resolutionEvents: ['end'] - }) - } - - Promise.resolve().then(async () => { - const handleResponse = async () => { - ctx.response = await target[key](call) - } - await composeFunc(ctx, handleResponse).catch((err: Error) => { - callback(this._createInternalErrorStatus(err)) - }) - callback(null, ctx.response) - }) - } - } - - private _callServerStreamProxyMethod(target: any, key: string, composeFunc: Function): any { - return (call: any) => { - const ctx = this._createContext(call) - - call.writeAll = (messages: any[]) => { - if (Array.isArray(messages)) { - messages.forEach((message) => { - call.write(message) - }) - } - } - call.writeEnd = call.end - - Promise.resolve().then(async () => { - const handleResponse = async () => { - await target[key](call) - } - await composeFunc(ctx, handleResponse).catch((err: Error) => { - call.destroy(this._createInternalErrorStatus(err)) - }) - call.end() - }) - } - } - - private _callDuplexStreamProxyMethod(target: any, key: string, composeFunc: Function): any { - return (call: any) => { - const ctx = this._createContext(call) - - call.writeAll = (messages: any[]) => { - if (Array.isArray(messages)) { - messages.forEach((message) => { - call.write(message) - }) - } - } - call.readAll = () => { - return iterator(call, 'data', { - resolutionEvents: ['end'] - }) - } - - Promise.resolve().then(async () => { - const handleResponse = async () => { - await target[key](call) - } - await composeFunc(ctx, handleResponse).catch((err: Error) => { - call.destroy(this._createInternalErrorStatus(err)) - }) - call.end() - }) - } - } - - private _createInternalErrorStatus(err: any): any { - err.code = err.code || 13 - if (typeof err.stack === 'string') { - const stack = err.stack.split('\n') - err.messages += ` [Error Message From Server, stack: ${stack[1].trim()}]` - } else { - err.messages += ' [Error Message From Server]' - } - return err - } -} - -export default ServerProxy diff --git a/src/schema/loader.ts b/src/schema/loader.ts deleted file mode 100644 index 251f484..0000000 --- a/src/schema/loader.ts +++ /dev/null @@ -1,37 +0,0 @@ -import Joi from 'joi' - -const addressSchema = Joi.object().pattern( - /\.*/, - Joi.alternatives([ - Joi.string().regex(/:/, 'host and port like 127.0.0.1:9090'), - Joi.object({ - host: Joi.string().required(), - port: Joi.number().integer().min(0).max(65535).required() - }) - ]) -) - -const loaderSchemas = { - constructor: Joi.array() - .items( - Joi.object({ - location: Joi.string().required(), - files: Joi.array().items(Joi.string()).required() - }) - ) - .single(), - init: Joi.object({ - services: addressSchema.optional(), - isDev: Joi.boolean().optional(), - packagePrefix: Joi.string().optional(), - loadOptions: Joi.object().optional(), - channelOptions: Joi.object().optional(), - appName: Joi.string().optional() - }), - initClients: Joi.object({ - services: addressSchema.required(), - channelOptions: Joi.object().optional() - }) -} - -export default loaderSchemas diff --git a/src/schema/server.ts b/src/schema/server.ts deleted file mode 100644 index bf087a6..0000000 --- a/src/schema/server.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Joi from 'joi' - -const serverSchemas = { - address: Joi.alternatives([ - Joi.string().regex(/:/, 'host and port like 127.0.0.1:9090'), - Joi.object({ - host: Joi.string().required(), - port: Joi.number().integer().min(0).max(65535).required() - }) - ]) -} - -export default serverSchemas diff --git a/src/util/compose.ts b/src/util/compose.ts deleted file mode 100644 index f3e6a60..0000000 --- a/src/util/compose.ts +++ /dev/null @@ -1,35 +0,0 @@ -export type MiddlewareFunction = (context: any, next: () => Promise) => Promise - -/** - * Compose `middleware` returning - * a fully valid middleware comprised - * of all those which are passed. - * - * @param {Array} middleware - * @return {Function} - * @api public - */ -export const compose = (middleware: MiddlewareFunction[]) => { - if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') - for (const fn of middleware) { - if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') - } - - return function (context: any, next: () => Promise) { - // last called middleware # - let index = -1 - return dispatch(0) - function dispatch(i: number): Promise { - if (i <= index) return Promise.reject(new Error('next() called multiple times')) - index = i - let fn = middleware[i] - if (i === middleware.length) fn = next - if (!fn) return Promise.resolve() - try { - return Promise.resolve(fn(context, dispatch.bind(null, i + 1))) - } catch (err) { - return Promise.reject(err) - } - } - } -} diff --git a/src/util/iterator.ts b/src/util/iterator.ts deleted file mode 100644 index f819745..0000000 --- a/src/util/iterator.ts +++ /dev/null @@ -1,187 +0,0 @@ -const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator' - -const normalizeEmitter = (emitter: any): { addListener: Function; removeListener: Function } => { - const addListener = emitter.on || emitter.addListener || emitter.addEventListener - const removeListener = emitter.off || emitter.removeListener || emitter.removeEventListener - - if (!addListener || !removeListener) { - throw new TypeError('Emitter is not compatible') - } - - return { - addListener: addListener.bind(emitter), - removeListener: removeListener.bind(emitter) - } -} - -const toArray = (value: any): any[] => (Array.isArray(value) ? value : [value]) - -export default (emitter: any, event: string | string[], options: any) => { - if (typeof options === 'function') { - options = { filter: options } - } - - // Allow multiple events - const events = toArray(event) - - options = { - rejectionEvents: ['error'], - resolutionEvents: [], - limit: Infinity, - multiArgs: false, - ...options - } - - const { limit } = options - const isValidLimit = limit >= 0 && (limit === Infinity || Number.isInteger(limit)) - if (!isValidLimit) { - throw new TypeError('The `limit` option should be a non-negative integer or Infinity') - } - - if (limit === 0) { - // Return an empty async iterator to avoid any further cost - return { - [Symbol.asyncIterator](): any { - return this - }, - async next(): Promise<{ done: boolean; value: any }> { - return { - done: true, - value: undefined - } - } - } - } - - const { addListener, removeListener } = normalizeEmitter(emitter) - - let isDone = false - let error: any - let hasPendingError = false - const nextQueue: { resolve: Function; reject: Function }[] = [] - const valueQueue: any[] = [] - let eventCount = 0 - let isLimitReached = false - - const valueHandler = (...args: any[]): void => { - eventCount++ - isLimitReached = eventCount === limit - - const value = options.multiArgs ? args : args[0] - - if (nextQueue.length > 0) { - const { resolve } = nextQueue.shift()! - resolve({ done: false, value }) - - if (isLimitReached) { - cancel() - } - - return - } - - valueQueue.push(value) - - if (isLimitReached) { - cancel() - } - } - - const cancel = (): void => { - isDone = true - for (const event of events) { - removeListener(event, valueHandler) - } - - for (const rejectionEvent of options.rejectionEvents!) { - removeListener(rejectionEvent, rejectHandler) - } - - for (const resolutionEvent of options.resolutionEvents!) { - removeListener(resolutionEvent, resolveHandler) - } - - while (nextQueue.length > 0) { - const { resolve } = nextQueue.shift()! - resolve({ done: true, value: undefined }) - } - } - - const rejectHandler = (...args: any[]): void => { - error = options.multiArgs ? args : args[0] - - if (nextQueue.length > 0) { - const { reject } = nextQueue.shift()! - reject(error) - } else { - hasPendingError = true - } - - cancel() - } - - const resolveHandler = (...args: any[]): void => { - const value = options.multiArgs ? args : args[0] - - if (options.filter && !options.filter(value)) { - return - } - - if (nextQueue.length > 0) { - const { resolve } = nextQueue.shift()! - resolve({ done: true, value }) - } else { - valueQueue.push(value) - } - - cancel() - } - - for (const event of events) { - addListener(event, valueHandler) - } - - for (const rejectionEvent of options.rejectionEvents!) { - addListener(rejectionEvent, rejectHandler) - } - - for (const resolutionEvent of options.resolutionEvents!) { - addListener(resolutionEvent, resolveHandler) - } - - return { - [symbolAsyncIterator](): any { - return this - }, - async next(): Promise<{ done: boolean; value: any }> { - if (valueQueue.length > 0) { - const value = valueQueue.shift() - return { - done: isDone && valueQueue.length === 0 && !isLimitReached, - value - } - } - - if (hasPendingError) { - hasPendingError = false - throw error - } - - if (isDone) { - return { - done: true, - value: undefined - } - } - - return new Promise((resolve, reject) => nextQueue.push({ resolve, reject })) - }, - async return(value: any): Promise<{ done: boolean; value: any }> { - cancel() - return { - done: isDone, - value - } - } - } -} diff --git a/src/util/prefixingDefinition.ts b/src/util/prefixingDefinition.ts deleted file mode 100644 index 9992ef7..0000000 --- a/src/util/prefixingDefinition.ts +++ /dev/null @@ -1,20 +0,0 @@ -export default (packageDefinition: any, packagePrefix: any) => { - for (const qualifiedName in packageDefinition) { - const definition = packageDefinition[qualifiedName] - const newPackage = `${packagePrefix}.${qualifiedName}` - if (definition.format && definition.type && definition.fileDescriptorProtos) { - packageDefinition[newPackage] = definition - } else { - const newDefinition: any = {} - for (const method in definition) { - const service = definition[method] - newDefinition[method] = Object.assign({}, service, { - path: service.path.replace(/^\//, `/${packagePrefix}.`) - }) - } - packageDefinition[newPackage] = newDefinition - } - } - - return packageDefinition -} diff --git a/test/benchmark/README.md b/test/benchmark/README.md deleted file mode 100644 index c516a52..0000000 --- a/test/benchmark/README.md +++ /dev/null @@ -1,109 +0,0 @@ -# Benchmark Test - -Tool: https://ghz.sh - -### gRPCity - -``` -node test/benchmark/server-grpcity.js - -ghz --insecure \ - --proto ./helloworld.proto \ - --call helloworld.Greeter/SayHello \ - -d '{"name": "grpcity"}' \ - -n 10000 \ - -c 100 \ - 0.0.0.0:9099 -``` - -result: - -``` -Summary: - Count: 10000 - Total: 1.56 s - Slowest: 30.57 ms - Fastest: 2.76 ms - Average: 12.81 ms - Requests/sec: 6418.79 - -Response time histogram: - 2.765 [1] | - 5.545 [132] |∎ - 8.326 [852] |∎∎∎∎∎∎∎∎∎∎ - 11.106 [1977] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ - 13.887 [3557] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ - 16.667 [2324] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ - 19.448 [833] |∎∎∎∎∎∎∎∎∎ - 22.228 [220] |∎∎ - 25.009 [79] |∎ - 27.789 [24] | - 30.570 [1] | - -Latency distribution: - 10 % in 8.37 ms - 25 % in 10.65 ms - 50 % in 12.83 ms - 75 % in 14.78 ms - 90 % in 16.97 ms - 95 % in 18.47 ms - 99 % in 22.30 ms - -Status code distribution: - [OK] 10000 responses -``` - -### grpc-js - -``` -node test/benchmark/server-grpcjs.js - -ghz --insecure \ - --proto ./helloworld.proto \ - --call helloworld.Greeter/SayHello \ - -d '{"name": "grpcity"}' \ - -n 10000 \ - -c 100 \ - 0.0.0.0:9098 -``` - -result: - -``` -Summary: - Count: 10000 - Total: 1.67 s - Slowest: 58.36 ms - Fastest: 1.09 ms - Average: 13.99 ms - Requests/sec: 5994.90 - -Response time histogram: - 1.090 [1] | - 6.817 [534] |∎∎∎∎∎ - 12.544 [4352] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ - 18.271 [3427] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ - 23.998 [1087] |∎∎∎∎∎∎∎∎∎∎ - 29.725 [344] |∎∎∎ - 35.452 [149] |∎ - 41.179 [15] | - 46.906 [52] | - 52.634 [32] | - 58.361 [7] | - -Latency distribution: - 10 % in 8.20 ms - 25 % in 10.35 ms - 50 % in 12.66 ms - 75 % in 16.05 ms - 90 % in 21.40 ms - 95 % in 24.94 ms - 99 % in 38.52 ms - -Status code distribution: - [OK] 10000 responses -``` - -### Conclusion - -Compared with grpc-js, gRPCity has no loss and almost the same performance. diff --git a/test/benchmark/benchmark.sh b/test/benchmark/benchmark.sh deleted file mode 100644 index 617c888..0000000 --- a/test/benchmark/benchmark.sh +++ /dev/null @@ -1,15 +0,0 @@ - ghz --insecure \ - --proto ./helloworld.proto \ - --call helloworld.Greeter/SayHello \ - -d '{"name": "grpcity"}' \ - -n 10000 \ - -c 100 \ - 0.0.0.0:9099 - -ghz --insecure \ - --proto ./helloworld.proto \ - --call helloworld.Greeter/SayHello \ - -d '{"name": "grpcity"}' \ - -n 10000 \ - -c 100 \ - 0.0.0.0:9098 diff --git a/test/benchmark/helloworld.proto b/test/benchmark/helloworld.proto deleted file mode 100644 index 0adef77..0000000 --- a/test/benchmark/helloworld.proto +++ /dev/null @@ -1,15 +0,0 @@ -syntax = "proto3"; - -package helloworld; - -service Greeter { - rpc SayHello(HelloRequest) returns (HelloReply) {} -} - -message HelloRequest { - string name = 1; -} - -message HelloReply { - string message = 1; -} diff --git a/test/benchmark/server-grpcity.js b/test/benchmark/server-grpcity.js deleted file mode 100755 index 8d0474d..0000000 --- a/test/benchmark/server-grpcity.js +++ /dev/null @@ -1,23 +0,0 @@ -const GrpcLoader = require('../../lib') -const path = require('path') - -const implementation = { - sayHello: async (call) => { - return { message: 'Hello ' + call.request.name } - } -} - -const start = async (addr) => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, './'), - files: ['helloworld.proto'] - }) - await loader.init() - - const server = loader.initServer() - server.addService('helloworld.Greeter', implementation) - await server.listen(addr) - console.log('start:', addr) -} - -start('0.0.0.0:9099') diff --git a/test/benchmark/server-grpcjs.js b/test/benchmark/server-grpcjs.js deleted file mode 100644 index 3653cee..0000000 --- a/test/benchmark/server-grpcjs.js +++ /dev/null @@ -1,30 +0,0 @@ -const grpc = require('@grpc/grpc-js') -const protoLoader = require('@grpc/proto-loader') -const path = require('path') - -const PROTO_PATH = path.resolve(__dirname, 'helloworld.proto') -const packageDefinition = protoLoader.loadSync(PROTO_PATH, { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true -}) -const helloProto = grpc.loadPackageDefinition(packageDefinition).helloworld - -const implementation = { - sayHello: (call, callback) => { - callback(null, { message: 'Hello ' + call.request.name }) - } -} - -const start = (addr) => { - const server = new grpc.Server() - server.addService(helloProto.Greeter.service, implementation) - server.bindAsync(addr, grpc.ServerCredentials.createInsecure(), () => { - server.start() - console.log('start:', addr) - }) -} - -start('0.0.0.0:9098') diff --git a/test/certs/ca.crt b/test/certs/ca.crt deleted file mode 100644 index 63925a7..0000000 --- a/test/certs/ca.crt +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFOjCCAyICCQD1P87TyGsOzDANBgkqhkiG9w0BAQsFADBfMQswCQYDVQQGEwJD -TjELMAkGA1UECAwCR0QxEjAQBgNVBAcMCUd1YW5nemhvdTEQMA4GA1UECgwHZ1JQ -Q2l0eTEQMA4GA1UECwwHZ1JQQ2l0eTELMAkGA1UEAwwCY2EwHhcNMjMxMDMxMTA1 -MzMxWhcNMjQxMDMwMTA1MzMxWjBfMQswCQYDVQQGEwJDTjELMAkGA1UECAwCR0Qx -EjAQBgNVBAcMCUd1YW5nemhvdTEQMA4GA1UECgwHZ1JQQ2l0eTEQMA4GA1UECwwH -Z1JQQ2l0eTELMAkGA1UEAwwCY2EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQDXC1Xgz4/aeZcxaaCuAxknzjhwWcbMMBgzBjEP45UoogwGFfvTWRp9BSvY -E4wnyea6ISR05dRUm24uB6Fx+BBmp9KlTVQRRub5QaoF0a7jc4UZhq0o31ViDex4 -d+IqsD/AjZJrzgzxTsh06+easjZCX0LUla+Yn4tjI6WhAez4Ly5otvBf7R4bVQPk -6xBe/GeNL7CXpBGoD2i+PV/812rVPlK1YD6nxz/Cn+s5Aff/W0xmAzYVk1lIgqUb -iaXjpWVTdjOFVOWLTP2F5rwiJUBZsBV/WvdDJ+1rqMDzntGKuuzUS/i9DOrnxM9I -zs9LIceQhiIS8HZYfpJePTlQFKohs6GFUj/AMF/6/EiUNjkeWc0eyt0a79iOm6B7 -8FgjBIzvDiw+SydSuq76LwGv+l2nqXzj8pEnIhU7co50KPfizR5ScytngamIi9hp -WONZevdMkbZLOzY2UYCsSdqoiZOCQVgE2JDEhkHCRDCejp7z9dnEaC4EY2Jd9iNI -+94rhr9HKx4jxNDmdWyBtkDIKUvhse4Mhcd9rMEJ40RNcvJhuYfgOxVRqSc7O/9o -PkR/grClMel1bBjXS61pYjh4MgSkcfROC/MfnLkSUYnlA382+06trdDfrJAoMEfL -sy3OpsjIIa55F3Bn9GhlYRK3Zxss8IXGb4QiZRTqyFB/Ka1g1QIDAQABMA0GCSqG -SIb3DQEBCwUAA4ICAQBBvV7OL40vQK+LH04KFBW/v+ba91Iiw9OQDUgcVn5PHj5n -Q5lQyLvzYFm4KJwiw+6WjPDRZisArWC88wsz3q7Iu5yjNSnWnlx44ng4NPcc358+ -9aKIVA/tiJBtf9PRd6jwvqu5uOHmavBd8vebxip/RhnSUL3xTKHJMJqY2hDWUEGa -U3KdLmYe+fdnmmyx05uWZ5Vo62e8UXJV3vUc1X4q/DBFPzSnI+EbSHoLQdrCd1jr -5FdPn0ToOyjIWhnvnj8pF6lbg8fNiWXrDwBzI/e3U6V6EKzL8HsgAkshfM7Qtr0Q -25LeyGRD7p9u3qVlxl1c2fWjmWiOb6Lq/zt0MJS8Zp1FJ80kMQjbdZkEXohn0MbH -fACkhUucvnWl28thi/A2su6F9TmWfl481x6RXjJ0UAXTuhW4sTUx4CBsvhjUYvoY -v2u7fQaFr883shwHKwFz/uM+wG0h+t67JrGdBRMjS1P00wUGn0XCsuXqFxVrVL+B -xce4DTo83AFlhs+L7BWlJ80ZHTw7XKoIBSfsURiZLviKqphjC0KUhrPCQcu3LMqK -AH9TpALcIXtyrqQgl/RlQd0ZY2+SzK7k+9wuPd5x4z7SgTpRMaShQoBby/hxzCk2 -idfiJ9Mcf4mpZScbXwE/3cFDeBW+q2aRwgjl6nPz9sQShcwlQh9PbRanryy0iw== ------END CERTIFICATE----- diff --git a/test/certs/ca.key b/test/certs/ca.key deleted file mode 100644 index 3619413..0000000 --- a/test/certs/ca.key +++ /dev/null @@ -1,54 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: DES-EDE3-CBC,F50D3CD9BB4D1C8E - -yGSFUasNYJeN4v3N41xUnWec3jh06ZY7YPMEtgbG82iegvJxj9eOaPyvzK/HOXt2 -PiMIB3IXmUZcGcQMb2GfGFCeAATsNKQH/V0rBNeUugTSm3DgbkMzFaiKggZDUo58 -IVMLAdDxcc/YVpJDfmLfPNdz8VscUUxxQ/jEXMXlFXKFk5FjeNrtW19xjUSkj+kG -JCEL0ydoLfFfakJs0tjYFMwmdTxcSU5RobCDHzYKh76AFiHOE2Y0u+VESKLNKj1u -UMDEAPu1fksdmyP34YnwFVX1nF/gAQ+EB6ofO/2rERDm+KKwiY7sn3VcmWsEc6u1 -JnbVIuwFaHTKXB/OtD7jQU/ok60HJ7b5+HafrxqMlh41bKrMe9+7pEGJ5n4ddU+S -05DtpkS9psVM7Smv2otDcXlkmH1BRhqF9FaCHWLovQLm99Aw/ANJCKbXOmpsgYgY -rPWcACq2lW6K7+iepXzdVzXAH/Al0X9S8OySB/Qnp9O+WYJKe0IjyksILyNEDynE -ISKZuX7I+VOi8PNooP35lcXupzkcQ6N64dTSo/5C+7hlBtyCExwISWses3goHp0v -PsZfGhV8y14bLY9n/84T+zV8+oxi4BEUS8QyVA8qApylIzAIQ73asYqWI5yQeJU5 -o86PAXCQZOdjSiNkPbsL7HOIQLfn1OGvS7x3PwWFvdfDmMGsTuK1ZVsGSwltYsoL -U6Lu/QFjrPXT44VzRCJjx6kUoWvvhkeTmu+jXEkW/FBNg2PouJj5IP5FEMShJCys -0FsIp03kjM2Gd1OsTXoEPDUmcx12nFvvgnXZ/okgQChE5g4kr8YuuzeJlr6Tgcdu -0psu5ny4flaMuo72dNDHXajMKk4sVNa9yJ746CEgIgXfugUGFQHwwBH+rUQwALsm -vb1M6r5qEBOCZUo0p8vSVCyKgrN5bzHbhW4Ni1/XldaB0PEymEZyVAmd4jBjVlPP -mntPLFt9N3D5M/NuTr1NOtGJWe4roCP8w0A+nKv6q3DLfdZSUF80XC36zl43Lcuk -emGj69Bt2J5z3kbPDiefe6RM5mq5T6Zu/a6Cq7gy4KMEtzusouE8Pis5NoqwKgNL -PYFp1+cjFlxl5yI0+UeF1R0tx7xXvZmn8kipVsj7CvE92CpsE1iISMJBh3o1nixl -YQBkbFk5oIj5RSOU0NYs7efPPr5rfXPdpfC2FGgIRwDQqan7TrvlbTcjMfC9QA4I -mqffVY8dIapcBakKi2VR1dChQ9tZ77ZXpvbluf7gYLmdcQ2poCivXq3PwoemWRd+ -RV4KHhZ1SHwditvnLcv47uSmDviWoJSBxqEmAtPuRtCX8DtSHh5WFvnq2f3/KrPq -A8xPAMG1cKaUacWarH5a+8A44ksTru2eCtwX1zE60fYwdc8ZYW+M4m/PIKNQWtbt -TS6EjVwg/+8Jp6O4FbfDuJSKdkFIvmricB9fwGjtPfE9lTcu8KqLq1F5Z9U18MUH -baZqCBnL6vADkCmoYGmNM+37v7KS9RlI1hzANNOmeBTtNsG9efvaot+cAof23SNs -4buOnXv9C+5ciFhRZ6m8Yqb2ljU2EHc8dEPVWoqZCrvjrMU1mOh0tsqqjVxELVNO -I+2FIckRV5Xhb74dJs2IoixzDz7gd090AFJhXjcrMMavpAacpIuJG9rEnZbcRJuj -joy6TWPwQxJKuaCeGR+1Ujs7zDG15l0ikGjYdVb0kUnmInx67J5ThfyzWTnn2fvl -RCdf+/gox5cD1yCyJefVP59VNlQzzuRKMdQ1si3cHE5VsLK/2czns6h/XUSRePx7 -fYqjk7vsq1LVwePE9azOLFiG+VZ65NDZVnFtKyWwDLjYS7n9ewcvA1oRLs6KnP3Y -MZoI0hnE27qCfZzfJkJXIlvcJl/dCQDOo9MJ1NM9TmuYMDnHk3LFjH9nQ1OTSFVw -MFFNL7PwYtdjSRm+Enl/lzmoSRELYivPgZV9SoxVUPJqrn6wJ6NR5FYWtyKVL0Je -NYPy/u9B6OfGJJ452NgxHXbCJbkr3TjxNA6bjsajRIXEginr7a1NVgbobJErc3t2 -l1bgZQR6BEvWOHvPJGT5vLlRDGE4DKPcD58MCBfgUjQKbF5BKMLgNTld+jV5sIia -m7/SdNOSX+ou2IMStP47esHsWLkIUFHyV7lwivTKCMO2KRaSyjiJHi7+AP5M4GV0 -mdtBqQp1AdeD/IVOShmE9eAeho1JCokl8QXhw/zjuGXikGRM+9TEIvRtCGBRCYv8 -8hp5H6Csl+C8qhXsFjDUTVQqFfDlSSjCAN4l15m4lq4R5uFf8xV1on5tGwkfqSFF -Gbgc1Vn9ieWH3zl/cmdMA6n7viWfpQAkgiM7IrzRR0Uyy2lgw1YZj4LDhT/J7qR6 -s4pgitbQ2SxBy3xbxPMSkLm2XoMEjl5spRnB4txy3gMgP+ReUTQDJmsdqswk6MTv -zTiQqVN67ZyTrUVNnSYGOUuqYZcmwmHy1uY9scJr4y0tnDbmxfDzU69XUuVIat/a -fDyZSWuVAd+Zbt3ueq8dQxfr46FnKBrNLyInCejIgOlLqc68Nji5RqnwfvxhYxcB -aDLi+h43ZBDiy4D8JbTCGtr4BCBZ7/YU7e7/i9elmFacQjV9m+xvfUrVnS23i4Ns -HIcbp0KAXjSEitahr0LjcxN09rjKtglqfQRz70uYnN5iBp2N3qNJMdda7md/szZc -5fr5W21Rb6HJ+eos/OkhwQ2ve3wx2QfsCDrM/sn1ds5IXv9lzEsuGLGAUm3LF46T -ZJW+weGWjYBD+N2HP19QHV2uebsdpAZLKpCchid3+21SBXcjhHqOzCoFtS4v+wxa -W+gNT0WxTHqWw+JyH7eqvwc06lxOOkLkX1dTGAFH3aeCZ2TEvFN7hMQa8dULrJfV -+c9LVhht3fx+hlFIk+X9QJBURgtVarHAR3E6unMpjba5Q8W9bcPDpCnmPgi2W1+u -MYeVk85RbvIItM4rzlaK3fIsQql4jz9i/T+NSTKCM6x8G2JHTz7bYMi5ZfJ7IaaK -IM7S6qIoumAy/WzJyYXiZX1L3yVbxJORvw3W3LHg0WLP3x8BweKhYKPMOi+edPmd -W6T43wECrwGKPsEbAkSEEXto+Wj3XmDpTMQlvQofVoM7uIe1R5PPUuRRpiggNeA9 ------END RSA PRIVATE KEY----- diff --git a/test/certs/client.crt b/test/certs/client.crt deleted file mode 100644 index 08b6b81..0000000 --- a/test/certs/client.crt +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFODCCAyACAQEwDQYJKoZIhvcNAQELBQAwXzELMAkGA1UEBhMCQ04xCzAJBgNV -BAgMAkdEMRIwEAYDVQQHDAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxEDAO -BgNVBAsMB2dSUENpdHkxCzAJBgNVBAMMAmNhMB4XDTIzMTAzMTEwNTMzM1oXDTI0 -MTAzMDEwNTMzM1owZTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMRIwEAYDVQQH -DAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxDzANBgNVBAsMBkNsaWVudDES -MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC -AgEAn4Fr3uQlGGjqealTYN5SN6tEM7WzD+VY+lt7f4NYdr2PLxswNbsdKk1dxvKr -e0RbmcPEZmQDKFI0hk2lM3/xZ4WkKI+IXNRh33CA/96NOeacxMpSnFXFhTQyxBaH -efR3OzLus3HCmxgfhktf6B2Razf0BehzXtEJ/LNnzO/zhgtpFDfuT4Ee9V5eYAzi -GLT/wgCRl7mnJGMf8QWGJndL4k/uKYEtNyjxCY1+g84hiLyEYsek8aBdoxfRzDPz -zid/4b3euNvwLSVQW5KBB/jCq4hI6ahZtSbKf9O1a5jW2APV3HDk9mXfjx/l6KMc -6OIu996kmiLSYDDDpLecTSBVuGIQfZw5A1ja97Z5M5/u6KIyjnMGuvC4ZqQfDYNC -9U8+M3dFWzEgqNflqdIK08/WsWjoX5Cwi2x2WuXPkiBZk31hgjifgWvB8AMqEVdf -ZJpuTMZxbjcwY1OUrET/e72DnN6Aev0Gm7czZrkLYwYSp4dfcQSgZ0bNM3O/XBMX -nEvRGtdITLYx4/wSaNcFs+E0fnvGbV/q8Gk4HehZCLmruEWM3VCfWuqN7QyLfs0X -ys7hi3a2YCtWDR8AVg5EiZUKT+qtbh3GuDg90nbDTnDzg6Rt5fv/vBfyGiNNan8M -JuBPzcqE4Ii+L7BBhOZultu3bVq5n3t763JJByyj2i9MSHcCAwEAATANBgkqhkiG -9w0BAQsFAAOCAgEAx8ctF/uR4qmiBeFfJOSk/aNDYTJLrW/VtBPayuawFfhHBlcJ -9pZ7xoJ+bTJSswxhz5YIItZ5DN7JIWCHnqOfYyNIrEvMrVxfma2TYELtmB3SXjst -e7K/pOu1Z2LiNogUdcbHpuHZtwQdCMukTBbTiUoFQyF7Ref506g8g1CnIS+8rCJ6 -F4/cCWEh/8rh3mXGUj7rxgTui4OI33ogRR2DrnDoWU89fxdFbEGWMj52VP5zozaI -uX4SLfTnpJtcewwUJCJXrF0rGH8l6LHXMOj1TnvveAkK4iegYddi+dJQ1u+/3f5n -MzXbYB+gUU/sERFe9mztttDfNqW0UQtU29hXyOCS9QUhyPzxT0jGfrK/tPeIkI9Y -sPsiG3awmnZ+7Ye8H2fctFEzSAufABhA4W16DiM0wYRwwbRuRCEdZplOMAElZMYW -G5++9/wBQEg2MsZgKP67Rn+ZPEEQrVC5ycm3Zbe1gCzDuTXzsNOpHTH8j1Ncje/G -W72/NNG2rK5Tp/TS/SAjr3/0xPRy8TwZNOthmCAlxtr8jKnjghPrdv4DgkfqW8lY -2nkUGsGC/Q/ajXjpZZA2x6zlh3Y0p5J+2E5Ppfgtcw62O5oyPq+081/TnfieQ7RA -PEjTX5f3qUO4pwxFAJq87QJV8egO1tbkZZ0guExdxo3/hiHZSrTFx3WeGys= ------END CERTIFICATE----- diff --git a/test/certs/client.csr b/test/certs/client.csr deleted file mode 100644 index 4a722a0..0000000 --- a/test/certs/client.csr +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIEqjCCApICAQAwZTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMRIwEAYDVQQH -DAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxDzANBgNVBAsMBkNsaWVudDES -MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC -AgEAn4Fr3uQlGGjqealTYN5SN6tEM7WzD+VY+lt7f4NYdr2PLxswNbsdKk1dxvKr -e0RbmcPEZmQDKFI0hk2lM3/xZ4WkKI+IXNRh33CA/96NOeacxMpSnFXFhTQyxBaH -efR3OzLus3HCmxgfhktf6B2Razf0BehzXtEJ/LNnzO/zhgtpFDfuT4Ee9V5eYAzi -GLT/wgCRl7mnJGMf8QWGJndL4k/uKYEtNyjxCY1+g84hiLyEYsek8aBdoxfRzDPz -zid/4b3euNvwLSVQW5KBB/jCq4hI6ahZtSbKf9O1a5jW2APV3HDk9mXfjx/l6KMc -6OIu996kmiLSYDDDpLecTSBVuGIQfZw5A1ja97Z5M5/u6KIyjnMGuvC4ZqQfDYNC -9U8+M3dFWzEgqNflqdIK08/WsWjoX5Cwi2x2WuXPkiBZk31hgjifgWvB8AMqEVdf -ZJpuTMZxbjcwY1OUrET/e72DnN6Aev0Gm7czZrkLYwYSp4dfcQSgZ0bNM3O/XBMX -nEvRGtdITLYx4/wSaNcFs+E0fnvGbV/q8Gk4HehZCLmruEWM3VCfWuqN7QyLfs0X -ys7hi3a2YCtWDR8AVg5EiZUKT+qtbh3GuDg90nbDTnDzg6Rt5fv/vBfyGiNNan8M -JuBPzcqE4Ii+L7BBhOZultu3bVq5n3t763JJByyj2i9MSHcCAwEAAaAAMA0GCSqG -SIb3DQEBCwUAA4ICAQB1KoPFz+w5zG6W7gpYYAxmwQa0CcriO0iEMVQkwv/satoY -f6F83tI0HCsuEwxyMI8IPd/LqvoqFRKJI+gBuou207NnvILj1P+QK0jiGO4fmK+j -SgJrxdqSLdJpeYGiqlAzVCfTMh7Z4vgIMCTgSWBqq0yBE/kQVsvuNF+/JNSOjtSi -K8jehp98RdiHNR58TTlywuQd4jAs5XBQSOYm2ye03kfHTDJ2ZKNaPobglrLX/hKt -cBW8cwO5GIhkFAPc3KUPS0iJQUkHyP23lMNV8qtmg2Edcd75mnnxdLp5vWOFb+RU -SalupDfRVhmO3NuHJeNvIAgsEf7tQmCO3CCJYs4QYNY+AUfWZQe1flRfda69/CAF -Sr91zel4XRH6DyR8VVd6KrlxjXUWJsdR9TMEsGW4eWYH2qLxZ7a+Hw3rdAnwvtLH -gO3rizcfA8T6W52fh4Bwp8LxOyBlPN/2bChTbTdGre5dcdub6c6ncOnpmW/CnPeP -yRu0dL3mKMcxerFmBc9svJW2hpgBpPGaV1sxzgpYaOkpN5IPUK0O/kvL8s4RsWcY -fsLNPIiFlrjkapiLaVeim9DOSKJl9kO5HWOUQ2ccc35E5EwFTmqIf0ix6eHogsg+ -FXR4OiiOoGVxPyoUewdytGgpIO9XKke52eytmFzjWV1sYf2xzlyAaim1aF0b9w== ------END CERTIFICATE REQUEST----- diff --git a/test/certs/client.key b/test/certs/client.key deleted file mode 100644 index 563d3cc..0000000 --- a/test/certs/client.key +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKQIBAAKCAgEAn4Fr3uQlGGjqealTYN5SN6tEM7WzD+VY+lt7f4NYdr2PLxsw -NbsdKk1dxvKre0RbmcPEZmQDKFI0hk2lM3/xZ4WkKI+IXNRh33CA/96NOeacxMpS -nFXFhTQyxBaHefR3OzLus3HCmxgfhktf6B2Razf0BehzXtEJ/LNnzO/zhgtpFDfu -T4Ee9V5eYAziGLT/wgCRl7mnJGMf8QWGJndL4k/uKYEtNyjxCY1+g84hiLyEYsek -8aBdoxfRzDPzzid/4b3euNvwLSVQW5KBB/jCq4hI6ahZtSbKf9O1a5jW2APV3HDk -9mXfjx/l6KMc6OIu996kmiLSYDDDpLecTSBVuGIQfZw5A1ja97Z5M5/u6KIyjnMG -uvC4ZqQfDYNC9U8+M3dFWzEgqNflqdIK08/WsWjoX5Cwi2x2WuXPkiBZk31hgjif -gWvB8AMqEVdfZJpuTMZxbjcwY1OUrET/e72DnN6Aev0Gm7czZrkLYwYSp4dfcQSg -Z0bNM3O/XBMXnEvRGtdITLYx4/wSaNcFs+E0fnvGbV/q8Gk4HehZCLmruEWM3VCf -WuqN7QyLfs0Xys7hi3a2YCtWDR8AVg5EiZUKT+qtbh3GuDg90nbDTnDzg6Rt5fv/ -vBfyGiNNan8MJuBPzcqE4Ii+L7BBhOZultu3bVq5n3t763JJByyj2i9MSHcCAwEA -AQKCAgB7HDI888wp3fhz9JwVHSNKMldOrgRJ1ZPqkCdu0Nq7sy1Lh2mbXxNwrgwe -XPi8CJPGx5HUEYdaXLCLpGxIpoyVgVGluLrgI1BzW+tFEeng8by4Kwy9+3mbiSFR -ta3VFDneqD4SpFA20tSqG40no4K0xZgD41kAqslRkMsiI2XLZQ0yfMTj+l4BtleT -PP5ZYYIxo6y79aAq9pMVvVTAZb1dLKI+yKQ0edYosFwgsT2ywwZPE5acpFEBa4YT -XVnlGRmcC6dW+PKUdcNjGl2a4IaoTGUyayzqI8mSBb52EJ5qVfN/1Gb0QsbdOhqC -hzruCH2F9QMIsK29boXioZDy+m0NzhsceFGq18GXCl1CEJlWTZKImLy8HY4E7ZMH -nK0iY3ccEBjGeaJthfvBEavztT1MMk20XHB+hQSncrL15P2uDdezrOMqxJ7JAVit -XJLZjiuCFYY3ZcMHEj3wFGqS/tkCPOvfw5HSc+GTbZovIBJy8+p9h9UD7IkhtzXW -x5HaajgOUOGQbY96JPgB7HKPTnmHCNuskjzebZJiTvVdTzczhE5kKWql05YPjIup -9ARwJWd3+EAFpAfA6reTCinPuqrLN8xqnUbyp7hEd9MWpReD23qvGzNTyVBI2Ulf -0TG16Hkpv7ESlg93aNaM5lz6L+2m5LKwcwg/WHmycT/5CS6E0QKCAQEAz+zMpDK6 -2zaK1H3ib2Ez1Cj+FRSHeKS4FQWYASDUNqpKInSQIjaar5Den3kOkXM8/YrPMLo4 -ju42XNFipHuqEOhwmd9vpZ45/E1LeKwJpSBV45P0Y8MwOotO5khixTSfC79yaR2w -7FiSiYwXlxv/0RZg8UhLQInU6KVU8vTd1pizrw7VbTSYjteirqQ818n0SkCH7JQS -O6vgEoqLkyH2kaPBE5RE/DpLuEziC/F1g7NgdOlv0N7Mze/2kgl0jP8waKnWorol -Rl46MSuQK4FAYs7XddYqYcsKbOICRrjnRN/xNKqtBL7VoWoqB2MqkUaufGGLYd2u -++ph6TqJStZcqQKCAQEAxGKlpnnj+Ou0j89a8Bb19eDXpQLFf1LTTHtbm5Dp+96M -WYsbBRI2pLQgmDGnwzGh1rbxVm2Tc3ujxD/9kpfEKsmfOT4pIF/G2U9e0RnE0Z6D -iTVMs8SWqpSYkQsfgGYGDDdHsO00Jds2QFzHtGmcPKhQO2oRLQzgWdcbPvbMqzVk -3rBcuXM8xyW8busYQmyKmKwqUihCA4mZriZ87MeOyBbWyuN7cW6ii7OqWEi4xfTy -UgCH+awjiuS1QAf04vv3S5mh/SOU49HHASpVGRNqPHDxbyCsCmW+93eiSXQ3GUXl -EsikrHCHPvn7y8SXeLvtbtPIlNbsIsHD9BQKvHiQHwKCAQEAoEQC/LFJi/x/iAg8 -B+Phgi/SoMcBIMG+Th3Qq1X1nOknWWWFT4nNM3Qz1LIHw58SrM9YolN3ktwUNPkD -0oqrbHrth/1MXlkWkt21RZ89k/TXnyIE5vylaQrF1wSGdUD5MqHvewxyucoPsUu1 -RzlCtpRMRs6VinpzDJubXeXWNDnhjhad9Z3r1XZqo7heWWoGDVGuM3FymGIDxeba -bJ9qIZoaEZBgmBYLFVTVi6UjEk+qEpN4J4QEUtwarzfwiVmNo679jNJ0NsgcjJfq -eU4YJdEPDHn2kwhg0cpnPMH7KQCAODsyP40kt1VQbf9G6VGU3rSFLfskjHJ947rF -5lRSeQKCAQEAmPQH9nAq6X2TBQrH1gTcDmPPMNrGvZLhtUjCoZgtVRkRENCx+7Ii -0wbj+AV8lx954AReVSVE2YXrl/cK5PjFNVoRZAERAQD3m9sgixVZ4LVn1x6nHcA4 -ZKUVaqpSH6vWe/82HAuzOOTSDTD17YLvx6KD0rKarA7CUdaihtirsZEFfhe4MEwb -gzPV3kHGhD2LeLtmvtRSDfVGt4eMdtI6V4bKRf0E8OTtPodxXg9NsghEDzAQt5ml -mRDmRfeseHksMzp2GvVyijmhmDvDSaOAc5C3ygiVVgfGw3Du+ezE2S9B6e5Rq2h9 -PcRvo1X7b3JWy6GxMJNwGOX3W4ucjQwNmwKCAQBximzQ7fRnm932y/w3u6RRKWb7 -FsKbNzcC4ZP+W1ecsj7Ck5qU5g2fAkTqpTTVKjMf9HDRtvzuBqgAWFS7G8IzJ98v -d2/o5m9vldhr76VI9aT8Oxh3fR4A35aaroP2y1jgdsQNizVgM5ujFRdVmVHNNgrI -v7ZoLQC3PFr/u0LDoSd8/pbHCLqA1JllXURj4qfqzdhEh0RpJF64VRa/kueVT6JT -ty9gBs7rPyvns3Jxs8eP334oyCpUYyBiS1o4dNJKryr4woIKSTzpuExNB/JVK7kY -QSF96Qt0Hjf3fxyf5W/V9njDSmOLzn/WPtGz0QvnUXyje512Rt/mJvwWyH2s ------END RSA PRIVATE KEY----- diff --git a/test/certs/server.crt b/test/certs/server.crt deleted file mode 100644 index 82c7185..0000000 --- a/test/certs/server.crt +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFODCCAyACAQEwDQYJKoZIhvcNAQELBQAwXzELMAkGA1UEBhMCQ04xCzAJBgNV -BAgMAkdEMRIwEAYDVQQHDAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxEDAO -BgNVBAsMB2dSUENpdHkxCzAJBgNVBAMMAmNhMB4XDTIzMTAzMTEwNTMzMloXDTI0 -MTAzMDEwNTMzMlowZTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMRIwEAYDVQQH -DAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxDzANBgNVBAsMBlNlcnZlcjES -MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC -AgEA1c5i2MumzL0VH3tbUZz7Qm02O0zRJVw0AeHTtnXkRHMrny2L/47BxxOUp0FQ -McDIVk1rlPZ4ukywgksqCfOtDboj5D97RQvaKW2vGeUL3A44ebzV3Uf0D8QFloPa -sqIBjQfCUM3POTEDqfe02LP4SjMzIvCGK7qoAgfcUavSo92U9h9dpi/nW33jJQ8I -piY1hCCWdc2K3dZE8UvjAZSPCEm0LHgUfh1fjJwTZY5MSIspn0c0d6eCS5B9irdY -dj4ZtLxpIRABhn/OUjWJTLn2pgIAm3JIeEN5hgvoU1qmacel8uuUiZbXYIwrKtwM -Tn7gvLJVS3sW1ZjdDTbK+twh/CwGLmrOtjBusetU4luo+th74stSaz3E9mAhh+gQ -6D+oAoRwwqEIyUJKe7lb3uFx8K2j8nI4fwxBa+B4OpHz1fLp7IQPfQ6pkCmyG3nr -KHjOdpgk2x1msr2QAoMiQjsOs5kGGuv2MRjm+Stnqv++VV9P3h1Ca7wTRsWVLuWU -nLGifExfF++621qy7S6v/xJJtaSN6CJzMFMjNHL9Fncsr/GLYmhNIxGq2mGcIFd5 -Z4hYRbPmTe+VFTaCLPSkMwJ64Je6nfjOLPtQz+brnovsrZdITBF+oKQD5zUeIFGE -7aizWHM5jLqwWK9dv/xj5UScmQ8G9H8vxAFW30QjVcGx4SUCAwEAATANBgkqhkiG -9w0BAQsFAAOCAgEAMQZYWnkxz3ElDDK3jh8JG7jR9bzVLiiKo6oMgrGdjEp1qB6d -9xNFlgxFDPwqPTrNyuUD7L+466TwJGLGsiBVrZ0YUk3NliqFf8CUTTSHiPC3pxhw -UwrrHOxzw1yDogfJwrcJb5l8L6AtP9IdeFf49PakwKf2jxUvWCuoz/sKq7OlPfC1 -4IDveF7zhGFvgk0LYNWleyXL9Y0kpmByXEWdA+3z6v2GzJJbZ1GoBli91+gpLLvt -zUJwGBCkwbhBHTHt3nx0DwgsuJxiJd/+ETYOQyBeYvgMHrIo9KnMQ7/v2flYbuSq -9z1t+JIzRUORzt2UmDQMebnlWMFLx1P3HP7RxRAqoBGKCwz4m9zFTYkB/QgRCde9 -HQz47tLhnT51W26lNHmVet7XZ1cD0xz7sUVxIypqQ6Z0D13xbP4Ii7sZ3KvRkd05 -11XrXqXO/WPSLrGTqS655jNsXdMDFOFyhPQw882XiXU/CCS5IlX/AAEqtoSdkdBq -PrReEH9lHjmshJCBWimNqsSv7SiX4afV+P5DmXpra/MgFR8YVxAVdEz5Qz7+jtZM -lMEf2CFEwkYxmpwYR4+kJfP07RfONOQH4huZSZaAOse/+dFKKoVRpOGmkFE4zS04 -VlzKtTIAk9btmxPlvdz9DfLN7AvDkMDIBGDap/K3heNxd3GOmziwtoZe4Sg= ------END CERTIFICATE----- diff --git a/test/certs/server.csr b/test/certs/server.csr deleted file mode 100644 index 3d9f12e..0000000 --- a/test/certs/server.csr +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIEqjCCApICAQAwZTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMRIwEAYDVQQH -DAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxDzANBgNVBAsMBlNlcnZlcjES -MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC -AgEA1c5i2MumzL0VH3tbUZz7Qm02O0zRJVw0AeHTtnXkRHMrny2L/47BxxOUp0FQ -McDIVk1rlPZ4ukywgksqCfOtDboj5D97RQvaKW2vGeUL3A44ebzV3Uf0D8QFloPa -sqIBjQfCUM3POTEDqfe02LP4SjMzIvCGK7qoAgfcUavSo92U9h9dpi/nW33jJQ8I -piY1hCCWdc2K3dZE8UvjAZSPCEm0LHgUfh1fjJwTZY5MSIspn0c0d6eCS5B9irdY -dj4ZtLxpIRABhn/OUjWJTLn2pgIAm3JIeEN5hgvoU1qmacel8uuUiZbXYIwrKtwM -Tn7gvLJVS3sW1ZjdDTbK+twh/CwGLmrOtjBusetU4luo+th74stSaz3E9mAhh+gQ -6D+oAoRwwqEIyUJKe7lb3uFx8K2j8nI4fwxBa+B4OpHz1fLp7IQPfQ6pkCmyG3nr -KHjOdpgk2x1msr2QAoMiQjsOs5kGGuv2MRjm+Stnqv++VV9P3h1Ca7wTRsWVLuWU -nLGifExfF++621qy7S6v/xJJtaSN6CJzMFMjNHL9Fncsr/GLYmhNIxGq2mGcIFd5 -Z4hYRbPmTe+VFTaCLPSkMwJ64Je6nfjOLPtQz+brnovsrZdITBF+oKQD5zUeIFGE -7aizWHM5jLqwWK9dv/xj5UScmQ8G9H8vxAFW30QjVcGx4SUCAwEAAaAAMA0GCSqG -SIb3DQEBCwUAA4ICAQB0Y1Rj2jdh9ISY/S3tSHsK4Le1y3aizF/pRoa1H8MG8LWb -2I0NNGUnXcpipyrjY5O6sn1X6Ea7W5SYWhijfVlLPXiYV4VWTdDIjI3c2PI6c1Nb -bART4TnFqcRdeifEsMOzUujMMf4MW/I4aW2648Wovid3Eo2YWhonWtj+Es+h8OEj -euhUFpeJSKy3RYqAqmG7+W4Bs8GdKjG6FIK7Xtf5VL3HCeIzu0mX56dGE2zrIblM -Fc8WUet87dbGMvzVEkeM2WAu3I1MNOzFU3hiD05G9REQPAooRgWQHNL+7P2ChBC3 -Q45vj1MKjR6bteqBs6unZpfvg9FYw0PFs1OHAmpPyG3OmpNtNL39Dxr0dXid4yp1 -XCLlOmaz8ff+v9DZrIGqyVQkpSI3pHKXchJTa+QFYk5ODs7fA7Uin6r84WNuCYZ5 -OjzGMlh/y15eR+Rpu6dXz9N/i/WJcJCg9bN6yvV58pyWj8iERNXjSbZaGQDfsSMN -EoEbqtBS/bFJprtUWDSBIafVXBfSDyAMH/rBVKMNLpnsAUON6DFfqgtKToFVVOiB -GvgxROb+oJnVMfesoX+TrtE1b4zm1aLs20BW1HLgkhMi8U+M3wxGHkZ+q9uQRXkH -yZvQh8NA5jCPcG6XqglfDlwWiOxe7tgsxLZyMmkpOe2Mome299578FAvIxe3XA== ------END CERTIFICATE REQUEST----- diff --git a/test/certs/server.key b/test/certs/server.key deleted file mode 100644 index f7b6d56..0000000 --- a/test/certs/server.key +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKQIBAAKCAgEA1c5i2MumzL0VH3tbUZz7Qm02O0zRJVw0AeHTtnXkRHMrny2L -/47BxxOUp0FQMcDIVk1rlPZ4ukywgksqCfOtDboj5D97RQvaKW2vGeUL3A44ebzV -3Uf0D8QFloPasqIBjQfCUM3POTEDqfe02LP4SjMzIvCGK7qoAgfcUavSo92U9h9d -pi/nW33jJQ8IpiY1hCCWdc2K3dZE8UvjAZSPCEm0LHgUfh1fjJwTZY5MSIspn0c0 -d6eCS5B9irdYdj4ZtLxpIRABhn/OUjWJTLn2pgIAm3JIeEN5hgvoU1qmacel8uuU -iZbXYIwrKtwMTn7gvLJVS3sW1ZjdDTbK+twh/CwGLmrOtjBusetU4luo+th74stS -az3E9mAhh+gQ6D+oAoRwwqEIyUJKe7lb3uFx8K2j8nI4fwxBa+B4OpHz1fLp7IQP -fQ6pkCmyG3nrKHjOdpgk2x1msr2QAoMiQjsOs5kGGuv2MRjm+Stnqv++VV9P3h1C -a7wTRsWVLuWUnLGifExfF++621qy7S6v/xJJtaSN6CJzMFMjNHL9Fncsr/GLYmhN -IxGq2mGcIFd5Z4hYRbPmTe+VFTaCLPSkMwJ64Je6nfjOLPtQz+brnovsrZdITBF+ -oKQD5zUeIFGE7aizWHM5jLqwWK9dv/xj5UScmQ8G9H8vxAFW30QjVcGx4SUCAwEA -AQKCAgASWPuyjwdpWnCNmxBjGI2XTbfxrs3j3t23q6F3bvZ/yUJdDpONArsuUkfX -cotnOZv3i/1Hcz84/YvIIpsg74BSRT7/P1NcwX+fPoJgPn+eCrpd+A6CRsJy9+di -2z3RRoXLjHboaED4L3SZCWDDl+4er/YbcXiSGBqC0hneCFizJzi5RkfLEyFPgKYV -cAzV9UFybTl8Mn9QOisAafq5D/6WP3zus/9OM0cX0ez4MhXpw8d2m62s9vr/cQ7U -8JbuzrV2BRUgeTcoS59w/pchtnOsG0/iBxSg/WlYT31IaecN/MwI5J6CkCc7acvq -iaektM6kYslfCNkUy2Fs2N7iWDGyQg1Bqaiz+Hhuvnyc+3+6oblChsEQ4STnpvR1 -L9UMsMke31q6FQRS/rOljCjVpLrv3NImz3O0IDZ0aMcPqpJVNHvyPk45GUNdVJ7Q -kG3eRlkUAtT/YKcQHtEpfJkPVyjLQ10vwV8vdV8Lnz0J/grWXZhOVwgVM+Z1zLxw -J1mjQQdzLNezKJp4iImKPigGQzmZjkuJQPJ9dpIJZkgXlv8pOcTt4X+L0ZhKn0bx -ASDdQEstkIhgha/V6Wu+qkHhDlQGQdRO3SvTS2VsdGsV06VrD1lziGdZSUlv2cGe -2cWH5TomzzfSvJx9DsNsCgJnUKS0u10UfI0P1K5sunBXBEWaoQKCAQEA67vmirK9 -SDrv4FTTPbILyBAjW1r5vNaaeO6/yl1LNA8scituLaGzVWP4YsY5/Ltn9QX6ZGBQ -7pD5BoYOE/hhnOrEurL114/J6mSADSt21Izjj0gDdo7TEOeGsNoBRsGNDm4fA71S -ZeAFmAB6CROs0KmdiUJrJoWgvofDBU/OFNInGEWjCRLnhLn0vxkz75x3IRUmtIlb -72L68t3FsN0HpY04Tvjor+Bg1u7IIgtoO7Usg0P3V0NS/SkCpvyUFk+kP3FXJIZg -D9EF0JuhHlqGbfmRD6aKODORkgwunmL53qjAGHDucQ6ScTsyYYhmVGA7enkTOaHs -KiPxey4I8gSYxwKCAQEA6C/kgsc7fmWuysGhgG+NxN0rlyGzrdKlpo5cIZsCYOiZ -9uLxaaSgh7pBzKTbQ13V7sjGF4/qNINyhaskxUPuYZji2+l7lfENn0BC+Ep+1RUK -TIygxYDOSqR200I/fNpU9roSjN4oakO4S/+3MisBBuFuIKKWEe1X9AO7Fs9uWsdm -2gXK09P2nLS/RxCVdPtR0YhvSmuKaw6E3aRUF/A9MX5cybJNRBt9M+/4zBquCldz -sgCbJN3LRUo7JTV9aTTb8R0uuk2bEPlsanPwYFBM0Podm7/5/Py1oNF2GOVtSn6T -jut88qJtPb+9bQuA/BHhCV10UMW5LCBK+BFGC+qCswKCAQEAkHJc/BQ2Q/JJVm7j -2eQlr/ujjrEaYoolsYCqaPftwwKhO3w9URzKZMKTSwMoOSMkulFlcuhJ70VKMqMm -MTp7pcl9ruFGH0ZudYALrwY5FFkYf42jAZzW3H7iW6/aJF8pbfQwcPwrZ110UAXK -wZEazemBLMBUJBCxxM4vxCt4ne2AIzFYi5DX6M6BmC97UZHQtabeRrX8bZ17JCKc -pplpvBnirRF8k3isHkfGvoW64wtLMUwOXZxVV+cvRt9yZpF6lZ1/xlPIvShZpdAX -VCAoS28nYi2seG+w/YsVbdw8PCGvQ8q/cOt69INPhdAs0/r7tzpFe4Uqz0+jAtXc -iWAjtQKCAQAL6/GauWoXmlb4OCr3skKgOg7z6poUMb2pqKOYYiIkIa1OHObyWq6X -aXvZaxmLAvVqFkr9iLkoyxsFO/1eV5eU0UnMqtdaoo7lf2Iw53pNrI/j0FCs82kk -Mf+b50nlOjykkndDXmDK3AFJfa7FV6ns1YRXDqIP95TNhaeEi1AKPzTLuwJoD/kY -oLs1hed2ozTXQl8cfhsUViGCU96xf/dUZD2VaZ9IAPVTxl2K9U+8XjMlj5xnry+x -thcFWRLAZSNp5OqyTCvLGlNzv9I2z8ix7jW4+ol2cO9Oe5LJwnXzHj6rVvIKb0aW -phKu16lex/g7B95iC1TvrBTNWe2zzO27AoIBAQCLU1chAwthFFUl0uqkBVFlmLqj -r3IS6CYUf38CEI4HyPIXk0FUCsphOaAcSbqtOFO4uvXy2srpnktIP5077wkH/Crd -p1e1mY8HxjUlimzOfM0hp3kFdslzjn59VtcLw52PyLqqTspTXlowlqery9Eh0qVa -c4s0/SDkWzHpb7FI7zW3X3+uol9bVNwXL4WIx313eI5oAXOP3XMwNXw7O4nSoSQg -cvP657XnBYBjtcefMTns4WkOeoc5tmzxTzJGLx6zPytaAdhbq0CAmErr5T+xfAom -CiRsq4THG0HX0olGxBYKskvwRJArRCnrTimEcVFRNagQgCbs5TvCyKCiZEwU ------END RSA PRIVATE KEY----- diff --git a/test/client.js b/test/client.js deleted file mode 100755 index 6bb5433..0000000 --- a/test/client.js +++ /dev/null @@ -1,75 +0,0 @@ -const GrpcLoader = require('../lib') -const path = require('path') -const fs = require('fs') - -const start = async (addr) => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, 'protos'), - files: ['test/helloworld/helloworld.proto'] - }) - await loader.init({ - isDev: true, - packagePrefix: 'dev' - }) - - const credentials = loader.makeCredentials( - fs.readFileSync(path.resolve(__dirname, 'certs/ca.crt')), - fs.readFileSync(path.resolve(__dirname, 'certs/client.key')), - fs.readFileSync(path.resolve(__dirname, 'certs/client.crt')) - ) - - await loader.initClients({ - services: { - 'test.helloworld.Greeter': addr, - 'test.helloworld.Hellor': addr - }, - credentials - }) - - const meta = loader.makeMetadata({ - 'x-cache-control': 'max-age=100', - 'x-business-id': ['grpcity', 'testing'], - 'x-timestamp-client': 'begin=' + new Date().toISOString() - }) - - // greeterClient - const greeterClient = loader.client('test.helloworld.Greeter', { - credentials - }) - const { status, metadata, response: result } = await greeterClient.sayHello({ name: 'greeter' }, meta) - console.log('greeterClient.sayHello', result) - console.log('greeterClient.sayHello metadata', metadata) - console.log('greeterClient.sayHello status', status) - - // hellorClient - const hellorClient = loader.client('test.helloworld.Hellor') - const { response: result2 } = await hellorClient.sayHello({ name: 'hellor2' }) - console.log('hellorClient.sayHello', result2) - - const { response: result3 } = await hellorClient.sayHello({ name: 'hellor3' }) - console.log('hellorClient.sayHello', result3) - - loader.closeClients() - - try { - const { response: result4 } = await hellorClient.sayHello({ - name: 'hellor4' - }) - console.log('hellorClient.sayHello, must not log here', result4) - } catch (error) { - // reconnect - await loader.initClients({ - services: { - 'test.helloworld.Hellor': addr - }, - credentials - }) - const newHellorClient = loader.client('test.helloworld.Hellor') - const { response: result5 } = await newHellorClient.sayHello({ - name: 'hellor5' - }) - console.log('newHellorClient.sayHello', result5) - } -} - -start('localhost:9099') diff --git a/test/index.test.js b/test/index.test.js deleted file mode 100755 index 8797375..0000000 --- a/test/index.test.js +++ /dev/null @@ -1,199 +0,0 @@ -const GrpcLoader = require('../lib') -const path = require('path') -const { expect } = require('chai') - -describe('Grpc Loader', () => { - class Greeter { - async init(server) { - server.addService('test.helloworld.Greeter', this, { exclude: ['init'] }) - } - - async SayHello(call) { - const metadata = call.metadata.clone() - metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) - call.sendMetadata(metadata) - if (metadata.get('x-throw-error').length > 0) { - throw new Error('throw error because x-throw-error') - } - - if (metadata.get('x-long-delay').length > 0) { - await new Promise((resolve) => setTimeout(resolve, 1000 * 10)) - } - - expect(this).to.be.an('object') - - return { message: `hello, ${call.request.name || 'world'}` } - } - - async SayHello2(call) { - return this.SayHello(call) - } - } - - it('Should sayHello from client to server', async () => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, 'protos'), - files: ['test/helloworld/helloworld.proto'] - }) - await loader.init() - - expect(loader._types).is.an('object') - - const server = loader.initServer() - const servicers = [new Greeter()] - await Promise.all(servicers.map(async (s) => s.init(server))) - const addr = { host: '127.0.0.1', port: 12305 } - await server.listen(addr) - - await loader.initClients({ - services: { - 'test.helloworld.Greeter': addr.host + ':' + addr.port - } - }) - const client = loader.client('test.helloworld.Greeter') - const { response: result } = await client.sayHello({ name: 'grpc' }) - expect(result).to.be.an('object') - expect(result.message).to.be.eq('hello, grpc') - - // 支持相同service的client访问不同host和port - const timeout = 50 - const client2 = loader.client('test.helloworld.Greeter', { - host: 'localhost', - port: 12305, - timeout - }) - const { response: result2 } = await client2.sayHello({ name: 'grpc' }) - expect(result2).to.be.an('object') - expect(result2.message).to.be.eq('hello, grpc') - - try { - await client2.sayHello({ name: 'grpc' }, loader.makeMetadata({ 'x-throw-error': 'true' })) - expect.fail('should not run here') - } catch (err) { - expect(/x-throw-error/.test(err.message)).to.be.eq(true) - expect(/SayHello/i.test(err.message)).to.be.eq(true) - } - - const start = Date.now() - try { - await client2.sayHello({ name: 'grpc' }, loader.makeMetadata({ 'x-long-delay': 'true' })) - expect.fail('should not run here') - } catch (err) { - expect(Date.now() - start).to.be.lte(timeout * 2) - expect(/Deadline/i.test(err.message)).to.be.eq(true) - expect(/SayHello/i.test(err.message)).to.be.eq(true) - } - - await server.shutdown() - }) - - it('Should run with dev and metadata', async () => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, 'protos'), - files: ['test/helloworld/helloworld.proto'] - }) - await loader.init({ - isDev: true, - packagePrefix: 'dev' - }) - - const server = loader.initServer() - const servicers = [new Greeter()] - await Promise.all(servicers.map(async (s) => s.init(server))) - const addr = { host: '127.0.0.1', port: 12306 } - await server.listen(addr) - - await loader.initClients({ - services: { - 'test.helloworld.Greeter': addr.host + ':' + addr.port - } - }) - const client = loader.client('test.helloworld.Greeter') - - await new Promise((resolve, reject) => { - const timestampClientSend = new Date() - const meta = loader.makeMetadata({ - 'x-cache-control': 'max-age=100', - 'x-business-id': ['grpcity', 'testing'], - 'x-timestamp-client': 'begin=' + timestampClientSend.toISOString() - }) - const call = client.call.sayHello({ name: 'grpc' }, meta, (err, result) => { - if (err) { - reject(err) - return - } - expect(result).to.be.an('object') - expect(result.message).to.be.eq('hello, grpc') - - resolve() - }) - - call.on('metadata', (metadata) => { - expect(metadata.get('x-cache-control')).to.be.an('array').deep.eq(['max-age=100']) - expect(metadata.get('x-business-id')).to.be.an('array').deep.eq(['grpcity, testing']) - - const timestamps = metadata.get('x-timestamp-server') - expect(timestamps).to.be.an('array').with.lengthOf(1) - const timestampServerReceived = new Date(timestamps[0].split('=')[1]) - const timeUsed = timestampServerReceived - timestampClientSend - expect(timeUsed).to.gte(0).lt(100) - }) - }) - - await server.shutdown() - }) - - it('Should run with dev with different init()', async () => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, 'protos'), - files: ['test/helloworld/helloworld.proto'] - }) - await loader.init({ - isDev: true - }) - }) -}) - -describe('Grpc protobuf message', () => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, 'protos'), - files: ['test/helloworld/helloworld.proto'] - }) - - before(async () => { - await loader.init({ - isDev: true, - packagePrefix: 'stage.dev' - }) - }) - - it('Should get protobuf service definition: Greeter', async () => { - const greeterDefinition = loader.service('test.helloworld.Greeter') - expect(greeterDefinition.SayHello).to.be.an('object') - expect(greeterDefinition.SayHello2).to.be.an('object') - }) - - it('Should get protobuf service definition: Hellor', async () => { - const hellorDefinition = loader.service('test.helloworld.Hellor') - expect(hellorDefinition.SayHello).to.be.an('object') - expect(hellorDefinition.SayHello2).to.be.an('object') - }) - - it('Should decode and encode protobuf message: HelloRequest', async () => { - const HelloRequest = loader.message('test.helloworld.model.HelloRequest') - const jsonData = { name: 'test' } - const buffer = HelloRequest.encode(jsonData).finish() - const decoded = HelloRequest.decode(buffer) - expect(decoded).to.be.an('object') - expect(decoded.name).to.be.eq(jsonData.name) - }) - - it('Should decode and encode protobuf message: HelloReply', async () => { - const HelloReply = loader.message('test.helloworld.model.HelloReply') - const jsonData = { message: 'test' } - const buffer = HelloReply.encode(jsonData).finish() - const decoded = HelloReply.decode(buffer) - expect(decoded).to.be.an('object') - expect(decoded.message).to.be.eq(jsonData.message) - }) -}) diff --git a/test/protos/test/helloworld/helloworld.proto b/test/protos/test/helloworld/helloworld.proto deleted file mode 100755 index cdea020..0000000 --- a/test/protos/test/helloworld/helloworld.proto +++ /dev/null @@ -1,21 +0,0 @@ -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "io.grpc.examples.helloworld"; -option java_outer_classname = "HelloWorldProto"; - -import "test/helloworld/model/message.proto"; - -package test.helloworld; - -// The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello(model.HelloRequest) returns (model.HelloReply) {} - rpc SayHello2(model.HelloRequest) returns (model.HelloReply) {} -} - -service Hellor { - rpc SayHello(model.HelloRequest) returns (model.HelloReply) {} - rpc SayHello2(model.HelloRequest) returns (model.HelloReply) {} -} diff --git a/test/protos/test/helloworld/model/message.proto b/test/protos/test/helloworld/model/message.proto deleted file mode 100755 index 45f05ec..0000000 --- a/test/protos/test/helloworld/model/message.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax = "proto3"; - -package test.helloworld.model; - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// The response message containing the greetings -message HelloReply { - string message = 1; - int32 name_count = 2; -} diff --git a/test/script/genCert.sh b/test/script/genCert.sh deleted file mode 100644 index d69ed02..0000000 --- a/test/script/genCert.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -echo "Creating certs folder ..." -mkdir -p certs && cd certs - -echo "Generating certificates ..." - -# 设置密码变量 -password="grpcity" - -# 证书有效期变量 -days=365 - -# 设置CA相关变量 -ca_key="ca.key" -ca_crt="ca.crt" -ca_subject="/C=CN/ST=GD/L=Guangzhou/O=gRPCity/OU=gRPCity/CN=ca" - -# 设置服务器相关变量 -server_key="server.key" -server_csr="server.csr" -server_crt="server.crt" -server_subject="/C=CN/ST=GD/L=Guangzhou/O=gRPCity/OU=Server/CN=localhost" - -# 设置客户端相关变量 -client_key="client.key" -client_csr="client.csr" -client_crt="client.crt" -client_subject="/C=CN/ST=GD/L=Guangzhou/O=gRPCity/OU=Client/CN=localhost" - -# 生成CA密钥和证书 -openssl genrsa -passout pass:$password -des3 -out $ca_key 4096 -openssl req -passin pass:$password -new -x509 -days $days -key $ca_key -out $ca_crt -subj "$ca_subject" - -# 生成服务器密钥和证书 -openssl genrsa -passout pass:$password -des3 -out $server_key 4096 -openssl req -passin pass:$password -new -key $server_key -out $server_csr -subj "$server_subject" -openssl x509 -req -passin pass:$password -days $days -in $server_csr -CA $ca_crt -CAkey $ca_key -set_serial 01 -out $server_crt -openssl rsa -passin pass:$password -in $server_key -out $server_key - -# 生成客户端密钥和证书 -openssl genrsa -passout pass:$password -des3 -out $client_key 4096 -openssl req -passin pass:$password -new -key $client_key -out $client_csr -subj "$client_subject" -openssl x509 -passin pass:$password -req -days $days -in $client_csr -CA $ca_crt -CAkey $ca_key -set_serial 01 -out $client_crt -openssl rsa -passin pass:$password -in $client_key -out $client_key diff --git a/test/server.js b/test/server.js deleted file mode 100755 index 4c2c3d4..0000000 --- a/test/server.js +++ /dev/null @@ -1,120 +0,0 @@ -const GrpcLoader = require('../lib') -const path = require('path') -const fs = require('fs') - -const timeout = (ms) => { - return new Promise((resolve, reject) => setTimeout(resolve, ms)) -} - -class Greeter { - constructor() { - this.count = 0 - } - - async init(server) { - server.addService('test.helloworld.Greeter', this, { exclude: ['init'] }) - } - - async SayHello(call) { - const metadata = call.metadata.clone() - metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) - call.sendMetadata(metadata) - if (metadata.get('x-throw-error').length > 0) { - throw new Error('throw error because x-throw-error') - } - - if (metadata.get('x-long-delay').length > 0) { - await new Promise((resolve) => setTimeout(resolve, 1000 * 10)) - } - await timeout(1000) - this.count++ - - return { - message: `hello, ${call.request.name || 'world'}`, - name_count: this.count - } - } - - async SayHello2(call) { - return this.SayHello(call) - } -} - -class Hellor { - async init(server) { - server.addService('test.helloworld.Hellor', this, { exclude: ['init'] }) - } - - async SayHello(call) { - const metadata = call.metadata.clone() - metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) - call.sendMetadata(metadata) - if (metadata.get('x-throw-error').length > 0) { - throw new Error('throw error because x-throw-error') - } - - if (metadata.get('x-long-delay').length > 0) { - await new Promise((resolve) => setTimeout(resolve, 1000 * 10)) - } - - return { message: `hello, ${call.request.name || 'world'}` } - } - - async SayHello2(call) { - return this.SayHello(call) - } -} - -const middlewareA = async (ctx, next) => { - const beginTime = new Date().getTime() - console.log('middlewareA: 1', ctx, beginTime) - await timeout(1000) - await next() - await timeout(1000) - const endTime = new Date().getTime() - console.log('middlewareA: 2', ctx, endTime, endTime - beginTime) -} - -const middlewareB = async (ctx, next) => { - const beginTime = new Date().getTime() - console.log('middlewareB: 1', ctx, beginTime) - await next() - const endTime = new Date().getTime() - console.log('middlewareB: 2', ctx, endTime, endTime - beginTime) -} - -const start = async (addr) => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, 'protos'), - files: ['test/helloworld/helloworld.proto'] - }) - await loader.init({ - isDev: true, - packagePrefix: 'dev' - }) - - const server = loader.initServer() - server.addMiddleware(middlewareA, middlewareB) - // server.addMiddleware([middlewareA, middlewareB]) - // server.addMiddleware(middlewareA) - // server.addMiddleware(middlewareB) - - const servicers = [new Greeter(), new Hellor()] - await Promise.all(servicers.map(async (s) => s.init(server))) - - const credentials = server.makeServerCredentials( - fs.readFileSync(path.resolve(__dirname, 'certs/ca.crt')), - [ - { - private_key: fs.readFileSync(path.resolve(__dirname, 'certs/server.key')), - cert_chain: fs.readFileSync(path.resolve(__dirname, 'certs/server.crt')) - } - ], - true - ) - - await server.listen(addr, credentials) - console.log('start:', addr) -} - -start('localhost:9099') diff --git a/test/stream/client-v2.js b/test/stream/client-v2.js deleted file mode 100644 index f194020..0000000 --- a/test/stream/client-v2.js +++ /dev/null @@ -1,66 +0,0 @@ -const GrpcLoader = require('../../lib') -const path = require('path') - -const start = async (addr) => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, './'), - files: ['stream.proto'] - }) - await loader.init({ - isDev: true, - packagePrefix: 'dev' - }) - - await loader.initClients({ - services: { - 'stream.Hellor': addr - } - }) - - const client = loader.client('stream.Hellor') - - const meta = loader.makeMetadata({ - 'x-cache-control': 'max-age=100', - 'x-business-id': ['grpcity', 'testing'], - 'x-timestamp-client': 'begin=' + new Date().toISOString() - }) - - // client to server - const unaryHelloCall = await client.unaryHello({ message: 'gRPCity' }, meta) - console.log(unaryHelloCall.response) - - // stream client to server - const clientStreamHelloCall = client.clientStreamHello(meta) - clientStreamHelloCall.write({ message: 'Hello!' }) - clientStreamHelloCall.write({ message: 'How are you?' }) - const writeResult = await clientStreamHelloCall.writeEnd() - console.log(writeResult) - - // client to stream server - const serverStreamHelloCall = client.serverStreamHello({ message: 'Hello! How are you?' }, meta) - const serverReadAllResult = serverStreamHelloCall.readAll() - for await (const data of serverReadAllResult) { - console.log(data) - } - const serverReadEndResult = serverStreamHelloCall.readEnd() - console.log(serverReadEndResult) - - // stream client to stream server - const mutualStreamHelloCall = client.mutualStreamHello(meta) - mutualStreamHelloCall.writeAll([{ message: 'Hello!' }, { message: 'How are you?' }, { message: 'other thing x' }]) - mutualStreamHelloCall.write({ message: 'maybe' }) - - const mutualReadAllResult = mutualStreamHelloCall.readAll() - for await (const data of mutualReadAllResult) { - if (data.message === 'delay 1s') { - mutualStreamHelloCall.write({ message: 'ok, I known you delay 1s' }) - mutualStreamHelloCall.writeEnd() - } - console.log(data) - } - - const mutualReadEndResult = mutualStreamHelloCall.readEnd() - console.log(mutualReadEndResult) -} - -start('localhost:9097') diff --git a/test/stream/client.js b/test/stream/client.js deleted file mode 100644 index 55f98df..0000000 --- a/test/stream/client.js +++ /dev/null @@ -1,78 +0,0 @@ -const GrpcLoader = require('../../lib') -const path = require('path') - -const start = async (addr) => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, './'), - files: ['stream.proto'] - }) - await loader.init({ - isDev: true, - packagePrefix: 'dev' - }) - - await loader.initClients({ - services: { - 'stream.Hellor': addr - } - }) - - const client = loader.client('stream.Hellor') - - const meta = loader.makeMetadata({ - 'x-cache-control': 'max-age=100', - 'x-business-id': ['grpcity', 'testing'], - 'x-timestamp-client': 'begin=' + new Date().toISOString() - }) - - // client to server - client.call.unaryHello({ message: 'gRPCity' }, meta, (err, response) => { - if (err) { - console.log(err) - } else { - console.log(response) - } - }) - - // stream client to server - const clientStreamHelloCall = client.call.clientStreamHello(meta, (err, response) => { - if (err) { - console.log(err) - } else { - console.log(response) - } - }) - clientStreamHelloCall.write({ message: 'Hello!' }) - clientStreamHelloCall.write({ message: 'How are you?' }) - clientStreamHelloCall.end() - - // client to stream server - const serverStreamHelloCall = client.call.serverStreamHello({ - message: 'Hello! How are you?' - }) - serverStreamHelloCall.on('data', (chunk) => { - console.log(chunk) - }) - serverStreamHelloCall.on('end', () => { - console.log('server call end.') - }) - - // stream client to stream server - const mutualStreamHelloCall = client.call.mutualStreamHello() - mutualStreamHelloCall.write({ message: 'Hello!' }) - mutualStreamHelloCall.write({ message: 'How are you?' }) - mutualStreamHelloCall.write({ message: 'other thing x' }) - - mutualStreamHelloCall.on('data', (data) => { - console.log(data) - if (data.message === 'delay 1s') { - mutualStreamHelloCall.write({ message: 'ok, I known you delay 1s' }) - mutualStreamHelloCall.end() - } - }) - mutualStreamHelloCall.on('end', () => { - console.log('server call end.') - }) -} - -start('localhost:9097') diff --git a/test/stream/server-v2.js b/test/stream/server-v2.js deleted file mode 100644 index b7343af..0000000 --- a/test/stream/server-v2.js +++ /dev/null @@ -1,79 +0,0 @@ -const GrpcLoader = require('../../lib') -const path = require('path') - -function timeout(ms) { - return new Promise((resolve, reject) => setTimeout(resolve, ms)) -} - -class Stream { - async unaryHello(call) { - console.log(call.request.message) - return { message: 'hello ' + call.request.message } - } - - async clientStreamHello(call) { - const metadata = call.metadata.clone() - metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) - call.sendMetadata(metadata) - - for await (const data of call.readAll()) { - console.log(data) - } - return { message: "Hello! I'm fine, thank you!" } - } - - async serverStreamHello(call) { - const metadata = call.metadata.clone() - metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) - call.sendMetadata(metadata) - - console.log(call.request.message) - call.write({ message: 'Hello! I got you message.' }) - call.write({ message: "I'm fine, thank you" }) - call.writeAll([{ message: 'other thing x' }, { message: 'other thing y' }]) - call.end() - } - - async mutualStreamHello(call) { - const metadata = call.metadata.clone() - metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) - call.sendMetadata(metadata) - - call.write({ message: 'emmm...' }) - - for await (const data of call.readAll()) { - console.log(data.message) - if (data.message === 'Hello!') { - call.write({ message: 'Hello too.' }) - } else if (data.message === 'How are you?') { - call.write({ message: "I'm fine, thank you" }) - await timeout(1000) - call.write({ message: 'delay 1s' }) - call.writeAll([{ message: 'emm... ' }, { message: 'emm......' }]) - } else { - call.write({ message: 'pardon?' }) - } - } - - call.end() - } -} - -const start = async (addr) => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, './'), - files: ['stream.proto'] - }) - await loader.init({ - isDev: true, - packagePrefix: 'dev' - }) - - const server = loader.initServer() - server.addService('stream.Hellor', new Stream()) - - await server.listen(addr) - console.log('start:', addr) -} - -start('localhost:9097') diff --git a/test/stream/server.js b/test/stream/server.js deleted file mode 100644 index 1b8e5f8..0000000 --- a/test/stream/server.js +++ /dev/null @@ -1,83 +0,0 @@ -const GrpcLoader = require('../../lib') -const path = require('path') - -class Stream { - constructor() { - this.count = 0 - } - - unaryHello(call, callback) { - console.log(call.request.message) - callback(null, { message: 'hello ' + call.request.message }) - } - - clientStreamHello(call, callback) { - const metadata = call.metadata.clone() - metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) - call.sendMetadata(metadata) - - call.on('data', (data) => { - console.log(data) - }) - call.on('end', () => { - callback(null, { message: "Hello! I'm fine, thank you!" }) - }) - } - - serverStreamHello(call) { - const metadata = call.metadata.clone() - metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) - call.sendMetadata(metadata) - - console.log(call.request.message) - call.write({ message: 'Hello! I got you message.' }) - call.write({ message: "I'm fine, thank you" }) - call.end() - } - - mutualStreamHello(call) { - const metadata = call.metadata.clone() - metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) - call.sendMetadata(metadata) - - call.write({ message: 'emmm...' }) - call.on('data', (chunk) => { - console.log(chunk.message) - if (chunk.message === 'Hello!') { - call.write({ message: 'Hello too.' }) - } else if (chunk.message === 'How are you?') { - call.write({ message: "I'm fine, thank you" }) - setTimeout(() => { - call.write({ message: 'delay 1s' }) - }, 1000) - } else { - call.write({ message: 'pardon?' }) - } - }) - call.on('end', () => { - setTimeout(() => { - console.log('client call end.') - call.end() - }, 3000) - }) - } -} - -const start = async (addr) => { - const loader = new GrpcLoader({ - location: path.resolve(__dirname, './'), - files: ['stream.proto'] - }) - await loader.init({ - isDev: true, - packagePrefix: 'dev' - }) - - const server = loader.initServer() - server.addService('stream.Hellor', new Stream()) - - await server.listen(addr) - console.log('start:', addr) -} - -start('localhost:9097') diff --git a/test/stream/stream.proto b/test/stream/stream.proto deleted file mode 100644 index b78cee6..0000000 --- a/test/stream/stream.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax="proto3"; - -package stream; - -service Hellor { - rpc UnaryHello (Message) returns (Message) {} - rpc ClientStreamHello (stream Message) returns (Message) {} - rpc ServerStreamHello (Message) returns (stream Message) {} - rpc MutualStreamHello (stream Message) returns (stream Message) {} -} - -message Message { - string message = 1; -} diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 0000000..e5ad8f5 --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "CommonJS", + "target": "ES2020", + "outDir": "lib/cjs", + "allowSyntheticDefaultImports": true + } +} diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 0000000..c59afdf --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "target": "ESNext", + "outDir": "lib/esm" + } +} diff --git a/tsconfig.json b/tsconfig.json index 73dc90b..f9b7da6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,18 @@ { - "extends": "@tsconfig/node14/tsconfig.json", "compilerOptions": { - "outDir": "lib", - "sourceMap": false, + "lib": ["ESNext"], + "rootDir": "src", + "types": ["node", "jest"], + "moduleResolution": "node", + "removeComments": true, + "declarationDir": "lib/types", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "sourceMap": true, "declaration": true, - "stripInternal": true, - "allowJs": true + "declarationMap": true }, - "include": ["src/*.ts"] + "include": ["src"], + "exclude": ["test"] } From e68eb5021ee7f3fb970924a96ebb68ee1379ff3b Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 16:37:55 +0800 Subject: [PATCH 02/30] feat: refactor core --- .github/workflows/build.yml | 2 +- .prettierrc | 2 +- README.md | 2 +- README_CN.md | 2 +- package.json | 38 +++--- pnpm-lock.yaml | 118 +++++++++--------- src/client/bidiStreamProxy.ts | 55 ++++++++ src/client/callbackProxy.ts | 7 ++ src/client/clientError.ts | 19 +++ src/client/clientFactory.ts | 66 ++++++++++ src/client/clientMetadata.ts | 8 ++ src/client/clientProxy.ts | 63 ++++++++++ src/client/clientStreamProxy.ts | 56 +++++++++ src/client/index.ts | 75 +++++++++++ src/client/serverStreamProxy.ts | 46 +++++++ src/client/unaryProxy.ts | 42 +++++++ src/config/defaultChannelOptions.ts | 23 ++++ src/config/defaultLoadOptions.ts | 11 ++ src/index.ts | 7 ++ src/loader.ts | 166 ++++++++++++++++++++++++ src/schema/client.ts | 32 +++++ src/schema/loader.ts | 76 +++++++++++ src/schema/server.ts | 9 ++ src/server/callBidiStreamProxy.ts | 33 +++++ src/server/callClientStreamProxy.ts | 26 ++++ src/server/callServerStreamProxy.ts | 28 +++++ src/server/callUnaryProxy.ts | 19 +++ src/server/callbackify.ts | 72 +++++++++++ src/server/context.ts | 18 +++ src/server/index.ts | 107 ++++++++++++++++ src/server/serverError.ts | 10 ++ src/utils/compose.ts | 25 ++++ src/utils/definition.ts | 22 ++++ src/utils/iterator.ts | 187 ++++++++++++++++++++++++++++ tsconfig.cjs.json | 9 -- tsconfig.esm.json | 8 -- tsconfig.json | 15 ++- 37 files changed, 1397 insertions(+), 107 deletions(-) create mode 100644 src/client/bidiStreamProxy.ts create mode 100644 src/client/callbackProxy.ts create mode 100644 src/client/clientError.ts create mode 100644 src/client/clientFactory.ts create mode 100644 src/client/clientMetadata.ts create mode 100644 src/client/clientProxy.ts create mode 100644 src/client/clientStreamProxy.ts create mode 100644 src/client/index.ts create mode 100644 src/client/serverStreamProxy.ts create mode 100644 src/client/unaryProxy.ts create mode 100644 src/config/defaultChannelOptions.ts create mode 100644 src/config/defaultLoadOptions.ts create mode 100644 src/loader.ts create mode 100644 src/schema/client.ts create mode 100644 src/schema/loader.ts create mode 100644 src/schema/server.ts create mode 100644 src/server/callBidiStreamProxy.ts create mode 100644 src/server/callClientStreamProxy.ts create mode 100644 src/server/callServerStreamProxy.ts create mode 100644 src/server/callUnaryProxy.ts create mode 100644 src/server/callbackify.ts create mode 100644 src/server/context.ts create mode 100644 src/server/index.ts create mode 100644 src/server/serverError.ts create mode 100644 src/utils/compose.ts create mode 100644 src/utils/definition.ts create mode 100644 src/utils/iterator.ts delete mode 100644 tsconfig.cjs.json delete mode 100644 tsconfig.esm.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d1694e7..affa0c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: node-version: [16.x, 18.x, 20.x] - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-latest] steps: - name: Checkout master diff --git a/.prettierrc b/.prettierrc index a037757..9ffbf57 100644 --- a/.prettierrc +++ b/.prettierrc @@ -7,7 +7,7 @@ "htmlWhitespaceSensitivity": "css", "insertPragma": false, "jsxSingleQuote": true, - "printWidth": 200, + "printWidth": 150, "proseWrap": "preserve", "quoteProps": "as-needed", "requirePragma": false, diff --git a/README.md b/README.md index 4320533..a095fe4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# gRPCity ![build-status](https://github.com/chakhsu/grpcity/actions/workflows/build.yml/badge.svg) ![npm](https://img.shields.io/npm/v/grpcity) ![license](https://img.shields.io/npm/l/grpcity) ![code-style](https://img.shields.io/badge/code_style-standard-brightgreen.svg) +# gRPCity ![build-status](https://github.com/chakhsu/grpcity/actions/workflows/build.yml/badge.svg) ![npm](https://img.shields.io/npm/v/grpcity) ![license](https://img.shields.io/npm/l/grpcity) [English](./README.md) | [简体中文](./README_CN.md) diff --git a/README_CN.md b/README_CN.md index 7e2bfaa..f9e174c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,4 +1,4 @@ -# gRPCity ![build-status](https://github.com/chakhsu/grpcity/actions/workflows/build.yml/badge.svg) ![npm](https://img.shields.io/npm/v/grpcity) ![license](https://img.shields.io/npm/l/grpcity) ![code-style](https://img.shields.io/badge/code_style-standard-brightgreen.svg) +# gRPCity ![build-status](https://github.com/chakhsu/grpcity/actions/workflows/build.yml/badge.svg) ![npm](https://img.shields.io/npm/v/grpcity) ![license](https://img.shields.io/npm/l/grpcity) [English](./README.md) | [简体中文](./README_CN.md) diff --git a/package.json b/package.json index c11e881..642819a 100644 --- a/package.json +++ b/package.json @@ -21,36 +21,25 @@ "bugs": { "url": "https://github.com/chakhsu/grpcity/issues" }, - "exports": { - ".": { - "types": "./lib/types/index.d.ts", - "import": "./lib/mjs/index.js", - "require": "./lib/esm/index.js" - }, - "./package.json": "./package.json" + "type": "module", + "exports": "./lib/index.js", + "types": "./lib/types/index.d.ts", + "engines": { + "node": ">=16" }, - "main": "lib/cjs/index.js", - "module": "lib/esm/index.js", - "types": "lib/types/index.d.ts", "files": [ "lib", "README_CN.md" ], - "engines": { - "node": ">=16" - }, "scripts": { "clear": "rimraf lib && rimraf coverage", - "patch:cjs-type": "echo '{ \"type\": \"commonjs\" }' >> lib/cjs/package.json", - "build:cjs": "tsc -P tsconfig.cjs.json && pnpm patch:cjs-type", - "patch:esm-type": "echo '{ \"type\": \"module\" }' >> lib/esm/package.json", - "patch:esm-js": "tsc-esm-fix --tsconfig=tsconfig.esm.json", - "build:esm": "tsc -P tsconfig.esm.json && pnpm patch:esm-js && pnpm patch:esm-type", - "build": "pnpm clear && pnpm build:cjs && pnpm build:esm", + "patch:esm-js": "tsc-esm-fix --tsconfig=tsconfig.json", + "build:esm": "tsc -P tsconfig.json && pnpm patch:esm-js", + "build": "pnpm clear && pnpm build:esm", "lint:prettier": "prettier --cache --check --ignore-path .gitignore --ignore-path .prettierignore .", "prettier": "pnpm lint:prettier --write", "prepare": "husky install", - "test": "jest" + "test": "NODE_OPTIONS=--experimental-vm-modules pnpm exec jest" }, "lint-staged": { "*.{ts,js,md,json,yaml}": "prettier --write" @@ -58,13 +47,14 @@ "dependencies": { "@grpc/grpc-js": "^1.9.13", "@grpc/proto-loader": "^0.7.10", - "lodash": "^4.17.21", + "joi": "^17.11.0", + "lodash-es": "^4.17.21", "protobufjs": "^7.2.5" }, "devDependencies": { "@types/jest": "^29.5.11", - "@types/lodash": "^4.14.202", - "@types/node": "^20.10.5", + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.10.6", "husky": "^8.0.3", "jest": "^29.7.0", "lint-staged": "^15.2.0", @@ -72,7 +62,7 @@ "rimraf": "^5.0.5", "ts-jest": "^29.1.1", "ts-node": "^10.9.2", - "tsc-esm-fix": "^2.20.18", + "tsc-esm-fix": "^2.20.21", "typescript": "^5.3.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c777d2a..a3a7faa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,7 +14,7 @@ dependencies: joi: specifier: ^17.11.0 version: 17.11.0 - lodash: + lodash-es: specifier: ^4.17.21 version: 4.17.21 protobufjs: @@ -22,24 +22,21 @@ dependencies: version: 7.2.5 devDependencies: - '@tsconfig/node14': - specifier: ^14.1.0 - version: 14.1.0 '@types/jest': specifier: ^29.5.11 version: 29.5.11 - '@types/lodash': - specifier: ^4.14.202 - version: 4.14.202 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 '@types/node': - specifier: ^20.10.5 - version: 20.10.5 + specifier: ^20.10.6 + version: 20.10.6 husky: specifier: ^8.0.3 version: 8.0.3 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + version: 29.7.0(@types/node@20.10.6)(ts-node@10.9.2) lint-staged: specifier: ^15.2.0 version: 15.2.0 @@ -54,10 +51,10 @@ devDependencies: version: 29.1.1(@babel/core@7.23.6)(jest@29.7.0)(typescript@5.3.3) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.10.5)(typescript@5.3.3) + version: 10.9.2(@types/node@20.10.6)(typescript@5.3.3) tsc-esm-fix: - specifier: ^2.20.18 - version: 2.20.18 + specifier: ^2.20.21 + version: 2.20.21 typescript: specifier: ^5.3.3 version: 5.3.3 @@ -412,7 +409,7 @@ packages: engines: { node: ^8.13.0 || >=10.10.0 } dependencies: '@grpc/proto-loader': 0.7.10 - '@types/node': 20.10.5 + '@types/node': 20.10.6 dev: false /@grpc/proto-loader@0.7.10: @@ -469,7 +466,7 @@ packages: engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -490,14 +487,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.10.6)(ts-node@10.9.2) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -525,7 +522,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 jest-mock: 29.7.0 dev: true @@ -552,7 +549,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.10.5 + '@types/node': 20.10.6 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -585,7 +582,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.20 - '@types/node': 20.10.5 + '@types/node': 20.10.6 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -673,7 +670,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.10.5 + '@types/node': 20.10.6 '@types/yargs': 17.0.32 chalk: 4.1.2 dev: true @@ -828,10 +825,6 @@ packages: resolution: { integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== } dev: true - /@tsconfig/node14@14.1.0: - resolution: { integrity: sha512-VmsCG04YR58ciHBeJKBDNMWWfYbyP8FekWVuTlpstaUPlat1D0x/tXzkWP7yCMU0eSz9V4OZU0LBWTFJ3xZf6w== } - dev: true - /@tsconfig/node16@1.0.4: resolution: { integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== } dev: true @@ -868,7 +861,7 @@ packages: /@types/graceful-fs@4.1.9: resolution: { integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== } dependencies: - '@types/node': 20.10.5 + '@types/node': 20.10.6 dev: true /@types/istanbul-lib-coverage@2.0.6: @@ -894,12 +887,18 @@ packages: pretty-format: 29.7.0 dev: true + /@types/lodash-es@4.17.12: + resolution: { integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ== } + dependencies: + '@types/lodash': 4.14.202 + dev: true + /@types/lodash@4.14.202: resolution: { integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== } dev: true - /@types/node@20.10.5: - resolution: { integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== } + /@types/node@20.10.6: + resolution: { integrity: sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw== } dependencies: undici-types: 5.26.5 @@ -1239,7 +1238,7 @@ packages: resolution: { integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== } dev: true - /create-jest@29.7.0(@types/node@20.10.5)(ts-node@10.9.2): + /create-jest@29.7.0(@types/node@20.10.6)(ts-node@10.9.2): resolution: { integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } hasBin: true @@ -1248,7 +1247,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.10.6)(ts-node@10.9.2) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -1297,6 +1296,10 @@ packages: engines: { node: '>=0.10.0' } dev: true + /depseek@0.2.1: + resolution: { integrity: sha512-LLWAVkTGDzGvAe5647Gyf6ggKHCA6TacjsPU4SDXpXn237Bcq62GCXRXy4MJfTyjjnth0v8Jkv038CdqEHu6NA== } + dev: true + /detect-newline@3.1.0: resolution: { integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== } engines: { node: '>=8' } @@ -1787,7 +1790,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -1808,7 +1811,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0(@types/node@20.10.5)(ts-node@10.9.2): + /jest-cli@29.7.0(@types/node@20.10.6)(ts-node@10.9.2): resolution: { integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } hasBin: true @@ -1822,10 +1825,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + create-jest: 29.7.0(@types/node@20.10.6)(ts-node@10.9.2) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.10.6)(ts-node@10.9.2) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -1836,7 +1839,7 @@ packages: - ts-node dev: true - /jest-config@29.7.0(@types/node@20.10.5)(ts-node@10.9.2): + /jest-config@29.7.0(@types/node@20.10.6)(ts-node@10.9.2): resolution: { integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } peerDependencies: @@ -1851,7 +1854,7 @@ packages: '@babel/core': 7.23.6 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 babel-jest: 29.7.0(@babel/core@7.23.6) chalk: 4.1.2 ci-info: 3.9.0 @@ -1871,7 +1874,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.2(@types/node@20.10.5)(typescript@5.3.3) + ts-node: 10.9.2(@types/node@20.10.6)(typescript@5.3.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -1912,7 +1915,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -1928,7 +1931,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.10.5 + '@types/node': 20.10.6 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -1979,7 +1982,7 @@ packages: engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 jest-util: 29.7.0 dev: true @@ -2034,7 +2037,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -2065,7 +2068,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -2117,7 +2120,7 @@ packages: engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -2142,7 +2145,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.10.6 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -2154,13 +2157,13 @@ packages: resolution: { integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } dependencies: - '@types/node': 20.10.5 + '@types/node': 20.10.6 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true - /jest@29.7.0(@types/node@20.10.5)(ts-node@10.9.2): + /jest@29.7.0(@types/node@20.10.6)(ts-node@10.9.2): resolution: { integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } hasBin: true @@ -2173,7 +2176,7 @@ packages: '@jest/core': 29.7.0(ts-node@10.9.2) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + jest-cli: 29.7.0(@types/node@20.10.6)(ts-node@10.9.2) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -2284,6 +2287,10 @@ packages: p-locate: 4.1.0 dev: true + /lodash-es@4.17.21: + resolution: { integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== } + dev: false + /lodash.camelcase@4.3.0: resolution: { integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== } dev: false @@ -2292,10 +2299,6 @@ packages: resolution: { integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== } dev: true - /lodash@4.17.21: - resolution: { integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== } - dev: false - /log-update@6.0.0: resolution: { integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw== } engines: { node: '>=18' } @@ -2589,7 +2592,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.10.5 + '@types/node': 20.10.6 long: 5.2.3 dev: false @@ -2900,7 +2903,7 @@ packages: '@babel/core': 7.23.6 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.10.5)(ts-node@10.9.2) + jest: 29.7.0(@types/node@20.10.6)(ts-node@10.9.2) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -2910,7 +2913,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-node@10.9.2(@types/node@20.10.5)(typescript@5.3.3): + /ts-node@10.9.2(@types/node@20.10.6)(typescript@5.3.3): resolution: { integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== } hasBin: true peerDependencies: @@ -2929,7 +2932,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.10.5 + '@types/node': 20.10.6 acorn: 8.11.2 acorn-walk: 8.3.1 arg: 4.1.3 @@ -2941,11 +2944,12 @@ packages: yn: 3.1.1 dev: true - /tsc-esm-fix@2.20.18: - resolution: { integrity: sha512-oPH79Z1AZxau4kc/Jw+LYVqs9NDTwsbyWwE57fVUgmwoPgxhGAwDTv0CjDEdMO1Aczg3EH1bLdsQmsoaJib4Tg== } + /tsc-esm-fix@2.20.21: + resolution: { integrity: sha512-v/n5ZKlt5j/UNFAKBua9cV5wZll6QlJEQgT4lwXkv8pcOXf9iR9qL3B2kYZUwZCi6vVPumztlq19Kh8bgcYoIw== } engines: { node: '>=16.0.0' } hasBin: true dependencies: + depseek: 0.2.1 fs-extra: 11.2.0 globby: 13.2.2 json5: 2.2.3 diff --git a/src/client/bidiStreamProxy.ts b/src/client/bidiStreamProxy.ts new file mode 100644 index 0000000..ba4cfb7 --- /dev/null +++ b/src/client/bidiStreamProxy.ts @@ -0,0 +1,55 @@ +import { createClientError } from './clientError' +import { combineMetadata } from './clientMetadata' +import iterator from '../utils/iterator' +import type { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' + +export const bidiStreamProxy = ( + client: UntypedServiceImplementation, + func: any, + defaultMetadata: Record, + defaultOptions: Record +) => { + return (metadata: Metadata, options: Record): any => { + if (typeof options === 'function') { + throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') + } else if (typeof metadata === 'function') { + throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') + } + + metadata = combineMetadata(metadata, defaultMetadata) + options = Object.assign({}, defaultOptions, options) + + const call = func.apply(client, [metadata, options]) + + call.writeAll = (messages: any[]) => { + if (Array.isArray(messages)) { + messages.forEach((message) => { + call.write(message) + }) + } + } + call.writeEnd = call.end + + call.on('error', (err: Error) => { + throw createClientError(err, metadata) + }) + + const result: { metadata?: Metadata; status?: StatusObject } = {} + call.readAll = () => { + call.on('metadata', (metadata: Metadata) => { + result.metadata = metadata + }) + call.on('status', (status: StatusObject) => { + result.status = status + }) + return iterator(call, 'data', { + resolutionEvents: ['status', 'end'] + }) + } + call.readEnd = () => { + return result + } + + return call + } +} diff --git a/src/client/callbackProxy.ts b/src/client/callbackProxy.ts new file mode 100644 index 0000000..4e5ef93 --- /dev/null +++ b/src/client/callbackProxy.ts @@ -0,0 +1,7 @@ +import type { UntypedServiceImplementation } from '@grpc/grpc-js' + +export const callbackProxy = (client: UntypedServiceImplementation, func: any) => { + return (...argumentsList: any[]) => { + return func.apply(client, argumentsList) + } +} diff --git a/src/client/clientError.ts b/src/client/clientError.ts new file mode 100644 index 0000000..8e60194 --- /dev/null +++ b/src/client/clientError.ts @@ -0,0 +1,19 @@ +import { Metadata } from '@grpc/grpc-js' + +export const createClientError = (err: any, metadata: Metadata) => { + const newError = new Error() as { + name: string + code: string + message: string + stack: string + } + + newError.name = 'GrpcClientError' + newError.code = err.code + newError.message = `${metadata.get('x-service-name')} (${err.message})` + + const stacks = newError.stack!.split('\n') + newError.stack = [stacks[0], ...stacks.slice(2), ' ...', ...(err.stack!.split('\n').slice(1, 3) as string[])].join('\n') + + return newError +} diff --git a/src/client/clientFactory.ts b/src/client/clientFactory.ts new file mode 100644 index 0000000..f3b91ef --- /dev/null +++ b/src/client/clientFactory.ts @@ -0,0 +1,66 @@ +import type { ChannelCredentials, ChannelOptions, ServiceClientConstructor } from '@grpc/grpc-js' +import { ProtoLoader } from '../loader' +import { assignChannelOptions } from '../schema/client' + +export class ClientFactory { + private _loader?: ProtoLoader + private _clientMap: Map = new Map() + private _clientAddrMap: Map = new Map() + + constructor(loader: ProtoLoader) { + this._loader = loader + if (!this._loader) { + this._loader = loader + } + } + + create(isDefault: boolean, name: string, addr: string, credentials?: ChannelCredentials, options?: ChannelOptions) { + const ctBool = !!credentials + const cacheKeyPrefix = isDefault ? 'default' : addr.replace(/\./g, '-') + + const cacheKey = `${cacheKeyPrefix}.${name}` + if (this._clientMap.has(cacheKey)) { + return this._clientMap.get(cacheKey) + } + + const cacheKeyWithCt = `${cacheKeyPrefix}.${name}.${ctBool}` + if (this._clientMap.has(cacheKeyWithCt)) { + return this._clientMap.get(cacheKeyWithCt) + } + + if (!ctBool) { + credentials = (this._loader as ProtoLoader).makeClientCredentials() + } + const newOptions = assignChannelOptions(options) + + let cacheAddr: string = addr + if (addr === 'undefined:undefined') { + cacheAddr = this._clientAddrMap.get(name) || addr + } + const client = this.createReal(name, cacheAddr, credentials, newOptions) + this._clientAddrMap.set(name, cacheAddr) + this._clientMap.set(cacheKey, client) + return client + } + + createReal(name: string, addr: string, credentials?: ChannelCredentials, options?: Partial) { + if (!credentials) { + credentials = (this._loader as ProtoLoader).makeClientCredentials() + } + const newOptions = assignChannelOptions(options) + + const ServiceProto = (this._loader as ProtoLoader).type(name) + const client = new (ServiceProto as ServiceClientConstructor)(addr, credentials, newOptions) + return client + } + + clear() { + this._clientMap.forEach((client, key) => { + if (client && typeof client.close === 'function') { + client.close() + } + }) + this._clientMap.clear() + this._clientAddrMap.clear() + } +} diff --git a/src/client/clientMetadata.ts b/src/client/clientMetadata.ts new file mode 100644 index 0000000..697cf4e --- /dev/null +++ b/src/client/clientMetadata.ts @@ -0,0 +1,8 @@ +import { Metadata, MetadataValue } from '@grpc/grpc-js' + +export const combineMetadata = (metadata: Metadata, options: Record) => { + Object.keys(options).forEach((key) => { + metadata.add(key, options[key] as MetadataValue) + }) + return metadata +} diff --git a/src/client/clientProxy.ts b/src/client/clientProxy.ts new file mode 100644 index 0000000..a52b6db --- /dev/null +++ b/src/client/clientProxy.ts @@ -0,0 +1,63 @@ +import os from 'node:os' +import { callbackProxy } from './callbackProxy' +import { unaryProxy } from './unaryProxy' +import { clientStreamProxy } from './clientStreamProxy' +import { serverStreamProxy } from './serverStreamProxy' +import { bidiStreamProxy } from './bidiStreamProxy' +import type { UntypedServiceImplementation } from '@grpc/grpc-js' + +const getFuncStreamType = (func: any) => { + const { requestStream, responseStream } = func + return { requestStream, responseStream } +} + +export const clientProxy = (client: UntypedServiceImplementation, options: Record) => { + const prototype = Object.getPrototypeOf(client) + const methodNames: any = Object.keys(prototype) + .filter((key) => prototype[key] && prototype[key].path) + .reduce((names: any, key) => { + names[key.toUpperCase()] = prototype[key].path + return names + }, {}) + + const metadata: Record = { + 'x-client-hostname': os.hostname() + } + + const target = Object.entries(prototype).reduce( + (target: any, [name, func]) => { + if (name !== 'constructor' && typeof func === 'function') { + metadata['x-service-name'] = `${methodNames[name.toUpperCase()]}` + + const { requestStream, responseStream } = getFuncStreamType(func) + + if (!requestStream && !responseStream) { + // promisify unary method + target[name] = unaryProxy(client, func, metadata, options) + } + + // stream + if (requestStream && !responseStream) { + // promisify only client stream method + target[name] = clientStreamProxy(client, func, metadata, options) + } + if (!requestStream && responseStream) { + // promisify only server stream method + target[name] = serverStreamProxy(client, func, metadata, options) + } + if (requestStream && responseStream) { + // promisify duplex stream method + target[name] = bidiStreamProxy(client, func, metadata, options) + } + + // keep callback method + target.call[name] = callbackProxy(client, func) + } + + return target + }, + { call: {} } + ) + + return target +} diff --git a/src/client/clientStreamProxy.ts b/src/client/clientStreamProxy.ts new file mode 100644 index 0000000..2e8e201 --- /dev/null +++ b/src/client/clientStreamProxy.ts @@ -0,0 +1,56 @@ +import { createClientError } from './clientError' +import { combineMetadata } from './clientMetadata' +import type { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' + +export const clientStreamProxy = ( + client: UntypedServiceImplementation, + func: any, + defaultMetadata: Record, + defaultOptions: Record +) => { + return (metadata: Metadata, options: Record): any => { + if (typeof options === 'function') { + throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') + } else if (typeof metadata === 'function') { + throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') + } + + metadata = combineMetadata(metadata, defaultMetadata) + options = Object.assign({}, defaultOptions, options) + + const result: { response?: any; metadata?: Metadata; status?: StatusObject } = {} + + const argumentsList: Array = [metadata, options] + argumentsList.push((err: any, response: any) => { + if (err) { + throw createClientError(err, metadata) + } + result.response = response + }) + + const call = func.apply(client, argumentsList) + + call.writeAll = (messages: any[]) => { + if (Array.isArray(messages)) { + messages.forEach((message) => { + call.write(message) + }) + } + } + call.writeEnd = async () => { + call.end() + await new Promise((resolve, reject) => { + call.on('metadata', (metadata: Metadata) => { + result.metadata = metadata + }) + call.on('status', (status: StatusObject) => { + result.status = status + resolve() + }) + }) + return result + } + + return call + } +} diff --git a/src/client/index.ts b/src/client/index.ts new file mode 100644 index 0000000..8cbc1ce --- /dev/null +++ b/src/client/index.ts @@ -0,0 +1,75 @@ +import * as _ from 'lodash-es' +import { ProtoLoader } from '../loader' +import { ClientFactory } from './clientFactory' +import { clientProxy } from './clientProxy' + +import type { ClientsOptionsType, AddressObject } from '../schema/loader' +import type { ClientOptionsType } from '../schema/client' + +const prepareUrl = (url: ClientOptionsType['url']) => { + return { + isDefaultClient: !!url, + addr: _.isString(url) ? (url as string) : (url as AddressObject).host + ':' + (url as AddressObject).port + } +} + +export default class Clients { + private _proxyClientMap: Map = new Map() + private _clientFactory: ClientFactory + + constructor(loader: ProtoLoader, options: ClientsOptionsType) { + this._clientFactory = new ClientFactory(loader) + const { services, channelOptions, credentials } = options + + const serviceNames = Object.keys(services) + serviceNames.forEach((name) => { + const isDefault = true + + const addr = _.isString(services[name]) + ? (services[name] as string) + : (services[name] as AddressObject).host + ':' + (services[name] as AddressObject).port + + this._clientFactory.create(isDefault, name, addr, credentials, channelOptions) + }) + return this + } + + client(name: string, clientOptions?: ClientOptionsType) { + let { url, channelOptions, credentials, timeout } = clientOptions as ClientOptionsType + const { isDefaultClient, addr } = prepareUrl(url) + + const cacheKeyPrefix = isDefaultClient ? 'default' : addr.replace(/\./g, '-') + const cacheKey = `proxy.${cacheKeyPrefix}.${name}.${timeout}` + + if (this._proxyClientMap.has(cacheKey)) { + return this._proxyClientMap.get(cacheKey) + } + + const client = this._clientFactory.create(isDefaultClient, name, addr, credentials, channelOptions) + const proxy = clientProxy(client, { timeout }) + this._proxyClientMap.set(cacheKey, proxy) + return proxy + } + + clientWithoutCache(name: string, clientOptions?: ClientOptionsType) { + const { url, channelOptions, credentials, timeout } = clientOptions as ClientOptionsType + const { isDefaultClient, addr } = prepareUrl(url) + + const client = this._clientFactory.create(isDefaultClient, name, addr, credentials, channelOptions) + const proxy = clientProxy(client, { timeout }) + return proxy + } + + realClient(name: string, clientOptions?: ClientOptionsType) { + const { url, channelOptions, credentials } = clientOptions as ClientOptionsType + const { isDefaultClient, addr } = prepareUrl(url) + + const client = this._clientFactory.create(isDefaultClient, name, addr, credentials, channelOptions) + return client + } + + clear() { + this._clientFactory.clear() + this._proxyClientMap.clear() + } +} diff --git a/src/client/serverStreamProxy.ts b/src/client/serverStreamProxy.ts new file mode 100644 index 0000000..d797a2e --- /dev/null +++ b/src/client/serverStreamProxy.ts @@ -0,0 +1,46 @@ +import { createClientError } from './clientError' +import { combineMetadata } from './clientMetadata' +import iterator from '../utils/iterator' +import type { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' + +export const serverStreamProxy = ( + client: UntypedServiceImplementation, + func: any, + defaultMetadata: Record, + defaultOptions: Record +) => { + return (request: any, metadata: Metadata, options: Record): any => { + if (typeof options === 'function') { + throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') + } else if (typeof metadata === 'function') { + throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') + } + + metadata = combineMetadata(metadata, defaultMetadata) + options = Object.assign({}, defaultOptions, options) + + const call = func.apply(client, [request, metadata, options]) + + call.on('error', (err: Error) => { + throw createClientError(err, metadata) + }) + + const result: { metadata?: Metadata; status?: StatusObject } = {} + call.readAll = () => { + call.on('metadata', (metadata: Metadata) => { + result.metadata = metadata + }) + call.on('status', (status: StatusObject) => { + result.status = status + }) + return iterator(call, 'data', { + resolutionEvents: ['status', 'end'] + }) + } + call.readEnd = () => { + return result + } + + return call + } +} diff --git a/src/client/unaryProxy.ts b/src/client/unaryProxy.ts new file mode 100644 index 0000000..6e61c3e --- /dev/null +++ b/src/client/unaryProxy.ts @@ -0,0 +1,42 @@ +import { createClientError } from './clientError' +import { combineMetadata } from './clientMetadata' +import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' + +export const unaryProxy = ( + client: UntypedServiceImplementation, + func: any, + defaultMetadata: Record, + defaultOptions: Record +) => { + return async (request: any, metadata: Metadata, options: Record): Promise => { + if (typeof options === 'function') { + throw new Error('gRPCity: AsyncFunction should not contain a callback function') + } else if (typeof metadata === 'function') { + throw new Error('gRPCity: AsyncFunction should not contain a callback function') + } + + metadata = combineMetadata(metadata, defaultMetadata) + options = Object.assign({}, defaultOptions, options) + + return new Promise((resolve, reject) => { + const result: { response?: any; metadata?: Metadata; status?: StatusObject } = {} + const argumentsList: Array = [request, metadata, options] + argumentsList.push((err: any, response: any) => { + if (err) { + reject(createClientError(err, metadata)) + } + result.response = response + }) + + const call = func.apply(client, argumentsList) + + call.on('metadata', (metadata: Metadata) => { + result.metadata = metadata + }) + call.on('status', (status: StatusObject) => { + result.status = status + resolve(result) + }) + }) + } +} diff --git a/src/config/defaultChannelOptions.ts b/src/config/defaultChannelOptions.ts new file mode 100644 index 0000000..dcc74db --- /dev/null +++ b/src/config/defaultChannelOptions.ts @@ -0,0 +1,23 @@ +// gRPC channel options +// Doc: https://grpc.github.io/grpc/core/group__grpc__arg__keys.html +// Doc: https://github.com/grpc/grpc-node/blob/master/packages/grpc-js/src/channel-options.ts +import type { ChannelOptions } from '@grpc/grpc-js' + +export const defaultChannelOptions: ChannelOptions = { + 'grpc.min_reconnect_backoff_ms': 1000, + 'grpc.max_reconnect_backoff_ms': 10000, + 'grpc.grpclb_call_timeout_ms': 5000, + 'grpc.keepalive_timeout_ms': 20 * 1000, + 'grpc.keepalive_time_ms': 120 * 1000, + 'grpc.keepalive_permit_without_calls': 1, + 'grpc.enable_retries': 1, + 'grpc.service_config': JSON.stringify({ + retryPolicy: { + maxAttempts: 4, + initialBackoff: '0.1s', + maxBackoff: '1s', + backoffMultiplier: 2, + retryableStatusCodes: ['UNAVAILABLE'] + } + }) +} diff --git a/src/config/defaultLoadOptions.ts b/src/config/defaultLoadOptions.ts new file mode 100644 index 0000000..aad5080 --- /dev/null +++ b/src/config/defaultLoadOptions.ts @@ -0,0 +1,11 @@ +// GRPC protos loader options +// Doc: https://www.npmjs.com/package/@grpc/proto-loader +import type { Options } from '@grpc/proto-loader' + +export const defaultLoadOptions: Options = { + keepCase: true, + longs: String, + enums: String, + defaults: false, + oneofs: true +} diff --git a/src/index.ts b/src/index.ts index e69de29..214c08a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -0,0 +1,7 @@ +// export loader +export * from './loader' +export type { ProtoFileOptionsType, InitOptionsType } from './schema/loader' +export type { ClientsOptionsType, ServerOptionsType } from './schema/loader' + +// export client +export type { ClientOptionsType } from './schema/client' diff --git a/src/loader.ts b/src/loader.ts new file mode 100644 index 0000000..e5c913a --- /dev/null +++ b/src/loader.ts @@ -0,0 +1,166 @@ +import assert from 'node:assert' +import * as grpc from '@grpc/grpc-js' +import * as protoLoader from '@grpc/proto-loader' +import * as protobuf from 'protobufjs' +import * as Descriptor from 'protobufjs/ext/descriptor' +import * as _ from 'lodash-es' + +import Server from './server' +import Clients from './client' +import { prefixingDefinition } from './utils/definition' +import { assertProtoFileOptionsOptions, attemptInitOptions, attemptInitClientsOptions } from './schema/loader' +import type { ClientsOptionsType, ServerOptionsType } from './schema/loader' +import type { ProtoFileOptionsType, ProtoFileOptionType, InitOptionsType } from './schema/loader' + +export class ProtoLoader { + private _protoFiles: ProtoFileOptionType[] + private _types?: grpc.GrpcObject + private _isDev?: boolean + private _packagePrefix?: string + private _packageDefinition?: protoLoader.PackageDefinition + private _insecureClientCredentials?: grpc.ChannelCredentials + private _insecureServerCredentials?: grpc.ServerCredentials + private _reflectedRoot?: protobuf.Root + private _clients?: Clients + + constructor(protoFileOptions: ProtoFileOptionsType) { + assertProtoFileOptionsOptions(protoFileOptions) + this._protoFiles = Array.isArray(protoFileOptions) ? protoFileOptions : [protoFileOptions] + } + + async init(InitOptions?: InitOptionsType) { + const newInitOptions = attemptInitOptions(InitOptions) + + if (this._types) { + return + } + + const { isDev, packagePrefix, loadOptions } = newInitOptions + + try { + this._isDev = isDev + this._packagePrefix = packagePrefix + + loadOptions.includeDirs = this._protoFiles.map((p) => p.location).concat(loadOptions.includeDirs || []) + const files = this._protoFiles.reduce((result, p) => { + if (p.files && p.files.length > 0) { + result.push(...p.files) + } + return result + }, [] as string[]) + + const packageDefinition = await protoLoader.load(files, loadOptions) + + if (this._packagePrefix) { + this._packageDefinition = prefixingDefinition(packageDefinition, packagePrefix) + } else { + this._packageDefinition = packageDefinition + } + + this._types = grpc.loadPackageDefinition(this._packageDefinition) + } catch (err) { + throw err + } + } + + async initClients(options: ClientsOptionsType) { + if (!this._packageDefinition) { + await this.init() + } + if (this._clients) { + return + } + const newOptions = attemptInitClientsOptions(options) + this._clients = new Clients(this, options) + return this._clients + } + + closeClients() { + this._clients = void 0 + } + + async initServer(options?: ServerOptionsType) { + if (!this._packageDefinition) { + await this.init() + } + const server = new Server(this, options) + return server + } + + makeClientCredentials(rootCerts?: any, privateKey?: any, certChain?: any, verifyOptions?: any) { + assert(this._types, 'Must called loader init() first. proto file has not been loaded.') + if (rootCerts && privateKey && certChain) { + return grpc.credentials.createSsl(rootCerts, privateKey, certChain, verifyOptions) + } else { + if (!this._insecureClientCredentials) { + this._insecureClientCredentials = grpc.credentials.createInsecure() + } + return this._insecureClientCredentials + } + } + + makeServerCredentials(rootCerts?: Buffer, keyCertPairs?: grpc.KeyCertPair[], checkClientCertificate?: boolean) { + assert(this._types, 'Must called loader init() first. proto file has not been loaded.') + if (rootCerts && keyCertPairs) { + return grpc.ServerCredentials.createSsl(rootCerts, keyCertPairs, checkClientCertificate) + } else { + if (!this._insecureServerCredentials) { + this._insecureServerCredentials = grpc.ServerCredentials.createInsecure() + } + return this._insecureServerCredentials + } + } + + makeMetadata(initialValues: Record) { + assert(this._types, 'Must called loader init() first. proto file has not been loaded.') + const meta = new grpc.Metadata() + if (typeof initialValues === 'object') { + Object.entries(initialValues).forEach(([key, value]: [string, any]) => { + if (Array.isArray(value)) { + value.map((v) => meta.add(key, _.isString(v) ? v : Buffer.from(v))) + } else { + meta.add(key, _.isString(value) ? value : Buffer.from(value)) + } + }) + } + return meta + } + + service(name: string) { + assert(this._types, 'Must called loader init() first. proto file has not been loaded.') + const fullName = this._isDev ? `${this._packagePrefix}.${name}` : name + const service = _.get(this._types, `${fullName}`) + assert(service, `Cannot find service with name: ${fullName}, please check whether the protos file is configured incorrectly.`) + return (service as grpc.ServiceClientConstructor).service + } + + type(name: string) { + assert(this._types, 'Must called loader init() first. proto file has not been loaded.') + const fullName = this._isDev ? `${this._packagePrefix}.${name}` : name + const type = _.get(this._types, `${fullName}`) + assert(type, `Cannot find type with name: ${fullName}, please check whether the protos file is configured incorrectly.`) + return type + } + + message(name: string) { + let root = this._reflectedRoot + + if (root) { + const found = root.lookupType(name) + if (found) { + return found + } + } + + const descriptor = (this.type(name) as grpc.ProtobufTypeDefinition).fileDescriptorProtos.map((proto: any) => + Descriptor.FileDescriptorProto.decode(proto) + ) + root = (protobuf.Root as protobuf.RootConstructor).fromDescriptor({ + file: descriptor as Descriptor.IFileDescriptorProto[] + }) + + this._reflectedRoot = root + + return root.lookupType(name) + } +} diff --git a/src/schema/client.ts b/src/schema/client.ts new file mode 100644 index 0000000..661b8dc --- /dev/null +++ b/src/schema/client.ts @@ -0,0 +1,32 @@ +import Joi from 'joi' +import type { ChannelOptions, ChannelCredentials } from '@grpc/grpc-js' +import { defaultChannelOptions } from '../config/defaultChannelOptions' +import { AddressSchema, Address } from './loader' + +export type { ChannelOptions } from '@grpc/grpc-js' + +export const assignChannelOptions = (options?: ChannelOptions): ChannelOptions => { + return Object.assign({}, defaultChannelOptions, options || {}) +} + +const ClientOptionsSchema = Joi.object({ + url: AddressSchema.optional(), + credentials: Joi.any().optional(), + channelOptions: Joi.object().optional().default(defaultChannelOptions), + timeout: Joi.number() + .min(0) + .optional() + .default(1000 * 10) +}).optional() + +export type ClientOptionsType = { + url: Address + channelOptions?: ChannelOptions + credentials?: ChannelCredentials + timeout?: number + [key: string]: any +} + +export const attemptClientOptions = (options: ClientOptionsType) => { + return Joi.attempt(options || {}, ClientOptionsSchema) +} diff --git a/src/schema/loader.ts b/src/schema/loader.ts new file mode 100644 index 0000000..9d051dc --- /dev/null +++ b/src/schema/loader.ts @@ -0,0 +1,76 @@ +import Joi from 'joi' +import type { Options as LoaderOptions } from '@grpc/proto-loader' +import type { ChannelOptions, ChannelCredentials } from '@grpc/grpc-js' +import { defaultLoadOptions } from '../config/defaultLoadOptions' +import { defaultChannelOptions } from '../config/defaultChannelOptions' + +export type { Options as LoaderOptions } from '@grpc/proto-loader' +export type { ChannelOptions as ServerOptionsType } from '@grpc/grpc-js' + +const protoFileOptionsSchema = Joi.array() + .items( + Joi.object({ + location: Joi.string().required(), + files: Joi.array().items(Joi.string()).required() + }) + ) + .single() + +export type ProtoFileOptionType = { + location: string + files: string[] +} + +export type ProtoFileOptionsType = ProtoFileOptionType[] | ProtoFileOptionType + +export const assertProtoFileOptionsOptions = (options: ProtoFileOptionsType) => { + Joi.assert(options, protoFileOptionsSchema, 'new ProtoLoader() params error') +} + +export type AddressObject = { + host: string + port: number +} +export type Address = AddressObject | string + +export type InitOptionsType = { + isDev?: boolean + packagePrefix?: string + loadOptions?: LoaderOptions +} + +export const AddressSchema = Joi.alternatives([ + Joi.string().regex(/:/, 'host and port like 127.0.0.1:9090'), + Joi.object({ + host: Joi.string().required(), + port: Joi.number().integer().min(0).max(65535).required() + }) +]) + +const ClientsAddressSchema = Joi.object().pattern(/\.*/, AddressSchema) + +const InitOptionsSchema = Joi.object({ + isDev: Joi.boolean().optional(), + packagePrefix: Joi.string().optional(), + loadOptions: Joi.object().optional().default(defaultLoadOptions) +}) + +export const attemptInitOptions = (options?: InitOptionsType) => { + return Joi.attempt(options || {}, InitOptionsSchema) +} + +const ClientsOptionsSchema = Joi.object({ + services: ClientsAddressSchema.optional(), + credentials: Joi.any().optional(), + channelOptions: Joi.object().optional().default(defaultChannelOptions) +}) + +export type ClientsOptionsType = { + services: Record + channelOptions?: ChannelOptions + credentials?: ChannelCredentials +} + +export const attemptInitClientsOptions = (options: ClientsOptionsType) => { + return Joi.attempt(options || {}, ClientsOptionsSchema) +} diff --git a/src/schema/server.ts b/src/schema/server.ts new file mode 100644 index 0000000..026d043 --- /dev/null +++ b/src/schema/server.ts @@ -0,0 +1,9 @@ +import Joi from 'joi' + +import type { ChannelOptions } from '@grpc/grpc-js' +import { defaultChannelOptions } from '../config/defaultChannelOptions' +import { ServerOptionsType } from './loader' + +export const assignServerOptions = (options?: ServerOptionsType): ChannelOptions => { + return Object.assign({}, defaultChannelOptions, options || {}) +} diff --git a/src/server/callBidiStreamProxy.ts b/src/server/callBidiStreamProxy.ts new file mode 100644 index 0000000..3b939d4 --- /dev/null +++ b/src/server/callBidiStreamProxy.ts @@ -0,0 +1,33 @@ +import * as grpc from '@grpc/grpc-js' +import iterator from '../utils/iterator' +import { createContext } from './context' +import { createServerError } from './serverError' + +export const callBidiStreamProxy = (target: any, key: string, composeFunc: Function): grpc.handleBidiStreamingCall => { + return (call: any) => { + const ctx = createContext(call) + + call.writeAll = (messages: any[]) => { + if (Array.isArray(messages)) { + messages.forEach((message) => { + call.write(message) + }) + } + } + call.readAll = () => { + return iterator(call, 'data', { + resolutionEvents: ['end'] + }) + } + + Promise.resolve().then(async () => { + const handleResponse = async () => { + await target[key](call) + } + await composeFunc(ctx, handleResponse).catch((err: Error) => { + call.destroy(createServerError(err)) + }) + call.end() + }) + } +} diff --git a/src/server/callClientStreamProxy.ts b/src/server/callClientStreamProxy.ts new file mode 100644 index 0000000..02e2de0 --- /dev/null +++ b/src/server/callClientStreamProxy.ts @@ -0,0 +1,26 @@ +import * as grpc from '@grpc/grpc-js' +import iterator from '../utils/iterator' +import { createContext } from './context' +import { createServerError } from './serverError' + +export const callClientStreamProxy = (target: any, key: string, composeFunc: Function): grpc.handleClientStreamingCall => { + return (call: any, callback) => { + const ctx = createContext(call) + + call.readAll = () => { + return iterator(call, 'data', { + resolutionEvents: ['end'] + }) + } + + Promise.resolve().then(async () => { + const handleResponse = async () => { + ctx.response = await target[key](call) + } + await composeFunc(ctx, handleResponse).catch((err: Error) => { + callback(createServerError(err)) + }) + callback(null, ctx.response) + }) + } +} diff --git a/src/server/callServerStreamProxy.ts b/src/server/callServerStreamProxy.ts new file mode 100644 index 0000000..e25f142 --- /dev/null +++ b/src/server/callServerStreamProxy.ts @@ -0,0 +1,28 @@ +import * as grpc from '@grpc/grpc-js' +import { createContext } from './context' +import { createServerError } from './serverError' + +export const callServerStreamProxy = (target: any, key: string, composeFunc: Function): grpc.handleServerStreamingCall => { + return (call: any) => { + const ctx = createContext(call) + + call.writeAll = (messages: any[]) => { + if (Array.isArray(messages)) { + messages.forEach((message) => { + call.write(message) + }) + } + } + call.writeEnd = call.end + + Promise.resolve().then(async () => { + const handleResponse = async () => { + await target[key](call) + } + await composeFunc(ctx, handleResponse).catch((err: Error) => { + call.destroy(createServerError(err)) + }) + call.end() + }) + } +} diff --git a/src/server/callUnaryProxy.ts b/src/server/callUnaryProxy.ts new file mode 100644 index 0000000..405686d --- /dev/null +++ b/src/server/callUnaryProxy.ts @@ -0,0 +1,19 @@ +import * as grpc from '@grpc/grpc-js' +import { createContext } from './context' +import { createServerError } from './serverError' + +export const callUnaryProxy = (target: any, key: string, composeFunc: Function): grpc.handleUnaryCall => { + return (call, callback) => { + const ctx = createContext(call) + + Promise.resolve().then(async () => { + const handleResponse = async () => { + ctx.response = await target[key](call) + } + await composeFunc(ctx, handleResponse).catch((err: Error) => { + callback(createServerError(err)) + }) + callback(null, ctx.response) + }) + } +} diff --git a/src/server/callbackify.ts b/src/server/callbackify.ts new file mode 100644 index 0000000..153ba63 --- /dev/null +++ b/src/server/callbackify.ts @@ -0,0 +1,72 @@ +import assert from 'node:assert' +import * as util from 'node:util' +import { compose, MiddlewareFunction } from '../utils/compose' +import { callUnaryProxy } from './callUnaryProxy' +import { callClientStreamProxy } from './callClientStreamProxy' +import { callServerStreamProxy } from './callServerStreamProxy' +import { callBidiStreamProxy } from './callBidiStreamProxy' + +export type CallbackifyOptions = { + _implementationType: Record + exclude?: string[] + inherit?: any +} + +export const callbackify = (target: any, middleware: MiddlewareFunction[], options: CallbackifyOptions): any => { + assert(typeof target === 'object', 'Must callbackify an object') + + let { _implementationType, exclude, inherit } = options + + const protoPropertyNames = Object.getOwnPropertyNames(Object.getPrototypeOf({})) + if (exclude) { + exclude.push(...protoPropertyNames) + } else { + exclude = [] + } + + const allPropertyNames = [ + ...new Set([ + ...Object.keys(target), + ...Object.getOwnPropertyNames(Object.getPrototypeOf(target)), + ...(inherit && inherit.prototype ? Object.getOwnPropertyNames(inherit.prototype) : []) + ]) + ] + + const methods: { [key: string]: any } = {} + for (const key of allPropertyNames) { + const fn = target[key] + if (typeof fn === 'function' && key !== 'constructor' && !exclude.includes(key)) { + if (util.types.isAsyncFunction(fn)) { + const eglWrapFunction = proxy(target, key, _implementationType[key], middleware) + methods[key] = eglWrapFunction + } else { + methods[key] = fn + } + } + } + + return methods +} + +const proxy = (target: any, key: string, options: any = {}, middleware: MiddlewareFunction[]) => { + const { requestStream, responseStream } = options + + const fn = compose(middleware) + + // unary + if (!requestStream && !responseStream) { + return callUnaryProxy(target, key, fn) + } + // client stream + if (requestStream && !responseStream) { + return callClientStreamProxy(target, key, fn) + } + // server stream + if (!requestStream && responseStream) { + return callServerStreamProxy(target, key, fn) + } + // duplex stream + if (requestStream && responseStream) { + return callBidiStreamProxy(target, key, fn) + } +} diff --git a/src/server/context.ts b/src/server/context.ts new file mode 100644 index 0000000..8a04fc8 --- /dev/null +++ b/src/server/context.ts @@ -0,0 +1,18 @@ +import * as grpc from '@grpc/grpc-js' + +export type ContextType = { + path: string + request: any + metadata: grpc.Metadata + response?: any +} + +export const createContext = (call: Record): ContextType => { + return { + // TODO: maybe need more details + // method: target.constructor.name + '.' + key, + path: call.getPath() || '', + request: call.request, + metadata: call.metadata.clone() + } +} diff --git a/src/server/index.ts b/src/server/index.ts new file mode 100644 index 0000000..0b13045 --- /dev/null +++ b/src/server/index.ts @@ -0,0 +1,107 @@ +import assert from 'node:assert' +import * as grpc from '@grpc/grpc-js' +import * as _ from 'lodash-es' + +import { MiddlewareFunction } from '../utils/compose' +import { assignServerOptions } from '../schema/server' +import { ProtoLoader } from '../loader' +import type { ServerOptionsType } from '../schema/loader' +import { callbackify } from './callbackify' +import type { CallbackifyOptions } from './callbackify' + +export default class Server { + private _middleware: MiddlewareFunction[] = [] + private _loader?: ProtoLoader + private _server?: grpc.Server + + constructor(loader: ProtoLoader, options?: ServerOptionsType) { + this._loader = loader + if (!this._loader) { + this._loader = loader + } + if (!this._server) { + this._server = new grpc.Server(assignServerOptions(options)) + } + return this + } + + async listen(addr: any, credentials?: grpc.ServerCredentials): Promise { + assert(this._server, 'must be first init() server before server listen()') + + const url = _.isString(addr) ? addr : `${addr.host}:${addr.port}` + const bindPort = await new Promise((resolve, reject) => { + this._server!.bindAsync(url, credentials || (this._loader as ProtoLoader).makeServerCredentials(), (err, result) => + err ? reject(err) : resolve(result) + ) + }) + const port = addr.port ? addr.port : Number(addr.match(/:(\d+)/)![1]) + assert(bindPort === port, 'server bind port not to be right') + + this._server!.start() + } + + async shutdown(): Promise { + if (!this._server) { + return + } + + await new Promise((resolve, reject) => { + this._server!.tryShutdown((err) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + + delete this._server + delete this._loader + } + + forceShutdown(): void { + if (!this._server) { + return + } + + this._server!.forceShutdown() + delete this._server + delete this._loader + } + + add(name: string, implementation: any, { exclude = [], inherit }: { exclude?: string[]; inherit?: any } = {}): void { + const service = (this._loader as ProtoLoader).service(name) + + const options: CallbackifyOptions = { exclude, inherit, _implementationType: {} } + Object.keys(service).forEach((key) => { + const { requestStream, responseStream } = service[key] + options._implementationType[service[key].originalName as string] = { + requestStream, + responseStream + } + }) + + this._server!.addService(service, callbackify(implementation, this._middleware, options)) + } + + removeService(name: string): void { + this._server!.removeService((this._loader as ProtoLoader).service(name)) + } + + use(...args: MiddlewareFunction[]): void { + assert(args.length >= 1, 'server addMiddleware() takes at least one argument.') + if (args.length === 1) { + if (Array.isArray(args[0])) { + args[0].forEach((fn) => { + this._middleware.push(fn) + }) + } else { + this._middleware.push(args[0]) + } + } else { + args.forEach((fn) => { + this._middleware.push(fn) + }) + } + } +} diff --git a/src/server/serverError.ts b/src/server/serverError.ts new file mode 100644 index 0000000..4809b64 --- /dev/null +++ b/src/server/serverError.ts @@ -0,0 +1,10 @@ +export const createServerError = (err: any): any => { + err.code = err.code || 13 + if (typeof err.stack === 'string') { + const stack = err.stack.split('\n') + err.messages += ` [Error Message From Server, stack: ${stack[1].trim()}]` + } else { + err.messages += ' [Error Message From Server]' + } + return err +} diff --git a/src/utils/compose.ts b/src/utils/compose.ts new file mode 100644 index 0000000..575153b --- /dev/null +++ b/src/utils/compose.ts @@ -0,0 +1,25 @@ +export type MiddlewareFunction = (context: any, next: () => Promise) => Promise + +export const compose = (middleware: MiddlewareFunction[]): Function => { + if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') + for (const fn of middleware) { + if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') + } + + return function (context: any, next: () => Promise) { + let index = -1 + return dispatch(0) + function dispatch(i: number): Promise { + if (i <= index) return Promise.reject(new Error('next() called multiple times')) + index = i + let fn = middleware[i] + if (i === middleware.length) fn = next + if (!fn) return Promise.resolve() + try { + return Promise.resolve(fn(context, dispatch.bind(null, i + 1))) + } catch (err) { + return Promise.reject(err) + } + } + } +} diff --git a/src/utils/definition.ts b/src/utils/definition.ts new file mode 100644 index 0000000..e037c04 --- /dev/null +++ b/src/utils/definition.ts @@ -0,0 +1,22 @@ +import type { PackageDefinition } from '@grpc/proto-loader' + +export const prefixingDefinition = (packageDefinition: any, packagePrefix: string) => { + for (const qualifiedName in packageDefinition) { + const definition = packageDefinition[qualifiedName] + const newPackage = `${packagePrefix}.${qualifiedName}` + if (definition.format && definition.type && definition.fileDescriptorProtos) { + packageDefinition[newPackage] = definition + } else { + const newDefinition: any = {} + for (const method in definition) { + const service = definition[method] + newDefinition[method] = Object.assign({}, service, { + path: service.path.replace(/^\//, `/${packagePrefix}.`) + }) + } + packageDefinition[newPackage] = newDefinition + } + } + + return packageDefinition as PackageDefinition +} diff --git a/src/utils/iterator.ts b/src/utils/iterator.ts new file mode 100644 index 0000000..f819745 --- /dev/null +++ b/src/utils/iterator.ts @@ -0,0 +1,187 @@ +const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator' + +const normalizeEmitter = (emitter: any): { addListener: Function; removeListener: Function } => { + const addListener = emitter.on || emitter.addListener || emitter.addEventListener + const removeListener = emitter.off || emitter.removeListener || emitter.removeEventListener + + if (!addListener || !removeListener) { + throw new TypeError('Emitter is not compatible') + } + + return { + addListener: addListener.bind(emitter), + removeListener: removeListener.bind(emitter) + } +} + +const toArray = (value: any): any[] => (Array.isArray(value) ? value : [value]) + +export default (emitter: any, event: string | string[], options: any) => { + if (typeof options === 'function') { + options = { filter: options } + } + + // Allow multiple events + const events = toArray(event) + + options = { + rejectionEvents: ['error'], + resolutionEvents: [], + limit: Infinity, + multiArgs: false, + ...options + } + + const { limit } = options + const isValidLimit = limit >= 0 && (limit === Infinity || Number.isInteger(limit)) + if (!isValidLimit) { + throw new TypeError('The `limit` option should be a non-negative integer or Infinity') + } + + if (limit === 0) { + // Return an empty async iterator to avoid any further cost + return { + [Symbol.asyncIterator](): any { + return this + }, + async next(): Promise<{ done: boolean; value: any }> { + return { + done: true, + value: undefined + } + } + } + } + + const { addListener, removeListener } = normalizeEmitter(emitter) + + let isDone = false + let error: any + let hasPendingError = false + const nextQueue: { resolve: Function; reject: Function }[] = [] + const valueQueue: any[] = [] + let eventCount = 0 + let isLimitReached = false + + const valueHandler = (...args: any[]): void => { + eventCount++ + isLimitReached = eventCount === limit + + const value = options.multiArgs ? args : args[0] + + if (nextQueue.length > 0) { + const { resolve } = nextQueue.shift()! + resolve({ done: false, value }) + + if (isLimitReached) { + cancel() + } + + return + } + + valueQueue.push(value) + + if (isLimitReached) { + cancel() + } + } + + const cancel = (): void => { + isDone = true + for (const event of events) { + removeListener(event, valueHandler) + } + + for (const rejectionEvent of options.rejectionEvents!) { + removeListener(rejectionEvent, rejectHandler) + } + + for (const resolutionEvent of options.resolutionEvents!) { + removeListener(resolutionEvent, resolveHandler) + } + + while (nextQueue.length > 0) { + const { resolve } = nextQueue.shift()! + resolve({ done: true, value: undefined }) + } + } + + const rejectHandler = (...args: any[]): void => { + error = options.multiArgs ? args : args[0] + + if (nextQueue.length > 0) { + const { reject } = nextQueue.shift()! + reject(error) + } else { + hasPendingError = true + } + + cancel() + } + + const resolveHandler = (...args: any[]): void => { + const value = options.multiArgs ? args : args[0] + + if (options.filter && !options.filter(value)) { + return + } + + if (nextQueue.length > 0) { + const { resolve } = nextQueue.shift()! + resolve({ done: true, value }) + } else { + valueQueue.push(value) + } + + cancel() + } + + for (const event of events) { + addListener(event, valueHandler) + } + + for (const rejectionEvent of options.rejectionEvents!) { + addListener(rejectionEvent, rejectHandler) + } + + for (const resolutionEvent of options.resolutionEvents!) { + addListener(resolutionEvent, resolveHandler) + } + + return { + [symbolAsyncIterator](): any { + return this + }, + async next(): Promise<{ done: boolean; value: any }> { + if (valueQueue.length > 0) { + const value = valueQueue.shift() + return { + done: isDone && valueQueue.length === 0 && !isLimitReached, + value + } + } + + if (hasPendingError) { + hasPendingError = false + throw error + } + + if (isDone) { + return { + done: true, + value: undefined + } + } + + return new Promise((resolve, reject) => nextQueue.push({ resolve, reject })) + }, + async return(value: any): Promise<{ done: boolean; value: any }> { + cancel() + return { + done: isDone, + value + } + } + } +} diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json deleted file mode 100644 index e5ad8f5..0000000 --- a/tsconfig.cjs.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "CommonJS", - "target": "ES2020", - "outDir": "lib/cjs", - "allowSyntheticDefaultImports": true - } -} diff --git a/tsconfig.esm.json b/tsconfig.esm.json deleted file mode 100644 index c59afdf..0000000 --- a/tsconfig.esm.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "ESNext", - "target": "ESNext", - "outDir": "lib/esm" - } -} diff --git a/tsconfig.json b/tsconfig.json index f9b7da6..3ab2191 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,13 @@ { "compilerOptions": { - "lib": ["ESNext"], "rootDir": "src", - "types": ["node", "jest"], + "outDir": "lib", + "lib": ["ESNext"], + "module": "ESNext", + "target": "ESNext", + "types": ["node", "jest", "@types/jest"], "moduleResolution": "node", "removeComments": true, - "declarationDir": "lib/types", "strict": true, "esModuleInterop": true, "skipLibCheck": true, @@ -13,6 +15,9 @@ "declaration": true, "declarationMap": true }, - "include": ["src"], - "exclude": ["test"] + "ts-node": { + "esm": true, + "experimentalSpecifierResolution": "node" + }, + "include": ["./src/**/*"] } From e579e284c75060ec681fdb33008e55519b30e7de Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 16:52:09 +0800 Subject: [PATCH 03/30] fix: export types --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 642819a..ed0edda 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "type": "module", "exports": "./lib/index.js", - "types": "./lib/types/index.d.ts", + "types": "./lib/index.d.ts", "engines": { "node": ">=16" }, From 6edfeaf1d9d6b55514ef0613537191ae8e2f3d20 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 18:54:10 +0800 Subject: [PATCH 04/30] feat: make it run --- src/client/clientError.ts | 4 ++-- src/client/clientFactory.ts | 5 +++-- src/client/clientMetadata.ts | 2 +- src/client/index.ts | 29 +++++++++++++++++------------ src/client/unaryProxy.ts | 4 ++-- src/loader.ts | 19 +++---------------- src/schema/client.ts | 2 +- src/server/index.ts | 4 ++-- 8 files changed, 31 insertions(+), 38 deletions(-) diff --git a/src/client/clientError.ts b/src/client/clientError.ts index 8e60194..82a3979 100644 --- a/src/client/clientError.ts +++ b/src/client/clientError.ts @@ -1,6 +1,6 @@ import { Metadata } from '@grpc/grpc-js' -export const createClientError = (err: any, metadata: Metadata) => { +export const createClientError = (err: any, metadata?: Metadata) => { const newError = new Error() as { name: string code: string @@ -10,7 +10,7 @@ export const createClientError = (err: any, metadata: Metadata) => { newError.name = 'GrpcClientError' newError.code = err.code - newError.message = `${metadata.get('x-service-name')} (${err.message})` + newError.message = `${metadata?.get('x-service-name')} (${err.message})` const stacks = newError.stack!.split('\n') newError.stack = [stacks[0], ...stacks.slice(2), ' ...', ...(err.stack!.split('\n').slice(1, 3) as string[])].join('\n') diff --git a/src/client/clientFactory.ts b/src/client/clientFactory.ts index f3b91ef..b1fb9c3 100644 --- a/src/client/clientFactory.ts +++ b/src/client/clientFactory.ts @@ -31,13 +31,14 @@ export class ClientFactory { if (!ctBool) { credentials = (this._loader as ProtoLoader).makeClientCredentials() } - const newOptions = assignChannelOptions(options) + const channelOptions = assignChannelOptions(options) let cacheAddr: string = addr if (addr === 'undefined:undefined') { cacheAddr = this._clientAddrMap.get(name) || addr } - const client = this.createReal(name, cacheAddr, credentials, newOptions) + + const client = this.createReal(name, cacheAddr, credentials, channelOptions) this._clientAddrMap.set(name, cacheAddr) this._clientMap.set(cacheKey, client) return client diff --git a/src/client/clientMetadata.ts b/src/client/clientMetadata.ts index 697cf4e..8d54a8c 100644 --- a/src/client/clientMetadata.ts +++ b/src/client/clientMetadata.ts @@ -2,7 +2,7 @@ import { Metadata, MetadataValue } from '@grpc/grpc-js' export const combineMetadata = (metadata: Metadata, options: Record) => { Object.keys(options).forEach((key) => { - metadata.add(key, options[key] as MetadataValue) + ;(metadata as Metadata).add(key, options[key] as MetadataValue) }) return metadata } diff --git a/src/client/index.ts b/src/client/index.ts index 8cbc1ce..c5bc906 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -9,18 +9,23 @@ import type { ClientOptionsType } from '../schema/client' const prepareUrl = (url: ClientOptionsType['url']) => { return { isDefaultClient: !!url, - addr: _.isString(url) ? (url as string) : (url as AddressObject).host + ':' + (url as AddressObject).port + addr: _.isString(url) ? (url as string) : (url as AddressObject)?.host + ':' + (url as AddressObject)?.port } } export default class Clients { private _proxyClientMap: Map = new Map() private _clientFactory: ClientFactory + private _credentials: ClientsOptionsType['credentials'] constructor(loader: ProtoLoader, options: ClientsOptionsType) { this._clientFactory = new ClientFactory(loader) const { services, channelOptions, credentials } = options + if (credentials) { + this._credentials = credentials + } + const serviceNames = Object.keys(services) serviceNames.forEach((name) => { const isDefault = true @@ -34,10 +39,14 @@ export default class Clients { return this } - client(name: string, clientOptions?: ClientOptionsType) { - let { url, channelOptions, credentials, timeout } = clientOptions as ClientOptionsType + get(name: string, clientOptions: ClientOptionsType = {}) { + let { url, channelOptions, credentials, timeout } = clientOptions const { isDefaultClient, addr } = prepareUrl(url) + if (!credentials) { + credentials = this._credentials + } + const cacheKeyPrefix = isDefaultClient ? 'default' : addr.replace(/\./g, '-') const cacheKey = `proxy.${cacheKeyPrefix}.${name}.${timeout}` @@ -51,17 +60,13 @@ export default class Clients { return proxy } - clientWithoutCache(name: string, clientOptions?: ClientOptionsType) { - const { url, channelOptions, credentials, timeout } = clientOptions as ClientOptionsType - const { isDefaultClient, addr } = prepareUrl(url) + getReal(name: string, clientOptions: ClientOptionsType = {}) { + let { url, channelOptions, credentials } = clientOptions as ClientOptionsType - const client = this._clientFactory.create(isDefaultClient, name, addr, credentials, channelOptions) - const proxy = clientProxy(client, { timeout }) - return proxy - } + if (!credentials) { + credentials = this._credentials + } - realClient(name: string, clientOptions?: ClientOptionsType) { - const { url, channelOptions, credentials } = clientOptions as ClientOptionsType const { isDefaultClient, addr } = prepareUrl(url) const client = this._clientFactory.create(isDefaultClient, name, addr, credentials, channelOptions) diff --git a/src/client/unaryProxy.ts b/src/client/unaryProxy.ts index 6e61c3e..293f2ce 100644 --- a/src/client/unaryProxy.ts +++ b/src/client/unaryProxy.ts @@ -8,14 +8,14 @@ export const unaryProxy = ( defaultMetadata: Record, defaultOptions: Record ) => { - return async (request: any, metadata: Metadata, options: Record): Promise => { + return async (request: any, metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { throw new Error('gRPCity: AsyncFunction should not contain a callback function') } else if (typeof metadata === 'function') { throw new Error('gRPCity: AsyncFunction should not contain a callback function') } - metadata = combineMetadata(metadata, defaultMetadata) + metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = Object.assign({}, defaultOptions, options) return new Promise((resolve, reject) => { diff --git a/src/loader.ts b/src/loader.ts index e5c913a..c4e2ca2 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -21,7 +21,6 @@ export class ProtoLoader { private _insecureClientCredentials?: grpc.ChannelCredentials private _insecureServerCredentials?: grpc.ServerCredentials private _reflectedRoot?: protobuf.Root - private _clients?: Clients constructor(protoFileOptions: ProtoFileOptionsType) { assertProtoFileOptionsOptions(protoFileOptions) @@ -67,28 +66,18 @@ export class ProtoLoader { if (!this._packageDefinition) { await this.init() } - if (this._clients) { - return - } - const newOptions = attemptInitClientsOptions(options) - this._clients = new Clients(this, options) - return this._clients - } - - closeClients() { - this._clients = void 0 + const clientsOptions = attemptInitClientsOptions(options) + return new Clients(this, clientsOptions) } async initServer(options?: ServerOptionsType) { if (!this._packageDefinition) { await this.init() } - const server = new Server(this, options) - return server + return new Server(this, options) } makeClientCredentials(rootCerts?: any, privateKey?: any, certChain?: any, verifyOptions?: any) { - assert(this._types, 'Must called loader init() first. proto file has not been loaded.') if (rootCerts && privateKey && certChain) { return grpc.credentials.createSsl(rootCerts, privateKey, certChain, verifyOptions) } else { @@ -100,7 +89,6 @@ export class ProtoLoader { } makeServerCredentials(rootCerts?: Buffer, keyCertPairs?: grpc.KeyCertPair[], checkClientCertificate?: boolean) { - assert(this._types, 'Must called loader init() first. proto file has not been loaded.') if (rootCerts && keyCertPairs) { return grpc.ServerCredentials.createSsl(rootCerts, keyCertPairs, checkClientCertificate) } else { @@ -112,7 +100,6 @@ export class ProtoLoader { } makeMetadata(initialValues: Record) { - assert(this._types, 'Must called loader init() first. proto file has not been loaded.') const meta = new grpc.Metadata() if (typeof initialValues === 'object') { Object.entries(initialValues).forEach(([key, value]: [string, any]) => { diff --git a/src/schema/client.ts b/src/schema/client.ts index 661b8dc..5ad9708 100644 --- a/src/schema/client.ts +++ b/src/schema/client.ts @@ -20,7 +20,7 @@ const ClientOptionsSchema = Joi.object({ }).optional() export type ClientOptionsType = { - url: Address + url?: Address channelOptions?: ChannelOptions credentials?: ChannelCredentials timeout?: number diff --git a/src/server/index.ts b/src/server/index.ts index 0b13045..05fadac 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -20,9 +20,9 @@ export default class Server { this._loader = loader } if (!this._server) { - this._server = new grpc.Server(assignServerOptions(options)) + const serverOptions = assignServerOptions(options) + this._server = new grpc.Server(serverOptions) } - return this } async listen(addr: any, credentials?: grpc.ServerCredentials): Promise { From abaf142d92f4188f088b054c46d92464461ac23e Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 18:54:42 +0800 Subject: [PATCH 05/30] feat: add example --- example/certs/ca.crt | 30 ++++++ example/certs/ca.key | 54 ++++++++++ example/certs/client.crt | 30 ++++++ example/certs/client.csr | 27 +++++ example/certs/client.key | 51 +++++++++ example/certs/genCert.sh | 45 ++++++++ example/certs/server.crt | 30 ++++++ example/certs/server.csr | 27 +++++ example/certs/server.key | 51 +++++++++ example/helloworld/client.js | 60 +++++++++++ example/helloworld/loader.js | 29 +++++ example/helloworld/server.js | 106 +++++++++++++++++++ example/proto/helloworld/model/message.proto | 14 +++ example/proto/helloworld/service.proto | 21 ++++ example/proto/stream/service.proto | 14 +++ 15 files changed, 589 insertions(+) create mode 100644 example/certs/ca.crt create mode 100644 example/certs/ca.key create mode 100644 example/certs/client.crt create mode 100644 example/certs/client.csr create mode 100644 example/certs/client.key create mode 100644 example/certs/genCert.sh create mode 100644 example/certs/server.crt create mode 100644 example/certs/server.csr create mode 100644 example/certs/server.key create mode 100644 example/helloworld/client.js create mode 100644 example/helloworld/loader.js create mode 100644 example/helloworld/server.js create mode 100755 example/proto/helloworld/model/message.proto create mode 100755 example/proto/helloworld/service.proto create mode 100644 example/proto/stream/service.proto diff --git a/example/certs/ca.crt b/example/certs/ca.crt new file mode 100644 index 0000000..63925a7 --- /dev/null +++ b/example/certs/ca.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFOjCCAyICCQD1P87TyGsOzDANBgkqhkiG9w0BAQsFADBfMQswCQYDVQQGEwJD +TjELMAkGA1UECAwCR0QxEjAQBgNVBAcMCUd1YW5nemhvdTEQMA4GA1UECgwHZ1JQ +Q2l0eTEQMA4GA1UECwwHZ1JQQ2l0eTELMAkGA1UEAwwCY2EwHhcNMjMxMDMxMTA1 +MzMxWhcNMjQxMDMwMTA1MzMxWjBfMQswCQYDVQQGEwJDTjELMAkGA1UECAwCR0Qx +EjAQBgNVBAcMCUd1YW5nemhvdTEQMA4GA1UECgwHZ1JQQ2l0eTEQMA4GA1UECwwH +Z1JQQ2l0eTELMAkGA1UEAwwCY2EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQDXC1Xgz4/aeZcxaaCuAxknzjhwWcbMMBgzBjEP45UoogwGFfvTWRp9BSvY +E4wnyea6ISR05dRUm24uB6Fx+BBmp9KlTVQRRub5QaoF0a7jc4UZhq0o31ViDex4 +d+IqsD/AjZJrzgzxTsh06+easjZCX0LUla+Yn4tjI6WhAez4Ly5otvBf7R4bVQPk +6xBe/GeNL7CXpBGoD2i+PV/812rVPlK1YD6nxz/Cn+s5Aff/W0xmAzYVk1lIgqUb +iaXjpWVTdjOFVOWLTP2F5rwiJUBZsBV/WvdDJ+1rqMDzntGKuuzUS/i9DOrnxM9I +zs9LIceQhiIS8HZYfpJePTlQFKohs6GFUj/AMF/6/EiUNjkeWc0eyt0a79iOm6B7 +8FgjBIzvDiw+SydSuq76LwGv+l2nqXzj8pEnIhU7co50KPfizR5ScytngamIi9hp +WONZevdMkbZLOzY2UYCsSdqoiZOCQVgE2JDEhkHCRDCejp7z9dnEaC4EY2Jd9iNI ++94rhr9HKx4jxNDmdWyBtkDIKUvhse4Mhcd9rMEJ40RNcvJhuYfgOxVRqSc7O/9o +PkR/grClMel1bBjXS61pYjh4MgSkcfROC/MfnLkSUYnlA382+06trdDfrJAoMEfL +sy3OpsjIIa55F3Bn9GhlYRK3Zxss8IXGb4QiZRTqyFB/Ka1g1QIDAQABMA0GCSqG +SIb3DQEBCwUAA4ICAQBBvV7OL40vQK+LH04KFBW/v+ba91Iiw9OQDUgcVn5PHj5n +Q5lQyLvzYFm4KJwiw+6WjPDRZisArWC88wsz3q7Iu5yjNSnWnlx44ng4NPcc358+ +9aKIVA/tiJBtf9PRd6jwvqu5uOHmavBd8vebxip/RhnSUL3xTKHJMJqY2hDWUEGa +U3KdLmYe+fdnmmyx05uWZ5Vo62e8UXJV3vUc1X4q/DBFPzSnI+EbSHoLQdrCd1jr +5FdPn0ToOyjIWhnvnj8pF6lbg8fNiWXrDwBzI/e3U6V6EKzL8HsgAkshfM7Qtr0Q +25LeyGRD7p9u3qVlxl1c2fWjmWiOb6Lq/zt0MJS8Zp1FJ80kMQjbdZkEXohn0MbH +fACkhUucvnWl28thi/A2su6F9TmWfl481x6RXjJ0UAXTuhW4sTUx4CBsvhjUYvoY +v2u7fQaFr883shwHKwFz/uM+wG0h+t67JrGdBRMjS1P00wUGn0XCsuXqFxVrVL+B +xce4DTo83AFlhs+L7BWlJ80ZHTw7XKoIBSfsURiZLviKqphjC0KUhrPCQcu3LMqK +AH9TpALcIXtyrqQgl/RlQd0ZY2+SzK7k+9wuPd5x4z7SgTpRMaShQoBby/hxzCk2 +idfiJ9Mcf4mpZScbXwE/3cFDeBW+q2aRwgjl6nPz9sQShcwlQh9PbRanryy0iw== +-----END CERTIFICATE----- diff --git a/example/certs/ca.key b/example/certs/ca.key new file mode 100644 index 0000000..3619413 --- /dev/null +++ b/example/certs/ca.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,F50D3CD9BB4D1C8E + +yGSFUasNYJeN4v3N41xUnWec3jh06ZY7YPMEtgbG82iegvJxj9eOaPyvzK/HOXt2 +PiMIB3IXmUZcGcQMb2GfGFCeAATsNKQH/V0rBNeUugTSm3DgbkMzFaiKggZDUo58 +IVMLAdDxcc/YVpJDfmLfPNdz8VscUUxxQ/jEXMXlFXKFk5FjeNrtW19xjUSkj+kG +JCEL0ydoLfFfakJs0tjYFMwmdTxcSU5RobCDHzYKh76AFiHOE2Y0u+VESKLNKj1u +UMDEAPu1fksdmyP34YnwFVX1nF/gAQ+EB6ofO/2rERDm+KKwiY7sn3VcmWsEc6u1 +JnbVIuwFaHTKXB/OtD7jQU/ok60HJ7b5+HafrxqMlh41bKrMe9+7pEGJ5n4ddU+S +05DtpkS9psVM7Smv2otDcXlkmH1BRhqF9FaCHWLovQLm99Aw/ANJCKbXOmpsgYgY +rPWcACq2lW6K7+iepXzdVzXAH/Al0X9S8OySB/Qnp9O+WYJKe0IjyksILyNEDynE +ISKZuX7I+VOi8PNooP35lcXupzkcQ6N64dTSo/5C+7hlBtyCExwISWses3goHp0v +PsZfGhV8y14bLY9n/84T+zV8+oxi4BEUS8QyVA8qApylIzAIQ73asYqWI5yQeJU5 +o86PAXCQZOdjSiNkPbsL7HOIQLfn1OGvS7x3PwWFvdfDmMGsTuK1ZVsGSwltYsoL +U6Lu/QFjrPXT44VzRCJjx6kUoWvvhkeTmu+jXEkW/FBNg2PouJj5IP5FEMShJCys +0FsIp03kjM2Gd1OsTXoEPDUmcx12nFvvgnXZ/okgQChE5g4kr8YuuzeJlr6Tgcdu +0psu5ny4flaMuo72dNDHXajMKk4sVNa9yJ746CEgIgXfugUGFQHwwBH+rUQwALsm +vb1M6r5qEBOCZUo0p8vSVCyKgrN5bzHbhW4Ni1/XldaB0PEymEZyVAmd4jBjVlPP +mntPLFt9N3D5M/NuTr1NOtGJWe4roCP8w0A+nKv6q3DLfdZSUF80XC36zl43Lcuk +emGj69Bt2J5z3kbPDiefe6RM5mq5T6Zu/a6Cq7gy4KMEtzusouE8Pis5NoqwKgNL +PYFp1+cjFlxl5yI0+UeF1R0tx7xXvZmn8kipVsj7CvE92CpsE1iISMJBh3o1nixl +YQBkbFk5oIj5RSOU0NYs7efPPr5rfXPdpfC2FGgIRwDQqan7TrvlbTcjMfC9QA4I +mqffVY8dIapcBakKi2VR1dChQ9tZ77ZXpvbluf7gYLmdcQ2poCivXq3PwoemWRd+ +RV4KHhZ1SHwditvnLcv47uSmDviWoJSBxqEmAtPuRtCX8DtSHh5WFvnq2f3/KrPq +A8xPAMG1cKaUacWarH5a+8A44ksTru2eCtwX1zE60fYwdc8ZYW+M4m/PIKNQWtbt +TS6EjVwg/+8Jp6O4FbfDuJSKdkFIvmricB9fwGjtPfE9lTcu8KqLq1F5Z9U18MUH +baZqCBnL6vADkCmoYGmNM+37v7KS9RlI1hzANNOmeBTtNsG9efvaot+cAof23SNs +4buOnXv9C+5ciFhRZ6m8Yqb2ljU2EHc8dEPVWoqZCrvjrMU1mOh0tsqqjVxELVNO +I+2FIckRV5Xhb74dJs2IoixzDz7gd090AFJhXjcrMMavpAacpIuJG9rEnZbcRJuj +joy6TWPwQxJKuaCeGR+1Ujs7zDG15l0ikGjYdVb0kUnmInx67J5ThfyzWTnn2fvl +RCdf+/gox5cD1yCyJefVP59VNlQzzuRKMdQ1si3cHE5VsLK/2czns6h/XUSRePx7 +fYqjk7vsq1LVwePE9azOLFiG+VZ65NDZVnFtKyWwDLjYS7n9ewcvA1oRLs6KnP3Y +MZoI0hnE27qCfZzfJkJXIlvcJl/dCQDOo9MJ1NM9TmuYMDnHk3LFjH9nQ1OTSFVw +MFFNL7PwYtdjSRm+Enl/lzmoSRELYivPgZV9SoxVUPJqrn6wJ6NR5FYWtyKVL0Je +NYPy/u9B6OfGJJ452NgxHXbCJbkr3TjxNA6bjsajRIXEginr7a1NVgbobJErc3t2 +l1bgZQR6BEvWOHvPJGT5vLlRDGE4DKPcD58MCBfgUjQKbF5BKMLgNTld+jV5sIia +m7/SdNOSX+ou2IMStP47esHsWLkIUFHyV7lwivTKCMO2KRaSyjiJHi7+AP5M4GV0 +mdtBqQp1AdeD/IVOShmE9eAeho1JCokl8QXhw/zjuGXikGRM+9TEIvRtCGBRCYv8 +8hp5H6Csl+C8qhXsFjDUTVQqFfDlSSjCAN4l15m4lq4R5uFf8xV1on5tGwkfqSFF +Gbgc1Vn9ieWH3zl/cmdMA6n7viWfpQAkgiM7IrzRR0Uyy2lgw1YZj4LDhT/J7qR6 +s4pgitbQ2SxBy3xbxPMSkLm2XoMEjl5spRnB4txy3gMgP+ReUTQDJmsdqswk6MTv +zTiQqVN67ZyTrUVNnSYGOUuqYZcmwmHy1uY9scJr4y0tnDbmxfDzU69XUuVIat/a +fDyZSWuVAd+Zbt3ueq8dQxfr46FnKBrNLyInCejIgOlLqc68Nji5RqnwfvxhYxcB +aDLi+h43ZBDiy4D8JbTCGtr4BCBZ7/YU7e7/i9elmFacQjV9m+xvfUrVnS23i4Ns +HIcbp0KAXjSEitahr0LjcxN09rjKtglqfQRz70uYnN5iBp2N3qNJMdda7md/szZc +5fr5W21Rb6HJ+eos/OkhwQ2ve3wx2QfsCDrM/sn1ds5IXv9lzEsuGLGAUm3LF46T +ZJW+weGWjYBD+N2HP19QHV2uebsdpAZLKpCchid3+21SBXcjhHqOzCoFtS4v+wxa +W+gNT0WxTHqWw+JyH7eqvwc06lxOOkLkX1dTGAFH3aeCZ2TEvFN7hMQa8dULrJfV ++c9LVhht3fx+hlFIk+X9QJBURgtVarHAR3E6unMpjba5Q8W9bcPDpCnmPgi2W1+u +MYeVk85RbvIItM4rzlaK3fIsQql4jz9i/T+NSTKCM6x8G2JHTz7bYMi5ZfJ7IaaK +IM7S6qIoumAy/WzJyYXiZX1L3yVbxJORvw3W3LHg0WLP3x8BweKhYKPMOi+edPmd +W6T43wECrwGKPsEbAkSEEXto+Wj3XmDpTMQlvQofVoM7uIe1R5PPUuRRpiggNeA9 +-----END RSA PRIVATE KEY----- diff --git a/example/certs/client.crt b/example/certs/client.crt new file mode 100644 index 0000000..08b6b81 --- /dev/null +++ b/example/certs/client.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFODCCAyACAQEwDQYJKoZIhvcNAQELBQAwXzELMAkGA1UEBhMCQ04xCzAJBgNV +BAgMAkdEMRIwEAYDVQQHDAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxEDAO +BgNVBAsMB2dSUENpdHkxCzAJBgNVBAMMAmNhMB4XDTIzMTAzMTEwNTMzM1oXDTI0 +MTAzMDEwNTMzM1owZTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMRIwEAYDVQQH +DAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxDzANBgNVBAsMBkNsaWVudDES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEAn4Fr3uQlGGjqealTYN5SN6tEM7WzD+VY+lt7f4NYdr2PLxswNbsdKk1dxvKr +e0RbmcPEZmQDKFI0hk2lM3/xZ4WkKI+IXNRh33CA/96NOeacxMpSnFXFhTQyxBaH +efR3OzLus3HCmxgfhktf6B2Razf0BehzXtEJ/LNnzO/zhgtpFDfuT4Ee9V5eYAzi +GLT/wgCRl7mnJGMf8QWGJndL4k/uKYEtNyjxCY1+g84hiLyEYsek8aBdoxfRzDPz +zid/4b3euNvwLSVQW5KBB/jCq4hI6ahZtSbKf9O1a5jW2APV3HDk9mXfjx/l6KMc +6OIu996kmiLSYDDDpLecTSBVuGIQfZw5A1ja97Z5M5/u6KIyjnMGuvC4ZqQfDYNC +9U8+M3dFWzEgqNflqdIK08/WsWjoX5Cwi2x2WuXPkiBZk31hgjifgWvB8AMqEVdf +ZJpuTMZxbjcwY1OUrET/e72DnN6Aev0Gm7czZrkLYwYSp4dfcQSgZ0bNM3O/XBMX +nEvRGtdITLYx4/wSaNcFs+E0fnvGbV/q8Gk4HehZCLmruEWM3VCfWuqN7QyLfs0X +ys7hi3a2YCtWDR8AVg5EiZUKT+qtbh3GuDg90nbDTnDzg6Rt5fv/vBfyGiNNan8M +JuBPzcqE4Ii+L7BBhOZultu3bVq5n3t763JJByyj2i9MSHcCAwEAATANBgkqhkiG +9w0BAQsFAAOCAgEAx8ctF/uR4qmiBeFfJOSk/aNDYTJLrW/VtBPayuawFfhHBlcJ +9pZ7xoJ+bTJSswxhz5YIItZ5DN7JIWCHnqOfYyNIrEvMrVxfma2TYELtmB3SXjst +e7K/pOu1Z2LiNogUdcbHpuHZtwQdCMukTBbTiUoFQyF7Ref506g8g1CnIS+8rCJ6 +F4/cCWEh/8rh3mXGUj7rxgTui4OI33ogRR2DrnDoWU89fxdFbEGWMj52VP5zozaI +uX4SLfTnpJtcewwUJCJXrF0rGH8l6LHXMOj1TnvveAkK4iegYddi+dJQ1u+/3f5n +MzXbYB+gUU/sERFe9mztttDfNqW0UQtU29hXyOCS9QUhyPzxT0jGfrK/tPeIkI9Y +sPsiG3awmnZ+7Ye8H2fctFEzSAufABhA4W16DiM0wYRwwbRuRCEdZplOMAElZMYW +G5++9/wBQEg2MsZgKP67Rn+ZPEEQrVC5ycm3Zbe1gCzDuTXzsNOpHTH8j1Ncje/G +W72/NNG2rK5Tp/TS/SAjr3/0xPRy8TwZNOthmCAlxtr8jKnjghPrdv4DgkfqW8lY +2nkUGsGC/Q/ajXjpZZA2x6zlh3Y0p5J+2E5Ppfgtcw62O5oyPq+081/TnfieQ7RA +PEjTX5f3qUO4pwxFAJq87QJV8egO1tbkZZ0guExdxo3/hiHZSrTFx3WeGys= +-----END CERTIFICATE----- diff --git a/example/certs/client.csr b/example/certs/client.csr new file mode 100644 index 0000000..4a722a0 --- /dev/null +++ b/example/certs/client.csr @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEqjCCApICAQAwZTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMRIwEAYDVQQH +DAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxDzANBgNVBAsMBkNsaWVudDES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEAn4Fr3uQlGGjqealTYN5SN6tEM7WzD+VY+lt7f4NYdr2PLxswNbsdKk1dxvKr +e0RbmcPEZmQDKFI0hk2lM3/xZ4WkKI+IXNRh33CA/96NOeacxMpSnFXFhTQyxBaH +efR3OzLus3HCmxgfhktf6B2Razf0BehzXtEJ/LNnzO/zhgtpFDfuT4Ee9V5eYAzi +GLT/wgCRl7mnJGMf8QWGJndL4k/uKYEtNyjxCY1+g84hiLyEYsek8aBdoxfRzDPz +zid/4b3euNvwLSVQW5KBB/jCq4hI6ahZtSbKf9O1a5jW2APV3HDk9mXfjx/l6KMc +6OIu996kmiLSYDDDpLecTSBVuGIQfZw5A1ja97Z5M5/u6KIyjnMGuvC4ZqQfDYNC +9U8+M3dFWzEgqNflqdIK08/WsWjoX5Cwi2x2WuXPkiBZk31hgjifgWvB8AMqEVdf +ZJpuTMZxbjcwY1OUrET/e72DnN6Aev0Gm7czZrkLYwYSp4dfcQSgZ0bNM3O/XBMX +nEvRGtdITLYx4/wSaNcFs+E0fnvGbV/q8Gk4HehZCLmruEWM3VCfWuqN7QyLfs0X +ys7hi3a2YCtWDR8AVg5EiZUKT+qtbh3GuDg90nbDTnDzg6Rt5fv/vBfyGiNNan8M +JuBPzcqE4Ii+L7BBhOZultu3bVq5n3t763JJByyj2i9MSHcCAwEAAaAAMA0GCSqG +SIb3DQEBCwUAA4ICAQB1KoPFz+w5zG6W7gpYYAxmwQa0CcriO0iEMVQkwv/satoY +f6F83tI0HCsuEwxyMI8IPd/LqvoqFRKJI+gBuou207NnvILj1P+QK0jiGO4fmK+j +SgJrxdqSLdJpeYGiqlAzVCfTMh7Z4vgIMCTgSWBqq0yBE/kQVsvuNF+/JNSOjtSi +K8jehp98RdiHNR58TTlywuQd4jAs5XBQSOYm2ye03kfHTDJ2ZKNaPobglrLX/hKt +cBW8cwO5GIhkFAPc3KUPS0iJQUkHyP23lMNV8qtmg2Edcd75mnnxdLp5vWOFb+RU +SalupDfRVhmO3NuHJeNvIAgsEf7tQmCO3CCJYs4QYNY+AUfWZQe1flRfda69/CAF +Sr91zel4XRH6DyR8VVd6KrlxjXUWJsdR9TMEsGW4eWYH2qLxZ7a+Hw3rdAnwvtLH +gO3rizcfA8T6W52fh4Bwp8LxOyBlPN/2bChTbTdGre5dcdub6c6ncOnpmW/CnPeP +yRu0dL3mKMcxerFmBc9svJW2hpgBpPGaV1sxzgpYaOkpN5IPUK0O/kvL8s4RsWcY +fsLNPIiFlrjkapiLaVeim9DOSKJl9kO5HWOUQ2ccc35E5EwFTmqIf0ix6eHogsg+ +FXR4OiiOoGVxPyoUewdytGgpIO9XKke52eytmFzjWV1sYf2xzlyAaim1aF0b9w== +-----END CERTIFICATE REQUEST----- diff --git a/example/certs/client.key b/example/certs/client.key new file mode 100644 index 0000000..563d3cc --- /dev/null +++ b/example/certs/client.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKQIBAAKCAgEAn4Fr3uQlGGjqealTYN5SN6tEM7WzD+VY+lt7f4NYdr2PLxsw +NbsdKk1dxvKre0RbmcPEZmQDKFI0hk2lM3/xZ4WkKI+IXNRh33CA/96NOeacxMpS +nFXFhTQyxBaHefR3OzLus3HCmxgfhktf6B2Razf0BehzXtEJ/LNnzO/zhgtpFDfu +T4Ee9V5eYAziGLT/wgCRl7mnJGMf8QWGJndL4k/uKYEtNyjxCY1+g84hiLyEYsek +8aBdoxfRzDPzzid/4b3euNvwLSVQW5KBB/jCq4hI6ahZtSbKf9O1a5jW2APV3HDk +9mXfjx/l6KMc6OIu996kmiLSYDDDpLecTSBVuGIQfZw5A1ja97Z5M5/u6KIyjnMG +uvC4ZqQfDYNC9U8+M3dFWzEgqNflqdIK08/WsWjoX5Cwi2x2WuXPkiBZk31hgjif +gWvB8AMqEVdfZJpuTMZxbjcwY1OUrET/e72DnN6Aev0Gm7czZrkLYwYSp4dfcQSg +Z0bNM3O/XBMXnEvRGtdITLYx4/wSaNcFs+E0fnvGbV/q8Gk4HehZCLmruEWM3VCf +WuqN7QyLfs0Xys7hi3a2YCtWDR8AVg5EiZUKT+qtbh3GuDg90nbDTnDzg6Rt5fv/ +vBfyGiNNan8MJuBPzcqE4Ii+L7BBhOZultu3bVq5n3t763JJByyj2i9MSHcCAwEA +AQKCAgB7HDI888wp3fhz9JwVHSNKMldOrgRJ1ZPqkCdu0Nq7sy1Lh2mbXxNwrgwe +XPi8CJPGx5HUEYdaXLCLpGxIpoyVgVGluLrgI1BzW+tFEeng8by4Kwy9+3mbiSFR +ta3VFDneqD4SpFA20tSqG40no4K0xZgD41kAqslRkMsiI2XLZQ0yfMTj+l4BtleT +PP5ZYYIxo6y79aAq9pMVvVTAZb1dLKI+yKQ0edYosFwgsT2ywwZPE5acpFEBa4YT +XVnlGRmcC6dW+PKUdcNjGl2a4IaoTGUyayzqI8mSBb52EJ5qVfN/1Gb0QsbdOhqC +hzruCH2F9QMIsK29boXioZDy+m0NzhsceFGq18GXCl1CEJlWTZKImLy8HY4E7ZMH +nK0iY3ccEBjGeaJthfvBEavztT1MMk20XHB+hQSncrL15P2uDdezrOMqxJ7JAVit +XJLZjiuCFYY3ZcMHEj3wFGqS/tkCPOvfw5HSc+GTbZovIBJy8+p9h9UD7IkhtzXW +x5HaajgOUOGQbY96JPgB7HKPTnmHCNuskjzebZJiTvVdTzczhE5kKWql05YPjIup +9ARwJWd3+EAFpAfA6reTCinPuqrLN8xqnUbyp7hEd9MWpReD23qvGzNTyVBI2Ulf +0TG16Hkpv7ESlg93aNaM5lz6L+2m5LKwcwg/WHmycT/5CS6E0QKCAQEAz+zMpDK6 +2zaK1H3ib2Ez1Cj+FRSHeKS4FQWYASDUNqpKInSQIjaar5Den3kOkXM8/YrPMLo4 +ju42XNFipHuqEOhwmd9vpZ45/E1LeKwJpSBV45P0Y8MwOotO5khixTSfC79yaR2w +7FiSiYwXlxv/0RZg8UhLQInU6KVU8vTd1pizrw7VbTSYjteirqQ818n0SkCH7JQS +O6vgEoqLkyH2kaPBE5RE/DpLuEziC/F1g7NgdOlv0N7Mze/2kgl0jP8waKnWorol +Rl46MSuQK4FAYs7XddYqYcsKbOICRrjnRN/xNKqtBL7VoWoqB2MqkUaufGGLYd2u +++ph6TqJStZcqQKCAQEAxGKlpnnj+Ou0j89a8Bb19eDXpQLFf1LTTHtbm5Dp+96M +WYsbBRI2pLQgmDGnwzGh1rbxVm2Tc3ujxD/9kpfEKsmfOT4pIF/G2U9e0RnE0Z6D +iTVMs8SWqpSYkQsfgGYGDDdHsO00Jds2QFzHtGmcPKhQO2oRLQzgWdcbPvbMqzVk +3rBcuXM8xyW8busYQmyKmKwqUihCA4mZriZ87MeOyBbWyuN7cW6ii7OqWEi4xfTy +UgCH+awjiuS1QAf04vv3S5mh/SOU49HHASpVGRNqPHDxbyCsCmW+93eiSXQ3GUXl +EsikrHCHPvn7y8SXeLvtbtPIlNbsIsHD9BQKvHiQHwKCAQEAoEQC/LFJi/x/iAg8 +B+Phgi/SoMcBIMG+Th3Qq1X1nOknWWWFT4nNM3Qz1LIHw58SrM9YolN3ktwUNPkD +0oqrbHrth/1MXlkWkt21RZ89k/TXnyIE5vylaQrF1wSGdUD5MqHvewxyucoPsUu1 +RzlCtpRMRs6VinpzDJubXeXWNDnhjhad9Z3r1XZqo7heWWoGDVGuM3FymGIDxeba +bJ9qIZoaEZBgmBYLFVTVi6UjEk+qEpN4J4QEUtwarzfwiVmNo679jNJ0NsgcjJfq +eU4YJdEPDHn2kwhg0cpnPMH7KQCAODsyP40kt1VQbf9G6VGU3rSFLfskjHJ947rF +5lRSeQKCAQEAmPQH9nAq6X2TBQrH1gTcDmPPMNrGvZLhtUjCoZgtVRkRENCx+7Ii +0wbj+AV8lx954AReVSVE2YXrl/cK5PjFNVoRZAERAQD3m9sgixVZ4LVn1x6nHcA4 +ZKUVaqpSH6vWe/82HAuzOOTSDTD17YLvx6KD0rKarA7CUdaihtirsZEFfhe4MEwb +gzPV3kHGhD2LeLtmvtRSDfVGt4eMdtI6V4bKRf0E8OTtPodxXg9NsghEDzAQt5ml +mRDmRfeseHksMzp2GvVyijmhmDvDSaOAc5C3ygiVVgfGw3Du+ezE2S9B6e5Rq2h9 +PcRvo1X7b3JWy6GxMJNwGOX3W4ucjQwNmwKCAQBximzQ7fRnm932y/w3u6RRKWb7 +FsKbNzcC4ZP+W1ecsj7Ck5qU5g2fAkTqpTTVKjMf9HDRtvzuBqgAWFS7G8IzJ98v +d2/o5m9vldhr76VI9aT8Oxh3fR4A35aaroP2y1jgdsQNizVgM5ujFRdVmVHNNgrI +v7ZoLQC3PFr/u0LDoSd8/pbHCLqA1JllXURj4qfqzdhEh0RpJF64VRa/kueVT6JT +ty9gBs7rPyvns3Jxs8eP334oyCpUYyBiS1o4dNJKryr4woIKSTzpuExNB/JVK7kY +QSF96Qt0Hjf3fxyf5W/V9njDSmOLzn/WPtGz0QvnUXyje512Rt/mJvwWyH2s +-----END RSA PRIVATE KEY----- diff --git a/example/certs/genCert.sh b/example/certs/genCert.sh new file mode 100644 index 0000000..d69ed02 --- /dev/null +++ b/example/certs/genCert.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +echo "Creating certs folder ..." +mkdir -p certs && cd certs + +echo "Generating certificates ..." + +# 设置密码变量 +password="grpcity" + +# 证书有效期变量 +days=365 + +# 设置CA相关变量 +ca_key="ca.key" +ca_crt="ca.crt" +ca_subject="/C=CN/ST=GD/L=Guangzhou/O=gRPCity/OU=gRPCity/CN=ca" + +# 设置服务器相关变量 +server_key="server.key" +server_csr="server.csr" +server_crt="server.crt" +server_subject="/C=CN/ST=GD/L=Guangzhou/O=gRPCity/OU=Server/CN=localhost" + +# 设置客户端相关变量 +client_key="client.key" +client_csr="client.csr" +client_crt="client.crt" +client_subject="/C=CN/ST=GD/L=Guangzhou/O=gRPCity/OU=Client/CN=localhost" + +# 生成CA密钥和证书 +openssl genrsa -passout pass:$password -des3 -out $ca_key 4096 +openssl req -passin pass:$password -new -x509 -days $days -key $ca_key -out $ca_crt -subj "$ca_subject" + +# 生成服务器密钥和证书 +openssl genrsa -passout pass:$password -des3 -out $server_key 4096 +openssl req -passin pass:$password -new -key $server_key -out $server_csr -subj "$server_subject" +openssl x509 -req -passin pass:$password -days $days -in $server_csr -CA $ca_crt -CAkey $ca_key -set_serial 01 -out $server_crt +openssl rsa -passin pass:$password -in $server_key -out $server_key + +# 生成客户端密钥和证书 +openssl genrsa -passout pass:$password -des3 -out $client_key 4096 +openssl req -passin pass:$password -new -key $client_key -out $client_csr -subj "$client_subject" +openssl x509 -passin pass:$password -req -days $days -in $client_csr -CA $ca_crt -CAkey $ca_key -set_serial 01 -out $client_crt +openssl rsa -passin pass:$password -in $client_key -out $client_key diff --git a/example/certs/server.crt b/example/certs/server.crt new file mode 100644 index 0000000..82c7185 --- /dev/null +++ b/example/certs/server.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFODCCAyACAQEwDQYJKoZIhvcNAQELBQAwXzELMAkGA1UEBhMCQ04xCzAJBgNV +BAgMAkdEMRIwEAYDVQQHDAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxEDAO +BgNVBAsMB2dSUENpdHkxCzAJBgNVBAMMAmNhMB4XDTIzMTAzMTEwNTMzMloXDTI0 +MTAzMDEwNTMzMlowZTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMRIwEAYDVQQH +DAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxDzANBgNVBAsMBlNlcnZlcjES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEA1c5i2MumzL0VH3tbUZz7Qm02O0zRJVw0AeHTtnXkRHMrny2L/47BxxOUp0FQ +McDIVk1rlPZ4ukywgksqCfOtDboj5D97RQvaKW2vGeUL3A44ebzV3Uf0D8QFloPa +sqIBjQfCUM3POTEDqfe02LP4SjMzIvCGK7qoAgfcUavSo92U9h9dpi/nW33jJQ8I +piY1hCCWdc2K3dZE8UvjAZSPCEm0LHgUfh1fjJwTZY5MSIspn0c0d6eCS5B9irdY +dj4ZtLxpIRABhn/OUjWJTLn2pgIAm3JIeEN5hgvoU1qmacel8uuUiZbXYIwrKtwM +Tn7gvLJVS3sW1ZjdDTbK+twh/CwGLmrOtjBusetU4luo+th74stSaz3E9mAhh+gQ +6D+oAoRwwqEIyUJKe7lb3uFx8K2j8nI4fwxBa+B4OpHz1fLp7IQPfQ6pkCmyG3nr +KHjOdpgk2x1msr2QAoMiQjsOs5kGGuv2MRjm+Stnqv++VV9P3h1Ca7wTRsWVLuWU +nLGifExfF++621qy7S6v/xJJtaSN6CJzMFMjNHL9Fncsr/GLYmhNIxGq2mGcIFd5 +Z4hYRbPmTe+VFTaCLPSkMwJ64Je6nfjOLPtQz+brnovsrZdITBF+oKQD5zUeIFGE +7aizWHM5jLqwWK9dv/xj5UScmQ8G9H8vxAFW30QjVcGx4SUCAwEAATANBgkqhkiG +9w0BAQsFAAOCAgEAMQZYWnkxz3ElDDK3jh8JG7jR9bzVLiiKo6oMgrGdjEp1qB6d +9xNFlgxFDPwqPTrNyuUD7L+466TwJGLGsiBVrZ0YUk3NliqFf8CUTTSHiPC3pxhw +UwrrHOxzw1yDogfJwrcJb5l8L6AtP9IdeFf49PakwKf2jxUvWCuoz/sKq7OlPfC1 +4IDveF7zhGFvgk0LYNWleyXL9Y0kpmByXEWdA+3z6v2GzJJbZ1GoBli91+gpLLvt +zUJwGBCkwbhBHTHt3nx0DwgsuJxiJd/+ETYOQyBeYvgMHrIo9KnMQ7/v2flYbuSq +9z1t+JIzRUORzt2UmDQMebnlWMFLx1P3HP7RxRAqoBGKCwz4m9zFTYkB/QgRCde9 +HQz47tLhnT51W26lNHmVet7XZ1cD0xz7sUVxIypqQ6Z0D13xbP4Ii7sZ3KvRkd05 +11XrXqXO/WPSLrGTqS655jNsXdMDFOFyhPQw882XiXU/CCS5IlX/AAEqtoSdkdBq +PrReEH9lHjmshJCBWimNqsSv7SiX4afV+P5DmXpra/MgFR8YVxAVdEz5Qz7+jtZM +lMEf2CFEwkYxmpwYR4+kJfP07RfONOQH4huZSZaAOse/+dFKKoVRpOGmkFE4zS04 +VlzKtTIAk9btmxPlvdz9DfLN7AvDkMDIBGDap/K3heNxd3GOmziwtoZe4Sg= +-----END CERTIFICATE----- diff --git a/example/certs/server.csr b/example/certs/server.csr new file mode 100644 index 0000000..3d9f12e --- /dev/null +++ b/example/certs/server.csr @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEqjCCApICAQAwZTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMRIwEAYDVQQH +DAlHdWFuZ3pob3UxEDAOBgNVBAoMB2dSUENpdHkxDzANBgNVBAsMBlNlcnZlcjES +MBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEA1c5i2MumzL0VH3tbUZz7Qm02O0zRJVw0AeHTtnXkRHMrny2L/47BxxOUp0FQ +McDIVk1rlPZ4ukywgksqCfOtDboj5D97RQvaKW2vGeUL3A44ebzV3Uf0D8QFloPa +sqIBjQfCUM3POTEDqfe02LP4SjMzIvCGK7qoAgfcUavSo92U9h9dpi/nW33jJQ8I +piY1hCCWdc2K3dZE8UvjAZSPCEm0LHgUfh1fjJwTZY5MSIspn0c0d6eCS5B9irdY +dj4ZtLxpIRABhn/OUjWJTLn2pgIAm3JIeEN5hgvoU1qmacel8uuUiZbXYIwrKtwM +Tn7gvLJVS3sW1ZjdDTbK+twh/CwGLmrOtjBusetU4luo+th74stSaz3E9mAhh+gQ +6D+oAoRwwqEIyUJKe7lb3uFx8K2j8nI4fwxBa+B4OpHz1fLp7IQPfQ6pkCmyG3nr +KHjOdpgk2x1msr2QAoMiQjsOs5kGGuv2MRjm+Stnqv++VV9P3h1Ca7wTRsWVLuWU +nLGifExfF++621qy7S6v/xJJtaSN6CJzMFMjNHL9Fncsr/GLYmhNIxGq2mGcIFd5 +Z4hYRbPmTe+VFTaCLPSkMwJ64Je6nfjOLPtQz+brnovsrZdITBF+oKQD5zUeIFGE +7aizWHM5jLqwWK9dv/xj5UScmQ8G9H8vxAFW30QjVcGx4SUCAwEAAaAAMA0GCSqG +SIb3DQEBCwUAA4ICAQB0Y1Rj2jdh9ISY/S3tSHsK4Le1y3aizF/pRoa1H8MG8LWb +2I0NNGUnXcpipyrjY5O6sn1X6Ea7W5SYWhijfVlLPXiYV4VWTdDIjI3c2PI6c1Nb +bART4TnFqcRdeifEsMOzUujMMf4MW/I4aW2648Wovid3Eo2YWhonWtj+Es+h8OEj +euhUFpeJSKy3RYqAqmG7+W4Bs8GdKjG6FIK7Xtf5VL3HCeIzu0mX56dGE2zrIblM +Fc8WUet87dbGMvzVEkeM2WAu3I1MNOzFU3hiD05G9REQPAooRgWQHNL+7P2ChBC3 +Q45vj1MKjR6bteqBs6unZpfvg9FYw0PFs1OHAmpPyG3OmpNtNL39Dxr0dXid4yp1 +XCLlOmaz8ff+v9DZrIGqyVQkpSI3pHKXchJTa+QFYk5ODs7fA7Uin6r84WNuCYZ5 +OjzGMlh/y15eR+Rpu6dXz9N/i/WJcJCg9bN6yvV58pyWj8iERNXjSbZaGQDfsSMN +EoEbqtBS/bFJprtUWDSBIafVXBfSDyAMH/rBVKMNLpnsAUON6DFfqgtKToFVVOiB +GvgxROb+oJnVMfesoX+TrtE1b4zm1aLs20BW1HLgkhMi8U+M3wxGHkZ+q9uQRXkH +yZvQh8NA5jCPcG6XqglfDlwWiOxe7tgsxLZyMmkpOe2Mome299578FAvIxe3XA== +-----END CERTIFICATE REQUEST----- diff --git a/example/certs/server.key b/example/certs/server.key new file mode 100644 index 0000000..f7b6d56 --- /dev/null +++ b/example/certs/server.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKQIBAAKCAgEA1c5i2MumzL0VH3tbUZz7Qm02O0zRJVw0AeHTtnXkRHMrny2L +/47BxxOUp0FQMcDIVk1rlPZ4ukywgksqCfOtDboj5D97RQvaKW2vGeUL3A44ebzV +3Uf0D8QFloPasqIBjQfCUM3POTEDqfe02LP4SjMzIvCGK7qoAgfcUavSo92U9h9d +pi/nW33jJQ8IpiY1hCCWdc2K3dZE8UvjAZSPCEm0LHgUfh1fjJwTZY5MSIspn0c0 +d6eCS5B9irdYdj4ZtLxpIRABhn/OUjWJTLn2pgIAm3JIeEN5hgvoU1qmacel8uuU +iZbXYIwrKtwMTn7gvLJVS3sW1ZjdDTbK+twh/CwGLmrOtjBusetU4luo+th74stS +az3E9mAhh+gQ6D+oAoRwwqEIyUJKe7lb3uFx8K2j8nI4fwxBa+B4OpHz1fLp7IQP +fQ6pkCmyG3nrKHjOdpgk2x1msr2QAoMiQjsOs5kGGuv2MRjm+Stnqv++VV9P3h1C +a7wTRsWVLuWUnLGifExfF++621qy7S6v/xJJtaSN6CJzMFMjNHL9Fncsr/GLYmhN +IxGq2mGcIFd5Z4hYRbPmTe+VFTaCLPSkMwJ64Je6nfjOLPtQz+brnovsrZdITBF+ +oKQD5zUeIFGE7aizWHM5jLqwWK9dv/xj5UScmQ8G9H8vxAFW30QjVcGx4SUCAwEA +AQKCAgASWPuyjwdpWnCNmxBjGI2XTbfxrs3j3t23q6F3bvZ/yUJdDpONArsuUkfX +cotnOZv3i/1Hcz84/YvIIpsg74BSRT7/P1NcwX+fPoJgPn+eCrpd+A6CRsJy9+di +2z3RRoXLjHboaED4L3SZCWDDl+4er/YbcXiSGBqC0hneCFizJzi5RkfLEyFPgKYV +cAzV9UFybTl8Mn9QOisAafq5D/6WP3zus/9OM0cX0ez4MhXpw8d2m62s9vr/cQ7U +8JbuzrV2BRUgeTcoS59w/pchtnOsG0/iBxSg/WlYT31IaecN/MwI5J6CkCc7acvq +iaektM6kYslfCNkUy2Fs2N7iWDGyQg1Bqaiz+Hhuvnyc+3+6oblChsEQ4STnpvR1 +L9UMsMke31q6FQRS/rOljCjVpLrv3NImz3O0IDZ0aMcPqpJVNHvyPk45GUNdVJ7Q +kG3eRlkUAtT/YKcQHtEpfJkPVyjLQ10vwV8vdV8Lnz0J/grWXZhOVwgVM+Z1zLxw +J1mjQQdzLNezKJp4iImKPigGQzmZjkuJQPJ9dpIJZkgXlv8pOcTt4X+L0ZhKn0bx +ASDdQEstkIhgha/V6Wu+qkHhDlQGQdRO3SvTS2VsdGsV06VrD1lziGdZSUlv2cGe +2cWH5TomzzfSvJx9DsNsCgJnUKS0u10UfI0P1K5sunBXBEWaoQKCAQEA67vmirK9 +SDrv4FTTPbILyBAjW1r5vNaaeO6/yl1LNA8scituLaGzVWP4YsY5/Ltn9QX6ZGBQ +7pD5BoYOE/hhnOrEurL114/J6mSADSt21Izjj0gDdo7TEOeGsNoBRsGNDm4fA71S +ZeAFmAB6CROs0KmdiUJrJoWgvofDBU/OFNInGEWjCRLnhLn0vxkz75x3IRUmtIlb +72L68t3FsN0HpY04Tvjor+Bg1u7IIgtoO7Usg0P3V0NS/SkCpvyUFk+kP3FXJIZg +D9EF0JuhHlqGbfmRD6aKODORkgwunmL53qjAGHDucQ6ScTsyYYhmVGA7enkTOaHs +KiPxey4I8gSYxwKCAQEA6C/kgsc7fmWuysGhgG+NxN0rlyGzrdKlpo5cIZsCYOiZ +9uLxaaSgh7pBzKTbQ13V7sjGF4/qNINyhaskxUPuYZji2+l7lfENn0BC+Ep+1RUK +TIygxYDOSqR200I/fNpU9roSjN4oakO4S/+3MisBBuFuIKKWEe1X9AO7Fs9uWsdm +2gXK09P2nLS/RxCVdPtR0YhvSmuKaw6E3aRUF/A9MX5cybJNRBt9M+/4zBquCldz +sgCbJN3LRUo7JTV9aTTb8R0uuk2bEPlsanPwYFBM0Podm7/5/Py1oNF2GOVtSn6T +jut88qJtPb+9bQuA/BHhCV10UMW5LCBK+BFGC+qCswKCAQEAkHJc/BQ2Q/JJVm7j +2eQlr/ujjrEaYoolsYCqaPftwwKhO3w9URzKZMKTSwMoOSMkulFlcuhJ70VKMqMm +MTp7pcl9ruFGH0ZudYALrwY5FFkYf42jAZzW3H7iW6/aJF8pbfQwcPwrZ110UAXK +wZEazemBLMBUJBCxxM4vxCt4ne2AIzFYi5DX6M6BmC97UZHQtabeRrX8bZ17JCKc +pplpvBnirRF8k3isHkfGvoW64wtLMUwOXZxVV+cvRt9yZpF6lZ1/xlPIvShZpdAX +VCAoS28nYi2seG+w/YsVbdw8PCGvQ8q/cOt69INPhdAs0/r7tzpFe4Uqz0+jAtXc +iWAjtQKCAQAL6/GauWoXmlb4OCr3skKgOg7z6poUMb2pqKOYYiIkIa1OHObyWq6X +aXvZaxmLAvVqFkr9iLkoyxsFO/1eV5eU0UnMqtdaoo7lf2Iw53pNrI/j0FCs82kk +Mf+b50nlOjykkndDXmDK3AFJfa7FV6ns1YRXDqIP95TNhaeEi1AKPzTLuwJoD/kY +oLs1hed2ozTXQl8cfhsUViGCU96xf/dUZD2VaZ9IAPVTxl2K9U+8XjMlj5xnry+x +thcFWRLAZSNp5OqyTCvLGlNzv9I2z8ix7jW4+ol2cO9Oe5LJwnXzHj6rVvIKb0aW +phKu16lex/g7B95iC1TvrBTNWe2zzO27AoIBAQCLU1chAwthFFUl0uqkBVFlmLqj +r3IS6CYUf38CEI4HyPIXk0FUCsphOaAcSbqtOFO4uvXy2srpnktIP5077wkH/Crd +p1e1mY8HxjUlimzOfM0hp3kFdslzjn59VtcLw52PyLqqTspTXlowlqery9Eh0qVa +c4s0/SDkWzHpb7FI7zW3X3+uol9bVNwXL4WIx313eI5oAXOP3XMwNXw7O4nSoSQg +cvP657XnBYBjtcefMTns4WkOeoc5tmzxTzJGLx6zPytaAdhbq0CAmErr5T+xfAom +CiRsq4THG0HX0olGxBYKskvwRJArRCnrTimEcVFRNagQgCbs5TvCyKCiZEwU +-----END RSA PRIVATE KEY----- diff --git a/example/helloworld/client.js b/example/helloworld/client.js new file mode 100644 index 0000000..d7af836 --- /dev/null +++ b/example/helloworld/client.js @@ -0,0 +1,60 @@ +import { loader, clientCredentials as credentials } from './loader.js' + +const start = async (addr) => { + await loader.init({ + isDev: true, + packagePrefix: 'dev' + }) + + const meta = loader.makeMetadata({ + 'x-cache-control': 'max-age=100', + 'x-business-id': ['grpcity', 'testing'], + 'x-timestamp-client': 'begin=' + new Date().toISOString() + }) + + const clients = await loader.initClients({ + services: { + 'helloworld.Greeter': addr, + 'helloworld.Hellor': addr + }, + credentials + }) + + // greeterClient + const greeterClient = clients.get('helloworld.Greeter', { + credentials + }) + const { status, metadata, response: result } = await greeterClient.sayGreet({ name: 'greeter' }, meta) + console.log('greeterClient.sayGreet', result) + console.log('greeterClient.sayGreet metadata', metadata) + console.log('greeterClient.sayGreet status', status) + + // hellorClient + const hellorClient = clients.get('helloworld.Hellor') + const { response: result2 } = await hellorClient.sayHello({ name: 'hellor2' }) + console.log('hellorClient.sayHello', result2) + + const { response: result3 } = await hellorClient.sayHello({ name: 'hellor3' }) + console.log('hellorClient.sayHello', result3) + + // initClients again + const twiceClients = await loader.initClients({ + services: { + 'helloworld.Hellor': addr + }, + credentials + }) + const newHellorClient = twiceClients.get('helloworld.Hellor') + const { response: result4 } = await newHellorClient.sayHello({ + name: 'hellor4' + }) + console.log('newHellorClient.sayHello', result4) + + // origin client + const { response: result5 } = await hellorClient.sayHello({ + name: 'hellor5' + }) + console.log('hellorClient.sayHello', result5) +} + +start('localhost:9099') diff --git a/example/helloworld/loader.js b/example/helloworld/loader.js new file mode 100644 index 0000000..6bfd22d --- /dev/null +++ b/example/helloworld/loader.js @@ -0,0 +1,29 @@ +import { ProtoLoader } from '../../lib/index.js' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +// get this file dir path +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export const loader = new ProtoLoader({ + location: path.join(__dirname, '../proto'), + files: ['helloworld/service.proto'] +}) + +export const serverCredentials = loader.makeServerCredentials( + fs.readFileSync(path.resolve(__dirname, '../certs/ca.crt')), + [ + { + private_key: fs.readFileSync(path.resolve(__dirname, '../certs/server.key')), + cert_chain: fs.readFileSync(path.resolve(__dirname, '../certs/server.crt')) + } + ], + true +) + +export const clientCredentials = loader.makeClientCredentials( + fs.readFileSync(path.resolve(__dirname, '../certs/ca.crt')), + fs.readFileSync(path.resolve(__dirname, '../certs/client.key')), + fs.readFileSync(path.resolve(__dirname, '../certs/client.crt')) +) diff --git a/example/helloworld/server.js b/example/helloworld/server.js new file mode 100644 index 0000000..8ea024c --- /dev/null +++ b/example/helloworld/server.js @@ -0,0 +1,106 @@ +import { loader, serverCredentials as credentials } from './loader.js' + +const timeout = (ms) => { + return new Promise((resolve, reject) => setTimeout(resolve, ms)) +} + +class Greeter { + init(server) { + server.add('helloworld.Greeter', this, { exclude: ['init'] }) + } + + async sayGreet(call) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + if (metadata.get('x-throw-error').length > 0) { + throw new Error('throw error because x-throw-error') + } + + if (metadata.get('x-long-delay').length > 0) { + await new Promise((resolve) => setTimeout(resolve, 1000 * 10)) + } + await timeout(1000) + + return { + message: `hello, ${call.request.name || 'world'}` + } + } + + async sayGreet2(call) { + return this.sayGreet(call) + } +} + +class Hellor { + constructor() { + this.count = 0 + } + + init(server) { + server.add('helloworld.Hellor', this, { exclude: ['init'] }) + } + + async SayHello(call) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + if (metadata.get('x-throw-error').length > 0) { + throw new Error('throw error because x-throw-error') + } + + if (metadata.get('x-long-delay').length > 0) { + await new Promise((resolve) => setTimeout(resolve, 1000 * 10)) + } + + this.count++ + + return { + message: `hello, ${call.request.name || 'world'}`, + count: this.count + } + } + + async SayHello2(call) { + return this.SayHello(call) + } +} + +const middlewareA = async (ctx, next) => { + const beginTime = new Date().getTime() + console.log('middlewareA: 1', ctx, beginTime) + await timeout(1000) + await next() + await timeout(1000) + const endTime = new Date().getTime() + console.log('middlewareA: 2', ctx, endTime, endTime - beginTime) +} + +const middlewareB = async (ctx, next) => { + const beginTime = new Date().getTime() + console.log('middlewareB: 1', ctx, beginTime) + await next() + const endTime = new Date().getTime() + console.log('middlewareB: 2', ctx, endTime, endTime - beginTime) +} + +const start = async (addr) => { + await loader.init({ + isDev: true, + packagePrefix: 'dev' + }) + + const server = await loader.initServer() + server.use(middlewareA, middlewareB) + // server.use([middlewareA, middlewareB]) + // server.use(middlewareA) + // server.use(middlewareB) + + const servicers = [new Greeter(), new Hellor()] + servicers.map((s) => s.init(server)) + + await server.listen(addr, credentials) + console.log('start:', addr) +} + +start('localhost:9099') diff --git a/example/proto/helloworld/model/message.proto b/example/proto/helloworld/model/message.proto new file mode 100755 index 0000000..37ee7f8 --- /dev/null +++ b/example/proto/helloworld/model/message.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package helloworld.model; + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; + int32 count = 2; +} diff --git a/example/proto/helloworld/service.proto b/example/proto/helloworld/service.proto new file mode 100755 index 0000000..4cfe6b1 --- /dev/null +++ b/example/proto/helloworld/service.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; + +import "helloworld/model/message.proto"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayGreet(model.HelloRequest) returns (model.HelloReply) {} + rpc SayGreet2(model.HelloRequest) returns (model.HelloReply) {} +} + +service Hellor { + rpc SayHello(model.HelloRequest) returns (model.HelloReply) {} + rpc SayHello2(model.HelloRequest) returns (model.HelloReply) {} +} diff --git a/example/proto/stream/service.proto b/example/proto/stream/service.proto new file mode 100644 index 0000000..fdd53fb --- /dev/null +++ b/example/proto/stream/service.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package stream; + +service Hellor { + rpc UnaryHello (Message) returns (Message) {} + rpc ClientStreamHello (stream Message) returns (Message) {} + rpc ServerStreamHello (Message) returns (stream Message) {} + rpc MutualStreamHello (stream Message) returns (stream Message) {} +} + +message Message { + string message = 1; +} From 8ad3593fbf17e1a169f5ce5d4da71f1132aaa71c Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 18:59:33 +0800 Subject: [PATCH 06/30] fix: allow no args --- src/client/bidiStreamProxy.ts | 6 +++--- src/client/clientStreamProxy.ts | 6 +++--- src/client/serverStreamProxy.ts | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/client/bidiStreamProxy.ts b/src/client/bidiStreamProxy.ts index ba4cfb7..0b7a6de 100644 --- a/src/client/bidiStreamProxy.ts +++ b/src/client/bidiStreamProxy.ts @@ -1,7 +1,7 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import iterator from '../utils/iterator' -import type { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' +import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const bidiStreamProxy = ( client: UntypedServiceImplementation, @@ -9,14 +9,14 @@ export const bidiStreamProxy = ( defaultMetadata: Record, defaultOptions: Record ) => { - return (metadata: Metadata, options: Record): any => { + return (metadata?: Metadata, options?: Record): any => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } - metadata = combineMetadata(metadata, defaultMetadata) + metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = Object.assign({}, defaultOptions, options) const call = func.apply(client, [metadata, options]) diff --git a/src/client/clientStreamProxy.ts b/src/client/clientStreamProxy.ts index 2e8e201..b90c66c 100644 --- a/src/client/clientStreamProxy.ts +++ b/src/client/clientStreamProxy.ts @@ -1,6 +1,6 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' -import type { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' +import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const clientStreamProxy = ( client: UntypedServiceImplementation, @@ -8,14 +8,14 @@ export const clientStreamProxy = ( defaultMetadata: Record, defaultOptions: Record ) => { - return (metadata: Metadata, options: Record): any => { + return (metadata?: Metadata, options?: Record): any => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } - metadata = combineMetadata(metadata, defaultMetadata) + metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = Object.assign({}, defaultOptions, options) const result: { response?: any; metadata?: Metadata; status?: StatusObject } = {} diff --git a/src/client/serverStreamProxy.ts b/src/client/serverStreamProxy.ts index d797a2e..5177d85 100644 --- a/src/client/serverStreamProxy.ts +++ b/src/client/serverStreamProxy.ts @@ -1,7 +1,7 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import iterator from '../utils/iterator' -import type { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' +import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const serverStreamProxy = ( client: UntypedServiceImplementation, @@ -9,14 +9,14 @@ export const serverStreamProxy = ( defaultMetadata: Record, defaultOptions: Record ) => { - return (request: any, metadata: Metadata, options: Record): any => { + return (request: any, metadata?: Metadata, options?: Record): any => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } - metadata = combineMetadata(metadata, defaultMetadata) + metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = Object.assign({}, defaultOptions, options) const call = func.apply(client, [request, metadata, options]) From 0e6f287520affac9cc37aedb48c1c74cc55266e6 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 19:44:47 +0800 Subject: [PATCH 07/30] feat: set deadline --- src/client/bidiStreamProxy.ts | 3 ++- src/client/clientDeadline.ts | 10 ++++++++++ src/client/clientStreamProxy.ts | 3 ++- src/client/serverStreamProxy.ts | 3 ++- src/client/unaryProxy.ts | 3 ++- 5 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 src/client/clientDeadline.ts diff --git a/src/client/bidiStreamProxy.ts b/src/client/bidiStreamProxy.ts index 0b7a6de..03c4bed 100644 --- a/src/client/bidiStreamProxy.ts +++ b/src/client/bidiStreamProxy.ts @@ -1,5 +1,6 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' +import { setDeadline } from './clientDeadline' import iterator from '../utils/iterator' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' @@ -17,7 +18,7 @@ export const bidiStreamProxy = ( } metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) - options = Object.assign({}, defaultOptions, options) + options = setDeadline(options, defaultOptions) const call = func.apply(client, [metadata, options]) diff --git a/src/client/clientDeadline.ts b/src/client/clientDeadline.ts new file mode 100644 index 0000000..8bf332a --- /dev/null +++ b/src/client/clientDeadline.ts @@ -0,0 +1,10 @@ +export const setDeadline = (options: Record | undefined, defaultOptions: Record) => { + options = Object.assign({}, defaultOptions, options) + if (!options?.deadline) { + const timeout = options.timeout || 1000 * 10 + const deadline = new Date(Date.now() + (timeout as number)) + options.deadline = deadline + delete options.timeout + } + return options +} diff --git a/src/client/clientStreamProxy.ts b/src/client/clientStreamProxy.ts index b90c66c..f3202fd 100644 --- a/src/client/clientStreamProxy.ts +++ b/src/client/clientStreamProxy.ts @@ -1,5 +1,6 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' +import { setDeadline } from './clientDeadline' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const clientStreamProxy = ( @@ -16,7 +17,7 @@ export const clientStreamProxy = ( } metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) - options = Object.assign({}, defaultOptions, options) + options = setDeadline(options, defaultOptions) const result: { response?: any; metadata?: Metadata; status?: StatusObject } = {} diff --git a/src/client/serverStreamProxy.ts b/src/client/serverStreamProxy.ts index 5177d85..8ed3416 100644 --- a/src/client/serverStreamProxy.ts +++ b/src/client/serverStreamProxy.ts @@ -1,5 +1,6 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' +import { setDeadline } from './clientDeadline' import iterator from '../utils/iterator' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' @@ -17,7 +18,7 @@ export const serverStreamProxy = ( } metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) - options = Object.assign({}, defaultOptions, options) + options = setDeadline(options, defaultOptions) const call = func.apply(client, [request, metadata, options]) diff --git a/src/client/unaryProxy.ts b/src/client/unaryProxy.ts index 293f2ce..298ea76 100644 --- a/src/client/unaryProxy.ts +++ b/src/client/unaryProxy.ts @@ -1,5 +1,6 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' +import { setDeadline } from './clientDeadline' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const unaryProxy = ( @@ -16,7 +17,7 @@ export const unaryProxy = ( } metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) - options = Object.assign({}, defaultOptions, options) + options = setDeadline(options, defaultOptions) return new Promise((resolve, reject) => { const result: { response?: any; metadata?: Metadata; status?: StatusObject } = {} From fd807658231487d4a14ff8838aa3a412574ea0e8 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 19:47:22 +0800 Subject: [PATCH 08/30] chore: rename x-service --- src/client/clientError.ts | 2 +- src/client/clientProxy.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/clientError.ts b/src/client/clientError.ts index 82a3979..2d2d62d 100644 --- a/src/client/clientError.ts +++ b/src/client/clientError.ts @@ -10,7 +10,7 @@ export const createClientError = (err: any, metadata?: Metadata) => { newError.name = 'GrpcClientError' newError.code = err.code - newError.message = `${metadata?.get('x-service-name')} (${err.message})` + newError.message = `${metadata?.get('x-service-path')} (${err.message})` const stacks = newError.stack!.split('\n') newError.stack = [stacks[0], ...stacks.slice(2), ' ...', ...(err.stack!.split('\n').slice(1, 3) as string[])].join('\n') diff --git a/src/client/clientProxy.ts b/src/client/clientProxy.ts index a52b6db..f068d53 100644 --- a/src/client/clientProxy.ts +++ b/src/client/clientProxy.ts @@ -27,7 +27,7 @@ export const clientProxy = (client: UntypedServiceImplementation, options: Recor const target = Object.entries(prototype).reduce( (target: any, [name, func]) => { if (name !== 'constructor' && typeof func === 'function') { - metadata['x-service-name'] = `${methodNames[name.toUpperCase()]}` + metadata['x-service-path'] = `${methodNames[name.toUpperCase()]}` const { requestStream, responseStream } = getFuncStreamType(func) From eaa25f88c70796a4373bfe3468e6ae9263b5bac0 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 20:31:31 +0800 Subject: [PATCH 09/30] chore: adjust make credentials type --- src/loader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/loader.ts b/src/loader.ts index c4e2ca2..4e08faf 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -77,7 +77,7 @@ export class ProtoLoader { return new Server(this, options) } - makeClientCredentials(rootCerts?: any, privateKey?: any, certChain?: any, verifyOptions?: any) { + makeClientCredentials(rootCerts?: Buffer, privateKey?: Buffer, certChain?: Buffer, verifyOptions?: any) { if (rootCerts && privateKey && certChain) { return grpc.credentials.createSsl(rootCerts, privateKey, certChain, verifyOptions) } else { From 30fa1f72a7459a259a7446b8da746c85918ca23f Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 21:14:06 +0800 Subject: [PATCH 10/30] fix: adjust remove --- src/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/index.ts b/src/server/index.ts index 05fadac..4e6945b 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -84,7 +84,7 @@ export default class Server { this._server!.addService(service, callbackify(implementation, this._middleware, options)) } - removeService(name: string): void { + remove(name: string): void { this._server!.removeService((this._loader as ProtoLoader).service(name)) } From 484af6546c6a662b5cb71df7691a81d410aa4399 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Thu, 4 Jan 2024 23:28:01 +0800 Subject: [PATCH 11/30] chore: adjust helloword console --- example/helloworld/client.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/example/helloworld/client.js b/example/helloworld/client.js index d7af836..5bc7316 100644 --- a/example/helloworld/client.js +++ b/example/helloworld/client.js @@ -31,12 +31,12 @@ const start = async (addr) => { // hellorClient const hellorClient = clients.get('helloworld.Hellor') + const { response: result1 } = await hellorClient.sayHello({ name: 'hellor1' }) + console.log('hellorClient.sayHello', result1) + const { response: result2 } = await hellorClient.sayHello({ name: 'hellor2' }) console.log('hellorClient.sayHello', result2) - const { response: result3 } = await hellorClient.sayHello({ name: 'hellor3' }) - console.log('hellorClient.sayHello', result3) - // initClients again const twiceClients = await loader.initClients({ services: { @@ -45,16 +45,15 @@ const start = async (addr) => { credentials }) const newHellorClient = twiceClients.get('helloworld.Hellor') - const { response: result4 } = await newHellorClient.sayHello({ - name: 'hellor4' - }) - console.log('newHellorClient.sayHello', result4) + const { response: result3 } = await newHellorClient.sayHello({ name: 'hellor3' }) + console.log('newHellorClient.sayHello', result3) // origin client - const { response: result5 } = await hellorClient.sayHello({ - name: 'hellor5' + const twiceHellorClient = clients.get('helloworld.Hellor') + const { response: result4 } = await twiceHellorClient.sayHello({ + name: 'hellor4' }) - console.log('hellorClient.sayHello', result5) + console.log('hellorClient.sayHello', result4) } start('localhost:9099') From abd5b613f3e682c02ca46b070ce9d482d8242d64 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sat, 6 Jan 2024 01:09:29 +0800 Subject: [PATCH 12/30] feat: support client middleware --- example/helloworld/client.js | 22 +++++---- src/client/bidiStreamProxy.ts | 37 ++++++++++------ src/client/clientContext.ts | 23 ++++++++++ src/client/clientProxy.ts | 10 ++--- src/client/clientStreamProxy.ts | 27 ++++++++---- src/client/index.ts | 25 ++++++++++- src/client/serverStreamProxy.ts | 37 ++++++++++------ src/client/unaryProxy.ts | 49 +++++++++++++-------- src/server/callBidiStreamProxy.ts | 2 +- src/server/callClientStreamProxy.ts | 2 +- src/server/callServerStreamProxy.ts | 2 +- src/server/callUnaryProxy.ts | 2 +- src/server/index.ts | 6 +-- src/server/{context.ts => serverContext.ts} | 4 +- test/bidiStream.test.ts | 0 test/clientStream.test.ts | 0 test/loader.test.ts | 0 test/serverStream.test.ts | 0 test/unary.test.ts | 0 19 files changed, 170 insertions(+), 78 deletions(-) create mode 100644 src/client/clientContext.ts rename src/server/{context.ts => serverContext.ts} (73%) create mode 100644 test/bidiStream.test.ts create mode 100644 test/clientStream.test.ts create mode 100644 test/loader.test.ts create mode 100644 test/serverStream.test.ts create mode 100644 test/unary.test.ts diff --git a/example/helloworld/client.js b/example/helloworld/client.js index 5bc7316..d6e9a80 100644 --- a/example/helloworld/client.js +++ b/example/helloworld/client.js @@ -12,6 +12,13 @@ const start = async (addr) => { 'x-timestamp-client': 'begin=' + new Date().toISOString() }) + const logMiddleware = async (ctx, next) => { + const beginTime = new Date().getTime() + await next() + const endTime = new Date().getTime() + console.log(ctx.res.response, endTime - beginTime) + } + const clients = await loader.initClients({ services: { 'helloworld.Greeter': addr, @@ -20,6 +27,8 @@ const start = async (addr) => { credentials }) + clients.use(logMiddleware) + // greeterClient const greeterClient = clients.get('helloworld.Greeter', { credentials @@ -31,11 +40,9 @@ const start = async (addr) => { // hellorClient const hellorClient = clients.get('helloworld.Hellor') - const { response: result1 } = await hellorClient.sayHello({ name: 'hellor1' }) - console.log('hellorClient.sayHello', result1) + await hellorClient.sayHello({ name: 'hellor1' }) - const { response: result2 } = await hellorClient.sayHello({ name: 'hellor2' }) - console.log('hellorClient.sayHello', result2) + await hellorClient.sayHello({ name: 'hellor2' }) // initClients again const twiceClients = await loader.initClients({ @@ -44,16 +51,15 @@ const start = async (addr) => { }, credentials }) + twiceClients.use(logMiddleware) const newHellorClient = twiceClients.get('helloworld.Hellor') - const { response: result3 } = await newHellorClient.sayHello({ name: 'hellor3' }) - console.log('newHellorClient.sayHello', result3) + await newHellorClient.sayHello({ name: 'hellor3' }) // origin client const twiceHellorClient = clients.get('helloworld.Hellor') - const { response: result4 } = await twiceHellorClient.sayHello({ + await twiceHellorClient.sayHello({ name: 'hellor4' }) - console.log('hellorClient.sayHello', result4) } start('localhost:9099') diff --git a/src/client/bidiStreamProxy.ts b/src/client/bidiStreamProxy.ts index 03c4bed..cad8ca4 100644 --- a/src/client/bidiStreamProxy.ts +++ b/src/client/bidiStreamProxy.ts @@ -1,6 +1,7 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' +import { createContext } from './clientContext' import iterator from '../utils/iterator' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' @@ -8,9 +9,10 @@ export const bidiStreamProxy = ( client: UntypedServiceImplementation, func: any, defaultMetadata: Record, - defaultOptions: Record + defaultOptions: Record, + composeFunc: Function ) => { - return (metadata?: Metadata, options?: Record): any => { + return async (metadata?: Metadata, options?: Record): any => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { @@ -20,6 +22,8 @@ export const bidiStreamProxy = ( metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = setDeadline(options, defaultOptions) + const ctx = createContext({ metadata, options }) + const call = func.apply(client, [metadata, options]) call.writeAll = (messages: any[]) => { @@ -35,20 +39,25 @@ export const bidiStreamProxy = ( throw createClientError(err, metadata) }) - const result: { metadata?: Metadata; status?: StatusObject } = {} - call.readAll = () => { - call.on('metadata', (metadata: Metadata) => { - result.metadata = metadata - }) - call.on('status', (status: StatusObject) => { - result.status = status - }) - return iterator(call, 'data', { - resolutionEvents: ['status', 'end'] - }) + const handler = async () => { + call.readAll = () => { + call.on('metadata', (metadata: Metadata) => { + ctx.res.metadata = metadata + }) + call.on('status', (status: StatusObject) => { + ctx.res.status = status + }) + return iterator(call, 'data', { + resolutionEvents: ['status', 'end'] + }) + } } + await composeFunc(ctx, handler).catch((err: Error) => { + throw createClientError(err, metadata) + }) + call.readEnd = () => { - return result + return ctx.res } return call diff --git a/src/client/clientContext.ts b/src/client/clientContext.ts new file mode 100644 index 0000000..7e32653 --- /dev/null +++ b/src/client/clientContext.ts @@ -0,0 +1,23 @@ +import * as grpc from '@grpc/grpc-js' + +export type ClientContextType = { + path: string + req: { + request?: any + metadata?: grpc.Metadata + options?: Record + } + res: { + response?: any + metadata?: grpc.Metadata + status?: grpc.StatusObject + } +} + +export const createContext = (args: { request?: any; metadata?: grpc.Metadata; options?: Record }): ClientContextType => { + return { + path: args.metadata?.get('x-service-path')[0].toString() || '', + req: args, + res: {} + } +} diff --git a/src/client/clientProxy.ts b/src/client/clientProxy.ts index f068d53..38496d3 100644 --- a/src/client/clientProxy.ts +++ b/src/client/clientProxy.ts @@ -11,7 +11,7 @@ const getFuncStreamType = (func: any) => { return { requestStream, responseStream } } -export const clientProxy = (client: UntypedServiceImplementation, options: Record) => { +export const clientProxy = (client: UntypedServiceImplementation, options: Record, fn: Function) => { const prototype = Object.getPrototypeOf(client) const methodNames: any = Object.keys(prototype) .filter((key) => prototype[key] && prototype[key].path) @@ -33,21 +33,21 @@ export const clientProxy = (client: UntypedServiceImplementation, options: Recor if (!requestStream && !responseStream) { // promisify unary method - target[name] = unaryProxy(client, func, metadata, options) + target[name] = unaryProxy(client, func, metadata, options, fn) } // stream if (requestStream && !responseStream) { // promisify only client stream method - target[name] = clientStreamProxy(client, func, metadata, options) + target[name] = clientStreamProxy(client, func, metadata, options, fn) } if (!requestStream && responseStream) { // promisify only server stream method - target[name] = serverStreamProxy(client, func, metadata, options) + target[name] = serverStreamProxy(client, func, metadata, options, fn) } if (requestStream && responseStream) { // promisify duplex stream method - target[name] = bidiStreamProxy(client, func, metadata, options) + target[name] = bidiStreamProxy(client, func, metadata, options, fn) } // keep callback method diff --git a/src/client/clientStreamProxy.ts b/src/client/clientStreamProxy.ts index f3202fd..bd2d1c3 100644 --- a/src/client/clientStreamProxy.ts +++ b/src/client/clientStreamProxy.ts @@ -1,15 +1,17 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' +import { createContext } from './clientContext' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const clientStreamProxy = ( client: UntypedServiceImplementation, func: any, defaultMetadata: Record, - defaultOptions: Record + defaultOptions: Record, + composeFunc: Function ) => { - return (metadata?: Metadata, options?: Record): any => { + return async (metadata?: Metadata, options?: Record): any => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { @@ -19,14 +21,14 @@ export const clientStreamProxy = ( metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = setDeadline(options, defaultOptions) - const result: { response?: any; metadata?: Metadata; status?: StatusObject } = {} + const ctx = createContext({ metadata, options }) const argumentsList: Array = [metadata, options] argumentsList.push((err: any, response: any) => { if (err) { throw createClientError(err, metadata) } - result.response = response + ctx.res.response = response }) const call = func.apply(client, argumentsList) @@ -38,18 +40,25 @@ export const clientStreamProxy = ( }) } } - call.writeEnd = async () => { + + const handler = async () => { call.end() - await new Promise((resolve, reject) => { + await new Promise((resolve, _) => { call.on('metadata', (metadata: Metadata) => { - result.metadata = metadata + ctx.res.metadata = metadata }) call.on('status', (status: StatusObject) => { - result.status = status + ctx.res.status = status resolve() }) }) - return result + } + + call.writeEnd = async () => { + await composeFunc(ctx, handler).catch((err: Error) => { + throw createClientError(err, metadata) + }) + return ctx.res } return call diff --git a/src/client/index.ts b/src/client/index.ts index c5bc906..c0c81c6 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,8 +1,11 @@ +import assert from 'node:assert' import * as _ from 'lodash-es' import { ProtoLoader } from '../loader' import { ClientFactory } from './clientFactory' import { clientProxy } from './clientProxy' +import { compose } from '../utils/compose' +import type { MiddlewareFunction } from '../utils/compose' import type { ClientsOptionsType, AddressObject } from '../schema/loader' import type { ClientOptionsType } from '../schema/client' @@ -14,6 +17,7 @@ const prepareUrl = (url: ClientOptionsType['url']) => { } export default class Clients { + private _middleware: MiddlewareFunction[] = [] private _proxyClientMap: Map = new Map() private _clientFactory: ClientFactory private _credentials: ClientsOptionsType['credentials'] @@ -54,8 +58,10 @@ export default class Clients { return this._proxyClientMap.get(cacheKey) } + const fn = compose(this._middleware) + const client = this._clientFactory.create(isDefaultClient, name, addr, credentials, channelOptions) - const proxy = clientProxy(client, { timeout }) + const proxy = clientProxy(client, { timeout }, fn) this._proxyClientMap.set(cacheKey, proxy) return proxy } @@ -73,6 +79,23 @@ export default class Clients { return client } + use(...args: MiddlewareFunction[]): void { + assert(args.length >= 1, 'client use() takes at least one middleware.') + if (args.length === 1) { + if (Array.isArray(args[0])) { + args[0].forEach((fn) => { + this._middleware.push(fn) + }) + } else { + this._middleware.push(args[0]) + } + } else { + args.forEach((fn) => { + this._middleware.push(fn) + }) + } + } + clear() { this._clientFactory.clear() this._proxyClientMap.clear() diff --git a/src/client/serverStreamProxy.ts b/src/client/serverStreamProxy.ts index 8ed3416..afbc6ac 100644 --- a/src/client/serverStreamProxy.ts +++ b/src/client/serverStreamProxy.ts @@ -2,15 +2,17 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' import iterator from '../utils/iterator' +import { createContext } from './clientContext' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const serverStreamProxy = ( client: UntypedServiceImplementation, func: any, defaultMetadata: Record, - defaultOptions: Record + defaultOptions: Record, + composeFunc: Function ) => { - return (request: any, metadata?: Metadata, options?: Record): any => { + return async (request?: any, metadata?: Metadata, options?: Record): any => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { @@ -20,26 +22,33 @@ export const serverStreamProxy = ( metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = setDeadline(options, defaultOptions) + const ctx = createContext({ request, metadata, options }) + const call = func.apply(client, [request, metadata, options]) call.on('error', (err: Error) => { throw createClientError(err, metadata) }) - const result: { metadata?: Metadata; status?: StatusObject } = {} - call.readAll = () => { - call.on('metadata', (metadata: Metadata) => { - result.metadata = metadata - }) - call.on('status', (status: StatusObject) => { - result.status = status - }) - return iterator(call, 'data', { - resolutionEvents: ['status', 'end'] - }) + const handler = async () => { + call.readAll = () => { + call.on('metadata', (metadata: Metadata) => { + ctx.res.metadata = metadata + }) + call.on('status', (status: StatusObject) => { + ctx.res.status = status + }) + return iterator(call, 'data', { + resolutionEvents: ['status', 'end'] + }) + } } + await composeFunc(ctx, handler).catch((err: Error) => { + throw createClientError(err, metadata) + }) + call.readEnd = () => { - return result + return ctx.res } return call diff --git a/src/client/unaryProxy.ts b/src/client/unaryProxy.ts index 298ea76..2aecfcc 100644 --- a/src/client/unaryProxy.ts +++ b/src/client/unaryProxy.ts @@ -1,15 +1,17 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' +import { createContext } from './clientContext' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const unaryProxy = ( client: UntypedServiceImplementation, func: any, defaultMetadata: Record, - defaultOptions: Record + defaultOptions: Record, + composeFunc: Function ) => { - return async (request: any, metadata?: Metadata, options?: Record): Promise => { + return async (request?: any, metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { throw new Error('gRPCity: AsyncFunction should not contain a callback function') } else if (typeof metadata === 'function') { @@ -19,25 +21,36 @@ export const unaryProxy = ( metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = setDeadline(options, defaultOptions) - return new Promise((resolve, reject) => { - const result: { response?: any; metadata?: Metadata; status?: StatusObject } = {} - const argumentsList: Array = [request, metadata, options] - argumentsList.push((err: any, response: any) => { - if (err) { - reject(createClientError(err, metadata)) - } - result.response = response - }) + const ctx = createContext({ request, metadata, options }) - const call = func.apply(client, argumentsList) + const handler = async () => { + await new Promise((resolve, reject) => { + let { request, metadata, options } = ctx.req - call.on('metadata', (metadata: Metadata) => { - result.metadata = metadata - }) - call.on('status', (status: StatusObject) => { - result.status = status - resolve(result) + const argumentsList: Array = [request, metadata, options] + argumentsList.push((err: any, response: any) => { + if (err) { + reject(createClientError(err, metadata)) + } + ctx.res.response = response + }) + + const call = func.apply(client, argumentsList) + + call.on('metadata', (metadata: Metadata) => { + ctx.res.metadata = metadata + }) + call.on('status', (status: StatusObject) => { + ctx.res.status = status + resolve() + }) }) + } + + await composeFunc(ctx, handler).catch((err: Error) => { + throw createClientError(err, metadata) }) + + return ctx.res } } diff --git a/src/server/callBidiStreamProxy.ts b/src/server/callBidiStreamProxy.ts index 3b939d4..5b46e95 100644 --- a/src/server/callBidiStreamProxy.ts +++ b/src/server/callBidiStreamProxy.ts @@ -1,6 +1,6 @@ import * as grpc from '@grpc/grpc-js' import iterator from '../utils/iterator' -import { createContext } from './context' +import { createContext } from './serverContext' import { createServerError } from './serverError' export const callBidiStreamProxy = (target: any, key: string, composeFunc: Function): grpc.handleBidiStreamingCall => { diff --git a/src/server/callClientStreamProxy.ts b/src/server/callClientStreamProxy.ts index 02e2de0..b57e218 100644 --- a/src/server/callClientStreamProxy.ts +++ b/src/server/callClientStreamProxy.ts @@ -1,6 +1,6 @@ import * as grpc from '@grpc/grpc-js' import iterator from '../utils/iterator' -import { createContext } from './context' +import { createContext } from './serverContext' import { createServerError } from './serverError' export const callClientStreamProxy = (target: any, key: string, composeFunc: Function): grpc.handleClientStreamingCall => { diff --git a/src/server/callServerStreamProxy.ts b/src/server/callServerStreamProxy.ts index e25f142..164c3dd 100644 --- a/src/server/callServerStreamProxy.ts +++ b/src/server/callServerStreamProxy.ts @@ -1,5 +1,5 @@ import * as grpc from '@grpc/grpc-js' -import { createContext } from './context' +import { createContext } from './serverContext' import { createServerError } from './serverError' export const callServerStreamProxy = (target: any, key: string, composeFunc: Function): grpc.handleServerStreamingCall => { diff --git a/src/server/callUnaryProxy.ts b/src/server/callUnaryProxy.ts index 405686d..2edf6eb 100644 --- a/src/server/callUnaryProxy.ts +++ b/src/server/callUnaryProxy.ts @@ -1,5 +1,5 @@ import * as grpc from '@grpc/grpc-js' -import { createContext } from './context' +import { createContext } from './serverContext' import { createServerError } from './serverError' export const callUnaryProxy = (target: any, key: string, composeFunc: Function): grpc.handleUnaryCall => { diff --git a/src/server/index.ts b/src/server/index.ts index 4e6945b..576a0ee 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -2,11 +2,11 @@ import assert from 'node:assert' import * as grpc from '@grpc/grpc-js' import * as _ from 'lodash-es' -import { MiddlewareFunction } from '../utils/compose' import { assignServerOptions } from '../schema/server' import { ProtoLoader } from '../loader' -import type { ServerOptionsType } from '../schema/loader' import { callbackify } from './callbackify' +import type { ServerOptionsType } from '../schema/loader' +import type { MiddlewareFunction } from '../utils/compose' import type { CallbackifyOptions } from './callbackify' export default class Server { @@ -89,7 +89,7 @@ export default class Server { } use(...args: MiddlewareFunction[]): void { - assert(args.length >= 1, 'server addMiddleware() takes at least one argument.') + assert(args.length >= 1, 'server use() takes at least one middleware.') if (args.length === 1) { if (Array.isArray(args[0])) { args[0].forEach((fn) => { diff --git a/src/server/context.ts b/src/server/serverContext.ts similarity index 73% rename from src/server/context.ts rename to src/server/serverContext.ts index 8a04fc8..1b791a0 100644 --- a/src/server/context.ts +++ b/src/server/serverContext.ts @@ -1,13 +1,13 @@ import * as grpc from '@grpc/grpc-js' -export type ContextType = { +export type ServerContextType = { path: string request: any metadata: grpc.Metadata response?: any } -export const createContext = (call: Record): ContextType => { +export const createContext = (call: Record): ServerContextType => { return { // TODO: maybe need more details // method: target.constructor.name + '.' + key, diff --git a/test/bidiStream.test.ts b/test/bidiStream.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/clientStream.test.ts b/test/clientStream.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/loader.test.ts b/test/loader.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/serverStream.test.ts b/test/serverStream.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/unary.test.ts b/test/unary.test.ts new file mode 100644 index 0000000..e69de29 From 4d664cf01ca82e43cdb175a230490c2764b69c09 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sat, 6 Jan 2024 03:05:55 +0800 Subject: [PATCH 13/30] feat: stream client support middleware --- example/asyncStream/client.js | 68 ++++++++++++++++++++++++++++++ example/asyncStream/loader.js | 11 +++++ example/asyncStream/server.js | 74 +++++++++++++++++++++++++++++++++ example/stream/client.js | 73 ++++++++++++++++++++++++++++++++ example/stream/loader.js | 11 +++++ example/stream/server.js | 74 +++++++++++++++++++++++++++++++++ src/client/bidiStreamProxy.ts | 2 +- src/client/clientMetadata.ts | 2 +- src/client/clientProxy.ts | 9 ++-- src/client/clientStreamProxy.ts | 2 +- src/client/serverStreamProxy.ts | 2 +- 11 files changed, 319 insertions(+), 9 deletions(-) create mode 100644 example/asyncStream/client.js create mode 100644 example/asyncStream/loader.js create mode 100644 example/asyncStream/server.js create mode 100644 example/stream/client.js create mode 100644 example/stream/loader.js create mode 100644 example/stream/server.js diff --git a/example/asyncStream/client.js b/example/asyncStream/client.js new file mode 100644 index 0000000..2494a27 --- /dev/null +++ b/example/asyncStream/client.js @@ -0,0 +1,68 @@ +import { loader } from './loader.js' + +const start = async (addr) => { + await loader.init({ + isDev: true, + packagePrefix: 'dev' + }) + + const clients = await loader.initClients({ + services: { + 'stream.Hellor': addr + } + }) + + const meta = loader.makeMetadata({ + 'x-cache-control': 'max-age=100', + 'x-business-id': ['grpcity', 'testing'], + 'x-timestamp-client': 'begin=' + new Date().toISOString() + }) + + const logMiddleware = async (ctx, next) => { + const beginTime = new Date().getTime() + await next() + const endTime = new Date().getTime() + console.log(ctx.path, ctx.res.response, endTime - beginTime) + } + + clients.use(logMiddleware) + + const client = clients.get('stream.Hellor') + + // client to server + await client.unaryHello({ message: 'gRPCity' }, meta) + + // stream client to server + // const clientStreamHelloCall = await client.clientStreamHello(meta) + // clientStreamHelloCall.write({ message: 'Hello!' }) + // clientStreamHelloCall.write({ message: 'How are you?' }) + // await clientStreamHelloCall.writeEnd() + + // client to stream server + // const serverStreamHelloCall = await client.serverStreamHello({ message: 'Hello! How are you?' }, meta) + // const serverReadAllResult = serverStreamHelloCall.readAll() + // for await (const data of serverReadAllResult) { + // console.log(data) + // } + // const serverReadEndResult = await serverStreamHelloCall.readEnd() + // console.log(serverReadEndResult) + + // stream client to stream server + const mutualStreamHelloCall = await client.mutualStreamHello(meta) + mutualStreamHelloCall.writeAll([{ message: 'Hello!' }, { message: 'How are you?' }, { message: 'other thing x' }]) + mutualStreamHelloCall.write({ message: 'maybe' }) + + const mutualReadAllResult = mutualStreamHelloCall.readAll() + for await (const data of mutualReadAllResult) { + if (data.message === 'delay 1s') { + mutualStreamHelloCall.write({ message: 'ok, I known you delay 1s' }) + mutualStreamHelloCall.writeEnd() + } + console.log(data) + } + + const mutualReadEndResult = await mutualStreamHelloCall.readEnd() + console.log(mutualReadEndResult) +} + +start('localhost:9097') diff --git a/example/asyncStream/loader.js b/example/asyncStream/loader.js new file mode 100644 index 0000000..5a84b11 --- /dev/null +++ b/example/asyncStream/loader.js @@ -0,0 +1,11 @@ +import { ProtoLoader } from '../../lib/index.js' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +// get this file dir path +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export const loader = new ProtoLoader({ + location: path.join(__dirname, '../proto'), + files: ['stream/service.proto'] +}) diff --git a/example/asyncStream/server.js b/example/asyncStream/server.js new file mode 100644 index 0000000..17db763 --- /dev/null +++ b/example/asyncStream/server.js @@ -0,0 +1,74 @@ +import { loader } from './loader.js' + +function timeout(ms) { + return new Promise((resolve, reject) => setTimeout(resolve, ms)) +} + +class Stream { + async unaryHello(call) { + return { message: 'hello ' + call.request.message } + } + + async clientStreamHello(call) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + + for await (const data of call.readAll()) { + console.log(data) + } + return { message: "Hello! I'm fine, thank you!" } + } + + async serverStreamHello(call) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + + console.log(call.request.message) + call.write({ message: 'Hello! I got you message.' }) + call.write({ message: "I'm fine, thank you" }) + call.writeAll([{ message: 'other thing x' }, { message: 'other thing y' }]) + call.end() + } + + async mutualStreamHello(call) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + + call.write({ message: 'emmm...' }) + + for await (const data of call.readAll()) { + console.log(data.message) + if (data.message === 'Hello!') { + call.write({ message: 'Hello too.' }) + } else if (data.message === 'How are you?') { + call.write({ message: "I'm fine, thank you" }) + await timeout(1000) + call.write({ message: 'delay 1s' }) + call.writeAll([{ message: 'emm... ' }, { message: 'emm......' }]) + } else { + call.write({ message: 'pardon?' }) + } + } + + call.end() + } +} + +const start = async (addr) => { + await loader.init({ + isDev: true, + packagePrefix: 'dev' + }) + + const server = await loader.initServer() + + server.add('stream.Hellor', new Stream()) + + await server.listen(addr) + console.log('start:', addr) +} + +start('localhost:9097') diff --git a/example/stream/client.js b/example/stream/client.js new file mode 100644 index 0000000..5685931 --- /dev/null +++ b/example/stream/client.js @@ -0,0 +1,73 @@ +import { loader } from './loader.js' + +const start = async (addr) => { + await loader.init({ + isDev: true, + packagePrefix: 'dev' + }) + + const clients = await loader.initClients({ + services: { + 'stream.Hellor': addr + } + }) + + const meta = loader.makeMetadata({ + 'x-cache-control': 'max-age=100', + 'x-business-id': ['grpcity', 'testing'], + 'x-timestamp-client': 'begin=' + new Date().toISOString() + }) + + const client = clients.get('stream.Hellor') + + // client to server + client.call.unaryHello({ message: 'gRPCity' }, meta, (err, response) => { + if (err) { + console.log(err) + } else { + console.log(response) + } + }) + + // stream client to server + const clientStreamHelloCall = client.call.clientStreamHello(meta, (err, response) => { + if (err) { + console.log(err) + } else { + console.log(response) + } + }) + clientStreamHelloCall.write({ message: 'Hello!' }) + clientStreamHelloCall.write({ message: 'How are you?' }) + clientStreamHelloCall.end() + + // client to stream server + const serverStreamHelloCall = client.call.serverStreamHello({ + message: 'Hello! How are you?' + }) + serverStreamHelloCall.on('data', (chunk) => { + console.log(chunk) + }) + serverStreamHelloCall.on('end', () => { + console.log('server call end.') + }) + + // stream client to stream server + const mutualStreamHelloCall = client.call.mutualStreamHello() + mutualStreamHelloCall.write({ message: 'Hello!' }) + mutualStreamHelloCall.write({ message: 'How are you?' }) + mutualStreamHelloCall.write({ message: 'other thing x' }) + + mutualStreamHelloCall.on('data', (data) => { + console.log(data) + if (data.message === 'delay 1s') { + mutualStreamHelloCall.write({ message: 'ok, I known you delay 1s' }) + mutualStreamHelloCall.end() + } + }) + mutualStreamHelloCall.on('end', () => { + console.log('server call end.') + }) +} + +start('localhost:9097') diff --git a/example/stream/loader.js b/example/stream/loader.js new file mode 100644 index 0000000..5a84b11 --- /dev/null +++ b/example/stream/loader.js @@ -0,0 +1,11 @@ +import { ProtoLoader } from '../../lib/index.js' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +// get this file dir path +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +export const loader = new ProtoLoader({ + location: path.join(__dirname, '../proto'), + files: ['stream/service.proto'] +}) diff --git a/example/stream/server.js b/example/stream/server.js new file mode 100644 index 0000000..1f38ab5 --- /dev/null +++ b/example/stream/server.js @@ -0,0 +1,74 @@ +import { loader } from './loader.js' + +class Stream { + unaryHello(call, callback) { + console.log(call.request.message) + callback(null, { message: 'hello ' + call.request.message }) + } + + clientStreamHello(call, callback) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + + call.on('data', (data) => { + console.log(data) + }) + call.on('end', () => { + callback(null, { message: "Hello! I'm fine, thank you!" }) + }) + } + + serverStreamHello(call) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + + console.log(call.request.message) + call.write({ message: 'Hello! I got you message.' }) + call.write({ message: "I'm fine, thank you" }) + call.end() + } + + mutualStreamHello(call) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + + call.write({ message: 'emmm...' }) + call.on('data', (chunk) => { + console.log(chunk.message) + if (chunk.message === 'Hello!') { + call.write({ message: 'Hello too.' }) + } else if (chunk.message === 'How are you?') { + call.write({ message: "I'm fine, thank you" }) + setTimeout(() => { + call.write({ message: 'delay 1s' }) + }, 1000) + } else { + call.write({ message: 'pardon?' }) + } + }) + call.on('end', () => { + setTimeout(() => { + console.log('client call end.') + call.end() + }, 3000) + }) + } +} + +const start = async (addr) => { + await loader.init({ + isDev: true, + packagePrefix: 'dev' + }) + + const server = await loader.initServer() + server.add('stream.Hellor', new Stream()) + + await server.listen(addr) + console.log('start:', addr) +} + +start('localhost:9097') diff --git a/src/client/bidiStreamProxy.ts b/src/client/bidiStreamProxy.ts index cad8ca4..d02f5f9 100644 --- a/src/client/bidiStreamProxy.ts +++ b/src/client/bidiStreamProxy.ts @@ -12,7 +12,7 @@ export const bidiStreamProxy = ( defaultOptions: Record, composeFunc: Function ) => { - return async (metadata?: Metadata, options?: Record): any => { + return async (metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { diff --git a/src/client/clientMetadata.ts b/src/client/clientMetadata.ts index 8d54a8c..55999a2 100644 --- a/src/client/clientMetadata.ts +++ b/src/client/clientMetadata.ts @@ -2,7 +2,7 @@ import { Metadata, MetadataValue } from '@grpc/grpc-js' export const combineMetadata = (metadata: Metadata, options: Record) => { Object.keys(options).forEach((key) => { - ;(metadata as Metadata).add(key, options[key] as MetadataValue) + ;(metadata as Metadata).set(key, options[key] as MetadataValue) }) return metadata } diff --git a/src/client/clientProxy.ts b/src/client/clientProxy.ts index 38496d3..afef9fd 100644 --- a/src/client/clientProxy.ts +++ b/src/client/clientProxy.ts @@ -20,14 +20,13 @@ export const clientProxy = (client: UntypedServiceImplementation, options: Recor return names }, {}) - const metadata: Record = { - 'x-client-hostname': os.hostname() - } - const target = Object.entries(prototype).reduce( (target: any, [name, func]) => { if (name !== 'constructor' && typeof func === 'function') { - metadata['x-service-path'] = `${methodNames[name.toUpperCase()]}` + const metadata: Record = { + 'x-client-hostname': os.hostname(), + 'x-service-path': `${methodNames[name.toUpperCase()]}` + } const { requestStream, responseStream } = getFuncStreamType(func) diff --git a/src/client/clientStreamProxy.ts b/src/client/clientStreamProxy.ts index bd2d1c3..69d118a 100644 --- a/src/client/clientStreamProxy.ts +++ b/src/client/clientStreamProxy.ts @@ -11,7 +11,7 @@ export const clientStreamProxy = ( defaultOptions: Record, composeFunc: Function ) => { - return async (metadata?: Metadata, options?: Record): any => { + return async (metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { diff --git a/src/client/serverStreamProxy.ts b/src/client/serverStreamProxy.ts index afbc6ac..50d4eea 100644 --- a/src/client/serverStreamProxy.ts +++ b/src/client/serverStreamProxy.ts @@ -12,7 +12,7 @@ export const serverStreamProxy = ( defaultOptions: Record, composeFunc: Function ) => { - return async (request?: any, metadata?: Metadata, options?: Record): any => { + return async (request?: any, metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { From 4992c560b92bf3f38e48907049882836d743721a Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sat, 6 Jan 2024 11:48:52 +0800 Subject: [PATCH 14/30] feat: new context for client and server --- example/asyncStream/client.js | 4 +-- example/asyncStream/server.js | 2 +- example/helloworld/client.js | 4 +-- example/helloworld/server.js | 2 +- example/stream/client.js | 2 +- example/stream/server.js | 2 +- src/client/bidiStreamProxy.ts | 17 +++++----- src/client/clientContext.ts | 49 ++++++++++++++++++++++------- src/client/clientMetadata.ts | 2 +- src/client/clientProxy.ts | 18 ++++++----- src/client/clientStreamProxy.ts | 21 +++++++------ src/client/index.ts | 4 +-- src/client/serverStreamProxy.ts | 18 ++++++----- src/client/unaryProxy.ts | 18 ++++++----- src/server/callBidiStreamProxy.ts | 9 ++++-- src/server/callClientStreamProxy.ts | 9 ++++-- src/server/callServerStreamProxy.ts | 9 ++++-- src/server/callUnaryProxy.ts | 9 ++++-- src/server/callbackify.ts | 11 ++++--- src/server/serverContext.ts | 13 ++++++-- 20 files changed, 145 insertions(+), 78 deletions(-) diff --git a/example/asyncStream/client.js b/example/asyncStream/client.js index 2494a27..4bde0a1 100644 --- a/example/asyncStream/client.js +++ b/example/asyncStream/client.js @@ -3,7 +3,7 @@ import { loader } from './loader.js' const start = async (addr) => { await loader.init({ isDev: true, - packagePrefix: 'dev' + packagePrefix: 'test' }) const clients = await loader.initClients({ @@ -22,7 +22,7 @@ const start = async (addr) => { const beginTime = new Date().getTime() await next() const endTime = new Date().getTime() - console.log(ctx.path, ctx.res.response, endTime - beginTime) + console.log(ctx.path, ctx.response, endTime - beginTime) } clients.use(logMiddleware) diff --git a/example/asyncStream/server.js b/example/asyncStream/server.js index 17db763..3a202bc 100644 --- a/example/asyncStream/server.js +++ b/example/asyncStream/server.js @@ -60,7 +60,7 @@ class Stream { const start = async (addr) => { await loader.init({ isDev: true, - packagePrefix: 'dev' + packagePrefix: 'test' }) const server = await loader.initServer() diff --git a/example/helloworld/client.js b/example/helloworld/client.js index d6e9a80..2cd8f7f 100644 --- a/example/helloworld/client.js +++ b/example/helloworld/client.js @@ -3,7 +3,7 @@ import { loader, clientCredentials as credentials } from './loader.js' const start = async (addr) => { await loader.init({ isDev: true, - packagePrefix: 'dev' + packagePrefix: 'test' }) const meta = loader.makeMetadata({ @@ -16,7 +16,7 @@ const start = async (addr) => { const beginTime = new Date().getTime() await next() const endTime = new Date().getTime() - console.log(ctx.res.response, endTime - beginTime) + console.log(ctx.response, endTime - beginTime) } const clients = await loader.initClients({ diff --git a/example/helloworld/server.js b/example/helloworld/server.js index 8ea024c..a5e6d69 100644 --- a/example/helloworld/server.js +++ b/example/helloworld/server.js @@ -87,7 +87,7 @@ const middlewareB = async (ctx, next) => { const start = async (addr) => { await loader.init({ isDev: true, - packagePrefix: 'dev' + packagePrefix: 'test' }) const server = await loader.initServer() diff --git a/example/stream/client.js b/example/stream/client.js index 5685931..47f466a 100644 --- a/example/stream/client.js +++ b/example/stream/client.js @@ -3,7 +3,7 @@ import { loader } from './loader.js' const start = async (addr) => { await loader.init({ isDev: true, - packagePrefix: 'dev' + packagePrefix: 'test' }) const clients = await loader.initClients({ diff --git a/example/stream/server.js b/example/stream/server.js index 1f38ab5..997328a 100644 --- a/example/stream/server.js +++ b/example/stream/server.js @@ -61,7 +61,7 @@ class Stream { const start = async (addr) => { await loader.init({ isDev: true, - packagePrefix: 'dev' + packagePrefix: 'test' }) const server = await loader.initServer() diff --git a/src/client/bidiStreamProxy.ts b/src/client/bidiStreamProxy.ts index d02f5f9..527ac51 100644 --- a/src/client/bidiStreamProxy.ts +++ b/src/client/bidiStreamProxy.ts @@ -1,16 +1,17 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' -import { createContext } from './clientContext' +import { createContext, createResponse } from './clientContext' import iterator from '../utils/iterator' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const bidiStreamProxy = ( client: UntypedServiceImplementation, func: any, + composeFunc: Function, defaultMetadata: Record, defaultOptions: Record, - composeFunc: Function + methodOptions: { requestStream: boolean; responseStream: boolean } ) => { return async (metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { @@ -22,9 +23,11 @@ export const bidiStreamProxy = ( metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = setDeadline(options, defaultOptions) - const ctx = createContext({ metadata, options }) + const ctx = createContext({ metadata, options, methodOptions }) - const call = func.apply(client, [metadata, options]) + let ctxMetadata = ctx.method.metadata + let ctxOptions = ctx.method.options + const call = func.apply(client, [ctxMetadata, ctxOptions]) call.writeAll = (messages: any[]) => { if (Array.isArray(messages)) { @@ -42,10 +45,10 @@ export const bidiStreamProxy = ( const handler = async () => { call.readAll = () => { call.on('metadata', (metadata: Metadata) => { - ctx.res.metadata = metadata + ctx.metadata = metadata }) call.on('status', (status: StatusObject) => { - ctx.res.status = status + ctx.status = status }) return iterator(call, 'data', { resolutionEvents: ['status', 'end'] @@ -57,7 +60,7 @@ export const bidiStreamProxy = ( }) call.readEnd = () => { - return ctx.res + return createResponse(ctx) } return call diff --git a/src/client/clientContext.ts b/src/client/clientContext.ts index 7e32653..ead45d7 100644 --- a/src/client/clientContext.ts +++ b/src/client/clientContext.ts @@ -2,22 +2,47 @@ import * as grpc from '@grpc/grpc-js' export type ClientContextType = { path: string - req: { - request?: any - metadata?: grpc.Metadata - options?: Record + method: { + requestStream: boolean + responseStream: boolean + metadata: grpc.Metadata + options: Record } - res: { - response?: any - metadata?: grpc.Metadata - status?: grpc.StatusObject + request?: any + metadata?: grpc.Metadata + response?: any + status?: grpc.StatusObject +} + +type createContextOptions = { + request?: any + metadata: grpc.Metadata + options: Record + methodOptions: { + requestStream: boolean + responseStream: boolean + } +} + +export const createContext = (args: createContextOptions): ClientContextType => { + const { request, metadata, options, methodOptions } = args + const { requestStream, responseStream } = methodOptions + return { + path: metadata?.get('x-service-path')[0].toString() || '', + method: { + requestStream: requestStream || false, + responseStream: responseStream || false, + metadata: metadata.clone(), + options + }, + request: request || {} } } -export const createContext = (args: { request?: any; metadata?: grpc.Metadata; options?: Record }): ClientContextType => { +export const createResponse = (ctx: ClientContextType) => { return { - path: args.metadata?.get('x-service-path')[0].toString() || '', - req: args, - res: {} + status: ctx.status, + metadata: ctx.metadata, + response: ctx.response } } diff --git a/src/client/clientMetadata.ts b/src/client/clientMetadata.ts index 55999a2..a141ab6 100644 --- a/src/client/clientMetadata.ts +++ b/src/client/clientMetadata.ts @@ -4,5 +4,5 @@ export const combineMetadata = (metadata: Metadata, options: Record { ;(metadata as Metadata).set(key, options[key] as MetadataValue) }) - return metadata + return metadata.clone() } diff --git a/src/client/clientProxy.ts b/src/client/clientProxy.ts index afef9fd..6ebb861 100644 --- a/src/client/clientProxy.ts +++ b/src/client/clientProxy.ts @@ -6,12 +6,12 @@ import { serverStreamProxy } from './serverStreamProxy' import { bidiStreamProxy } from './bidiStreamProxy' import type { UntypedServiceImplementation } from '@grpc/grpc-js' -const getFuncStreamType = (func: any) => { +const getMethodStreamType = (func: any) => { const { requestStream, responseStream } = func - return { requestStream, responseStream } + return { requestStream: !!requestStream, responseStream: !!responseStream } } -export const clientProxy = (client: UntypedServiceImplementation, options: Record, fn: Function) => { +export const clientProxy = (client: UntypedServiceImplementation, options: Record, composeFunc: Function) => { const prototype = Object.getPrototypeOf(client) const methodNames: any = Object.keys(prototype) .filter((key) => prototype[key] && prototype[key].path) @@ -28,25 +28,27 @@ export const clientProxy = (client: UntypedServiceImplementation, options: Recor 'x-service-path': `${methodNames[name.toUpperCase()]}` } - const { requestStream, responseStream } = getFuncStreamType(func) + const { requestStream, responseStream } = getMethodStreamType(func) + + const methodOptions = { requestStream, responseStream } if (!requestStream && !responseStream) { // promisify unary method - target[name] = unaryProxy(client, func, metadata, options, fn) + target[name] = unaryProxy(client, func, composeFunc, metadata, options, methodOptions) } // stream if (requestStream && !responseStream) { // promisify only client stream method - target[name] = clientStreamProxy(client, func, metadata, options, fn) + target[name] = clientStreamProxy(client, func, composeFunc, metadata, options, methodOptions) } if (!requestStream && responseStream) { // promisify only server stream method - target[name] = serverStreamProxy(client, func, metadata, options, fn) + target[name] = serverStreamProxy(client, func, composeFunc, metadata, options, methodOptions) } if (requestStream && responseStream) { // promisify duplex stream method - target[name] = bidiStreamProxy(client, func, metadata, options, fn) + target[name] = bidiStreamProxy(client, func, composeFunc, metadata, options, methodOptions) } // keep callback method diff --git a/src/client/clientStreamProxy.ts b/src/client/clientStreamProxy.ts index 69d118a..a844d63 100644 --- a/src/client/clientStreamProxy.ts +++ b/src/client/clientStreamProxy.ts @@ -1,15 +1,16 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' -import { createContext } from './clientContext' +import { createContext, createResponse } from './clientContext' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const clientStreamProxy = ( client: UntypedServiceImplementation, func: any, + composeFunc: Function, defaultMetadata: Record, defaultOptions: Record, - composeFunc: Function + methodOptions: { requestStream: boolean; responseStream: boolean } ) => { return async (metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { @@ -21,14 +22,16 @@ export const clientStreamProxy = ( metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = setDeadline(options, defaultOptions) - const ctx = createContext({ metadata, options }) + const ctx = createContext({ metadata, options, methodOptions }) - const argumentsList: Array = [metadata, options] + let ctxMetadata = ctx.method.metadata + let ctxOptions = ctx.method.options + const argumentsList: Array = [ctxMetadata, ctxOptions] argumentsList.push((err: any, response: any) => { if (err) { - throw createClientError(err, metadata) + throw createClientError(err, ctxMetadata) } - ctx.res.response = response + ctx.response = response }) const call = func.apply(client, argumentsList) @@ -45,10 +48,10 @@ export const clientStreamProxy = ( call.end() await new Promise((resolve, _) => { call.on('metadata', (metadata: Metadata) => { - ctx.res.metadata = metadata + ctx.metadata = metadata }) call.on('status', (status: StatusObject) => { - ctx.res.status = status + ctx.status = status resolve() }) }) @@ -58,7 +61,7 @@ export const clientStreamProxy = ( await composeFunc(ctx, handler).catch((err: Error) => { throw createClientError(err, metadata) }) - return ctx.res + return createResponse(ctx) } return call diff --git a/src/client/index.ts b/src/client/index.ts index c0c81c6..3ce7e02 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -58,10 +58,10 @@ export default class Clients { return this._proxyClientMap.get(cacheKey) } - const fn = compose(this._middleware) + const composeFunc = compose(this._middleware) const client = this._clientFactory.create(isDefaultClient, name, addr, credentials, channelOptions) - const proxy = clientProxy(client, { timeout }, fn) + const proxy = clientProxy(client, { timeout }, composeFunc) this._proxyClientMap.set(cacheKey, proxy) return proxy } diff --git a/src/client/serverStreamProxy.ts b/src/client/serverStreamProxy.ts index 50d4eea..6c838f5 100644 --- a/src/client/serverStreamProxy.ts +++ b/src/client/serverStreamProxy.ts @@ -2,15 +2,16 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' import iterator from '../utils/iterator' -import { createContext } from './clientContext' +import { createContext, createResponse } from './clientContext' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const serverStreamProxy = ( client: UntypedServiceImplementation, func: any, + composeFunc: Function, defaultMetadata: Record, defaultOptions: Record, - composeFunc: Function + methodOptions: { requestStream: boolean; responseStream: boolean } ) => { return async (request?: any, metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { @@ -22,9 +23,12 @@ export const serverStreamProxy = ( metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = setDeadline(options, defaultOptions) - const ctx = createContext({ request, metadata, options }) + const ctx = createContext({ request, metadata, options, methodOptions }) - const call = func.apply(client, [request, metadata, options]) + let ctxRequest = ctx.request + let ctxMetadata = ctx.method.metadata + let ctxOptions = ctx.method.options + const call = func.apply(client, [ctxRequest, ctxMetadata, ctxOptions]) call.on('error', (err: Error) => { throw createClientError(err, metadata) @@ -33,10 +37,10 @@ export const serverStreamProxy = ( const handler = async () => { call.readAll = () => { call.on('metadata', (metadata: Metadata) => { - ctx.res.metadata = metadata + ctx.metadata = metadata }) call.on('status', (status: StatusObject) => { - ctx.res.status = status + ctx.status = status }) return iterator(call, 'data', { resolutionEvents: ['status', 'end'] @@ -48,7 +52,7 @@ export const serverStreamProxy = ( }) call.readEnd = () => { - return ctx.res + return createResponse(ctx) } return call diff --git a/src/client/unaryProxy.ts b/src/client/unaryProxy.ts index 2aecfcc..be7f565 100644 --- a/src/client/unaryProxy.ts +++ b/src/client/unaryProxy.ts @@ -1,15 +1,16 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' -import { createContext } from './clientContext' +import { createContext, createResponse } from './clientContext' import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' export const unaryProxy = ( client: UntypedServiceImplementation, func: any, + composeFunc: Function, defaultMetadata: Record, defaultOptions: Record, - composeFunc: Function + methodOptions: { requestStream: boolean; responseStream: boolean } ) => { return async (request?: any, metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { @@ -21,27 +22,28 @@ export const unaryProxy = ( metadata = combineMetadata(metadata || new Metadata(), defaultMetadata) options = setDeadline(options, defaultOptions) - const ctx = createContext({ request, metadata, options }) + const ctx = createContext({ request, metadata, options, methodOptions }) const handler = async () => { await new Promise((resolve, reject) => { - let { request, metadata, options } = ctx.req + let { request } = ctx + let { metadata, options } = ctx.method const argumentsList: Array = [request, metadata, options] argumentsList.push((err: any, response: any) => { if (err) { reject(createClientError(err, metadata)) } - ctx.res.response = response + ctx.response = response }) const call = func.apply(client, argumentsList) call.on('metadata', (metadata: Metadata) => { - ctx.res.metadata = metadata + ctx.metadata = metadata }) call.on('status', (status: StatusObject) => { - ctx.res.status = status + ctx.status = status resolve() }) }) @@ -51,6 +53,6 @@ export const unaryProxy = ( throw createClientError(err, metadata) }) - return ctx.res + return createResponse(ctx) } } diff --git a/src/server/callBidiStreamProxy.ts b/src/server/callBidiStreamProxy.ts index 5b46e95..9aeffd6 100644 --- a/src/server/callBidiStreamProxy.ts +++ b/src/server/callBidiStreamProxy.ts @@ -3,9 +3,14 @@ import iterator from '../utils/iterator' import { createContext } from './serverContext' import { createServerError } from './serverError' -export const callBidiStreamProxy = (target: any, key: string, composeFunc: Function): grpc.handleBidiStreamingCall => { +export const callBidiStreamProxy = ( + target: any, + key: string, + composeFunc: Function, + methodOptions: { requestStream: boolean; responseStream: boolean } +): grpc.handleBidiStreamingCall => { return (call: any) => { - const ctx = createContext(call) + const ctx = createContext(call, methodOptions) call.writeAll = (messages: any[]) => { if (Array.isArray(messages)) { diff --git a/src/server/callClientStreamProxy.ts b/src/server/callClientStreamProxy.ts index b57e218..70e37df 100644 --- a/src/server/callClientStreamProxy.ts +++ b/src/server/callClientStreamProxy.ts @@ -3,9 +3,14 @@ import iterator from '../utils/iterator' import { createContext } from './serverContext' import { createServerError } from './serverError' -export const callClientStreamProxy = (target: any, key: string, composeFunc: Function): grpc.handleClientStreamingCall => { +export const callClientStreamProxy = ( + target: any, + key: string, + composeFunc: Function, + methodOptions: { requestStream: boolean; responseStream: boolean } +): grpc.handleClientStreamingCall => { return (call: any, callback) => { - const ctx = createContext(call) + const ctx = createContext(call, methodOptions) call.readAll = () => { return iterator(call, 'data', { diff --git a/src/server/callServerStreamProxy.ts b/src/server/callServerStreamProxy.ts index 164c3dd..1af0eba 100644 --- a/src/server/callServerStreamProxy.ts +++ b/src/server/callServerStreamProxy.ts @@ -2,9 +2,14 @@ import * as grpc from '@grpc/grpc-js' import { createContext } from './serverContext' import { createServerError } from './serverError' -export const callServerStreamProxy = (target: any, key: string, composeFunc: Function): grpc.handleServerStreamingCall => { +export const callServerStreamProxy = ( + target: any, + key: string, + composeFunc: Function, + methodOptions: { requestStream: boolean; responseStream: boolean } +): grpc.handleServerStreamingCall => { return (call: any) => { - const ctx = createContext(call) + const ctx = createContext(call, methodOptions) call.writeAll = (messages: any[]) => { if (Array.isArray(messages)) { diff --git a/src/server/callUnaryProxy.ts b/src/server/callUnaryProxy.ts index 2edf6eb..ee3be6e 100644 --- a/src/server/callUnaryProxy.ts +++ b/src/server/callUnaryProxy.ts @@ -2,9 +2,14 @@ import * as grpc from '@grpc/grpc-js' import { createContext } from './serverContext' import { createServerError } from './serverError' -export const callUnaryProxy = (target: any, key: string, composeFunc: Function): grpc.handleUnaryCall => { +export const callUnaryProxy = ( + target: any, + key: string, + composeFunc: Function, + methodOptions: { requestStream: boolean; responseStream: boolean } +): grpc.handleUnaryCall => { return (call, callback) => { - const ctx = createContext(call) + const ctx = createContext(call, methodOptions) Promise.resolve().then(async () => { const handleResponse = async () => { diff --git a/src/server/callbackify.ts b/src/server/callbackify.ts index 153ba63..af4272c 100644 --- a/src/server/callbackify.ts +++ b/src/server/callbackify.ts @@ -51,22 +51,23 @@ export const callbackify = (target: any, middleware: MiddlewareFunction[], optio const proxy = (target: any, key: string, options: any = {}, middleware: MiddlewareFunction[]) => { const { requestStream, responseStream } = options - const fn = compose(middleware) + const composeFunc = compose(middleware) + const methodsOptions = { requestStream, responseStream } // unary if (!requestStream && !responseStream) { - return callUnaryProxy(target, key, fn) + return callUnaryProxy(target, key, composeFunc, methodsOptions) } // client stream if (requestStream && !responseStream) { - return callClientStreamProxy(target, key, fn) + return callClientStreamProxy(target, key, composeFunc, methodsOptions) } // server stream if (!requestStream && responseStream) { - return callServerStreamProxy(target, key, fn) + return callServerStreamProxy(target, key, composeFunc, methodsOptions) } // duplex stream if (requestStream && responseStream) { - return callBidiStreamProxy(target, key, fn) + return callBidiStreamProxy(target, key, composeFunc, methodsOptions) } } diff --git a/src/server/serverContext.ts b/src/server/serverContext.ts index 1b791a0..bc34f30 100644 --- a/src/server/serverContext.ts +++ b/src/server/serverContext.ts @@ -2,16 +2,23 @@ import * as grpc from '@grpc/grpc-js' export type ServerContextType = { path: string + method: { + requestStream: boolean + responseStream: boolean + } request: any metadata: grpc.Metadata response?: any } -export const createContext = (call: Record): ServerContextType => { +export const createContext = (call: any, methodOptions: { requestStream: boolean; responseStream: boolean }): ServerContextType => { + const { requestStream, responseStream } = methodOptions return { - // TODO: maybe need more details - // method: target.constructor.name + '.' + key, path: call.getPath() || '', + method: { + requestStream: requestStream || false, + responseStream: responseStream || false + }, request: call.request, metadata: call.metadata.clone() } From babb01303c52442c468f7be8084e7db7205e8777 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sat, 6 Jan 2024 20:23:58 +0800 Subject: [PATCH 15/30] feat: remove lodash and loader.message --- src/client/index.ts | 6 +++--- src/loader.ts | 36 ++++++------------------------------ src/server/index.ts | 4 ++-- src/utils/object.ts | 12 ++++++++++++ src/utils/string.ts | 4 ++++ 5 files changed, 27 insertions(+), 35 deletions(-) create mode 100644 src/utils/object.ts create mode 100644 src/utils/string.ts diff --git a/src/client/index.ts b/src/client/index.ts index 3ce7e02..2964356 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,9 +1,9 @@ import assert from 'node:assert' -import * as _ from 'lodash-es' import { ProtoLoader } from '../loader' import { ClientFactory } from './clientFactory' import { clientProxy } from './clientProxy' +import { isString } from '../utils/string' import { compose } from '../utils/compose' import type { MiddlewareFunction } from '../utils/compose' import type { ClientsOptionsType, AddressObject } from '../schema/loader' @@ -12,7 +12,7 @@ import type { ClientOptionsType } from '../schema/client' const prepareUrl = (url: ClientOptionsType['url']) => { return { isDefaultClient: !!url, - addr: _.isString(url) ? (url as string) : (url as AddressObject)?.host + ':' + (url as AddressObject)?.port + addr: isString(url) ? (url as string) : (url as AddressObject)?.host + ':' + (url as AddressObject)?.port } } @@ -34,7 +34,7 @@ export default class Clients { serviceNames.forEach((name) => { const isDefault = true - const addr = _.isString(services[name]) + const addr = isString(services[name]) ? (services[name] as string) : (services[name] as AddressObject).host + ':' + (services[name] as AddressObject).port diff --git a/src/loader.ts b/src/loader.ts index 4e08faf..dff7317 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -1,12 +1,11 @@ import assert from 'node:assert' import * as grpc from '@grpc/grpc-js' import * as protoLoader from '@grpc/proto-loader' -import * as protobuf from 'protobufjs' -import * as Descriptor from 'protobufjs/ext/descriptor' -import * as _ from 'lodash-es' import Server from './server' import Clients from './client' +import { isString } from './utils/string' +import { get } from './utils/object' import { prefixingDefinition } from './utils/definition' import { assertProtoFileOptionsOptions, attemptInitOptions, attemptInitClientsOptions } from './schema/loader' import type { ClientsOptionsType, ServerOptionsType } from './schema/loader' @@ -20,7 +19,6 @@ export class ProtoLoader { private _packageDefinition?: protoLoader.PackageDefinition private _insecureClientCredentials?: grpc.ChannelCredentials private _insecureServerCredentials?: grpc.ServerCredentials - private _reflectedRoot?: protobuf.Root constructor(protoFileOptions: ProtoFileOptionsType) { assertProtoFileOptionsOptions(protoFileOptions) @@ -104,9 +102,9 @@ export class ProtoLoader { if (typeof initialValues === 'object') { Object.entries(initialValues).forEach(([key, value]: [string, any]) => { if (Array.isArray(value)) { - value.map((v) => meta.add(key, _.isString(v) ? v : Buffer.from(v))) + value.map((v) => meta.add(key, isString(v) ? v : Buffer.from(v))) } else { - meta.add(key, _.isString(value) ? value : Buffer.from(value)) + meta.add(key, isString(value) ? value : Buffer.from(value)) } }) } @@ -116,7 +114,7 @@ export class ProtoLoader { service(name: string) { assert(this._types, 'Must called loader init() first. proto file has not been loaded.') const fullName = this._isDev ? `${this._packagePrefix}.${name}` : name - const service = _.get(this._types, `${fullName}`) + const service = get(this._types, `${fullName}`) assert(service, `Cannot find service with name: ${fullName}, please check whether the protos file is configured incorrectly.`) return (service as grpc.ServiceClientConstructor).service } @@ -124,30 +122,8 @@ export class ProtoLoader { type(name: string) { assert(this._types, 'Must called loader init() first. proto file has not been loaded.') const fullName = this._isDev ? `${this._packagePrefix}.${name}` : name - const type = _.get(this._types, `${fullName}`) + const type = get(this._types, `${fullName}`) assert(type, `Cannot find type with name: ${fullName}, please check whether the protos file is configured incorrectly.`) return type } - - message(name: string) { - let root = this._reflectedRoot - - if (root) { - const found = root.lookupType(name) - if (found) { - return found - } - } - - const descriptor = (this.type(name) as grpc.ProtobufTypeDefinition).fileDescriptorProtos.map((proto: any) => - Descriptor.FileDescriptorProto.decode(proto) - ) - root = (protobuf.Root as protobuf.RootConstructor).fromDescriptor({ - file: descriptor as Descriptor.IFileDescriptorProto[] - }) - - this._reflectedRoot = root - - return root.lookupType(name) - } } diff --git a/src/server/index.ts b/src/server/index.ts index 576a0ee..bd510ff 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,6 +1,6 @@ import assert from 'node:assert' import * as grpc from '@grpc/grpc-js' -import * as _ from 'lodash-es' +import { isString } from '../utils/string' import { assignServerOptions } from '../schema/server' import { ProtoLoader } from '../loader' @@ -28,7 +28,7 @@ export default class Server { async listen(addr: any, credentials?: grpc.ServerCredentials): Promise { assert(this._server, 'must be first init() server before server listen()') - const url = _.isString(addr) ? addr : `${addr.host}:${addr.port}` + const url = isString(addr) ? addr : `${addr.host}:${addr.port}` const bindPort = await new Promise((resolve, reject) => { this._server!.bindAsync(url, credentials || (this._loader as ProtoLoader).makeServerCredentials(), (err, result) => err ? reject(err) : resolve(result) diff --git a/src/utils/object.ts b/src/utils/object.ts new file mode 100644 index 0000000..50b8d37 --- /dev/null +++ b/src/utils/object.ts @@ -0,0 +1,12 @@ +export function get(object: any, path: string, defaultValue?: any): any { + const pathArray = path.split('.') + + for (let i = 0; i < pathArray.length; i++) { + if (!object || typeof object !== 'object') { + return defaultValue + } + object = object[pathArray[i]] + } + + return object !== undefined ? object : defaultValue +} diff --git a/src/utils/string.ts b/src/utils/string.ts new file mode 100644 index 0000000..a617f6b --- /dev/null +++ b/src/utils/string.ts @@ -0,0 +1,4 @@ +export function isString(value: any): boolean { + const type = typeof value + return type === 'string' || value instanceof String +} From ea7f9c553c7d5be80de38a3c26afb6aaa6728c59 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sat, 6 Jan 2024 20:25:05 +0800 Subject: [PATCH 16/30] feat: add loader test --- test/bidiStream.test.ts | 0 test/clientStream.test.ts | 0 test/loader.test.ts | 67 +++++++++++++++++++++++++++++++++++++++ test/serverStream.test.ts | 0 test/unary.test.ts | 0 5 files changed, 67 insertions(+) delete mode 100644 test/bidiStream.test.ts delete mode 100644 test/clientStream.test.ts delete mode 100644 test/serverStream.test.ts delete mode 100644 test/unary.test.ts diff --git a/test/bidiStream.test.ts b/test/bidiStream.test.ts deleted file mode 100644 index e69de29..0000000 diff --git a/test/clientStream.test.ts b/test/clientStream.test.ts deleted file mode 100644 index e69de29..0000000 diff --git a/test/loader.test.ts b/test/loader.test.ts index e69de29..32c25ae 100644 --- a/test/loader.test.ts +++ b/test/loader.test.ts @@ -0,0 +1,67 @@ +import path from 'node:path' +import { ProtoLoader } from '../src' + +describe('ProtoLoader', () => { + test('Should be load a single proto path', async () => { + const loader = new ProtoLoader({ + location: path.join(__dirname, '../example/proto'), + files: ['helloworld/service.proto'] + }) + + await loader.init() + + expect(!!loader.service('helloworld.Greeter')).toBeTruthy + expect(!!loader.service('helloworld.Hellor')).toBeTruthy + }) + + test('Should be load multi proto paths', async () => { + const loader = new ProtoLoader([ + { + location: path.join(__dirname, '../example/proto'), + files: ['helloworld/service.proto'] + }, + { + location: path.join(__dirname, '../example/proto'), + files: ['stream/service.proto'] + } + ]) + + await loader.init() + + expect(!!loader.service('helloworld.Greeter')).toBeTruthy + expect(!!loader.service('helloworld.Hellor')).toBeTruthy + expect(!!loader.service('stream.Hellor')).toBeTruthy + }) + + test('Should get protobuf service definition: Greeter', async () => { + const loader = new ProtoLoader({ + location: path.join(__dirname, '../example/proto'), + files: ['helloworld/service.proto'] + }) + + await loader.init({ + isDev: true, + packagePrefix: 'stage.dev' + }) + + const greeterDefinition = loader.service('helloworld.Greeter') + expect(typeof greeterDefinition.SayGreet).toBe('object') + expect(typeof greeterDefinition.SayGreet2).toBe('object') + }) + + test('Should get protobuf service definition: Hellor', async () => { + const loader = new ProtoLoader({ + location: path.join(__dirname, '../example/proto'), + files: ['helloworld/service.proto'] + }) + + await loader.init({ + isDev: true, + packagePrefix: 'stage.dev' + }) + + const hellorDefinition = loader.service('helloworld.Hellor') + expect(typeof hellorDefinition.SayHello).toBe('object') + expect(typeof hellorDefinition.SayHello2).toBe('object') + }) +}) diff --git a/test/serverStream.test.ts b/test/serverStream.test.ts deleted file mode 100644 index e69de29..0000000 diff --git a/test/unary.test.ts b/test/unary.test.ts deleted file mode 100644 index e69de29..0000000 From 70ea0b3850a9557681b6f6fef5dc7c6607cfc804 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sat, 6 Jan 2024 20:25:28 +0800 Subject: [PATCH 17/30] feat: change to commonjs --- example/package.json | 3 + example/proto/helloworld/service.proto | 4 - package.json | 13 +- pnpm-lock.yaml | 360 +++++++------------------ tsconfig.json | 20 +- 5 files changed, 114 insertions(+), 286 deletions(-) create mode 100644 example/package.json diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/example/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/example/proto/helloworld/service.proto b/example/proto/helloworld/service.proto index 4cfe6b1..a8ca54d 100755 --- a/example/proto/helloworld/service.proto +++ b/example/proto/helloworld/service.proto @@ -1,9 +1,5 @@ syntax = "proto3"; -option java_multiple_files = true; -option java_package = "io.grpc.examples.helloworld"; -option java_outer_classname = "HelloWorldProto"; - import "helloworld/model/message.proto"; package helloworld; diff --git a/package.json b/package.json index ed0edda..053d523 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,8 @@ "bugs": { "url": "https://github.com/chakhsu/grpcity/issues" }, - "type": "module", - "exports": "./lib/index.js", - "types": "./lib/index.d.ts", + "main": "lib/index.js", + "types": "lib/index.d.ts", "engines": { "node": ">=16" }, @@ -33,9 +32,7 @@ ], "scripts": { "clear": "rimraf lib && rimraf coverage", - "patch:esm-js": "tsc-esm-fix --tsconfig=tsconfig.json", - "build:esm": "tsc -P tsconfig.json && pnpm patch:esm-js", - "build": "pnpm clear && pnpm build:esm", + "build": "pnpm clear && tsc -P tsconfig.json", "lint:prettier": "prettier --cache --check --ignore-path .gitignore --ignore-path .prettierignore .", "prettier": "pnpm lint:prettier --write", "prepare": "husky install", @@ -48,12 +45,11 @@ "@grpc/grpc-js": "^1.9.13", "@grpc/proto-loader": "^0.7.10", "joi": "^17.11.0", - "lodash-es": "^4.17.21", "protobufjs": "^7.2.5" }, "devDependencies": { + "@tsconfig/node14": "^14.1.0", "@types/jest": "^29.5.11", - "@types/lodash-es": "^4.17.12", "@types/node": "^20.10.6", "husky": "^8.0.3", "jest": "^29.7.0", @@ -62,7 +58,6 @@ "rimraf": "^5.0.5", "ts-jest": "^29.1.1", "ts-node": "^10.9.2", - "tsc-esm-fix": "^2.20.21", "typescript": "^5.3.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3a7faa..3b27d99 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,9 @@ dependencies: version: 7.2.5 devDependencies: + '@tsconfig/node14': + specifier: ^14.1.0 + version: 14.1.0 '@types/jest': specifier: ^29.5.11 version: 29.5.11 @@ -48,13 +51,10 @@ devDependencies: version: 5.0.5 ts-jest: specifier: ^29.1.1 - version: 29.1.1(@babel/core@7.23.6)(jest@29.7.0)(typescript@5.3.3) + version: 29.1.1(@babel/core@7.23.7)(jest@29.7.0)(typescript@5.3.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.10.6)(typescript@5.3.3) - tsc-esm-fix: - specifier: ^2.20.21 - version: 2.20.21 typescript: specifier: ^5.3.3 version: 5.3.3 @@ -81,19 +81,19 @@ packages: engines: { node: '>=6.9.0' } dev: true - /@babel/core@7.23.6: - resolution: { integrity: sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw== } + /@babel/core@7.23.7: + resolution: { integrity: sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== } engines: { node: '>=6.9.0' } dependencies: '@ampproject/remapping': 2.2.1 '@babel/code-frame': 7.23.5 '@babel/generator': 7.23.6 '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.6) - '@babel/helpers': 7.23.6 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.7) + '@babel/helpers': 7.23.7 '@babel/parser': 7.23.6 '@babel/template': 7.22.15 - '@babel/traverse': 7.23.6 + '@babel/traverse': 7.23.7 '@babel/types': 7.23.6 convert-source-map: 2.0.0 debug: 4.3.4 @@ -152,13 +152,13 @@ packages: '@babel/types': 7.23.6 dev: true - /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.6): + /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.7): resolution: { integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== } engines: { node: '>=6.9.0' } peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-module-imports': 7.22.15 '@babel/helper-simple-access': 7.22.5 @@ -200,12 +200,12 @@ packages: engines: { node: '>=6.9.0' } dev: true - /@babel/helpers@7.23.6: - resolution: { integrity: sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA== } + /@babel/helpers@7.23.7: + resolution: { integrity: sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ== } engines: { node: '>=6.9.0' } dependencies: '@babel/template': 7.22.15 - '@babel/traverse': 7.23.6 + '@babel/traverse': 7.23.7 '@babel/types': 7.23.6 transitivePeerDependencies: - supports-color @@ -228,132 +228,132 @@ packages: '@babel/types': 7.23.6 dev: true - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.6): + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.7): resolution: { integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.6): + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.7): resolution: { integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.6): + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.7): resolution: { integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.6): + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.7): resolution: { integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.6): + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.7): resolution: { integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.6): + /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.7): resolution: { integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== } engines: { node: '>=6.9.0' } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.6): + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.7): resolution: { integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.6): + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.7): resolution: { integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.6): + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.7): resolution: { integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.6): + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.7): resolution: { integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.6): + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.7): resolution: { integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.6): + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.7): resolution: { integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.6): + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.7): resolution: { integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== } engines: { node: '>=6.9.0' } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.6): + /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.7): resolution: { integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ== } engines: { node: '>=6.9.0' } peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 dev: true @@ -366,8 +366,8 @@ packages: '@babel/types': 7.23.6 dev: true - /@babel/traverse@7.23.6: - resolution: { integrity: sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ== } + /@babel/traverse@7.23.7: + resolution: { integrity: sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== } engines: { node: '>=6.9.0' } dependencies: '@babel/code-frame': 7.23.5 @@ -586,7 +586,7 @@ packages: chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 - glob: 7.2.0 + glob: 7.2.3 graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.1 @@ -644,7 +644,7 @@ packages: resolution: { integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.20 babel-plugin-istanbul: 6.1.1 @@ -712,27 +712,6 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /@nodelib/fs.scandir@2.1.5: - resolution: { integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== } - engines: { node: '>= 8' } - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - - /@nodelib/fs.stat@2.0.5: - resolution: { integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== } - engines: { node: '>= 8' } - dev: true - - /@nodelib/fs.walk@1.2.8: - resolution: { integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== } - engines: { node: '>= 8' } - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.16.0 - dev: true - /@pkgjs/parseargs@0.11.0: resolution: { integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== } engines: { node: '>=14' } @@ -825,6 +804,10 @@ packages: resolution: { integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== } dev: true + /@tsconfig/node14@14.1.0: + resolution: { integrity: sha512-VmsCG04YR58ciHBeJKBDNMWWfYbyP8FekWVuTlpstaUPlat1D0x/tXzkWP7yCMU0eSz9V4OZU0LBWTFJ3xZf6w== } + dev: true + /@tsconfig/node16@1.0.4: resolution: { integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== } dev: true @@ -836,7 +819,7 @@ packages: '@babel/types': 7.23.6 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.4 + '@types/babel__traverse': 7.20.5 dev: true /@types/babel__generator@7.6.8: @@ -852,8 +835,8 @@ packages: '@babel/types': 7.23.6 dev: true - /@types/babel__traverse@7.20.4: - resolution: { integrity: sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA== } + /@types/babel__traverse@7.20.5: + resolution: { integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ== } dependencies: '@babel/types': 7.23.6 dev: true @@ -921,8 +904,8 @@ packages: engines: { node: '>=0.4.0' } dev: true - /acorn@8.11.2: - resolution: { integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== } + /acorn@8.11.3: + resolution: { integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== } engines: { node: '>=0.4.0' } hasBin: true dev: true @@ -991,17 +974,17 @@ packages: sprintf-js: 1.0.3 dev: true - /babel-jest@29.7.0(@babel/core@7.23.6): + /babel-jest@29.7.0(@babel/core@7.23.7): resolution: { integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } peerDependencies: '@babel/core': ^7.8.0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.23.6) + babel-preset-jest: 29.6.3(@babel/core@7.23.7) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -1029,38 +1012,38 @@ packages: '@babel/template': 7.22.15 '@babel/types': 7.23.6 '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.20.4 + '@types/babel__traverse': 7.20.5 dev: true - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.6): + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.7): resolution: { integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== } peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.6 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.6) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.23.6) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.6) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.6) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.6) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.6) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.6) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.6) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.6) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.6) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.6) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.6) - dev: true - - /babel-preset-jest@29.6.3(@babel/core@7.23.6): + '@babel/core': 7.23.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.23.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.7) + dev: true + + /babel-preset-jest@29.6.3(@babel/core@7.23.7): resolution: { integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.6) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.7) dev: true /balanced-match@1.0.2: @@ -1092,8 +1075,8 @@ packages: engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } hasBin: true dependencies: - caniuse-lite: 1.0.30001570 - electron-to-chromium: 1.4.614 + caniuse-lite: 1.0.30001574 + electron-to-chromium: 1.4.623 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.22.2) dev: true @@ -1130,8 +1113,8 @@ packages: engines: { node: '>=10' } dev: true - /caniuse-lite@1.0.30001570: - resolution: { integrity: sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== } + /caniuse-lite@1.0.30001574: + resolution: { integrity: sha512-BtYEK4r/iHt/txm81KBudCUcTy7t+s9emrIaHqjYurQ10x71zJ5VQ9x1dYPcz/b+pKSp4y/v1xSI67A+LzpNyg== } dev: true /chalk@2.4.2: @@ -1296,10 +1279,6 @@ packages: engines: { node: '>=0.10.0' } dev: true - /depseek@0.2.1: - resolution: { integrity: sha512-LLWAVkTGDzGvAe5647Gyf6ggKHCA6TacjsPU4SDXpXn237Bcq62GCXRXy4MJfTyjjnth0v8Jkv038CdqEHu6NA== } - dev: true - /detect-newline@3.1.0: resolution: { integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== } engines: { node: '>=8' } @@ -1315,19 +1294,12 @@ packages: engines: { node: '>=0.3.1' } dev: true - /dir-glob@3.0.1: - resolution: { integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== } - engines: { node: '>=8' } - dependencies: - path-type: 4.0.0 - dev: true - /eastasianwidth@0.2.0: resolution: { integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== } dev: true - /electron-to-chromium@1.4.614: - resolution: { integrity: sha512-X4ze/9Sc3QWs6h92yerwqv7aB/uU8vCjZcrMjA8N9R1pjMFRe44dLsck5FzLilOYvcXuDn93B+bpGYyufc70gQ== } + /electron-to-chromium@1.4.623: + resolution: { integrity: sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A== } dev: true /emittery@0.13.1: @@ -1400,7 +1372,7 @@ packages: human-signals: 5.0.0 is-stream: 3.0.0 merge-stream: 2.0.0 - npm-run-path: 5.1.0 + npm-run-path: 5.2.0 onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 @@ -1422,27 +1394,10 @@ packages: jest-util: 29.7.0 dev: true - /fast-glob@3.3.2: - resolution: { integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== } - engines: { node: '>=8.6.0' } - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - dev: true - /fast-json-stable-stringify@2.1.0: resolution: { integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== } dev: true - /fastq@1.16.0: - resolution: { integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA== } - dependencies: - reusify: 1.0.4 - dev: true - /fb-watchman@2.0.2: resolution: { integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== } dependencies: @@ -1472,15 +1427,6 @@ packages: signal-exit: 4.1.0 dev: true - /fs-extra@11.2.0: - resolution: { integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== } - engines: { node: '>=14.14' } - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - dev: true - /fs.realpath@1.0.0: resolution: { integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== } dev: true @@ -1526,13 +1472,6 @@ packages: engines: { node: '>=16' } dev: true - /glob-parent@5.1.2: - resolution: { integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== } - engines: { node: '>= 6' } - dependencies: - is-glob: 4.0.3 - dev: true - /glob@10.3.10: resolution: { integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== } engines: { node: '>=16 || 14 >=14.17' } @@ -1545,8 +1484,8 @@ packages: path-scurry: 1.10.1 dev: true - /glob@7.2.0: - resolution: { integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== } + /glob@7.2.3: + resolution: { integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== } dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -1561,17 +1500,6 @@ packages: engines: { node: '>=4' } dev: true - /globby@13.2.2: - resolution: { integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } - dependencies: - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.0 - merge2: 1.4.1 - slash: 4.0.0 - dev: true - /graceful-fs@4.2.11: resolution: { integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== } dev: true @@ -1613,11 +1541,6 @@ packages: hasBin: true dev: true - /ignore@5.3.0: - resolution: { integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== } - engines: { node: '>= 4' } - dev: true - /import-local@3.1.0: resolution: { integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== } engines: { node: '>=8' } @@ -1653,11 +1576,6 @@ packages: hasown: 2.0.0 dev: true - /is-extglob@2.1.1: - resolution: { integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== } - engines: { node: '>=0.10.0' } - dev: true - /is-fullwidth-code-point@3.0.0: resolution: { integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== } engines: { node: '>=8' } @@ -1679,13 +1597,6 @@ packages: engines: { node: '>=6' } dev: true - /is-glob@4.0.3: - resolution: { integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== } - engines: { node: '>=0.10.0' } - dependencies: - is-extglob: 2.1.1 - dev: true - /is-number@7.0.0: resolution: { integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== } engines: { node: '>=0.12.0' } @@ -1714,7 +1625,7 @@ packages: resolution: { integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== } engines: { node: '>=8' } dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/parser': 7.23.6 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -1727,7 +1638,7 @@ packages: resolution: { integrity: sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA== } engines: { node: '>=10' } dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/parser': 7.23.6 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 @@ -1851,15 +1762,15 @@ packages: ts-node: optional: true dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 '@types/node': 20.10.6 - babel-jest: 29.7.0(@babel/core@7.23.6) + babel-jest: 29.7.0(@babel/core@7.23.7) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 - glob: 7.2.0 + glob: 7.2.3 graceful-fs: 4.2.11 jest-circus: 29.7.0 jest-environment-node: 29.7.0 @@ -2072,7 +1983,7 @@ packages: chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 - glob: 7.2.0 + glob: 7.2.3 graceful-fs: 4.2.11 jest-haste-map: 29.7.0 jest-message-util: 29.7.0 @@ -2091,15 +2002,15 @@ packages: resolution: { integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 '@babel/generator': 7.23.6 - '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.6) - '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.6) + '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.7) + '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.7) '@babel/types': 7.23.6 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.6) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.7) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -2222,14 +2133,6 @@ packages: hasBin: true dev: true - /jsonfile@6.1.0: - resolution: { integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== } - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - dev: true - /kleur@3.0.3: resolution: { integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== } engines: { node: '>=6' } @@ -2349,20 +2252,10 @@ packages: tmpl: 1.0.5 dev: true - /meow@12.1.1: - resolution: { integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw== } - engines: { node: '>=16.10' } - dev: true - /merge-stream@2.0.0: resolution: { integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== } dev: true - /merge2@1.4.1: - resolution: { integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== } - engines: { node: '>= 8' } - dev: true - /micromatch@4.0.5: resolution: { integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== } engines: { node: '>=8.6' } @@ -2427,8 +2320,8 @@ packages: path-key: 3.1.1 dev: true - /npm-run-path@5.1.0: - resolution: { integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== } + /npm-run-path@5.2.0: + resolution: { integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } dependencies: path-key: 4.0.0 @@ -2522,11 +2415,6 @@ packages: minipass: 7.0.4 dev: true - /path-type@4.0.0: - resolution: { integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== } - engines: { node: '>=8' } - dev: true - /picocolors@1.0.0: resolution: { integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== } dev: true @@ -2600,10 +2488,6 @@ packages: resolution: { integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== } dev: true - /queue-microtask@1.2.3: - resolution: { integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== } - dev: true - /react-is@18.2.0: resolution: { integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== } dev: true @@ -2646,11 +2530,6 @@ packages: signal-exit: 3.0.7 dev: true - /reusify@1.0.4: - resolution: { integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== } - engines: { iojs: '>=1.0.0', node: '>=0.10.0' } - dev: true - /rfdc@1.3.0: resolution: { integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== } dev: true @@ -2663,12 +2542,6 @@ packages: glob: 10.3.10 dev: true - /run-parallel@1.2.0: - resolution: { integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== } - dependencies: - queue-microtask: 1.2.3 - dev: true - /semver@6.3.1: resolution: { integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== } hasBin: true @@ -2712,11 +2585,6 @@ packages: engines: { node: '>=8' } dev: true - /slash@4.0.0: - resolution: { integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== } - engines: { node: '>=12' } - dev: true - /slice-ansi@5.0.0: resolution: { integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== } engines: { node: '>=12' } @@ -2859,7 +2727,7 @@ packages: engines: { node: '>=8' } dependencies: '@istanbuljs/schema': 0.1.3 - glob: 7.2.0 + glob: 7.2.3 minimatch: 3.1.2 dev: true @@ -2879,7 +2747,7 @@ packages: is-number: 7.0.0 dev: true - /ts-jest@29.1.1(@babel/core@7.23.6)(jest@29.7.0)(typescript@5.3.3): + /ts-jest@29.1.1(@babel/core@7.23.7)(jest@29.7.0)(typescript@5.3.3): resolution: { integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== } engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } hasBin: true @@ -2900,7 +2768,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.23.6 + '@babel/core': 7.23.7 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 jest: 29.7.0(@types/node@20.10.6)(ts-node@10.9.2) @@ -2933,7 +2801,7 @@ packages: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 20.10.6 - acorn: 8.11.2 + acorn: 8.11.3 acorn-walk: 8.3.1 arg: 4.1.3 create-require: 1.1.1 @@ -2944,23 +2812,6 @@ packages: yn: 3.1.1 dev: true - /tsc-esm-fix@2.20.21: - resolution: { integrity: sha512-v/n5ZKlt5j/UNFAKBua9cV5wZll6QlJEQgT4lwXkv8pcOXf9iR9qL3B2kYZUwZCi6vVPumztlq19Kh8bgcYoIw== } - engines: { node: '>=16.0.0' } - hasBin: true - dependencies: - depseek: 0.2.1 - fs-extra: 11.2.0 - globby: 13.2.2 - json5: 2.2.3 - meow: 12.1.1 - tslib: 2.6.2 - dev: true - - /tslib@2.6.2: - resolution: { integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== } - dev: true - /type-detect@4.0.8: resolution: { integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== } engines: { node: '>=4' } @@ -2985,11 +2836,6 @@ packages: /undici-types@5.26.5: resolution: { integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== } - /universalify@2.0.1: - resolution: { integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== } - engines: { node: '>= 10.0.0' } - dev: true - /update-browserslist-db@1.0.13(browserslist@4.22.2): resolution: { integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== } hasBin: true diff --git a/tsconfig.json b/tsconfig.json index 3ab2191..58425a6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,11 @@ { + "extends": "@tsconfig/node14/tsconfig.json", "compilerOptions": { - "rootDir": "src", "outDir": "lib", - "lib": ["ESNext"], - "module": "ESNext", - "target": "ESNext", - "types": ["node", "jest", "@types/jest"], - "moduleResolution": "node", - "removeComments": true, - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, "sourceMap": true, "declaration": true, - "declarationMap": true + "stripInternal": true, + "allowJs": true }, - "ts-node": { - "esm": true, - "experimentalSpecifierResolution": "node" - }, - "include": ["./src/**/*"] + "include": ["src/*.ts"] } From 8b2014b7c7deb56982b996599c66d66aa1633bb6 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sat, 6 Jan 2024 22:05:06 +0800 Subject: [PATCH 18/30] feat: status support --- src/index.ts | 3 + src/schema/status.ts | 152 ++++++++++++++++++++++++++++++++++++++ src/server/serverError.ts | 4 +- 3 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/schema/status.ts diff --git a/src/index.ts b/src/index.ts index 214c08a..0a292c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,5 +3,8 @@ export * from './loader' export type { ProtoFileOptionsType, InitOptionsType } from './schema/loader' export type { ClientsOptionsType, ServerOptionsType } from './schema/loader' +// export status +export { Status } from './schema/status' + // export client export type { ClientOptionsType } from './schema/client' diff --git a/src/schema/status.ts b/src/schema/status.ts new file mode 100644 index 0000000..27cf798 --- /dev/null +++ b/src/schema/status.ts @@ -0,0 +1,152 @@ +/** + * gRPC status code. + * + * See https://grpc.github.io/grpc/core/md_doc_statuscodes.html. + */ +export enum Status { + /** + * Not an error; returned on success. + */ + OK = 0, + /** + * The operation was cancelled, typically by the caller. + */ + CANCELLED = 1, + /** + * Unknown error. + * + * For example, this error may be returned when a `Status` value received from + * another address space belongs to an error space that is not known in this + * address space. + * + * Also errors raised by APIs that do not return enough error information may + * be converted to this error. + */ + UNKNOWN = 2, + /** + * The client specified an invalid argument. + * + * Note that this differs from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` + * indicates arguments that are problematic regardless of the state of the + * system (e.g., a malformed file name). + */ + INVALID_ARGUMENT = 3, + /** + * The deadline expired before the operation could complete. + * + * For operations that change the state of the system, this error may be + * returned even if the operation has completed successfully. + * + * For example, a successful response from a server could have been delayed + * long enough for the deadline to expire. + */ + DEADLINE_EXCEEDED = 4, + /** + * Some requested entity (e.g., file or directory) was not found. + * + * Note to server developers: if a request is denied for an entire class of + * users, such as gradual feature rollout or undocumented allowlist, + * `NOT_FOUND` may be used. If a request is denied for some users within a + * class of users, such as user-based access control, `PERMISSION_DENIED` must + * be used. + */ + NOT_FOUND = 5, + /** + * The entity that a client attempted to create (e.g., file or directory) + * already exists. + */ + ALREADY_EXISTS = 6, + /** + * The caller does not have permission to execute the specified operation. + * + * `PERMISSION_DENIED` must not be used for rejections caused by exhausting + * some resource (use `RESOURCE_EXHAUSTED` instead for those errors). + * `PERMISSION_DENIED` must not be used if the caller can not be identified + * (use `UNAUTHENTICATED` instead for those errors). + * + * This error code does not imply the request is valid or the requested entity + * exists or satisfies other pre-conditions. + */ + PERMISSION_DENIED = 7, + /** + * Some resource has been exhausted, perhaps a per-user quota, or perhaps the + * entire file system is out of space. + */ + RESOURCE_EXHAUSTED = 8, + /** + * The operation was rejected because the system is not in a state required + * for the operation's execution. + * + * For example, the directory to be deleted is non-empty, an rmdir operation + * is applied to a non-directory, etc. + * + * Service implementors can use the following guidelines to decide between + * `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: + * + * (a) Use `UNAVAILABLE` if the client can retry just the failing call. + * (b) Use `ABORTED` if the client should retry at a higher level (e.g., + * when a client-specified test-and-set fails, indicating the client + * should restart a read-modify-write sequence). + * (c) Use `FAILED_PRECONDITION` if the client should not retry until the + * system state has been explicitly fixed. E.g., if an "rmdir" fails + * because the directory is non-empty, `FAILED_PRECONDITION` should be + * returned since the client should not retry unless the files are + * deleted from the directory. + */ + FAILED_PRECONDITION = 9, + /** + * The operation was aborted, typically due to a concurrency issue such as a + * sequencer check failure or transaction abort. + * + * See the guidelines above for deciding between `FAILED_PRECONDITION`, + * `ABORTED`, and `UNAVAILABLE`. + */ + ABORTED = 10, + /** + * The operation was attempted past the valid range. + * + * E.g., seeking or reading past end-of-file. + * + * Unlike `INVALID_ARGUMENT`, this error indicates a problem that may be fixed + * if the system state changes. For example, a 32-bit file system will + * generate `INVALID_ARGUMENT` if asked to read at an offset that is not in + * the range [0,2^32-1], but it will generate `OUT_OF_RANGE` if asked to read + * from an offset past the current file size. + * + * There is a fair bit of overlap between `FAILED_PRECONDITION` and + * `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific error) + * when it applies so that callers who are iterating through a space can + * easily look for an `OUT_OF_RANGE` error to detect when they are done. + */ + OUT_OF_RANGE = 11, + /** + * The operation is not implemented or is not supported/enabled in this + * service. + */ + UNIMPLEMENTED = 12, + /** + * Internal errors. + * + * This means that some invariants expected by the underlying system have been + * broken. This error code is reserved for serious errors. + */ + INTERNAL = 13, + /** + * The service is currently unavailable. + * + * This is most likely a transient condition, which can be corrected by + * retrying with a backoff. + * + * Note that it is not always safe to retry non-idempotent operations. + */ + UNAVAILABLE = 14, + /** + * Unrecoverable data loss or corruption. + */ + DATA_LOSS = 15, + /** + * The request does not have valid authentication credentials for the + * operation. + */ + UNAUTHENTICATED = 16 +} diff --git a/src/server/serverError.ts b/src/server/serverError.ts index 4809b64..6adf3cc 100644 --- a/src/server/serverError.ts +++ b/src/server/serverError.ts @@ -1,5 +1,7 @@ +import { Status } from '../schema/status' + export const createServerError = (err: any): any => { - err.code = err.code || 13 + err.code = err.code || Status.INTERNAL if (typeof err.stack === 'string') { const stack = err.stack.split('\n') err.messages += ` [Error Message From Server, stack: ${stack[1].trim()}]` From 8e12f455735ced3bd77ccc6c955f22e3d0010150 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sat, 6 Jan 2024 22:15:32 +0800 Subject: [PATCH 19/30] feat: allow to add new record to context --- src/client/clientContext.ts | 1 + src/server/serverContext.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/client/clientContext.ts b/src/client/clientContext.ts index ead45d7..bc79093 100644 --- a/src/client/clientContext.ts +++ b/src/client/clientContext.ts @@ -12,6 +12,7 @@ export type ClientContextType = { metadata?: grpc.Metadata response?: any status?: grpc.StatusObject + [key: string]: any } type createContextOptions = { diff --git a/src/server/serverContext.ts b/src/server/serverContext.ts index bc34f30..87ed730 100644 --- a/src/server/serverContext.ts +++ b/src/server/serverContext.ts @@ -9,6 +9,7 @@ export type ServerContextType = { request: any metadata: grpc.Metadata response?: any + [key: string]: any } export const createContext = (call: any, methodOptions: { requestStream: boolean; responseStream: boolean }): ServerContextType => { From 6f3113b3829bb169f8894f9c35cd7da2cf596318 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sat, 6 Jan 2024 22:43:05 +0800 Subject: [PATCH 20/30] feat: add peer --- example/helloworld/client.js | 2 +- src/client/bidiStreamProxy.ts | 1 + src/client/clientContext.ts | 4 +++- src/client/clientStreamProxy.ts | 1 + src/client/serverStreamProxy.ts | 1 + src/client/unaryProxy.ts | 1 + 6 files changed, 8 insertions(+), 2 deletions(-) diff --git a/example/helloworld/client.js b/example/helloworld/client.js index 2cd8f7f..991ee51 100644 --- a/example/helloworld/client.js +++ b/example/helloworld/client.js @@ -16,7 +16,7 @@ const start = async (addr) => { const beginTime = new Date().getTime() await next() const endTime = new Date().getTime() - console.log(ctx.response, endTime - beginTime) + console.log(ctx.peer, ctx.response, endTime - beginTime) } const clients = await loader.initClients({ diff --git a/src/client/bidiStreamProxy.ts b/src/client/bidiStreamProxy.ts index 527ac51..3f09a20 100644 --- a/src/client/bidiStreamProxy.ts +++ b/src/client/bidiStreamProxy.ts @@ -49,6 +49,7 @@ export const bidiStreamProxy = ( }) call.on('status', (status: StatusObject) => { ctx.status = status + ctx.peer = call.getPeer() }) return iterator(call, 'data', { resolutionEvents: ['status', 'end'] diff --git a/src/client/clientContext.ts b/src/client/clientContext.ts index bc79093..dbf4c8e 100644 --- a/src/client/clientContext.ts +++ b/src/client/clientContext.ts @@ -9,9 +9,10 @@ export type ClientContextType = { options: Record } request?: any + status?: grpc.StatusObject + peer?: string metadata?: grpc.Metadata response?: any - status?: grpc.StatusObject [key: string]: any } @@ -43,6 +44,7 @@ export const createContext = (args: createContextOptions): ClientContextType => export const createResponse = (ctx: ClientContextType) => { return { status: ctx.status, + peer: ctx.peer, metadata: ctx.metadata, response: ctx.response } diff --git a/src/client/clientStreamProxy.ts b/src/client/clientStreamProxy.ts index a844d63..5bd1356 100644 --- a/src/client/clientStreamProxy.ts +++ b/src/client/clientStreamProxy.ts @@ -52,6 +52,7 @@ export const clientStreamProxy = ( }) call.on('status', (status: StatusObject) => { ctx.status = status + ctx.peer = call.getPeer() resolve() }) }) diff --git a/src/client/serverStreamProxy.ts b/src/client/serverStreamProxy.ts index 6c838f5..368fbe7 100644 --- a/src/client/serverStreamProxy.ts +++ b/src/client/serverStreamProxy.ts @@ -41,6 +41,7 @@ export const serverStreamProxy = ( }) call.on('status', (status: StatusObject) => { ctx.status = status + ctx.peer = call.getPeer() }) return iterator(call, 'data', { resolutionEvents: ['status', 'end'] diff --git a/src/client/unaryProxy.ts b/src/client/unaryProxy.ts index be7f565..f6afd38 100644 --- a/src/client/unaryProxy.ts +++ b/src/client/unaryProxy.ts @@ -44,6 +44,7 @@ export const unaryProxy = ( }) call.on('status', (status: StatusObject) => { ctx.status = status + ctx.peer = call.getPeer() resolve() }) }) From 417854cd1c23a3b024c76007816fece069b4aad2 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sun, 7 Jan 2024 01:35:32 +0800 Subject: [PATCH 21/30] feat: add more proxy type --- src/client/bidiStreamProxy.ts | 17 ++++++++++++----- src/client/clientContext.ts | 9 ++++++++- src/client/clientStreamProxy.ts | 13 +++++++++---- src/client/index.ts | 16 ++++++++-------- src/client/serverStreamProxy.ts | 15 ++++++++++----- src/client/unaryProxy.ts | 10 ++++++---- src/index.ts | 18 +++++++++++++++--- src/loader.ts | 12 ++++++------ src/schema/client.ts | 4 ++-- src/schema/loader.ts | 14 +++++++------- src/schema/server.ts | 4 ++-- ...treamProxy.ts => bidiStreamingCallProxy.ts} | 11 +++++++++-- src/server/callbackify.ts | 8 ++++---- ...eamProxy.ts => clientStreamingCallProxy.ts} | 10 ++++++++-- src/server/index.ts | 10 +++++----- ...eamProxy.ts => serverStreamingCallProxy.ts} | 11 +++++++++-- .../{callUnaryProxy.ts => unaryCallProxy.ts} | 6 +++++- 17 files changed, 125 insertions(+), 63 deletions(-) rename src/server/{callBidiStreamProxy.ts => bidiStreamingCallProxy.ts} (78%) rename src/server/{callClientStreamProxy.ts => clientStreamingCallProxy.ts} (74%) rename src/server/{callServerStreamProxy.ts => serverStreamingCallProxy.ts} (73%) rename src/server/{callUnaryProxy.ts => unaryCallProxy.ts} (78%) diff --git a/src/client/bidiStreamProxy.ts b/src/client/bidiStreamProxy.ts index 3f09a20..780ac7b 100644 --- a/src/client/bidiStreamProxy.ts +++ b/src/client/bidiStreamProxy.ts @@ -1,9 +1,16 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' -import { createContext, createResponse } from './clientContext' -import iterator from '../utils/iterator' -import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' +import { createContext, createResponse, ClientResponse } from './clientContext' +import Iterator from '../utils/iterator' +import { UntypedServiceImplementation, Metadata, StatusObject, ClientDuplexStream, InterceptingCall } from '@grpc/grpc-js' + +export type ClientDuplexStreamCall = ClientDuplexStream & { + writeAll: (message: any[]) => void + writeEnd: Function + readAll: () => Iterator + readEnd: () => ClientResponse +} export const bidiStreamProxy = ( client: UntypedServiceImplementation, @@ -13,7 +20,7 @@ export const bidiStreamProxy = ( defaultOptions: Record, methodOptions: { requestStream: boolean; responseStream: boolean } ) => { - return async (metadata?: Metadata, options?: Record): Promise => { + return async (metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { @@ -51,7 +58,7 @@ export const bidiStreamProxy = ( ctx.status = status ctx.peer = call.getPeer() }) - return iterator(call, 'data', { + return Iterator(call, 'data', { resolutionEvents: ['status', 'end'] }) } diff --git a/src/client/clientContext.ts b/src/client/clientContext.ts index dbf4c8e..7e5ee40 100644 --- a/src/client/clientContext.ts +++ b/src/client/clientContext.ts @@ -41,7 +41,14 @@ export const createContext = (args: createContextOptions): ClientContextType => } } -export const createResponse = (ctx: ClientContextType) => { +export type ClientResponse = { + status?: grpc.StatusObject + peer?: string + metadata?: grpc.Metadata + response?: any +} + +export const createResponse = (ctx: ClientContextType): ClientResponse => { return { status: ctx.status, peer: ctx.peer, diff --git a/src/client/clientStreamProxy.ts b/src/client/clientStreamProxy.ts index 5bd1356..070d87d 100644 --- a/src/client/clientStreamProxy.ts +++ b/src/client/clientStreamProxy.ts @@ -1,8 +1,13 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' -import { createContext, createResponse } from './clientContext' -import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' +import { createContext, createResponse, ClientResponse } from './clientContext' +import { UntypedServiceImplementation, Metadata, StatusObject, ClientWritableStream } from '@grpc/grpc-js' + +export type ClientWritableStreamCall = ClientWritableStream & { + writeAll: (message: any[]) => void + writeEnd: () => Promise +} export const clientStreamProxy = ( client: UntypedServiceImplementation, @@ -12,7 +17,7 @@ export const clientStreamProxy = ( defaultOptions: Record, methodOptions: { requestStream: boolean; responseStream: boolean } ) => { - return async (metadata?: Metadata, options?: Record): Promise => { + return async (metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { @@ -34,7 +39,7 @@ export const clientStreamProxy = ( ctx.response = response }) - const call = func.apply(client, argumentsList) + const call: ClientWritableStreamCall = func.apply(client, argumentsList) call.writeAll = (messages: any[]) => { if (Array.isArray(messages)) { diff --git a/src/client/index.ts b/src/client/index.ts index 2964356..eaa3934 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -6,10 +6,10 @@ import { clientProxy } from './clientProxy' import { isString } from '../utils/string' import { compose } from '../utils/compose' import type { MiddlewareFunction } from '../utils/compose' -import type { ClientsOptionsType, AddressObject } from '../schema/loader' -import type { ClientOptionsType } from '../schema/client' +import type { ClientsOptions, AddressObject } from '../schema/loader' +import type { ClientOptions } from '../schema/client' -const prepareUrl = (url: ClientOptionsType['url']) => { +const prepareUrl = (url: ClientOptions['url']) => { return { isDefaultClient: !!url, addr: isString(url) ? (url as string) : (url as AddressObject)?.host + ':' + (url as AddressObject)?.port @@ -20,9 +20,9 @@ export default class Clients { private _middleware: MiddlewareFunction[] = [] private _proxyClientMap: Map = new Map() private _clientFactory: ClientFactory - private _credentials: ClientsOptionsType['credentials'] + private _credentials: ClientsOptions['credentials'] - constructor(loader: ProtoLoader, options: ClientsOptionsType) { + constructor(loader: ProtoLoader, options: ClientsOptions) { this._clientFactory = new ClientFactory(loader) const { services, channelOptions, credentials } = options @@ -43,7 +43,7 @@ export default class Clients { return this } - get(name: string, clientOptions: ClientOptionsType = {}) { + get(name: string, clientOptions: ClientOptions = {}) { let { url, channelOptions, credentials, timeout } = clientOptions const { isDefaultClient, addr } = prepareUrl(url) @@ -66,8 +66,8 @@ export default class Clients { return proxy } - getReal(name: string, clientOptions: ClientOptionsType = {}) { - let { url, channelOptions, credentials } = clientOptions as ClientOptionsType + getReal(name: string, clientOptions: ClientOptions = {}) { + let { url, channelOptions, credentials } = clientOptions as ClientOptions if (!credentials) { credentials = this._credentials diff --git a/src/client/serverStreamProxy.ts b/src/client/serverStreamProxy.ts index 368fbe7..a88dce6 100644 --- a/src/client/serverStreamProxy.ts +++ b/src/client/serverStreamProxy.ts @@ -1,9 +1,14 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' -import iterator from '../utils/iterator' -import { createContext, createResponse } from './clientContext' -import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' +import Iterator from '../utils/iterator' +import { createContext, createResponse, ClientResponse } from './clientContext' +import { UntypedServiceImplementation, Metadata, StatusObject, ClientReadableStream } from '@grpc/grpc-js' + +export type ClientReadableStreamCall = ClientReadableStream & { + readAll: () => Iterator + readEnd: () => ClientResponse +} export const serverStreamProxy = ( client: UntypedServiceImplementation, @@ -13,7 +18,7 @@ export const serverStreamProxy = ( defaultOptions: Record, methodOptions: { requestStream: boolean; responseStream: boolean } ) => { - return async (request?: any, metadata?: Metadata, options?: Record): Promise => { + return async (request?: any, metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { throw new Error('gRPCity: asyncStreamFunction should not contain a callback function') } else if (typeof metadata === 'function') { @@ -43,7 +48,7 @@ export const serverStreamProxy = ( ctx.status = status ctx.peer = call.getPeer() }) - return iterator(call, 'data', { + return Iterator(call, 'data', { resolutionEvents: ['status', 'end'] }) } diff --git a/src/client/unaryProxy.ts b/src/client/unaryProxy.ts index f6afd38..23b48b0 100644 --- a/src/client/unaryProxy.ts +++ b/src/client/unaryProxy.ts @@ -1,8 +1,10 @@ import { createClientError } from './clientError' import { combineMetadata } from './clientMetadata' import { setDeadline } from './clientDeadline' -import { createContext, createResponse } from './clientContext' -import { UntypedServiceImplementation, Metadata, StatusObject } from '@grpc/grpc-js' +import { createContext, createResponse, ClientResponse } from './clientContext' +import { UntypedServiceImplementation, Metadata, StatusObject, ClientUnaryCall } from '@grpc/grpc-js' + +export type { ClientUnaryCall } from '@grpc/grpc-js' export const unaryProxy = ( client: UntypedServiceImplementation, @@ -12,7 +14,7 @@ export const unaryProxy = ( defaultOptions: Record, methodOptions: { requestStream: boolean; responseStream: boolean } ) => { - return async (request?: any, metadata?: Metadata, options?: Record): Promise => { + return async (request?: any, metadata?: Metadata, options?: Record): Promise => { if (typeof options === 'function') { throw new Error('gRPCity: AsyncFunction should not contain a callback function') } else if (typeof metadata === 'function') { @@ -37,7 +39,7 @@ export const unaryProxy = ( ctx.response = response }) - const call = func.apply(client, argumentsList) + const call: ClientUnaryCall = func.apply(client, argumentsList) call.on('metadata', (metadata: Metadata) => { ctx.metadata = metadata diff --git a/src/index.ts b/src/index.ts index 0a292c2..573c657 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,22 @@ // export loader export * from './loader' -export type { ProtoFileOptionsType, InitOptionsType } from './schema/loader' -export type { ClientsOptionsType, ServerOptionsType } from './schema/loader' +export { defaultChannelOptions } from './config/defaultChannelOptions' +export { defaultLoadOptions } from './config/defaultLoadOptions' +export type { ProtoFileOptions, InitOptions, Address } from './schema/loader' +export type { ClientsOptions, ServerOptions, LoaderOptions } from './schema/loader' // export status export { Status } from './schema/status' // export client -export type { ClientOptionsType } from './schema/client' +export type { ClientOptions } from './schema/client' +export type { ClientUnaryCall } from './client/unaryProxy' +export type { ClientWritableStreamCall } from './client/clientStreamProxy' +export type { ClientReadableStreamCall } from './client/serverStreamProxy' +export type { ClientDuplexStreamCall } from './client/bidiStreamProxy' + +// export server +export type { HandleUnaryCall, ServerUnaryCall } from './server/unaryCallProxy' +export type { HandleClientStreamingCall, ServerReadableStream } from './server/clientStreamingCallProxy' +export type { HandleServerStreamingCall, ServerWritableStream } from './server/serverStreamingCallProxy' +export type { HandleBidiStreamingCall, ServerDuplexStream } from './server/bidiStreamingCallProxy' diff --git a/src/loader.ts b/src/loader.ts index dff7317..1a3f087 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -8,8 +8,8 @@ import { isString } from './utils/string' import { get } from './utils/object' import { prefixingDefinition } from './utils/definition' import { assertProtoFileOptionsOptions, attemptInitOptions, attemptInitClientsOptions } from './schema/loader' -import type { ClientsOptionsType, ServerOptionsType } from './schema/loader' -import type { ProtoFileOptionsType, ProtoFileOptionType, InitOptionsType } from './schema/loader' +import type { ClientsOptions, ServerOptions } from './schema/loader' +import type { ProtoFileOptions, ProtoFileOptionType, InitOptions } from './schema/loader' export class ProtoLoader { private _protoFiles: ProtoFileOptionType[] @@ -20,12 +20,12 @@ export class ProtoLoader { private _insecureClientCredentials?: grpc.ChannelCredentials private _insecureServerCredentials?: grpc.ServerCredentials - constructor(protoFileOptions: ProtoFileOptionsType) { + constructor(protoFileOptions: ProtoFileOptions) { assertProtoFileOptionsOptions(protoFileOptions) this._protoFiles = Array.isArray(protoFileOptions) ? protoFileOptions : [protoFileOptions] } - async init(InitOptions?: InitOptionsType) { + async init(InitOptions?: InitOptions) { const newInitOptions = attemptInitOptions(InitOptions) if (this._types) { @@ -60,7 +60,7 @@ export class ProtoLoader { } } - async initClients(options: ClientsOptionsType) { + async initClients(options: ClientsOptions) { if (!this._packageDefinition) { await this.init() } @@ -68,7 +68,7 @@ export class ProtoLoader { return new Clients(this, clientsOptions) } - async initServer(options?: ServerOptionsType) { + async initServer(options?: ServerOptions) { if (!this._packageDefinition) { await this.init() } diff --git a/src/schema/client.ts b/src/schema/client.ts index 5ad9708..f4333cf 100644 --- a/src/schema/client.ts +++ b/src/schema/client.ts @@ -19,7 +19,7 @@ const ClientOptionsSchema = Joi.object({ .default(1000 * 10) }).optional() -export type ClientOptionsType = { +export type ClientOptions = { url?: Address channelOptions?: ChannelOptions credentials?: ChannelCredentials @@ -27,6 +27,6 @@ export type ClientOptionsType = { [key: string]: any } -export const attemptClientOptions = (options: ClientOptionsType) => { +export const attemptClientOptions = (options: ClientOptions) => { return Joi.attempt(options || {}, ClientOptionsSchema) } diff --git a/src/schema/loader.ts b/src/schema/loader.ts index 9d051dc..1a93a3c 100644 --- a/src/schema/loader.ts +++ b/src/schema/loader.ts @@ -5,7 +5,7 @@ import { defaultLoadOptions } from '../config/defaultLoadOptions' import { defaultChannelOptions } from '../config/defaultChannelOptions' export type { Options as LoaderOptions } from '@grpc/proto-loader' -export type { ChannelOptions as ServerOptionsType } from '@grpc/grpc-js' +export type { ChannelOptions as ServerOptions } from '@grpc/grpc-js' const protoFileOptionsSchema = Joi.array() .items( @@ -21,9 +21,9 @@ export type ProtoFileOptionType = { files: string[] } -export type ProtoFileOptionsType = ProtoFileOptionType[] | ProtoFileOptionType +export type ProtoFileOptions = ProtoFileOptionType[] | ProtoFileOptionType -export const assertProtoFileOptionsOptions = (options: ProtoFileOptionsType) => { +export const assertProtoFileOptionsOptions = (options: ProtoFileOptions) => { Joi.assert(options, protoFileOptionsSchema, 'new ProtoLoader() params error') } @@ -33,7 +33,7 @@ export type AddressObject = { } export type Address = AddressObject | string -export type InitOptionsType = { +export type InitOptions = { isDev?: boolean packagePrefix?: string loadOptions?: LoaderOptions @@ -55,7 +55,7 @@ const InitOptionsSchema = Joi.object({ loadOptions: Joi.object().optional().default(defaultLoadOptions) }) -export const attemptInitOptions = (options?: InitOptionsType) => { +export const attemptInitOptions = (options?: InitOptions) => { return Joi.attempt(options || {}, InitOptionsSchema) } @@ -65,12 +65,12 @@ const ClientsOptionsSchema = Joi.object({ channelOptions: Joi.object().optional().default(defaultChannelOptions) }) -export type ClientsOptionsType = { +export type ClientsOptions = { services: Record channelOptions?: ChannelOptions credentials?: ChannelCredentials } -export const attemptInitClientsOptions = (options: ClientsOptionsType) => { +export const attemptInitClientsOptions = (options: ClientsOptions) => { return Joi.attempt(options || {}, ClientsOptionsSchema) } diff --git a/src/schema/server.ts b/src/schema/server.ts index 026d043..7f45e45 100644 --- a/src/schema/server.ts +++ b/src/schema/server.ts @@ -2,8 +2,8 @@ import Joi from 'joi' import type { ChannelOptions } from '@grpc/grpc-js' import { defaultChannelOptions } from '../config/defaultChannelOptions' -import { ServerOptionsType } from './loader' +import { ServerOptions } from './loader' -export const assignServerOptions = (options?: ServerOptionsType): ChannelOptions => { +export const assignServerOptions = (options?: ServerOptions): ChannelOptions => { return Object.assign({}, defaultChannelOptions, options || {}) } diff --git a/src/server/callBidiStreamProxy.ts b/src/server/bidiStreamingCallProxy.ts similarity index 78% rename from src/server/callBidiStreamProxy.ts rename to src/server/bidiStreamingCallProxy.ts index 9aeffd6..b29791b 100644 --- a/src/server/callBidiStreamProxy.ts +++ b/src/server/bidiStreamingCallProxy.ts @@ -3,13 +3,20 @@ import iterator from '../utils/iterator' import { createContext } from './serverContext' import { createServerError } from './serverError' +export type ServerDuplexStream = grpc.ServerDuplexStream & { + writeAll: (message: any[]) => void + readAll: Function +} + +export type HandleBidiStreamingCall = (call: ServerDuplexStream) => void + export const callBidiStreamProxy = ( target: any, key: string, composeFunc: Function, methodOptions: { requestStream: boolean; responseStream: boolean } -): grpc.handleBidiStreamingCall => { - return (call: any) => { +): HandleBidiStreamingCall => { + return (call) => { const ctx = createContext(call, methodOptions) call.writeAll = (messages: any[]) => { diff --git a/src/server/callbackify.ts b/src/server/callbackify.ts index af4272c..2135c45 100644 --- a/src/server/callbackify.ts +++ b/src/server/callbackify.ts @@ -1,10 +1,10 @@ import assert from 'node:assert' import * as util from 'node:util' import { compose, MiddlewareFunction } from '../utils/compose' -import { callUnaryProxy } from './callUnaryProxy' -import { callClientStreamProxy } from './callClientStreamProxy' -import { callServerStreamProxy } from './callServerStreamProxy' -import { callBidiStreamProxy } from './callBidiStreamProxy' +import { callUnaryProxy } from './unaryCallProxy' +import { callClientStreamProxy } from './clientStreamingCallProxy' +import { callServerStreamProxy } from './serverStreamingCallProxy' +import { callBidiStreamProxy } from './bidiStreamingCallProxy' export type CallbackifyOptions = { _implementationType: Record diff --git a/src/server/callClientStreamProxy.ts b/src/server/clientStreamingCallProxy.ts similarity index 74% rename from src/server/callClientStreamProxy.ts rename to src/server/clientStreamingCallProxy.ts index 70e37df..43b529c 100644 --- a/src/server/callClientStreamProxy.ts +++ b/src/server/clientStreamingCallProxy.ts @@ -3,13 +3,19 @@ import iterator from '../utils/iterator' import { createContext } from './serverContext' import { createServerError } from './serverError' +export type ServerReadableStream = grpc.ServerReadableStream & { + readAll: Function +} + +export type HandleClientStreamingCall = (call: ServerReadableStream, callback: grpc.sendUnaryData) => void + export const callClientStreamProxy = ( target: any, key: string, composeFunc: Function, methodOptions: { requestStream: boolean; responseStream: boolean } -): grpc.handleClientStreamingCall => { - return (call: any, callback) => { +): HandleClientStreamingCall => { + return (call, callback) => { const ctx = createContext(call, methodOptions) call.readAll = () => { diff --git a/src/server/index.ts b/src/server/index.ts index bd510ff..1f8d495 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -5,7 +5,7 @@ import { isString } from '../utils/string' import { assignServerOptions } from '../schema/server' import { ProtoLoader } from '../loader' import { callbackify } from './callbackify' -import type { ServerOptionsType } from '../schema/loader' +import type { ServerOptions } from '../schema/loader' import type { MiddlewareFunction } from '../utils/compose' import type { CallbackifyOptions } from './callbackify' @@ -14,7 +14,7 @@ export default class Server { private _loader?: ProtoLoader private _server?: grpc.Server - constructor(loader: ProtoLoader, options?: ServerOptionsType) { + constructor(loader: ProtoLoader, options?: ServerOptions) { this._loader = loader if (!this._loader) { this._loader = loader @@ -25,16 +25,16 @@ export default class Server { } } - async listen(addr: any, credentials?: grpc.ServerCredentials): Promise { + async listen(addr: string | { host: string; port: number }, credentials?: grpc.ServerCredentials): Promise { assert(this._server, 'must be first init() server before server listen()') - const url = isString(addr) ? addr : `${addr.host}:${addr.port}` + const url = isString(addr) ? (addr as string) : `${(addr as any).host}:${(addr as any).port}` const bindPort = await new Promise((resolve, reject) => { this._server!.bindAsync(url, credentials || (this._loader as ProtoLoader).makeServerCredentials(), (err, result) => err ? reject(err) : resolve(result) ) }) - const port = addr.port ? addr.port : Number(addr.match(/:(\d+)/)![1]) + const port = (addr as any).port ? (addr as any).port : Number((addr as string).match(/:(\d+)/)![1]) assert(bindPort === port, 'server bind port not to be right') this._server!.start() diff --git a/src/server/callServerStreamProxy.ts b/src/server/serverStreamingCallProxy.ts similarity index 73% rename from src/server/callServerStreamProxy.ts rename to src/server/serverStreamingCallProxy.ts index 1af0eba..caae1a9 100644 --- a/src/server/callServerStreamProxy.ts +++ b/src/server/serverStreamingCallProxy.ts @@ -2,13 +2,20 @@ import * as grpc from '@grpc/grpc-js' import { createContext } from './serverContext' import { createServerError } from './serverError' +export type ServerWritableStream = grpc.ServerWritableStream & { + writeAll: (message: any[]) => void + writeEnd: (metadata?: grpc.Metadata) => void +} + +export type HandleServerStreamingCall = (call: ServerWritableStream) => void + export const callServerStreamProxy = ( target: any, key: string, composeFunc: Function, methodOptions: { requestStream: boolean; responseStream: boolean } -): grpc.handleServerStreamingCall => { - return (call: any) => { +): HandleServerStreamingCall => { + return (call) => { const ctx = createContext(call, methodOptions) call.writeAll = (messages: any[]) => { diff --git a/src/server/callUnaryProxy.ts b/src/server/unaryCallProxy.ts similarity index 78% rename from src/server/callUnaryProxy.ts rename to src/server/unaryCallProxy.ts index ee3be6e..437b902 100644 --- a/src/server/callUnaryProxy.ts +++ b/src/server/unaryCallProxy.ts @@ -2,12 +2,16 @@ import * as grpc from '@grpc/grpc-js' import { createContext } from './serverContext' import { createServerError } from './serverError' +export type ServerUnaryCall = grpc.ServerUnaryCall + +export type HandleUnaryCall = (call: ServerUnaryCall, callback: grpc.sendUnaryData) => void + export const callUnaryProxy = ( target: any, key: string, composeFunc: Function, methodOptions: { requestStream: boolean; responseStream: boolean } -): grpc.handleUnaryCall => { +): HandleUnaryCall => { return (call, callback) => { const ctx = createContext(call, methodOptions) From 102e6b97977ab8064405508706032ea282357f01 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sun, 7 Jan 2024 11:21:13 +0800 Subject: [PATCH 22/30] feat: improve middleware context --- src/client/clientContext.ts | 8 ++++---- src/client/index.ts | 1 - src/index.ts | 8 ++++++++ src/server/serverContext.ts | 8 +++++--- src/server/serverStreamingCallProxy.ts | 3 +++ src/server/unaryCallProxy.ts | 3 +++ src/utils/compose.ts | 6 ++++-- src/utils/string.ts | 3 +-- 8 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/client/clientContext.ts b/src/client/clientContext.ts index 7e5ee40..d134280 100644 --- a/src/client/clientContext.ts +++ b/src/client/clientContext.ts @@ -1,6 +1,6 @@ import * as grpc from '@grpc/grpc-js' -export type ClientContextType = { +export type ClientContext = { path: string method: { requestStream: boolean @@ -26,7 +26,7 @@ type createContextOptions = { } } -export const createContext = (args: createContextOptions): ClientContextType => { +export const createContext = (args: createContextOptions): ClientContext => { const { request, metadata, options, methodOptions } = args const { requestStream, responseStream } = methodOptions return { @@ -34,7 +34,7 @@ export const createContext = (args: createContextOptions): ClientContextType => method: { requestStream: requestStream || false, responseStream: responseStream || false, - metadata: metadata.clone(), + metadata: metadata?.clone() || new grpc.Metadata(), options }, request: request || {} @@ -48,7 +48,7 @@ export type ClientResponse = { response?: any } -export const createResponse = (ctx: ClientContextType): ClientResponse => { +export const createResponse = (ctx: ClientContext): ClientResponse => { return { status: ctx.status, peer: ctx.peer, diff --git a/src/client/index.ts b/src/client/index.ts index eaa3934..aaeeaae 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -40,7 +40,6 @@ export default class Clients { this._clientFactory.create(isDefault, name, addr, credentials, channelOptions) }) - return this } get(name: string, clientOptions: ClientOptions = {}) { diff --git a/src/index.ts b/src/index.ts index 573c657..a0a7439 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,3 +20,11 @@ export type { HandleUnaryCall, ServerUnaryCall } from './server/unaryCallProxy' export type { HandleClientStreamingCall, ServerReadableStream } from './server/clientStreamingCallProxy' export type { HandleServerStreamingCall, ServerWritableStream } from './server/serverStreamingCallProxy' export type { HandleBidiStreamingCall, ServerDuplexStream } from './server/bidiStreamingCallProxy' + +// export grpc-js +export type { Metadata, StatusObject } from '@grpc/grpc-js' + +// export middleware +export type { ClientContext } from './client/clientContext' +export type { ServerContext } from './server/serverContext' +export type { Next } from './utils/compose' diff --git a/src/server/serverContext.ts b/src/server/serverContext.ts index 87ed730..6103e10 100644 --- a/src/server/serverContext.ts +++ b/src/server/serverContext.ts @@ -1,6 +1,6 @@ import * as grpc from '@grpc/grpc-js' -export type ServerContextType = { +export type ServerContext = { path: string method: { requestStream: boolean @@ -8,11 +8,12 @@ export type ServerContextType = { } request: any metadata: grpc.Metadata + peer: string response?: any [key: string]: any } -export const createContext = (call: any, methodOptions: { requestStream: boolean; responseStream: boolean }): ServerContextType => { +export const createContext = (call: any, methodOptions: { requestStream: boolean; responseStream: boolean }): ServerContext => { const { requestStream, responseStream } = methodOptions return { path: call.getPath() || '', @@ -21,6 +22,7 @@ export const createContext = (call: any, methodOptions: { requestStream: boolean responseStream: responseStream || false }, request: call.request, - metadata: call.metadata.clone() + metadata: call.metadata.clone(), + peer: call.getPeer() || '' } } diff --git a/src/server/serverStreamingCallProxy.ts b/src/server/serverStreamingCallProxy.ts index caae1a9..4c8eaee 100644 --- a/src/server/serverStreamingCallProxy.ts +++ b/src/server/serverStreamingCallProxy.ts @@ -29,6 +29,9 @@ export const callServerStreamProxy = ( Promise.resolve().then(async () => { const handleResponse = async () => { + if (call.request) { + call.request = ctx.request + } await target[key](call) } await composeFunc(ctx, handleResponse).catch((err: Error) => { diff --git a/src/server/unaryCallProxy.ts b/src/server/unaryCallProxy.ts index 437b902..ef75518 100644 --- a/src/server/unaryCallProxy.ts +++ b/src/server/unaryCallProxy.ts @@ -17,6 +17,9 @@ export const callUnaryProxy = ( Promise.resolve().then(async () => { const handleResponse = async () => { + if (call.request) { + call.request = ctx.request + } ctx.response = await target[key](call) } await composeFunc(ctx, handleResponse).catch((err: Error) => { diff --git a/src/utils/compose.ts b/src/utils/compose.ts index 575153b..f3ea1e1 100644 --- a/src/utils/compose.ts +++ b/src/utils/compose.ts @@ -1,4 +1,6 @@ -export type MiddlewareFunction = (context: any, next: () => Promise) => Promise +export type Next = () => Promise + +export type MiddlewareFunction = (context: any, next: Next) => Promise export const compose = (middleware: MiddlewareFunction[]): Function => { if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!') @@ -6,7 +8,7 @@ export const compose = (middleware: MiddlewareFunction[]): Function => { if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!') } - return function (context: any, next: () => Promise) { + return function (context: any, next: Next) { let index = -1 return dispatch(0) function dispatch(i: number): Promise { diff --git a/src/utils/string.ts b/src/utils/string.ts index a617f6b..53c50c8 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -1,4 +1,3 @@ export function isString(value: any): boolean { - const type = typeof value - return type === 'string' || value instanceof String + return typeof value === 'string' || value instanceof String } From 74245aaf05cb9fcf07ab9090730475e4ccfbe646 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sun, 7 Jan 2024 11:22:00 +0800 Subject: [PATCH 23/30] feat: add protoloader & unarycall test --- test/bidiStreamCall.test.ts | 6 + test/clientStreamCall.test.ts | 6 + test/{loader.test.ts => protoLoader.test.ts} | 2 +- test/serverStreamCall.test.ts | 6 + test/unaryCall.test.ts | 359 +++++++++++++++++++ 5 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 test/bidiStreamCall.test.ts create mode 100644 test/clientStreamCall.test.ts rename test/{loader.test.ts => protoLoader.test.ts} (98%) create mode 100644 test/serverStreamCall.test.ts create mode 100644 test/unaryCall.test.ts diff --git a/test/bidiStreamCall.test.ts b/test/bidiStreamCall.test.ts new file mode 100644 index 0000000..78aaa0d --- /dev/null +++ b/test/bidiStreamCall.test.ts @@ -0,0 +1,6 @@ +import path from 'node:path' +import { ProtoLoader } from '../src' + +describe('gRPC Bidi Stream Call', () => { + test('Should', async () => {}) +}) diff --git a/test/clientStreamCall.test.ts b/test/clientStreamCall.test.ts new file mode 100644 index 0000000..997da57 --- /dev/null +++ b/test/clientStreamCall.test.ts @@ -0,0 +1,6 @@ +import path from 'node:path' +import { ProtoLoader } from '../src' + +describe('gRPC Client Stream Call', () => { + test('Should', async () => {}) +}) diff --git a/test/loader.test.ts b/test/protoLoader.test.ts similarity index 98% rename from test/loader.test.ts rename to test/protoLoader.test.ts index 32c25ae..9123702 100644 --- a/test/loader.test.ts +++ b/test/protoLoader.test.ts @@ -1,7 +1,7 @@ import path from 'node:path' import { ProtoLoader } from '../src' -describe('ProtoLoader', () => { +describe('gRPC Proto Loader', () => { test('Should be load a single proto path', async () => { const loader = new ProtoLoader({ location: path.join(__dirname, '../example/proto'), diff --git a/test/serverStreamCall.test.ts b/test/serverStreamCall.test.ts new file mode 100644 index 0000000..63765f4 --- /dev/null +++ b/test/serverStreamCall.test.ts @@ -0,0 +1,6 @@ +import path from 'node:path' +import { ProtoLoader } from '../src' + +describe('gRPC Server Stream Call', () => { + test('Should', async () => {}) +}) diff --git a/test/unaryCall.test.ts b/test/unaryCall.test.ts new file mode 100644 index 0000000..2cbefb5 --- /dev/null +++ b/test/unaryCall.test.ts @@ -0,0 +1,359 @@ +import fs from 'node:fs' +import path from 'node:path' +import { ProtoLoader } from '../src' +import type { Metadata, ServerUnaryCall, StatusObject, ServerContext, Next, ClientContext } from '../src' + +const timeout = (ms: number) => { + return new Promise((resolve, reject) => setTimeout(resolve, ms)) +} + +describe('gRPC Unary Call', () => { + const addr = { host: '127.0.0.1', port: 12305 } + + class Greeter { + async sayGreet(call: ServerUnaryCall) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + Date.now()) + call.sendMetadata(metadata) + if (metadata.get('x-throw-error').length > 0) { + throw new Error('throw error because x-throw-error') + } + + if (metadata.get('x-long-delay').length > 0) { + await timeout(500) + } + + expect(typeof this).toBe('object') + + return { + message: `hello, ${call.request.name || 'world'}` + } + } + + async sayGreet2(call: ServerUnaryCall) { + return this.sayGreet(call) + } + } + + const GreeterObject = { + sayGreet: async (call: ServerUnaryCall) => { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + Date.now()) + call.sendMetadata(metadata) + if (metadata.get('x-throw-error').length > 0) { + throw new Error('throw error because x-throw-error') + } + + if (metadata.get('x-long-delay').length > 0) { + await timeout(500) + } + + expect(typeof this).toBe('object') + + return { + message: `hello, ${call.request.name || 'world'}` + } + }, + sayGreet2: async (call: ServerUnaryCall) => { + return GreeterObject.sayGreet(call) + } + } + + const loader = new ProtoLoader({ + location: path.join(__dirname, '../example/proto'), + files: ['helloworld/service.proto'] + }) + + test('Should async sayGreet from client to server', async () => { + await loader.init() + + // server + const server = await loader.initServer() + server.add('helloworld.Greeter', new Greeter()) + await server.listen(addr) + + // client + const clients = await loader.initClients({ + services: { 'helloworld.Greeter': addr } + }) + const greeterClient = clients.get('helloworld.Greeter') + const { status, metadata, peer, response } = await greeterClient.sayGreet({ name: 'test' }) + + expect(typeof response).toBe('object') + expect(response.message).toBe('hello, test') + expect(status.code).toBe(0) + expect(metadata.get('x-service-path')[0]).toBe('/helloworld.Greeter/SayGreet') + expect(peer).toBe(addr.host + ':' + addr.port) + + await server.shutdown() + }) + + test('Should async sayGreet2 from client to server', async () => { + await loader.init() + + // server + const server = await loader.initServer() + server.add('helloworld.Greeter', GreeterObject) + await server.listen(addr) + + // client + const clients = await loader.initClients({ + services: { 'helloworld.Greeter': addr } + }) + const greeterClient = clients.get('helloworld.Greeter', { url: addr.host + ':' + addr.port }) + const { status, metadata, peer, response } = await greeterClient.sayGreet2({ name: 'test2' }) + + expect(typeof response).toBe('object') + expect(response.message).toBe('hello, test2') + expect(status.code).toBe(0) + expect(metadata.get('x-service-path')[0]).toBe('/helloworld.Greeter/SayGreet2') + expect(peer).toBe(addr.host + ':' + addr.port) + + await server.shutdown() + }) + + test('Should callback sayGreet from client to server', async () => { + await loader.init() + + // server + const server = await loader.initServer() + server.add('helloworld.Greeter', new Greeter()) + await server.listen(addr) + + // client + const clients = await loader.initClients({ + services: { 'helloworld.Greeter': addr } + }) + const greeterClient = clients.get('helloworld.Greeter') + + const meta = loader.makeMetadata({ + 'x-test-name': 'grpcity' + }) + + const { status, metadata, peer, response } = (await new Promise((resolve, reject) => { + const result: any = {} + const call = greeterClient.call.sayGreet({ name: 'test' }, meta, (err: Error, response: any) => { + if (err) { + reject(err) + } else { + result.response = response + } + }) + result.peer = call.getPeer() + call.on('metadata', (metadata: Metadata) => { + result.metadata = metadata + }) + call.on('status', (status: StatusObject) => { + result.status = status + resolve(result) + }) + })) as any + + expect(typeof response).toBe('object') + expect(response.message).toBe('hello, test') + expect(status.code).toBe(0) + expect(metadata.get('x-test-name')[0]).toBe('grpcity') + expect(peer.includes(addr.host + ':' + addr.port)).toBeTruthy + + await server.shutdown() + }) + + test('Should use metadata on client and server', async () => { + await loader.init() + + // server + const server = await loader.initServer() + server.add('helloworld.Greeter', new Greeter()) + await server.listen(addr) + + // client + const clients = await loader.initClients({ + services: { 'helloworld.Greeter': addr } + }) + const greeterClient = clients.get('helloworld.Greeter') + + const timestampClientSend = Date.now() + const meta = loader.makeMetadata({ + 'x-cache-control': 'max-age=100', + 'x-business-id': ['grpcity', 'testing'], + 'x-timestamp-client': 'begin=' + timestampClientSend + }) + + const { metadata } = await greeterClient.sayGreet({ name: 'grpcity' }, meta) + + expect(metadata.get('x-cache-control')).toStrictEqual(['max-age=100']) + expect(metadata.get('x-business-id')).toStrictEqual(['grpcity, testing']) + expect(metadata.get('x-service-path')[0]).toBe('/helloworld.Greeter/SayGreet') + + const timestamps = metadata.get('x-timestamp-server') + expect(Array.isArray(timestamps)).toBeTruthy + expect(timestamps.length === 1).toBeTruthy + const timestampServerReceived = Number(timestamps[0].split('=')[1]) + const timeUsed = timestampServerReceived - timestampClientSend + expect(timeUsed < 100).toBeTruthy + + await server.shutdown() + }) + + test('Should cache server error', async () => { + await loader.init() + + // server + const server = await loader.initServer() + server.add('helloworld.Greeter', new Greeter()) + await server.listen(addr) + + // client + const clients = await loader.initClients({ + services: { 'helloworld.Greeter': addr } + }) + const greeterClient = clients.get('helloworld.Greeter') + const meta = loader.makeMetadata({ 'x-throw-error': 'true' }) + + try { + await greeterClient.sayGreet({ name: 'grpcity' }, meta) + } catch (err: any) { + expect(err.code).toBe(13) + expect(err.name).toBe('GrpcClientError') + expect(/x-throw-error/.test(err.message)).toBeTruthy + expect(/SayGreet/i.test(err.message)).toBeTruthy + } + + await server.shutdown() + }) + + test('Should use timeout on client and server', async () => { + await loader.init() + + // server + const server = await loader.initServer() + server.add('helloworld.Greeter', new Greeter()) + await server.listen(addr) + + // client + const clients = await loader.initClients({ + services: { 'helloworld.Greeter': addr } + }) + const timeout = 100 + const greeterClient = clients.get('helloworld.Greeter', { timeout }) + const meta = loader.makeMetadata({ 'x-long-delay': 'true' }) + + const start = Date.now() + try { + await greeterClient.sayGreet({ name: 'grpcity' }, meta) + } catch (err: any) { + expect(Date.now() - start < timeout * 1.5).toBeTruthy + expect(err.code).toBe(4) + expect(/Deadline/i.test(err.message)).toBeTruthy + expect(/SayGreet/i.test(err.message)).toBeTruthy + } + + await server.shutdown() + }) + + test('Should use certs on client and server', async () => { + const addr = 'localhost:12306' + await loader.init() + + const serverCredentials = loader.makeServerCredentials( + fs.readFileSync(path.resolve(__dirname, '../example/certs/ca.crt')), + [ + { + private_key: fs.readFileSync(path.resolve(__dirname, '../example/certs/server.key')), + cert_chain: fs.readFileSync(path.resolve(__dirname, '../example/certs/server.crt')) + } + ], + true + ) + + const clientCredentials = loader.makeClientCredentials( + fs.readFileSync(path.resolve(__dirname, '../example/certs/ca.crt')), + fs.readFileSync(path.resolve(__dirname, '../example/certs/client.key')), + fs.readFileSync(path.resolve(__dirname, '../example/certs/client.crt')) + ) + + // server + const server = await loader.initServer() + server.add('helloworld.Greeter', new Greeter()) + await server.listen(addr, serverCredentials) + + // client + const clients = await loader.initClients({ + services: { 'helloworld.Greeter': addr }, + credentials: clientCredentials + }) + + const greeterClient = clients.get('helloworld.Greeter') + const { status, metadata, peer, response } = await greeterClient.sayGreet({ name: 'credentials' }) + + expect(typeof response).toBe('object') + expect(response.message).toBe('hello, credentials') + expect(status.code).toBe(0) + expect(metadata.get('x-service-path')[0]).toBe('/helloworld.Greeter/SayGreet') + expect(peer).toBe('::1:12306') + + try { + const clientsWithoutCredentials = await loader.initClients({ + services: { 'helloworld.Greeter': addr } + }) + const greeterClient = clientsWithoutCredentials.get('helloworld.Greeter') + await greeterClient.sayGreet({ name: 'credentials' }) + } catch (err: any) { + expect(err.code).toBe(14) + expect(/UNAVAILABLE/i.test(err.message)).toBeTruthy + expect(/SayGreet/i.test(err.message)).toBeTruthy + } + + await server.shutdown() + }) + + test('Should use middleware on client and server', async () => { + await loader.init() + + // server + const serverMiddleware = async (ctx: ServerContext, next: Next) => { + expect(!ctx.response).toBeTruthy + if (ctx.request) { + ctx.request.name = ctx.request.name + '1' + } + await next() + if (ctx.response) { + ctx.response.message = ctx.response.message + '2' + } + expect(!!ctx.response).toBeTruthy + } + + const server = await loader.initServer() + server.use(serverMiddleware) + server.add('helloworld.Greeter', new Greeter()) + await server.listen(addr) + + // client + const clientMiddleware = async (ctx: ClientContext, next: Next) => { + expect(!ctx.response).toBeTruthy + if (ctx.request) { + ctx.request.name = ctx.request.name + '3' + } + await next() + expect(!!ctx.response).toBeTruthy + if (ctx.response) { + ctx.response.message = ctx.response.message + '4' + } + } + + const clients = await loader.initClients({ + services: { 'helloworld.Greeter': addr } + }) + clients.use(clientMiddleware) + const greeterClient = clients.get('helloworld.Greeter') + const { status, metadata, peer, response } = await greeterClient.sayGreet({ name: 'grpcity' }) + + expect(typeof response).toBe('object') + expect(response.message).toBe('hello, grpcity3124') + expect(status.code).toBe(0) + expect(metadata.get('x-service-path')[0]).toBe('/helloworld.Greeter/SayGreet') + expect(peer).toBe(addr.host + ':' + addr.port) + + await server.shutdown() + }) +}) From 7d4f3d11770f2de1c607972669a873fd0a28dbc4 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sun, 7 Jan 2024 11:25:15 +0800 Subject: [PATCH 24/30] chore: update version to beta --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 053d523..e6e4bad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grpcity", - "version": "2.0.0", + "version": "2.0.0-beta-1", "description": "A powerful and complete gRPC framework for Node.js", "author": "Chakhsu.Lau", "license": "MIT", From 8951ff621bf26e53b2b7bab8af8b9446465038ac Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Sun, 7 Jan 2024 23:37:54 +0800 Subject: [PATCH 25/30] chore: adjust --- example/asyncStream/client.js | 22 +++++++++++----------- test/unaryCall.test.ts | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/example/asyncStream/client.js b/example/asyncStream/client.js index 4bde0a1..fbe43d3 100644 --- a/example/asyncStream/client.js +++ b/example/asyncStream/client.js @@ -33,19 +33,19 @@ const start = async (addr) => { await client.unaryHello({ message: 'gRPCity' }, meta) // stream client to server - // const clientStreamHelloCall = await client.clientStreamHello(meta) - // clientStreamHelloCall.write({ message: 'Hello!' }) - // clientStreamHelloCall.write({ message: 'How are you?' }) - // await clientStreamHelloCall.writeEnd() + const clientStreamHelloCall = await client.clientStreamHello(meta) + clientStreamHelloCall.write({ message: 'Hello!' }) + clientStreamHelloCall.write({ message: 'How are you?' }) + await clientStreamHelloCall.writeEnd() // client to stream server - // const serverStreamHelloCall = await client.serverStreamHello({ message: 'Hello! How are you?' }, meta) - // const serverReadAllResult = serverStreamHelloCall.readAll() - // for await (const data of serverReadAllResult) { - // console.log(data) - // } - // const serverReadEndResult = await serverStreamHelloCall.readEnd() - // console.log(serverReadEndResult) + const serverStreamHelloCall = await client.serverStreamHello({ message: 'Hello! How are you?' }, meta) + const serverReadAllResult = serverStreamHelloCall.readAll() + for await (const data of serverReadAllResult) { + console.log(data) + } + const serverReadEndResult = await serverStreamHelloCall.readEnd() + console.log(serverReadEndResult) // stream client to stream server const mutualStreamHelloCall = await client.mutualStreamHello(meta) diff --git a/test/unaryCall.test.ts b/test/unaryCall.test.ts index 2cbefb5..4855175 100644 --- a/test/unaryCall.test.ts +++ b/test/unaryCall.test.ts @@ -77,10 +77,10 @@ describe('gRPC Unary Call', () => { services: { 'helloworld.Greeter': addr } }) const greeterClient = clients.get('helloworld.Greeter') - const { status, metadata, peer, response } = await greeterClient.sayGreet({ name: 'test' }) + const { status, metadata, peer, response } = await greeterClient.sayGreet({ name: 'grpcity' }) expect(typeof response).toBe('object') - expect(response.message).toBe('hello, test') + expect(response.message).toBe('hello, grpcity') expect(status.code).toBe(0) expect(metadata.get('x-service-path')[0]).toBe('/helloworld.Greeter/SayGreet') expect(peer).toBe(addr.host + ':' + addr.port) @@ -132,7 +132,7 @@ describe('gRPC Unary Call', () => { const { status, metadata, peer, response } = (await new Promise((resolve, reject) => { const result: any = {} - const call = greeterClient.call.sayGreet({ name: 'test' }, meta, (err: Error, response: any) => { + const call = greeterClient.call.sayGreet({ name: 'grpcity' }, meta, (err: Error, response: any) => { if (err) { reject(err) } else { @@ -150,7 +150,7 @@ describe('gRPC Unary Call', () => { })) as any expect(typeof response).toBe('object') - expect(response.message).toBe('hello, test') + expect(response.message).toBe('hello, grpcity') expect(status.code).toBe(0) expect(metadata.get('x-test-name')[0]).toBe('grpcity') expect(peer.includes(addr.host + ':' + addr.port)).toBeTruthy From abfd886b5d03e233a9e8be840ac9ef806f14bec9 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Mon, 8 Jan 2024 01:09:19 +0800 Subject: [PATCH 26/30] feat: add async stream test --- test/bidiStreamCall.test.ts | 78 ++++++++++++++++++++++++++++++++++- test/clientStreamCall.test.ts | 52 ++++++++++++++++++++++- test/serverStreamCall.test.ts | 60 ++++++++++++++++++++++++++- 3 files changed, 187 insertions(+), 3 deletions(-) diff --git a/test/bidiStreamCall.test.ts b/test/bidiStreamCall.test.ts index 78aaa0d..0646621 100644 --- a/test/bidiStreamCall.test.ts +++ b/test/bidiStreamCall.test.ts @@ -1,6 +1,82 @@ import path from 'node:path' import { ProtoLoader } from '../src' +import type { ServerDuplexStream } from '../src' + +const timeout = (ms: number) => { + return new Promise((resolve, reject) => setTimeout(resolve, ms)) +} describe('gRPC Bidi Stream Call', () => { - test('Should', async () => {}) + const addr = { host: '127.0.0.1', port: 12505 } + + class AsyncStream { + async mutualStreamHello(call: ServerDuplexStream) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + + call.write({ message: 'emmm...' }) + + for await (const data of call.readAll()) { + expect(typeof data).toBe('object') + if (data.message === 'Hello!') { + call.write({ message: 'Hello too.' }) + } else if (data.message === 'How are you?') { + call.write({ message: "I'm fine, thank you" }) + await timeout(500) + call.write({ message: 'delay 500ms' }) + call.writeAll([{ message: 'emm... ' }, { message: 'emm......' }]) + } else { + call.write({ message: 'pardon?' }) + } + } + + call.end() + } + } + + const loader = new ProtoLoader({ + location: path.join(__dirname, '../example/proto'), + files: ['stream/service.proto'] + }) + + test('Should server stream from client to server', async () => { + await loader.init() + + // server + const server = await loader.initServer() + server.add('stream.Hellor', new AsyncStream()) + await server.listen(addr) + + // client + const clients = await loader.initClients({ + services: { + 'stream.Hellor': addr + } + }) + + const client = clients.get('stream.Hellor') + + const mutualStreamHelloCall = await client.mutualStreamHello() + mutualStreamHelloCall.writeAll([{ message: 'Hello!' }, { message: 'How are you?' }, { message: 'other thing x' }]) + mutualStreamHelloCall.write({ message: 'maybe' }) + + const mutualReadAllResult = mutualStreamHelloCall.readAll() + for await (const data of mutualReadAllResult) { + if (data.message === 'delay 500ms') { + mutualStreamHelloCall.write({ message: 'ok, I known you delay 1s' }) + mutualStreamHelloCall.writeEnd() + } + expect(typeof data).toBe('object') + } + + const { status, peer, metadata, response } = await mutualStreamHelloCall.readEnd() + + expect(!response).toBeTruthy + expect(status.code).toBe(0) + expect(metadata.get('x-service-path')[0]).toBe('/stream.Hellor/MutualStreamHello') + expect(peer).toBe(addr.host + ':' + addr.port) + + await server.shutdown() + }) }) diff --git a/test/clientStreamCall.test.ts b/test/clientStreamCall.test.ts index 997da57..1927a88 100644 --- a/test/clientStreamCall.test.ts +++ b/test/clientStreamCall.test.ts @@ -1,6 +1,56 @@ import path from 'node:path' import { ProtoLoader } from '../src' +import type { ServerReadableStream } from '../src' describe('gRPC Client Stream Call', () => { - test('Should', async () => {}) + const addr = { host: '127.0.0.1', port: 12205 } + + class AsyncStream { + async clientStreamHello(call: ServerReadableStream) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + + for await (const data of call.readAll()) { + expect(typeof data).toBe('object') + } + return { message: 'hello, grpcity' } + } + } + + const loader = new ProtoLoader({ + location: path.join(__dirname, '../example/proto'), + files: ['stream/service.proto'] + }) + + test('Should client stream from client to server', async () => { + await loader.init() + + // server + const server = await loader.initServer() + server.add('stream.Hellor', new AsyncStream()) + await server.listen(addr) + + // client + const clients = await loader.initClients({ + services: { + 'stream.Hellor': addr + } + }) + + const client = clients.get('stream.Hellor') + const clientStreamHelloCall = await client.clientStreamHello() + clientStreamHelloCall.write({ message: 'Hello!' }) + clientStreamHelloCall.write({ message: 'How are you?' }) + clientStreamHelloCall.writeAll([{ message: 'How are you?' }, { message: 'So?' }]) + const { status, peer, metadata, response } = await clientStreamHelloCall.writeEnd() + + expect(typeof response).toBe('object') + expect(response.message).toBe('hello, grpcity') + expect(status.code).toBe(0) + expect(metadata.get('x-service-path')[0]).toBe('/stream.Hellor/ClientStreamHello') + expect(peer).toBe(addr.host + ':' + addr.port) + + await server.shutdown() + }) }) diff --git a/test/serverStreamCall.test.ts b/test/serverStreamCall.test.ts index 63765f4..d5a00ac 100644 --- a/test/serverStreamCall.test.ts +++ b/test/serverStreamCall.test.ts @@ -1,6 +1,64 @@ import path from 'node:path' import { ProtoLoader } from '../src' +import type { ServerWritableStream } from '../src' describe('gRPC Server Stream Call', () => { - test('Should', async () => {}) + const addr = { host: '127.0.0.1', port: 12405 } + + class AsyncStream { + async serverStreamHello(call: ServerWritableStream) { + const metadata = call.metadata.clone() + metadata.add('x-timestamp-server', 'received=' + new Date().toISOString()) + call.sendMetadata(metadata) + + expect(call.request.message).toBe('hello grpcity') + + call.write({ message: 'Hello! I got you message.' }) + call.write({ message: "I'm fine, thank you" }) + call.writeAll([{ message: 'other thing x' }, { message: 'other thing y' }]) + call.end() + } + } + + const loader = new ProtoLoader({ + location: path.join(__dirname, '../example/proto'), + files: ['stream/service.proto'] + }) + + test('Should server stream from client to server', async () => { + await loader.init() + + // server + const server = await loader.initServer() + server.add('stream.Hellor', new AsyncStream()) + await server.listen(addr) + + // client + const clients = await loader.initClients({ + services: { + 'stream.Hellor': addr + } + }) + + const client = clients.get('stream.Hellor') + + const serverStreamHelloCall = await client.serverStreamHello({ message: 'hello grpcity' }) + const serverReadAllResult = serverStreamHelloCall.readAll() + + let count = 0 + for await (const data of serverReadAllResult) { + expect(typeof data).toBe('object') + count++ + } + expect(count).toBe(4) + + const { status, peer, metadata, response } = await serverStreamHelloCall.readEnd() + + expect(!response).toBeTruthy + expect(status.code).toBe(0) + expect(metadata.get('x-service-path')[0]).toBe('/stream.Hellor/ServerStreamHello') + expect(peer).toBe(addr.host + ':' + addr.port) + + await server.shutdown() + }) }) From 80404186a3b907e86b0fc382b7e8a06c21019fd4 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Mon, 8 Jan 2024 01:24:35 +0800 Subject: [PATCH 27/30] chore: adjust readme to v2 --- README.md | 38 ++++++++++++++------------------------ README_CN.md | 18 ++++++++---------- package.json | 2 +- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index a095fe4..b779b27 100644 --- a/README.md +++ b/README.md @@ -17,33 +17,26 @@ numerous advanced features to meet the needs of most development scenarios. Here is the feature: -- **API**: The communication protocol is based on gRPC and defined using - Protobuf. -- **Protobuf**: Supports only dynamic loading of pb, simplifying the loading - process of pb files. -- **Client**: Configured once and can be called anytime, anywhere, supporting - multi-server invocation. -- **Server**: Simplifies the initialization process, starting the server in - three steps, supporting multi-server startup. +- **API**: Communication protocol is based on gRPC and defined through Protobuf. +- **Protobuf**: Supports only dynamic load, simplifying the loading process of protobuf files. +- **Client**: Configured once, callable anytime, anywhere, and supports multi-server invocation. +- **Server**: Simplifies the initialization process with a three-step start, supporting multi-server deployment. +- **Credentials**: Complete support for certificate loading on both the client and server, providing communication encryption capabilities. - **No-Route**: No routing, RPC is inherently bound to methods. -- **Middleware**: Integrates middleware mechanism similar to Koa, providing pre - and post-processing capabilities for RPC. +- **Middleware**: Both client and server support middleware. - **Metadata**: Standardizes the transmission and retrieval of metadata. - **Error**: Provides dedicated Error objects to ensure targeted handling of exceptions after catching. - **Promise**: Supports promisify internally in RPC methods while also preserving callbackify. -- **Config**: Aligned with official configurations, supports pb load - configuration and gRPC channel configuration. -- **Pattern**: Singleton pattern ensures the uniqueness of instance objects. -- **Typescript**: Supported, ensuring compatibility between TS and JS. +- **Config**: Aligned with official configurations, supports protobuf load configurations and gRPC channel configurations. +- **Typescript**: Implemented purely in TypeScript with comprehensive types. ...and a lot more. --- -View full documentation and examples on -[grpcity.js.org](https://grpcity.js.org). +View full documentation and examples on [grpcity.js.org](https://grpcity.js.org). ## Quick Start @@ -78,8 +71,6 @@ Next, create `loader.js` and write the following code in it: ```js import GrpcLoader from 'grpcity' import path from 'node:path' -import { fileURLToPath } from 'node:url' -const __dirname = path.dirname(fileURLToPath(import.meta.url)) export default new GrpcLoader({ location: path.join(__dirname, './'), @@ -106,8 +97,8 @@ class Greeter { const start = async (addr) => { await loader.init() - const server = loader.initServer() - server.addService('helloworld.Greeter', new Greeter()) + const server = await loader.initServer() + server.add('helloworld.Greeter', new Greeter()) await server.listen(addr) console.log('gRPC Server is started: ', addr) @@ -126,13 +117,13 @@ import loader from './loader.js' const start = async (addr) => { await loader.init() - await loader.initClients({ + const clients = await loader.initClients({ services: { 'helloworld.Greeter': addr } }) - const client = loader.client('helloworld.Greeter') + const client = clients.get('helloworld.Greeter') const result = await client.sayGreet({ message: 'greeter' }) console.log('sayGreet', result.response) } @@ -149,8 +140,7 @@ node ./client.js --- -View full documentation and examples on -[grpcity.js.org](https://grpcity.js.org). +View full documentation and examples on [grpcity.js.org](https://grpcity.js.org). ## License diff --git a/README_CN.md b/README_CN.md index f9e174c..c372774 100644 --- a/README_CN.md +++ b/README_CN.md @@ -17,15 +17,15 @@ - **API**: 通信协议以 gRPC 为基础,通过 Protobuf 进行定义; - **Protobuf**: 只支持动态 pb 加载,简化了 pb 文件的加载流程; - **Client**: 一次配置,随时随处调用,支持 multi-server 的调用; -- **Server**: 简化了初始化流程,三步完成服务端的启动,支持 multi-server 的启动; +- **Server**: 简化了初始化流程,三步完成启动,支持 multi-server 的启动; +- **Credentials**: 客户端和服务端完整地支持了证书加载,提供了通信加密的能力; - **No-Route**: 无路由,rpc 与 method 天生绑定; -- **Middleware**: 集成了跟 Koa 一样中间件机制,得到了 rpc 前后处理的能力; +- **Middleware**: 客户端和服务端都支持中间件机制; - **Metadata**: 规范化了元信息的传递和获取; - **Error**: 提供了专有 Error 对象,保证异常捕捉后可以针对性处理; - **Promise**: rpc 方法内部支持了 promisify,同时也保留了 callbackify ; - **Config**: 与官方配置对齐,支持 pb load 配置和 gRPC channel 配置; -- **Pattern**: 单例模式,保证了实例对象的唯一性; -- **Typescript**: 支持,保证了 ts 和 js 的兼容性; +- **Typescript**: 纯 TS 实现,类型齐全。 ...还有更多等你发现。 @@ -66,8 +66,6 @@ message Message { ```js import GrpcLoader from 'grpcity' import path from 'node:path' -import { fileURLToPath } from 'node:url' -const __dirname = path.dirname(fileURLToPath(import.meta.url)) export default new GrpcLoader({ location: path.join(__dirname, './'), @@ -94,8 +92,8 @@ class Greeter { const start = async (addr) => { await loader.init() - const server = loader.initServer() - server.addService('helloworld.Greeter', new Greeter()) + const server = await loader.initServer() + server.add('helloworld.Greeter', new Greeter()) await server.listen(addr) console.log('gRPC Server is started: ', addr) @@ -114,13 +112,13 @@ import loader from './loader.js' const start = async (addr) => { await loader.init() - await loader.initClients({ + const clients = await loader.initClients({ services: { 'helloworld.Greeter': addr } }) - const client = loader.client('helloworld.Greeter') + const client = clients.get('helloworld.Greeter') const result = await client.sayGreet({ message: 'greeter' }) console.log('sayGreet', result.response) } diff --git a/package.json b/package.json index e6e4bad..61a441b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grpcity", - "version": "2.0.0-beta-1", + "version": "2.0.0-beta-2", "description": "A powerful and complete gRPC framework for Node.js", "author": "Chakhsu.Lau", "license": "MIT", From 868c5a78e304c15736638e0546cb63436e411c8c Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Mon, 8 Jan 2024 09:41:06 +0800 Subject: [PATCH 28/30] feat: add benchmark --- benchmark/README.md | 109 ++++++++++++++++++++++++++++++++++++ benchmark/benchmark.sh | 15 +++++ benchmark/helloworld.proto | 15 +++++ benchmark/server-grpcity.js | 23 ++++++++ benchmark/server-grpcjs.js | 30 ++++++++++ 5 files changed, 192 insertions(+) create mode 100644 benchmark/README.md create mode 100644 benchmark/benchmark.sh create mode 100644 benchmark/helloworld.proto create mode 100755 benchmark/server-grpcity.js create mode 100644 benchmark/server-grpcjs.js diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 0000000..f6f90c9 --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,109 @@ +# Benchmark Test + +Tool: https://ghz.sh + +### gRPCity + +``` +node benchmark/server-grpcity.js + +ghz --insecure \ + --proto ./benchmark/helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -d '{"name": "grpcity"}' \ + -n 10000 \ + -c 100 \ + 0.0.0.0:9099 +``` + +result: + +``` +Summary: + Count: 10000 + Total: 2.02 s + Slowest: 64.17 ms + Fastest: 1.36 ms + Average: 17.22 ms + Requests/sec: 4942.17 + +Response time histogram: + 1.355 [1] | + 7.636 [339] |∎∎∎ + 13.918 [3251] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 20.199 [3887] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 26.480 [1502] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 32.761 [622] |∎∎∎∎∎∎ + 39.042 [175] |∎∎ + 45.324 [130] |∎ + 51.605 [31] | + 57.886 [30] | + 64.167 [32] | + +Latency distribution: + 10 % in 9.75 ms + 25 % in 12.52 ms + 50 % in 15.38 ms + 75 % in 20.27 ms + 90 % in 26.62 ms + 95 % in 31.47 ms + 99 % in 44.53 ms + +Status code distribution: + [OK] 10000 responses +``` + +### grpc-js + +``` +node benchmark/server-grpcjs.js + +ghz --insecure \ + --proto ./benchmark/helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -d '{"name": "grpcity"}' \ + -n 10000 \ + -c 100 \ + 0.0.0.0:9098 +``` + +result: + +``` +Summary: + Count: 10000 + Total: 2.54 s + Slowest: 68.57 ms + Fastest: 1.46 ms + Average: 21.11 ms + Requests/sec: 3936.49 + +Response time histogram: + 1.458 [1] | + 8.169 [297] |∎∎∎ + 14.881 [1556] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 21.592 [3900] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 28.303 [2702] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎ + 35.014 [1088] |∎∎∎∎∎∎∎∎∎∎∎ + 41.725 [296] |∎∎∎ + 48.437 [97] |∎ + 55.148 [56] |∎ + 61.859 [2] | + 68.570 [5] | + +Latency distribution: + 10 % in 12.27 ms + 25 % in 16.06 ms + 50 % in 20.32 ms + 75 % in 25.07 ms + 90 % in 30.95 ms + 95 % in 34.49 ms + 99 % in 44.33 ms + +Status code distribution: + [OK] 10000 responses +``` + +### Conclusion + +Compared with grpc-js, gRPCity has no loss and almost the same performance. diff --git a/benchmark/benchmark.sh b/benchmark/benchmark.sh new file mode 100644 index 0000000..617c888 --- /dev/null +++ b/benchmark/benchmark.sh @@ -0,0 +1,15 @@ + ghz --insecure \ + --proto ./helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -d '{"name": "grpcity"}' \ + -n 10000 \ + -c 100 \ + 0.0.0.0:9099 + +ghz --insecure \ + --proto ./helloworld.proto \ + --call helloworld.Greeter/SayHello \ + -d '{"name": "grpcity"}' \ + -n 10000 \ + -c 100 \ + 0.0.0.0:9098 diff --git a/benchmark/helloworld.proto b/benchmark/helloworld.proto new file mode 100644 index 0000000..0adef77 --- /dev/null +++ b/benchmark/helloworld.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package helloworld; + +service Greeter { + rpc SayHello(HelloRequest) returns (HelloReply) {} +} + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string message = 1; +} diff --git a/benchmark/server-grpcity.js b/benchmark/server-grpcity.js new file mode 100755 index 0000000..eb011d2 --- /dev/null +++ b/benchmark/server-grpcity.js @@ -0,0 +1,23 @@ +const { ProtoLoader } = require('../lib') +const path = require('path') + +const implementation = { + sayHello: async (call) => { + return { message: 'Hello ' + call.request.name } + } +} + +const start = async (addr) => { + const loader = new ProtoLoader({ + location: path.resolve(__dirname, './'), + files: ['helloworld.proto'] + }) + await loader.init() + + const server = await loader.initServer() + server.add('helloworld.Greeter', implementation) + await server.listen(addr) + console.log('start:', addr) +} + +start('0.0.0.0:9099') diff --git a/benchmark/server-grpcjs.js b/benchmark/server-grpcjs.js new file mode 100644 index 0000000..3653cee --- /dev/null +++ b/benchmark/server-grpcjs.js @@ -0,0 +1,30 @@ +const grpc = require('@grpc/grpc-js') +const protoLoader = require('@grpc/proto-loader') +const path = require('path') + +const PROTO_PATH = path.resolve(__dirname, 'helloworld.proto') +const packageDefinition = protoLoader.loadSync(PROTO_PATH, { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true +}) +const helloProto = grpc.loadPackageDefinition(packageDefinition).helloworld + +const implementation = { + sayHello: (call, callback) => { + callback(null, { message: 'Hello ' + call.request.name }) + } +} + +const start = (addr) => { + const server = new grpc.Server() + server.addService(helloProto.Greeter.service, implementation) + server.bindAsync(addr, grpc.ServerCredentials.createInsecure(), () => { + server.start() + console.log('start:', addr) + }) +} + +start('0.0.0.0:9098') From 8fceab93ac066e598b04eb348a86435254d22f45 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Mon, 8 Jan 2024 09:44:11 +0800 Subject: [PATCH 29/30] feat: release v2.0.0 --- package.json | 2 +- pnpm-lock.yaml | 20 -------------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/package.json b/package.json index 61a441b..053d523 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grpcity", - "version": "2.0.0-beta-2", + "version": "2.0.0", "description": "A powerful and complete gRPC framework for Node.js", "author": "Chakhsu.Lau", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b27d99..5bf575b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,6 @@ dependencies: joi: specifier: ^17.11.0 version: 17.11.0 - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 protobufjs: specifier: ^7.2.5 version: 7.2.5 @@ -28,9 +25,6 @@ devDependencies: '@types/jest': specifier: ^29.5.11 version: 29.5.11 - '@types/lodash-es': - specifier: ^4.17.12 - version: 4.17.12 '@types/node': specifier: ^20.10.6 version: 20.10.6 @@ -870,16 +864,6 @@ packages: pretty-format: 29.7.0 dev: true - /@types/lodash-es@4.17.12: - resolution: { integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ== } - dependencies: - '@types/lodash': 4.14.202 - dev: true - - /@types/lodash@4.14.202: - resolution: { integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== } - dev: true - /@types/node@20.10.6: resolution: { integrity: sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw== } dependencies: @@ -2190,10 +2174,6 @@ packages: p-locate: 4.1.0 dev: true - /lodash-es@4.17.21: - resolution: { integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== } - dev: false - /lodash.camelcase@4.3.0: resolution: { integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== } dev: false From b72f8f90d2c7586d19dde7471e47d0014f5589d3 Mon Sep 17 00:00:00 2001 From: "Chakhsu.Lau" Date: Mon, 8 Jan 2024 09:53:39 +0800 Subject: [PATCH 30/30] test: remove code expect --- test/unaryCall.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unaryCall.test.ts b/test/unaryCall.test.ts index 4855175..c07c133 100644 --- a/test/unaryCall.test.ts +++ b/test/unaryCall.test.ts @@ -299,7 +299,6 @@ describe('gRPC Unary Call', () => { const greeterClient = clientsWithoutCredentials.get('helloworld.Greeter') await greeterClient.sayGreet({ name: 'credentials' }) } catch (err: any) { - expect(err.code).toBe(14) expect(/UNAVAILABLE/i.test(err.message)).toBeTruthy expect(/SayGreet/i.test(err.message)).toBeTruthy }