fixed all libs to be buildiable and dependency hell from removing some
This commit is contained in:
parent
5c64b1ccf8
commit
a282dac6cd
40 changed files with 4050 additions and 8219 deletions
15
README.md
Normal file
15
README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# stock-bot
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
bun run
|
||||
```
|
||||
|
||||
This project was created using `bun init` in bun v1.2.15. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||
292
libs/config/bun.lock
Normal file
292
libs/config/bun.lock
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "@stock-bot/config",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.3.1",
|
||||
"envalid": "^8.0.0",
|
||||
"zod": "^3.22.4",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||
"@typescript-eslint/parser": "^6.19.0",
|
||||
"bun-types": "^1.2.15",
|
||||
"eslint": "^8.56.0",
|
||||
"typescript": "^5.3.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="],
|
||||
|
||||
"@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
|
||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/node": ["@types/node@20.17.57", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ=="],
|
||||
|
||||
"@types/semver": ["@types/semver@7.7.0", "", {}, "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@6.21.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/type-utils": "6.21.0", "@typescript-eslint/utils": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", "natural-compare": "^1.4.0", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@6.21.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0" } }, "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@6.21.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@6.21.0", "", {}, "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" } }, "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@6.21.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
"acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "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" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
|
||||
|
||||
"doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="],
|
||||
|
||||
"dotenv": ["dotenv@16.5.0", "", {}, "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg=="],
|
||||
|
||||
"envalid": ["envalid@8.0.0", "", { "dependencies": { "tslib": "2.6.2" } }, "sha512-PGeYJnJB5naN0ME6SH8nFcDj9HVbLpYIfg1p5lAyM9T4cH2lwtu2fLbozC/bq+HUUOIFxhX/LP0/GmlqPHT4tQ=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
|
||||
|
||||
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="],
|
||||
|
||||
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
|
||||
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="],
|
||||
|
||||
"globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
|
||||
|
||||
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="],
|
||||
|
||||
"tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@3.25.51", "", {}, "sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.3", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg=="],
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json", "compilerOptions": {
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"declaration": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": false,
|
||||
},"include": ["src/**/*", "test/**/*"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"references": [
|
||||
{ "path": "../api-client" },
|
||||
{ "path": "../event-bus" },
|
||||
{ "path": "../http-client" },
|
||||
{ "path": "../utils" }
|
||||
]
|
||||
"rootDir": "./src",
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
|
||||
}
|
||||
|
|
|
|||
283
libs/http-client/README.md
Normal file
283
libs/http-client/README.md
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
# HTTP Client Library
|
||||
|
||||
A comprehensive HTTP client library for the Stock Bot platform with built-in support for:
|
||||
|
||||
- ✅ **Fetch API** - Modern, promise-based HTTP requests
|
||||
- ✅ **Proxy Support** - HTTP, HTTPS, SOCKS4, and SOCKS5 proxies
|
||||
- ✅ **Rate Limiting** - Configurable request rate limiting
|
||||
- ✅ **Timeout Handling** - Request timeouts with abort controllers
|
||||
- ✅ **Retry Logic** - Automatic retries with exponential backoff
|
||||
- ✅ **TypeScript** - Full TypeScript support with type safety
|
||||
- ✅ **Logging Integration** - Optional logger integration
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bun add @stock-bot/http-client
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```typescript
|
||||
import { HttpClient } from '@stock-bot/http-client';
|
||||
|
||||
// Create a client with default configuration
|
||||
const client = new HttpClient();
|
||||
|
||||
// Make a GET request
|
||||
const response = await client.get('https://api.example.com/data');
|
||||
console.log(response.data);
|
||||
|
||||
// Make a POST request
|
||||
const postResponse = await client.post('https://api.example.com/users', {
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com'
|
||||
});
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
```typescript
|
||||
import { HttpClient } from '@stock-bot/http-client';
|
||||
import { logger } from '@stock-bot/logger';
|
||||
|
||||
const client = new HttpClient({
|
||||
baseURL: 'https://api.example.com',
|
||||
timeout: 10000, // 10 seconds
|
||||
retries: 3,
|
||||
retryDelay: 1000, // 1 second base delay
|
||||
defaultHeaders: {
|
||||
'Authorization': 'Bearer token',
|
||||
'User-Agent': 'Stock-Bot/1.0'
|
||||
},
|
||||
validateStatus: (status) => status < 400
|
||||
}, logger);
|
||||
```
|
||||
|
||||
## Proxy Support
|
||||
|
||||
### HTTP/HTTPS Proxy
|
||||
|
||||
```typescript
|
||||
const client = new HttpClient({
|
||||
proxy: {
|
||||
type: 'http',
|
||||
host: 'proxy.example.com',
|
||||
port: 8080,
|
||||
username: 'user', // optional
|
||||
password: 'pass' // optional
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### SOCKS Proxy
|
||||
|
||||
```typescript
|
||||
const client = new HttpClient({
|
||||
proxy: {
|
||||
type: 'socks5',
|
||||
host: 'socks-proxy.example.com',
|
||||
port: 1080,
|
||||
username: 'user', // optional
|
||||
password: 'pass' // optional
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
```typescript
|
||||
const client = new HttpClient({
|
||||
rateLimit: {
|
||||
maxRequests: 100, // Max 100 requests
|
||||
windowMs: 60 * 1000, // Per 1 minute
|
||||
skipSuccessfulRequests: false,
|
||||
skipFailedRequests: true // Don't count failed requests
|
||||
}
|
||||
});
|
||||
|
||||
// Check rate limit status
|
||||
const status = client.getRateLimitStatus();
|
||||
console.log(`${status.currentCount}/${status.maxRequests} requests used`);
|
||||
```
|
||||
|
||||
## Request Methods
|
||||
|
||||
```typescript
|
||||
// GET request
|
||||
const getData = await client.get('/api/data');
|
||||
|
||||
// POST request with body
|
||||
const postData = await client.post('/api/users', {
|
||||
name: 'John',
|
||||
email: 'john@example.com'
|
||||
});
|
||||
|
||||
// PUT request
|
||||
const putData = await client.put('/api/users/1', updatedUser);
|
||||
|
||||
// DELETE request
|
||||
const deleteData = await client.delete('/api/users/1');
|
||||
|
||||
// PATCH request
|
||||
const patchData = await client.patch('/api/users/1', { name: 'Jane' });
|
||||
|
||||
// Custom request
|
||||
const customResponse = await client.request({
|
||||
method: 'POST',
|
||||
url: '/api/custom',
|
||||
headers: { 'X-Custom': 'value' },
|
||||
body: { data: 'custom' },
|
||||
timeout: 5000
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```typescript
|
||||
import { HttpError, TimeoutError, RateLimitError } from '@stock-bot/http-client';
|
||||
|
||||
try {
|
||||
const response = await client.get('/api/data');
|
||||
} catch (error) {
|
||||
if (error instanceof TimeoutError) {
|
||||
console.log('Request timed out');
|
||||
} else if (error instanceof RateLimitError) {
|
||||
console.log(`Rate limited: retry after ${error.retryAfter}ms`);
|
||||
} else if (error instanceof HttpError) {
|
||||
console.log(`HTTP error ${error.status}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Retry Configuration
|
||||
|
||||
```typescript
|
||||
const client = new HttpClient({
|
||||
retries: 3, // Retry up to 3 times
|
||||
retryDelay: 1000, // Base delay of 1 second
|
||||
// Exponential backoff: 1s, 2s, 4s
|
||||
});
|
||||
|
||||
// Or per-request retry configuration
|
||||
const response = await client.get('/api/data', {
|
||||
retries: 5,
|
||||
retryDelay: 500
|
||||
});
|
||||
```
|
||||
|
||||
## Timeout Handling
|
||||
|
||||
```typescript
|
||||
// Global timeout
|
||||
const client = new HttpClient({
|
||||
timeout: 30000 // 30 seconds
|
||||
});
|
||||
|
||||
// Per-request timeout
|
||||
const response = await client.get('/api/data', {
|
||||
timeout: 5000 // 5 seconds for this request
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Status Validation
|
||||
|
||||
```typescript
|
||||
const client = new HttpClient({
|
||||
validateStatus: (status) => {
|
||||
// Accept 2xx and 3xx status codes
|
||||
return status >= 200 && status < 400;
|
||||
}
|
||||
});
|
||||
|
||||
// Or per-request validation
|
||||
const response = await client.get('/api/data', {
|
||||
validateStatus: (status) => status === 200 || status === 404
|
||||
});
|
||||
```
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
The library is fully typed with TypeScript:
|
||||
|
||||
```typescript
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
// Response data is properly typed
|
||||
const response = await client.get<User[]>('/api/users');
|
||||
const users: User[] = response.data;
|
||||
|
||||
// Request configuration is validated
|
||||
const config: RequestConfig = {
|
||||
method: 'POST',
|
||||
url: '/api/users',
|
||||
body: { name: 'John' },
|
||||
timeout: 5000
|
||||
};
|
||||
```
|
||||
|
||||
## Integration with Logger
|
||||
|
||||
```typescript
|
||||
import { logger } from '@stock-bot/logger';
|
||||
import { HttpClient } from '@stock-bot/http-client';
|
||||
|
||||
const client = new HttpClient({
|
||||
baseURL: 'https://api.example.com'
|
||||
}, logger);
|
||||
|
||||
// All requests will be logged with debug/warn/error levels
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
bun test
|
||||
|
||||
# Run with coverage
|
||||
bun test --coverage
|
||||
|
||||
# Watch mode
|
||||
bun test --watch
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Proxy Support
|
||||
- HTTP and HTTPS proxies
|
||||
- SOCKS4 and SOCKS5 proxies
|
||||
- Authentication support
|
||||
- Automatic agent creation
|
||||
|
||||
### Rate Limiting
|
||||
- Token bucket algorithm
|
||||
- Configurable window and request limits
|
||||
- Skip successful/failed requests options
|
||||
- Real-time status monitoring
|
||||
|
||||
### Retry Logic
|
||||
- Exponential backoff
|
||||
- Configurable retry attempts
|
||||
- Smart retry conditions (5xx errors only)
|
||||
- Per-request retry override
|
||||
|
||||
### Error Handling
|
||||
- Typed error classes
|
||||
- Detailed error information
|
||||
- Request/response context
|
||||
- Timeout detection
|
||||
|
||||
### Performance
|
||||
- Built on modern Fetch API
|
||||
- Minimal dependencies
|
||||
- Tree-shakeable exports
|
||||
- TypeScript optimization
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE file for details.
|
||||
35
libs/http-client/bun.lock
Normal file
35
libs/http-client/bun.lock
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"socks-proxy-agent": "^8.0.5",
|
||||
"zod": "^3.25.51",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"ip-address": ["ip-address@9.0.5", "", { "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" } }, "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g=="],
|
||||
|
||||
"jsbn": ["jsbn@1.1.0", "", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
|
||||
|
||||
"socks": ["socks@2.8.4", "", { "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" } }, "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ=="],
|
||||
|
||||
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
|
||||
|
||||
"sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
|
||||
|
||||
"zod": ["zod@3.25.51", "", {}, "sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg=="],
|
||||
}
|
||||
}
|
||||
0
libs/http-client/bunfig.toml
Normal file
0
libs/http-client/bunfig.toml
Normal file
46
libs/http-client/package.json
Normal file
46
libs/http-client/package.json
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "@stock-bot/http-client",
|
||||
"version": "1.0.0",
|
||||
"description": "HTTP client library with proxy support, rate limiting, and timeout for Stock Bot platform",
|
||||
"main": "src/index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "bun test",
|
||||
"test:watch": "bun test --watch",
|
||||
"test:coverage": "bun test --coverage",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"type-check": "tsc --noEmit",
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stock-bot/logger": "*",
|
||||
"@stock-bot/types": "*",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"socks-proxy-agent": "^8.0.5",
|
||||
"zod": "^3.25.51"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"typescript": "^5.3.0",
|
||||
"eslint": "^8.56.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||
"@typescript-eslint/parser": "^6.19.0",
|
||||
"bun-types": "^1.2.15"
|
||||
},
|
||||
"keywords": [
|
||||
"http",
|
||||
"client",
|
||||
"fetch",
|
||||
"proxy",
|
||||
"rate-limiting",
|
||||
"timeout",
|
||||
"stock-bot"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
"require": "./dist/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
291
libs/http-client/src/client.ts
Normal file
291
libs/http-client/src/client.ts
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
import type { Logger } from '@stock-bot/logger';
|
||||
import type {
|
||||
HttpClientConfig,
|
||||
RequestConfig,
|
||||
HttpResponse,
|
||||
HttpMethod,
|
||||
ProxyConfig,
|
||||
} from './types.js';
|
||||
import {
|
||||
HttpError,
|
||||
TimeoutError,
|
||||
HttpClientConfigSchema,
|
||||
RequestConfigSchema,
|
||||
} from './types.js';
|
||||
import { RateLimiter } from './rate-limiter.js';
|
||||
import { ProxyManager } from './proxy-manager.js';
|
||||
|
||||
export class HttpClient {
|
||||
private readonly config: HttpClientConfig;
|
||||
private readonly rateLimiter?: RateLimiter;
|
||||
private readonly logger?: Logger;
|
||||
|
||||
constructor(config: Partial<HttpClientConfig> = {}, logger?: Logger) {
|
||||
// Validate and set default configuration
|
||||
this.config = HttpClientConfigSchema.parse(config);
|
||||
this.logger = logger;
|
||||
|
||||
// Initialize rate limiter if configured
|
||||
if (this.config.rateLimit) {
|
||||
this.rateLimiter = new RateLimiter(this.config.rateLimit);
|
||||
}
|
||||
|
||||
// Validate proxy configuration if provided
|
||||
if (this.config.proxy) {
|
||||
ProxyManager.validateConfig(this.config.proxy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an HTTP request
|
||||
*/
|
||||
async request<T = any>(config: RequestConfig): Promise<HttpResponse<T>> {
|
||||
// Validate request configuration
|
||||
const validatedConfig = RequestConfigSchema.parse(config);
|
||||
|
||||
// Merge with default configuration
|
||||
const finalConfig = this.mergeConfig(validatedConfig);
|
||||
|
||||
// Check rate limiting
|
||||
if (this.rateLimiter) {
|
||||
await this.rateLimiter.checkRateLimit();
|
||||
}
|
||||
|
||||
this.logger?.debug('Making HTTP request', {
|
||||
method: finalConfig.method,
|
||||
url: finalConfig.url
|
||||
});
|
||||
|
||||
let lastError: Error | undefined;
|
||||
const maxRetries = finalConfig.retries ?? this.config.retries;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const response = await this.executeRequest<T>(finalConfig);
|
||||
|
||||
// Record successful request for rate limiting
|
||||
if (this.rateLimiter) {
|
||||
this.rateLimiter.recordRequest(true);
|
||||
}
|
||||
|
||||
this.logger?.debug('HTTP request successful', {
|
||||
method: finalConfig.method,
|
||||
url: finalConfig.url,
|
||||
status: response.status,
|
||||
attempt: attempt + 1,
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
|
||||
// Record failed request for rate limiting
|
||||
if (this.rateLimiter) {
|
||||
this.rateLimiter.recordRequest(false);
|
||||
}
|
||||
|
||||
this.logger?.warn('HTTP request failed', {
|
||||
method: finalConfig.method,
|
||||
url: finalConfig.url,
|
||||
attempt: attempt + 1,
|
||||
error: lastError.message,
|
||||
});
|
||||
|
||||
// Don't retry on certain errors
|
||||
if (error instanceof TimeoutError ||
|
||||
(error instanceof HttpError && error.status && error.status < 500)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait before retrying (except on last attempt)
|
||||
if (attempt < maxRetries) {
|
||||
const delay = finalConfig.retryDelay ?? this.config.retryDelay;
|
||||
await this.sleep(delay * Math.pow(2, attempt)); // Exponential backoff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience methods for common HTTP methods
|
||||
*/
|
||||
async get<T = any>(url: string, config: Omit<RequestConfig, 'method' | 'url'> = {}): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, method: 'GET', url });
|
||||
}
|
||||
|
||||
async post<T = any>(url: string, body?: any, config: Omit<RequestConfig, 'method' | 'url' | 'body'> = {}): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, method: 'POST', url, body });
|
||||
}
|
||||
|
||||
async put<T = any>(url: string, body?: any, config: Omit<RequestConfig, 'method' | 'url' | 'body'> = {}): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, method: 'PUT', url, body });
|
||||
}
|
||||
|
||||
async delete<T = any>(url: string, config: Omit<RequestConfig, 'method' | 'url'> = {}): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, method: 'DELETE', url });
|
||||
}
|
||||
|
||||
async patch<T = any>(url: string, body?: any, config: Omit<RequestConfig, 'method' | 'url' | 'body'> = {}): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, method: 'PATCH', url, body });
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the actual HTTP request
|
||||
*/
|
||||
private async executeRequest<T>(config: RequestConfig): Promise<HttpResponse<T>> {
|
||||
const url = this.buildUrl(config.url);
|
||||
const timeout = config.timeout ?? this.config.timeout;
|
||||
|
||||
// Create abort controller for timeout
|
||||
const abortController = new AbortController();
|
||||
const timeoutId = setTimeout(() => {
|
||||
abortController.abort();
|
||||
}, timeout);
|
||||
|
||||
try {
|
||||
// Prepare request options
|
||||
const requestOptions: RequestInit = {
|
||||
method: config.method,
|
||||
headers: config.headers,
|
||||
signal: abortController.signal,
|
||||
};
|
||||
|
||||
// Add body for non-GET requests
|
||||
if (config.body && config.method !== 'GET' && config.method !== 'HEAD') {
|
||||
if (typeof config.body === 'object') {
|
||||
requestOptions.body = JSON.stringify(config.body);
|
||||
requestOptions.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...requestOptions.headers,
|
||||
};
|
||||
} else {
|
||||
requestOptions.body = config.body;
|
||||
}
|
||||
}
|
||||
|
||||
// Add proxy agent if configured
|
||||
if (this.config.proxy) {
|
||||
const agent = ProxyManager.createAgent(this.config.proxy);
|
||||
(requestOptions as any).agent = agent;
|
||||
}
|
||||
|
||||
// Make the request
|
||||
const response = await fetch(url, requestOptions);
|
||||
|
||||
// Clear timeout
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
// Check if response status is valid
|
||||
const validateStatus = config.validateStatus ?? this.config.validateStatus ?? this.defaultValidateStatus;
|
||||
if (!validateStatus(response.status)) {
|
||||
throw new HttpError(
|
||||
`Request failed with status ${response.status}`,
|
||||
response.status,
|
||||
undefined,
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
// Parse response
|
||||
const responseHeaders: Record<string, string> = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
responseHeaders[key] = value;
|
||||
});
|
||||
|
||||
let data: T;
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
|
||||
if (contentType.includes('application/json')) {
|
||||
data = await response.json();
|
||||
} else if (contentType.includes('text/')) {
|
||||
data = await response.text() as any;
|
||||
} else {
|
||||
data = await response.arrayBuffer() as any;
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: responseHeaders,
|
||||
config,
|
||||
};
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (abortController.signal.aborted) {
|
||||
throw new TimeoutError(config, timeout);
|
||||
}
|
||||
|
||||
if (error instanceof HttpError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw new HttpError(`Request failed: ${(error as Error).message}`, undefined, undefined, config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge request config with default config
|
||||
*/
|
||||
private mergeConfig(config: RequestConfig): RequestConfig {
|
||||
return {
|
||||
...config,
|
||||
headers: {
|
||||
...this.config.defaultHeaders,
|
||||
...config.headers,
|
||||
},
|
||||
timeout: config.timeout ?? this.config.timeout,
|
||||
retries: config.retries ?? this.config.retries,
|
||||
retryDelay: config.retryDelay ?? this.config.retryDelay,
|
||||
validateStatus: config.validateStatus ?? this.config.validateStatus,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build full URL from base URL and request URL
|
||||
*/
|
||||
private buildUrl(url: string): string {
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (this.config.baseURL) {
|
||||
return new URL(url, this.config.baseURL).toString();
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default status validator
|
||||
*/
|
||||
private defaultValidateStatus(status: number): boolean {
|
||||
return status >= 200 && status < 300;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep utility for retries
|
||||
*/
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current rate limiting status
|
||||
*/
|
||||
getRateLimitStatus() {
|
||||
if (!this.rateLimiter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
currentCount: this.rateLimiter.getCurrentCount(),
|
||||
maxRequests: this.config.rateLimit!.maxRequests,
|
||||
windowMs: this.config.rateLimit!.windowMs,
|
||||
timeUntilReset: this.rateLimiter.getTimeUntilReset(),
|
||||
};
|
||||
}
|
||||
}
|
||||
8
libs/http-client/src/index.ts
Normal file
8
libs/http-client/src/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Re-export all types and classes
|
||||
export * from './types.js';
|
||||
export * from './client.js';
|
||||
export * from './rate-limiter.js';
|
||||
export * from './proxy-manager.js';
|
||||
|
||||
// Default export
|
||||
export { HttpClient as default } from './client.js';
|
||||
48
libs/http-client/src/proxy-manager.ts
Normal file
48
libs/http-client/src/proxy-manager.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
import { SocksProxyAgent } from 'socks-proxy-agent';
|
||||
import type { ProxyConfig } from './types.js';
|
||||
|
||||
export class ProxyManager {
|
||||
/**
|
||||
* Create appropriate proxy agent based on configuration
|
||||
*/
|
||||
static createAgent(proxy: ProxyConfig): HttpsProxyAgent<string> | SocksProxyAgent {
|
||||
const { type, host, port, username, password } = proxy;
|
||||
|
||||
let proxyUrl: string;
|
||||
|
||||
if (username && password) {
|
||||
proxyUrl = `${type}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}`;
|
||||
} else {
|
||||
proxyUrl = `${type}://${host}:${port}`;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'http':
|
||||
case 'https':
|
||||
return new HttpsProxyAgent(proxyUrl);
|
||||
case 'socks4':
|
||||
case 'socks5':
|
||||
return new SocksProxyAgent(proxyUrl);
|
||||
default:
|
||||
throw new Error(`Unsupported proxy type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate proxy configuration
|
||||
*/
|
||||
static validateConfig(proxy: ProxyConfig): void {
|
||||
if (!proxy.host) {
|
||||
throw new Error('Proxy host is required');
|
||||
}
|
||||
|
||||
if (!proxy.port || proxy.port < 1 || proxy.port > 65535) {
|
||||
throw new Error('Invalid proxy port');
|
||||
}
|
||||
|
||||
if (!['http', 'https', 'socks4', 'socks5'].includes(proxy.type)) {
|
||||
throw new Error(`Invalid proxy type: ${proxy.type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
79
libs/http-client/src/rate-limiter.ts
Normal file
79
libs/http-client/src/rate-limiter.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import type { RateLimitConfig } from './types.js';
|
||||
import { RateLimitError } from './types.js';
|
||||
|
||||
interface RequestRecord {
|
||||
timestamp: number;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export class RateLimiter {
|
||||
private requests: RequestRecord[] = [];
|
||||
private readonly config: RateLimitConfig;
|
||||
|
||||
constructor(config: RateLimitConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if request is allowed based on rate limiting configuration
|
||||
* @returns Promise that resolves when request can proceed
|
||||
*/
|
||||
async checkRateLimit(): Promise<void> {
|
||||
const now = Date.now();
|
||||
const windowStart = now - this.config.windowMs;
|
||||
|
||||
// Remove old requests outside the window
|
||||
this.requests = this.requests.filter(req => req.timestamp > windowStart);
|
||||
|
||||
// Filter requests based on configuration
|
||||
const relevantRequests = this.requests.filter(req => {
|
||||
if (this.config.skipSuccessfulRequests && req.success) {
|
||||
return false;
|
||||
}
|
||||
if (this.config.skipFailedRequests && !req.success) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (relevantRequests.length >= this.config.maxRequests) {
|
||||
const oldestRequest = relevantRequests[0];
|
||||
const retryAfter = oldestRequest.timestamp + this.config.windowMs - now;
|
||||
throw new RateLimitError(this.config.maxRequests, this.config.windowMs, retryAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a request for rate limiting purposes
|
||||
*/
|
||||
recordRequest(success: boolean): void {
|
||||
this.requests.push({
|
||||
timestamp: Date.now(),
|
||||
success,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current request count in the window
|
||||
*/
|
||||
getCurrentCount(): number {
|
||||
const now = Date.now();
|
||||
const windowStart = now - this.config.windowMs;
|
||||
return this.requests.filter(req => req.timestamp > windowStart).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get time until next request is allowed
|
||||
*/
|
||||
getTimeUntilReset(): number {
|
||||
if (this.requests.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const oldestRequest = this.requests[0];
|
||||
const resetTime = oldestRequest.timestamp + this.config.windowMs;
|
||||
|
||||
return Math.max(0, resetTime - now);
|
||||
}
|
||||
}
|
||||
93
libs/http-client/src/types.ts
Normal file
93
libs/http-client/src/types.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
// HTTP Methods
|
||||
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
||||
|
||||
// Proxy configuration
|
||||
export const ProxyConfigSchema = z.object({
|
||||
type: z.enum(['http', 'https', 'socks4', 'socks5']),
|
||||
host: z.string(),
|
||||
port: z.number().min(1).max(65535),
|
||||
username: z.string().optional(),
|
||||
password: z.string().optional(),
|
||||
});
|
||||
|
||||
export type ProxyConfig = z.infer<typeof ProxyConfigSchema>;
|
||||
|
||||
// Rate limiting configuration
|
||||
export const RateLimitConfigSchema = z.object({
|
||||
maxRequests: z.number().min(1),
|
||||
windowMs: z.number().min(1),
|
||||
skipSuccessfulRequests: z.boolean().default(false),
|
||||
skipFailedRequests: z.boolean().default(false),
|
||||
});
|
||||
|
||||
export type RateLimitConfig = z.infer<typeof RateLimitConfigSchema>;
|
||||
|
||||
// HTTP client configuration
|
||||
export const HttpClientConfigSchema = z.object({
|
||||
baseURL: z.string().url().optional(),
|
||||
timeout: z.number().min(1).default(30000), // 30 seconds default
|
||||
retries: z.number().min(0).default(3),
|
||||
retryDelay: z.number().min(0).default(1000), // 1 second default
|
||||
proxy: ProxyConfigSchema.optional(),
|
||||
rateLimit: RateLimitConfigSchema.optional(),
|
||||
defaultHeaders: z.record(z.string()).default({}),
|
||||
validateStatus: z.function().args(z.number()).returns(z.boolean()).optional(),
|
||||
});
|
||||
|
||||
export type HttpClientConfig = z.infer<typeof HttpClientConfigSchema>;
|
||||
|
||||
// Request configuration
|
||||
export const RequestConfigSchema = z.object({
|
||||
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']).default('GET'),
|
||||
url: z.string(),
|
||||
headers: z.record(z.string()).default({}).optional(),
|
||||
body: z.any().optional(),
|
||||
timeout: z.number().min(1).optional(),
|
||||
retries: z.number().min(0).optional(),
|
||||
retryDelay: z.number().min(0).optional(),
|
||||
validateStatus: z.function().args(z.number()).returns(z.boolean()).optional(),
|
||||
});
|
||||
|
||||
export type RequestConfig = z.infer<typeof RequestConfigSchema>;
|
||||
|
||||
// Response type
|
||||
export interface HttpResponse<T = any> {
|
||||
data: T;
|
||||
status: number;
|
||||
statusText: string;
|
||||
headers: Record<string, string>;
|
||||
config: RequestConfig;
|
||||
}
|
||||
|
||||
// Error types
|
||||
export class HttpError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public status?: number,
|
||||
public response?: HttpResponse,
|
||||
public config?: RequestConfig
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'HttpError';
|
||||
}
|
||||
}
|
||||
|
||||
export class TimeoutError extends HttpError {
|
||||
constructor(config: RequestConfig, timeout: number) {
|
||||
super(`Request timeout after ${timeout}ms`, undefined, undefined, config);
|
||||
this.name = 'TimeoutError';
|
||||
}
|
||||
}
|
||||
|
||||
export class RateLimitError extends HttpError {
|
||||
constructor(
|
||||
public maxRequests: number,
|
||||
public windowMs: number,
|
||||
public retryAfter?: number
|
||||
) {
|
||||
super(`Rate limit exceeded: ${maxRequests} requests per ${windowMs}ms`);
|
||||
this.name = 'RateLimitError';
|
||||
}
|
||||
}
|
||||
190
libs/http-client/test/http-client.test.ts
Normal file
190
libs/http-client/test/http-client.test.ts
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
import { describe, test, expect, beforeEach } from 'bun:test';
|
||||
import { HttpClient, RateLimiter, ProxyManager } from '../src/index.js';
|
||||
import type { RateLimitConfig, ProxyConfig } from '../src/types.js';
|
||||
import { RateLimitError } from '../src/types.js';
|
||||
|
||||
describe('HttpClient', () => {
|
||||
let client: HttpClient;
|
||||
|
||||
beforeEach(() => {
|
||||
client = new HttpClient();
|
||||
});
|
||||
|
||||
test('should create client with default config', () => {
|
||||
expect(client).toBeInstanceOf(HttpClient);
|
||||
});
|
||||
|
||||
test('should make GET request', async () => {
|
||||
// Using a mock endpoint that returns JSON
|
||||
const response = await client.get('https://jsonplaceholder.typicode.com/posts/1');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data).toHaveProperty('id');
|
||||
expect(response.data).toHaveProperty('title');
|
||||
});
|
||||
|
||||
test('should handle timeout', async () => {
|
||||
const clientWithTimeout = new HttpClient({ timeout: 1 }); // 1ms timeout
|
||||
|
||||
await expect(
|
||||
clientWithTimeout.get('https://jsonplaceholder.typicode.com/posts/1')
|
||||
).rejects.toThrow('timeout');
|
||||
});
|
||||
|
||||
test('should handle 404 error', async () => {
|
||||
await expect(
|
||||
client.get('https://jsonplaceholder.typicode.com/posts/999999')
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
test('should make POST request with body', async () => {
|
||||
const response = await client.post('https://jsonplaceholder.typicode.com/posts', {
|
||||
title: 'Test Post',
|
||||
body: 'Test body',
|
||||
userId: 1,
|
||||
});
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(response.data).toHaveProperty('id');
|
||||
});
|
||||
|
||||
test('should use base URL', async () => {
|
||||
const clientWithBase = new HttpClient({
|
||||
baseURL: 'https://jsonplaceholder.typicode.com'
|
||||
});
|
||||
|
||||
const response = await clientWithBase.get('/posts/1');
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
test('should merge headers', async () => {
|
||||
const clientWithHeaders = new HttpClient({
|
||||
defaultHeaders: {
|
||||
'X-API-Key': 'test-key',
|
||||
}
|
||||
});
|
||||
|
||||
const response = await clientWithHeaders.get('https://jsonplaceholder.typicode.com/posts/1', {
|
||||
headers: {
|
||||
'X-Custom': 'custom-value',
|
||||
}
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RateLimiter', () => {
|
||||
let rateLimiter: RateLimiter;
|
||||
const config: RateLimitConfig = {
|
||||
maxRequests: 2,
|
||||
windowMs: 1000, // 1 second
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
rateLimiter = new RateLimiter(config);
|
||||
});
|
||||
|
||||
test('should allow requests within limit', async () => {
|
||||
await expect(rateLimiter.checkRateLimit()).resolves.toBeUndefined();
|
||||
rateLimiter.recordRequest(true);
|
||||
|
||||
await expect(rateLimiter.checkRateLimit()).resolves.toBeUndefined();
|
||||
rateLimiter.recordRequest(true);
|
||||
});
|
||||
|
||||
test('should reject requests exceeding limit', async () => {
|
||||
// Fill up the rate limit
|
||||
await rateLimiter.checkRateLimit();
|
||||
rateLimiter.recordRequest(true);
|
||||
await rateLimiter.checkRateLimit();
|
||||
rateLimiter.recordRequest(true);
|
||||
|
||||
// This should throw
|
||||
await expect(rateLimiter.checkRateLimit()).rejects.toThrow(RateLimitError);
|
||||
});
|
||||
|
||||
test('should reset after window', async () => {
|
||||
const shortConfig: RateLimitConfig = {
|
||||
maxRequests: 1,
|
||||
windowMs: 100, // 100ms
|
||||
};
|
||||
const shortRateLimiter = new RateLimiter(shortConfig);
|
||||
|
||||
await shortRateLimiter.checkRateLimit();
|
||||
shortRateLimiter.recordRequest(true);
|
||||
|
||||
// Should be at limit
|
||||
await expect(shortRateLimiter.checkRateLimit()).rejects.toThrow(RateLimitError);
|
||||
|
||||
// Wait for window to reset
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
|
||||
// Should be allowed again
|
||||
await expect(shortRateLimiter.checkRateLimit()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test('should get current count', () => {
|
||||
expect(rateLimiter.getCurrentCount()).toBe(0);
|
||||
|
||||
rateLimiter.recordRequest(true);
|
||||
expect(rateLimiter.getCurrentCount()).toBe(1);
|
||||
|
||||
rateLimiter.recordRequest(false);
|
||||
expect(rateLimiter.getCurrentCount()).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProxyManager', () => {
|
||||
test('should validate proxy config', () => {
|
||||
const validConfig: ProxyConfig = {
|
||||
type: 'http',
|
||||
host: 'proxy.example.com',
|
||||
port: 8080,
|
||||
};
|
||||
|
||||
expect(() => ProxyManager.validateConfig(validConfig)).not.toThrow();
|
||||
});
|
||||
|
||||
test('should reject invalid proxy config', () => {
|
||||
const invalidConfig = {
|
||||
type: 'http',
|
||||
host: '',
|
||||
port: 8080,
|
||||
} as ProxyConfig;
|
||||
|
||||
expect(() => ProxyManager.validateConfig(invalidConfig)).toThrow('Proxy host is required');
|
||||
});
|
||||
|
||||
test('should reject invalid port', () => {
|
||||
const invalidConfig = {
|
||||
type: 'http',
|
||||
host: 'proxy.example.com',
|
||||
port: 70000,
|
||||
} as ProxyConfig;
|
||||
|
||||
expect(() => ProxyManager.validateConfig(invalidConfig)).toThrow('Invalid proxy port');
|
||||
});
|
||||
|
||||
test('should create HTTP proxy agent', () => {
|
||||
const config: ProxyConfig = {
|
||||
type: 'http',
|
||||
host: 'proxy.example.com',
|
||||
port: 8080,
|
||||
};
|
||||
|
||||
const agent = ProxyManager.createAgent(config);
|
||||
expect(agent).toBeDefined();
|
||||
});
|
||||
|
||||
test('should create SOCKS proxy agent', () => {
|
||||
const config: ProxyConfig = {
|
||||
type: 'socks5',
|
||||
host: 'proxy.example.com',
|
||||
port: 1080,
|
||||
};
|
||||
|
||||
const agent = ProxyManager.createAgent(config);
|
||||
expect(agent).toBeDefined();
|
||||
});
|
||||
});
|
||||
21
libs/http-client/tsconfig.json
Normal file
21
libs/http-client/tsconfig.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../logger" },
|
||||
{ "path": "../types" }
|
||||
]
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/types": "*",
|
||||
"pino": "^9.7.0",
|
||||
"pino-loki": "^2.6.0",
|
||||
"pino-pretty": "^13.0.0"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { Document } from 'mongodb';
|
||||
import type { MongoDBClient } from './client';
|
||||
import type { CollectionNames } from './types';
|
||||
|
||||
|
|
@ -103,11 +104,10 @@ export class MongoDBAggregationBuilder {
|
|||
this.pipeline.push(stage);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the aggregation pipeline
|
||||
*/
|
||||
async execute<T = any>(): Promise<T[]> {
|
||||
async execute<T extends Document = Document>(): Promise<T[]> {
|
||||
if (!this.collection) {
|
||||
throw new Error('Collection not specified. Use .from() to set the collection.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { MongoClient, Db, Collection, MongoClientOptions } from 'mongodb';
|
||||
import { MongoClient, Db, Collection, MongoClientOptions, Document, WithId, OptionalUnlessRequiredId } from 'mongodb';
|
||||
import { mongodbConfig } from '@stock-bot/config';
|
||||
import { Logger } from '@stock-bot/logger';
|
||||
import type {
|
||||
|
|
@ -144,12 +144,10 @@ export class MongoDBClient {
|
|||
...document,
|
||||
created_at: document.created_at || now,
|
||||
updated_at: now
|
||||
} as T;
|
||||
|
||||
// Validate document if schema exists
|
||||
if (schemaMap[collectionName]) {
|
||||
} as T; // Validate document if schema exists
|
||||
if (collectionName in schemaMap) {
|
||||
try {
|
||||
schemaMap[collectionName].parse(docWithTimestamps);
|
||||
(schemaMap as any)[collectionName].parse(docWithTimestamps);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
this.logger.error(`Document validation failed for ${collectionName}:`, error.errors);
|
||||
|
|
@ -157,9 +155,7 @@ export class MongoDBClient {
|
|||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await collection.insertOne(docWithTimestamps);
|
||||
} const result = await collection.insertOne(docWithTimestamps as OptionalUnlessRequiredId<T>);
|
||||
return { ...docWithTimestamps, _id: result.insertedId } as T;
|
||||
}
|
||||
|
||||
|
|
@ -182,7 +178,6 @@ export class MongoDBClient {
|
|||
const result = await collection.updateOne(filter, { $set: updateWithTimestamp });
|
||||
return result.modifiedCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find documents with optional validation
|
||||
*/
|
||||
|
|
@ -192,7 +187,7 @@ export class MongoDBClient {
|
|||
options: any = {}
|
||||
): Promise<T[]> {
|
||||
const collection = this.getCollection<T>(collectionName);
|
||||
return await collection.find(filter, options).toArray();
|
||||
return await collection.find(filter, options).toArray() as T[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -203,7 +198,7 @@ export class MongoDBClient {
|
|||
filter: any
|
||||
): Promise<T | null> {
|
||||
const collection = this.getCollection<T>(collectionName);
|
||||
return await collection.findOne(filter);
|
||||
return await collection.findOne(filter) as T | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -259,11 +254,12 @@ export class MongoDBClient {
|
|||
{ key: { filing_type: 1 } },
|
||||
{ key: { cik: 1 } },
|
||||
{ key: { created_at: -1 } }
|
||||
]);
|
||||
|
||||
// Raw documents indexes
|
||||
]); // Raw documents indexes
|
||||
await this.db.collection('raw_documents').createIndex(
|
||||
{ content_hash: 1 },
|
||||
{ unique: true }
|
||||
);
|
||||
await this.db.collection('raw_documents').createIndexes([
|
||||
{ key: { content_hash: 1 }, options: { unique: true } },
|
||||
{ key: { processing_status: 1 } },
|
||||
{ key: { document_type: 1 } },
|
||||
{ key: { created_at: -1 } }
|
||||
|
|
@ -366,8 +362,11 @@ export class MongoDBClient {
|
|||
serverSelectionTimeoutMS: this.config.timeouts?.serverSelectionTimeout,
|
||||
retryWrites: this.config.options?.retryWrites,
|
||||
journal: this.config.options?.journal,
|
||||
readPreference: this.config.options?.readPreference,
|
||||
writeConcern: { w: this.config.options?.writeConcern },
|
||||
readPreference: this.config.options?.readPreference, writeConcern: this.config.options?.writeConcern ? {
|
||||
w: this.config.options.writeConcern === 'majority'
|
||||
? 'majority' as const
|
||||
: parseInt(this.config.options.writeConcern, 10) || 1
|
||||
} : undefined,
|
||||
tls: this.config.tls?.enabled,
|
||||
tlsInsecure: this.config.tls?.insecure,
|
||||
tlsCAFile: this.config.tls?.caFile
|
||||
|
|
|
|||
|
|
@ -176,10 +176,8 @@ export class MongoDBHealthMonitor {
|
|||
// Average latency (from durability stats if available)
|
||||
if (dur.timeMS) {
|
||||
this.metrics.averageLatency = dur.timeMS.dt || 0;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.logger.debug('Error parsing server status for metrics:', error);
|
||||
} } catch (error) {
|
||||
this.logger.debug('Error parsing server status for metrics:', error as any);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Logger } from '@stock-bot/logger';
|
||||
import type { MongoDBClient } from './client';
|
||||
import type { CollectionNames, DocumentBase } from './types';
|
||||
import type { WithId, OptionalUnlessRequiredId } from 'mongodb';
|
||||
|
||||
/**
|
||||
* MongoDB Transaction Manager
|
||||
|
|
@ -41,10 +42,9 @@ export class MongoDBTransactionManager {
|
|||
const result = await session.withTransaction(
|
||||
async () => {
|
||||
return await operations(session);
|
||||
},
|
||||
{
|
||||
readPreference: options?.readPreference,
|
||||
readConcern: { level: options?.readConcern || 'majority' },
|
||||
}, {
|
||||
readPreference: options?.readPreference as any,
|
||||
readConcern: { level: options?.readConcern || 'majority' } as any,
|
||||
writeConcern: options?.writeConcern || { w: 'majority' },
|
||||
maxCommitTimeMS: options?.maxCommitTimeMS || 10000
|
||||
}
|
||||
|
|
@ -155,21 +155,17 @@ export class MongoDBTransactionManager {
|
|||
|
||||
if (documents.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Transform documents if needed
|
||||
} // Transform documents if needed
|
||||
const documentsToInsert = transform
|
||||
? documents.map(transform)
|
||||
? documents.map((doc: WithId<T>) => transform(doc as T))
|
||||
: documents;
|
||||
|
||||
// Add updated timestamp
|
||||
const now = new Date();
|
||||
documentsToInsert.forEach(doc => {
|
||||
doc.updated_at = now;
|
||||
});
|
||||
|
||||
// Insert into target collection
|
||||
await targetCollection.insertMany(documentsToInsert, { session });
|
||||
}); // Insert into target collection
|
||||
await targetCollection.insertMany(documentsToInsert as OptionalUnlessRequiredId<T>[], { session });
|
||||
|
||||
// Remove from source collection
|
||||
const deleteResult = await sourceCollection.deleteMany(filter, { session });
|
||||
|
|
|
|||
|
|
@ -15,5 +15,10 @@
|
|||
"node_modules",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../config" },
|
||||
{ "path": "../logger" },
|
||||
{ "path": "../types" }
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Pool, PoolClient, QueryResult as PgQueryResult } from 'pg';
|
||||
import { Pool, PoolClient, QueryResult as PgQueryResult, QueryResultRow } from 'pg';
|
||||
import { postgresConfig } from '@stock-bot/config';
|
||||
import { Logger } from '@stock-bot/logger';
|
||||
import type {
|
||||
|
|
@ -115,7 +115,7 @@ export class PostgreSQLClient {
|
|||
/**
|
||||
* Execute a query
|
||||
*/
|
||||
async query<T = any>(text: string, params?: any[]): Promise<QueryResult<T>> {
|
||||
async query<T extends QueryResultRow = any>(text: string, params?: any[]): Promise<QueryResult<T>> {
|
||||
if (!this.pool) {
|
||||
throw new Error('PostgreSQL client not connected');
|
||||
}
|
||||
|
|
@ -191,7 +191,7 @@ export class PostgreSQLClient {
|
|||
/**
|
||||
* Execute a stored procedure or function
|
||||
*/
|
||||
async callFunction<T = any>(functionName: string, params?: any[]): Promise<QueryResult<T>> {
|
||||
async callFunction<T extends QueryResultRow = any>(functionName: string, params?: any[]): Promise<QueryResult<T>> {
|
||||
const placeholders = params ? params.map((_, i) => `$${i + 1}`).join(', ') : '';
|
||||
const query = `SELECT * FROM ${functionName}(${placeholders})`;
|
||||
return await this.query<T>(query, params);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export { PostgreSQLClient } from './client';
|
|||
export { PostgreSQLHealthMonitor } from './health';
|
||||
export { PostgreSQLTransactionManager } from './transactions';
|
||||
export { PostgreSQLQueryBuilder } from './query-builder';
|
||||
export { PostgreSQLMigrationManager } from './migrations';
|
||||
// export { PostgreSQLMigrationManager } from './migrations'; // TODO: Implement migrations
|
||||
|
||||
// Types
|
||||
export type {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { QueryResultRow } from 'pg';
|
||||
import type { PostgreSQLClient } from './client';
|
||||
import type { WhereCondition, JoinCondition, OrderByCondition, QueryResult } from './types';
|
||||
|
||||
|
|
@ -137,7 +138,7 @@ export class PostgreSQLQueryBuilder {
|
|||
/**
|
||||
* Build and execute the query
|
||||
*/
|
||||
async execute<T = any>(): Promise<QueryResult<T>> {
|
||||
async execute<T extends QueryResultRow = any>(): Promise<QueryResult<T>> {
|
||||
const { sql, params } = this.build();
|
||||
return await this.client.query<T>(sql, params);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Pool, PoolClient, QueryResult as PgQueryResult } from 'pg';
|
||||
import type { Pool, PoolClient, QueryResult as PgQueryResult, QueryResultRow } from 'pg';
|
||||
|
||||
/**
|
||||
* PostgreSQL Client Configuration
|
||||
|
|
@ -64,7 +64,7 @@ export interface PostgreSQLMetrics {
|
|||
/**
|
||||
* Query Result Types
|
||||
*/
|
||||
export interface QueryResult<T = any> extends PgQueryResult<T> {
|
||||
export interface QueryResult<T extends QueryResultRow = any> extends PgQueryResult<T> {
|
||||
executionTime?: number;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,5 +15,10 @@
|
|||
"node_modules",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../config" },
|
||||
{ "path": "../logger" },
|
||||
{ "path": "../types" }
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"@questdb/nodejs-client": "^3.0.0",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/logger": "*",
|
||||
"@stock-bot/types": "*",
|
||||
"immutable": "^5.1.2",
|
||||
"pg": "^8.11.3",
|
||||
"pg-mem": "^3.0.5",
|
||||
"zod": "^3.22.4"
|
||||
|
|
|
|||
|
|
@ -15,13 +15,10 @@
|
|||
"node_modules",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../api-client" },
|
||||
{ "path": "../event-bus" },
|
||||
{ "path": "../http-client" },
|
||||
], "references": [
|
||||
{ "path": "../utils" },
|
||||
{ "path": "../config" },
|
||||
{ "path": "../logger" },
|
||||
{ "path": "../types" }
|
||||
]
|
||||
}
|
||||
|
|
|
|||
22
libs/types/package.json
Normal file
22
libs/types/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@stock-bot/types",
|
||||
"version": "1.0.0",
|
||||
"description": "Shared type definitions for Stock Bot platform",
|
||||
"main": "src/index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"keywords": [
|
||||
"types",
|
||||
"typescript",
|
||||
"stock-bot"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
"require": "./dist/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
1
libs/types/src/index.ts
Normal file
1
libs/types/src/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Export all types from the events module
|
||||
19
libs/types/tsconfig.json
Normal file
19
libs/types/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -23,29 +23,12 @@ const nextTradingDay = dateUtils.getNextTradingDay(new Date());
|
|||
Mathematical functions for financial calculations:
|
||||
|
||||
```typescript
|
||||
import { financialUtils } from '@stock-bot/utils';
|
||||
import { calculateCAGR, calculateSharpeRatio } from '@stock-bot/utils';
|
||||
|
||||
// Calculate compound annual growth rate
|
||||
const returns = [0.05, 0.03, -0.01, 0.04, 0.02];
|
||||
const cagr = calculateCAGR(startValue, endValue, years);
|
||||
|
||||
// Calculate Sharpe ratio
|
||||
const returns = [0.05, 0.03, -0.01, 0.04, 0.02];
|
||||
const sharpeRatio = financialUtils.calculateSharpeRatio(returns, 0.02);
|
||||
|
||||
// Calculate maximum drawdown
|
||||
const equityCurve = [10000, 10500, 10200, 10800, 10300];
|
||||
const maxDrawdown = financialUtils.calculateMaxDrawdown(equityCurve);
|
||||
```
|
||||
|
||||
### Logger
|
||||
|
||||
Standardized logging service:
|
||||
|
||||
```typescript
|
||||
import { createLogger, LogLevel } from '@stock-bot/utils';
|
||||
|
||||
// Create a logger for your service
|
||||
const logger = createLogger('strategy-orchestrator', LogLevel.INFO);
|
||||
|
||||
// Log at different levels
|
||||
logger.info('Strategy initialized');
|
||||
logger.warn('Position size exceeds recommended limit');
|
||||
logger.error('Failed to execute order', { orderId: '123', reason: 'Insufficient funds' });
|
||||
const sharpeRatio = calculateSharpeRatio(returns, 0.02);
|
||||
```
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
"test": "jest"
|
||||
}, "dependencies": {
|
||||
"@stock-bot/types": "*",
|
||||
"@stock-bot/config": "*",
|
||||
"date-fns": "^2.30.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
export * from './dateUtils';
|
||||
export * from './logger';
|
||||
export * from './calculations/index';
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
/**
|
||||
* Logger utility with consistent formatting and log levels
|
||||
* Supports console and Loki logging
|
||||
*/
|
||||
import { loggingConfig, lokiConfig } from '@stock-bot/config';
|
||||
|
||||
// Singleton Loki client
|
||||
let lokiClient: LokiClient | null = null;
|
||||
|
||||
function getLokiClient(): LokiClient {
|
||||
if (!lokiClient) {
|
||||
lokiClient = new LokiClient();
|
||||
}
|
||||
return lokiClient;
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
constructor(private serviceName: string, private level: LogLevel = LogLevel.INFO) {}
|
||||
|
||||
debug(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.DEBUG, message, ...args);
|
||||
}
|
||||
|
||||
info(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.INFO, message, ...args);
|
||||
}
|
||||
|
||||
warn(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.WARN, message, ...args);
|
||||
}
|
||||
|
||||
error(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.ERROR, message, ...args);
|
||||
}
|
||||
|
||||
private log(level: LogLevel, message: string, ...args: any[]): void {
|
||||
if (level < this.level) return;
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
const levelStr = LogLevel[level].padEnd(5);
|
||||
|
||||
const formattedArgs = args.length ? this.formatArgs(args) : '';
|
||||
const fullMessage = `${message}${formattedArgs}`;
|
||||
const logMessage = `[${timestamp}] [${levelStr}] [${this.serviceName}] ${fullMessage}`;
|
||||
|
||||
// Console logging
|
||||
if (loggingConfig.LOG_CONSOLE) {
|
||||
switch (level) {
|
||||
case LogLevel.ERROR:
|
||||
console.error(logMessage);
|
||||
break;
|
||||
case LogLevel.WARN:
|
||||
console.warn(logMessage);
|
||||
break;
|
||||
case LogLevel.INFO:
|
||||
console.info(logMessage);
|
||||
break;
|
||||
case LogLevel.DEBUG:
|
||||
default:
|
||||
console.debug(logMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Loki logging
|
||||
try {
|
||||
const loki = getLokiClient();
|
||||
loki.log(LogLevel[level].toLowerCase(), fullMessage, this.serviceName);
|
||||
} catch (error) {
|
||||
console.error('Failed to send log to Loki:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private formatArgs(args: any[]): string {
|
||||
try {
|
||||
return args.map(arg => {
|
||||
if (arg instanceof Error) {
|
||||
return ` ${arg.message}\n${arg.stack}`;
|
||||
} else if (typeof arg === 'object') {
|
||||
return ` ${JSON.stringify(arg)}`;
|
||||
} else {
|
||||
return ` ${arg}`;
|
||||
}
|
||||
}).join('');
|
||||
} catch (error) {
|
||||
return ` [Error formatting log arguments: ${error}]`;
|
||||
}
|
||||
}
|
||||
|
||||
setLevel(level: LogLevel): void {
|
||||
this.level = level;
|
||||
}
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = 0,
|
||||
INFO = 1,
|
||||
WARN = 2,
|
||||
ERROR = 3
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new logger instance
|
||||
*/
|
||||
export function createLogger(serviceName: string, level: LogLevel = LogLevel.INFO): Logger {
|
||||
return new Logger(serviceName, level);
|
||||
}
|
||||
|
||||
export class LokiClient {
|
||||
private batchQueue: any[] = [];
|
||||
private flushInterval: NodeJS.Timeout;
|
||||
private lokiUrl: string;
|
||||
private authHeader?: string;
|
||||
|
||||
constructor() {
|
||||
const { LOKI_HOST, LOKI_PORT, LOKI_USERNAME, LOKI_PASSWORD } = lokiConfig;
|
||||
|
||||
this.lokiUrl = `http://${LOKI_HOST}:${LOKI_PORT}/loki/api/v1/push`;
|
||||
|
||||
if (LOKI_USERNAME && LOKI_PASSWORD) {
|
||||
const authString = Buffer.from(`${LOKI_USERNAME}:${LOKI_PASSWORD}`).toString('base64');
|
||||
this.authHeader = `Basic ${authString}`;
|
||||
}
|
||||
|
||||
this.flushInterval = setInterval(
|
||||
() => this.flush(),
|
||||
lokiConfig.LOKI_FLUSH_INTERVAL_MS || 1000 // Default to 1 second if not set
|
||||
);
|
||||
}
|
||||
|
||||
async log(level: string, message: string, serviceName: string, labels: Record<string, string> = {}) {
|
||||
const timestamp = Date.now() * 1000000; // Loki expects nanoseconds
|
||||
|
||||
this.batchQueue.push({
|
||||
streams: [{
|
||||
stream: {
|
||||
level,
|
||||
service: serviceName,
|
||||
...lokiConfig.LOKI_DEFAULT_LABELS ? JSON.parse(lokiConfig.LOKI_DEFAULT_LABELS) : {},
|
||||
...labels,
|
||||
},
|
||||
values: [[`${timestamp}`, message]],
|
||||
}],
|
||||
});
|
||||
|
||||
if (this.batchQueue.length >= lokiConfig.LOKI_BATCH_SIZE) {
|
||||
await this.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private async flush() {
|
||||
if (this.batchQueue.length === 0) return;
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
if (this.authHeader) {
|
||||
headers['Authorization'] = this.authHeader;
|
||||
}
|
||||
|
||||
const response = await fetch(this.lokiUrl, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
streams: this.batchQueue.flatMap(batch => batch.streams),
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to send logs to Loki: ${response.status} ${response.statusText}`);
|
||||
const text = await response.text();
|
||||
if (text) {
|
||||
console.error(text);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending logs to Loki:', error);
|
||||
} finally {
|
||||
this.batchQueue = [];
|
||||
}
|
||||
}
|
||||
|
||||
async destroy() {
|
||||
clearInterval(this.flushInterval);
|
||||
return this.flush();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,5 @@
|
|||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
|
||||
"references": [
|
||||
{ "path": "../api-client" },
|
||||
{ "path": "../config" },
|
||||
{ "path": "../event-bus" },
|
||||
{ "path": "../http-client" },
|
||||
]
|
||||
], "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
||||
}
|
||||
|
|
|
|||
7927
package-lock.json
generated
7927
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -47,7 +47,8 @@
|
|||
"mongodb-memory-server": "^9.1.6",
|
||||
"pg-mem": "^2.8.1",
|
||||
"supertest": "^6.3.4",
|
||||
"@types/supertest": "^6.0.2"
|
||||
"@types/supertest": "^6.0.2",
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"packageManager": "bun@1.1.12",
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -39,14 +39,15 @@
|
|||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "./libs/api-client" },
|
||||
], "references": [
|
||||
{ "path": "./libs/config" },
|
||||
{ "path": "./libs/event-bus" },
|
||||
{ "path": "./libs/http-client" },
|
||||
{ "path": "./libs/logger" },
|
||||
{ "path": "./libs/utils" },
|
||||
{ "path": "./libs/mongodb-client" },
|
||||
{ "path": "./libs/postgres-client" },
|
||||
{ "path": "./libs/questdb-client" },
|
||||
{ "path": "./libs/types" },
|
||||
{ "path": "./libs/utils" }
|
||||
]
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true
|
||||
"declaration": true,
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue