commit da8024fc30bf04821e08991e88cd81069b7840a7 Author: JianMiau Date: Fri Apr 29 15:25:10 2022 +0800 [add] first diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54512fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +dist +node_modules +yarn.lock +logs +.rpt2_cache +.nyc_output +coverage +docs +temp +lib +.ds_store \ No newline at end of file diff --git a/.mocharc.js b/.mocharc.js new file mode 100644 index 0000000..034220e --- /dev/null +++ b/.mocharc.js @@ -0,0 +1,22 @@ +module.exports = { + require: [ + 'ts-node/register', + './test/Base.ts' + ], + exit: true, + timeout: 999999, + 'preserve-symlinks': true, + spec: [ + './test/cases/http.test.ts', + './test/cases/httpJSON.test.ts', + './test/cases/ws.test.ts', + './test/cases/wsJSON.test.ts', + './test/cases/inner.test.ts', + './test/cases/inputJSON.test.ts', + './test/cases/inputBuffer.test.ts', + ], + // parallel: false, + + // 'expose-gc': true, + // fgrep: 'throw type error in server' +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c77bfcb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,34 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "mocha current file", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "args": [ + "${file}" + ], + "internalConsoleOptions": "openOnSessionStart", + "cwd": "${workspaceFolder}" + }, + { + "type": "node", + "request": "launch", + "name": "ts-node current file", + "protocol": "inspector", + "args": [ + "${relativeFile}" + ], + "cwd": "${workspaceRoot}", + "runtimeArgs": [ + "-r", + "ts-node/register" + ], + "internalConsoleOptions": "openOnSessionStart" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..00ad71f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules\\typescript\\lib" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f16c3cd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,161 @@ +# CHANGELOG + +## [3.3.1-dev.0] - 2022-04-27 +### Fixed +- `HttpConnection.status` not correct when request aborted by client + +## [3.3.0] - 2022-04-15 +### Added +- Builtin heartbeat support +- New options `logLevel` +### Fixed +- Add response header `Content-Type: application/json; charset=utf-8` for JSON mode under HttpServer, to fix the decoding issue in Chrome dev tools. + +## [3.2.5] - 2022-04-12 +### Added +- New server options `corsMaxAge` to optimized preflight requests, default value is 3600. +### Fixed +- `NonNullable` cannot be encoded and decoded when as a property in interface + +## [3.2.3] - 2022-03-25 +### Added +- Print debug-level log when "pre flow" is canceled +### Changed +- Log `[ResErr]` renamed to `[ApiErr]` to consist with client's. +- Log `ApiRes` and `ApiErr` once they are ready to send, instead of after send them. +### Fixed +- When `preSendDataFlow` return undefined, do not send "Internal Server Error". +- Remove some unused code. + +## [3.2.2] - 2022-03-22 +### Fixed +- `postDisconnectFlow` not executed when `disconnect()` manually + + +## [3.2.1] - 2022-03-21 +### Added +- `preRecvDataFlow` add param `serviceName` +- Support change `dataType` in `postConnectFlow` +### Fixed +- Remark text error + +## [3.2.0] - 2022-02-26 +### Added +- Support using `keyof` +- Support type alias and `keyof` in `Pick` and `Omit` +- Support `Pick` and `Omit` +- Support `interface` extends Mapped Type, like `Pick` `Omit` +- Support `Pick` +- Support `Pick` +- Support `Pick` and `Pick`, the same to `Omit` +- Support reference enum value as literal type,like: + ```ts + export enum Types { + Type1, + Type2 + } + export interface Obj { + type: Types.Type1, + value: string + } + ``` +### Changed +- `SchemaType` switched to class + +## [3.1.9] - 2022-01-12 +### Added +- `mongodb-polyfill.d.ts` to fixed mongodb type bug. + +## [3.1.6] - 2021-12-29 +### Changed +- Return request type error detail when using JSON + +## [3.1.5] - 2021-12-23 +### Fixed +- Optimize aliyun FC support of `server.inputJSON` + +## [3.1.4] - 2021-12-18 +### Added +- `WsServer` now support client use `buffer` as transfering format when server set `json: true` +### Fixed +- Type error when disable `skipLibChecks` +- Cannot resolve JSON when `headers` is `application/json; charset=utf-8` +- Cannot resolve serviceName when there is query string in the URL + +## [3.1.3] - 2021-12-04 +### Added +- `conn.listenMsg` +### Fixed +- Do not `broadcastMsg` when `conns.length` is `0` + +## [3.1.2] - 2021-11-17 +### Added +- `server.inputJSON` and `server.inputBuffer` +- Add new dataType `json` + +## [3.1.1] - 2021-11-09 +### Added +- HTTP Text 传输模式下,区分 HTTP 状态码返回,不再统一返回 200 + +## [3.1.0] - 2021-11-08 +### Added +- WebSocket 支持 JSON 格式传输 +- JSON 格式传输支持 `ArrayBuffer`、`Date`、`ObjectId`,自动根据协议编解码为 `string` +### Changed +- `jsonEnabled` -> `json` + +## [3.0.14] - 2021-10-25 +### Added +- 增加 `server.autoImplementApi` 第二个参数 `delay`,用于延迟自动协议注册,加快冷启动速度。 + +## [3.0.13] - 2021-10-22 +### Added +- 增加 `server.callApi` 的支持,以更方便的适配 Serverless 云函数等自定义传输场景。 + +## [3.0.12] - 2021-10-22 +### Fixed +- 修复 `WsServer` 客户端断开连接后,日志显示的 `ActiveConn` 总是比实际多 1 的 BUG + +## [3.0.11] - 2021-10-18 +### Added +- 增加对 `mongodb/ObjectId` 的支持 + +## [3.0.10] - 2021-10-13 +### Changed +- `BaseConnection` 泛型参数默认为 `any`,便于扩展类型 +- `HttpClient` and `WsClient` no longer have default type param + +## [3.0.9] - 2021-10-06 +### Changed +- `strictNullChecks` 默认改为 `false` + +## [3.0.8] - 2021-10-06 +### Added +- Optimize log level + +## [3.0.7] - 2021-10-06 +### Added +- Optimize log color +## [3.0.6] - 2021-09-30 +### Added +- "Server started at ..." 前增加 "ERROR:X API registered failed." +### Changed +- `HttpServer.onInputBufferError` 改为 `call.error('InputBufferError')` +- 替换 `colors` 为 `chalk` + +## [3.0.5] - 2021-08-14 +### Added +- Optimize log for `sendMsg` and `broadcastMsg` +- Return `Internal Server Error` when `SendReturnErr` occured + +### Changed +- Remove error `API not return anything` +- handler of `client.listenMsg` changed to `(msg, msgName, client)=>void` + +### Fixed +- NodeJS 12 compability issue (`Uint8Array` and `Buffer` is not treated samely) + +## [3.0.3] - 2021-06-27 + +### Added +- `server.listenMsg` would return `handler` that passed in \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2cb5e68 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) King Wang. https://github.com/k8w + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a8c3dfb --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# TSRPC + +EN / [中文](https://tsrpc.cn/docs/introduction.html) + +A TypeScript RPC framework with runtime type checking and binary serialization. + +Official site: https://tsrpc.cn (English version is on the way) + +## Features +- Runtime type checking +- Binary serialization +- Pure TypeScript, without any decorater or other language +- HTTP / WebSocket / and more protocols... +- Optional backward-compatibility to JSON +- High performance and reliable, verified by services over 100,000,000 users + +## Create Full-stack Project +``` +npx create-tsrpc-app@latest +``` + +## Usage + +### Define Protocol (Shared) +```ts +export interface ReqHello { + name: string; +} + +export interface ResHello { + reply: string; +} +``` + +### Implement API (Server) +```ts +import { ApiCall } from "tsrpc"; + +export async function ApiHello(call: ApiCall) { + call.succ({ + reply: 'Hello, ' + call.req.name + }); +} +``` + +### Call API (Client) +```ts +let ret = await client.callApi('Hello', { + name: 'World' +}); +``` + +## Examples + +https://github.com/k8w/tsrpc-examples + +## Serialization Algorithm +The best TypeScript serialization algorithm ever. +Without any 3rd-party IDL language (like protobuf), it is fully based on TypeScript source file. Define the protocols directly by your code. + +This is powered by [TSBuffer](https://github.com/tsbuffer), which is going to be open-source. + +TypeScript has the best type system, with some unique advanced features like union type, intersection type, mapped type, etc. + +TSBuffer may be the only serialization algorithm that support them all. + + + +## API Reference +See [API Reference](./docs/api/tsrpc.md). \ No newline at end of file diff --git a/api-extractor.json b/api-extractor.json new file mode 100644 index 0000000..f563567 --- /dev/null +++ b/api-extractor.json @@ -0,0 +1,345 @@ +/** + * Config file for API Extractor. For more info, please visit: https://api-extractor.com + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + /** + * Optionally specifies another JSON config file that this file extends from. This provides a way for + * standard settings to be shared across multiple projects. + * + * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains + * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be + * resolved using NodeJS require(). + * + * SUPPORTED TOKENS: none + * DEFAULT VALUE: "" + */ + // "extends": "./shared/api-extractor-base.json" + // "extends": "my-package/include/api-extractor-base.json" + /** + * Determines the "" token that can be used with other config file settings. The project folder + * typically contains the tsconfig.json and package.json config files, but the path is user-defined. + * + * The path is resolved relative to the folder of the config file that contains the setting. + * + * The default value for "projectFolder" is the token "", which means the folder is determined by traversing + * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder + * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error + * will be reported. + * + * SUPPORTED TOKENS: + * DEFAULT VALUE: "" + */ + // "projectFolder": "..", + /** + * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor + * analyzes the symbols exported by this module. + * + * The file extension must be ".d.ts" and not ".ts". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + */ + "mainEntryPointFilePath": "/lib/index.d.ts", + /** + * A list of NPM package names whose exports should be treated as part of this package. + * + * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", + * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part + * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly + * imports library2. To avoid this, we can specify: + * + * "bundledPackages": [ "library2" ], + * + * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been + * local files for library1. + */ + "bundledPackages": [], + /** + * Determines how the TypeScript compiler engine will be invoked by API Extractor. + */ + "compiler": { + /** + * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * Note: This setting will be ignored if "overrideTsconfig" is used. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/tsconfig.json" + */ + // "tsconfigFilePath": "/tsconfig.json", + /** + * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. + * The object must conform to the TypeScript tsconfig schema: + * + * http://json.schemastore.org/tsconfig + * + * If omitted, then the tsconfig.json file will be read from the "projectFolder". + * + * DEFAULT VALUE: no overrideTsconfig section + */ + // "overrideTsconfig": { + // . . . + // } + /** + * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended + * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when + * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses + * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. + * + * DEFAULT VALUE: false + */ + // "skipLibCheck": true, + }, + /** + * Configures how the API report file (*.api.md) will be generated. + */ + "apiReport": { + /** + * (REQUIRED) Whether to generate an API report. + */ + "enabled": false + /** + * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce + * a full file path. + * + * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". + * + * SUPPORTED TOKENS: , + * DEFAULT VALUE: ".api.md" + */ + // "reportFileName": ".api.md", + /** + * Specifies the folder where the API report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, + * e.g. for an API review. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/etc/" + */ + // "reportFolder": "/etc/", + /** + * Specifies the folder where the temporary report file is written. The file name portion is determined by + * the "reportFileName" setting. + * + * After the temporary file is written to disk, it is compared with the file in the "reportFolder". + * If they are different, a production build will fail. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/" + */ + // "reportTempFolder": "/temp/" + }, + /** + * Configures how the doc model file (*.api.json) will be generated. + */ + "docModel": { + /** + * (REQUIRED) Whether to generate a doc model file. + */ + "enabled": true + /** + * The output path for the doc model file. The file extension should be ".api.json". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/temp/.api.json" + */ + // "apiJsonFilePath": "/temp/.api.json" + }, + /** + * Configures how the .d.ts rollup file will be generated. + */ + "dtsRollup": { + /** + * (REQUIRED) Whether to generate the .d.ts rollup file. + */ + "enabled": true, + /** + * Specifies the output path for a .d.ts rollup file to be generated without any trimming. + * This file will include all declarations that are exported by the main entry point. + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "/dist/.d.ts" + */ + "untrimmedFilePath": "", + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. + * This file will include only declarations that are marked as "@public" or "@beta". + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "betaTrimmedFilePath": "/dist/-beta.d.ts", + /** + * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. + * This file will include only declarations that are marked as "@public". + * + * If the path is an empty string, then this file will not be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + "publicTrimmedFilePath": "/dist/index.d.ts", + /** + * When a declaration is trimmed, by default it will be replaced by a code comment such as + * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the + * declaration completely. + * + * DEFAULT VALUE: false + */ + "omitTrimmingComments": true + }, + /** + * Configures how the tsdoc-metadata.json file will be generated. + */ + "tsdocMetadata": { + /** + * Whether to generate the tsdoc-metadata.json file. + * + * DEFAULT VALUE: true + */ + "enabled": false + /** + * Specifies where the TSDoc metadata file should be written. + * + * The path is resolved relative to the folder of the config file that contains the setting; to change this, + * prepend a folder token such as "". + * + * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", + * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup + * falls back to "tsdoc-metadata.json" in the package folder. + * + * SUPPORTED TOKENS: , , + * DEFAULT VALUE: "" + */ + // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" + }, + /** + * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files + * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. + * To use the OS's default newline kind, specify "os". + * + * DEFAULT VALUE: "crlf" + */ + // "newlineKind": "crlf", + /** + * Configures how API Extractor reports error and warning messages produced during analysis. + * + * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. + */ + "messages": { + /** + * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing + * the input .d.ts files. + * + * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "compilerMessageReporting": { + /** + * Configures the default routing for messages that don't match an explicit rule in this table. + */ + "default": { + /** + * Specifies whether the message should be written to the the tool's output log. Note that + * the "addToApiReportFile" property may supersede this option. + * + * Possible values: "error", "warning", "none" + * + * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail + * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes + * the "--local" option), the warning is displayed but the build will not fail. + * + * DEFAULT VALUE: "warning" + */ + "logLevel": "warning" + /** + * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), + * then the message will be written inside that file; otherwise, the message is instead logged according to + * the "logLevel" option. + * + * DEFAULT VALUE: false + */ + // "addToApiReportFile": false + } + // "TS2551": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + /** + * Configures handling of messages reported by API Extractor during its analysis. + * + * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" + * + * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings + */ + "extractorMessageReporting": { + "default": { + "logLevel": "warning" + // "addToApiReportFile": false + }, + "ae-missing-release-tag": { + "logLevel": "none" + } + // "ae-extra-release-tag": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + }, + /** + * Configures handling of messages reported by the TSDoc parser when analyzing code comments. + * + * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" + * + * DEFAULT VALUE: A single "default" entry with logLevel=warning. + */ + "tsdocMessageReporting": { + "default": { + "logLevel": "warning" + // "addToApiReportFile": false + }, + "tsdoc-param-tag-missing-hyphen": { + "logLevel": "none" + } + // "tsdoc-link-tag-unescaped-text": { + // "logLevel": "warning", + // "addToApiReportFile": true + // }, + // + // . . . + } + } +} \ No newline at end of file diff --git a/benchmark/config/BenchmarkConfig.ts b/benchmark/config/BenchmarkConfig.ts new file mode 100644 index 0000000..9f24144 --- /dev/null +++ b/benchmark/config/BenchmarkConfig.ts @@ -0,0 +1,13 @@ +export const benchmarkConfig = { + /** 压测使用的APIServer */ + server: 'http://127.0.0.1:3000', + + /** 一共运行几次压测事务 */ + total: 200000, + /** 同时并发的请求数量 */ + concurrency: 100, + /** API请求的超时时间(超时将断开HTTP连接,释放资源,前端默认为10) */ + timeout: 10000, + /** 是否将错误的详情日志打印到Log */ + showError: false +} \ No newline at end of file diff --git a/benchmark/http.ts b/benchmark/http.ts new file mode 100644 index 0000000..a20dbaa --- /dev/null +++ b/benchmark/http.ts @@ -0,0 +1,13 @@ +import { benchmarkConfig } from './config/BenchmarkConfig'; +import { HttpRunner } from './models/HTTPRunner'; + +const req = { + a: 123456, + b: 'Hello, World!', + c: true, + d: new Uint8Array(100000) +} + +new HttpRunner(async function () { + await this.callApi('Test', req); +}, benchmarkConfig).start(); \ No newline at end of file diff --git a/benchmark/models/HTTPRunner.ts b/benchmark/models/HTTPRunner.ts new file mode 100644 index 0000000..00a233f --- /dev/null +++ b/benchmark/models/HTTPRunner.ts @@ -0,0 +1,285 @@ +import 'colors'; +import * as http from "http"; +import * as https from "https"; +import 'k8w-extend-native'; +import { TsrpcError, TsrpcErrorType } from "tsrpc-proto"; +import { HttpClient } from '../../src/client/http/HttpClient'; +import { benchmarkConfig } from "../config/BenchmarkConfig"; +import { serviceProto } from '../protocols/proto'; + +export interface HttpRunnerConfig { + total: number; + concurrency: number; + showError?: boolean; +} + +export class HttpRunner { + + private _config: HttpRunnerConfig; + + // 执行单个事务的方法 + private _single: (this: HttpRunner) => Promise; + + // 执行进度信息 + private _progress?: { + startTime: number, + lastSuccTime?: number, + started: number, + finished: number, + succ: number, + fail: number + }; + + constructor(single: HttpRunner['_single'], config: HttpRunnerConfig) { + this._single = single.bind(this); + this._config = config; + } + + start() { + this._progress = { + startTime: Date.now(), + started: 0, + finished: 0, + succ: 0, + fail: 0 + } + + // 启动并发 + for (let i = 0; i < this._config.concurrency; ++i) { + this._doTrans(); + } + + console.log('Benchmark start!'); + this._startReport(); + } + + private _doTrans() { + if (this._isStoped || !this._progress) { + return; + } + + if (this._progress.started < this._config.total) { + ++this._progress.started; + let startTime = Date.now(); + this._single().then(v => { + ++this._progress!.succ; + this._progress!.lastSuccTime = Date.now(); + }).catch(e => { + ++this._progress!.fail; + if (this._config.showError) { + console.error('[Error]', e.message); + } + }).then(() => { + ++this._progress!.finished; + if (this._progress!.finished === this._config.total) { + this._finish(); + } + else { + this._doTrans(); + } + }) + } + } + + private _reportInterval?: NodeJS.Timeout; + private _startReport() { + this._reportInterval = setInterval(() => { + this._report(); + }, 1000) + } + + private _isStoped = false; + stop() { + this._isStoped = true; + } + + private _finish() { + if (!this._progress) { + return; + } + + this._reportInterval && clearInterval(this._reportInterval); + + console.log('\n\n-------------------------------\n Benchmark finished! \n-------------------------------'); + + let usedTime = Date.now() - this._progress.startTime; + console.log(` Transaction Execution Result `.bgBlue.white); + console.log(`Started=${this._progress.started}, Finished=${this._progress.finished}, UsedTime=${usedTime}ms`.green); + console.log(`Succ=${this._progress.succ}, Fail=${this._progress.fail}, TPS=${this._progress.succ / (this._progress.lastSuccTime! - this._progress.startTime) * 1000 | 0}\n`.green) + + // TIME TPS(完成的) + console.log(` API Execution Result `.bgBlue.white); + + // [KEY] RPS(完成的) AVG P95 P99 + for (let key in this._apiStat) { + let stat = this._apiStat[key]; + stat.resTime = stat.resTime.orderBy(v => v); + + let send = stat.sendReq; + let succ = stat.resTime.length; + let netErr = stat.networkError; + let apiErr = stat.otherError; + let avg = stat.resTime[stat.resTime.length >> 1] | 0; + let p95 = stat.resTime[stat.resTime.length * 0.95 | 0] | 0; + let p99 = stat.resTime[stat.resTime.length * 0.99 | 0] | 0; + + this._logTable([ + [{ text: 'Api' + key + ' '.repeat(this._maxApiNameLength - key.length), color: 'green' }, 'Send', 'Succ', 'QPS', 'NetErr', 'ApiErr', 'AVG ', 'P95 ', 'P99 '], + ['', '' + send, + { text: '' + succ, color: 'green' }, + { text: '' + (succ / (stat.last.succTime - stat.startTime) * 1000 | 0), color: 'green' }, + netErr ? { text: '' + netErr, color: 'red' } : '0', + apiErr ? { text: '' + apiErr, color: 'red' } : '0', + { text: avg ? avg + 'ms' : '-', color: 'yellow' }, + { text: p95 ? p95 + 'ms' : '-', color: 'yellow' }, + { text: p99 ? p99 + 'ms' : '-', color: 'yellow' } + ] + ]) + } + } + + private _apiStat: { + [key: string]: { + sendReq: number, + resTime: number[], + succ: number, + networkError: number, + otherError: number, + startTime: number, + last: { + sendReq: number, + resTime: number[], + succ: number, + networkError: number, + otherError: number, + startTime: number, + succTime: number + } + } + } = {}; + + private _maxApiNameLength = 0; + /** + * callApi 并且计入统计 + */ + callApi: typeof benchmarkClient.callApi = async (apiName, req) => { + this._maxApiNameLength = Math.max(apiName.length, this._maxApiNameLength); + + if (!this._apiStat[apiName]) { + this._apiStat[apiName] = { + sendReq: 0, + resTime: [], + succ: 0, + networkError: 0, + otherError: 0, + startTime: Date.now(), + last: { + sendReq: 0, + resTime: [], + succ: 0, + networkError: 0, + otherError: 0, + startTime: Date.now(), + succTime: 0 + } + }; + } + + ++this._apiStat[apiName].sendReq; + ++this._apiStat[apiName].last.sendReq; + + let startTime = Date.now(); + let ret = await benchmarkClient.callApi(apiName, req); + + if (ret.isSucc) { + this._apiStat[apiName].last.succTime = Date.now(); + this._apiStat[apiName].resTime.push(Date.now() - startTime); + this._apiStat[apiName].last.resTime.push(Date.now() - startTime); + ++this._apiStat[apiName].succ; + ++this._apiStat[apiName].last.succ; + } + else { + if (ret.err.type === TsrpcErrorType.NetworkError) { + ++this._apiStat[apiName].networkError; + ++this._apiStat[apiName].last.networkError; + } + else { + ++this._apiStat[apiName].otherError; + ++this._apiStat[apiName].last.otherError; + } + } + + return ret; + } + + private _report() { + console.log(new Date().format('hh:mm:ss').gray, `Started=${this._progress!.started}/${this._config.total}, Finished=${this._progress!.finished}, Succ=${this._progress!.succ.toString().green}, Fail=${this._progress!.fail.toString()[this._progress!.fail > 0 ? 'red' : 'white']}`, + this._progress!.lastSuccTime ? `TPS=${this._progress!.succ / (this._progress!.lastSuccTime - this._progress!.startTime) * 1000 | 0}` : '') + + for (let key in this._apiStat) { + let stat = this._apiStat[key]; + + let send = stat.last.sendReq; + let succ = stat.last.resTime.length; + let netErr = stat.last.networkError; + let apiErr = stat.last.otherError; + + this._logTable([ + [{ text: 'Api' + key + ' '.repeat(this._maxApiNameLength - key.length), color: 'green' }, 'Send', 'Succ', 'QPS', 'NetErr', 'ApiErr'], + ['', '' + send, + { text: '' + succ, color: 'green' }, + { text: '' + (succ / (stat.last.succTime - stat.last.startTime) * 1000 | 0), color: 'green' }, + netErr ? { text: '' + netErr, color: 'red' } : '0', + apiErr ? { text: '' + apiErr, color: 'red' } : '0' + ] + ]) + + Object.assign(stat.last, { + sendReq: 0, + resTime: [], + succ: 0, + networkError: 0, + otherError: 0, + startTime: Date.now(), + }) + } + } + + private _logTable(rows: [TableCellItem[], TableCellItem[]]) { + let cellWidths: number[] = []; + for (let cell of rows[0]) { + cellWidths.push(typeof cell === 'string' ? cell.length + 4 : cell.text.length + 4); + } + + for (let row of rows) { + let line = ''; + for (let i = 0; i < row.length; ++i) { + let cell = row[i]; + let cellWidth = cellWidths[i]; + if (typeof cell === 'string') { + line += cell + ' '.repeat(cellWidth - cell.length); + } + else { + line += cell.text[cell.color] + ' '.repeat(cellWidth - cell.text.length); + } + } + console.log(line); + } + } +} + +export const benchmarkClient = new HttpClient(serviceProto, { + server: benchmarkConfig.server, + logger: { + debug: function () { }, + log: function () { }, + warn: function () { }, + error: function () { }, + }, + timeout: benchmarkConfig.timeout, + agent: new (benchmarkConfig.server.startsWith('https') ? https : http).Agent({ + keepAlive: true + }) +}) + +type TableCellItem = (string | { text: string, color: 'green' | 'red' | 'yellow' }); \ No newline at end of file diff --git a/benchmark/models/WsRunner.ts b/benchmark/models/WsRunner.ts new file mode 100644 index 0000000..677f39c --- /dev/null +++ b/benchmark/models/WsRunner.ts @@ -0,0 +1,283 @@ +import assert from 'assert'; +import 'colors'; +import 'k8w-extend-native'; +import { TsrpcErrorType } from "tsrpc-proto"; +import { WsClient } from '../../src/client/ws/WsClient'; +import { benchmarkConfig } from "../config/BenchmarkConfig"; +import { serviceProto } from '../protocols/proto'; + +export interface WsRunnerConfig { + total: number; + concurrency: number; + showError?: boolean; +} + +export class WsRunner { + + private _config: WsRunnerConfig; + + // 执行单个事务的方法 + private _single: (this: WsRunner) => Promise; + + // 执行进度信息 + private _progress?: { + startTime: number, + lastSuccTime?: number, + started: number, + finished: number, + succ: number, + fail: number + }; + + constructor(single: WsRunner['_single'], config: WsRunnerConfig) { + this._single = single.bind(this); + this._config = config; + } + + async start() { + this._progress = { + startTime: Date.now(), + started: 0, + finished: 0, + succ: 0, + fail: 0 + } + + assert.ok(await benchmarkClient.connect(), 'Connect failed'); + + // 启动并发 + for (let i = 0; i < this._config.concurrency; ++i) { + this._doTrans(); + } + + console.log('Benchmark start!'); + this._startReport(); + } + + private _doTrans() { + if (this._isStoped || !this._progress) { + return; + } + + if (this._progress.started < this._config.total) { + ++this._progress.started; + let startTime = Date.now(); + this._single().then(v => { + ++this._progress!.succ; + this._progress!.lastSuccTime = Date.now(); + }).catch(e => { + ++this._progress!.fail; + if (this._config.showError) { + console.error('[Error]', e.message); + } + }).then(() => { + ++this._progress!.finished; + if (this._progress!.finished === this._config.total) { + this._finish(); + } + else { + this._doTrans(); + } + }) + } + } + + private _reportInterval?: NodeJS.Timeout; + private _startReport() { + this._reportInterval = setInterval(() => { + this._report(); + }, 1000) + } + + private _isStoped = false; + stop() { + this._isStoped = true; + } + + private _finish() { + if (!this._progress) { + return; + } + + this._reportInterval && clearInterval(this._reportInterval); + + console.log('\n\n-------------------------------\n Benchmark finished! \n-------------------------------'); + + let usedTime = Date.now() - this._progress.startTime; + console.log(` Transaction Execution Result `.bgBlue.white); + console.log(`Started=${this._progress.started}, Finished=${this._progress.finished}, UsedTime=${usedTime}ms`.green); + console.log(`Succ=${this._progress.succ}, Fail=${this._progress.fail}, TPS=${this._progress.succ / (this._progress.lastSuccTime! - this._progress.startTime) * 1000 | 0}\n`.green) + + // TIME TPS(完成的) + console.log(` API Execution Result `.bgBlue.white); + + // [KEY] RPS(完成的) AVG P95 P99 + for (let key in this._apiStat) { + let stat = this._apiStat[key]; + stat.resTime = stat.resTime.orderBy(v => v); + + let send = stat.sendReq; + let succ = stat.resTime.length; + let netErr = stat.networkError; + let apiErr = stat.otherError; + let avg = stat.resTime[stat.resTime.length >> 1] | 0; + let p95 = stat.resTime[stat.resTime.length * 0.95 | 0] | 0; + let p99 = stat.resTime[stat.resTime.length * 0.99 | 0] | 0; + + this._logTable([ + [{ text: 'Api' + key + ' '.repeat(this._maxApiNameLength - key.length), color: 'green' }, 'Send', 'Succ', 'QPS', 'NetErr', 'ApiErr', 'AVG ', 'P95 ', 'P99 '], + ['', '' + send, + { text: '' + succ, color: 'green' }, + { text: '' + (succ / (stat.last.succTime - stat.startTime) * 1000 | 0), color: 'green' }, + netErr ? { text: '' + netErr, color: 'red' } : '0', + apiErr ? { text: '' + apiErr, color: 'red' } : '0', + { text: avg ? avg + 'ms' : '-', color: 'yellow' }, + { text: p95 ? p95 + 'ms' : '-', color: 'yellow' }, + { text: p99 ? p99 + 'ms' : '-', color: 'yellow' } + ] + ]) + } + } + + private _apiStat: { + [key: string]: { + sendReq: number, + resTime: number[], + succ: number, + networkError: number, + otherError: number, + startTime: number, + last: { + sendReq: number, + resTime: number[], + succ: number, + networkError: number, + otherError: number, + startTime: number, + succTime: number + } + } + } = {}; + + private _maxApiNameLength = 0; + /** + * callApi 并且计入统计 + */ + callApi: typeof benchmarkClient.callApi = async (apiName, req) => { + this._maxApiNameLength = Math.max(apiName.length, this._maxApiNameLength); + + if (!this._apiStat[apiName]) { + this._apiStat[apiName] = { + sendReq: 0, + resTime: [], + succ: 0, + networkError: 0, + otherError: 0, + startTime: Date.now(), + last: { + sendReq: 0, + resTime: [], + succ: 0, + networkError: 0, + otherError: 0, + startTime: Date.now(), + succTime: 0 + } + }; + } + + ++this._apiStat[apiName].sendReq; + ++this._apiStat[apiName].last.sendReq; + + let startTime = Date.now(); + let ret = await benchmarkClient.callApi(apiName, req); + + if (ret.isSucc) { + this._apiStat[apiName].last.succTime = Date.now(); + this._apiStat[apiName].resTime.push(Date.now() - startTime); + this._apiStat[apiName].last.resTime.push(Date.now() - startTime); + ++this._apiStat[apiName].succ; + ++this._apiStat[apiName].last.succ; + } + else { + if (ret.err.type === TsrpcErrorType.NetworkError) { + ++this._apiStat[apiName].networkError; + ++this._apiStat[apiName].last.networkError; + } + else { + ++this._apiStat[apiName].otherError; + ++this._apiStat[apiName].last.otherError; + } + } + + return ret; + } + + private _report() { + console.log(new Date().format('hh:mm:ss').gray, `Started=${this._progress!.started}/${this._config.total}, Finished=${this._progress!.finished}, Succ=${this._progress!.succ.toString().green}, Fail=${this._progress!.fail.toString()[this._progress!.fail > 0 ? 'red' : 'white']}`, + this._progress!.lastSuccTime ? `TPS=${this._progress!.succ / (this._progress!.lastSuccTime - this._progress!.startTime) * 1000 | 0}` : '') + + for (let key in this._apiStat) { + let stat = this._apiStat[key]; + + let send = stat.last.sendReq; + let succ = stat.last.resTime.length; + let netErr = stat.last.networkError; + let apiErr = stat.last.otherError; + + this._logTable([ + [{ text: 'Api' + key + ' '.repeat(this._maxApiNameLength - key.length), color: 'green' }, 'Send', 'Succ', 'QPS', 'NetErr', 'ApiErr'], + ['', '' + send, + { text: '' + succ, color: 'green' }, + { text: '' + (succ / (stat.last.succTime - stat.last.startTime) * 1000 | 0), color: 'green' }, + netErr ? { text: '' + netErr, color: 'red' } : '0', + apiErr ? { text: '' + apiErr, color: 'red' } : '0' + ] + ]) + + Object.assign(stat.last, { + sendReq: 0, + resTime: [], + succ: 0, + networkError: 0, + otherError: 0, + startTime: Date.now(), + }) + } + } + + private _logTable(rows: [TableCellItem[], TableCellItem[]]) { + let cellWidths: number[] = []; + for (let cell of rows[0]) { + cellWidths.push(typeof cell === 'string' ? cell.length + 4 : cell.text.length + 4); + } + + for (let row of rows) { + let line = ''; + for (let i = 0; i < row.length; ++i) { + let cell = row[i]; + let cellWidth = cellWidths[i]; + if (typeof cell === 'string') { + line += cell + ' '.repeat(cellWidth - cell.length); + } + else { + line += cell.text[cell.color] + ' '.repeat(cellWidth - cell.text.length); + } + } + console.log(line); + } + } +} + +export const benchmarkClient = new WsClient(serviceProto, { + server: benchmarkConfig.server, + logger: { + debug: function () { }, + log: function () { }, + warn: function () { }, + error: function () { }, + }, + timeout: benchmarkConfig.timeout +}) + +type TableCellItem = (string | { text: string, color: 'green' | 'red' | 'yellow' }); \ No newline at end of file diff --git a/benchmark/protocols/PtlTest.ts b/benchmark/protocols/PtlTest.ts new file mode 100644 index 0000000..24a3e06 --- /dev/null +++ b/benchmark/protocols/PtlTest.ts @@ -0,0 +1,14 @@ +import { uint } from 'tsbuffer-schema'; +export interface ReqTest { + a?: uint; + b?: string; + c?: boolean; + d?: Uint8Array; +} + +export interface ResTest { + a?: uint; + b?: string; + c?: boolean; + d?: Uint8Array; +} \ No newline at end of file diff --git a/benchmark/protocols/proto.ts b/benchmark/protocols/proto.ts new file mode 100644 index 0000000..dae4eb8 --- /dev/null +++ b/benchmark/protocols/proto.ts @@ -0,0 +1,104 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqTest, ResTest } from './PtlTest' + +export interface ServiceType { + api: { + "Test": { + req: ReqTest, + res: ResTest + } + }, + msg: { + + } +} + +export const serviceProto: ServiceProto = { + "services": [ + { + "id": 0, + "name": "Test", + "type": "api" + } + ], + "types": { + "PtlTest/ReqTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "a", + "type": { + "type": "Number", + "scalarType": "uint" + }, + "optional": true + }, + { + "id": 1, + "name": "b", + "type": { + "type": "String" + }, + "optional": true + }, + { + "id": 2, + "name": "c", + "type": { + "type": "Boolean" + }, + "optional": true + }, + { + "id": 3, + "name": "d", + "type": { + "type": "Buffer", + "arrayType": "Uint8Array" + }, + "optional": true + } + ] + }, + "PtlTest/ResTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "a", + "type": { + "type": "Number", + "scalarType": "uint" + }, + "optional": true + }, + { + "id": 1, + "name": "b", + "type": { + "type": "String" + }, + "optional": true + }, + { + "id": 2, + "name": "c", + "type": { + "type": "Boolean" + }, + "optional": true + }, + { + "id": 3, + "name": "d", + "type": { + "type": "Buffer", + "arrayType": "Uint8Array" + }, + "optional": true + } + ] + } + } +}; \ No newline at end of file diff --git a/benchmark/server/http.ts b/benchmark/server/http.ts new file mode 100644 index 0000000..6127a65 --- /dev/null +++ b/benchmark/server/http.ts @@ -0,0 +1,26 @@ +import { HttpServer } from '../../src/index'; +import { serviceProto } from "../protocols/proto"; + +async function main() { + let server = new HttpServer(serviceProto, { + logger: { + debug: () => { }, + log: () => { }, + error: console.error.bind(console), + warn: console.warn.bind(console) + } + }); + + server.implementApi('Test', call => { + call.succ(call.req); + }); + + await server.start(); + + setInterval(() => { + let used = process.memoryUsage().heapUsed / 1024 / 1024; + console.log(`内存: ${Math.round(used * 100) / 100} MB`); + }, 2000) +} + +main(); \ No newline at end of file diff --git a/benchmark/server/ws.ts b/benchmark/server/ws.ts new file mode 100644 index 0000000..4ef9980 --- /dev/null +++ b/benchmark/server/ws.ts @@ -0,0 +1,26 @@ +import { WsServer } from '../../src/index'; +import { serviceProto } from "../protocols/proto"; + +async function main() { + let server = new WsServer(serviceProto, { + logger: { + debug: () => { }, + log: () => { }, + error: console.error.bind(console), + warn: console.warn.bind(console) + } + }); + + server.implementApi('Test', call => { + call.succ(call.req); + }); + + await server.start(); + + setInterval(() => { + let used = process.memoryUsage().heapUsed / 1024 / 1024; + console.log(`内存: ${Math.round(used * 100) / 100} MB`); + }, 2000) +} + +main(); \ No newline at end of file diff --git a/benchmark/tsconfig.json b/benchmark/tsconfig.json new file mode 100644 index 0000000..e7f648d --- /dev/null +++ b/benchmark/tsconfig.json @@ -0,0 +1,63 @@ +{ + "compilerOptions": { + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + } +} diff --git a/benchmark/ws.ts b/benchmark/ws.ts new file mode 100644 index 0000000..123fc97 --- /dev/null +++ b/benchmark/ws.ts @@ -0,0 +1,13 @@ +import { benchmarkConfig } from './config/BenchmarkConfig'; +import { WsRunner } from './models/WsRunner'; + +const req = { + a: 123456, + b: 'Hello, World!', + c: true, + d: new Uint8Array(100000) +} + +new WsRunner(async function () { + await this.callApi('Test', req); +}, benchmarkConfig).start(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..938d855 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6105 @@ +{ + "name": "tsrpc", + "version": "3.3.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "tsrpc", + "version": "3.3.0", + "license": "MIT", + "dependencies": { + "@types/ws": "^7.4.7", + "bson": "*", + "chalk": "^4.1.2", + "tsbuffer": "^2.2.2", + "tsrpc-base-client": "^2.0.5", + "tsrpc-proto": "^1.4.1", + "uuid": "^8.3.2", + "ws": "^7.5.7" + }, + "devDependencies": { + "@microsoft/api-documenter": "^7.17.9", + "@microsoft/api-extractor": "^7.22.2", + "@types/chai": "^4.3.1", + "@types/mocha": "^8.2.3", + "@types/node": "^15.14.9", + "@types/uuid": "^8.3.4", + "chai": "^4.3.6", + "mocha": "^9.2.2", + "nyc": "^15.1.0", + "rollup": "^2.70.2", + "rollup-plugin-typescript2": "^0.31.2", + "ts-node": "^10.7.0", + "typescript": "^4.6.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", + "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.17.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", + "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.17.2", + "@babel/parser": "^7.17.3", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", + "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", + "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.16.4", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", + "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", + "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", + "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.0", + "@babel/types": "^7.17.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", + "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", + "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@microsoft/api-documenter": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.17.9.tgz", + "integrity": "sha512-crmuhaqKG0zfeoCjIjvRfUzUe/JZaMGE3HS7FwtH7jROy/AHU8nL2Zpq8RzFRNKV9yHMiF8wVcEgfGmH5oM3hw==", + "dev": true, + "dependencies": { + "@microsoft/api-extractor-model": "7.17.1", + "@microsoft/tsdoc": "0.14.1", + "@rushstack/node-core-library": "3.45.3", + "@rushstack/ts-command-line": "4.10.9", + "colors": "~1.2.1", + "js-yaml": "~3.13.1", + "resolve": "~1.17.0" + }, + "bin": { + "api-documenter": "bin/api-documenter" + } + }, + "node_modules/@microsoft/api-extractor": { + "version": "7.22.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.22.2.tgz", + "integrity": "sha512-G7vXz6UHz+qoaUGPf2k5Md4bSpHii9nFys3sIe3bmFUbmhAe+HfSB/dCn1PsLhW7tZfEXwMHTj7fbL5vcZkrEw==", + "dev": true, + "dependencies": { + "@microsoft/api-extractor-model": "7.17.1", + "@microsoft/tsdoc": "0.14.1", + "@microsoft/tsdoc-config": "~0.16.1", + "@rushstack/node-core-library": "3.45.3", + "@rushstack/rig-package": "0.3.10", + "@rushstack/ts-command-line": "4.10.9", + "colors": "~1.2.1", + "lodash": "~4.17.15", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "source-map": "~0.6.1", + "typescript": "~4.5.2" + }, + "bin": { + "api-extractor": "bin/api-extractor" + } + }, + "node_modules/@microsoft/api-extractor-model": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.17.1.tgz", + "integrity": "sha512-DCDtD8TdEpNk2lW4JvXgwwpxKy70P0JLad55iahwO8A+C63KYsrHIpAzo0FUauh5pwJ0v5QVNIJ+OBgKGteemg==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "0.14.1", + "@microsoft/tsdoc-config": "~0.16.1", + "@rushstack/node-core-library": "3.45.3" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz", + "integrity": "sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw==", + "dev": true + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.1.tgz", + "integrity": "sha512-2RqkwiD4uN6MLnHFljqBlZIXlt/SaUT6cuogU1w2ARw4nKuuppSmR0+s+NC+7kXBQykd9zzu0P4HtBpZT5zBpQ==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "0.14.1", + "ajv": "~6.12.6", + "jju": "~1.4.0", + "resolve": "~1.19.0" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz", + "integrity": "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==", + "dev": true, + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@rushstack/node-core-library": { + "version": "3.45.3", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.45.3.tgz", + "integrity": "sha512-Rn0mxqC3MPb+YbvaeFcRWfcYHLwyZ99/ffYA8chpq5OpqoY+Mr1ycTbMvzl5AxWf1pYmi/2+Eo3iTOsQdYR8xw==", + "dev": true, + "dependencies": { + "@types/node": "12.20.24", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~5.0.2" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/@types/node": { + "version": "12.20.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.24.tgz", + "integrity": "sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==", + "dev": true + }, + "node_modules/@rushstack/rig-package": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.3.10.tgz", + "integrity": "sha512-4Z2HhXM4YBWOi4ZYFQNK6Yxz641v+cvc8NKiaNZh+RIdNb3D4Rfpy3XUkggbCozpfDriBfL1+KaXlJtfJfAIXw==", + "dev": true, + "dependencies": { + "resolve": "~1.17.0", + "strip-json-comments": "~3.1.1" + } + }, + "node_modules/@rushstack/ts-command-line": { + "version": "4.10.9", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.10.9.tgz", + "integrity": "sha512-TE3eZgHNVHOY3p8lp38FoNEJUr0+swPb24sCcYuwlC+MHgMGXyJNM+p7l3TKSBRiY01XShoL2k601oGwL00KlA==", + "dev": true, + "dependencies": { + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "colors": "~1.2.1", + "string-argv": "~0.3.1" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/argparse": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", + "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", + "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", + "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "15.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", + "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" + }, + "node_modules/@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "node_modules/@yarn-tool/resolve-package": { + "version": "1.0.43", + "resolved": "https://registry.npmjs.org/@yarn-tool/resolve-package/-/resolve-package-1.0.43.tgz", + "integrity": "sha512-axFruggDvKkfTuevJzqYFJ9XtJkKqaDYjXA1Ugx9mEpMX8HRGib86IgryAf5HfN3RCbawIkwCLjzGFm8H4DkPA==", + "dev": true, + "dependencies": { + "pkg-dir": "< 6 >= 5", + "tslib": "^2.3.1", + "upath2": "^3.1.12" + } + }, + "node_modules/@yarn-tool/resolve-package/node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@yarn-tool/resolve-package/node_modules/upath2": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/upath2/-/upath2-3.1.12.tgz", + "integrity": "sha512-yC3eZeCyCXFWjy7Nu4pgjLhXNYjuzuUmJiRgSSw6TJp8Emc+E4951HGPJf+bldFC5SL7oBLeNbtm1fGzXn2gxw==", + "dev": true, + "dependencies": { + "path-is-network-drive": "^1.0.13", + "path-strip-sep": "^1.0.10", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "@types/node": "*" + } + }, + "node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", + "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", + "dev": true, + "dependencies": { + "caniuse-lite": "^1.0.30001312", + "electron-to-chromium": "^1.4.71", + "escalade": "^3.1.1", + "node-releases": "^2.0.2", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/bson": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz", + "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001312", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", + "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colors": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz", + "integrity": "sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.72", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.72.tgz", + "integrity": "sha512-9LkRQwjW6/wnSfevR21a3k8sOJ+XWSH7kkzs9/EUenKmuDkndP3W9y1yCZpOxufwGbX3JV8glZZSDb4o95zwXQ==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/k8w-extend-native": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/k8w-extend-native/-/k8w-extend-native-1.4.6.tgz", + "integrity": "sha512-AHTCyFshldMme0s9FKD+QKG+QZdBkHXzl+8kYfNhsSDhcdQ5TYWQwphjecSJjxNdGd78TIbO0fHiOvM+Ei22YA==", + "dependencies": { + "k8w-linq-array": "*", + "k8w-super-date": "*", + "k8w-super-object": "*" + } + }, + "node_modules/k8w-linq-array": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/k8w-linq-array/-/k8w-linq-array-0.2.8.tgz", + "integrity": "sha512-4IAkQN8UJdk804tQi++wuwSZvFWk/Wcl1uG5PR/0c0YvB5hUd2f8tJm3OgOMOxjV9UVByNLvnPYGIwrFQPpjlA==" + }, + "node_modules/k8w-super-date": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/k8w-super-date/-/k8w-super-date-0.1.3.tgz", + "integrity": "sha512-IBqKOAMAXR/bgzu+rYI30tEMP/Y6Q8HQuqJiTkE2mLJg11yok9guoi8uZTynTahviVBndcfBpOgi1H/zhihv7w==" + }, + "node_modules/k8w-super-object": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/k8w-super-object/-/k8w-super-object-0.3.0.tgz", + "integrity": "sha512-u2jfh4goYXKZmSucaLaOTaNbLRatjv0CSRpzE0KU0732+9XtYZFd5vrdw/mzJfK5fPHb/zyikOSHDX5mJrav+g==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-network-drive": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/path-is-network-drive/-/path-is-network-drive-1.0.13.tgz", + "integrity": "sha512-Hg74mRN6mmXV+gTm3INjFK40ncAmC/Lo4qoQaSZ+GT3hZzlKdWQSqAjqyPeW0SvObP2W073WyYEBWY9d3wOm3A==", + "dev": true, + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-strip-sep": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/path-strip-sep/-/path-strip-sep-1.0.10.tgz", + "integrity": "sha512-JpCy+8LAJQQTO1bQsb/84s1g+/Stm3h39aOpPRBQ/paMUGVPPZChLTOTKHoaCkc/6sKuF7yVsnq5Pe1S6xQGcA==", + "dev": true, + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.70.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.2.tgz", + "integrity": "sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-typescript2": { + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.31.2.tgz", + "integrity": "sha512-hRwEYR1C8xDGVVMFJQdEVnNAeWRvpaY97g5mp3IeLnzhNXzSVq78Ye/BJ9PAaUfN4DXa/uDnqerifMOaMFY54Q==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^4.1.2", + "@yarn-tool/resolve-package": "^1.0.40", + "find-cache-dir": "^3.3.2", + "fs-extra": "^10.0.0", + "resolve": "^1.20.0", + "tslib": "^2.3.1" + }, + "peerDependencies": { + "rollup": ">=1.26.3", + "typescript": ">=2.4.0" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/fs-extra": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "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 + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tsbuffer": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tsbuffer/-/tsbuffer-2.2.2.tgz", + "integrity": "sha512-iP5xC1iDs/ILYBzi7CCsbGCLp+QGDIHMjW2jzBmV8JVbw//wb1NbQCDXL7pCxU9pI3uQsDpMAu/RPvc7fuLZSQ==", + "dependencies": { + "k8w-extend-native": "^1.4.6", + "tsbuffer-validator": "^2.1.0", + "tslib": "*" + } + }, + "node_modules/tsbuffer-schema": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tsbuffer-schema/-/tsbuffer-schema-2.2.0.tgz", + "integrity": "sha512-I4+5Xfk7G+D++kXdNnYTeY26WQTaf14C84XQwPKteNmrwxRY3CQCkMqASRiCUqtpOuDn43qmoxuXpT+Vo8Wltg==" + }, + "node_modules/tsbuffer-validator": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tsbuffer-validator/-/tsbuffer-validator-2.1.0.tgz", + "integrity": "sha512-pI4qqKVVFWOF8nF7wGGT+WzNnOMOb1skhSjigtKiWOzkpVhoiOvyPJamFQvc0MVbhoGTySuVUzp3KFclo4lVxw==", + "dependencies": { + "k8w-extend-native": "^1.4.6", + "tsbuffer-schema": "^2.2.0", + "tslib": "*" + } + }, + "node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/tsrpc-base-client": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/tsrpc-base-client/-/tsrpc-base-client-2.0.5.tgz", + "integrity": "sha512-ocYmnLLZ2fNaIOwaFxDnKPYFn25cXeVWbyojC1WRjaGes8uzS0XL//6Hh5yCYsZzOdUlnqucd3MeFm4zRgeYKg==", + "dependencies": { + "k8w-extend-native": "^1.4.6", + "tsbuffer": "^2.2.2", + "tslib": "*", + "tsrpc-proto": "^1.4.1" + } + }, + "node_modules/tsrpc-proto": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tsrpc-proto/-/tsrpc-proto-1.4.1.tgz", + "integrity": "sha512-qk0/vd5QSf8lx2kHxhhf59wji20RU7KjZ2md8NHLwFV09kLJ/jvR2kwM6lNQRIxYOrM9HvvdySlrnSJUkNkRyA==", + "dependencies": { + "tsbuffer-schema": "^2.2.0", + "tslib": "*" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", + "dev": true + }, + "node_modules/validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", + "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/z-schema": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.3.tgz", + "integrity": "sha512-sGvEcBOTNum68x9jCpCVGPFJ6mWnkD0YxOcddDlJHRx3tKdB2q8pCHExMVZo/AV/6geuVJXG7hljDaWG8+5GDw==", + "dev": true, + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^2.20.3" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", + "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.0" + } + }, + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/compat-data": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", + "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", + "dev": true + }, + "@babel/core": { + "version": "7.17.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", + "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.17.2", + "@babel/parser": "^7.17.3", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", + "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "dev": true, + "requires": { + "@babel/types": "^7.17.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", + "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.16.4", + "@babel/helper-validator-option": "^7.16.7", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-function-name": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-module-transforms": { + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", + "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", + "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "dev": true, + "requires": { + "@babel/types": "^7.16.7" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "dev": true + }, + "@babel/helpers": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", + "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", + "dev": true, + "requires": { + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.0", + "@babel/types": "^7.17.0" + } + }, + "@babel/highlight": { + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", + "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", + "dev": true + }, + "@babel/template": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" + } + }, + "@babel/traverse": { + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "to-fast-properties": "^2.0.0" + } + }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jridgewell/resolve-uri": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", + "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", + "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@microsoft/api-documenter": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.17.9.tgz", + "integrity": "sha512-crmuhaqKG0zfeoCjIjvRfUzUe/JZaMGE3HS7FwtH7jROy/AHU8nL2Zpq8RzFRNKV9yHMiF8wVcEgfGmH5oM3hw==", + "dev": true, + "requires": { + "@microsoft/api-extractor-model": "7.17.1", + "@microsoft/tsdoc": "0.14.1", + "@rushstack/node-core-library": "3.45.3", + "@rushstack/ts-command-line": "4.10.9", + "colors": "~1.2.1", + "js-yaml": "~3.13.1", + "resolve": "~1.17.0" + } + }, + "@microsoft/api-extractor": { + "version": "7.22.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.22.2.tgz", + "integrity": "sha512-G7vXz6UHz+qoaUGPf2k5Md4bSpHii9nFys3sIe3bmFUbmhAe+HfSB/dCn1PsLhW7tZfEXwMHTj7fbL5vcZkrEw==", + "dev": true, + "requires": { + "@microsoft/api-extractor-model": "7.17.1", + "@microsoft/tsdoc": "0.14.1", + "@microsoft/tsdoc-config": "~0.16.1", + "@rushstack/node-core-library": "3.45.3", + "@rushstack/rig-package": "0.3.10", + "@rushstack/ts-command-line": "4.10.9", + "colors": "~1.2.1", + "lodash": "~4.17.15", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "source-map": "~0.6.1", + "typescript": "~4.5.2" + }, + "dependencies": { + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + } + } + }, + "@microsoft/api-extractor-model": { + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.17.1.tgz", + "integrity": "sha512-DCDtD8TdEpNk2lW4JvXgwwpxKy70P0JLad55iahwO8A+C63KYsrHIpAzo0FUauh5pwJ0v5QVNIJ+OBgKGteemg==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "0.14.1", + "@microsoft/tsdoc-config": "~0.16.1", + "@rushstack/node-core-library": "3.45.3" + } + }, + "@microsoft/tsdoc": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz", + "integrity": "sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw==", + "dev": true + }, + "@microsoft/tsdoc-config": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.1.tgz", + "integrity": "sha512-2RqkwiD4uN6MLnHFljqBlZIXlt/SaUT6cuogU1w2ARw4nKuuppSmR0+s+NC+7kXBQykd9zzu0P4HtBpZT5zBpQ==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "0.14.1", + "ajv": "~6.12.6", + "jju": "~1.4.0", + "resolve": "~1.19.0" + }, + "dependencies": { + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + } + } + }, + "@rollup/pluginutils": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz", + "integrity": "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==", + "dev": true, + "requires": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + } + }, + "@rushstack/node-core-library": { + "version": "3.45.3", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.45.3.tgz", + "integrity": "sha512-Rn0mxqC3MPb+YbvaeFcRWfcYHLwyZ99/ffYA8chpq5OpqoY+Mr1ycTbMvzl5AxWf1pYmi/2+Eo3iTOsQdYR8xw==", + "dev": true, + "requires": { + "@types/node": "12.20.24", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~5.0.2" + }, + "dependencies": { + "@types/node": { + "version": "12.20.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.24.tgz", + "integrity": "sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==", + "dev": true + } + } + }, + "@rushstack/rig-package": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.3.10.tgz", + "integrity": "sha512-4Z2HhXM4YBWOi4ZYFQNK6Yxz641v+cvc8NKiaNZh+RIdNb3D4Rfpy3XUkggbCozpfDriBfL1+KaXlJtfJfAIXw==", + "dev": true, + "requires": { + "resolve": "~1.17.0", + "strip-json-comments": "~3.1.1" + } + }, + "@rushstack/ts-command-line": { + "version": "4.10.9", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.10.9.tgz", + "integrity": "sha512-TE3eZgHNVHOY3p8lp38FoNEJUr0+swPb24sCcYuwlC+MHgMGXyJNM+p7l3TKSBRiY01XShoL2k601oGwL00KlA==", + "dev": true, + "requires": { + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "colors": "~1.2.1", + "string-argv": "~0.3.1" + } + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@types/argparse": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", + "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true + }, + "@types/chai": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", + "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", + "dev": true + }, + "@types/mocha": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", + "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", + "dev": true + }, + "@types/node": { + "version": "15.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", + "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" + }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, + "@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "requires": { + "@types/node": "*" + } + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "@yarn-tool/resolve-package": { + "version": "1.0.43", + "resolved": "https://registry.npmjs.org/@yarn-tool/resolve-package/-/resolve-package-1.0.43.tgz", + "integrity": "sha512-axFruggDvKkfTuevJzqYFJ9XtJkKqaDYjXA1Ugx9mEpMX8HRGib86IgryAf5HfN3RCbawIkwCLjzGFm8H4DkPA==", + "dev": true, + "requires": { + "pkg-dir": "< 6 >= 5", + "tslib": "^2.3.1", + "upath2": "^3.1.12" + }, + "dependencies": { + "pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "requires": { + "find-up": "^5.0.0" + } + }, + "upath2": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/upath2/-/upath2-3.1.12.tgz", + "integrity": "sha512-yC3eZeCyCXFWjy7Nu4pgjLhXNYjuzuUmJiRgSSw6TJp8Emc+E4951HGPJf+bldFC5SL7oBLeNbtm1fGzXn2gxw==", + "dev": true, + "requires": { + "path-is-network-drive": "^1.0.13", + "path-strip-sep": "^1.0.10", + "tslib": "^2.3.1" + } + } + } + }, + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserslist": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.3.tgz", + "integrity": "sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001312", + "electron-to-chromium": "^1.4.71", + "escalade": "^3.1.1", + "node-releases": "^2.0.2", + "picocolors": "^1.0.0" + } + }, + "bson": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz", + "integrity": "sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw==", + "requires": { + "buffer": "^5.6.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001312", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", + "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==", + "dev": true + }, + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "colors": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz", + "integrity": "sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.72", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.72.tgz", + "integrity": "sha512-9LkRQwjW6/wnSfevR21a3k8sOJ+XWSH7kkzs9/EUenKmuDkndP3W9y1yCZpOxufwGbX3JV8glZZSDb4o95zwXQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "k8w-extend-native": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/k8w-extend-native/-/k8w-extend-native-1.4.6.tgz", + "integrity": "sha512-AHTCyFshldMme0s9FKD+QKG+QZdBkHXzl+8kYfNhsSDhcdQ5TYWQwphjecSJjxNdGd78TIbO0fHiOvM+Ei22YA==", + "requires": { + "k8w-linq-array": "*", + "k8w-super-date": "*", + "k8w-super-object": "*" + } + }, + "k8w-linq-array": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/k8w-linq-array/-/k8w-linq-array-0.2.8.tgz", + "integrity": "sha512-4IAkQN8UJdk804tQi++wuwSZvFWk/Wcl1uG5PR/0c0YvB5hUd2f8tJm3OgOMOxjV9UVByNLvnPYGIwrFQPpjlA==" + }, + "k8w-super-date": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/k8w-super-date/-/k8w-super-date-0.1.3.tgz", + "integrity": "sha512-IBqKOAMAXR/bgzu+rYI30tEMP/Y6Q8HQuqJiTkE2mLJg11yok9guoi8uZTynTahviVBndcfBpOgi1H/zhihv7w==" + }, + "k8w-super-object": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/k8w-super-object/-/k8w-super-object-0.3.0.tgz", + "integrity": "sha512-u2jfh4goYXKZmSucaLaOTaNbLRatjv0CSRpzE0KU0732+9XtYZFd5vrdw/mzJfK5fPHb/zyikOSHDX5mJrav+g==" + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mocha": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", + "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "4.2.1", + "ms": "2.1.3", + "nanoid": "3.3.1", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "workerpool": "6.2.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "minimatch": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", + "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "dev": true + }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, + "node-releases": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-network-drive": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/path-is-network-drive/-/path-is-network-drive-1.0.13.tgz", + "integrity": "sha512-Hg74mRN6mmXV+gTm3INjFK40ncAmC/Lo4qoQaSZ+GT3hZzlKdWQSqAjqyPeW0SvObP2W073WyYEBWY9d3wOm3A==", + "dev": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-strip-sep": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/path-strip-sep/-/path-strip-sep-1.0.10.tgz", + "integrity": "sha512-JpCy+8LAJQQTO1bQsb/84s1g+/Stm3h39aOpPRBQ/paMUGVPPZChLTOTKHoaCkc/6sKuF7yVsnq5Pe1S6xQGcA==", + "dev": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "2.70.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.2.tgz", + "integrity": "sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "rollup-plugin-typescript2": { + "version": "0.31.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.31.2.tgz", + "integrity": "sha512-hRwEYR1C8xDGVVMFJQdEVnNAeWRvpaY97g5mp3IeLnzhNXzSVq78Ye/BJ9PAaUfN4DXa/uDnqerifMOaMFY54Q==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^4.1.2", + "@yarn-tool/resolve-package": "^1.0.40", + "find-cache-dir": "^3.3.2", + "fs-extra": "^10.0.0", + "resolve": "^1.20.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "fs-extra": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, + "tsbuffer": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tsbuffer/-/tsbuffer-2.2.2.tgz", + "integrity": "sha512-iP5xC1iDs/ILYBzi7CCsbGCLp+QGDIHMjW2jzBmV8JVbw//wb1NbQCDXL7pCxU9pI3uQsDpMAu/RPvc7fuLZSQ==", + "requires": { + "k8w-extend-native": "^1.4.6", + "tsbuffer-validator": "^2.1.0", + "tslib": "*" + } + }, + "tsbuffer-schema": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tsbuffer-schema/-/tsbuffer-schema-2.2.0.tgz", + "integrity": "sha512-I4+5Xfk7G+D++kXdNnYTeY26WQTaf14C84XQwPKteNmrwxRY3CQCkMqASRiCUqtpOuDn43qmoxuXpT+Vo8Wltg==" + }, + "tsbuffer-validator": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tsbuffer-validator/-/tsbuffer-validator-2.1.0.tgz", + "integrity": "sha512-pI4qqKVVFWOF8nF7wGGT+WzNnOMOb1skhSjigtKiWOzkpVhoiOvyPJamFQvc0MVbhoGTySuVUzp3KFclo4lVxw==", + "requires": { + "k8w-extend-native": "^1.4.6", + "tsbuffer-schema": "^2.2.0", + "tslib": "*" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "tsrpc-base-client": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/tsrpc-base-client/-/tsrpc-base-client-2.0.5.tgz", + "integrity": "sha512-ocYmnLLZ2fNaIOwaFxDnKPYFn25cXeVWbyojC1WRjaGes8uzS0XL//6Hh5yCYsZzOdUlnqucd3MeFm4zRgeYKg==", + "requires": { + "k8w-extend-native": "^1.4.6", + "tsbuffer": "^2.2.2", + "tslib": "*", + "tsrpc-proto": "^1.4.1" + } + }, + "tsrpc-proto": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tsrpc-proto/-/tsrpc-proto-1.4.1.tgz", + "integrity": "sha512-qk0/vd5QSf8lx2kHxhhf59wji20RU7KjZ2md8NHLwFV09kLJ/jvR2kwM6lNQRIxYOrM9HvvdySlrnSJUkNkRyA==", + "requires": { + "tsbuffer-schema": "^2.2.0", + "tslib": "*" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "v8-compile-cache-lib": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", + "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==", + "dev": true + }, + "validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "workerpool": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", + "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "requires": {} + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, + "z-schema": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.3.tgz", + "integrity": "sha512-sGvEcBOTNum68x9jCpCVGPFJ6mWnkD0YxOcddDlJHRx3tKdB2q8pCHExMVZo/AV/6geuVJXG7hljDaWG8+5GDw==", + "dev": true, + "requires": { + "commander": "^2.20.3", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..dd2efdb --- /dev/null +++ b/package.json @@ -0,0 +1,77 @@ +{ + "name": "tsrpc", + "version": "3.3.1-dev.0", + "description": "A TypeScript RPC Framework, with runtime type checking and built-in serialization, support both HTTP and WebSocket.", + "main": "index.js", + "exports": { + "require": "./index.js", + "import": "./index.mjs" + }, + "typings": "index.d.ts", + "directories": { + "doc": "docs" + }, + "scripts": { + "test": "npx mocha", + "genTestProto": "npx tsrpc-cli@latest proto --input test/proto --output test/proto/serviceProto.ts", + "coverage": "nyc mocha test/**/*.test.ts && start coverage\\index.html", + "build": "npm run build:js && npm run build:dts && npm run build:doc && node scripts/postBuild && cp package.json LICENSE README.md dist/", + "build:js": "rm -rf dist && npx rollup -c", + "build:dts": "rm -rf lib && npx tsc && npx api-extractor run --local --verbose && rm -rf lib", + "build:doc": "rm -rf docs/api && npx api-documenter markdown --input temp --output docs/api" + }, + "repository": { + "type": "git", + "url": "https://github.com/k8w/tsrpc.git" + }, + "keywords": [ + "k8w", + "ts", + "rpc", + "grpc", + "tsbuffer", + "fullstack", + "websocket", + "protobuf", + "socket.io" + ], + "author": "k8w", + "license": "MIT", + "devDependencies": { + "@microsoft/api-documenter": "^7.17.9", + "@microsoft/api-extractor": "^7.22.2", + "@types/chai": "^4.3.1", + "@types/mocha": "^8.2.3", + "@types/node": "^15.14.9", + "@types/uuid": "^8.3.4", + "chai": "^4.3.6", + "mocha": "^9.2.2", + "nyc": "^15.1.0", + "rollup": "^2.70.2", + "rollup-plugin-typescript2": "^0.31.2", + "ts-node": "^10.7.0", + "typescript": "^4.6.3" + }, + "dependencies": { + "@types/ws": "^7.4.7", + "bson": "*", + "chalk": "^4.1.2", + "tsbuffer": "^2.2.2", + "tsrpc-base-client": "^2.0.5", + "tsrpc-proto": "^1.4.1", + "uuid": "^8.3.2", + "ws": "^7.5.7" + }, + "nyc": { + "extension": [ + ".ts" + ], + "include": [ + "src/**/*.ts" + ], + "reporter": [ + "html" + ], + "all": true + } +} \ No newline at end of file diff --git a/res/mongodb-polyfill.d.ts b/res/mongodb-polyfill.d.ts new file mode 100644 index 0000000..2f4a675 --- /dev/null +++ b/res/mongodb-polyfill.d.ts @@ -0,0 +1,17 @@ +import { ObjectId } from 'bson'; +import { OmitUnion } from 'k8w-extend-native'; + +type InsertOneResult = any; +type OptionalId = any; +type Document = any; + +declare module 'mongodb' { + export interface Collection { + insertOne(doc: OptionalUnlessRequiredId_1): Promise>; + } + export type OptionalUnlessRequiredId_1 = TSchema extends { + _id: ObjectId; + } ? (OmitUnion & { _id?: ObjectId }) : TSchema extends { + _id: any; + } ? TSchema : OptionalId; +} \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..5008f42 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,42 @@ +import typescript from 'rollup-plugin-typescript2'; + +export default [ + { + input: './src/index.ts', + output: [{ + format: 'cjs', + file: './dist/index.js', + banner: require('./scripts/copyright') + }], + plugins: [ + typescript({ + tsconfigOverride: { + compilerOptions: { + declaration: false, + declarationMap: false, + module: "esnext" + } + } + }) + ] + }, + { + input: './src/index.ts', + output: [{ + format: 'es', + file: './dist/index.mjs', + banner: require('./scripts/copyright') + }], + plugins: [ + typescript({ + tsconfigOverride: { + compilerOptions: { + declaration: false, + declarationMap: false, + module: "esnext" + } + } + }) + ] + } +] \ No newline at end of file diff --git a/scripts/copyright.js b/scripts/copyright.js new file mode 100644 index 0000000..b17c121 --- /dev/null +++ b/scripts/copyright.js @@ -0,0 +1,7 @@ +module.exports = `/*! + * TSRPC v${require('../package.json').version} + * ----------------------------------------- + * Copyright (c) King Wang. + * MIT License + * https://github.com/k8w/tsrpc + */` \ No newline at end of file diff --git a/scripts/postBuild.js b/scripts/postBuild.js new file mode 100644 index 0000000..83c301f --- /dev/null +++ b/scripts/postBuild.js @@ -0,0 +1,26 @@ +const fs = require('fs'); +const path = require('path'); + +// remove private / protected index.d.ts +(() => { + let content = fs.readFileSync(path.resolve(__dirname, '../dist/index.d.ts'), 'utf-8'); + content = content.replace(/^\s*(private|protected)\s+\_.+;/g, ''); + content = require('./copyright') + '\n' + content; + fs.writeFileSync(path.resolve(__dirname, '../dist/index.d.ts'), content, 'utf-8'); +})(); + +// replace __TSRPC_VERSION__from index.js/mjs +[ + path.resolve(__dirname, '../dist/index.js'), + path.resolve(__dirname, '../dist/index.mjs') +].forEach(filepath => { + let content = fs.readFileSync(filepath, 'utf-8'); + content = content.replace('__TSRPC_VERSION__', require('../package.json').version);; + fs.writeFileSync(filepath, content, 'utf-8'); +}); + +// mongodb-polyfill +fs.copyFileSync(path.resolve(__dirname, '../res/mongodb-polyfill.d.ts'), path.resolve(__dirname, '../dist/mongodb-polyfill.d.ts')); +let content = fs.readFileSync(path.resolve(__dirname, '../dist/index.d.ts'), 'utf-8'); +content = content.replace(`/// `, `/// \n/// `) +fs.writeFileSync(path.resolve(__dirname, '../dist/index.d.ts'), content, 'utf-8'); \ No newline at end of file diff --git a/src/client/http/HttpClient.ts b/src/client/http/HttpClient.ts new file mode 100644 index 0000000..41f61e5 --- /dev/null +++ b/src/client/http/HttpClient.ts @@ -0,0 +1,37 @@ +import { ObjectId } from "bson"; +import http from "http"; +import https from "https"; +import { BaseHttpClient, BaseHttpClientOptions, defaultBaseHttpClientOptions } from "tsrpc-base-client"; +import { BaseServiceType, ServiceProto } from "tsrpc-proto"; +import { HttpProxy } from "./HttpProxy"; + +/** + * Client for TSRPC HTTP Server. + * It uses native http module of NodeJS. + * @typeParam ServiceType - `ServiceType` from generated `proto.ts` + */ +export class HttpClient extends BaseHttpClient { + + readonly options!: Readonly; + + constructor(proto: ServiceProto, options?: Partial) { + let httpProxy = new HttpProxy; + super(proto, httpProxy, { + customObjectIdClass: ObjectId, + ...defaultHttpClientOptions, + ...options + }); + + httpProxy.agent = this.options.agent; + } + +} + +const defaultHttpClientOptions: HttpClientOptions = { + ...defaultBaseHttpClientOptions +} + +export interface HttpClientOptions extends BaseHttpClientOptions { + /** NodeJS HTTP Agent */ + agent?: http.Agent | https.Agent; +} \ No newline at end of file diff --git a/src/client/http/HttpProxy.ts b/src/client/http/HttpProxy.ts new file mode 100644 index 0000000..b21ffe9 --- /dev/null +++ b/src/client/http/HttpProxy.ts @@ -0,0 +1,69 @@ +import http from "http"; +import https from "https"; +import { IHttpProxy } from "tsrpc-base-client"; +import { TsrpcError } from "tsrpc-proto"; + +/** @internal */ +export class HttpProxy implements IHttpProxy { + + /** NodeJS HTTP Agent */ + agent?: http.Agent | https.Agent; + + fetch(options: Parameters[0]): ReturnType { + let nodeHttp = options.url.startsWith('https://') ? https : http; + + let rs!: (v: { isSucc: true, res: string | Uint8Array } | { isSucc: false, err: TsrpcError }) => void; + let promise: ReturnType['promise'] = new Promise(_rs => { + rs = _rs; + }) + + let httpReq: http.ClientRequest; + httpReq = nodeHttp.request(options.url, { + method: options.method, + agent: this.agent, + timeout: options.timeout, + headers: options.headers, + }, httpRes => { + let data: Buffer[] = []; + httpRes.on('data', (v: Buffer) => { + data.push(v) + }); + httpRes.on('end', () => { + let buf: Uint8Array = Buffer.concat(data); + if (options.responseType === 'text') { + rs({ + isSucc: true, + res: buf.toString() + }) + } + else { + rs({ + isSucc: true, + res: buf + }) + } + }) + }); + + httpReq.on('error', e => { + rs({ + isSucc: false, + err: new TsrpcError(e.message, { + type: TsrpcError.Type.NetworkError, + code: (e as any).code + }) + }); + }); + + let buf = options.data; + httpReq.end(typeof buf === 'string' ? buf : Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength)); + + let abort = httpReq.abort.bind(httpReq); + + return { + promise: promise, + abort: abort + } + } + +} \ No newline at end of file diff --git a/src/client/ws/WebSocketProxy.ts b/src/client/ws/WebSocketProxy.ts new file mode 100644 index 0000000..65c74ae --- /dev/null +++ b/src/client/ws/WebSocketProxy.ts @@ -0,0 +1,58 @@ +import { IWebSocketProxy } from "tsrpc-base-client"; +import { TsrpcError } from "tsrpc-proto"; +import WebSocket from 'ws'; + +/** + * @internal + */ +export class WebSocketProxy implements IWebSocketProxy { + + options!: IWebSocketProxy['options'] + + private _ws?: WebSocket; + connect(server: string, protocols?: string[]): void { + this._ws = new WebSocket(server, protocols); + this._ws.onopen = this.options.onOpen; + this._ws.onclose = e => { + this.options.onClose(e.code, e.reason); + this._ws = undefined; + } + this._ws.onerror = e => { + this.options.onError(e.error); + } + this._ws.onmessage = e => { + if (e.data instanceof ArrayBuffer) { + this.options.onMessage(new Uint8Array(e.data)); + } + else if (Array.isArray(e.data)) { + this.options.onMessage(Buffer.concat(e.data)); + } + else { + this.options.onMessage(e.data); + } + } + } + close(code?: number, reason?: string): void { + this._ws?.close(code, reason); + this._ws = undefined; + } + send(data: string | Uint8Array): Promise<{ err?: TsrpcError | undefined; }> { + return new Promise(rs => { + this._ws?.send(data, err => { + if (err) { + this.options.logger?.error('WebSocket Send Error:', err); + rs({ + err: new TsrpcError('Network Error', { + code: 'SEND_BUF_ERR', + type: TsrpcError.Type.NetworkError, + innerErr: err + }) + }); + return; + } + rs({}); + }); + }) + } + +} \ No newline at end of file diff --git a/src/client/ws/WsClient.ts b/src/client/ws/WsClient.ts new file mode 100644 index 0000000..d4142dd --- /dev/null +++ b/src/client/ws/WsClient.ts @@ -0,0 +1,31 @@ +import { ObjectId } from "bson"; +import { BaseWsClient, BaseWsClientOptions, defaultBaseWsClientOptions } from "tsrpc-base-client"; +import { BaseServiceType, ServiceProto } from "tsrpc-proto"; +import { WebSocketProxy } from "./WebSocketProxy"; + +/** + * Client for TSRPC WebSocket Server. + * @typeParam ServiceType - `ServiceType` from generated `proto.ts` + */ +export class WsClient extends BaseWsClient { + + readonly options!: Readonly; + + constructor(proto: ServiceProto, options?: Partial) { + let wsp = new WebSocketProxy(); + super(proto, wsp, { + customObjectIdClass: ObjectId, + ...defaultWsClientOptions, + ...options + }) + } + +} + +const defaultWsClientOptions: WsClientOptions = { + ...defaultBaseWsClientOptions, +} + +export interface WsClientOptions extends BaseWsClientOptions { + +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..c1c1208 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,27 @@ +import 'k8w-extend-native'; + +// Common +export * from 'tsrpc-base-client'; +export * from 'tsrpc-proto'; +export * from './client/http/HttpClient'; +export * from './client/ws/WsClient'; +export * from './models/version'; +export * from './server/base/ApiCall'; +// Base +export * from './server/base/BaseCall'; +export * from './server/base/BaseConnection'; +export * from './server/base/BaseServer'; +export * from './server/base/MsgCall'; +export * from './server/http/ApiCallHttp'; +// Http +export * from './server/http/HttpConnection'; +export * from './server/http/HttpServer'; +export * from './server/http/MsgCallHttp'; +export * from './server/models/PrefixLogger'; +export * from './server/models/TerminalColorLogger'; +// WebSocket +export * from './server/ws/ApiCallWs'; +export * from './server/ws/MsgCallWs'; +export * from './server/ws/WsConnection'; +export * from './server/ws/WsServer'; + diff --git a/src/models/HttpUtil.ts b/src/models/HttpUtil.ts new file mode 100644 index 0000000..7bbe72d --- /dev/null +++ b/src/models/HttpUtil.ts @@ -0,0 +1,22 @@ +import * as http from "http"; + +export class HttpUtil { + static getClientIp(req: http.IncomingMessage) { + var ipAddress; + // The request may be forwarded from local web server. + var forwardedIpsStr = req.headers['x-forwarded-for'] as string | undefined; + if (forwardedIpsStr) { + // 'x-forwarded-for' header may return multiple IP addresses in + // the format: "client IP, proxy 1 IP, proxy 2 IP" so take the + // the first one + var forwardedIps = forwardedIpsStr.split(','); + ipAddress = forwardedIps[0]; + } + if (!ipAddress) { + // If request was not forwarded + ipAddress = req.connection.remoteAddress; + } + // Remove prefix ::ffff: + return ipAddress ? ipAddress.replace(/^::ffff:/, '') : ''; + }; +} \ No newline at end of file diff --git a/src/models/Pool.ts b/src/models/Pool.ts new file mode 100644 index 0000000..af697b0 --- /dev/null +++ b/src/models/Pool.ts @@ -0,0 +1,35 @@ +export class Pool { + + private _pools: ItemClass[] = []; + private _itemClass: { new(): ItemClass }; + enabled: boolean; + + constructor(itemClass: { new(): ItemClass }, enabled: boolean) { + this._itemClass = itemClass; + this.enabled = enabled; + } + + get() { + let item = this.enabled && this._pools.pop(); + if (!item) { + item = new this._itemClass(); + } + item.reuse?.(); + return item; + } + + put(item: ItemClass) { + if (!this.enabled || this._pools.indexOf(item) > -1) { + return; + } + + item.unuse?.(); + this._pools.push(item); + } + +} + +export interface PoolItem { + reuse: () => void; + unuse: () => void; +} \ No newline at end of file diff --git a/src/models/version.ts b/src/models/version.ts new file mode 100644 index 0000000..061ebe8 --- /dev/null +++ b/src/models/version.ts @@ -0,0 +1,2 @@ +/** Version of TSRPC */ +export const TSRPC_VERSION = '__TSRPC_VERSION__'; \ No newline at end of file diff --git a/src/server/base/ApiCall.ts b/src/server/base/ApiCall.ts new file mode 100644 index 0000000..9a684eb --- /dev/null +++ b/src/server/base/ApiCall.ts @@ -0,0 +1,215 @@ +import { TSBuffer } from 'tsbuffer'; +import { ApiService, TransportDataUtil } from "tsrpc-base-client"; +import { ApiReturn, BaseServiceType, ServerOutputData, TsrpcError, TsrpcErrorData, TsrpcErrorType } from "tsrpc-proto"; +import { PrefixLogger } from "../models/PrefixLogger"; +import { BaseCall, BaseCallOptions } from "./BaseCall"; +import { BaseConnection } from './BaseConnection'; + +export interface ApiCallOptions extends BaseCallOptions { + /** Which service the Call is belong to */ + service: ApiService, + /** Only exists in long connection, it is used to associate request and response. + * It is created by the client, and the server would return the same value in `ApiReturn`. + */ + sn?: number, + /** Request Data */ + req: Req +} + +/** + * A call request by `client.callApi()` + * @typeParam Req - Type of request + * @typeParam Res - Type of response + * @typeParam ServiceType - The same `ServiceType` to server, it is used for code auto hint. + */ +export abstract class ApiCall extends BaseCall { + readonly type = 'api' as const; + + /** + * Which `ApiService` the request is calling for + */ + readonly service!: ApiService; + /** Only exists in long connection, it is used to associate request and response. + * It is created by the client, and the server would return the same value in `ApiReturn`. + */ + readonly sn?: number; + /** + * Request data from the client, type of it is checked by the framework already. + */ + readonly req: Req; + + constructor(options: ApiCallOptions, logger?: PrefixLogger) { + super(options, logger ?? new PrefixLogger({ + logger: options.conn.logger, + prefixs: [`[Api:${options.service.name}]${options.sn !== undefined ? ` SN=${options.sn}` : ''}`] + })); + + this.sn = options.sn; + this.req = options.req; + } + + protected _return?: ApiReturn; + /** + * Response Data that sent already. + * `undefined` means no return data is sent yet. (Never `call.succ()` and `call.error()`) + */ + public get return(): ApiReturn | undefined { + return this._return; + } + + protected _usedTime: number | undefined; + /** Time from received req to send return data */ + public get usedTime(): number | undefined { + return this._usedTime; + } + + /** + * Send a successful `ApiReturn` with response data + * @param res - Response data + * @returns Promise resolved means the buffer is sent to kernel + */ + succ(res: Res): Promise { + return this._prepareReturn({ + isSucc: true, + res: res + }) + } + + /** + * Send a error `ApiReturn` with a `TsrpcError` + * @returns Promise resolved means the buffer is sent to kernel + */ + error(message: string, info?: Partial): Promise; + error(err: TsrpcError): Promise; + error(errOrMsg: string | TsrpcError, data?: Partial): Promise { + let error: TsrpcError = typeof errOrMsg === 'string' ? new TsrpcError(errOrMsg, data) : errOrMsg; + return this._prepareReturn({ + isSucc: false, + err: error + }) + }; + + + protected async _prepareReturn(ret: ApiReturn): Promise { + if (this._return) { + return; + } + this._return = ret; + + // Pre Flow + let preFlow = await this.server.flows.preApiReturnFlow.exec({ call: this, return: ret }, this.logger); + // Stopped! + if (!preFlow) { + this.logger.debug('[preApiReturnFlow]', 'Canceled') + return; + } + ret = preFlow.return; + + // record & log ret + this._usedTime = Date.now() - this.startTime; + if (ret.isSucc) { + this.logger.log('[ApiRes]', `${this.usedTime}ms`, this.server.options.logResBody ? ret.res : ''); + } + else { + if (ret.err.type === TsrpcErrorType.ApiError) { + this.logger.log('[ApiErr]', `${this.usedTime}ms`, ret.err, 'req=', this.req); + } + else { + this.logger.error(`[ApiErr]`, `${this.usedTime}ms`, ret.err, 'req=', this.req) + } + } + + // Do send! + this._return = ret; + let opSend = await this._sendReturn(ret); + if (!opSend.isSucc) { + if (opSend.canceledByFlow) { + this.logger.debug(`[${opSend.canceledByFlow}]`, 'Canceled'); + } + else { + this.logger.error('[SendDataErr]', opSend.errMsg); + if (ret.isSucc || ret.err.type === TsrpcErrorType.ApiError) { + this._return = undefined; + this.server.onInternalServerError({ message: opSend.errMsg, name: 'SendReturnErr' }, this) + } + } + + return; + } + + // Post Flow + await this.server.flows.postApiReturnFlow.exec(preFlow, this.logger); + } + + protected async _sendReturn(ret: ApiReturn): ReturnType> { + // Encode + let opServerOutput = ApiCall.encodeApiReturn(this.server.tsbuffer, this.service, ret, this.conn.dataType, this.sn);; + if (!opServerOutput.isSucc) { + this.server.onInternalServerError({ message: opServerOutput.errMsg, stack: ' |- TransportDataUtil.encodeApiReturn\n |- ApiCall._sendReturn' }, this); + return opServerOutput; + } + + let opSend = await this.conn.sendData(opServerOutput.output); + if (!opSend.isSucc) { + return opSend; + } + return opSend; + } + + static encodeApiReturn(tsbuffer: TSBuffer, service: ApiService, apiReturn: ApiReturn, type: 'text', sn?: number): EncodeApiReturnOutput + static encodeApiReturn(tsbuffer: TSBuffer, service: ApiService, apiReturn: ApiReturn, type: 'buffer', sn?: number): EncodeApiReturnOutput + static encodeApiReturn(tsbuffer: TSBuffer, service: ApiService, apiReturn: ApiReturn, type: 'json', sn?: number): EncodeApiReturnOutput + static encodeApiReturn(tsbuffer: TSBuffer, service: ApiService, apiReturn: ApiReturn, type: 'text' | 'buffer' | 'json', sn?: number): EncodeApiReturnOutput | EncodeApiReturnOutput | EncodeApiReturnOutput; + static encodeApiReturn(tsbuffer: TSBuffer, service: ApiService, apiReturn: ApiReturn, type: 'text' | 'buffer' | 'json', sn?: number): EncodeApiReturnOutput | EncodeApiReturnOutput | EncodeApiReturnOutput { + if (type === 'buffer') { + let serverOutputData: ServerOutputData = { + sn: sn, + serviceId: sn !== undefined ? service.id : undefined + }; + if (apiReturn.isSucc) { + let op = tsbuffer.encode(apiReturn.res, service.resSchemaId); + if (!op.isSucc) { + return op; + } + serverOutputData.buffer = op.buf; + } + else { + serverOutputData.error = apiReturn.err; + } + + let op = TransportDataUtil.tsbuffer.encode(serverOutputData, 'ServerOutputData'); + return op.isSucc ? { isSucc: true, output: op.buf } : { isSucc: false, errMsg: op.errMsg }; + } + else { + apiReturn = { ...apiReturn }; + if (apiReturn.isSucc) { + let op = tsbuffer.encodeJSON(apiReturn.res, service.resSchemaId); + if (!op.isSucc) { + return op; + } + apiReturn.res = op.json; + } + else { + apiReturn.err = { + ...apiReturn.err + } + } + let json = sn == undefined ? apiReturn : [service.name, apiReturn, sn]; + return { isSucc: true, output: type === 'json' ? json : JSON.stringify(json) }; + } + } +} + +export type SendReturnMethod = (ret: ApiReturn) => ReturnType; + +export declare type EncodeApiReturnOutput = { + isSucc: true; + /** Encoded binary buffer */ + output: T; + errMsg?: undefined; +} | { + isSucc: false; + /** Error message */ + errMsg: string; + output?: undefined; +}; \ No newline at end of file diff --git a/src/server/base/BaseCall.ts b/src/server/base/BaseCall.ts new file mode 100644 index 0000000..904740f --- /dev/null +++ b/src/server/base/BaseCall.ts @@ -0,0 +1,30 @@ +import { ApiService, MsgService } from 'tsrpc-base-client'; +import { BaseServiceType } from 'tsrpc-proto'; +import { PrefixLogger } from '../models/PrefixLogger'; +import { BaseConnection } from './BaseConnection'; + +export interface BaseCallOptions { + /** Connection */ + conn: BaseConnection, + /** Which service the call is belong to */ + service: ApiService | MsgService +} + +export abstract class BaseCall { + readonly conn: BaseConnection; + readonly service: ApiService | MsgService; + /** Time that server created the call */ + readonly startTime: number; + readonly logger: PrefixLogger; + + constructor(options: BaseCallOptions, logger: PrefixLogger) { + this.conn = options.conn; + this.service = options.service; + this.startTime = Date.now(); + this.logger = logger; + } + + get server(): this['conn']['server'] { + return this.conn.server; + } +} \ No newline at end of file diff --git a/src/server/base/BaseConnection.ts b/src/server/base/BaseConnection.ts new file mode 100644 index 0000000..66742ab --- /dev/null +++ b/src/server/base/BaseConnection.ts @@ -0,0 +1,182 @@ +import { MsgHandlerManager, ParsedServerInput, TransportDataUtil } from "tsrpc-base-client"; +import { BaseServiceType } from "tsrpc-proto"; +import { PrefixLogger } from "../models/PrefixLogger"; +import { ApiCall } from "./ApiCall"; +import { BaseServer, MsgHandler } from "./BaseServer"; +import { MsgCall } from "./MsgCall"; + +export interface BaseConnectionOptions { + /** Created by server, each Call has a unique id. */ + id: string; + /** Client IP address */ + ip: string, + server: BaseServer, + dataType: 'text' | 'buffer' | 'json' +} + +export abstract class BaseConnection { + /** It is long connection or short connection */ + abstract readonly type: 'LONG' | 'SHORT'; + + protected abstract readonly ApiCallClass: { new(options: any): ApiCall }; + protected abstract readonly MsgCallClass: { new(options: any): MsgCall }; + + /** Connection unique ID */ + readonly id: string; + /** Client IP address */ + readonly ip: string; + readonly server: BaseServer; + readonly logger: PrefixLogger; + dataType: BaseConnectionOptions['dataType']; + + constructor(options: BaseConnectionOptions, logger: PrefixLogger) { + this.id = options.id; + this.ip = options.ip; + this.server = options.server; + this.logger = logger; + this.dataType = options.dataType; + } + + abstract get status(): ConnectionStatus; + /** Close the connection */ + abstract close(reason?: string): void; + + /** Send buffer (with pre-flow and post-flow) */ + async sendData(data: string | Uint8Array | object, call?: ApiCall): Promise<{ isSucc: true } | { isSucc: false, errMsg: string, canceledByFlow?: string }> { + // Pre Flow + let pre = await this.server.flows.preSendDataFlow.exec({ conn: this, data: data, call: call }, call?.logger || this.logger); + if (!pre) { + return { isSucc: false, errMsg: 'Canceled by preSendDataFlow', canceledByFlow: 'preSendDataFlow' }; + } + data = pre.data; + + // @deprecated Pre Buffer Flow + if (data instanceof Uint8Array) { + let preBuf = await this.server.flows.preSendBufferFlow.exec({ conn: this, buf: data, call: call }, call?.logger || this.logger); + if (!preBuf) { + return { isSucc: false, errMsg: 'Canceled by preSendBufferFlow', canceledByFlow: 'preSendBufferFlow' }; + } + data = preBuf.buf; + } + + // debugBuf log + if (this.server.options.debugBuf) { + if (typeof data === 'string') { + (call?.logger ?? this.logger)?.debug(`[SendText] length=${data.length}`, data); + } + else if (data instanceof Uint8Array) { + (call?.logger ?? this.logger)?.debug(`[SendBuf] length=${data.length}`, data); + } + else { + (call?.logger ?? this.logger)?.debug('[SendJSON]', data); + } + } + + return this.doSendData(data, call); + } + protected abstract doSendData(data: string | Uint8Array | object, call?: ApiCall): Promise<{ isSucc: true } | { isSucc: false, errMsg: string }>; + + makeCall(input: ParsedServerInput): ApiCall | MsgCall { + if (input.type === 'api') { + return new this.ApiCallClass({ + conn: this, + service: input.service, + req: input.req, + sn: input.sn, + }) + } + else { + return new this.MsgCallClass({ + conn: this, + service: input.service, + msg: input.msg + }) + } + } + + /** + * Send message to the client, only be available when it is long connection. + * @param msgName + * @param msg - Message body + * @returns Promise resolved when the buffer is sent to kernel, it not represents the server received it. + */ + async sendMsg(msgName: T, msg: ServiceType['msg'][T]): ReturnType { + if (this.type === 'SHORT') { + this.logger.warn('[SendMsgErr]', `[${msgName}]`, 'Short connection cannot sendMsg'); + return { isSucc: false, errMsg: 'Short connection cannot sendMsg' } + } + + let service = this.server.serviceMap.msgName2Service[msgName as string]; + if (!service) { + this.logger.warn('[SendMsgErr]', `[${msgName}]`, `Invalid msg name: ${msgName}`); + return { isSucc: false, errMsg: `Invalid msg name: ${msgName}` } + } + + // Pre Flow + let pre = await this.server.flows.preSendMsgFlow.exec({ conn: this, service: service, msg: msg }, this.logger); + if (!pre) { + this.logger.debug('[preSendMsgFlow]', 'Canceled'); + return { isSucc: false, errMsg: 'Canceled by preSendMsgFlow', canceledByFlow: 'preSendMsgFlow' }; + } + msg = pre.msg; + + // Encode + let opServerOutput = TransportDataUtil.encodeServerMsg(this.server.tsbuffer, service, msg, this.dataType, this.type); + if (!opServerOutput.isSucc) { + this.logger.warn('[SendMsgErr]', `[${msgName}]`, opServerOutput.errMsg); + return opServerOutput; + } + + // Do send! + this.server.options.logMsg && this.logger.log('[SendMsg]', `[${msgName}]`, msg); + let opSend = await this.sendData(opServerOutput.output); + if (!opSend.isSucc) { + return opSend; + } + + // Post Flow + await this.server.flows.postSendMsgFlow.exec(pre, this.logger); + + return { isSucc: true }; + } + + // 多个Handler将异步并行执行 + private _msgHandlers?: MsgHandlerManager; + /** + * Add a message handler, + * duplicate handlers to the same `msgName` would be ignored. + * @param msgName + * @param handler + */ + listenMsg>(msgName: Msg, handler: MsgHandler): MsgHandler { + if (!this._msgHandlers) { + this._msgHandlers = new MsgHandlerManager(); + } + this._msgHandlers.addHandler(msgName as string, handler); + return handler; + }; + /** + * Remove a message handler + */ + unlistenMsg>(msgName: Msg, handler: Function): void { + if (!this._msgHandlers) { + this._msgHandlers = new MsgHandlerManager(); + } + this._msgHandlers.removeHandler(msgName as string, handler); + }; + /** + * Remove all handlers from a message + */ + unlistenMsgAll>(msgName: Msg): void { + if (!this._msgHandlers) { + this._msgHandlers = new MsgHandlerManager(); + } + this._msgHandlers.removeAllHandlers(msgName as string); + }; +} + +export enum ConnectionStatus { + Opened = 'OPENED', + Closing = 'CLOSING', + Closed = 'CLOSED' +} \ No newline at end of file diff --git a/src/server/base/BaseServer.ts b/src/server/base/BaseServer.ts new file mode 100644 index 0000000..09bf809 --- /dev/null +++ b/src/server/base/BaseServer.ts @@ -0,0 +1,937 @@ +import { ObjectId } from "bson"; +import chalk from "chalk"; +import * as path from "path"; +import { TSBuffer } from 'tsbuffer'; +import { ApiService, Counter, Flow, getCustomObjectIdTypes, MsgHandlerManager, MsgService, ParsedServerInput, ServiceMap, ServiceMapUtil, TransportDataUtil } from 'tsrpc-base-client'; +import { ApiReturn, ApiServiceDef, BaseServiceType, Logger, LogLevel, ServerInputData, ServiceProto, setLogLevel, TsrpcError, TsrpcErrorType } from 'tsrpc-proto'; +import { ApiCallInner } from "../inner/ApiCallInner"; +import { InnerConnection } from "../inner/InnerConnection"; +import { TerminalColorLogger } from '../models/TerminalColorLogger'; +import { ApiCall } from './ApiCall'; +import { BaseConnection } from './BaseConnection'; +import { MsgCall } from './MsgCall'; + +/** + * Abstract base class for TSRPC Server. + * Implement on a transportation protocol (like HTTP WebSocket) by extend it. + * @typeParam ServiceType - `ServiceType` from generated `proto.ts` + */ +export abstract class BaseServer{ + /** + * Start the server + * @throws + */ + abstract start(): Promise; + + /** + * Stop server immediately, not waiting for the requests ending. + */ + abstract stop(): Promise; + + protected _status: ServerStatus = ServerStatus.Closed; + get status(): ServerStatus { + return this._status; + } + + // 配置及其衍生项 + readonly proto: ServiceProto; + readonly options: BaseServerOptions; + readonly tsbuffer: TSBuffer; + readonly serviceMap: ServiceMap; + readonly logger: Logger; + + protected _connIdCounter = new Counter(1); + + /** + * Flow is a specific concept created by TSRPC family. + * All pre-flow can interrupt latter behaviours. + * All post-flow can NOT interrupt latter behaviours. + */ + readonly flows = { + // Conn Flows + /** After the connection is created */ + postConnectFlow: new Flow>(), + /** After the connection is disconnected */ + postDisconnectFlow: new Flow<{ conn: BaseConnection, reason?: string }>(), + + // Buffer Flows + /** + * Before processing the received data, usually be used to encryption / decryption. + * Return `null | undefined` would ignore the buffer. + */ + preRecvDataFlow: new Flow<{ conn: BaseConnection, data: string | Uint8Array | object, serviceName?: string }>(), + /** + * Before send out data to network, usually be used to encryption / decryption. + * Return `null | undefined` would not send the buffer. + */ + preSendDataFlow: new Flow<{ conn: BaseConnection, data: string | Uint8Array | object, call?: ApiCall }>(), + /** + * @deprecated Use `preRecvDataFlow` instead. + */ + preRecvBufferFlow: new Flow<{ conn: BaseConnection, buf: Uint8Array }>(), + /** + * @deprecated Use `preSendDataFlow` instead. + */ + preSendBufferFlow: new Flow<{ conn: BaseConnection, buf: Uint8Array, call?: ApiCall }>(), + + // ApiCall Flows + /** + * Before a API request is send. + * Return `null | undefined` would cancel the request. + */ + preApiCallFlow: new Flow(), + /** + * Before return the `ApiReturn` to the client. + * It may be used to change the return value, or return `null | undefined` to abort the request. + */ + preApiReturnFlow: new Flow<{ call: ApiCall, return: ApiReturn }>(), + /** + * After the `ApiReturn` is send. + * return `null | undefined` would NOT interrupt latter behaviours. + */ + postApiReturnFlow: new Flow<{ call: ApiCall, return: ApiReturn }>(), + /** + * After the api handler is executed. + * return `null | undefined` would NOT interrupt latter behaviours. + */ + postApiCallFlow: new Flow(), + + // MsgCall Flows + /** + * Before handle a `MsgCall` + */ + preMsgCallFlow: new Flow(), + /** + * After handlers of a `MsgCall` are executed. + * return `null | undefined` would NOT interrupt latter behaviours. + */ + postMsgCallFlow: new Flow(), + /** + * Before send out a message. + * return `null | undefined` would NOT interrupt latter behaviours. + */ + preSendMsgFlow: new Flow<{ conn: BaseConnection, service: MsgService, msg: any }>(), + /** + * After send out a message. + * return `null | undefined` would NOT interrupt latter behaviours. + */ + postSendMsgFlow: new Flow<{ conn: BaseConnection, service: MsgService, msg: any }>(), + } as const; + + // Handlers + private _apiHandlers: { [apiName: string]: ApiHandler | undefined } = {}; + // 多个Handler将异步并行执行 + private _msgHandlers: MsgHandlerManager = new MsgHandlerManager(); + + private static _isUncaughtExceptionProcessed = false; + /** + * It makes the `uncaughtException` and `unhandledRejection` not lead to the server stopping. + * @param logger + * @returns + */ + static processUncaughtException(logger: Logger) { + if (this._isUncaughtExceptionProcessed) { + return; + } + this._isUncaughtExceptionProcessed = true; + + process.on('uncaughtException', e => { + logger.error('[uncaughtException]', e); + }); + + process.on('unhandledRejection', e => { + logger.error('[unhandledRejection]', e); + }); + } + + constructor(proto: ServiceProto, options: BaseServerOptions) { + this.proto = proto; + this.options = options; + + // @deprecated jsonEnabled + if (this.options.json) { + this.options.jsonEnabled = true; + } + + this.tsbuffer = new TSBuffer({ + ...proto.types, + // Support mongodb/ObjectId + ...getCustomObjectIdTypes(ObjectId) + }, { + strictNullChecks: this.options.strictNullChecks + }); + this.serviceMap = ServiceMapUtil.getServiceMap(proto); + this.logger = this.options.logger; + setLogLevel(this.logger, this.options.logLevel); + + // Process uncaught exception, so that Node.js process would not exit easily + BaseServer.processUncaughtException(this.logger); + + // default flows onError handler + this._setDefaultFlowOnError(); + } + + protected _setDefaultFlowOnError() { + // API Flow Error: return [InternalServerError] + this.flows.preApiCallFlow.onError = (e, call) => { + if (e instanceof TsrpcError) { + call.error(e) + } + else { + this.onInternalServerError(e, call) + } + }; + this.flows.postApiCallFlow.onError = (e, call) => { + if (!call.return) { + if (e instanceof TsrpcError) { + call.error(e) + } + else { + this.onInternalServerError(e, call) + } + } + else { + call.logger.error('postApiCallFlow Error:', e); + } + }; + this.flows.preApiReturnFlow.onError = (e, last) => { + last.call['_return'] = undefined; + if (e instanceof TsrpcError) { + last.call.error(e) + } + else { + this.onInternalServerError(e, last.call) + } + } + this.flows.postApiReturnFlow.onError = (e, last) => { + if (!last.call.return) { + if (e instanceof TsrpcError) { + last.call.error(e) + } + else { + this.onInternalServerError(e, last.call) + } + } + } + } + + protected _pendingApiCallNum = 0; + + // #region receive buffer process flow + /** + * Process the buffer, after the `preRecvBufferFlow`. + */ + async _onRecvData(conn: BaseConnection, data: string | Uint8Array | object, serviceName?: string) { + // 非 OPENED 状态 停止接受新的请求 + if (!(conn instanceof InnerConnection) && this.status !== ServerStatus.Opened) { + return; + } + + // debugBuf log + if (this.options.debugBuf) { + if (typeof data === 'string') { + conn.logger?.debug(`[RecvText] length=${data.length}`, data); + } + else if (data instanceof Uint8Array) { + conn.logger?.debug(`[RecvBuf] length=${data.length}`, data); + } + else { + conn.logger?.debug('[RecvJSON]', data); + } + } + + // jsonEnabled 未启用,不支持文本请求 + if (typeof data === 'string' && !this.options.jsonEnabled) { + this.onInputDataError('JSON mode is not enabled, please use binary instead.', conn, data); + return; + } + + let pre = await this.flows.preRecvDataFlow.exec({ conn: conn, data: data, serviceName: serviceName }, conn.logger); + if (!pre) { + conn.logger.debug('[preRecvDataFlow] Canceled'); + return; + } + data = pre.data; + serviceName = pre.serviceName; + + // @deprecated preRecvBuffer + if (data instanceof Uint8Array) { + let preBuf = await this.flows.preRecvBufferFlow.exec({ conn: conn, buf: data }, conn.logger); + if (!preBuf) { + conn.logger.debug('[preRecvBufferFlow] Canceled'); + return; + } + data = preBuf.buf; + } + + // Parse Call + let opInput = this._parseServerInput(this.tsbuffer, this.serviceMap, data, serviceName); + if (!opInput.isSucc) { + this.onInputDataError(opInput.errMsg, conn, data); + return; + } + let call = conn.makeCall(opInput.result); + + if (call.type === 'api') { + await this._handleApiCall(call); + } + else { + await this._onMsgCall(call); + } + } + + protected async _handleApiCall(call: ApiCall) { + ++this._pendingApiCallNum; + await this._onApiCall(call); + if (--this._pendingApiCallNum === 0) { + this._gracefulStop?.rs(); + } + } + + protected async _onApiCall(call: ApiCall) { + let timeoutTimer = this.options.apiTimeout ? setTimeout(() => { + if (!call.return) { + call.error('Server Timeout', { + code: 'SERVER_TIMEOUT', + type: TsrpcErrorType.ServerError + }) + } + timeoutTimer = undefined; + }, this.options.apiTimeout) : undefined; + + // Pre Flow + let preFlow = await this.flows.preApiCallFlow.exec(call, call.logger); + if (!preFlow) { + if (timeoutTimer) { + clearTimeout(timeoutTimer); + timeoutTimer = undefined; + } + call.logger.debug('[preApiCallFlow] Canceled'); + return; + } + call = preFlow; + + // exec ApiCall + call.logger.log('[ApiReq]', this.options.logReqBody ? call.req : ''); + let { handler } = await this.getApiHandler(call.service, this._delayImplementApiPath, call.logger); + // exec API handler + if (handler) { + try { + await handler(call); + } + catch (e: any) { + if (e instanceof TsrpcError) { + call.error(e); + } + else { + this.onInternalServerError(e, call); + } + } + } + // 未找到ApiHandler,且未进行任何输出 + else { + call.error(`Unhandled API: ${call.service.name}`, { code: 'UNHANDLED_API', type: TsrpcErrorType.ServerError }); + } + + // Post Flow + await this.flows.postApiCallFlow.exec(call, call.logger); + + if (timeoutTimer) { + clearTimeout(timeoutTimer); + timeoutTimer = undefined; + } + + // Destroy call + // if (!call.return) { + // this.onInternalServerError({ message: 'API not return anything' }, call); + // } + } + + protected async _onMsgCall(call: MsgCall) { + // 收到Msg即可断开连接(短连接) + if (call.conn.type === 'SHORT') { + call.conn.close(); + } + + // Pre Flow + let preFlow = await this.flows.preMsgCallFlow.exec(call, call.logger); + if (!preFlow) { + call.logger.debug('[preMsgCallFlow]', 'Canceled') + return; + } + call = preFlow; + + // MsgHandler + this.options.logMsg && call.logger.log('[RecvMsg]', call.msg); + let promises = [ + // Conn Handlers + ...(call.conn['_msgHandlers']?.forEachHandler(call.service.name, call.logger, call) ?? []), + // Server Handlers + this._msgHandlers.forEachHandler(call.service.name, call.logger, call) + ]; + if (!promises.length) { + this.logger.debug('[UNHANDLED_MSG]', call.service.name); + } + else { + await Promise.all(promises); + } + + // Post Flow + await this.flows.postMsgCallFlow.exec(call, call.logger); + } + // #endregion + + // #region Api/Msg handler register + /** + * Associate a `ApiHandler` to a specific `apiName`. + * So that when `ApiCall` is receiving, it can be handled correctly. + * @param apiName + * @param handler + */ + implementApi>(apiName: Api, handler: ApiHandler): void { + if (this._apiHandlers[apiName as string]) { + throw new Error('Already exist handler for API: ' + apiName); + } + this._apiHandlers[apiName as string] = handler; + this.logger.log(`API implemented succ: [${apiName}]`); + }; + + /** 用于延迟注册 API */ + protected _delayImplementApiPath?: string; + + /** + * Auto call `imeplementApi` by traverse the `apiPath` and find all matched `PtlXXX` and `ApiXXX`. + * It is matched by checking whether the relative path and name of an API is consistent to the service name in `serviceProto`. + * Notice that the name prefix of protocol is `Ptl`, of API is `Api`. + * For example, `protocols/a/b/c/PtlTest` is matched to `api/a/b/c/ApiTest`. + * @param apiPath Absolute path or relative path to `process.cwd()`. + * @returns + */ + async autoImplementApi(apiPath: string, delay?: boolean): Promise<{ succ: string[], fail: string[] }> { + let apiServices = Object.values(this.serviceMap.apiName2Service) as ApiServiceDef[]; + let output: { succ: string[], fail: string[] } = { succ: [], fail: [] }; + + if (delay) { + this._delayImplementApiPath = apiPath; + return output; + } + + for (let svc of apiServices) { + //get api handler + let { handler } = await this.getApiHandler(svc, apiPath, this.logger) + + if (!handler) { + output.fail.push(svc.name); + continue; + } + + this.implementApi(svc.name, handler); + output.succ.push(svc.name); + } + + if (output.fail.length) { + this.logger.error(chalk.red(`${output.fail.length} API implemented failed: ` + output.fail.map(v => chalk.cyan.underline(v)).join(' '))) + } + + return output; + } + + async getApiHandler(svc: ApiServiceDef, apiPath?: string, logger?: Logger): Promise<{ handler: ApiHandler, errMsg?: undefined } | { handler?: undefined, errMsg: string }> { + if (this._apiHandlers[svc.name]) { + return { handler: this._apiHandlers[svc.name]! }; + } + + if (!apiPath) { + return { errMsg: `Api not implemented: ${svc.name}` }; + } + + // get api last name + let match = svc.name.match(/^(.+\/)*(.+)$/); + if (!match) { + logger?.error('Invalid apiName: ' + svc.name); + return { errMsg: `Invalid api name: ${svc.name}` }; + } + let handlerPath = match[1] || ''; + let handlerName = match[2]; + + // try import + let modulePath = path.resolve(apiPath, handlerPath, 'Api' + handlerName); + try { + var handlerModule = await import(modulePath); + } + catch (e: unknown) { + this.logger.error(chalk.red(`Implement API ${chalk.cyan.underline(`${svc.name}`)} failed:`), e); + return { errMsg: (e as Error).message }; + } + + // 优先 default,其次 ApiName 同名 + let handler = handlerModule.default ?? handlerModule['Api' + handlerName]; + if (handler) { + return { handler: handler }; + } + else { + return { errMsg: `Missing 'export Api${handlerName}' or 'export default' in: ${modulePath}` } + } + } + + /** + * Add a message handler, + * duplicate handlers to the same `msgName` would be ignored. + * @param msgName + * @param handler + */ + listenMsg>(msgName: Msg, handler: MsgHandler): MsgHandler { + this._msgHandlers.addHandler(msgName as string, handler); + return handler; + }; + /** + * Remove a message handler + */ + unlistenMsg>(msgName: Msg, handler: Function): void { + this._msgHandlers.removeHandler(msgName as string, handler); + }; + /** + * Remove all handlers from a message + */ + unlistenMsgAll>(msgName: Msg): void { + this._msgHandlers.removeAllHandlers(msgName as string); + }; + // #endregion + + /** + * Event when the server cannot parse input buffer to api/msg call. + * By default, it will return "Input Data Error" . + */ + async onInputDataError(errMsg: string, conn: BaseConnection, data: string | Uint8Array | object) { + if (this.options.debugBuf) { + if (typeof data === 'string') { + conn.logger.error(`[InputDataError] ${errMsg} length = ${data.length}`, data) + } + else if (data instanceof Uint8Array) { + conn.logger.error(`[InputBufferError] ${errMsg} length = ${data.length}`, data.subarray(0, 16)) + } + else { + conn.logger.error(`[InputJsonError] ${errMsg} `, data) + } + } + + const message = data instanceof Uint8Array ? `Invalid request buffer, please check the version of service proto.` : errMsg; + + // Short conn, send apiReturn with error + if (conn.type === 'SHORT') { + // Return API Error + let opEncode = ApiCall.encodeApiReturn(this.tsbuffer, { + type: 'api', + name: '?', + id: 0, + reqSchemaId: '?', + resSchemaId: '?' + }, { + isSucc: false, + err: new TsrpcError({ + message: message, + type: TsrpcErrorType.ServerError, + code: 'INPUT_DATA_ERR' + }) + }, conn.dataType) + if (opEncode.isSucc) { + let opSend = await conn.sendData(opEncode.output); + if (opSend.isSucc) { + return; + } + } + } + + conn.close(message); + } + + /** + * Event when a uncaught error (except `TsrpcError`) is throwed. + * By default, it will return a `TsrpcError` with message "Internal server error". + * If `returnInnerError` is `true`, the original error would be returned as `innerErr` property. + */ + onInternalServerError(err: { message: string, stack?: string, name?: string }, call: ApiCall) { + call.logger.error(err); + call.error('Internal Server Error', { + code: 'INTERNAL_ERR', + type: TsrpcErrorType.ServerError, + innerErr: call.conn.server.options.returnInnerError ? err.message : undefined + }); + } + + protected _gracefulStop?: { + rs: () => void + }; + /** + * Stop the server gracefully. + * Wait all API requests finished and then stop the server. + * @param maxWaitTime - The max time(ms) to wait before force stop the server. + * `undefined` and `0` means unlimited time. + */ + async gracefulStop(maxWaitTime?: number) { + if (this._status !== ServerStatus.Opened) { + throw new Error(`Cannot gracefulStop when server status is '${this._status}'.`); + } + + this.logger.log('[GracefulStop] Start graceful stop, waiting all ApiCall finished...') + this._status = ServerStatus.Closing; + let promiseWaitApi = new Promise(rs => { + this._gracefulStop = { + rs: rs + }; + }); + + return new Promise(rs => { + let maxWaitTimer: ReturnType | undefined; + if (maxWaitTime) { + maxWaitTimer = setTimeout(() => { + maxWaitTimer = undefined; + if (this._gracefulStop) { + this._gracefulStop = undefined; + this.logger.log('Graceful stop timeout, stop the server directly.'); + this.stop().then(() => { rs() }); + } + }, maxWaitTime); + } + + promiseWaitApi.then(() => { + this.logger.log('All ApiCall finished, continue stop server.'); + if (maxWaitTimer) { + clearTimeout(maxWaitTimer); + maxWaitTimer = undefined; + } + if (this._gracefulStop) { + this._gracefulStop = undefined; + this.stop().then(() => { rs() }); + } + }) + }) + } + + /** + * Execute API function through the inner connection, which is useful for unit test. + * + * **NOTICE** + * The `req` and return value is native JavaScript object which is not compatible to JSON. (etc. ArrayBuffer, Date, ObjectId) + * If you are using pure JSON as transfering, you may need use `callApiByJSON`. + * @param apiName + * @param req + * @param options + */ + callApi(apiName: T, req: ServiceType['api'][T]['req']): Promise> { + return new Promise(rs => { + // 确认是哪个Service + let service = this.serviceMap.apiName2Service[apiName as string]; + if (!service) { + let errMsg = `Cannot find service: ${apiName}`; + this.logger.warn(`[callApi]`, errMsg); + rs({ isSucc: false, err: new TsrpcError(errMsg, { type: TsrpcErrorType.ServerError, code: 'ERR_API_NAME' }) }); + return; + } + + let conn = new InnerConnection({ + dataType: 'json', + server: this, + id: '' + this._connIdCounter.getNext(), + ip: '', + return: { + type: 'raw', + rs: rs + } + }); + let call = new ApiCallInner({ + conn: conn, + req: req, + service: service + }); + this._handleApiCall(call); + }) + } + + /** + * Like `server.callApi`, but both input and output are pure JSON object, + * which can be `JSON.stringify()` and `JSON.parse()` directly. + * Types that not compatible to JSON, would be encoded and decoded automatically. + * @param apiName - The same with `server.callApi`, may be parsed from the URL. + * @param jsonReq - Request data in pure JSON + * @returns Encoded `ApiReturn` in pure JSON + */ + + /** + * Process JSON request by inner proxy, this is useful when you are porting to cloud function services. + * Both the input and output is pure JSON, ArrayBuffer/Date/ObjectId are encoded to string automatically. + * @param apiName - Parsed from URL + * @param req - Pure JSON + * @returns - Pure JSON + */ + async inputJSON(apiName: string, req: object): Promise> { + if (apiName.startsWith('/')) { + apiName = apiName.slice(1); + } + if (!this.serviceMap.apiName2Service[apiName]) { + return { + isSucc: false, + err: new TsrpcError(`Invalid service name: ${apiName}`, { + type: TsrpcErrorType.ServerError, + code: 'INPUT_DATA_ERR' + }) + }; + } + + return new Promise(rs => { + let conn = new InnerConnection({ + dataType: 'json', + server: this, + id: '' + this._connIdCounter.getNext(), + ip: '', + return: { + type: 'json', + rs: rs + } + }); + + this._onRecvData(conn, req, apiName); + }) + } + + /** + * Process input buffer by inner proxy, this is useful when you are porting to cloud function services. + * @param buf Input buffer (may be sent by TSRPC client) + * @returns Response buffer + */ + inputBuffer(buf: Uint8Array): Promise { + return new Promise(rs => { + let conn = new InnerConnection({ + dataType: 'buffer', + server: this, + id: '' + this._connIdCounter.getNext(), + ip: '', + return: { + type: 'buffer', + rs: rs + } + }); + + this._onRecvData(conn, buf); + }) + } + + protected _parseServerInput(tsbuffer: TSBuffer, serviceMap: ServiceMap, data: string | Uint8Array | object, serviceName?: string): { isSucc: true, result: ParsedServerInput } | { isSucc: false, errMsg: string } { + if (data instanceof Uint8Array) { + let opServerInputData = TransportDataUtil.tsbuffer.decode(data, 'ServerInputData'); + + if (!opServerInputData.isSucc) { + return opServerInputData; + } + let serverInput = opServerInputData.value as ServerInputData; + + // 确认是哪个Service + let service = serviceMap.id2Service[serverInput.serviceId]; + if (!service) { + return { isSucc: false, errMsg: `Cannot find service ID: ${serverInput.serviceId}` } + } + + // 解码Body + if (service.type === 'api') { + let opReq = tsbuffer.decode(serverInput.buffer, service.reqSchemaId); + return opReq.isSucc ? { + isSucc: true, + result: { + type: 'api', + service: service, + req: opReq.value, + sn: serverInput.sn + } + } : opReq + } + else { + let opMsg = tsbuffer.decode(serverInput.buffer, service.msgSchemaId); + return opMsg.isSucc ? { + isSucc: true, + result: { + type: 'msg', + service: service, + msg: opMsg.value + } + } : opMsg; + } + } + else { + let json: object; + if (typeof data === 'string') { + try { + json = JSON.parse(data); + } + catch (e: any) { + return { isSucc: false, errMsg: `Input is not a valid JSON string: ${e.message}` }; + } + } + else { + json = data; + } + + let body: any; + let sn: number | undefined; + + // Parse serviceName / body / sn + let service: ApiService | MsgService | undefined; + const oriServiceName = serviceName; + if (serviceName == undefined) { + if (!Array.isArray(json)) { + return { isSucc: false, errMsg: `Invalid request format: unresolved service name.` }; + } + serviceName = json[0] as string; + body = json[1]; + sn = json[2]; + } + else { + body = json; + } + + // Get Service + service = serviceMap.apiName2Service[serviceName] ?? serviceMap.msgName2Service[serviceName]; + if (!service) { + let errMsg = `Invalid service name: ${chalk.cyan.underline(serviceName)}`; + + // 可能是 JSON 模式下,jsonHostPath 未设置妥当的原因,此时给予友好提示 + if (oriServiceName) { + // TODO + } + + return { isSucc: false, errMsg: errMsg }; + } + + // Decode + if (service.type === 'api') { + let op = tsbuffer.decodeJSON(body, service.reqSchemaId); + if (!op.isSucc) { + return op; + } + return { + isSucc: true, + result: { + type: 'api', + service: service, + sn: sn, + req: op.value + } + }; + } + else { + let op = tsbuffer.decodeJSON(body, service.msgSchemaId); + if (!op.isSucc) { + return op; + } + return { + isSucc: true, + result: { + type: 'msg', + service: service, + msg: op.value + } + } + } + } + } +} + +export interface BaseServerOptions { + /** + * Whether to enable JSON compatible mode. + * When it is true, it can be compatible with typical HTTP JSON request (like RESTful API). + * + * @remarks + * The JSON request methods are: + * + * 1. Add `Content-type: application/json` to request header. + * 2. HTTP request is: `POST /{jsonUrlPath}/{apiName}`. + * 3. POST body is JSON string. + * 4. The response body is JSON string also. + * + * NOTICE: Buffer type are not supported due to JSON not support them. + * For security and efficient reason, we strongly recommend you use binary encoded transportation. + * + * @defaultValue `false` + */ + json: boolean, + /** @deprecated Use `json` instead. */ + jsonEnabled?: boolean, + + // TSBuffer相关 + /** + * Whether to strictly distinguish between `null` and `undefined` when encoding, decoding, and type checking. + * @defaultValue false + */ + strictNullChecks: boolean, + + /** + * Timeout for processing an `ApiCall`(ms) + * `0` and `undefined` means unlimited time + * @defaultValue 30000 + */ + apiTimeout: number | undefined, + + // LOG相关 + /** + * Logger for processing log + * @defaultValue `new TerminalColorLogger()` (print to console with color) + */ + logger: Logger; + /** + * The minimum log level of `logger` + * @defaultValue `debug` + */ + logLevel: LogLevel; + /** + * Whethere to print API request body into log (may increase log size) + * @defaultValue `true` + */ + logReqBody: boolean; + /** + * Whethere to print API response body into log (may increase log size) + * @defaultValue `true` + */ + logResBody: boolean; + /** + * Whethere to print `[SendMsg]` and `[RecvMsg]` log into log + * @defaultValue `true` + */ + logMsg: boolean; + + /** + * If `true`, all sent and received raw buffer would be print into the log. + * It may be useful when you do something for buffer encryption/decryption, and want to debug them. + */ + debugBuf?: boolean; + + /** + * When uncaught error throwed, + * whether to return the original error as a property `innerErr`. + * (May include some sensitive information, suggests set to `false` in production environment.) + * @defaultValue It depends on environment variable `NODE_ENV`. + * If `NODE_ENV` equals to `production`, the default value is `false`, otherwise is `true`. + */ + returnInnerError: boolean; +} + +export const defaultBaseServerOptions: BaseServerOptions = { + json: false, + strictNullChecks: false, + apiTimeout: 30000, + logger: new TerminalColorLogger, + logLevel: 'debug', + logReqBody: true, + logResBody: true, + logMsg: true, + returnInnerError: process.env['NODE_ENV'] !== 'production' +} + +export type ApiHandler = (call: Call) => void | Promise; +export type MsgHandler = (call: Call) => void | Promise; + +export enum ServerStatus { + Opening = 'OPENING', + Opened = 'OPENED', + Closing = 'CLOSING', + Closed = 'CLOSED', +} \ No newline at end of file diff --git a/src/server/base/MsgCall.ts b/src/server/base/MsgCall.ts new file mode 100644 index 0000000..6fdf97c --- /dev/null +++ b/src/server/base/MsgCall.ts @@ -0,0 +1,30 @@ +import { MsgService } from "tsrpc-base-client"; +import { BaseServiceType } from "tsrpc-proto"; +import { PrefixLogger } from "../models/PrefixLogger"; +import { BaseCall, BaseCallOptions } from "./BaseCall"; + +export interface MsgCallOptions extends BaseCallOptions { + service: MsgService, + msg: Msg +} + +/** + * A call request by `client.sendMsg()` + * @typeParam Msg - Type of the message + * @typeParam ServiceType - The same `ServiceType` to server, it is used for code auto hint. + */ +export abstract class MsgCall extends BaseCall { + readonly type = 'msg' as const; + + readonly service!: MsgService; + readonly msg: Msg; + + constructor(options: MsgCallOptions, logger?: PrefixLogger) { + super(options, logger ?? new PrefixLogger({ + logger: options.conn.logger, + prefixs: [`[Msg:${options.service.name}]`] + })); + + this.msg = options.msg; + } +} diff --git a/src/server/http/ApiCallHttp.ts b/src/server/http/ApiCallHttp.ts new file mode 100644 index 0000000..4922158 --- /dev/null +++ b/src/server/http/ApiCallHttp.ts @@ -0,0 +1,28 @@ +import { ApiReturn, BaseServiceType, TsrpcErrorType } from 'tsrpc-proto'; +import { ApiCall, ApiCallOptions } from '../base/ApiCall'; +import { HttpConnection } from './HttpConnection'; + +export interface ApiCallHttpOptions extends ApiCallOptions { + conn: HttpConnection; +} +export class ApiCallHttp extends ApiCall { + + readonly conn!: HttpConnection; + + constructor(options: ApiCallHttpOptions) { + super(options); + } + + protected async _sendReturn(ret: ApiReturn): Promise<{ isSucc: true } | { isSucc: false, errMsg: string }> { + if (this.conn.dataType === 'text') { + if (ret.isSucc) { + this.conn.httpRes.statusCode = 200; + } + else { + this.conn.httpRes.statusCode = ret.err.type === TsrpcErrorType.ApiError ? 200 : 500; + } + } + return super._sendReturn(ret); + } + +} \ No newline at end of file diff --git a/src/server/http/HttpConnection.ts b/src/server/http/HttpConnection.ts new file mode 100644 index 0000000..b349131 --- /dev/null +++ b/src/server/http/HttpConnection.ts @@ -0,0 +1,90 @@ +import * as http from "http"; +import { ParsedServerInput } from "tsrpc-base-client"; +import { BaseServiceType } from "tsrpc-proto"; +import { ApiCall } from "../base/ApiCall"; +import { BaseConnection, BaseConnectionOptions, ConnectionStatus } from '../base/BaseConnection'; +import { PrefixLogger } from "../models/PrefixLogger"; +import { ApiCallHttp } from "./ApiCallHttp"; +import { HttpServer } from './HttpServer'; +import { MsgCallHttp } from "./MsgCallHttp"; + +export interface HttpConnectionOptions extends BaseConnectionOptions { + server: HttpServer, + httpReq: http.IncomingMessage, + httpRes: http.ServerResponse, +} + +export class HttpConnection extends BaseConnection { + readonly type = 'SHORT'; + + protected readonly ApiCallClass = ApiCallHttp; + protected readonly MsgCallClass = MsgCallHttp; + + readonly httpReq: http.IncomingMessage; + readonly httpRes: http.ServerResponse; + readonly server!: HttpServer; + /** + * Whether the transportation of the connection is JSON encoded instead of binary encoded. + */ + readonly isJSON: boolean | undefined; + + /** + * In short connection, one connection correspond one call. + * It may be `undefined` when the request data is not fully received yet. + */ + call?: ApiCallHttp | MsgCallHttp; + + constructor(options: HttpConnectionOptions) { + super(options, new PrefixLogger({ + logger: options.server.logger, + prefixs: [`${options.ip} #${options.id}`] + })); + + this.httpReq = options.httpReq; + this.httpRes = options.httpRes; + } + + + public get status(): ConnectionStatus { + if (this.httpRes.socket?.writableFinished) { + return ConnectionStatus.Closed; + } + else if (this.httpRes.socket?.writableEnded) { + return ConnectionStatus.Closing; + } + else { + return ConnectionStatus.Opened; + } + } + + protected async doSendData(data: string | Uint8Array, call?: ApiCall): Promise<{ isSucc: true; } | { isSucc: false; errMsg: string; }> { + if (typeof data === 'string') { + this.httpRes.setHeader('Content-Type', 'application/json; charset=utf-8'); + } + this.httpRes.end(typeof data === 'string' ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength)); + return { isSucc: true } + } + + /** + * Close the connection, the reason would be attached to response header `X-TSRPC-Close-Reason`. + */ + close(reason?: string) { + if (this.status !== ConnectionStatus.Opened) { + return; + } + + // 有Reason代表是异常关闭 + if (reason) { + this.logger.warn(this.httpReq.method, this.httpReq.url, reason); + } + reason && this.httpRes.setHeader('X-TSRPC-Close-Reason', reason); + this.httpRes.end(); + } + + // HTTP Server 一个conn只有一个call,对应关联之 + makeCall(input: ParsedServerInput): ApiCallHttp | MsgCallHttp { + let call = super.makeCall(input) as ApiCallHttp | MsgCallHttp; + this.call = call; + return call; + } +} \ No newline at end of file diff --git a/src/server/http/HttpServer.ts b/src/server/http/HttpServer.ts new file mode 100644 index 0000000..683ace0 --- /dev/null +++ b/src/server/http/HttpServer.ts @@ -0,0 +1,241 @@ +import * as http from "http"; +import { BaseServiceType, ServiceProto } from 'tsrpc-proto'; +import { HttpUtil } from '../../models/HttpUtil'; +import { TSRPC_VERSION } from "../../models/version"; +import { BaseServer, BaseServerOptions, defaultBaseServerOptions, ServerStatus } from '../base/BaseServer'; +import { HttpConnection } from './HttpConnection'; + +/** + * TSRPC Server, based on HTTP connection. + * @typeParam ServiceType - `ServiceType` from generated `proto.ts` + */ +export class HttpServer extends BaseServer{ + readonly options!: HttpServerOptions; + + constructor(proto: ServiceProto, options?: Partial>) { + super(proto, { + ...defaultHttpServerOptions, + ...options + }); + + // 确保 jsonHostPath 以 / 开头和结尾 + this.options.jsonHostPath = this.options.jsonHostPath ? + (this.options.jsonHostPath.startsWith('/') ? '' : '/') + this.options.jsonHostPath + (this.options.jsonHostPath.endsWith('/') ? '' : '/') + : '/'; + } + + /** Native `http.Server` of NodeJS */ + httpServer?: http.Server; + /** + * {@inheritDoc BaseServer.start} + */ + start(): Promise { + if (this.httpServer) { + throw new Error('Server already started'); + } + + return new Promise(rs => { + this._status = ServerStatus.Opening; + this.logger.log(`Starting HTTP server ...`); + this.httpServer = http.createServer((httpReq, httpRes) => { + if (this.status !== ServerStatus.Opened) { + httpRes.statusCode = 503; + httpRes.end(); + return; + } + + let ip = HttpUtil.getClientIp(httpReq); + + httpRes.statusCode = 200; + httpRes.setHeader('X-Powered-By', `TSRPC ${TSRPC_VERSION}`); + if (this.options.cors) { + httpRes.setHeader('Access-Control-Allow-Origin', this.options.cors); + httpRes.setHeader('Access-Control-Allow-Headers', 'Content-Type,*'); + if (this.options.corsMaxAge) { + httpRes.setHeader('Access-Control-Max-Age', '' + this.options.corsMaxAge); + } + if (httpReq.method === 'OPTIONS') { + httpRes.writeHead(200); + httpRes.end(); + return; + } + }; + + let chunks: Buffer[] = []; + httpReq.on('data', data => { + chunks.push(data); + }); + + let conn: HttpConnection | undefined; + httpReq.on('end', async () => { + let isJSON = this.options.jsonEnabled && httpReq.headers["content-type"]?.toLowerCase().includes('application/json') + && httpReq.method === 'POST' && httpReq.url?.startsWith(this.options.jsonHostPath); + conn = new HttpConnection({ + server: this, + id: '' + this._connIdCounter.getNext(), + ip: ip, + httpReq: httpReq, + httpRes: httpRes, + dataType: isJSON ? 'text' : 'buffer' + }); + await this.flows.postConnectFlow.exec(conn, conn.logger); + + let buf = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks); + + if (conn.dataType === 'text') { + let url = conn.httpReq.url!; + + let urlEndPos = url.indexOf('?'); + if (urlEndPos > -1) { + url = url.slice(0, urlEndPos); + } + + let serviceName = url.slice(this.options.jsonHostPath.length); + this._onRecvData(conn, buf.toString(), serviceName); + } + else { + this._onRecvData(conn, buf); + } + }); + + // 处理连接异常关闭的情况 + httpRes.on('close', async () => { + // 客户端Abort + if (httpReq.aborted) { + if (conn) { + if (conn.call) { + conn.call.logger.log('[ReqAborted]'); + } + else { + conn.logger.log('[ReqAborted]'); + } + } + else { + this.logger.log('[ReqAborted]', { + url: httpReq.url, + method: httpReq.method, + ip: ip, + chunksLength: chunks.length, + chunksSize: chunks.sum(v => v.byteLength), + reqComplete: httpReq.complete, + headers: httpReq.rawHeaders + }); + } + } + // 非Abort,异常中断:直到连接关闭,Client也未end(Conn未生成) + else if (!conn) { + this.logger.warn('Socket closed before request end', { + url: httpReq.url, + method: httpReq.method, + ip: ip, + chunksLength: chunks.length, + chunksSize: chunks.sum(v => v.byteLength), + reqComplete: httpReq.complete, + headers: httpReq.rawHeaders + }); + } + // 有Conn,但连接非正常end:直到连接关闭,也未调用过 httpRes.end 方法 + else if (!httpRes.writableEnded) { + (conn.call?.logger || conn.logger).warn('Socket closed without response') + } + + // Post Flow + if (conn) { + await this.flows.postDisconnectFlow.exec({ conn: conn }, conn.logger) + } + }); + }); + + if (this.options.socketTimeout) { + this.httpServer.timeout = this.options.socketTimeout; + } + if (this.options.keepAliveTimeout) { + this.httpServer.keepAliveTimeout = this.options.keepAliveTimeout; + } + + this.httpServer.listen(this.options.port, () => { + this._status = ServerStatus.Opened; + this.logger.log(`Server started at ${this.options.port}.`); + rs(); + }) + }); + } + + /** + * {@inheritDoc BaseServer.stop} + */ + async stop(): Promise { + if (!this.httpServer) { + return; + } + this.logger.log('Stopping server...'); + + return new Promise((rs) => { + this._status = ServerStatus.Closing; + + // 立即close,不再接受新请求 + // 等所有连接都断开后rs + this.httpServer?.close(err => { + this._status = ServerStatus.Closed; + this.httpServer = undefined; + + if (err) { + this.logger.error(err); + } + this.logger.log('Server stopped'); + rs(); + }); + }) + + } +} + +export interface HttpServerOptions extends BaseServerOptions { + /** Which port the HTTP server listen to */ + port: number, + /** + * Passed to the `timeout` property to the native `http.Server` of NodeJS, in milliseconds. + * `0` and `undefined` will disable the socket timeout behavior. + * NOTICE: this `socketTimeout` be `undefined` only means disabling of the socket timeout, the `apiTimeout` is still working. + * `socketTimeout` should always greater than `apiTimeout`. + * @defaultValue `undefined` + * @see {@link https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_server_timeout} + */ + socketTimeout?: number, + /** + * Passed to the `keepAliveTimeout` property to the native `http.Server` of NodeJS, in milliseconds. + * It means keep-alive timeout of HTTP socket connection. + * @defaultValue 5000 (from NodeJS) + * @see {@link https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_server_keepalivetimeout} + */ + keepAliveTimeout?: number, + /** + * Response header value of `Access-Control-Allow-Origin`. + * If this has any value, it would also set `Access-Control-Allow-Headers` as `*`. + * `undefined` means no CORS header. + * @defaultValue `*` + */ + cors?: string, + /** + * Response header value of `Access-Control-Allow-Origin`. + * @defaultValue `3600` + */ + corsMaxAge?: number, + + /** + * Actual URL path is `${jsonHostPath}/${apiName}`. + * For example, if `jsonHostPath` is `'/api'`, then you can send `POST /api/a/b/c/Test` to call API `a/b/c/Test`. + * @defaultValue `'/'` + */ + jsonHostPath: string +} + +export const defaultHttpServerOptions: HttpServerOptions = { + ...defaultBaseServerOptions, + port: 3000, + cors: '*', + corsMaxAge: 3600, + jsonHostPath: '/', + + // TODO: keep-alive time (to SLB) +} \ No newline at end of file diff --git a/src/server/http/MsgCallHttp.ts b/src/server/http/MsgCallHttp.ts new file mode 100644 index 0000000..cb24166 --- /dev/null +++ b/src/server/http/MsgCallHttp.ts @@ -0,0 +1,16 @@ +import { BaseServiceType } from "tsrpc-proto"; +import { MsgCall, MsgCallOptions } from "../base/MsgCall"; +import { HttpConnection } from "./HttpConnection"; + +export interface MsgCallHttpOptions extends MsgCallOptions { + conn: HttpConnection; +} +export class MsgCallHttp extends MsgCall { + + readonly conn!: HttpConnection; + + constructor(options: MsgCallHttpOptions) { + super(options); + } + +} \ No newline at end of file diff --git a/src/server/inner/ApiCallInner.ts b/src/server/inner/ApiCallInner.ts new file mode 100644 index 0000000..fe7b2c3 --- /dev/null +++ b/src/server/inner/ApiCallInner.ts @@ -0,0 +1,31 @@ +import { ApiReturn, BaseServiceType } from 'tsrpc-proto'; +import { ApiCall, ApiCallOptions } from '../base/ApiCall'; +import { InnerConnection } from './InnerConnection'; + +export interface ApiCallInnerOptions extends ApiCallOptions { + conn: InnerConnection; +} +export class ApiCallInner extends ApiCall { + + readonly conn!: InnerConnection; + + constructor(options: ApiCallInnerOptions) { + super(options); + } + + protected async _sendReturn(ret: ApiReturn): Promise<{ isSucc: true } | { isSucc: false, errMsg: string }> { + if (this.conn.return.type === 'raw') { + // Validate Res + if (ret.isSucc) { + let resValidate = this.server.tsbuffer.validate(ret.res, this.service.resSchemaId); + if (!resValidate.isSucc) { + return resValidate; + } + } + return this.conn.sendData(ret); + } + + return super._sendReturn(ret); + } + +} \ No newline at end of file diff --git a/src/server/inner/InnerConnection.ts b/src/server/inner/InnerConnection.ts new file mode 100644 index 0000000..3ddc96a --- /dev/null +++ b/src/server/inner/InnerConnection.ts @@ -0,0 +1,79 @@ +import { ApiReturn, TsrpcError, TsrpcErrorType } from "tsrpc-proto"; +import { ApiCall, BaseConnection, BaseServiceType, PrefixLogger, TransportDataUtil } from "../.."; +import { BaseConnectionOptions, ConnectionStatus } from "../base/BaseConnection"; +import { ApiCallInner } from "./ApiCallInner"; + +export interface InnerConnectionOptions extends BaseConnectionOptions { + return: { + type: 'raw' | 'json', + rs: (ret: ApiReturn) => void; + } | { + type: 'buffer', + rs: (ret: Uint8Array) => void; + } +} + +/** + * Server can `callApi` it self by using this inner connection + */ +export class InnerConnection extends BaseConnection { + readonly type = 'SHORT'; + + protected readonly ApiCallClass = ApiCallInner; + protected readonly MsgCallClass = null as any; + + return!: InnerConnectionOptions['return']; + + constructor(options: InnerConnectionOptions) { + super(options, new PrefixLogger({ + logger: options.server.logger, + prefixs: [`Inner #${options.id}`] + })); + + this.return = options.return; + } + + private _status: ConnectionStatus = ConnectionStatus.Opened; + get status(): ConnectionStatus { + return this._status; + } + + close(reason?: string): void { + this.doSendData({ + isSucc: false, + err: new TsrpcError(reason ?? 'Internal Server Error', { + type: TsrpcErrorType.ServerError, + code: 'CONN_CLOSED', + reason: reason + }) + }); + } + + protected async doSendData(data: Uint8Array | ApiReturn, call?: ApiCall): Promise<{ isSucc: true; } | { isSucc: false; errMsg: string; }> { + this._status = ConnectionStatus.Closed; + + if (this.return.type === 'buffer') { + if (!(data instanceof Uint8Array)) { + // encode tsrpc error + if (!data.isSucc) { + let op = TransportDataUtil.tsbuffer.encode({ + error: data.err + }, 'ServerOutputData'); + if (op.isSucc) { + return this.doSendData(op.buf, call); + } + } + return { isSucc: false, errMsg: 'Error data type' }; + } + this.return.rs(data); + return { isSucc: true } + } + else { + if (data instanceof Uint8Array) { + return { isSucc: false, errMsg: 'Error data type' }; + } + this.return.rs(data); + return { isSucc: true } + } + } +} \ No newline at end of file diff --git a/src/server/models/PrefixLogger.ts b/src/server/models/PrefixLogger.ts new file mode 100644 index 0000000..8ede758 --- /dev/null +++ b/src/server/models/PrefixLogger.ts @@ -0,0 +1,41 @@ +import { Logger } from 'tsrpc-proto'; + +export interface PrefixLoggerOptions { + logger: Logger + prefixs: (string | (() => string))[]; +} + +/** + * Auto add prefix using existed `Logger` + */ +export class PrefixLogger implements Logger { + + readonly logger: PrefixLoggerOptions['logger']; + readonly prefixs: PrefixLoggerOptions['prefixs']; + + constructor(options: PrefixLoggerOptions) { + this.logger = options.logger; + this.prefixs = options.prefixs; + } + + getPrefix(): string[] { + return this.prefixs.map(v => typeof v === 'string' ? v : v()); + } + + log(...args: any[]) { + this.logger.log(...this.getPrefix().concat(args)); + } + + debug(...args: any[]) { + this.logger.debug(...this.getPrefix().concat(args)); + } + + warn(...args: any[]) { + this.logger.warn(...this.getPrefix().concat(args)); + } + + error(...args: any[]) { + this.logger.error(...this.getPrefix().concat(args)); + } + +} \ No newline at end of file diff --git a/src/server/models/TerminalColorLogger.ts b/src/server/models/TerminalColorLogger.ts new file mode 100644 index 0000000..0665985 --- /dev/null +++ b/src/server/models/TerminalColorLogger.ts @@ -0,0 +1,54 @@ +import chalk from "chalk"; +import { Logger } from "tsrpc-proto"; + +export interface TerminalColorLoggerOptions { + /** + * Process ID prefix + * @defaultValue `process.pid` + */ + pid: string, + + /** + * `undefined` represents not print time + * @defaultValue 'yyyy-MM-dd hh:mm:ss' + */ + timeFormat?: string +} + +/** + * Print log to terminal, with color. + */ +export class TerminalColorLogger implements Logger { + + options: TerminalColorLoggerOptions = { + pid: process.pid.toString(), + timeFormat: 'yyyy-MM-dd hh:mm:ss' + } + + private _pid: string; + constructor(options?: Partial) { + Object.assign(this.options, options); + this._pid = this.options.pid ? `<${this.options.pid}> ` : ''; + } + + private _time(): string { + return this.options.timeFormat ? new Date().format(this.options.timeFormat) : ''; + } + + debug(...args: any[]) { + console.debug.call(console, chalk.gray(`${this._pid}${this._time()}`), chalk.cyan('[DEBUG]'), ...args); + } + + log(...args: any[]) { + console.log.call(console, chalk.gray(`${this._pid}${this._time()}`), chalk.green('[INFO]'), ...args); + } + + warn(...args: any[]) { + console.warn.call(console, chalk.gray(`${this._pid}${this._time()}`), chalk.yellow('[WARN]'), ...args); + } + + error(...args: any[]) { + console.error.call(console, chalk.gray(`${this._pid}${this._time()}`), chalk.red('[ERROR]'), ...args); + } + +} \ No newline at end of file diff --git a/src/server/ws/ApiCallWs.ts b/src/server/ws/ApiCallWs.ts new file mode 100644 index 0000000..8a1efb9 --- /dev/null +++ b/src/server/ws/ApiCallWs.ts @@ -0,0 +1,27 @@ +import { ApiReturn, BaseServiceType } from 'tsrpc-proto'; +import { ApiCall, ApiCallOptions, SendReturnMethod } from '../base/ApiCall'; +import { ConnectionStatus } from '../base/BaseConnection'; +import { WsConnection } from './WsConnection'; + +export interface ApiCallWsOptions extends ApiCallOptions { + conn: WsConnection +} + +export class ApiCallWs extends ApiCall { + + readonly conn!: WsConnection; + + constructor(options: ApiCallWsOptions) { + super(options); + } + + protected async _prepareReturn(ret: ApiReturn): Promise { + if (this.conn.status !== ConnectionStatus.Opened) { + this.logger.error('[SendReturnErr]', 'WebSocket is not opened', ret); + this._return = ret; + return; + } + + return super._prepareReturn(ret); + } +} \ No newline at end of file diff --git a/src/server/ws/MsgCallWs.ts b/src/server/ws/MsgCallWs.ts new file mode 100644 index 0000000..58407fb --- /dev/null +++ b/src/server/ws/MsgCallWs.ts @@ -0,0 +1,16 @@ +import { BaseServiceType } from "tsrpc-proto"; +import { MsgCall, MsgCallOptions } from "../base/MsgCall"; +import { WsConnection } from "./WsConnection"; + +export interface MsgCallWsOptions extends MsgCallOptions { + conn: WsConnection; +} +export class MsgCallWs extends MsgCall { + + readonly conn!: WsConnection; + + constructor(options: MsgCallWsOptions) { + super(options); + } + +} \ No newline at end of file diff --git a/src/server/ws/WsConnection.ts b/src/server/ws/WsConnection.ts new file mode 100644 index 0000000..9695329 --- /dev/null +++ b/src/server/ws/WsConnection.ts @@ -0,0 +1,141 @@ +import * as http from "http"; +import { TransportDataUtil } from "tsrpc-base-client"; +import { BaseServiceType } from "tsrpc-proto"; +import * as WebSocket from "ws"; +import { BaseConnection, BaseConnectionOptions, ConnectionStatus } from "../base/BaseConnection"; +import { PrefixLogger } from "../models/PrefixLogger"; +import { ApiCallWs } from "./ApiCallWs"; +import { MsgCallWs } from "./MsgCallWs"; +import { WsServer } from "./WsServer"; + +export interface WsConnectionOptions extends BaseConnectionOptions { + server: WsServer, + ws: WebSocket, + httpReq: http.IncomingMessage, + onClose: (conn: WsConnection, code: number, reason: string) => Promise, + dataType: 'text' | 'buffer', + isDataTypeConfirmed?: boolean +} + +/** + * Connected client + */ +export class WsConnection extends BaseConnection { + readonly type = "LONG"; + + protected readonly ApiCallClass = ApiCallWs; + protected readonly MsgCallClass = MsgCallWs; + + readonly ws: WebSocket; + readonly httpReq: http.IncomingMessage; + readonly server!: WsServer; + dataType!: 'text' | 'buffer'; + // 是否已经收到了客户端的第一条消息,以确认了客户端的 dataType + isDataTypeConfirmed?: boolean; + + constructor(options: WsConnectionOptions) { + super(options, new PrefixLogger({ + logger: options.server.logger, + prefixs: [`${options.ip} Conn#${options.id}`] + })); + this.ws = options.ws; + this.httpReq = options.httpReq; + this.isDataTypeConfirmed = options.isDataTypeConfirmed; + + if (this.server.options.heartbeatWaitTime) { + const timeout = this.server.options.heartbeatWaitTime; + this._heartbeatInterval = setInterval(() => { + if (Date.now() - this._lastHeartbeatTime > timeout) { + this.ws.close(3001, 'Receive heartbeat timeout'); + } + }, timeout); + } + + // Init WS + this.ws.onclose = async e => { + if (this._heartbeatInterval) { + clearInterval(this._heartbeatInterval); + this._heartbeatInterval = undefined; + } + await options.onClose(this, e.code, e.reason); + this._rsClose?.(); + }; + this.ws.onerror = e => { this.logger.warn('[ClientErr]', e.error) }; + this.ws.onmessage = e => { + let data: Buffer | string; + if (e.data instanceof ArrayBuffer) { + data = Buffer.from(e.data); + } + else if (Array.isArray(e.data)) { + data = Buffer.concat(e.data) + } + else if (Buffer.isBuffer(e.data)) { + data = e.data; + } + else { + data = e.data; + } + + // 心跳包,直接回复 + if (data instanceof Buffer && data.equals(TransportDataUtil.HeartbeatPacket)) { + this.server.options.debugBuf && this.logger.log('[Heartbeat] Recv ping and send pong', TransportDataUtil.HeartbeatPacket); + this._lastHeartbeatTime = Date.now(); + this.ws.send(TransportDataUtil.HeartbeatPacket); + return; + } + + // dataType 尚未确认,自动检测 + if (!this.isDataTypeConfirmed) { + if (this.server.options.jsonEnabled && typeof data === 'string') { + this.dataType = 'text'; + } + else { + this.dataType = 'buffer'; + } + + this.isDataTypeConfirmed = true; + } + + // dataType 已确认 + this.server._onRecvData(this, data) + }; + } + + private _lastHeartbeatTime = 0; + private _heartbeatInterval?: ReturnType; + + get status(): ConnectionStatus { + if (this.ws.readyState === WebSocket.CLOSED) { + return ConnectionStatus.Closed; + } + if (this.ws.readyState === WebSocket.CLOSING) { + return ConnectionStatus.Closing; + } + return ConnectionStatus.Opened; + } + + protected async doSendData(data: string | Uint8Array, call?: ApiCallWs): Promise<{ isSucc: true; } | { isSucc: false; errMsg: string; }> { + let opSend = await new Promise<{ isSucc: true } | { isSucc: false, errMsg: string }>((rs) => { + this.ws.send(data, e => { + e ? rs({ isSucc: false, errMsg: e.message || 'Send buffer error' }) : rs({ isSucc: true }); + }) + }); + if (!opSend.isSucc) { + return opSend; + } + + return { isSucc: true } + } + + protected _rsClose?: () => void; + /** Close WebSocket connection */ + close(reason?: string): Promise { + // 已连接 Close之 + return new Promise(rs => { + this._rsClose = rs; + this.ws.close(1000, reason); + }).finally(() => { + this._rsClose = undefined + }) + } +} \ No newline at end of file diff --git a/src/server/ws/WsServer.ts b/src/server/ws/WsServer.ts new file mode 100644 index 0000000..fd9b325 --- /dev/null +++ b/src/server/ws/WsServer.ts @@ -0,0 +1,241 @@ +import * as http from "http"; +import { EncodeOutput, TransportDataUtil } from "tsrpc-base-client"; +import { BaseServiceType, ServiceProto } from 'tsrpc-proto'; +import * as WebSocket from 'ws'; +import { Server as WebSocketServer } from 'ws'; +import { HttpUtil } from '../../models/HttpUtil'; +import { BaseServer, BaseServerOptions, defaultBaseServerOptions, ServerStatus } from '../base/BaseServer'; +import { WsConnection } from './WsConnection'; + +/** + * TSRPC Server, based on WebSocket connection. + * It can support realtime cases. + * @typeParam ServiceType - `ServiceType` from generated `proto.ts` + */ +export class WsServer extends BaseServer { + readonly options!: WsServerOptions; + + readonly connections: WsConnection[] = []; + private readonly _id2Conn: { [connId: string]: WsConnection | undefined } = {}; + + constructor(proto: ServiceProto, options?: Partial>) { + super(proto, { + ...defaultWsServerOptions, + ...options + }); + } + + private _wsServer?: WebSocketServer; + + /** + * {@inheritDoc BaseServer.start} + */ + start(): Promise { + if (this._wsServer) { + throw new Error('Server already started'); + } + this._status = ServerStatus.Opening; + return new Promise((rs, rj) => { + this.logger.log('Starting WebSocket server...'); + this._wsServer = new WebSocketServer({ + port: this.options.port + }, () => { + this.logger.log(`Server started at ${this.options.port}...`); + this._status = ServerStatus.Opened; + rs(); + }); + + this._wsServer.on('connection', this._onClientConnect); + this._wsServer.on('error', e => { + this.logger.error('[ServerError]', e); + rj(e); + }); + }) + } + + /** + * {@inheritDoc BaseServer.stop} + */ + async stop(): Promise { + // Closed Already + if (!this._wsServer) { + throw new Error('Server has not been started') + } + if (this._status === ServerStatus.Closed) { + throw new Error('Server is closed already'); + } + + this._status = ServerStatus.Closing; + + return new Promise(async (rs, rj) => { + await Promise.all(this.connections.map(v => v.close('Server Stop'))); + this._wsServer!.close(err => { err ? rj(err) : rs() }) + }).then(() => { + this.logger.log('Server stopped'); + this._status = ServerStatus.Closed; + this._wsServer = undefined; + }); + } + + private _onClientConnect = (ws: WebSocket, httpReq: http.IncomingMessage) => { + // 停止中 不再接受新的连接 + if (this._status !== ServerStatus.Opened) { + ws.close(1012); + return; + } + + // 推测 dataType 和 isDataTypeConfirmed + let isDataTypeConfirmed = true; + let dataType: 'text' | 'buffer'; + let protocols = httpReq.headers['sec-websocket-protocol']?.split(',').map(v => v.trim()).filter(v => !!v); + if (protocols?.includes('text')) { + dataType = 'text'; + } + else if (protocols?.includes('buffer')) { + dataType = 'buffer'; + } + else { + dataType = this.options.jsonEnabled ? 'text' : 'buffer'; + isDataTypeConfirmed = false; + } + + // Create Active Connection + let conn = new WsConnection({ + id: '' + this._connIdCounter.getNext(), + ip: HttpUtil.getClientIp(httpReq), + server: this, + ws: ws, + httpReq: httpReq, + onClose: this._onClientClose, + dataType: dataType, + isDataTypeConfirmed: isDataTypeConfirmed + }); + this.connections.push(conn); + this._id2Conn[conn.id] = conn; + + conn.logger.log('[Connected]', `ActiveConn=${this.connections.length}`); + this.flows.postConnectFlow.exec(conn, conn.logger); + }; + + private _onClientClose = async (conn: WsConnection, code: number, reason: string) => { + this.connections.removeOne(v => v.id === conn.id); + delete this._id2Conn[conn.id]; + conn.logger.log('[Disconnected]', `Code=${code} ${reason ? `Reason=${reason} ` : ''}ActiveConn=${this.connections.length}`) + + await this.flows.postDisconnectFlow.exec({ conn: conn, reason: reason }, conn.logger); + } + + /** + * Send the same message to many connections. + * No matter how many target connections are, the message would be only encoded once. + * @param msgName + * @param msg - Message body + * @param connIds - `id` of target connections, `undefined` means broadcast to every connections. + * @returns Send result, `isSucc: true` means the message buffer is sent to kernel, not represents the clients received. + */ + async broadcastMsg(msgName: T, msg: ServiceType['msg'][T], conns?: WsConnection[]): Promise<{ isSucc: true; } | { isSucc: false; errMsg: string; }> { + let connStr: string; + if (!conns) { + conns = this.connections; + connStr = '*'; + } + else { + connStr = conns ? conns.map(v => v.id).join(',') : '*'; + } + + if (!conns.length) { + return { isSucc: true }; + } + + if (this.status !== ServerStatus.Opened) { + this.logger.warn('[BroadcastMsgErr]', `[${msgName}]`, `[To:${connStr}]`, 'Server not open'); + return { isSucc: false, errMsg: 'Server not open' }; + } + + // GetService + let service = this.serviceMap.msgName2Service[msgName as string]; + if (!service) { + this.logger.warn('[BroadcastMsgErr]', `[${msgName}]`, `[To:${connStr}]`, 'Invalid msg name: ' + msgName); + return { isSucc: false, errMsg: 'Invalid msg name: ' + msgName }; + } + + // Encode group by dataType + let _opEncodeBuf: EncodeOutput | undefined; + let _opEncodeText: EncodeOutput | undefined; + const getOpEncodeBuf = () => { + if (!_opEncodeBuf) { + _opEncodeBuf = TransportDataUtil.encodeServerMsg(this.tsbuffer, service!, msg, 'buffer', 'LONG'); + } + return _opEncodeBuf; + } + const getOpEncodeText = () => { + if (!_opEncodeText) { + _opEncodeText = TransportDataUtil.encodeServerMsg(this.tsbuffer, service!, msg, 'text', 'LONG'); + } + return _opEncodeText; + } + + // 测试一下编码可以通过 + let op = conns.some(v => v.dataType === 'buffer') ? getOpEncodeBuf() : getOpEncodeText(); + if (!op.isSucc) { + this.logger.warn('[BroadcastMsgErr]', `[${msgName}]`, `[To:${connStr}]`, op.errMsg); + return op; + } + + this.options.logMsg && this.logger.log(`[BroadcastMsg]`, `[${msgName}]`, `[To:${connStr}]`, msg); + + // Batch send + let errMsgs: string[] = []; + return Promise.all(conns.map(async conn => { + // Pre Flow + let pre = await this.flows.preSendMsgFlow.exec({ conn: conn, service: service!, msg: msg }, this.logger); + if (!pre) { + conn.logger.debug('[preSendMsgFlow]', 'Canceled'); + return { isSucc: false, errMsg: 'Prevented by preSendMsgFlow' }; + } + msg = pre.msg; + + // Do send! + let opSend = await conn.sendData((conn.dataType === 'buffer' ? getOpEncodeBuf() : getOpEncodeText())!.output!); + if (!opSend.isSucc) { + return opSend; + } + + // Post Flow + this.flows.postSendMsgFlow.exec(pre, this.logger); + + return { isSucc: true }; + })).then(results => { + for (let i = 0; i < results.length; ++i) { + let op = results[i]; + if (!op.isSucc) { + errMsgs.push(`Conn#conns[i].id: ${op.errMsg}`) + }; + } + if (errMsgs.length) { + return { isSucc: false, errMsg: errMsgs.join('\n') } + } + else { + return { isSucc: true } + } + }) + }; +} + +export interface WsServerOptions extends BaseServerOptions { + /** Which port the WebSocket server is listen to */ + port: number; + + /** + * Close a connection if not receive heartbeat after the time (ms). + * This value should be greater than `client.heartbeat.interval`, for exmaple 2x of it. + * `undefined` or `0` represent disable this feature. + * @defaultValue `undefined` + */ + heartbeatWaitTime?: number; +}; + +const defaultWsServerOptions: WsServerOptions = { + ...defaultBaseServerOptions, + port: 3000 +} \ No newline at end of file diff --git a/test/Base.ts b/test/Base.ts new file mode 100644 index 0000000..00fd439 --- /dev/null +++ b/test/Base.ts @@ -0,0 +1 @@ +import 'k8w-extend-native'; \ No newline at end of file diff --git a/test/api/ApiObjId.ts b/test/api/ApiObjId.ts new file mode 100644 index 0000000..e28f321 --- /dev/null +++ b/test/api/ApiObjId.ts @@ -0,0 +1,10 @@ +import { ApiCall } from "../../src/server/base/ApiCall"; +import { ReqObjId, ResObjId } from "../proto/PtlObjId"; + +export async function ApiObjId(call: ApiCall) { + call.succ({ + id2: call.req.id1, + buf: call.req.buf, + date: call.req.date + }) +} \ No newline at end of file diff --git a/test/api/ApiTest.ts b/test/api/ApiTest.ts new file mode 100644 index 0000000..0070a92 --- /dev/null +++ b/test/api/ApiTest.ts @@ -0,0 +1,27 @@ +import { TsrpcError } from "tsrpc-proto"; +import { ApiCall } from "../../src/server/base/ApiCall"; +import { ReqTest, ResTest } from "../proto/PtlTest"; + +export async function ApiTest(call: ApiCall) { + if (call.req.name === 'InnerError') { + await new Promise(rs => { setTimeout(rs, 50) }) + throw new Error('Test InnerError') + } + else if (call.req.name === 'TsrpcError') { + await new Promise(rs => { setTimeout(rs, 50) }) + throw new TsrpcError('Test TsrpcError', { + code: 'CODE_TEST', + info: 'ErrInfo Test' + }); + } + else if (call.req.name === 'error') { + await new Promise(rs => { setTimeout(rs, 50) }) + call.error('Got an error') + } + else { + await new Promise(rs => { setTimeout(rs, 50) }) + call.succ({ + reply: 'Test reply: ' + call.req.name + }) + } +} \ No newline at end of file diff --git a/test/api/a/b/c/ApiTest.ts b/test/api/a/b/c/ApiTest.ts new file mode 100644 index 0000000..c254272 --- /dev/null +++ b/test/api/a/b/c/ApiTest.ts @@ -0,0 +1,21 @@ +import { TsrpcError } from "tsrpc-proto"; + +export async function ApiTest(call: any) { + if (call.req.name === 'InnerError') { + throw new Error('a/b/c/Test InnerError') + } + else if (call.req.name === 'TsrpcError') { + throw new TsrpcError('a/b/c/Test TsrpcError', { + code: 'CODE_TEST', + info: 'ErrInfo a/b/c/Test' + }); + } + else if (call.req.name === 'error') { + call.error('Got an error') + } + else { + call.succ({ + reply: 'a/b/c/Test reply: ' + call.req.name + }) + } +} \ No newline at end of file diff --git a/test/cases/http.test.ts b/test/cases/http.test.ts new file mode 100644 index 0000000..22ebc67 --- /dev/null +++ b/test/cases/http.test.ts @@ -0,0 +1,907 @@ +import { ObjectId } from 'bson'; +import { assert } from 'chai'; +import chalk from 'chalk'; +import * as path from "path"; +import { ServiceProto, TsrpcError, TsrpcErrorType } from 'tsrpc-proto'; +import { ApiCall, BaseServer, HttpConnection, MsgCall, TerminalColorLogger } from '../../src'; +import { HttpClient } from '../../src/client/http/HttpClient'; +import { HttpServer } from '../../src/server/http/HttpServer'; +import { PrefixLogger } from '../../src/server/models/PrefixLogger'; +import { ApiTest as ApiAbcTest } from '../api/a/b/c/ApiTest'; +import { ApiTest } from '../api/ApiTest'; +import { MsgChat } from '../proto/MsgChat'; +import { ReqTest, ResTest } from '../proto/PtlTest'; +import { serviceProto, ServiceType } from '../proto/serviceProto'; + +const serverLogger = new PrefixLogger({ + prefixs: [chalk.bgGreen.white(' Server ')], + logger: new TerminalColorLogger({ pid: 'Server' }) +}); +const clientLogger = new PrefixLogger({ + prefixs: [chalk.bgBlue.white(' Client ')], + logger: new TerminalColorLogger({ pid: 'Client' }) +}) + +const getProto = () => Object.merge({}, serviceProto) as ServiceProto; + +async function testApi(server: HttpServer, client: HttpClient) { + // Succ + assert.deepStrictEqual(await client.callApi('Test', { + name: 'Req1' + }), { + isSucc: true, + res: { + reply: 'Test reply: Req1' + } + }); + assert.deepStrictEqual(await client.callApi('a/b/c/Test', { + name: 'Req2' + }), { + isSucc: true, + res: { + reply: 'a/b/c/Test reply: Req2' + } + }); + + // Inner error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'InnerError' + }); + delete ret.err!.innerErr.stack; + + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + code: 'INTERNAL_ERR', + type: TsrpcErrorType.ServerError, + innerErr: `${v} InnerError` + }) + }); + } + + // TsrpcError + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'TsrpcError' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError(`${v} TsrpcError`, { + code: 'CODE_TEST', + type: TsrpcErrorType.ApiError, + info: 'ErrInfo ' + v + }) + }); + } + + // call.error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'error' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Got an error', { + type: TsrpcErrorType.ApiError + }) + }); + } +} + +describe('HTTP Server & Client basic', function () { + it('implement API manually', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + await server.start(); + + server.implementApi('Test', ApiTest); + server.implementApi('a/b/c/Test', ApiAbcTest); + + let client = new HttpClient(getProto(), { + logger: clientLogger, + debugBuf: true + }) + + await testApi(server, client); + + await server.stop(); + }) + + it('extend call in handler', function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + type MyApiCall = ApiCall & { + value1?: string; + value2: string; + } + type MyMsgCall = MsgCall & { + value1?: string; + value2: string; + } + + server.implementApi('Test', (call: MyApiCall) => { + call.value1 = 'xxx'; + call.value2 = 'xxx'; + }); + server.listenMsg('Chat', (call: MyMsgCall) => { + call.msg.content; + call.value1 = 'xxx'; + call.value2 = 'xxx'; + }) + }) + + it('extend call in flow', function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + type MyApiCall = ApiCall & { + value1?: string; + value2: string; + } + type MyMsgCall = MsgCall & { + value1?: string; + value2: string; + } + type MyConn = HttpConnection & { + currentUser: { + uid: string, + nickName: string + } + } + + server.flows.postConnectFlow.push((conn: MyConn) => { + conn.currentUser.nickName = 'asdf'; + return conn; + }); + server.flows.postConnectFlow.exec(null as any as MyConn, console); + server.flows.preApiCallFlow.push((call: MyApiCall) => { + call.value2 = 'x'; + return call; + }); + server.flows.preSendMsgFlow.push((call: MyMsgCall) => { + call.value2 = 'f'; + return call; + }) + }) + + it('autoImplementApi', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 5000 + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new HttpClient(getProto(), { + logger: clientLogger + }) + + await testApi(server, client); + + await server.stop(); + }); + + it('sendMsg', async function () { + let server = new HttpServer(getProto(), { + port: 3001, + logger: serverLogger, + // debugBuf: true + }); + + await server.start(); + + let client = new HttpClient(getProto(), { + server: 'http://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + + return new Promise(rs => { + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + server.listenMsg('Chat', async v => { + assert.deepStrictEqual(v.msg, msg); + await server.stop(); + rs(); + }); + + client.sendMsg('Chat', msg); + }) + }) + + it('abort', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new HttpClient(getProto(), { + logger: clientLogger + }) + + let result: any | undefined; + let promise = client.callApi('Test', { name: 'aaaaaaaa' }); + let sn = client.lastSN; + setTimeout(() => { + client.abort(sn) + }, 10); + promise.then(v => { + result = v; + }); + + await new Promise(rs => { + setTimeout(() => { + assert.strictEqual(result, undefined); + rs(); + }, 150) + }) + + await server.stop(); + }); + + it('abortByKey', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new HttpClient(getProto(), { + logger: clientLogger + }) + + let result: any | undefined; + let result1: any | undefined; + + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + + client.callApi('Test', { name: 'bbbbbb' }).then(v => { result1 = v; }); + + setTimeout(() => { + client.abortByKey('XXX') + }, 10); + + await new Promise(rs => { + setTimeout(() => { + assert.strictEqual(result, undefined); + assert.deepStrictEqual(result1, { + isSucc: true, + res: { + reply: 'Test reply: bbbbbb' + } + }) + rs(); + }, 150) + }) + + await server.stop(); + }) + + it('abortAll', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new HttpClient(getProto(), { + logger: clientLogger + }) + + let result: any | undefined; + let result1: any | undefined; + + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + + client.callApi('Test', { name: 'bbbbbb' }).then(v => { result1 = v; }); + + setTimeout(() => { + client.abortAll() + }, 10); + + await new Promise(rs => { + setTimeout(() => { + assert.strictEqual(result, undefined); + assert.strictEqual(result1, undefined); + rs(); + }, 150) + }) + + await server.stop(); + }) + + it('pendingApis', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new HttpClient(getProto(), { + logger: clientLogger + }) + + for (let i = 0; i < 10; ++i) { + let promise = Promise.all(Array.from({ length: 10 }, () => new Promise(rs => { + let name = ['Req', 'InnerError', 'TsrpcError', 'error'][Math.random() * 4 | 0]; + let ret: any | undefined; + let promise = client.callApi('Test', { name: name }); + let sn = client.lastSN; + let abort = Math.random() > 0.5; + if (abort) { + setTimeout(() => { + client.abort(sn) + }, 0); + } + promise.then(v => { + ret = v; + }); + + setTimeout(() => { + client.logger?.log('sn', sn, name, abort, ret) + if (abort) { + assert.strictEqual(ret, undefined); + } + else { + assert.notEqual(ret, undefined); + if (name === 'Req') { + assert.strictEqual(ret.isSucc, true); + } + else { + assert.strictEqual(ret.isSucc, false) + } + } + rs(); + }, 300) + }))); + assert.strictEqual(client['_pendingApis'].length, 10); + await promise; + assert.strictEqual(client['_pendingApis'].length, 0); + } + + await server.stop(); + }) + + it('error', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + await server.start(); + + let client1 = new HttpClient(getProto(), { + server: 'http://localhost:80', + logger: clientLogger + }) + + let ret = await client1.callApi('Test', { name: 'xx' }); + console.log(ret); + assert.strictEqual(ret.isSucc, false); + assert.strictEqual(ret.err?.code, 'ECONNREFUSED'); + assert.strictEqual(ret.err?.type, TsrpcErrorType.NetworkError); + + await server.stop(); + }) + + it('server timeout', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 100 + }); + server.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.req && call.succ({ + reply: 'Hi, ' + call.req.name + }); + rs(); + }, 200) + }) + }) + await server.start(); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + let ret = await client.callApi('Test', { name: 'Jack' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Server Timeout', { + code: 'SERVER_TIMEOUT', + type: TsrpcErrorType.ServerError + }) + }); + + await server.stop(); + }); + + it('client timeout', async function () { + let server1 = new HttpServer(getProto(), { + logger: serverLogger + }); + server1.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.succ({ + reply: 'Hello, ' + call.req.name + }); + rs(); + }, 2000) + }) + }) + await server1.start(); + + let client = new HttpClient(getProto(), { + timeout: 100, + logger: clientLogger + }); + + let ret = await client.callApi('Test', { name: 'Jack123' }); + // SERVER TIMEOUT的call还没执行完,但是call却被放入Pool了,导致这个BUG + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError({ + message: 'Request Timeout', + code: 'TIMEOUT', + type: TsrpcErrorType.NetworkError + }) + }); + await server1.stop(); + }); + + it('Graceful stop', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + let reqNum = 0; + server.implementApi('Test', async call => { + if (++reqNum === 10) { + server.gracefulStop(); + } + await new Promise(rs => setTimeout(rs, parseInt(call.req.name))); + call.succ({ reply: 'OK' }); + }); + + await server.start(); + let isStopped = false; + + let client = new HttpClient(getProto(), { + logger: clientLogger + }) + + let succNum = 0; + await Promise.all(Array.from({ length: 10 }, (v, i) => client.callApi('Test', { name: '' + (i * 100) }).then(v => { + if (v.res?.reply === 'OK') { + ++succNum; + } + }))) + assert.strictEqual(succNum, 10); + }) +}) + +describe('HTTP Flows', function () { + it('Server conn flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + assert.strictEqual((call.conn as any).xxxx, 'asdfasdf') + assert.strictEqual(flowExecResult.postConnectFlow, true); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + call.succ({ reply: 'ok' }); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + }); + + server.flows.postConnectFlow.push(v => { + flowExecResult.postConnectFlow = true; + (v as any).xxxx = 'asdfasdf'; + return v; + }); + server.flows.postDisconnectFlow.push(v => { + flowExecResult.postDisconnectFlow = true; + return v; + }) + + await server.start(); + + assert.strictEqual(flowExecResult.postConnectFlow, undefined); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postConnectFlow, true); + assert.strictEqual(flowExecResult.postDisconnectFlow, true); + + await server.stop(); + }) + + it('Buffer enc/dec flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'Enc&Dec' }); + }); + + server.flows.preRecvBufferFlow.push(v => { + flowExecResult.preRecvBufferFlow = true; + for (let i = 0; i < v.buf.length; ++i) { + v.buf[i] ^= 128; + } + return v; + }); + server.flows.preSendBufferFlow.push(v => { + flowExecResult.preSendBufferFlow = true; + for (let i = 0; i < v.buf.length; ++i) { + v.buf[i] ^= 128; + } + return v; + }) + + await server.start(); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + + client.flows.preSendBufferFlow.push(v => { + for (let i = 0; i < v.buf.length; ++i) { + v.buf[i] ^= 128; + } + return v; + }); + + client.flows.preRecvBufferFlow.push(v => { + for (let i = 0; i < v.buf.length; ++i) { + v.buf[i] ^= 128; + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preRecvBufferFlow, true); + assert.strictEqual(flowExecResult.preSendBufferFlow, true); + assert.deepStrictEqual(ret, { + isSucc: true, + res: { + reply: 'Enc&Dec' + } + }) + + await server.stop(); + }); + + it('ApiCall flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return call; + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + + await server.stop(); + }); + + it('ApiCall flow break', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return undefined; + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, undefined); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + + await server.stop(); + }); + + it('ApiCall flow error', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + throw new Error('ASDFASDF') + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, undefined); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + type: TsrpcErrorType.ServerError, + innerErr: 'ASDFASDF', + code: 'INTERNAL_ERR' + }) + }) + + await server.stop(); + }); + + it('server ApiReturn flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + server.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: new TsrpcError('Ret changed') + } + return v; + }); + server.flows.postApiReturnFlow.push(v => { + flowExecResult.postApiReturnFlow = true; + v.call.logger.log('RETTT', v.return); + return v; + }) + + await server.start(); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.strictEqual(flowExecResult.postApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Ret changed') + }) + + await server.stop(); + }); + + it('client ApiReturn flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof HttpClient['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + await server.start(); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + + client.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: new TsrpcError('Ret changed') + } + return v; + }); + client.flows.postApiReturnFlow.push(v => { + flowExecResult.postApiReturnFlow = true; + client.logger?.log('RETTT', v.return); + return v; + }) + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.strictEqual(flowExecResult.postApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Ret changed') + }) + + await server.stop(); + }); + + it('client SendBufferFlow prevent', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + // const flowExecResult: { [K in (keyof BaseClient['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + await server.start(); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + + client.flows.preSendBufferFlow.push(v => { + return undefined + }); + + let ret: any; + client.callApi('Test', { name: 'xxx' }).then(v => { ret = v }); + await new Promise(rs => { setTimeout(rs, 200) }); + assert.strictEqual(ret, undefined) + + await server.stop(); + }); + + it('onInputBufferError', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + await server.start(); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + client.flows.preSendBufferFlow.push(v => { + for (let i = 0; i < v.buf.length; ++i) { + v.buf[i] += 1; + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'XXX' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Invalid request buffer, please check the version of service proto.', { + type: TsrpcErrorType.ServerError, + code: 'INPUT_DATA_ERR' + }) + }) + + await server.stop(); + }) + + it('ObjectId', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + server.autoImplementApi(path.resolve(__dirname, '../api')) + await server.start(); + + let client = new HttpClient(getProto(), { + logger: clientLogger + }); + + // ObjectId + let objId1 = new ObjectId(); + let ret = await client.callApi('ObjId', { + id1: objId1 + }); + assert.strictEqual(ret.isSucc, true, ret.err?.message); + assert.strictEqual(objId1.toString(), ret.res!.id2.toString()); + + await server.stop(); + }) +}) \ No newline at end of file diff --git a/test/cases/httpJSON.test.ts b/test/cases/httpJSON.test.ts new file mode 100644 index 0000000..ab4525b --- /dev/null +++ b/test/cases/httpJSON.test.ts @@ -0,0 +1,997 @@ +import { ObjectId } from 'bson'; +import { assert } from 'chai'; +import chalk from 'chalk'; +import * as path from "path"; +import { ServiceProto, TsrpcError, TsrpcErrorType } from 'tsrpc-proto'; +import { ApiCall, BaseServer, HttpConnection, MsgCall, TerminalColorLogger } from '../../src'; +import { HttpClient } from '../../src/client/http/HttpClient'; +import { HttpProxy } from '../../src/client/http/HttpProxy'; +import { HttpServer } from '../../src/server/http/HttpServer'; +import { PrefixLogger } from '../../src/server/models/PrefixLogger'; +import { ApiTest as ApiAbcTest } from '../api/a/b/c/ApiTest'; +import { ApiTest } from '../api/ApiTest'; +import { MsgChat } from '../proto/MsgChat'; +import { ReqTest, ResTest } from '../proto/PtlTest'; +import { serviceProto, ServiceType } from '../proto/serviceProto'; + +const serverLogger = new PrefixLogger({ + prefixs: [chalk.bgGreen.white(' Server ')], + logger: new TerminalColorLogger({ pid: 'Server' }) +}); +const clientLogger = new PrefixLogger({ + prefixs: [chalk.bgBlue.white(' Client ')], + logger: new TerminalColorLogger({ pid: 'Client' }) +}) + +const getProto = () => Object.merge({}, serviceProto) as ServiceProto; + +async function testApi(server: HttpServer, client: HttpClient) { + // Succ + assert.deepStrictEqual(await client.callApi('Test', { + name: 'Req1' + }), { + isSucc: true, + res: { + reply: 'Test reply: Req1' + } + }); + assert.deepStrictEqual(await client.callApi('a/b/c/Test', { + name: 'Req2' + }), { + isSucc: true, + res: { + reply: 'a/b/c/Test reply: Req2' + } + }); + + // Inner error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'InnerError' + }); + delete ret.err!.innerErr.stack; + + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + code: 'INTERNAL_ERR', + type: TsrpcErrorType.ServerError, + innerErr: `${v} InnerError` + }) + }); + } + + // TsrpcError + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'TsrpcError' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError(`${v} TsrpcError`, { + code: 'CODE_TEST', + type: TsrpcErrorType.ApiError, + info: 'ErrInfo ' + v + }) + }); + } + + // call.error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'error' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Got an error', { + type: TsrpcErrorType.ApiError + }) + }); + } +} + +describe('HTTP Server & Client basic', function () { + it('implement API manually', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger, + debugBuf: true + }); + await server.start(); + + server.implementApi('Test', ApiTest); + server.implementApi('a/b/c/Test', ApiAbcTest); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger, + debugBuf: true + }) + + await testApi(server, client); + + await server.stop(); + }) + + it('extend call in handler', function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger, + debugBuf: true + }); + + type MyApiCall = ApiCall & { + value1?: string; + value2: string; + } + type MyMsgCall = MsgCall & { + value1?: string; + value2: string; + } + + server.implementApi('Test', (call: MyApiCall) => { + call.value1 = 'xxx'; + call.value2 = 'xxx'; + }); + server.listenMsg('Chat', (call: MyMsgCall) => { + call.msg.content; + call.value1 = 'xxx'; + call.value2 = 'xxx'; + }) + }) + + it('extend call in flow', function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger, + debugBuf: true + }); + + type MyApiCall = ApiCall & { + value1?: string; + value2: string; + } + type MyMsgCall = MsgCall & { + value1?: string; + value2: string; + } + type MyConn = HttpConnection & { + currentUser: { + uid: string, + nickName: string + } + } + + server.flows.postConnectFlow.push((conn: MyConn) => { + conn.currentUser.nickName = 'asdf'; + return conn; + }); + server.flows.postConnectFlow.exec(null as any as MyConn, console); + server.flows.preApiCallFlow.push((call: MyApiCall) => { + call.value2 = 'x'; + return call; + }); + server.flows.preSendMsgFlow.push((call: MyMsgCall) => { + call.value2 = 'f'; + return call; + }) + }) + + it('autoImplementApi', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger, + apiTimeout: 5000 + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }) + + await testApi(server, client); + + await server.stop(); + }); + + it('sendMsg', async function () { + let server = new HttpServer(getProto(), { + json: true, + port: 3001, + logger: serverLogger, + // debugBuf: true + }); + + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + server: 'http://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + + return new Promise(rs => { + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + server.listenMsg('Chat', async v => { + assert.deepStrictEqual(v.msg, msg); + await server.stop(); + rs(); + }); + + client.sendMsg('Chat', msg); + }) + }) + + it('abort', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }) + + let result: any | undefined; + let promise = client.callApi('Test', { name: 'aaaaaaaa' }); + let sn = client.lastSN; + setTimeout(() => { + client.abort(sn) + }, 10); + promise.then(v => { + result = v; + }); + + await new Promise(rs => { + setTimeout(() => { + assert.strictEqual(result, undefined); + rs(); + }, 150) + }) + + await server.stop(); + }); + + it('abortByKey', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }) + + let result: any | undefined; + let result1: any | undefined; + + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + + client.callApi('Test', { name: 'bbbbbb' }).then(v => { result1 = v; }); + + setTimeout(() => { + client.abortByKey('XXX') + }, 10); + + await new Promise(rs => { + setTimeout(() => { + assert.strictEqual(result, undefined); + assert.deepStrictEqual(result1, { + isSucc: true, + res: { + reply: 'Test reply: bbbbbb' + } + }) + rs(); + }, 150) + }) + + await server.stop(); + }) + + it('abortAll', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }) + + let result: any | undefined; + let result1: any | undefined; + + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + client.callApi('Test', { name: 'aaaaaaaa' }, { abortKey: 'XXX' }).then(v => { result = v; }); + + client.callApi('Test', { name: 'bbbbbb' }).then(v => { result1 = v; }); + + setTimeout(() => { + client.abortAll() + }, 10); + + await new Promise(rs => { + setTimeout(() => { + assert.strictEqual(result, undefined); + assert.strictEqual(result1, undefined); + rs(); + }, 150) + }) + + await server.stop(); + }) + + it('pendingApis', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }) + + for (let i = 0; i < 10; ++i) { + let promise = Promise.all(Array.from({ length: 10 }, () => new Promise(rs => { + let name = ['Req', 'InnerError', 'TsrpcError', 'error'][Math.random() * 4 | 0]; + let ret: any | undefined; + let promise = client.callApi('Test', { name: name }); + let sn = client.lastSN; + let abort = Math.random() > 0.5; + if (abort) { + setTimeout(() => { + client.abort(sn) + }, 0); + } + promise.then(v => { + ret = v; + }); + + setTimeout(() => { + client.logger?.log('sn', sn, name, abort, ret) + if (abort) { + assert.strictEqual(ret, undefined); + } + else { + assert.notEqual(ret, undefined); + if (name === 'Req') { + assert.strictEqual(ret.isSucc, true); + } + else { + assert.strictEqual(ret.isSucc, false) + } + } + rs(); + }, 300) + }))); + assert.strictEqual(client['_pendingApis'].length, 10); + await promise; + assert.strictEqual(client['_pendingApis'].length, 0); + } + + await server.stop(); + }) + + it('error', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + let client1 = new HttpClient(getProto(), { + json: true, + server: 'http://localhost:80', + logger: clientLogger + }) + + let ret = await client1.callApi('Test', { name: 'xx' }); + console.log(ret); + assert.strictEqual(ret.isSucc, false); + assert.strictEqual(ret.err?.code, 'ECONNREFUSED'); + assert.strictEqual(ret.err?.type, TsrpcErrorType.NetworkError); + + await server.stop(); + }) + + it('server timeout', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger, + apiTimeout: 100 + }); + server.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.req && call.succ({ + reply: 'Hi, ' + call.req.name + }); + rs(); + }, 200) + }) + }) + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + let ret = await client.callApi('Test', { name: 'Jack' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Server Timeout', { + code: 'SERVER_TIMEOUT', + type: TsrpcErrorType.ServerError + }) + }); + + await server.stop(); + }); + + it('client timeout', async function () { + let server1 = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + server1.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.succ({ + reply: 'Hello, ' + call.req.name + }); + rs(); + }, 2000) + }) + }) + await server1.start(); + + let client = new HttpClient(getProto(), { + json: true, + timeout: 100, + logger: clientLogger + }); + + let ret = await client.callApi('Test', { name: 'Jack123' }); + // SERVER TIMEOUT的call还没执行完,但是call却被放入Pool了,导致这个BUG + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError({ + message: 'Request Timeout', + code: 'TIMEOUT', + type: TsrpcErrorType.NetworkError + }) + }); + await server1.stop(); + }); + + it('Graceful stop', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + + let reqNum = 0; + server.implementApi('Test', async call => { + if (++reqNum === 10) { + server.gracefulStop(); + } + await new Promise(rs => setTimeout(rs, parseInt(call.req.name))); + call.succ({ reply: 'OK' }); + }); + + await server.start(); + let isStopped = false; + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }) + + let succNum = 0; + await Promise.all(Array.from({ length: 10 }, (v, i) => client.callApi('Test', { name: '' + (i * 100) }).then(v => { + if (v.res?.reply === 'OK') { + ++succNum; + } + }))) + assert.strictEqual(succNum, 10); + }) +}) + +describe('HTTP Flows', function () { + it('Server conn flow', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + assert.strictEqual((call.conn as any).xxxx, 'asdfasdf') + assert.strictEqual(flowExecResult.postConnectFlow, true); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + call.succ({ reply: 'ok' }); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + }); + + server.flows.postConnectFlow.push(v => { + flowExecResult.postConnectFlow = true; + (v as any).xxxx = 'asdfasdf'; + return v; + }); + server.flows.postDisconnectFlow.push(v => { + flowExecResult.postDisconnectFlow = true; + return v; + }) + + await server.start(); + + assert.strictEqual(flowExecResult.postConnectFlow, undefined); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postConnectFlow, true); + assert.strictEqual(flowExecResult.postDisconnectFlow, true); + + await server.stop(); + }) + + it('Buffer enc/dec flow', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'Enc&Dec' }); + }); + + server.flows.preRecvDataFlow.push(v => { + flowExecResult.preRecvDataFlow = true; + v.data = (v.data as string).split('').reverse().join(''); + return v; + }); + server.flows.preSendDataFlow.push(v => { + flowExecResult.preSendDataFlow = true; + v.data = (v.data as string).split('').reverse().join(''); + return v; + }) + + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + + client.flows.preSendDataFlow.push(v => { + v.data = (v.data as string).split('').reverse().join(''); + return v; + }); + + client.flows.preRecvDataFlow.push(v => { + v.data = (v.data as string).split('').reverse().join(''); + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preRecvDataFlow, true); + assert.strictEqual(flowExecResult.preSendDataFlow, true); + assert.deepStrictEqual(ret, { + isSucc: true, + res: { + reply: 'Enc&Dec' + } + }) + + await server.stop(); + }); + + it('ApiCall flow', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return call; + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + + await server.stop(); + }); + + it('ApiCall flow break', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return undefined; + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, undefined); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + + await server.stop(); + }); + + it('ApiCall flow error', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + throw new Error('ASDFASDF') + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, undefined); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + type: TsrpcErrorType.ServerError, + innerErr: 'ASDFASDF', + code: 'INTERNAL_ERR' + }) + }) + + await server.stop(); + }); + + it('server ApiReturn flow', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + server.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: new TsrpcError('Ret changed') + } + return v; + }); + server.flows.postApiReturnFlow.push(v => { + flowExecResult.postApiReturnFlow = true; + v.call.logger.log('RETTT', v.return); + return v; + }) + + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.strictEqual(flowExecResult.postApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Ret changed') + }) + + await server.stop(); + }); + + it('client ApiReturn flow', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof HttpClient['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + + client.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: new TsrpcError('Ret changed') + } + return v; + }); + client.flows.postApiReturnFlow.push(v => { + flowExecResult.postApiReturnFlow = true; + client.logger?.log('RETTT', v.return); + return v; + }) + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.strictEqual(flowExecResult.postApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Ret changed') + }) + + await server.stop(); + }); + + it('client SendBufferFlow prevent', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + + // const flowExecResult: { [K in (keyof BaseClient['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + + client.flows.preSendDataFlow.push(v => { + return undefined + }); + + let ret: any; + client.callApi('Test', { name: 'xxx' }).then(v => { ret = v }); + await new Promise(rs => { setTimeout(rs, 200) }); + assert.strictEqual(ret, undefined) + + await server.stop(); + }); + + it('onInputBufferError', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + client.flows.preSendDataFlow.push(v => { + v.data = (v.data as string).split('').reverse().join(''); + return v; + }); + + let ret = await client.callApi('Test', { name: 'XXX' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Input is not a valid JSON string: Unexpected token } in JSON at position 0', { + type: TsrpcErrorType.ServerError, + code: 'INPUT_DATA_ERR' + }) + }) + + await server.stop(); + }) + + it('throw type error in client', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + + let ret = await client.callApi('Test', { name: 23456 } as any); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError({ + "code": "INPUT_DATA_ERR", + "message": "Property `name`: Expected type to be `string`, actually `number`.", + "type": TsrpcErrorType.ClientError + }) + }) + + await server.stop(); + }) + + it('throw type error in server', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + let retHttp = await new HttpProxy().fetch({ + url: 'http://127.0.0.1:3000/Test', + data: JSON.stringify({ name: 12345 }), + method: 'POST', + headers: { + 'content-type': 'application/json; charset=utf-8' + }, + transportOptions: {}, + responseType: 'text' + }).promise; + + assert.deepStrictEqual(JSON.parse((retHttp as any).res), { + isSucc: false, + err: { + "code": "INPUT_DATA_ERR", + "message": "Property `name`: Expected type to be `string`, actually `number`.", + "type": TsrpcErrorType.ServerError + } + }) + + await server.stop(); + }) + + it('ObjectId', async function () { + let server = new HttpServer(getProto(), { + json: true, + logger: serverLogger + }); + server.autoImplementApi(path.resolve(__dirname, '../api')) + await server.start(); + + let client = new HttpClient(getProto(), { + json: true, + logger: clientLogger + }); + + // ObjectId + let objId1 = new ObjectId(); + let ret = await client.callApi('ObjId', { + id1: objId1 + }); + assert.strictEqual(ret.isSucc, true, ret.err?.message); + assert.strictEqual(objId1.toString(), ret.res!.id2.toString()); + + await server.stop(); + }) +}) \ No newline at end of file diff --git a/test/cases/inner.test.ts b/test/cases/inner.test.ts new file mode 100644 index 0000000..a18371a --- /dev/null +++ b/test/cases/inner.test.ts @@ -0,0 +1,396 @@ +import { ObjectId } from 'bson'; +import { assert } from 'chai'; +import chalk from 'chalk'; +import * as path from "path"; +import { ServiceProto, TsrpcError, TsrpcErrorType } from 'tsrpc-proto'; +import { ApiCall, BaseServer, HttpConnection, MsgCall, TerminalColorLogger } from '../../src'; +import { HttpServer } from '../../src/server/http/HttpServer'; +import { PrefixLogger } from '../../src/server/models/PrefixLogger'; +import { ApiTest as ApiAbcTest } from '../api/a/b/c/ApiTest'; +import { ApiTest } from '../api/ApiTest'; +import { MsgChat } from '../proto/MsgChat'; +import { ReqTest, ResTest } from '../proto/PtlTest'; +import { serviceProto, ServiceType } from '../proto/serviceProto'; + +const serverLogger = new PrefixLogger({ + prefixs: [chalk.bgGreen.white(' Server ')], + logger: new TerminalColorLogger({ pid: 'Server' }) +}); + +const getProto = () => Object.merge({}, serviceProto) as ServiceProto; + +async function testApi(server: HttpServer) { + // Succ + assert.deepStrictEqual(await server.callApi('Test', { + name: 'Req1' + }), { + isSucc: true, + res: { + reply: 'Test reply: Req1' + } + }); + assert.deepStrictEqual(await server.callApi('a/b/c/Test', { + name: 'Req2' + }), { + isSucc: true, + res: { + reply: 'a/b/c/Test reply: Req2' + } + }); + + // Inner error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await server.callApi(v as any, { + name: 'InnerError' + }); + delete ret.err!.innerErr.stack; + + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + code: 'INTERNAL_ERR', + type: TsrpcErrorType.ServerError, + innerErr: `${v} InnerError` + }) + }); + } + + // TsrpcError + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await server.callApi(v as any, { + name: 'TsrpcError' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError(`${v} TsrpcError`, { + code: 'CODE_TEST', + type: TsrpcErrorType.ApiError, + info: 'ErrInfo ' + v + }) + }); + } + + // call.error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await server.callApi(v as any, { + name: 'error' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Got an error', { + type: TsrpcErrorType.ApiError + }) + }); + } +} + +describe('HTTP Server & Client basic', function () { + it('implement API manually', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + + server.implementApi('Test', ApiTest); + server.implementApi('a/b/c/Test', ApiAbcTest); + + await testApi(server); + + + }) + + it('extend call in handler', function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + type MyApiCall = ApiCall & { + value1?: string; + value2: string; + } + type MyMsgCall = MsgCall & { + value1?: string; + value2: string; + } + + server.implementApi('Test', (call: MyApiCall) => { + call.value1 = 'xxx'; + call.value2 = 'xxx'; + }); + server.listenMsg('Chat', (call: MyMsgCall) => { + call.msg.content; + call.value1 = 'xxx'; + call.value2 = 'xxx'; + }) + }) + + it('extend call in flow', function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + type MyApiCall = ApiCall & { + value1?: string; + value2: string; + } + type MyMsgCall = MsgCall & { + value1?: string; + value2: string; + } + type MyConn = HttpConnection & { + currentUser: { + uid: string, + nickName: string + } + } + + server.flows.postConnectFlow.push((conn: MyConn) => { + conn.currentUser.nickName = 'asdf'; + return conn; + }); + server.flows.postConnectFlow.exec(null as any as MyConn, console); + server.flows.preApiCallFlow.push((call: MyApiCall) => { + call.value2 = 'x'; + return call; + }); + server.flows.preSendMsgFlow.push((call: MyMsgCall) => { + call.value2 = 'f'; + return call; + }) + }) + + it('autoImplementApi', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 5000 + }); + + await server.autoImplementApi(path.resolve(__dirname, '../api')) + + await testApi(server); + }); + + it('autoImplementApi delay', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 5000 + }); + + server.autoImplementApi(path.resolve(__dirname, '../api'), true) + + await testApi(server); + }); + + it('error', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + + let ret = await server.callApi('TesASFt' as any, { name: 'xx' } as any); + console.log(ret); + assert.strictEqual(ret.isSucc, false); + assert.strictEqual(ret.err?.code, 'ERR_API_NAME'); + assert.strictEqual(ret.err?.type, TsrpcErrorType.ServerError); + + + }) + + it('server timeout', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 100 + }); + server.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.req && call.succ({ + reply: 'Hi, ' + call.req.name + }); + rs(); + }, 200) + }) + }) + + + let ret = await server.callApi('Test', { name: 'Jack' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Server Timeout', { + code: 'SERVER_TIMEOUT', + type: TsrpcErrorType.ServerError + }) + }); + + + }); + + it('Graceful stop', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + let reqNum = 0; + server.implementApi('Test', async call => { + if (++reqNum === 10) { + server.gracefulStop(); + } + await new Promise(rs => setTimeout(rs, parseInt(call.req.name))); + call.succ({ reply: 'OK' }); + }); + + + let isStopped = false; + + let succNum = 0; + await Promise.all(Array.from({ length: 10 }, (v, i) => server.callApi('Test', { name: '' + (i * 100) }).then(v => { + if (v.res?.reply === 'OK') { + ++succNum; + } + }))) + assert.strictEqual(succNum, 10); + }) +}) + +describe('HTTP Flows', function () { + it('ApiCall flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + if (call.req.apiName !== 'ObjId') { + call.req.name = 'Changed' + } + return call; + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return call; + }); + + let ret = await server.callApi('Test', { name: 'xxx' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + }); + + it('ApiCall flow break', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + call.error('You need login'); + return undefined; + }); + + let ret = await server.callApi('Test', { name: 'xxx' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + + + }); + + it('ApiCall flow error', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + throw new Error('ASDFASDF') + }); + + let ret = await server.callApi('Test', { name: 'xxx' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + type: TsrpcErrorType.ServerError, + innerErr: 'ASDFASDF', + code: 'INTERNAL_ERR' + }) + }) + + + }); + + it('server ApiReturn flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + server.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: new TsrpcError('Ret changed') + } + return v; + }); + + let ret = await server.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Ret changed') + }) + + + }); + + it('Extended JSON Types', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + await server.autoImplementApi(path.resolve(__dirname, '../api')) + + let buf = new Uint8Array([0, 1, 2, 3, 255, 254, 253, 252]); + let date = new Date('2021/11/17'); + + // ObjectId + let objId1 = new ObjectId(); + let ret = await server.callApi('ObjId', { + id1: objId1, + buf: buf, + date: date + }); + assert.deepStrictEqual(ret, { + isSucc: true, + res: { + id2: objId1, + buf: buf, + date: date + } + }); + }) +}) \ No newline at end of file diff --git a/test/cases/inputBuffer.test.ts b/test/cases/inputBuffer.test.ts new file mode 100644 index 0000000..6013e7a --- /dev/null +++ b/test/cases/inputBuffer.test.ts @@ -0,0 +1,414 @@ +import { ObjectId } from 'bson'; +import { assert } from 'chai'; +import chalk from 'chalk'; +import * as path from "path"; +import { ServiceProto, TsrpcError, TsrpcErrorType } from 'tsrpc-proto'; +import { ApiCall, ApiService, BaseServer, HttpConnection, MsgCall, TerminalColorLogger, TransportDataUtil } from '../../src'; +import { HttpServer } from '../../src/server/http/HttpServer'; +import { PrefixLogger } from '../../src/server/models/PrefixLogger'; +import { ApiTest as ApiAbcTest } from '../api/a/b/c/ApiTest'; +import { ApiTest } from '../api/ApiTest'; +import { MsgChat } from '../proto/MsgChat'; +import { ReqTest, ResTest } from '../proto/PtlTest'; +import { serviceProto, ServiceType } from '../proto/serviceProto'; + +const serverLogger = new PrefixLogger({ + prefixs: [chalk.bgGreen.white(' Server ')], + logger: new TerminalColorLogger({ pid: 'Server' }) +}); + +async function inputBuffer(server: BaseServer, apiName: string, req: any) { + let apiSvc: ApiService | undefined = server.serviceMap.apiName2Service[apiName]; + let inputBuf = apiSvc ? (await TransportDataUtil.encodeApiReq(server.tsbuffer, apiSvc, req, 'buffer')).output : new Uint8Array([0, 1, 2, 3, 255, 254, 253, 252]); + if (!inputBuf) { + throw new Error('failed to encode inputBuf') + } + + let retBuf = await server.inputBuffer(inputBuf); + assert.ok(retBuf instanceof Uint8Array) + + let opDecode = await TransportDataUtil.parseServerOutout(server.tsbuffer, server.serviceMap, retBuf, apiSvc?.id ?? 0); + if (!opDecode.isSucc) { + throw new Error('decode buf failed') + } + if (opDecode.result.type !== 'api') { + throw new Error('decode result is not api') + } + return opDecode.result.ret; +} + +const getProto = () => Object.merge({}, serviceProto) as ServiceProto; + +async function testApi(server: HttpServer) { + // Succ + assert.deepStrictEqual(await inputBuffer(server, 'Test', { + name: 'Req1' + }), { + isSucc: true, + res: { + reply: 'Test reply: Req1' + } + }); + assert.deepStrictEqual(await inputBuffer(server, 'a/b/c/Test', { + name: 'Req2' + }), { + isSucc: true, + res: { + reply: 'a/b/c/Test reply: Req2' + } + }); + + // Inner error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await inputBuffer(server, v as any, { + name: 'InnerError' + }); + delete ret.err!.innerErr.stack; + + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + code: 'INTERNAL_ERR', + type: TsrpcErrorType.ServerError, + innerErr: `${v} InnerError` + }) + }); + } + + // TsrpcError + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await inputBuffer(server, v as any, { + name: 'TsrpcError' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError(`${v} TsrpcError`, { + code: 'CODE_TEST', + type: TsrpcErrorType.ApiError, + info: 'ErrInfo ' + v + }) + }); + } + + // call.error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await inputBuffer(server, v as any, { + name: 'error' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Got an error', { + type: TsrpcErrorType.ApiError + }) + }); + } +} + +describe('HTTP Server & Client basic', function () { + it('implement API manually', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + + server.implementApi('Test', ApiTest); + server.implementApi('a/b/c/Test', ApiAbcTest); + + await testApi(server); + + + }) + + it('extend call in handler', function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + type MyApiCall = ApiCall & { + value1?: string; + value2: string; + } + type MyMsgCall = MsgCall & { + value1?: string; + value2: string; + } + + server.implementApi('Test', (call: MyApiCall) => { + call.value1 = 'xxx'; + call.value2 = 'xxx'; + }); + server.listenMsg('Chat', (call: MyMsgCall) => { + call.msg.content; + call.value1 = 'xxx'; + call.value2 = 'xxx'; + }) + }) + + it('extend call in flow', function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + type MyApiCall = ApiCall & { + value1?: string; + value2: string; + } + type MyMsgCall = MsgCall & { + value1?: string; + value2: string; + } + type MyConn = HttpConnection & { + currentUser: { + uid: string, + nickName: string + } + } + + server.flows.postConnectFlow.push((conn: MyConn) => { + conn.currentUser.nickName = 'asdf'; + return conn; + }); + server.flows.postConnectFlow.exec(null as any as MyConn, console); + server.flows.preApiCallFlow.push((call: MyApiCall) => { + call.value2 = 'x'; + return call; + }); + server.flows.preSendMsgFlow.push((call: MyMsgCall) => { + call.value2 = 'f'; + return call; + }) + }) + + it('autoImplementApi', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 5000 + }); + + await server.autoImplementApi(path.resolve(__dirname, '../api')) + + await testApi(server); + }); + + it('autoImplementApi delay', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 5000 + }); + + server.autoImplementApi(path.resolve(__dirname, '../api'), true) + + await testApi(server); + }); + + it('error', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + + let ret = await inputBuffer(server, 'TesASFt' as any, { name: 'xx' } as any); + console.log(ret); + assert.strictEqual(ret.isSucc, false); + assert.strictEqual(ret.err?.code, 'INPUT_DATA_ERR'); + assert.strictEqual(ret.err?.type, TsrpcErrorType.ServerError); + }) + + it('server timeout', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 100 + }); + server.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.req && call.succ({ + reply: 'Hi, ' + call.req.name + }); + rs(); + }, 200) + }) + }) + + + let ret = await inputBuffer(server, 'Test', { name: 'Jack' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Server Timeout', { + code: 'SERVER_TIMEOUT', + type: TsrpcErrorType.ServerError + }) + }); + + + }); + + it('Graceful stop', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + let reqNum = 0; + server.implementApi('Test', async call => { + if (++reqNum === 10) { + server.gracefulStop(); + } + await new Promise(rs => setTimeout(rs, parseInt(call.req.name))); + call.succ({ reply: 'OK' }); + }); + + + let isStopped = false; + + let succNum = 0; + await Promise.all(Array.from({ length: 10 }, (v, i) => inputBuffer(server, 'Test', { name: '' + (i * 100) }).then((v: any) => { + if (v.res?.reply === 'OK') { + ++succNum; + } + }))) + assert.strictEqual(succNum, 10); + }) +}) + +describe('HTTP Flows', function () { + it('ApiCall flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + if (call.req.apiName !== 'ObjId') { + call.req.name = 'Changed' + } + return call; + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return call; + }); + + let ret = await inputBuffer(server, 'Test', { name: 'xxx' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + }); + + it('ApiCall flow break', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + call.error('You need login'); + return undefined; + }); + + let ret = await inputBuffer(server, 'Test', { name: 'xxx' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + + + }); + + it('ApiCall flow error', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + throw new Error('ASDFASDF') + }); + + let ret = await inputBuffer(server, 'Test', { name: 'xxx' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + type: TsrpcErrorType.ServerError, + innerErr: 'ASDFASDF', + code: 'INTERNAL_ERR' + }) + }) + + + }); + + it('server ApiReturn flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + server.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: new TsrpcError('Ret changed') + } + return v; + }); + + let ret = await inputBuffer(server, 'Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Ret changed') + }) + + + }); + + it('Extended JSON Types', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + await server.autoImplementApi(path.resolve(__dirname, '../api')) + + let buf = new Uint8Array([0, 1, 2, 3, 255, 254, 253, 252]); + let date = new Date('2021/11/17'); + + // ObjectId + let objId1 = new ObjectId(); + let ret = await inputBuffer(server, 'ObjId', { + id1: objId1, + buf: buf, + date: date + }); + assert.deepStrictEqual(ret, { + isSucc: true, + res: { + id2: objId1, + buf: buf, + date: date + } + }); + }) +}) \ No newline at end of file diff --git a/test/cases/inputJSON.test.ts b/test/cases/inputJSON.test.ts new file mode 100644 index 0000000..d198587 --- /dev/null +++ b/test/cases/inputJSON.test.ts @@ -0,0 +1,410 @@ +import { ObjectId } from 'bson'; +import { assert } from 'chai'; +import chalk from 'chalk'; +import * as path from "path"; +import { Base64Util } from 'tsbuffer'; +import { ServiceProto, TsrpcError, TsrpcErrorType } from 'tsrpc-proto'; +import { ApiCall, BaseServer, HttpConnection, MsgCall, TerminalColorLogger } from '../../src'; +import { HttpServer } from '../../src/server/http/HttpServer'; +import { PrefixLogger } from '../../src/server/models/PrefixLogger'; +import { ApiTest as ApiAbcTest } from '../api/a/b/c/ApiTest'; +import { ApiTest } from '../api/ApiTest'; +import { MsgChat } from '../proto/MsgChat'; +import { ReqTest, ResTest } from '../proto/PtlTest'; +import { serviceProto, ServiceType } from '../proto/serviceProto'; + +const serverLogger = new PrefixLogger({ + prefixs: [chalk.bgGreen.white(' Server ')], + logger: new TerminalColorLogger({ pid: 'Server' }) +}); + +const getProto = () => Object.merge({}, serviceProto) as ServiceProto; + +async function testApi(server: HttpServer) { + // Succ + assert.deepStrictEqual(await server.inputJSON('Test', { + name: 'Req1' + }), { + isSucc: true, + res: { + reply: 'Test reply: Req1' + } + }); + assert.deepStrictEqual(await server.inputJSON('/a/b/c/Test', { + name: 'Req2' + }), { + isSucc: true, + res: { + reply: 'a/b/c/Test reply: Req2' + } + }); + + // Inner error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await server.inputJSON(v as any, { + name: 'InnerError' + }); + delete ret.err!.innerErr.stack; + + assert.deepStrictEqual(ret, { + isSucc: false, + err: { + ...new TsrpcError('Internal Server Error', { + code: 'INTERNAL_ERR', + type: TsrpcErrorType.ServerError, + innerErr: `${v} InnerError` + }) + } + }); + } + + // TsrpcError + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await server.inputJSON(v as any, { + name: 'TsrpcError' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: { + ...new TsrpcError(`${v} TsrpcError`, { + code: 'CODE_TEST', + type: TsrpcErrorType.ApiError, + info: 'ErrInfo ' + v + }) + } + }); + } + + // call.error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await server.inputJSON(v as any, { + name: 'error' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: { + ...new TsrpcError('Got an error', { + type: TsrpcErrorType.ApiError + }) + } + }); + } +} + +describe('HTTP Server & Client basic', function () { + it('implement API manually', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + + server.implementApi('Test', ApiTest); + server.implementApi('a/b/c/Test', ApiAbcTest); + + await testApi(server); + + + }) + + it('extend call in handler', function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + type MyApiCall = ApiCall & { + value1?: string; + value2: string; + } + type MyMsgCall = MsgCall & { + value1?: string; + value2: string; + } + + server.implementApi('Test', (call: MyApiCall) => { + call.value1 = 'xxx'; + call.value2 = 'xxx'; + }); + server.listenMsg('Chat', (call: MyMsgCall) => { + call.msg.content; + call.value1 = 'xxx'; + call.value2 = 'xxx'; + }) + }) + + it('extend call in flow', function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + type MyApiCall = ApiCall & { + value1?: string; + value2: string; + } + type MyMsgCall = MsgCall & { + value1?: string; + value2: string; + } + type MyConn = HttpConnection & { + currentUser: { + uid: string, + nickName: string + } + } + + server.flows.postConnectFlow.push((conn: MyConn) => { + conn.currentUser.nickName = 'asdf'; + return conn; + }); + server.flows.postConnectFlow.exec(null as any as MyConn, console); + server.flows.preApiCallFlow.push((call: MyApiCall) => { + call.value2 = 'x'; + return call; + }); + server.flows.preSendMsgFlow.push((call: MyMsgCall) => { + call.value2 = 'f'; + return call; + }) + }) + + it('autoImplementApi', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 5000 + }); + + await server.autoImplementApi(path.resolve(__dirname, '../api')) + + await testApi(server); + }); + + it('autoImplementApi delay', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 5000 + }); + + server.autoImplementApi(path.resolve(__dirname, '../api'), true) + + await testApi(server); + }); + + it('error', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + + let ret = await server.inputJSON('TesASFt' as any, { name: 'xx' } as any); + console.log(ret); + assert.strictEqual(ret.isSucc, false); + assert.strictEqual(ret.err?.message, 'Invalid service name: TesASFt'); + assert.strictEqual(ret.err?.code, 'INPUT_DATA_ERR'); + assert.strictEqual(ret.err?.type, TsrpcErrorType.ServerError); + }) + + it('server timeout', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger, + apiTimeout: 100 + }); + server.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.req && call.succ({ + reply: 'Hi, ' + call.req.name + }); + rs(); + }, 200) + }) + }) + + + let ret = await server.inputJSON('Test', { name: 'Jack' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: { + ...new TsrpcError('Server Timeout', { + code: 'SERVER_TIMEOUT', + type: TsrpcErrorType.ServerError + }) + } + }); + + + }); + + it('Graceful stop', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + let reqNum = 0; + server.implementApi('Test', async call => { + if (++reqNum === 10) { + server.gracefulStop(); + } + await new Promise(rs => setTimeout(rs, parseInt(call.req.name))); + call.succ({ reply: 'OK' }); + }); + + + let isStopped = false; + + let succNum = 0; + await Promise.all(Array.from({ length: 10 }, (v, i) => server.inputJSON('Test', { name: '' + (i * 100) }).then((v: any) => { + if (v.res?.reply === 'OK') { + ++succNum; + } + }))) + assert.strictEqual(succNum, 10); + }) +}) + +describe('HTTP Flows', function () { + it('ApiCall flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + if (call.req.apiName !== 'ObjId') { + call.req.name = 'Changed' + } + return call; + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return call; + }); + + let ret = await server.inputJSON('Test', { name: 'xxx' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: { + ...new TsrpcError('You need login') + } + }) + }); + + it('ApiCall flow break', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + call.error('You need login'); + return undefined; + }); + + let ret = await server.inputJSON('Test', { name: 'xxx' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: { + ...new TsrpcError('You need login') + } + }) + + + }); + + it('ApiCall flow error', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + throw new Error('ASDFASDF') + }); + + let ret = await server.inputJSON('Test', { name: 'xxx' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: { + ...new TsrpcError('Internal Server Error', { + type: TsrpcErrorType.ServerError, + innerErr: 'ASDFASDF', + code: 'INTERNAL_ERR' + }) + } + }) + + + }); + + it('server ApiReturn flow', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + server.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: { ...new TsrpcError('Ret changed') } + } + return v; + }); + + let ret = await server.inputJSON('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: { ...new TsrpcError('Ret changed') } + }) + + + }); + + it('Extended JSON Types', async function () { + let server = new HttpServer(getProto(), { + logger: serverLogger + }); + await server.autoImplementApi(path.resolve(__dirname, '../api')) + + let buf = new Uint8Array([0, 1, 2, 3, 255, 254, 253, 252]); + let date = new Date('2021/11/17'); + + // ObjectId + let objId1 = new ObjectId(); + let ret = await server.inputJSON('ObjId', { + id1: objId1, + buf: buf, + date: date + }); + assert.deepStrictEqual(ret, { + isSucc: true, + res: { + id2: objId1.toHexString(), + buf: Base64Util.bufferToBase64(buf), + date: date.toJSON() + } + }); + }) +}) \ No newline at end of file diff --git a/test/cases/ws.test.ts b/test/cases/ws.test.ts new file mode 100644 index 0000000..1f94d1c --- /dev/null +++ b/test/cases/ws.test.ts @@ -0,0 +1,1051 @@ +import { ObjectId } from 'bson'; +import { assert } from 'chai'; +import chalk from 'chalk'; +import * as path from "path"; +import { ServiceProto, TsrpcError, TsrpcErrorType } from 'tsrpc-proto'; +import { BaseServer, TerminalColorLogger, TransportDataUtil, WsClientStatus, WsConnection } from '../../src'; +import { WsClient } from '../../src/client/ws/WsClient'; +import { PrefixLogger } from '../../src/server/models/PrefixLogger'; +import { WsServer } from '../../src/server/ws/WsServer'; +import { ApiTest as ApiAbcTest } from '../api/a/b/c/ApiTest'; +import { ApiTest } from '../api/ApiTest'; +import { MsgChat } from '../proto/MsgChat'; +import { serviceProto, ServiceType } from '../proto/serviceProto'; + +const serverLogger = new PrefixLogger({ + prefixs: [chalk.bgGreen.white(' Server ')], + logger: new TerminalColorLogger({ pid: 'Server' }) +}); +const clientLogger = new PrefixLogger({ + prefixs: [chalk.bgBlue.white(' Client ')], + logger: new TerminalColorLogger({ pid: 'Client' }) +}) + +const getProto = () => Object.merge({}, serviceProto) as ServiceProto; + +async function testApi(server: WsServer, client: WsClient) { + // Succ + assert.deepStrictEqual(await client.callApi('Test', { + name: 'Req1' + }), { + isSucc: true, + res: { + reply: 'Test reply: Req1' + } + }); + assert.deepStrictEqual(await client.callApi('a/b/c/Test', { + name: 'Req2' + }), { + isSucc: true, + res: { + reply: 'a/b/c/Test reply: Req2' + } + }); + + // Inner error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'InnerError' + }); + delete ret.err!.innerErr.stack; + + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + code: 'INTERNAL_ERR', + type: TsrpcErrorType.ServerError, + innerErr: `${v} InnerError` + }) + }); + } + + // TsrpcError + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'TsrpcError' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError(`${v} TsrpcError`, { + code: 'CODE_TEST', + type: TsrpcErrorType.ApiError, + info: 'ErrInfo ' + v + }) + }); + } + + // call.error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'error' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Got an error', { + type: TsrpcErrorType.ApiError + }) + }); + } +} + +describe('WS Server & Client basic', function () { + it('cannot callApi before connect', async function () { + let client = new WsClient(getProto(), { + logger: clientLogger, + debugBuf: true + }) + let res = await client.callApi('Test', { name: 'xxx' }); + assert.deepStrictEqual(res, { + isSucc: false, + err: new TsrpcError('WebSocket is not connected', { + code: 'WS_NOT_OPEN', + type: TsrpcErrorType.ClientError + }) + }) + }) + + it('implement API manually', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + await server.start(); + + server.implementApi('Test', ApiTest); + server.implementApi('a/b/c/Test', ApiAbcTest); + + let client = new WsClient(getProto(), { + logger: clientLogger, + debugBuf: true + }) + await client.connect(); + + await testApi(server, client); + + await server.stop(); + }) + + it('extend conn', function () { + let server = new WsServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + type MyConn = WsConnection & { + sessionData: { + value: string; + } + } + server.flows.postConnectFlow.push((conn: MyConn) => { + conn.sessionData.value = 'zxcdv'; + return conn; + }) + }) + + it('autoImplementApi', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger, + apiTimeout: 5000 + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + await testApi(server, client); + + await server.stop(); + }); + + it('sendMsg', async function () { + let server = new WsServer(getProto(), { + port: 3001, + logger: serverLogger, + // debugBuf: true + }); + + await server.start(); + + let client = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + await client.connect(); + + return new Promise(rs => { + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + server.listenMsg('Chat', async v => { + assert.deepStrictEqual(v.msg, msg); + await server.stop(); + rs(); + }); + + client.sendMsg('Chat', msg); + }) + }); + + it('server send msg', async function () { + let server = new WsServer(getProto(), { + port: 3001, + logger: serverLogger, + // debugBuf: true + }); + + await server.start(); + + let client = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + await client.connect(); + + return new Promise(rs => { + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + client.listenMsg('Chat', async msg1 => { + assert.deepStrictEqual(msg1, msg); + await server.stop(); + rs(); + }); + + server.connections[0].sendMsg('Chat', msg); + }) + }); + + it('listen msg by regexp', async function () { + let server = new WsServer(getProto(), { + port: 3001, + logger: serverLogger, + // debugBuf: true + }); + + await server.start(); + + let client = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + await client.connect(); + + return new Promise(rs => { + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + client.listenMsg(/.*/, async (msg1, msgName: string) => { + assert.deepStrictEqual(msg1, msg); + assert.deepStrictEqual(msgName, 'Chat'); + await server.stop(); + rs(); + }); + + server.connections[0].sendMsg('Chat', msg); + }) + }); + + it('server broadcast msg', async function () { + let server = new WsServer(getProto(), { + port: 3001, + logger: serverLogger, + // debugBuf: true + }); + + await server.start(); + + let client1 = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + let client2 = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + await client1.connect(); + await client2.connect(); + + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + await new Promise(rs => { + let recvClients: WsClient[] = []; + let msgHandler = async (client: WsClient, msg1: MsgChat, msgName: string) => { + recvClients.push(client); + assert.deepStrictEqual(msg1, msg); + assert.deepStrictEqual(msgName, 'Chat') + if (recvClients.some(v => v === client1) && recvClients.some(v => v === client2)) { + client1.unlistenMsgAll('Chat'); + client2.unlistenMsgAll('Chat'); + rs(); + } + } + + client1.listenMsg('Chat', msgHandler.bind(null, client1)); + client2.listenMsg('Chat', msgHandler.bind(null, client2)); + + server.broadcastMsg('Chat', msg); + }) + + await new Promise(rs => { + let recvClients: WsClient[] = []; + let msgHandler = async (client: WsClient, msg1: MsgChat, msgName: string) => { + recvClients.push(client); + assert.deepStrictEqual(msg1, msg); + assert.deepStrictEqual(msgName, 'Chat'); + if (recvClients.some(v => v === client1) && recvClients.some(v => v === client2)) { + await server.stop(); + rs(); + } + } + + client1.listenMsg('Chat', msgHandler.bind(null, client1)); + client2.listenMsg('Chat', msgHandler.bind(null, client2)); + + server.broadcastMsg('Chat', msg, server.connections.slice()); + }) + }) + + it('abort', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + let result: any | undefined; + let promise = client.callApi('Test', { name: 'aaaaaaaa' }); + let sn = client.lastSN; + setTimeout(() => { + client.abort(sn) + }, 10); + promise.then(v => { + result = v; + }); + + await new Promise(rs => { + setTimeout(() => { + assert.strictEqual(result, undefined); + rs(); + }, 150) + }) + + await server.stop(); + }); + + it('pendingApis', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + for (let i = 0; i < 10; ++i) { + let promise = Promise.all(Array.from({ length: 10 }, () => new Promise(rs => { + let name = ['Req', 'InnerError', 'TsrpcError', 'error'][Math.random() * 4 | 0]; + let ret: any | undefined; + let promise = client.callApi('Test', { name: name }); + let sn = client.lastSN; + let abort = Math.random() > 0.5; + if (abort) { + setTimeout(() => { + client.abort(sn) + }, 0); + } + promise.then(v => { + ret = v; + }); + + setTimeout(() => { + client.logger?.log('sn', sn, name, abort, ret) + if (abort) { + assert.strictEqual(ret, undefined); + } + else { + assert.notEqual(ret, undefined); + if (name === 'Req') { + assert.strictEqual(ret.isSucc, true); + } + else { + assert.strictEqual(ret.isSucc, false) + } + } + rs(); + }, 300) + }))); + assert.strictEqual(client['_pendingApis'].length, 10); + await promise; + assert.strictEqual(client['_pendingApis'].length, 0); + } + + await server.stop(); + }) + + it('error', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + await server.start(); + + let client1 = new WsClient(getProto(), { + server: 'ws://localhost:80', + logger: clientLogger + }) + let res = await client1.connect(); + assert.strictEqual(res.isSucc, false); + + let ret = await client1.callApi('Test', { name: 'xx' }); + console.log(ret); + assert.strictEqual(ret.isSucc, false); + assert.strictEqual(ret.err?.code, 'WS_NOT_OPEN'); + assert.strictEqual(ret.err?.type, TsrpcErrorType.ClientError); + + await server.stop(); + }) + + it('server timeout', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger, + apiTimeout: 100 + }); + server.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.req && call.succ({ + reply: 'Hi, ' + call.req.name + }); + rs(); + }, 200) + }) + }) + await server.start(); + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + let ret = await client.callApi('Test', { name: 'Jack' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Server Timeout', { + code: 'SERVER_TIMEOUT', + type: TsrpcErrorType.ServerError + }) + }); + + await server.stop(); + }); + + it('client timeout', async function () { + let server1 = new WsServer(getProto(), { + logger: serverLogger + }); + server1.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.succ({ + reply: 'Hello, ' + call.req.name + }); + rs(); + }, 2000) + }) + }) + await server1.start(); + + let client = new WsClient(getProto(), { + timeout: 100, + logger: clientLogger + }); + await client.connect(); + + let ret = await client.callApi('Test', { name: 'Jack123' }); + // SERVER TIMEOUT的call还没执行完,但是call却被放入Pool了,导致这个BUG + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError({ + message: 'Request Timeout', + code: 'TIMEOUT', + type: TsrpcErrorType.NetworkError + }) + }); + await server1.stop(); + }); + + it('Graceful stop', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + + let reqNum = 0; + server.implementApi('Test', async call => { + if (++reqNum === 10) { + server.gracefulStop(); + } + await new Promise(rs => setTimeout(rs, parseInt(call.req.name))); + call.succ({ reply: 'OK' }); + }); + + await server.start(); + let isStopped = false; + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + let succNum = 0; + await Promise.all(Array.from({ length: 10 }, (v, i) => client.callApi('Test', { name: '' + (i * 100) }).then(v => { + console.log('xxx', v) + if (v.res?.reply === 'OK') { + ++succNum; + } + }))) + assert.strictEqual(succNum, 10); + }) + + it('Client heartbeat works', async function () { + let server = new WsServer(getProto(), { + port: 3001, + logger: serverLogger, + debugBuf: true + }); + await server.start(); + + let client = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + heartbeat: { + interval: 1000, + timeout: 1000 + }, + debugBuf: true + }); + await client.connect(); + + await new Promise(rs => { setTimeout(rs, 2000) }); + client.logger?.log('lastHeartbeatLatency', client.lastHeartbeatLatency); + assert.strictEqual(client.status, WsClientStatus.Opened) + assert.ok(client.lastHeartbeatLatency > 0); + + await client.disconnect(); + await server.stop(); + }) + + it('Client heartbeat error', async function () { + let server = new WsServer(getProto(), { + port: 3001, + logger: serverLogger, + debugBuf: true + }); + await server.start(); + + let client = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + heartbeat: { + interval: 1000, + timeout: 1000 + }, + debugBuf: true + }); + + let disconnectFlowData: { isManual?: boolean } | undefined; + client.flows.postDisconnectFlow.push(v => { + disconnectFlowData = {} + return v; + }) + + await client.connect(); + + const temp = TransportDataUtil.HeartbeatPacket; + (TransportDataUtil as any).HeartbeatPacket = new Uint8Array([0, 0]); + + await new Promise(rs => { setTimeout(rs, 2000) }); + client.logger?.log('lastHeartbeatLatency', client.lastHeartbeatLatency); + assert.strictEqual(client.status, WsClientStatus.Closed) + assert.deepStrictEqual(disconnectFlowData, {}) + + await client.disconnect(); + await server.stop(); + (TransportDataUtil as any).HeartbeatPacket = temp; + }) + + it('Server heartbeat kick', async function () { + let server = new WsServer(getProto(), { + port: 3001, + logger: serverLogger, + debugBuf: true, + heartbeatWaitTime: 1000 + }); + await server.start(); + + let client = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + debugBuf: true + }); + + let disconnectFlowData: { isManual?: boolean } | undefined; + client.flows.postDisconnectFlow.push(v => { + disconnectFlowData = {} + return v; + }) + + await client.connect(); + + await new Promise(rs => { setTimeout(rs, 2000) }); + assert.strictEqual(client.status, WsClientStatus.Closed) + assert.deepStrictEqual(disconnectFlowData, {}) + + await client.disconnect(); + await server.stop(); + }) +}) + +describe('WS Flows', function () { + it('Server conn flow', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + assert.strictEqual((call.conn as any).xxxx, 'asdfasdf') + assert.strictEqual(flowExecResult.postConnectFlow, true); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + call.succ({ reply: 'ok' }); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + }); + + server.flows.postConnectFlow.push(v => { + flowExecResult.postConnectFlow = true; + (v as any).xxxx = 'asdfasdf'; + return v; + }); + server.flows.postDisconnectFlow.push(v => { + server.logger.log('server postDisconnectFlow') + flowExecResult.postDisconnectFlow = true; + return v; + }) + + await server.start(); + + assert.strictEqual(flowExecResult.postConnectFlow, undefined); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postConnectFlow, true); + await server.stop(); + assert.strictEqual(flowExecResult.postDisconnectFlow, true); + }) + + it('Buffer enc/dec flow', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger, + debugBuf: true + }); + + const flowExecResult: { [key: string]: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'Enc&Dec' }); + }); + + server.flows.preRecvBufferFlow.push(v => { + flowExecResult.preRecvBufferFlow = true; + for (let i = 0; i < v.buf.length; ++i) { + v.buf[i] ^= 128; + } + return v; + }); + server.flows.preSendBufferFlow.push(v => { + flowExecResult.preSendBufferFlow = true; + for (let i = 0; i < v.buf.length; ++i) { + v.buf[i] ^= 128; + } + return v; + }) + + await server.start(); + + let client = new WsClient(getProto(), { + logger: clientLogger, + debugBuf: true + }); + await client.connect(); + + client.flows.preSendBufferFlow.push(v => { + flowExecResult.client_preSendBufferFlow = true; + for (let i = 0; i < v.buf.length; ++i) { + v.buf[i] ^= 128; + } + return v; + }); + + client.flows.preRecvBufferFlow.push(v => { + flowExecResult.client_preRecvBufferFlow = true; + for (let i = 0; i < v.buf.length; ++i) { + v.buf[i] ^= 128; + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.client_preSendBufferFlow, true); + assert.strictEqual(flowExecResult.client_preRecvBufferFlow, true); + assert.strictEqual(flowExecResult.preRecvBufferFlow, true); + assert.strictEqual(flowExecResult.preSendBufferFlow, true); + assert.deepStrictEqual(ret, { + isSucc: true, + res: { + reply: 'Enc&Dec' + } + }) + + await server.stop(); + }); + + it('ApiCall flow', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return call; + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + + await server.stop(); + }); + + it('ApiCall flow break', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return undefined; + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, undefined); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + + await server.stop(); + }); + + it('ApiCall flow error', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + throw new Error('ASDFASDF') + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, undefined); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + type: TsrpcErrorType.ServerError, + innerErr: 'ASDFASDF', + code: 'INTERNAL_ERR' + }) + }) + + await server.stop(); + }); + + it('server ApiReturn flow', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + server.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: new TsrpcError('Ret changed') + } + return v; + }); + server.flows.postApiReturnFlow.push(v => { + flowExecResult.postApiReturnFlow = true; + v.call.logger.log('RETTT', v.return); + return v; + }) + + await server.start(); + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.strictEqual(flowExecResult.postApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Ret changed') + }) + + await server.stop(); + }); + + it('client ApiReturn flow', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof WsClient['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + await server.start(); + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + client.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: new TsrpcError('Ret changed') + } + return v; + }); + client.flows.postApiReturnFlow.push(v => { + flowExecResult.postApiReturnFlow = true; + client.logger?.log('RETTT', v.return); + return v; + }) + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.strictEqual(flowExecResult.postApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Ret changed') + }) + + await server.stop(); + }); + + it('client SendBufferFlow prevent', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + + // const flowExecResult: { [K in (keyof BaseClient['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + await server.start(); + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + client.flows.preSendBufferFlow.push(v => { + return undefined + }); + + let ret: any; + client.callApi('Test', { name: 'xxx' }).then(v => { ret = v }); + await new Promise(rs => { setTimeout(rs, 200) }); + assert.strictEqual(ret, undefined) + + await server.stop(); + }); + + it('onInputBufferError', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + await server.start(); + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + client.flows.preSendBufferFlow.push(v => { + for (let i = 0; i < v.buf.length; ++i) { + v.buf[i] += 1; + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'XXX' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Invalid request buffer, please check the version of service proto.', { type: TsrpcErrorType.NetworkError, code: 'LOST_CONN' }) + }) + + await server.stop(); + }) + + it('ObjectId', async function () { + let server = new WsServer(getProto(), { + logger: serverLogger + }); + server.autoImplementApi(path.resolve(__dirname, '../api')) + await server.start(); + + let client = new WsClient(getProto(), { + logger: clientLogger + }); + await client.connect(); + + // ObjectId + let objId1 = new ObjectId(); + let ret = await client.callApi('ObjId', { + id1: objId1 + }); + assert.strictEqual(ret.isSucc, true, ret.err?.message); + assert.strictEqual(objId1.toString(), ret.res!.id2.toString()); + + await server.stop(); + }) +}) \ No newline at end of file diff --git a/test/cases/wsJSON.test.ts b/test/cases/wsJSON.test.ts new file mode 100644 index 0000000..3afe27b --- /dev/null +++ b/test/cases/wsJSON.test.ts @@ -0,0 +1,1278 @@ +import { ObjectId } from 'bson'; +import { assert } from 'chai'; +import chalk from 'chalk'; +import * as path from "path"; +import { ServiceProto, TsrpcError, TsrpcErrorType } from 'tsrpc-proto'; +import { BaseServer, TerminalColorLogger, TransportDataUtil, WsClientStatus, WsConnection } from '../../src'; +import { WsClient } from '../../src/client/ws/WsClient'; +import { PrefixLogger } from '../../src/server/models/PrefixLogger'; +import { WsServer } from '../../src/server/ws/WsServer'; +import { ApiTest as ApiAbcTest } from '../api/a/b/c/ApiTest'; +import { ApiTest } from '../api/ApiTest'; +import { MsgChat } from '../proto/MsgChat'; +import { serviceProto, ServiceType } from '../proto/serviceProto'; + +const serverLogger = new PrefixLogger({ + prefixs: [chalk.bgGreen.white(' Server ')], + logger: new TerminalColorLogger({ pid: 'Server' }) +}); +const clientLogger = new PrefixLogger({ + prefixs: [chalk.bgBlue.white(' Client ')], + logger: new TerminalColorLogger({ pid: 'Client' }) +}) + +const getProto = () => Object.merge({}, serviceProto) as ServiceProto; + +async function testApi(server: WsServer, client: WsClient) { + // Succ + assert.deepStrictEqual(await client.callApi('Test', { + name: 'Req1' + }), { + isSucc: true, + res: { + reply: 'Test reply: Req1' + } + }); + assert.deepStrictEqual(await client.callApi('a/b/c/Test', { + name: 'Req2' + }), { + isSucc: true, + res: { + reply: 'a/b/c/Test reply: Req2' + } + }); + + // Inner error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'InnerError' + }); + delete ret.err!.innerErr.stack; + + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + code: 'INTERNAL_ERR', + type: TsrpcErrorType.ServerError, + innerErr: `${v} InnerError` + }) + }); + } + + // TsrpcError + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'TsrpcError' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError(`${v} TsrpcError`, { + code: 'CODE_TEST', + type: TsrpcErrorType.ApiError, + info: 'ErrInfo ' + v + }) + }); + } + + // call.error + for (let v of ['Test', 'a/b/c/Test']) { + let ret = await client.callApi(v as any, { + name: 'error' + }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Got an error', { + type: TsrpcErrorType.ApiError + }) + }); + } +} + +describe('WS Server & Client basic', function () { + it('cannot callApi before connect', async function () { + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger, + debugBuf: true + }) + let res = await client.callApi('Test', { name: 'xxx' }); + assert.deepStrictEqual(res, { + isSucc: false, + err: new TsrpcError('WebSocket is not connected', { + code: 'WS_NOT_OPEN', + type: TsrpcErrorType.ClientError + }) + }) + }) + + it('implement API manually', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger, + debugBuf: true + }); + await server.start(); + + server.implementApi('Test', ApiTest); + server.implementApi('a/b/c/Test', ApiAbcTest); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger, + debugBuf: true + }) + assert.ok((await client.connect()).isSucc); + + await testApi(server, client); + + await server.stop(); + }) + + it('extend conn', function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger, + debugBuf: true + }); + type MyConn = WsConnection & { + sessionData: { + value: string; + } + } + server.flows.postConnectFlow.push((conn: MyConn) => { + conn.sessionData.value = 'zxcdv'; + return conn; + }) + }) + + it('autoImplementApi', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger, + apiTimeout: 5000 + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + await testApi(server, client); + + await server.stop(); + }); + + it('Client use buffer when server is `json: true`', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger, + apiTimeout: 5000 + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new WsClient(getProto(), { + json: false, + logger: clientLogger + }); + await client.connect(); + + await testApi(server, client); + + await server.stop(); + }) + + it('Client use JSON when server is `json: false`', async function () { + let server = new WsServer(getProto(), { + json: false, + logger: serverLogger, + apiTimeout: 5000 + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + let isErr = false; + await testApi(server, client).catch(() => { isErr = true }); + assert.strictEqual(isErr, true); + + await server.stop(); + }) + + it('sendMsg', async function () { + let server = new WsServer(getProto(), { + json: true, + port: 3001, + logger: serverLogger, + // debugBuf: true + }); + + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + await client.connect(); + + return new Promise(rs => { + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + server.listenMsg('Chat', async v => { + assert.deepStrictEqual(v.msg, msg); + await server.stop(); + rs(); + }); + + client.sendMsg('Chat', msg); + }) + }); + + it('server send msg', async function () { + let server = new WsServer(getProto(), { + json: true, + port: 3001, + logger: serverLogger, + // debugBuf: true + }); + + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + await client.connect(); + + return new Promise(rs => { + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + client.listenMsg('Chat', async msg1 => { + assert.deepStrictEqual(msg1, msg); + await server.stop(); + rs(); + }); + + server.connections[0].sendMsg('Chat', msg); + }) + }); + + it('listen msg by regexp', async function () { + let server = new WsServer(getProto(), { + json: true, + port: 3001, + logger: serverLogger, + // debugBuf: true + }); + + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + await client.connect(); + + return new Promise(rs => { + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + client.listenMsg(/.*/, async (msg1, msgName: string) => { + assert.deepStrictEqual(msg1, msg); + assert.deepStrictEqual(msgName, 'Chat'); + await server.stop(); + rs(); + }); + + server.connections[0].sendMsg('Chat', msg); + }) + }); + + it('server broadcast msg', async function () { + let server = new WsServer(getProto(), { + json: true, + port: 3001, + logger: serverLogger, + // debugBuf: true + }); + + await server.start(); + + let client1 = new WsClient(getProto(), { + json: true, + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + let client2 = new WsClient(getProto(), { + json: true, + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + // debugBuf: true + }); + await client1.connect(); + await client2.connect(); + + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + await new Promise(rs => { + let recvClients: WsClient[] = []; + let msgHandler = async (client: WsClient, msg1: MsgChat, msgName: string) => { + recvClients.push(client); + assert.deepStrictEqual(msg1, msg); + assert.deepStrictEqual(msgName, 'Chat') + if (recvClients.some(v => v === client1) && recvClients.some(v => v === client2)) { + client1.unlistenMsgAll('Chat'); + client2.unlistenMsgAll('Chat'); + rs(); + } + } + + client1.listenMsg('Chat', msgHandler.bind(null, client1)); + client2.listenMsg('Chat', msgHandler.bind(null, client2)); + + server.broadcastMsg('Chat', msg); + }) + + await new Promise(rs => { + let recvClients: WsClient[] = []; + let msgHandler = async (client: WsClient, msg1: MsgChat, msgName: string) => { + recvClients.push(client); + assert.deepStrictEqual(msg1, msg); + assert.deepStrictEqual(msgName, 'Chat'); + if (recvClients.some(v => v === client1) && recvClients.some(v => v === client2)) { + await server.stop(); + rs(); + } + } + + client1.listenMsg('Chat', msgHandler.bind(null, client1)); + client2.listenMsg('Chat', msgHandler.bind(null, client2)); + + server.broadcastMsg('Chat', msg, server.connections.slice()); + }) + }) + + it('broadcast to both text and buffer client (server: json)', async function () { + let server = new WsServer(getProto(), { + json: true, + port: 3001, + logger: serverLogger, + debugBuf: true + }); + + await server.start(); + + let client1 = new WsClient(getProto(), { + json: true, + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + debugBuf: true + }); + let client2 = new WsClient(getProto(), { + json: false, + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + debugBuf: true + }); + await client1.connect(); + await client2.connect(); + + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + await new Promise(rs => { + let recvClients: WsClient[] = []; + let msgHandler = async (client: WsClient, msg1: MsgChat, msgName: string) => { + recvClients.push(client); + assert.deepStrictEqual(msg1, msg); + assert.deepStrictEqual(msgName, 'Chat') + if (recvClients.some(v => v === client1) && recvClients.some(v => v === client2)) { + client1.unlistenMsgAll('Chat'); + client2.unlistenMsgAll('Chat'); + rs(); + } + } + + client1.listenMsg('Chat', msgHandler.bind(null, client1)); + client2.listenMsg('Chat', msgHandler.bind(null, client2)); + + server.broadcastMsg('Chat', msg); + }) + + await new Promise(rs => { + let recvClients: WsClient[] = []; + let msgHandler = async (client: WsClient, msg1: MsgChat, msgName: string) => { + recvClients.push(client); + assert.deepStrictEqual(msg1, msg); + assert.deepStrictEqual(msgName, 'Chat'); + if (recvClients.some(v => v === client1) && recvClients.some(v => v === client2)) { + await server.stop(); + rs(); + } + } + + client1.listenMsg('Chat', msgHandler.bind(null, client1)); + client2.listenMsg('Chat', msgHandler.bind(null, client2)); + + server.broadcastMsg('Chat', msg, server.connections.slice()); + }) + }) + + it('broadcast to both text and buffer client (server: buffer)', async function () { + let server = new WsServer(getProto(), { + json: false, + port: 3001, + logger: serverLogger, + debugBuf: true + }); + + await server.start(); + + let client1 = new WsClient(getProto(), { + json: true, + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + debugBuf: true + }); + let client2 = new WsClient(getProto(), { + json: false, + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + debugBuf: true + }); + await client1.connect(); + await client2.connect(); + + let msg: MsgChat = { + channel: 123, + userName: 'fff', + content: '666', + time: Date.now() + }; + + await new Promise(rs => { + let recvClients: WsClient[] = []; + let msgHandler = async (client: WsClient, msg1: MsgChat, msgName: string) => { + recvClients.push(client); + assert.deepStrictEqual(msg1, msg); + assert.deepStrictEqual(msgName, 'Chat') + if (recvClients.some(v => v === client1) && recvClients.some(v => v === client2)) { + client1.unlistenMsgAll('Chat'); + client2.unlistenMsgAll('Chat'); + rs(); + } + } + + client1.listenMsg('Chat', msgHandler.bind(null, client1)); + client2.listenMsg('Chat', msgHandler.bind(null, client2)); + + server.broadcastMsg('Chat', msg); + }) + + await new Promise(rs => { + let recvClients: WsClient[] = []; + let msgHandler = async (client: WsClient, msg1: MsgChat, msgName: string) => { + recvClients.push(client); + assert.deepStrictEqual(msg1, msg); + assert.deepStrictEqual(msgName, 'Chat'); + if (recvClients.some(v => v === client1) && recvClients.some(v => v === client2)) { + await server.stop(); + rs(); + } + } + + client1.listenMsg('Chat', msgHandler.bind(null, client1)); + client2.listenMsg('Chat', msgHandler.bind(null, client2)); + + server.broadcastMsg('Chat', msg, server.connections.slice()); + }) + }) + + it('abort', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + let result: any | undefined; + let promise = client.callApi('Test', { name: 'aaaaaaaa' }); + let sn = client.lastSN; + setTimeout(() => { + client.abort(sn) + }, 10); + promise.then(v => { + result = v; + }); + + await new Promise(rs => { + setTimeout(() => { + assert.strictEqual(result, undefined); + rs(); + }, 150) + }) + + await server.stop(); + }); + + it('pendingApis', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + server.autoImplementApi(path.resolve(__dirname, '../api')) + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + for (let i = 0; i < 10; ++i) { + let promise = Promise.all(Array.from({ length: 10 }, () => new Promise(rs => { + let name = ['Req', 'InnerError', 'TsrpcError', 'error'][Math.random() * 4 | 0]; + let ret: any | undefined; + let promise = client.callApi('Test', { name: name }); + let sn = client.lastSN; + let abort = Math.random() > 0.5; + if (abort) { + setTimeout(() => { + client.abort(sn) + }, 0); + } + promise.then(v => { + ret = v; + }); + + setTimeout(() => { + client.logger?.log('sn', sn, name, abort, ret) + if (abort) { + assert.strictEqual(ret, undefined); + } + else { + assert.notEqual(ret, undefined); + if (name === 'Req') { + assert.strictEqual(ret.isSucc, true); + } + else { + assert.strictEqual(ret.isSucc, false) + } + } + rs(); + }, 300) + }))); + assert.strictEqual(client['_pendingApis'].length, 10); + await promise; + assert.strictEqual(client['_pendingApis'].length, 0); + } + + await server.stop(); + }) + + it('error', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + let client1 = new WsClient(getProto(), { + json: true, + server: 'ws://localhost:80', + logger: clientLogger + }) + let res = await client1.connect(); + assert.strictEqual(res.isSucc, false); + + let ret = await client1.callApi('Test', { name: 'xx' }); + console.log(ret); + assert.strictEqual(ret.isSucc, false); + assert.strictEqual(ret.err?.code, 'WS_NOT_OPEN'); + assert.strictEqual(ret.err?.type, TsrpcErrorType.ClientError); + + await server.stop(); + }) + + it('server timeout', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger, + apiTimeout: 100 + }); + server.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.req && call.succ({ + reply: 'Hi, ' + call.req.name + }); + rs(); + }, 200) + }) + }) + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + let ret = await client.callApi('Test', { name: 'Jack' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Server Timeout', { + code: 'SERVER_TIMEOUT', + type: TsrpcErrorType.ServerError + }) + }); + + await server.stop(); + }); + + it('client timeout', async function () { + let server1 = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + server1.implementApi('Test', call => { + return new Promise(rs => { + setTimeout(() => { + call.succ({ + reply: 'Hello, ' + call.req.name + }); + rs(); + }, 2000) + }) + }) + await server1.start(); + + let client = new WsClient(getProto(), { + json: true, + timeout: 100, + logger: clientLogger + }); + await client.connect(); + + let ret = await client.callApi('Test', { name: 'Jack123' }); + // SERVER TIMEOUT的call还没执行完,但是call却被放入Pool了,导致这个BUG + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError({ + message: 'Request Timeout', + code: 'TIMEOUT', + type: TsrpcErrorType.NetworkError + }) + }); + await server1.stop(); + }); + + it('Graceful stop', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + + let reqNum = 0; + server.implementApi('Test', async call => { + if (++reqNum === 10) { + server.gracefulStop(); + } + await new Promise(rs => setTimeout(rs, parseInt(call.req.name))); + call.succ({ reply: 'OK' }); + }); + + await server.start(); + let isStopped = false; + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + let succNum = 0; + await Promise.all(Array.from({ length: 10 }, (v, i) => client.callApi('Test', { name: '' + (i * 100) }).then(v => { + console.log('xxx', v) + if (v.res?.reply === 'OK') { + ++succNum; + } + }))) + assert.strictEqual(succNum, 10); + }) + + it('Client heartbeat works', async function () { + let server = new WsServer(getProto(), { + port: 3001, + logger: serverLogger, + debugBuf: true, + json: true + }); + await server.start(); + + let client = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + heartbeat: { + interval: 1000, + timeout: 1000 + }, + debugBuf: true, + json: true + }); + await client.connect(); + + await new Promise(rs => { setTimeout(rs, 2000) }); + client.logger?.log('lastHeartbeatLatency', client.lastHeartbeatLatency); + assert.strictEqual(client.status, WsClientStatus.Opened) + assert.ok(client.lastHeartbeatLatency > 0); + + await client.disconnect(); + await server.stop(); + }) + + it('Client heartbeat error', async function () { + let server = new WsServer(getProto(), { + port: 3001, + logger: serverLogger, + debugBuf: true, + json: true + }); + await server.start(); + + let client = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + heartbeat: { + interval: 1000, + timeout: 1000 + }, + debugBuf: true, + json: true + }); + + let disconnectFlowData: { isManual?: boolean } | undefined; + client.flows.postDisconnectFlow.push(v => { + disconnectFlowData = {} + return v; + }) + + await client.connect(); + + const temp = TransportDataUtil.HeartbeatPacket; + (TransportDataUtil as any).HeartbeatPacket = new Uint8Array([0, 0]); + + await new Promise(rs => { setTimeout(rs, 2000) }); + client.logger?.log('lastHeartbeatLatency', client.lastHeartbeatLatency); + assert.strictEqual(client.status, WsClientStatus.Closed) + assert.deepStrictEqual(disconnectFlowData, {}) + + await client.disconnect(); + await server.stop(); + (TransportDataUtil as any).HeartbeatPacket = temp; + }) + + it('Server heartbeat kick', async function () { + let server = new WsServer(getProto(), { + port: 3001, + logger: serverLogger, + debugBuf: true, + heartbeatWaitTime: 1000, + json: true + }); + await server.start(); + + let client = new WsClient(getProto(), { + server: 'ws://127.0.0.1:3001', + logger: clientLogger, + debugBuf: true, + json: true + }); + + let disconnectFlowData: { isManual?: boolean } | undefined; + client.flows.postDisconnectFlow.push(v => { + disconnectFlowData = {} + return v; + }) + + await client.connect(); + + await new Promise(rs => { setTimeout(rs, 2000) }); + assert.strictEqual(client.status, WsClientStatus.Closed) + assert.deepStrictEqual(disconnectFlowData, {}) + + await client.disconnect(); + await server.stop(); + }) +}) + +describe('WS Flows', function () { + it('Server conn flow', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + assert.strictEqual((call.conn as any).xxxx, 'asdfasdf') + assert.strictEqual(flowExecResult.postConnectFlow, true); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + call.succ({ reply: 'ok' }); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + }); + + server.flows.postConnectFlow.push(v => { + flowExecResult.postConnectFlow = true; + (v as any).xxxx = 'asdfasdf'; + return v; + }); + server.flows.postDisconnectFlow.push(v => { + server.logger.log('server postDisconnectFlow') + flowExecResult.postDisconnectFlow = true; + return v; + }) + + await server.start(); + + assert.strictEqual(flowExecResult.postConnectFlow, undefined); + assert.strictEqual(flowExecResult.postDisconnectFlow, undefined); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postConnectFlow, true); + await server.stop(); + assert.strictEqual(flowExecResult.postDisconnectFlow, true); + }) + + it('Buffer enc/dec flow', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger, + debugBuf: true + }); + + const flowExecResult: { [key: string]: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'Enc&Dec' }); + }); + + server.flows.preRecvDataFlow.push(v => { + flowExecResult.preRecvDataFlow = true; + v.data = (v.data as string).split('').reverse().join(''); + return v; + }); + server.flows.preSendDataFlow.push(v => { + flowExecResult.preSendDataFlow = true; + v.data = (v.data as string).split('').reverse().join(''); + return v; + }) + + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger, + debugBuf: true + }); + await client.connect(); + + client.flows.preSendDataFlow.push(v => { + flowExecResult.client_preSendDataFlow = true; + v.data = (v.data as string).split('').reverse().join(''); + return v; + }); + + client.flows.preRecvDataFlow.push(v => { + flowExecResult.client_preRecvDataFlow = true; + v.data = (v.data as string).split('').reverse().join(''); + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.client_preSendDataFlow, true); + assert.strictEqual(flowExecResult.client_preRecvDataFlow, true); + assert.strictEqual(flowExecResult.preRecvDataFlow, true); + assert.strictEqual(flowExecResult.preSendDataFlow, true); + assert.deepStrictEqual(ret, { + isSucc: true, + res: { + reply: 'Enc&Dec' + } + }) + + await server.stop(); + }); + + it('ApiCall flow', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return call; + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + + await server.stop(); + }); + + it('ApiCall flow break', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + call.error('You need login'); + return undefined; + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, undefined); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('You need login') + }) + + await server.stop(); + }); + + it('ApiCall flow error', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'asdgasdgasdgasdg' }); + }); + + server.flows.preApiCallFlow.push(call => { + assert.strictEqual(call.req.name, 'Changed') + throw new Error('ASDFASDF') + }); + server.flows.postApiCallFlow.push(v => { + flowExecResult.postApiCallFlow = true; + return v; + }) + + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + client.flows.preCallApiFlow.push(v => { + if (v.apiName !== 'ObjId') { + v.req.name = 'Changed' + } + return v; + }); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.postApiCallFlow, undefined); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Internal Server Error', { + type: TsrpcErrorType.ServerError, + innerErr: 'ASDFASDF', + code: 'INTERNAL_ERR' + }) + }) + + await server.stop(); + }); + + it('server ApiReturn flow', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof BaseServer['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + server.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: new TsrpcError('Ret changed') + } + return v; + }); + server.flows.postApiReturnFlow.push(v => { + flowExecResult.postApiReturnFlow = true; + v.call.logger.log('RETTT', v.return); + return v; + }) + + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.strictEqual(flowExecResult.postApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Ret changed') + }) + + await server.stop(); + }); + + it('client ApiReturn flow', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + + const flowExecResult: { [K in (keyof WsClient['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + client.flows.preApiReturnFlow.push(v => { + flowExecResult.preApiReturnFlow = true; + v.return = { + isSucc: false, + err: new TsrpcError('Ret changed') + } + return v; + }); + client.flows.postApiReturnFlow.push(v => { + flowExecResult.postApiReturnFlow = true; + client.logger?.log('RETTT', v.return); + return v; + }) + + let ret = await client.callApi('Test', { name: 'xxx' }); + assert.strictEqual(flowExecResult.preApiReturnFlow, true); + assert.strictEqual(flowExecResult.postApiReturnFlow, true); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Ret changed') + }) + + await server.stop(); + }); + + it('client SendBufferFlow prevent', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + + // const flowExecResult: { [K in (keyof BaseClient['flows'])]?: boolean } = {}; + + server.implementApi('Test', async call => { + call.succ({ reply: 'xxxxxxxxxxxxxxxxxxxx' }); + }); + + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + client.flows.preSendDataFlow.push(v => { + return undefined + }); + + let ret: any; + client.callApi('Test', { name: 'xxx' }).then(v => { ret = v }); + await new Promise(rs => { setTimeout(rs, 200) }); + assert.strictEqual(ret, undefined) + + await server.stop(); + }); + + it('onInputBufferError', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + client.flows.preSendDataFlow.push(v => { + v.data = (v.data as string).split('').reverse().join(''); + return v; + }); + + let ret = await client.callApi('Test', { name: 'XXX' }); + assert.deepStrictEqual(ret, { + isSucc: false, + err: new TsrpcError('Input is not a valid JSON string: Unexpected token ] in JSON at position 0', { type: TsrpcErrorType.NetworkError, code: 'LOST_CONN' }) + }) + + await server.stop(); + }) + + it('ObjectId', async function () { + let server = new WsServer(getProto(), { + json: true, + logger: serverLogger + }); + server.autoImplementApi(path.resolve(__dirname, '../api')) + await server.start(); + + let client = new WsClient(getProto(), { + json: true, + logger: clientLogger + }); + await client.connect(); + + // ObjectId + let objId1 = new ObjectId(); + let ret = await client.callApi('ObjId', { + id1: objId1 + }); + assert.strictEqual(ret.isSucc, true, ret.err?.message); + assert.strictEqual(objId1.toString(), ret.res!.id2.toString()); + + await server.stop(); + }) +}) \ No newline at end of file diff --git a/test/proto/MsgChat.ts b/test/proto/MsgChat.ts new file mode 100644 index 0000000..7616d8a --- /dev/null +++ b/test/proto/MsgChat.ts @@ -0,0 +1,6 @@ +export interface MsgChat { + channel: number, + userName: string, + content: string, + time: number +} \ No newline at end of file diff --git a/test/proto/PtlObjId.ts b/test/proto/PtlObjId.ts new file mode 100644 index 0000000..3fd26eb --- /dev/null +++ b/test/proto/PtlObjId.ts @@ -0,0 +1,14 @@ +// @ts-ignore +import { ObjectId } from "mongodb"; + +export interface ReqObjId { + id1: ObjectId; + buf?: Uint8Array, + date?: Date +} + +export interface ResObjId { + id2: ObjectId; + buf?: Uint8Array, + date?: Date +} \ No newline at end of file diff --git a/test/proto/PtlTest.ts b/test/proto/PtlTest.ts new file mode 100644 index 0000000..71c859d --- /dev/null +++ b/test/proto/PtlTest.ts @@ -0,0 +1,7 @@ +export interface ReqTest { + name: string +}; + +export type ResTest = { + reply: string +}; \ No newline at end of file diff --git a/test/proto/a/b/c/PtlTest.ts b/test/proto/a/b/c/PtlTest.ts new file mode 100644 index 0000000..f3a1146 --- /dev/null +++ b/test/proto/a/b/c/PtlTest.ts @@ -0,0 +1,10 @@ +import { MsgChat } from '../../../MsgChat'; + +export interface ReqTest { + name: string +}; + +export type ResTest = { + reply: string, + chat?: MsgChat +}; \ No newline at end of file diff --git a/test/proto/serviceProto.ts b/test/proto/serviceProto.ts new file mode 100644 index 0000000..ee82feb --- /dev/null +++ b/test/proto/serviceProto.ts @@ -0,0 +1,203 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqTest, ResTest } from './a/b/c/PtlTest'; +import { MsgChat } from './MsgChat'; +import { ReqObjId, ResObjId } from './PtlObjId'; +import { ReqTest as ReqTest_1, ResTest as ResTest_1 } from './PtlTest'; + +export interface ServiceType { + api: { + "a/b/c/Test": { + req: ReqTest, + res: ResTest + }, + "ObjId": { + req: ReqObjId, + res: ResObjId + }, + "Test": { + req: ReqTest_1, + res: ResTest_1 + } + }, + msg: { + "Chat": MsgChat + } +} + +export const serviceProto: ServiceProto = { + "version": 1, + "services": [ + { + "id": 0, + "name": "a/b/c/Test", + "type": "api" + }, + { + "id": 1, + "name": "Chat", + "type": "msg" + }, + { + "id": 2, + "name": "ObjId", + "type": "api" + }, + { + "id": 3, + "name": "Test", + "type": "api" + } + ], + "types": { + "a/b/c/PtlTest/ReqTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "name", + "type": { + "type": "String" + } + } + ] + }, + "a/b/c/PtlTest/ResTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "reply", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "chat", + "type": { + "type": "Reference", + "target": "MsgChat/MsgChat" + }, + "optional": true + } + ] + }, + "MsgChat/MsgChat": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "channel", + "type": { + "type": "Number" + } + }, + { + "id": 1, + "name": "userName", + "type": { + "type": "String" + } + }, + { + "id": 2, + "name": "content", + "type": { + "type": "String" + } + }, + { + "id": 3, + "name": "time", + "type": { + "type": "Number" + } + } + ] + }, + "PtlObjId/ReqObjId": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "id1", + "type": { + "type": "Reference", + "target": "?mongodb/ObjectId" + } + }, + { + "id": 1, + "name": "buf", + "type": { + "type": "Buffer", + "arrayType": "Uint8Array" + }, + "optional": true + }, + { + "id": 2, + "name": "date", + "type": { + "type": "Date" + }, + "optional": true + } + ] + }, + "PtlObjId/ResObjId": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "id2", + "type": { + "type": "Reference", + "target": "?mongodb/ObjectId" + } + }, + { + "id": 1, + "name": "buf", + "type": { + "type": "Buffer", + "arrayType": "Uint8Array" + }, + "optional": true + }, + { + "id": 2, + "name": "date", + "type": { + "type": "Date" + }, + "optional": true + } + ] + }, + "PtlTest/ReqTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "name", + "type": { + "type": "String" + } + } + ] + }, + "PtlTest/ResTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "reply", + "type": { + "type": "String" + } + } + ] + } + } +}; \ No newline at end of file diff --git a/test/test.ts b/test/test.ts new file mode 100644 index 0000000..5729add --- /dev/null +++ b/test/test.ts @@ -0,0 +1,18 @@ +import { HttpServer } from '../src/server/http/HttpServer'; +import { serviceProto } from './proto/serviceProto'; + +let server = new HttpServer(serviceProto, { + jsonEnabled: true, + jsonRootPath: 'api' +}); + +server.implementApi('a/b/c/Test', call => { + call.logger.log('xxx', call.req); + call.succ({ + reply: 'xxxxxxxxxxx', + aasdg: 'd', + b: 'asdg' + } as any) +}); + +server.start(); \ No newline at end of file diff --git a/test/try/client/http.ts b/test/try/client/http.ts new file mode 100644 index 0000000..f78f28a --- /dev/null +++ b/test/try/client/http.ts @@ -0,0 +1,22 @@ +import { HttpClient } from '../../src/client/http/HttpClient'; +import { serviceProto, ServiceType } from '../proto/serviceProto'; +let client = new HttpClient({ + server: 'http://localhost:3000', + proto: serviceProto +}); + +async function main() { + const P = 50, N = 1000; + let max = 0; + console.time(`test ${P}/${N}`); + for (let i = 0, len = N / P; i < len; ++i) { + let res = await Promise.all(Array.from({ length: P }, () => { + let start = Date.now(); + return client.callApi('a/b/c/Test', { name: '123' }).then(() => Date.now() - start); + })); + max = Math.max(res.max(), max) + } + console.timeEnd(`test ${P}/${N}`); + console.log('max', max) +} +main(); \ No newline at end of file diff --git a/test/try/client/ws.ts b/test/try/client/ws.ts new file mode 100644 index 0000000..286b4f2 --- /dev/null +++ b/test/try/client/ws.ts @@ -0,0 +1,82 @@ +import { serviceProto, ServiceType } from '../proto/serviceProto'; +import { WsClient } from '../../src/client/ws/WsClient'; +import SuperPromise from 'k8w-super-promise'; +import { Func } from 'mocha'; + +async function main() { + let client = new WsClient({ + server: 'ws://127.0.0.1:3000', + proto: serviceProto, + onStatusChange: v => { + console.log('StatusChange', v); + }, + // onLostConnection: () => { + // console.log('连接断开,2秒后重连'); + // setTimeout(() => { + // client.connect().catch(() => { }); + // }, 2000) + // } + }); + + await client.connect(); + + let cancel = client.callApi('Test', { name: 'XXXXXXXXXXXXX' }).catch(e => e); + cancel.cancel(); + + let res = await client.callApi('Test', { name: '小明同学' }).catch(e => e); + console.log('Test Res', res); + + res = await client.callApi('a/b/c/Test', { name: '小明同学' }).catch(e => e); + console.log('Test1 Res', res); + + // setInterval(async () => { + // try { + // let res = await client.callApi('Test', { name: '小明同学' }); + // console.log('收到回复', res); + // } + // catch (e) { + // if (e.info === 'NETWORK_ERR') { + // return; + // } + // console.log('API错误', e) + // } + // }, 1000); + + // client.listenMsg('Chat', msg => { + // console.log('收到MSG', msg); + // }); + + // setInterval(() => { + // try { + // client.sendMsg('Chat', { + // channel: 123, + // userName: '王小明', + // content: '你好', + // time: Date.now() + // }).catch(e => { + // console.log('SendMsg Failed', e.message) + // }) + // } + // catch{ } + // }, 1000) + + // #region Benchmark + // let maxTime = 0; + // let done = 0; + // let startTime = Date.now(); + + // setTimeout(() => { + // console.log('done', maxTime, done); + // process.exit(); + // }, 3000); + + // for (let i = 0; i < 10000; ++i) { + // client.callApi('Test', { name: '小明同学' }).then(() => { + // ++done; + // maxTime = Math.max(maxTime, Date.now() - startTime) + // }) + // } + // #endregion +} + +main(); \ No newline at end of file diff --git a/test/try/massive.ts b/test/try/massive.ts new file mode 100644 index 0000000..fb10630 --- /dev/null +++ b/test/try/massive.ts @@ -0,0 +1,27 @@ +import { TSRPCClient } from ".."; +import { serviceProto, ServiceType } from './proto/serviceProto'; + +async function main() { + setInterval(() => { + for (let i = 0; i < 100; ++i) { + let client = new TSRPCClient({ + server: 'ws://127.0.0.1:3000', + proto: serviceProto + }); + + client.connect().then(() => { + client.callApi('a/b/c/Test1', { name: '小明同学' }).then(v => { + // console.log('成功', v) + }).catch(e => { + console.error('错误', e.message) + }).then(() => { + client.disconnect(); + }); + }).catch(e => { + console.error('连接错误', e) + }) + } + }, 1000) +} + +main(); \ No newline at end of file diff --git a/test/try/no-res-issue/client/index.ts b/test/try/no-res-issue/client/index.ts new file mode 100644 index 0000000..114d653 --- /dev/null +++ b/test/try/no-res-issue/client/index.ts @@ -0,0 +1,12 @@ +import { TsrpcClient } from "../../.."; +import { serviceProto } from '../server/protocols/proto'; + +let client = new TsrpcClient({ + proto: serviceProto +}); + +client.callApi('Test', { name: 'ssss' }).then(v => { + console.log('then', v) +}).catch(e => { + console.log('catch', e) +}) \ No newline at end of file diff --git a/test/try/no-res-issue/server/index.ts b/test/try/no-res-issue/server/index.ts new file mode 100644 index 0000000..f772abd --- /dev/null +++ b/test/try/no-res-issue/server/index.ts @@ -0,0 +1,10 @@ +import { TsrpcServer } from '../../../index'; +import { serviceProto } from './protocols/proto'; + +let server = new TsrpcServer({ + proto: serviceProto, +}); + +server.autoImplementApi('src/api'); + +server.start(); \ No newline at end of file diff --git a/test/try/no-res-issue/server/protocols/PtlTest.ts b/test/try/no-res-issue/server/protocols/PtlTest.ts new file mode 100644 index 0000000..ce7579b --- /dev/null +++ b/test/try/no-res-issue/server/protocols/PtlTest.ts @@ -0,0 +1,7 @@ +export interface ReqTest { + name: string +} + +export interface ResTest { + reply: string +} \ No newline at end of file diff --git a/test/try/no-res-issue/server/protocols/proto.ts b/test/try/no-res-issue/server/protocols/proto.ts new file mode 100644 index 0000000..89df20c --- /dev/null +++ b/test/try/no-res-issue/server/protocols/proto.ts @@ -0,0 +1,52 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqTest, ResTest } from './PtlTest' + +export interface ServiceType { + req: { + "Test": ReqTest + }, + res: { + "Test": ResTest + }, + msg: { + + } +} + +export const serviceProto: ServiceProto = { + "services": [ + { + "id": 0, + "name": "Test", + "type": "api", + "req": "PtlTest/ReqTest", + "res": "PtlTest/ResTest" + } + ], + "types": { + "PtlTest/ReqTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "name", + "type": { + "type": "String" + } + } + ] + }, + "PtlTest/ResTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "reply", + "type": { + "type": "String" + } + } + ] + } + } +}; \ No newline at end of file diff --git a/test/try/no-res-issue/server/src/api/ApiTest.ts b/test/try/no-res-issue/server/src/api/ApiTest.ts new file mode 100644 index 0000000..c71d2e9 --- /dev/null +++ b/test/try/no-res-issue/server/src/api/ApiTest.ts @@ -0,0 +1,18 @@ +import { ReqTest, ResTest } from "../../protocols/PtlTest"; +import { ApiCall } from "../../../../.."; + +export async function ApiTest(call: ApiCall) { + await new Promise(rs => { + let i = 5; + call.logger.log(i); + let interval = setInterval(() => { + call.logger.log(--i); + if (i === 0) { + clearInterval(interval); + rs(); + } + }, 1000); + }); + + call.error('asdfasdf', { a: 1, b: 2 }) +} \ No newline at end of file diff --git a/test/try/package-lock.json b/test/try/package-lock.json new file mode 100644 index 0000000..d4c87c3 --- /dev/null +++ b/test/try/package-lock.json @@ -0,0 +1,12 @@ +{ + "name": "try", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "version": "1.0.0", + "license": "ISC" + } + } +} diff --git a/test/try/package.json b/test/try/package.json new file mode 100644 index 0000000..36e8cd2 --- /dev/null +++ b/test/try/package.json @@ -0,0 +1,11 @@ +{ + "name": "try", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/test/try/proto/MsgChat.ts b/test/try/proto/MsgChat.ts new file mode 100644 index 0000000..7616d8a --- /dev/null +++ b/test/try/proto/MsgChat.ts @@ -0,0 +1,6 @@ +export interface MsgChat { + channel: number, + userName: string, + content: string, + time: number +} \ No newline at end of file diff --git a/test/try/proto/PtlTest.ts b/test/try/proto/PtlTest.ts new file mode 100644 index 0000000..71c859d --- /dev/null +++ b/test/try/proto/PtlTest.ts @@ -0,0 +1,7 @@ +export interface ReqTest { + name: string +}; + +export type ResTest = { + reply: string +}; \ No newline at end of file diff --git a/test/try/proto/a/b/c/PtlTest.ts b/test/try/proto/a/b/c/PtlTest.ts new file mode 100644 index 0000000..f3a1146 --- /dev/null +++ b/test/try/proto/a/b/c/PtlTest.ts @@ -0,0 +1,10 @@ +import { MsgChat } from '../../../MsgChat'; + +export interface ReqTest { + name: string +}; + +export type ResTest = { + reply: string, + chat?: MsgChat +}; \ No newline at end of file diff --git a/test/try/proto/serviceProto.ts b/test/try/proto/serviceProto.ts new file mode 100644 index 0000000..ce24e8f --- /dev/null +++ b/test/try/proto/serviceProto.ts @@ -0,0 +1,135 @@ +import { ServiceProto } from 'tsrpc-proto'; +import { ReqTest, ResTest } from './a/b/c/PtlTest' +import { MsgChat } from './MsgChat' +import { ReqTest as ReqTest_1, ResTest as ResTest_1 } from './PtlTest' + +export interface ServiceType { + req: { + "a/b/c/Test": ReqTest, + "Test": ReqTest_1 + }, + res: { + "a/b/c/Test": ResTest, + "Test": ResTest_1 + }, + msg: { + "Chat": MsgChat + } +} + +export const serviceProto: ServiceProto = { + "services": [ + { + "id": 0, + "name": "a/b/c/Test", + "type": "api", + "req": "a/b/c/PtlTest/ReqTest", + "res": "a/b/c/PtlTest/ResTest" + }, + { + "id": 1, + "name": "Chat", + "type": "msg", + "msg": "MsgChat/MsgChat" + }, + { + "id": 2, + "name": "Test", + "type": "api", + "req": "PtlTest/ReqTest", + "res": "PtlTest/ResTest" + } + ], + "types": { + "a/b/c/PtlTest/ReqTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "name", + "type": { + "type": "String" + } + } + ] + }, + "a/b/c/PtlTest/ResTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "reply", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "chat", + "type": { + "type": "Reference", + "target": "MsgChat/MsgChat" + }, + "optional": true + } + ] + }, + "MsgChat/MsgChat": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "channel", + "type": { + "type": "Number" + } + }, + { + "id": 1, + "name": "userName", + "type": { + "type": "String" + } + }, + { + "id": 2, + "name": "content", + "type": { + "type": "String" + } + }, + { + "id": 3, + "name": "time", + "type": { + "type": "Number" + } + } + ] + }, + "PtlTest/ReqTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "name", + "type": { + "type": "String" + } + } + ] + }, + "PtlTest/ResTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "reply", + "type": { + "type": "String" + } + } + ] + } + } +}; \ No newline at end of file diff --git a/test/try/proto/typeProto.json b/test/try/proto/typeProto.json new file mode 100644 index 0000000..a8f9060 --- /dev/null +++ b/test/try/proto/typeProto.json @@ -0,0 +1,59 @@ +{ + "MsgChat/MsgChat": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "channel", + "type": { + "type": "String" + } + }, + { + "id": 1, + "name": "userName", + "type": { + "type": "String" + } + }, + { + "id": 2, + "name": "content", + "type": { + "type": "String" + } + }, + { + "id": 3, + "name": "time", + "type": { + "type": "Number" + } + } + ] + }, + "PtlTest/ReqTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "name", + "type": { + "type": "String" + } + } + ] + }, + "PtlTest/ResTest": { + "type": "Interface", + "properties": [ + { + "id": 0, + "name": "reply", + "type": { + "type": "String" + } + } + ] + } +} \ No newline at end of file diff --git a/test/try/server/api/ApiTest.ts b/test/try/server/api/ApiTest.ts new file mode 100644 index 0000000..2d5e24a --- /dev/null +++ b/test/try/server/api/ApiTest.ts @@ -0,0 +1,21 @@ +import { TsrpcError } from "tsrpc-proto"; +import { ApiCallHttp } from '../../../src/server/http/HttpCall'; +import { ReqTest } from "../../proto/PtlTest"; +import { ResTest } from '../../proto/a/b/c/PtlTest'; + +export async function ApiTest(call: ApiCallHttp) { + if (Math.random() > 0.75) { + call.succ({ + reply: 'Hello, ' + call.req.name + }) + } + else if (Math.random() > 0.5) { + call.error('What the fuck??', { msg: '哈哈哈哈' }) + } + else if (Math.random() > 0.25) { + throw new Error('这应该是InternalERROR') + } + else { + throw new TsrpcError('返回到前台的错误', 'ErrInfo'); + } +} \ No newline at end of file diff --git a/test/try/server/api/a/b/c/ApiTest.ts b/test/try/server/api/a/b/c/ApiTest.ts new file mode 100644 index 0000000..dcfb075 --- /dev/null +++ b/test/try/server/api/a/b/c/ApiTest.ts @@ -0,0 +1,5 @@ +export async function ApiTest(call: any) { + call.succ({ + reply: 'Api Test1 Succ' + }) +} \ No newline at end of file diff --git a/test/try/server/http.ts b/test/try/server/http.ts new file mode 100644 index 0000000..243f270 --- /dev/null +++ b/test/try/server/http.ts @@ -0,0 +1,22 @@ +import { serviceProto } from '../proto/serviceProto'; +import * as path from "path"; +import { TsrpcServer } from '../../index'; + +let server = new TsrpcServer({ + proto: serviceProto +}); + +server.dataFlow.push((data, conn) => { + let httpReq = conn.options.httpReq; + if (httpReq.method === 'GET') { + conn.logger.log('url', httpReq.url); + conn.logger.log('host', httpReq.headers.host); + conn.options.httpRes.end('Hello~~~'); + return false; + } + + return true; +}) + +server.autoImplementApi(path.resolve(__dirname, 'api')); +server.start(); \ No newline at end of file diff --git a/test/try/server/ws.ts b/test/try/server/ws.ts new file mode 100644 index 0000000..4aad514 --- /dev/null +++ b/test/try/server/ws.ts @@ -0,0 +1,18 @@ +import { serviceProto, ServiceType } from '../proto/serviceProto'; +import * as path from "path"; +import { TsrpcServerWs } from '../../index'; + +let server = new TsrpcServerWs({ + proto: serviceProto +}); +server.start(); + +server.autoImplementApi(path.resolve(__dirname, 'api')); +server.listenMsg('Chat', v => { + v.conn.sendMsg('Chat', { + channel: v.msg.channel, + userName: 'SYSTEM', + content: '收到', + time: Date.now() + }) +}) \ No newline at end of file diff --git a/test/try/tsconfig.json b/test/try/tsconfig.json new file mode 100644 index 0000000..2b032d8 --- /dev/null +++ b/test/try/tsconfig.json @@ -0,0 +1,59 @@ +{ + "compilerOptions": { + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "useUnknownInCatchVariables": false + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8b77b41 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,66 @@ +{ + "compilerOptions": { + /* Basic Options */ + "target": "es2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [ + // "es5", + // "dom" + // ], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./lib", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "incremental": true, /* Enable incremental compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true /* Do not resolve the real path of symlinks. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "useUnknownInCatchVariables": false + }, + "include": [ + "src" + // "test" + // "benchmark" + ] +} \ No newline at end of file