Initial Commit
This commit is contained in:
26
packages/eve/.github/workflows/release.yml
vendored
Normal file
26
packages/eve/.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Release
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- run: npx changelogithub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
38
packages/eve/.github/workflows/unit-test.yml
vendored
Normal file
38
packages/eve/.github/workflows/unit-test.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Unit Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Set node LTS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: pnpm
|
||||
|
||||
- name: Install
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Test
|
||||
run: pnpm run test
|
||||
6
packages/eve/.gitignore
vendored
Normal file
6
packages/eve/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
*.log
|
||||
.DS_Store
|
||||
data
|
||||
coverage
|
||||
12
packages/eve/.prettierrc.yaml
Normal file
12
packages/eve/.prettierrc.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
trailingComma: all
|
||||
tabWidth: 2
|
||||
useTabs: false
|
||||
semi: true
|
||||
singleQuote: true
|
||||
printWidth: 140
|
||||
experimentalTernaries: true
|
||||
quoteProps: consistent
|
||||
plugins:
|
||||
- prettier-plugin-multiline-arrays
|
||||
multilineArrayWrapThreshold: 1
|
||||
multilineArrayElementsPerLine: 1
|
||||
6
packages/eve/.vscode/extensions.json
vendored
Normal file
6
packages/eve/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"oven.bun-vscode"
|
||||
]
|
||||
}
|
||||
3
packages/eve/.vscode/settings.json
vendored
Normal file
3
packages/eve/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
23
packages/eve/README.md
Normal file
23
packages/eve/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# tsdown-starter
|
||||
|
||||
A starter for creating a TypeScript package.
|
||||
|
||||
## Development
|
||||
|
||||
- Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
- Run the unit tests:
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
- Build the library:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
660
packages/eve/bun.lock
Normal file
660
packages/eve/bun.lock
Normal file
@@ -0,0 +1,660 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "tsdown-starter",
|
||||
"dependencies": {
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"@star-kitten/util": "link:@star-kitten/util",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"fp-filters": "^0.5.4",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwk-to-pem": "^2.0.7",
|
||||
"jwt-decode": "^4.0.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.21",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/jwk-to-pem": "^2.0.3",
|
||||
"@types/node": "^22.15.17",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"bumpp": "^10.1.0",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"prettier-plugin-multiline-arrays": "^4.0.3",
|
||||
"tsdown": "^0.14.2",
|
||||
"typescript": "^5.9.2",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.1.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||
|
||||
"@augment-vir/assert": ["@augment-vir/assert@31.34.1", "", { "dependencies": { "@augment-vir/core": "^31.34.1", "@date-vir/duration": "^7.4.2", "deep-eql": "^5.0.2", "expect-type": "^1.2.2", "type-fest": "^4.41.0" } }, "sha512-VqIN8muhVrU5A5SqZNrq/siO3+DoY1g+EzypJvw44xxHK398dVPLk5caJIcPRPJSPHOaE7osADh5nFf69aSsoQ=="],
|
||||
|
||||
"@augment-vir/common": ["@augment-vir/common@31.34.1", "", { "dependencies": { "@augment-vir/assert": "^31.34.1", "@augment-vir/core": "^31.34.1", "@date-vir/duration": "^7.4.2", "ansi-styles": "^6.2.1", "deepcopy-esm": "^2.1.1", "json5": "^2.2.3", "type-fest": "^4.41.0", "typed-event-target": "^4.1.0" } }, "sha512-pXDdldftWKltkpaXv/DeLhoYmyIVEumg7nhovDH0XssNm24VfzbbQqi2aYxOHL/MBni5sAfLM2saMEOxMgWOYQ=="],
|
||||
|
||||
"@augment-vir/core": ["@augment-vir/core@31.34.1", "", { "dependencies": { "@date-vir/duration": "^7.4.2", "browser-or-node": "^3.0.0", "json5": "^2.2.3", "type-fest": "^4.41.0" } }, "sha512-YhMCvtNw887dKJpk5DMZqHuejZgcsVJyeEVTDVJ3t6EF+ejtm2wctMRmtVtciOtnRamWEJ6QPwfiXiD/Jbe7LA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="],
|
||||
|
||||
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
|
||||
|
||||
"@date-vir/duration": ["@date-vir/duration@7.4.2", "", { "dependencies": { "@types/luxon": "^3.7.1", "luxon": "^3.7.1", "type-fest": "^4.41.0" } }, "sha512-6SNXMv8SncOo6Yla4twZseWxmi37oBOvnXS59TXRm6LJ28lgqtaOd5YCzW5qOsT/jstgfO0ax7qGUsmm812unw=="],
|
||||
|
||||
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
|
||||
|
||||
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.3", "", { "dependencies": { "@emnapi/core": "^1.4.5", "@emnapi/runtime": "^1.4.5", "@tybys/wasm-util": "^0.10.0" } }, "sha512-rZxtMsLwjdXkMUGC3WwsPwLNVqVqnTJT6MNIB6e+5fhMcSCPP0AOsNWuMQ5mdCq6HNjs/ZeWAEchpqeprqBD2Q=="],
|
||||
|
||||
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
|
||||
|
||||
"@oxc-project/runtime": ["@oxc-project/runtime@0.82.3", "", {}, "sha512-LNh5GlJvYHAnMurO+EyA8jJwN1rki7l3PSHuosDh2I7h00T6/u9rCkUjg/SvPmT1CZzvhuW0y+gf7jcqUy/Usg=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.82.3", "", {}, "sha512-6nCUxBnGX0c6qfZW5MaF6/fmu5dHJDMiMPaioKHKs5mi5+8/FHQ7WGjgQIz1zxpmceMYfdIXkOaLYE+ejbuOtA=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
"@quansync/fs": ["@quansync/fs@0.1.5", "", { "dependencies": { "quansync": "^0.2.11" } }, "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.35", "", { "os": "android", "cpu": "arm64" }, "sha512-zVTg0544Ib1ldJSWwjy8URWYHlLFJ98rLnj+2FIj5fRs4KqGKP4VgH/pVUbXNGxeLFjItie6NSK1Un7nJixneQ=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.35", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WPy0qx22CABTKDldEExfpYHWHulRoPo+m/YpyxP+6ODUPTQexWl8Wp12fn1CVP0xi0rOBj7ugs6+kKMAJW56wQ=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.35", "", { "os": "darwin", "cpu": "x64" }, "sha512-3k1TabJafF/GgNubXMkfp93d5p30SfIMOmQ5gm1tFwO+baMxxVPwDs3FDvSl+feCWwXxBA+bzemgkaDlInmp1Q=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.35", "", { "os": "freebsd", "cpu": "x64" }, "sha512-GAiapN5YyIocnBVNEiOxMfWO9NqIeEKKWohj1sPLGc61P+9N1meXOOCiAPbLU+adXq0grtbYySid+Or7f2q+Mg=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.35", "", { "os": "linux", "cpu": "arm" }, "sha512-okPKKIE73qkUMvq7dxDyzD0VIysdV4AirHqjf8tGTjuNoddUAl3WAtMYbuZCEKJwUyI67UINKO1peFVlYEb+8w=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.35", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nky8Q2cxyKVkEETntrvcmlzNir5khQbDfX3PflHPbZY7XVZalllRqw7+MW5vn+jTsk5BfKVeLsvrF4344IU55g=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.35", "", { "os": "linux", "cpu": "arm64" }, "sha512-8aHpWVSfZl3Dy2VNFG9ywmlCPAJx45g0z+qdOeqmYceY7PBAT4QGzii9ig1hPb1pY8K45TXH44UzQwr2fx352Q=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.35", "", { "os": "linux", "cpu": "x64" }, "sha512-1r1Ac/vTcm1q4kRiX/NB6qtorF95PhjdCxKH3Z5pb+bWMDZnmcz18fzFlT/3C6Qpj/ZqUF+EUrG4QEDXtVXGgg=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.35", "", { "os": "linux", "cpu": "x64" }, "sha512-AFl1LnuhUBDfX2j+cE6DlVGROv4qG7GCPDhR1kJqi2+OuXGDkeEjqRvRQOFErhKz1ckkP/YakvN7JheLJ2PKHQ=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.35", "", { "os": "none", "cpu": "arm64" }, "sha512-Tuwb8vPs+TVJlHhyLik+nwln/burvIgaPDgg6wjNZ23F1ttjZi0w0rQSZfAgsX4jaUbylwCETXQmTp3w6vcJMw=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.35", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.3" }, "cpu": "none" }, "sha512-rG0OozgqNUYcpu50MpICMlJflexRVtQfjlN9QYf6hoel46VvY0FbKGwBKoeUp2K5D4i8lV04DpEMfTZlzRjeiA=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.35", "", { "os": "win32", "cpu": "arm64" }, "sha512-WeOfAZrycFo9+ZqTDp3YDCAOLolymtKGwImrr9n+OW0lpwI2UKyKXbAwGXRhydAYbfrNmuqWyfyoAnLh3X9Hjg=="],
|
||||
|
||||
"@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.35", "", { "os": "win32", "cpu": "ia32" }, "sha512-XkLT7ikKGiUDvLh7qtJHRukbyyP1BIrD1xb7A+w4PjIiOKeOH8NqZ+PBaO4plT7JJnLxx+j9g/3B7iylR1nTFQ=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.35", "", { "os": "win32", "cpu": "x64" }, "sha512-rftASFKVzjbcQHTCYHaBIDrnQFzbeV50tm4hVugG3tPjd435RHZC2pbeGV5IPdKEqyJSuurM/GfbV3kLQ3LY/A=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.35", "", {}, "sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.1", "", { "os": "android", "cpu": "arm" }, "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.50.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.50.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.50.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.50.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.50.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.50.1", "", { "os": "linux", "cpu": "arm" }, "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.50.1", "", { "os": "linux", "cpu": "arm" }, "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.50.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.50.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w=="],
|
||||
|
||||
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.50.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.50.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.50.1", "", { "os": "linux", "cpu": "x64" }, "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.50.1", "", { "os": "linux", "cpu": "x64" }, "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.50.1", "", { "os": "none", "cpu": "arm64" }, "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.50.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.50.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.50.1", "", { "os": "win32", "cpu": "x64" }, "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA=="],
|
||||
|
||||
"@star-kitten/util": ["@star-kitten/util@link:@star-kitten/util", {}],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||
|
||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="],
|
||||
|
||||
"@types/jwk-to-pem": ["@types/jwk-to-pem@2.0.3", "", {}, "sha512-I/WFyFgk5GrNbkpmt14auGO3yFK1Wt4jXzkLuI+fDBNtO5ZI2rbymyGd6bKzfSBEuyRdM64ZUwxU1+eDcPSOEQ=="],
|
||||
|
||||
"@types/luxon": ["@types/luxon@3.7.1", "", {}, "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg=="],
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/node": ["@types/node@22.18.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-rzSDyhn4cYznVG+PCzGe1lwuMYJrcBS1fc3JqSa2PvtABwWo+dZ1ij5OVok3tqfpEBCBoaR4d7upFJk73HRJDw=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
|
||||
|
||||
"@vitest/coverage-v8": ["@vitest/coverage-v8@3.2.4", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", "ast-v8-to-istanbul": "^0.3.3", "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.9.0", "test-exclude": "^7.0.1", "tinyrainbow": "^2.0.0" }, "peerDependencies": { "@vitest/browser": "3.2.4", "vitest": "3.2.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ=="],
|
||||
|
||||
"@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
|
||||
|
||||
"@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
|
||||
|
||||
"@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||
|
||||
"@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="],
|
||||
|
||||
"@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="],
|
||||
|
||||
"@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.0", "", {}, "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||
|
||||
"ansis": ["ansis@4.1.0", "", {}, "sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w=="],
|
||||
|
||||
"args-tokenizer": ["args-tokenizer@0.3.0", "", {}, "sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q=="],
|
||||
|
||||
"asn1.js": ["asn1.js@5.4.1", "", { "dependencies": { "bn.js": "^4.0.0", "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "safer-buffer": "^2.1.0" } }, "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"ast-kit": ["ast-kit@2.1.2", "", { "dependencies": { "@babel/parser": "^7.28.0", "pathe": "^2.0.3" } }, "sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g=="],
|
||||
|
||||
"ast-v8-to-istanbul": ["ast-v8-to-istanbul@0.3.5", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.30", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } }, "sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"birpc": ["birpc@2.5.0", "", {}, "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ=="],
|
||||
|
||||
"bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"brorand": ["brorand@1.1.0", "", {}, "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="],
|
||||
|
||||
"browser-or-node": ["browser-or-node@3.0.0", "", {}, "sha512-iczIdVJzGEYhP5DqQxYM9Hh7Ztpqqi+CXZpSmX8ALFs9ecXkQIeqRyM6TfxEfMVpwhl3dSuDvxdzzo9sUOIVBQ=="],
|
||||
|
||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"bumpp": ["bumpp@10.2.3", "", { "dependencies": { "ansis": "^4.1.0", "args-tokenizer": "^0.3.0", "c12": "^3.2.0", "cac": "^6.7.14", "escalade": "^3.2.0", "jsonc-parser": "^3.3.1", "package-manager-detector": "^1.3.0", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "yaml": "^2.8.1" }, "bin": { "bumpp": "bin/bumpp.mjs" } }, "sha512-nsFBZACxuBVu6yzDSaZZaWpX5hTQ+++9WtYkmO+0Bd3cpSq0Mzvqw5V83n+fOyRj3dYuZRFCQf5Z9NNfZj+Rnw=="],
|
||||
|
||||
"bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
|
||||
|
||||
"c12": ["c12@3.2.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.5.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-ixkEtbYafL56E6HiFuonMm1ZjoKtIo7TH68/uiEq4DAwv9NcUX2nJ95F8TrbMeNjqIkZpruo3ojXQJ+MGG5gcQ=="],
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
|
||||
|
||||
"check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
|
||||
|
||||
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
||||
|
||||
"deepcopy-esm": ["deepcopy-esm@2.1.1", "", {}, "sha512-0lopQd/gi3excE3sgBrjuR3gJv6ZElk027i30pUgdjtvSJl/OoZ8B6L42GUBm6C3G8hD1EB5ir2gTYnINzWx4g=="],
|
||||
|
||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||
|
||||
"diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
|
||||
|
||||
"dotenv": ["dotenv@17.2.2", "", {}, "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q=="],
|
||||
|
||||
"drizzle-kit": ["drizzle-kit@0.31.4", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA=="],
|
||||
|
||||
"drizzle-orm": ["drizzle-orm@0.44.5", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-jBe37K7d8ZSKptdKfakQFdeljtu3P2Cbo7tJoJSVZADzIKOBo9IAJPOmMsH2bZl90bZgh8FQlD8BjxXA/zuBkQ=="],
|
||||
|
||||
"dts-resolver": ["dts-resolver@2.1.2", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
||||
|
||||
"elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="],
|
||||
|
||||
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="],
|
||||
|
||||
"exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"fp-booleans": ["fp-booleans@0.5.2", "", {}, "sha512-H5FFrtD2zNKL1if70v6duGyOYlTS1XqhZANsVk90wrTn8M/LXk4pFRnEQN3MGfzfCXUmEU/ofUGku5wSXkbtfA=="],
|
||||
|
||||
"fp-filters": ["fp-filters@0.5.4", "", { "dependencies": { "fp-booleans": "0.5.2" } }, "sha512-jwrnD0wTOe6OPc757jI/kR6s5gJi45OqnSeQp1SnAlUZTDpJCn/T6IXfPn0xmbaHc2utkMFI8QQ/TVc4TYMirw=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="],
|
||||
|
||||
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
|
||||
|
||||
"glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||
|
||||
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"hash.js": ["hash.js@1.1.7", "", { "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA=="],
|
||||
|
||||
"hmac-drbg": ["hmac-drbg@1.0.1", "", { "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg=="],
|
||||
|
||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||
|
||||
"html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
|
||||
|
||||
"istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
|
||||
|
||||
"istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="],
|
||||
|
||||
"istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="],
|
||||
|
||||
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="],
|
||||
|
||||
"js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
||||
|
||||
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
|
||||
|
||||
"jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="],
|
||||
|
||||
"jwk-to-pem": ["jwk-to-pem@2.0.7", "", { "dependencies": { "asn1.js": "^5.3.0", "elliptic": "^6.6.1", "safe-buffer": "^5.0.1" } }, "sha512-cSVphrmWr6reVchuKQZdfSs4U9c5Y4hwZggPoz6cbVnTpAVgGRpEuQng86IyqLeGZlhTh+c4MAreB6KbdQDKHQ=="],
|
||||
|
||||
"jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
|
||||
|
||||
"jwt-decode": ["jwt-decode@4.0.0", "", {}, "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="],
|
||||
|
||||
"lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
|
||||
|
||||
"lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
|
||||
|
||||
"lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
|
||||
|
||||
"lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
|
||||
|
||||
"lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
|
||||
|
||||
"lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
|
||||
|
||||
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
|
||||
|
||||
"loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
|
||||
|
||||
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.18", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ=="],
|
||||
|
||||
"magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="],
|
||||
|
||||
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
|
||||
|
||||
"minimalistic-assert": ["minimalistic-assert@1.0.1", "", {}, "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="],
|
||||
|
||||
"minimalistic-crypto-utils": ["minimalistic-crypto-utils@1.0.1", "", {}, "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="],
|
||||
|
||||
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"nypm": ["nypm@0.6.1", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.2.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w=="],
|
||||
|
||||
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||
|
||||
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||
|
||||
"package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
|
||||
|
||||
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
||||
|
||||
"prettier-plugin-multiline-arrays": ["prettier-plugin-multiline-arrays@4.0.3", "", { "dependencies": { "@augment-vir/common": "^31.10.1", "proxy-vir": "^2.0.1" }, "peerDependencies": { "prettier": ">=3.0.0" } }, "sha512-H1f/0zbvlO/FR0Fmyl31sSBodsIZkuQF0Omi9BrptLU31rZ+Almt9BbrE8IS3BFT/DGKePKb55XqN660LTnmsQ=="],
|
||||
|
||||
"proxy-vir": ["proxy-vir@2.0.1", "", { "dependencies": { "@augment-vir/assert": "^31.1.0", "@augment-vir/common": "^31.1.0" } }, "sha512-hjy5mWzHZhgRGh0f90f0Bz3VrGUe0T+AlhwnETakzRdvaN9RtPYLQG1+ZuEzSDK95FAhPYd26nEi1xVrXqvBwg=="],
|
||||
|
||||
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
|
||||
|
||||
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-beta.35", "", { "dependencies": { "@oxc-project/runtime": "=0.82.3", "@oxc-project/types": "=0.82.3", "@rolldown/pluginutils": "1.0.0-beta.35", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.35", "@rolldown/binding-darwin-arm64": "1.0.0-beta.35", "@rolldown/binding-darwin-x64": "1.0.0-beta.35", "@rolldown/binding-freebsd-x64": "1.0.0-beta.35", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.35", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.35", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.35", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.35", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.35", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.35", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.35", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.35", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.35", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.35" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-gJATyqcsJe0Cs8RMFO8XgFjfTc0lK1jcSvirDQDSIfsJE+vt53QH/Ob+OBSJsXb98YtZXHfP/bHpELpPwCprow=="],
|
||||
|
||||
"rolldown-plugin-dts": ["rolldown-plugin-dts@0.15.10", "", { "dependencies": { "@babel/generator": "^7.28.3", "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "ast-kit": "^2.1.2", "birpc": "^2.5.0", "debug": "^4.4.1", "dts-resolver": "^2.1.2", "get-tsconfig": "^4.10.1" }, "peerDependencies": { "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.9", "typescript": "^5.0.0", "vue-tsc": "~3.0.3" }, "optionalPeers": ["@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-8cPVAVQUo9tYAoEpc3jFV9RxSil13hrRRg8cHC9gLXxRMNtWPc1LNMSDXzjyD+5Vny49sDZH77JlXp/vlc4I3g=="],
|
||||
|
||||
"rollup": ["rollup@4.50.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.50.1", "@rollup/rollup-android-arm64": "4.50.1", "@rollup/rollup-darwin-arm64": "4.50.1", "@rollup/rollup-darwin-x64": "4.50.1", "@rollup/rollup-freebsd-arm64": "4.50.1", "@rollup/rollup-freebsd-x64": "4.50.1", "@rollup/rollup-linux-arm-gnueabihf": "4.50.1", "@rollup/rollup-linux-arm-musleabihf": "4.50.1", "@rollup/rollup-linux-arm64-gnu": "4.50.1", "@rollup/rollup-linux-arm64-musl": "4.50.1", "@rollup/rollup-linux-loongarch64-gnu": "4.50.1", "@rollup/rollup-linux-ppc64-gnu": "4.50.1", "@rollup/rollup-linux-riscv64-gnu": "4.50.1", "@rollup/rollup-linux-riscv64-musl": "4.50.1", "@rollup/rollup-linux-s390x-gnu": "4.50.1", "@rollup/rollup-linux-x64-gnu": "4.50.1", "@rollup/rollup-linux-x64-musl": "4.50.1", "@rollup/rollup-openharmony-arm64": "4.50.1", "@rollup/rollup-win32-arm64-msvc": "4.50.1", "@rollup/rollup-win32-ia32-msvc": "4.50.1", "@rollup/rollup-win32-x64-msvc": "4.50.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||
|
||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
"std-env": ["std-env@3.9.0", "", {}, "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw=="],
|
||||
|
||||
"string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-literal": ["strip-literal@3.0.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"test-exclude": ["test-exclude@7.0.1", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^9.0.4" } }, "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg=="],
|
||||
|
||||
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
|
||||
|
||||
"tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
|
||||
|
||||
"tinyspy": ["tinyspy@4.0.3", "", {}, "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A=="],
|
||||
|
||||
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
|
||||
|
||||
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
|
||||
|
||||
"tsdown": ["tsdown@0.14.2", "", { "dependencies": { "ansis": "^4.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.1", "diff": "^8.0.2", "empathic": "^2.0.0", "hookable": "^5.5.3", "rolldown": "latest", "rolldown-plugin-dts": "^0.15.8", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "tree-kill": "^1.2.2", "unconfig": "^7.3.3" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-6ThtxVZoTlR5YJov5rYvH8N1+/S/rD/pGfehdCLGznGgbxz+73EASV1tsIIZkLw2n+SXcERqHhcB/OkyxdKv3A=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"typed-event-target": ["typed-event-target@4.1.0", "", { "dependencies": { "@augment-vir/assert": "^31.19.1", "@augment-vir/common": "^31.19.1", "@augment-vir/core": "^31.19.1" } }, "sha512-fDFhZb7ofywLsVv8mYePD6ONfCpVHyM1t2dboEJx/XMsnflljnu3GQ5qH09hS1USuypGMR7wRbdWQPydgJ8nGQ=="],
|
||||
|
||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||
|
||||
"unconfig": ["unconfig@7.3.3", "", { "dependencies": { "@quansync/fs": "^0.1.5", "defu": "^6.1.4", "jiti": "^2.5.1", "quansync": "^0.2.11" } }, "sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"vite": ["vite@7.1.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw=="],
|
||||
|
||||
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||
|
||||
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
|
||||
|
||||
"vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||
|
||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"vitest/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||
|
||||
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
|
||||
|
||||
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
}
|
||||
}
|
||||
7
packages/eve/bunfig.toml
Normal file
7
packages/eve/bunfig.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[test]
|
||||
coverage = true
|
||||
coverageSkipTestFiles = true
|
||||
coverageReporter = ["text", "lcov"]
|
||||
|
||||
[run]
|
||||
bun = true
|
||||
7
packages/eve/drizzle.config.ts
Normal file
7
packages/eve/drizzle.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
|
||||
export default defineConfig({
|
||||
dialect: "sqlite",
|
||||
schema: "./src/db/schema.ts",
|
||||
out: "./drizzle",
|
||||
});
|
||||
29
packages/eve/fixtures/markdown/test-data-colors.json
Normal file
29
packages/eve/fixtures/markdown/test-data-colors.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"colors": ["red", "blue", "green", "yellow"],
|
||||
"testText": {
|
||||
"simple": "Hello World",
|
||||
"multiline": "Line 1\nLine 2\nLine 3",
|
||||
"withSpecialChars": "Text with !@#$%^&*()_+-=[]{}|;':\",./<>?",
|
||||
"empty": "",
|
||||
"unicode": "Unicode: 🌟 ❤️ 🔥",
|
||||
"code": "function test() { return 'hello'; }"
|
||||
},
|
||||
"expected": {
|
||||
"red": {
|
||||
"simple": "```ansi\n\u001b[2;31mHello World\u001b[0m```\n",
|
||||
"empty": "```ansi\n\u001b[2;31m\u001b[0m```\n"
|
||||
},
|
||||
"blue": {
|
||||
"simple": "```ansi\n\u001b[2;32m\u001b[2;36m\u001b[2;34mHello World\u001b[0m\u001b[2;36m\u001b[0m\u001b[2;32m\u001b[0m```\n",
|
||||
"empty": "```ansi\n\u001b[2;32m\u001b[2;36m\u001b[2;34m\u001b[0m\u001b[2;36m\u001b[0m\u001b[2;32m\u001b[0m```\n"
|
||||
},
|
||||
"green": {
|
||||
"simple": "```ansi\n\u001b[2;36mHello World\u001b[0m```\n",
|
||||
"empty": "```ansi\n\u001b[2;36m\u001b[0m```\n"
|
||||
},
|
||||
"yellow": {
|
||||
"simple": "```ansi\n\u001b[2;33mHello World\u001b[0m```\n",
|
||||
"empty": "```ansi\n\u001b[2;33m\u001b[0m```\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
41
packages/eve/fixtures/markdown/test-data-markup.json
Normal file
41
packages/eve/fixtures/markdown/test-data-markup.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"boldMarkup": {
|
||||
"complete": "<b>bold text</b>",
|
||||
"openOnly": "<b>bold text",
|
||||
"closeOnly": "bold text</b>",
|
||||
"nested": "<b>outer <b>inner</b> text</b>",
|
||||
"empty": "<b></b>",
|
||||
"multiple": "<b>first</b> and <b>second</b>",
|
||||
"mixed": "<b>bold</b> with <i>italic</i> text"
|
||||
},
|
||||
"italicMarkup": {
|
||||
"complete": "<i>italic text</i>",
|
||||
"openOnly": "<i>italic text",
|
||||
"closeOnly": "italic text</i>",
|
||||
"nested": "<i>outer <i>inner</i> text</i>",
|
||||
"empty": "<i></i>",
|
||||
"multiple": "<i>first</i> and <i>second</i>",
|
||||
"mixed": "<i>italic</i> with <b>bold</b> text"
|
||||
},
|
||||
"colorTags": {
|
||||
"hex6": "<color=0xFF5733>colored text</color>",
|
||||
"hex8": "<color=0xFF5733AA>colored text</color>",
|
||||
"hexWithoutPrefix": "<color=FF5733>colored text</color>",
|
||||
"namedColor": "<color=red>colored text</color>",
|
||||
"nested": "<color=blue>outer <color=red>inner</color> text</color>",
|
||||
"empty": "<color=green></color>",
|
||||
"multiple": "<color=red>first</color> and <color=blue>second</color>"
|
||||
},
|
||||
"eveLinks": {
|
||||
"simple": "<a href=showinfo:587>Rifter</a>",
|
||||
"withSpaces": "<a href=showinfo:12345>Ship Name With Spaces</a>",
|
||||
"multiple": "<a href=showinfo:587>Rifter</a> and <a href=showinfo:588>Merlin</a>",
|
||||
"nested": "Check out <a href=showinfo:587>Rifter</a> for PvP",
|
||||
"empty": "<a href=showinfo:587></a>"
|
||||
},
|
||||
"combined": {
|
||||
"allMarkup": "<b>Bold</b> <i>italic</i> <color=red>colored</color> <a href=showinfo:587>linked</a>",
|
||||
"nestedComplex": "<b><color=blue><a href=showinfo:587>Bold Blue Rifter</a></color></b>",
|
||||
"realWorldExample": "The <b><color=0xFF5733>Rifter</color></b> is a <i>fast</i> <a href=showinfo:587>frigate</a> used in PvP."
|
||||
}
|
||||
}
|
||||
34
packages/eve/fixtures/markdown/test-data-time.json
Normal file
34
packages/eve/fixtures/markdown/test-data-time.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"milliseconds": {
|
||||
"zero": 0,
|
||||
"oneSecond": 1000,
|
||||
"oneMinute": 60000,
|
||||
"oneHour": 3600000,
|
||||
"complex": 3661500,
|
||||
"daysWorthMs": 86400000,
|
||||
"fractionalSeconds": 1500,
|
||||
"smallFraction": 100
|
||||
},
|
||||
"seconds": {
|
||||
"zero": 0,
|
||||
"oneSecond": 1,
|
||||
"oneMinute": 60,
|
||||
"oneHour": 3600,
|
||||
"complex": 3661,
|
||||
"daysWorthSec": 86400,
|
||||
"fractionalInput": 3661.5
|
||||
},
|
||||
"expected": {
|
||||
"zero": "0.0s",
|
||||
"oneSecond": "1.0s",
|
||||
"oneMinute": "1m",
|
||||
"oneHour": "1h",
|
||||
"complexMs": "1h 1m 1.5s",
|
||||
"complexSec": "1h 1m 1s",
|
||||
"daysMs": "24h",
|
||||
"daysSec": "24h",
|
||||
"fractionalSeconds": "1.5s",
|
||||
"smallFraction": "0.1s",
|
||||
"fractionalInputSec": "1h 1m 1s"
|
||||
}
|
||||
}
|
||||
1564
packages/eve/package-lock.json
generated
Normal file
1564
packages/eve/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
70
packages/eve/package.json
Normal file
70
packages/eve/package.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "@star-kitten/eve",
|
||||
"version": "0.0.0",
|
||||
"description": "Star Kitten Eve library",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/author/library#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/author/library/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/author/library.git"
|
||||
},
|
||||
"author": "Author Name <author.name@mail.com>",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./*": "./dist/*",
|
||||
"./esi": "./dist/esi/index.js",
|
||||
"./db": "./dist/db/index.js",
|
||||
"./ref": "./dist/ref/index.js",
|
||||
"./third-party": "./dist/third-party/index.js",
|
||||
"./models": "./dist/models/index.js",
|
||||
"./package.json": "./package.json",
|
||||
"./data/*": "./data/*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.21",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/jwk-to-pem": "^2.0.3",
|
||||
"@types/node": "^22.15.17",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"bumpp": "^10.1.0",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"prettier-plugin-multiline-arrays": "^4.0.3",
|
||||
"tsdown": "^0.14.2",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@orama/orama": "^3.1.13",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"@star-kitten/util": "workspace:^0.0.0",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"fp-filters": "^0.5.4",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwk-to-pem": "^2.0.7",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"stream-chain": "^3.4.0",
|
||||
"stream-json": "^1.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"dev": "tsdown --watch",
|
||||
"test": "bun test",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"release": "bumpp && npm publish",
|
||||
"generate-migrations": "bunx drizzle-kit generate --dialect sqlite --schema ./src/db/schema.ts",
|
||||
"migrate": "bun run ./src/db/migrate.ts",
|
||||
"get-data": "bun refresh:reference-data && bun refresh:hoboleaks && bun static-export",
|
||||
"refresh:reference-data": "bun run ../util/dist/download-and-extract.js https://data.everef.net/reference-data/reference-data-latest.tar.xz ./data/reference-data",
|
||||
"refresh:hoboleaks": "bun run ../util/dist/download-and-extract.js https://data.everef.net/hoboleaks-sde/hoboleaks-sde-latest.tar.xz ./data/hoboleaks",
|
||||
"static-export": "bun run ./scripts/export-solar-systems.ts"
|
||||
}
|
||||
}
|
||||
19
packages/eve/scripts/export-solar-systems.ts
Normal file
19
packages/eve/scripts/export-solar-systems.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Database } from "bun:sqlite";
|
||||
import { join } from "node:path";
|
||||
|
||||
const db = new Database(join(process.cwd(), 'data/evestatic.db'));
|
||||
|
||||
const query = db.query("SELECT * FROM mapSolarSystems");
|
||||
const results = query.all();
|
||||
|
||||
const output = results.reduce((acc: any, system: any) => {
|
||||
acc[system.solarSystemID] = system;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const jsonData = JSON.stringify(output, null, 2);
|
||||
|
||||
const fs = await import('fs/promises');
|
||||
await fs.writeFile(join(process.cwd(), 'data/reference-data/solar_systems.json'), jsonData);
|
||||
|
||||
db.close();
|
||||
30
packages/eve/src/esi/alliance.ts
Normal file
30
packages/eve/src/esi/alliance.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { esiFetch } from './fetch';
|
||||
|
||||
// PUBLIC APIS ---------------------------------------------------------------
|
||||
|
||||
interface AllianceData {
|
||||
creator_corporation_id: number;
|
||||
creator_id: number;
|
||||
date_founded: string;
|
||||
executor_corporation_id: number;
|
||||
faction_id: number;
|
||||
name: string;
|
||||
ticker: string;
|
||||
}
|
||||
|
||||
export async function getAllianceData(id: number) {
|
||||
return await esiFetch<Partial<AllianceData>>(`/alliances/${id}/`);
|
||||
}
|
||||
|
||||
export async function getAllianceCorporations(id: number) {
|
||||
return await esiFetch<number[]>(`/alliances/${id}/corporations/`);
|
||||
}
|
||||
|
||||
interface AllianceIcons {
|
||||
px128x128: string;
|
||||
px64x64: string;
|
||||
}
|
||||
|
||||
export async function getAllianceIcons(id: number) {
|
||||
return await esiFetch<Partial<AllianceIcons>>(`/alliances/${id}/icons/`);
|
||||
}
|
||||
102
packages/eve/src/esi/auth.ts
Normal file
102
packages/eve/src/esi/auth.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { encodeBase64urlNoPadding } from '@oslojs/encoding';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import jwkToPem from 'jwk-to-pem';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import { options } from './options';
|
||||
|
||||
export interface EveTokens {
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
function generateState(): string {
|
||||
const randomValues = new Uint8Array(32);
|
||||
crypto.getRandomValues(randomValues);
|
||||
return encodeBase64urlNoPadding(randomValues);
|
||||
}
|
||||
|
||||
export async function createAuthorizationURL(scopes: string[] | string = 'publicData') {
|
||||
const state = generateState();
|
||||
const url = new URL('https://login.eveonline.com/v2/oauth/authorize/');
|
||||
url.searchParams.set('response_type', 'code');
|
||||
url.searchParams.set('redirect_uri', options.callback_url);
|
||||
url.searchParams.set('client_id', options.client_id);
|
||||
url.searchParams.set('state', state);
|
||||
url.searchParams.set('scope', Array.isArray(scopes) ? scopes.join(' ') : scopes);
|
||||
return {
|
||||
url,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
export async function validateAuthorizationCode(code: string): Promise<EveTokens> {
|
||||
try {
|
||||
const response = await fetch('https://login.eveonline.com/v2/oauth/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Authorization: `Basic ${Buffer.from(`${options.client_id}:${options.client_secret}`).toString('base64')}`,
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
}),
|
||||
});
|
||||
return (await response.json()) as EveTokens;
|
||||
} catch (error) {
|
||||
console.error(`failed to validate EVE authorization code`, error);
|
||||
throw `${error}`;
|
||||
}
|
||||
}
|
||||
|
||||
// cache the public key for EVE Online's OAuth2 provider
|
||||
let eveAuthPublicKey: any;
|
||||
export async function validateToken(token: string) {
|
||||
if (!eveAuthPublicKey) {
|
||||
try {
|
||||
const eveJWKS = (await (await fetch('https://login.eveonline.com/oauth/jwks')).json()) as { keys: any[] };
|
||||
eveAuthPublicKey = jwkToPem(eveJWKS.keys[0]);
|
||||
} catch (err) {
|
||||
console.error(`failed to get EVE Auth public keys`, err);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, eveAuthPublicKey);
|
||||
return decoded;
|
||||
} catch (err) {
|
||||
console.error(`failed to validate EVE token`, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function refresh(
|
||||
{ refresh_token }: { refresh_token: string },
|
||||
scopes?: string[] | string,
|
||||
): Promise<EveTokens> {
|
||||
const params = {
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token,
|
||||
scope: '' as string | string[],
|
||||
};
|
||||
|
||||
if (scopes) {
|
||||
params['scope'] = Array.isArray(scopes) ? scopes.join(' ') : scopes;
|
||||
}
|
||||
|
||||
const response = await fetch('https://login.eveonline.com/v2/oauth/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Authorization: `Basic ${Buffer.from(`${options.client_id}:${options.client_secret}`).toString('base64')}`,
|
||||
},
|
||||
body: new URLSearchParams(params),
|
||||
});
|
||||
return (await response.json()) as EveTokens;
|
||||
}
|
||||
|
||||
export function characterIdFromToken(token: string) {
|
||||
const payload = jwtDecode(token);
|
||||
return parseInt(payload.sub!.split(':')[2]);
|
||||
}
|
||||
381
packages/eve/src/esi/character.ts
Normal file
381
packages/eve/src/esi/character.ts
Normal file
@@ -0,0 +1,381 @@
|
||||
import { CharacterHelper, type Character } from '@/db';
|
||||
import { esiFetch } from './fetch';
|
||||
import { tokenHasScopes } from './scopes';
|
||||
|
||||
// PUBLIC APIS ---------------------------------------------------------------
|
||||
|
||||
export interface CharacterData {
|
||||
alliance_id: number;
|
||||
birthday: string;
|
||||
bloodline_id: number;
|
||||
corporation_id: number;
|
||||
description: string;
|
||||
faction_id: number;
|
||||
gender: 'male' | 'female';
|
||||
name: string;
|
||||
race_id: number;
|
||||
security_status: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export function getCharacterPublicData(id: number) {
|
||||
return esiFetch<Partial<CharacterData>>(`/characters/${id}/`);
|
||||
}
|
||||
|
||||
export interface CharacterAffiliations {
|
||||
alliance_id: number;
|
||||
character_id: number;
|
||||
corporation_id: number;
|
||||
faction_id: number;
|
||||
}
|
||||
|
||||
export function getCharacterAffiliations(ids: number[]) {
|
||||
return esiFetch<Partial<CharacterAffiliations>[]>(`/characters/affiliation/`, undefined, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
})[0] as Partial<CharacterAffiliations>;
|
||||
}
|
||||
|
||||
export interface CharacterPortraits {
|
||||
px128x128: string;
|
||||
px256x256: string;
|
||||
px512x512: string;
|
||||
px64x64: string;
|
||||
}
|
||||
|
||||
export function getCharacterPortraits(id: number) {
|
||||
return esiFetch<Partial<CharacterPortraits>>(`/characters/${id}/portrait/`);
|
||||
}
|
||||
|
||||
export interface CharacterCorporationHistory {
|
||||
corporation_id: number;
|
||||
is_deleted: boolean;
|
||||
record_id: number;
|
||||
start_date: string;
|
||||
}
|
||||
|
||||
export function getCharacterCorporationHistory(id: number) {
|
||||
return esiFetch<Partial<CharacterCorporationHistory>[]>(`/characters/${id}/corporationhistory/`);
|
||||
}
|
||||
|
||||
export function getPortraitURL(id: number) {
|
||||
return `https://images.evetech.net/characters/${id}/portrait`;
|
||||
}
|
||||
|
||||
// PRIVATE APIS --------------------------------------------------------------
|
||||
|
||||
export interface CharacterRoles {
|
||||
roles: string[];
|
||||
roles_at_base: string[];
|
||||
roles_at_hq: string[];
|
||||
roles_at_other: string[];
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_corporation_roles.v1
|
||||
export function getCharacterRoles(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-characters.read_corporation_roles.v1')) return null;
|
||||
return esiFetch<Partial<CharacterRoles>>(`/characters/${character.eveID}/roles/`, character);
|
||||
}
|
||||
|
||||
export interface CharacterTitles {
|
||||
titles: {
|
||||
name: string;
|
||||
title_id: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_titles.v1
|
||||
export function getCharacterTitles(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-characters.read_titles.v1')) return null;
|
||||
return esiFetch<Partial<CharacterTitles>>(`/characters/${character.eveID}/titles/`, character);
|
||||
}
|
||||
|
||||
export interface CharacterStandings {
|
||||
from_id: number;
|
||||
from_type: 'agent' | 'npc_corp' | 'faction';
|
||||
standing: number;
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_standings.v1
|
||||
export function getCharacterStandings(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-characters.read_standings.v1')) return null;
|
||||
return esiFetch<Partial<CharacterStandings>[]>(`/characters/${character.eveID}/standings/`, character);
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
is_read: boolean;
|
||||
sender_id: number;
|
||||
sender_type: 'character' | 'corporation' | 'alliance' | 'faction' | 'system';
|
||||
text: string;
|
||||
timestamp: string;
|
||||
type:
|
||||
| 'character'
|
||||
| 'corporation'
|
||||
| 'alliance'
|
||||
| 'faction'
|
||||
| 'inventory'
|
||||
| 'industry'
|
||||
| 'loyalty'
|
||||
| 'skills'
|
||||
| 'sov'
|
||||
| 'structures'
|
||||
| 'war';
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_notifications.v1
|
||||
export function getCharacterNotifications(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-characters.read_notifications.v1')) return null;
|
||||
return esiFetch<Partial<Notification>[]>(`/characters/${character.eveID}/notifications/`, character);
|
||||
}
|
||||
|
||||
export interface ContactNotification {
|
||||
message: string;
|
||||
notification_id: number;
|
||||
send_date: string;
|
||||
sender_character_id: number;
|
||||
standing_level: number;
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_notifications.v1
|
||||
export function getCharacterContactNotifications(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-characters.read_notifications.v1')) return null;
|
||||
return esiFetch<Partial<ContactNotification>[]>(`/characters/${character.eveID}/notifications/contacts`, character);
|
||||
}
|
||||
|
||||
export interface Medals {
|
||||
corporation_id: number;
|
||||
date: string;
|
||||
description: string;
|
||||
graphics: {
|
||||
color: number;
|
||||
graphic: number;
|
||||
layer: number;
|
||||
part: number;
|
||||
}[];
|
||||
issuer_id: number;
|
||||
medal_id: number;
|
||||
reason: string;
|
||||
status: 'private' | 'public';
|
||||
title: string;
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_medals.v1
|
||||
export function getCharacterMedals(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-characters.read_medals.v1')) return null;
|
||||
return esiFetch<Partial<Medals>[]>(`/characters/${character.eveID}/medals/`, character);
|
||||
}
|
||||
|
||||
export interface JumpFatigue {
|
||||
jump_fatigue_expire_date: string;
|
||||
last_jump_date: string;
|
||||
last_update_date: string;
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_fatigue.v1
|
||||
export function getCharacterJumpFatigue(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-characters.read_fatigue.v1')) return null;
|
||||
return esiFetch<Partial<JumpFatigue>>(`/characters/${character.eveID}/fatigue/`, character);
|
||||
}
|
||||
|
||||
export interface Blueprint {
|
||||
item_id: number;
|
||||
location_flag: string;
|
||||
location_id: number;
|
||||
material_efficiency: number;
|
||||
quantity: number;
|
||||
runs: number;
|
||||
time_efficiency: number;
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_blueprints.v1
|
||||
export function getCharacterBlueprints(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-characters.read_blueprints.v1')) return null;
|
||||
return esiFetch<Partial<Blueprint>[]>(`/characters/${character.eveID}/blueprints/`, character);
|
||||
}
|
||||
|
||||
export interface AgentResearch {
|
||||
agent_id: number;
|
||||
points_per_day: number;
|
||||
remainder_points: number;
|
||||
skill_type_id: number;
|
||||
started_at: string;
|
||||
}
|
||||
|
||||
// required scope: esi-characters.read_agents_research.v1
|
||||
export function getCharacterAgentResearch(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-characters.read_agents_research.v1')) return null;
|
||||
return esiFetch<Partial<AgentResearch>[]>(`/characters/${character.eveID}/agents_research/`, character);
|
||||
}
|
||||
|
||||
// CLONES --------------------------------------------------------------------
|
||||
|
||||
export interface Clones {
|
||||
home_location: {
|
||||
location_id: number;
|
||||
location_type: 'station' | 'structure';
|
||||
};
|
||||
jump_clones: {
|
||||
implants: number[];
|
||||
jump_clone_id: number;
|
||||
location_id: number;
|
||||
location_type: 'station' | 'structure';
|
||||
name: string;
|
||||
}[];
|
||||
last_clone_jump_date: string;
|
||||
last_station_change_date: string;
|
||||
}
|
||||
|
||||
// required scope: esi-clones.read_clones.v1
|
||||
export function getCharacterClones(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-clones.read_clones.v1')) return null;
|
||||
return esiFetch<Partial<Clones>>(`/characters/${character.eveID}/clones/`, character);
|
||||
}
|
||||
|
||||
// required scope: esi-clones.read_implants.v1
|
||||
export function getCharacterImplants(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-clones.read_implants.v1')) return null;
|
||||
return esiFetch<number[]>(`/characters/${character.eveID}/implants/`, character);
|
||||
}
|
||||
|
||||
// ASSETS --------------------------------------------------------------------
|
||||
|
||||
export interface Asset {
|
||||
is_blueprint_copy: boolean;
|
||||
is_singleton: boolean;
|
||||
item_id: number;
|
||||
location_flag: string;
|
||||
location_id: number;
|
||||
location_type: 'station' | 'solar_system' | 'other';
|
||||
quantity: number;
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
// required scope: esi-assets.read_assets.v1
|
||||
export function getCharacterAssets(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-assets.read_assets.v1')) return null;
|
||||
return esiFetch<Partial<Asset>[]>(`/characters/${character.eveID}/assets/`, character);
|
||||
}
|
||||
|
||||
export interface AssetLocation {
|
||||
item_id: number;
|
||||
position: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
};
|
||||
}
|
||||
|
||||
// required scope: esi-assets.read_assets.v1
|
||||
export function getCharacterAssetLocations(character: Character, ids: number[]) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-assets.read_assets.v1')) return null;
|
||||
return esiFetch<Partial<AssetLocation>[]>(`/characters/${character.eveID}/assets/locations/`, character, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
});
|
||||
}
|
||||
|
||||
export interface AssetNames {
|
||||
item_id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// required scope: esi-assets.read_assets.v1
|
||||
export function getCharacterAssetNames(character: Character, ids: number[]) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-assets.read_assets.v1')) return null;
|
||||
return esiFetch<Partial<AssetNames>[]>(`/characters/${character.eveID}/assets/names/`, character, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
});
|
||||
}
|
||||
|
||||
// WALLET --------------------------------------------------------------------
|
||||
|
||||
// required scope: esi-wallet.read_character_wallet.v1
|
||||
export function getCharacterWallet(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-wallet.read_character_wallet.v1')) return null;
|
||||
return esiFetch<number>(`/characters/${character.eveID}/wallet/`, character);
|
||||
}
|
||||
|
||||
export interface WalletTransaction {
|
||||
client_id: number;
|
||||
date: string;
|
||||
is_buy: boolean;
|
||||
is_personal: boolean;
|
||||
journal_ref_id: number;
|
||||
location_id: number;
|
||||
quantity: number;
|
||||
transaction_id: number;
|
||||
type_id: number;
|
||||
unit_price: number;
|
||||
}
|
||||
|
||||
// required scope: esi-wallet.read_character_wallet.v1
|
||||
export function getCharacterWalletTransactions(character: Character, fromId: number) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-wallet.read_character_wallet.v1')) return null;
|
||||
return esiFetch<Partial<WalletTransaction>[]>(`/characters/${character.eveID}/wallet/transactions/`, character, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(fromId),
|
||||
});
|
||||
}
|
||||
|
||||
export interface WalletJournalEntry {
|
||||
amount: number; // The amount of ISK given or taken from the wallet as a result of the given transaction. Positive when ISK is deposited into the wallet and negative when ISK is withdrawn
|
||||
balance: number; // Wallet balance after transaction occurred
|
||||
context_id: number; // And ID that gives extra context to the particualr transaction. Because of legacy reasons the context is completely different per ref_type and means different things. It is also possible to not have a context_id
|
||||
context_id_type: 'character' | 'corporation' | 'alliance' | 'faction'; // The type of the given context_id if present
|
||||
date: string; // Date and time of transaction
|
||||
description: string;
|
||||
first_party_id: number;
|
||||
id: number;
|
||||
reason: string;
|
||||
ref_type: 'agent' | 'assetSafety' | 'bounty' | 'bountyPrizes' | 'contract' | 'dividend' | 'marketTransaction' | 'other';
|
||||
second_party_id: number;
|
||||
tax: number;
|
||||
tax_receiver_id: number;
|
||||
}
|
||||
|
||||
// required scope: esi-wallet.read_character_wallet.v1
|
||||
export function getCharacterWalletJournal(character: Character, page: number = 1) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-wallet.read_character_wallet.v1')) return null;
|
||||
return esiFetch<Partial<WalletJournalEntry>[]>(`/characters/${character.eveID}/wallet/journal/?page=${page}`, character);
|
||||
}
|
||||
|
||||
// LOCATION --------------------------------------------------
|
||||
|
||||
export interface Location {
|
||||
solar_system_id: number;
|
||||
station_id: number;
|
||||
structure_id: number;
|
||||
}
|
||||
|
||||
// required scope: esi-location.read_location.v1
|
||||
export function getCharacterLocation(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-location.read_location.v1')) return null;
|
||||
return esiFetch<Partial<Location>>(`/characters/${character.eveID}/location/`, character);
|
||||
}
|
||||
|
||||
export interface Online {
|
||||
last_login: string;
|
||||
last_logout: string;
|
||||
logins: number;
|
||||
online: boolean;
|
||||
}
|
||||
|
||||
// required scope: esi-location.read_online.v1
|
||||
export function getCharacterOnline(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-location.read_online.v1')) return null;
|
||||
return esiFetch<Partial<Online>>(`/characters/${character.eveID}/online/`, character);
|
||||
}
|
||||
|
||||
export interface CurrentShip {
|
||||
ship_item_id: number;
|
||||
ship_type_id: number;
|
||||
ship_name: string;
|
||||
}
|
||||
|
||||
// required scope: esi-location.read_ship_type.v1
|
||||
export function getCharacterCurrentShip(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-location.read_ship_type.v1')) return null;
|
||||
return esiFetch<Partial<CurrentShip>>(`/characters/${character.eveID}/ship/`, character);
|
||||
}
|
||||
97
packages/eve/src/esi/corporation.ts
Normal file
97
packages/eve/src/esi/corporation.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { CharacterHelper, type Character } from '@/db';
|
||||
import { esiFetch } from './fetch';
|
||||
|
||||
// PUBLIC APIS ---------------------------------------------------------------
|
||||
|
||||
interface CorporationData {
|
||||
alliance_id: number;
|
||||
ceo_id: number;
|
||||
creator_id: number;
|
||||
date_founded: string;
|
||||
description: string;
|
||||
faction_id: number;
|
||||
home_station_id: number;
|
||||
member_count: number;
|
||||
name: string;
|
||||
shares: number;
|
||||
tax_rate: number;
|
||||
ticker: string;
|
||||
url: string;
|
||||
war_eligible: boolean;
|
||||
}
|
||||
|
||||
export async function getCorporationData(id: number) {
|
||||
return await esiFetch<Partial<CorporationData>>(`/corporations/${id}/`);
|
||||
}
|
||||
|
||||
interface AllianceHistory {
|
||||
alliance_id: number;
|
||||
is_deleted: boolean;
|
||||
record_id: number;
|
||||
start_date: string;
|
||||
}
|
||||
|
||||
export async function getCorporationAllianceHistory(id: number) {
|
||||
return await esiFetch<Partial<AllianceHistory>[]>(`/corporations/${id}/alliancehistory/`);
|
||||
}
|
||||
|
||||
interface CorporationIcons {
|
||||
px256x256: string;
|
||||
px128x128: string;
|
||||
px64x64: string;
|
||||
}
|
||||
|
||||
export async function getCorporationIcons(id: number) {
|
||||
return await esiFetch<Partial<CorporationIcons>>(`/corporations/${id}/icons/`);
|
||||
}
|
||||
|
||||
// ASSETS -------------------------------------------------------------------
|
||||
|
||||
export interface AssetData {
|
||||
is_blueprint_copy: boolean;
|
||||
is_singleton: boolean;
|
||||
item_id: number;
|
||||
location_flag: string;
|
||||
location_id: number;
|
||||
location_type: string;
|
||||
quantity: number;
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
// required scope: esi-assets.read_corporation_assets.v1
|
||||
export async function getCorporationAssets(id: number, character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-assets.read_corporation_assets.v1')) return null;
|
||||
return await esiFetch<Partial<AssetData>[]>(`/corporations/${id}/assets/`, character);
|
||||
}
|
||||
|
||||
export interface AssetLocation {
|
||||
item_id: number;
|
||||
position: {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
};
|
||||
}
|
||||
|
||||
// required scope: esi-assets.read_corporation_assets.v1
|
||||
export async function getCorporationAssetLocations(id: number, character: Character, ids: number[]) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-assets.read_corporation_assets.v1')) return null;
|
||||
return await esiFetch<Partial<AssetLocation>[]>(`/corporations/${id}/assets/locations/`, character, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
});
|
||||
}
|
||||
|
||||
export interface AssetNames {
|
||||
item_id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// required scope: esi-assets.read_corporation_assets.v1
|
||||
export async function getCorporationAssetNames(id: number, character: Character, ids: number[]) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-assets.read_corporation_assets.v1')) return null;
|
||||
return await esiFetch<Partial<AssetNames>[]>(`/corporations/${id}/assets/names/`, character, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(ids),
|
||||
});
|
||||
}
|
||||
92
packages/eve/src/esi/fetch.ts
Normal file
92
packages/eve/src/esi/fetch.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { type Character, CharacterHelper } from '@/db/models';
|
||||
import { options } from './options';
|
||||
import { ESI_LATEST_URL } from './scopes';
|
||||
|
||||
const cache = new Map<string, CacheItem>();
|
||||
|
||||
interface RequestOptions extends RequestInit {
|
||||
noCache?: boolean;
|
||||
cacheDuration?: number; // default 30 minutes
|
||||
}
|
||||
|
||||
interface CacheItem {
|
||||
expires: number;
|
||||
data: any;
|
||||
}
|
||||
|
||||
function cleanCache() {
|
||||
const now = Date.now();
|
||||
for (const [key, value] of cache) {
|
||||
if (value.expires < now) {
|
||||
cache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(cleanCache, 1000 * 60 * 15); // clean cache every 15 minutes
|
||||
|
||||
const defaultCacheDuration = 1000 * 60 * 30; // 30 minutes
|
||||
|
||||
export async function esiFetch<T>(
|
||||
path: string,
|
||||
character?: Character,
|
||||
{ method = 'GET', body, noCache = false, cacheDuration = defaultCacheDuration }: Partial<RequestOptions> = {},
|
||||
) {
|
||||
try {
|
||||
const headers = {
|
||||
'User-Agent': options.user_agent,
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
if (character) {
|
||||
// check if the token is expired
|
||||
if (!CharacterHelper.hasValidToken(character)) {
|
||||
await CharacterHelper.refreshTokens(character);
|
||||
if (!CharacterHelper.hasValidToken(character)) {
|
||||
throw new Error(`Failed to refresh token for character: ${character.eveID}`);
|
||||
}
|
||||
}
|
||||
|
||||
headers['Authorization'] = `Bearer ${character.accessToken}`;
|
||||
}
|
||||
|
||||
const init: RequestInit = {
|
||||
headers,
|
||||
method: method || 'GET',
|
||||
body: body || undefined,
|
||||
};
|
||||
|
||||
const url = new URL(`${ESI_LATEST_URL}${path.startsWith('/') ? path : '/' + path}`);
|
||||
url.searchParams.set('datasource', 'tranquility');
|
||||
|
||||
if (!noCache && init.method === 'GET') {
|
||||
const cached = cache.get(url.href);
|
||||
if (cached && cached?.expires > Date.now()) {
|
||||
return cached.data as T;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch(url, init);
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(`ESI request failure at ${path} | ${res.status}:${res.statusText} => ${JSON.stringify(data)}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (init.method === 'GET') {
|
||||
cache.set(url.href, {
|
||||
expires: Math.max(
|
||||
(res.headers.get('expires') && new Date(Number(res.headers.get('expires') || '')).getTime()) || 0,
|
||||
Date.now() + cacheDuration,
|
||||
),
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
return data as T;
|
||||
} catch (err) {
|
||||
console.error(`ESI request failure at ${path} | ${JSON.stringify(err)}`, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
12
packages/eve/src/esi/index.ts
Normal file
12
packages/eve/src/esi/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export * from './scopes';
|
||||
export * as CharacterAPI from './character';
|
||||
export * as CorporationAPI from './corporation';
|
||||
export * as AllianceAPI from './alliance';
|
||||
export * as auth from './auth';
|
||||
export * from './auth';
|
||||
export * from './fetch';
|
||||
export * from './skills';
|
||||
export * from './options';
|
||||
export * from './mail';
|
||||
export * from './character';
|
||||
export * from './alliance';
|
||||
130
packages/eve/src/esi/mail.ts
Normal file
130
packages/eve/src/esi/mail.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { esiFetch } from './fetch';
|
||||
import { CharacterHelper, type Character } from '@/db';
|
||||
|
||||
export interface MailHeader {
|
||||
from: number; // From whom the mail was sent
|
||||
is_read: boolean; // is_read boolean
|
||||
labels: string[]; // maxItems: 25, minimum: 0, title: get_characters_character_id_mail_labels, uniqueItems: true, labels array
|
||||
mail_id: number; // mail_id integer
|
||||
recipients: {
|
||||
recipient_id: number; // recipient_id integer
|
||||
recipient_type: 'alliance' | 'character' | 'corporation' | 'mailing_list'; // recipient_type enum
|
||||
}[]; // maxItems: 52, minimum: 0, title: get_characters_character_id_mail_recipients, uniqueItems: true, recipients of the mail
|
||||
subject: string; // Mail subject
|
||||
timestamp: string; // When the mail was sent
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.read_mail.v1
|
||||
export function getMailHeaders(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-mail.read_mail.v1')) return null;
|
||||
return esiFetch<MailHeader[]>(`/characters/${character.eveID}/mail/`, character);
|
||||
}
|
||||
|
||||
export interface SendMail {
|
||||
approved_cost?: number; // approved_cost number
|
||||
body: string; // body string; max length 10000
|
||||
recipients: {
|
||||
recipient_id: number; // recipient_id integer
|
||||
recipient_type: 'alliance' | 'character' | 'corporation' | 'mailing_list'; // recipient_type enum
|
||||
}[]; // maxItems: 50, minimum: 1, title: post_characters_character_id_mail, recipients of the mail
|
||||
subject: string; // subject string; max length 1000
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.send_mail.v1
|
||||
export function sendMail(character: Character, mail: SendMail) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-mail.send_mail.v1')) return null;
|
||||
return esiFetch(`/characters/${character.eveID}/mail/`, character, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(mail),
|
||||
});
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.read_mail.v1
|
||||
export function deleteMail(character: Character, mailID: number) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-mail.organize_mail.v1')) return null;
|
||||
return esiFetch(`/characters/${character.eveID}/mail/${mailID}/`, character, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
export interface Mail {
|
||||
body: string; // body string
|
||||
from: number; // from integer
|
||||
labels: string[]; // labels array
|
||||
read: boolean; // read boolean
|
||||
subject: string; // subject string
|
||||
timestamp: string; // timestamp string
|
||||
recipients: {
|
||||
recipient_id: number; // recipient_id integer
|
||||
recipient_type: 'alliance' | 'character' | 'corporation' | 'mailing_list'; // recipient_type enum
|
||||
}[]; // recipients array
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.read_mail.v1
|
||||
export function getMail(character: Character, mailID: number) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-mail.read_mail.v1')) return null;
|
||||
return esiFetch<Mail>(`/characters/${character.eveID}/mail/${mailID}/`, character);
|
||||
}
|
||||
|
||||
export interface MailMetadata {
|
||||
labels: string[]; // labels array
|
||||
read: boolean; // read boolean
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.organize_mail.v1
|
||||
export function updateMailMetadata(character: Character, mailID: number, metadata: MailMetadata) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-mail.organize_mail.v1')) return null;
|
||||
return esiFetch(`/characters/${character.eveID}/mail/${mailID}/`, character, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(metadata),
|
||||
});
|
||||
}
|
||||
|
||||
export interface MailLabels {
|
||||
labels: {
|
||||
color: number; // color integer
|
||||
label_id: number; // label_id integer
|
||||
name: string; // name string
|
||||
unread_count: number; // unread_count integer
|
||||
}[]; // labels array
|
||||
total_unread_count: number; // total_unread_count integer
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.read_mail.v1
|
||||
export function getMailLabels(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-mail.read_mail.v1')) return null;
|
||||
return esiFetch<MailLabels>(`/characters/${character.eveID}/mail/labels/`, character);
|
||||
}
|
||||
|
||||
export interface CreateMailLabel {
|
||||
color: number; // color integer
|
||||
name: string; // name string
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.organize_mail.v1
|
||||
export function createMailLabel(character: Character, label: CreateMailLabel) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-mail.organize_mail.v1')) return null;
|
||||
return esiFetch(`/characters/${character.eveID}/mail/labels/`, character, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(label),
|
||||
});
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.organize_mail.v1
|
||||
export function deleteMailLabel(character: Character, labelID: number) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-mail.organize_mail.v1')) return null;
|
||||
return esiFetch(`/characters/${character.eveID}/mail/labels/${labelID}/`, character, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
export interface MailingList {
|
||||
mailing_list_id: number; // mailing_list_id integer
|
||||
name: string; // name string
|
||||
}
|
||||
|
||||
// requires scope: esi-mail.read_mail.v1
|
||||
export function getMailingLists(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-mail.read_mail.v1')) return null;
|
||||
return esiFetch<MailingList[]>(`/characters/${character.eveID}/mail/lists/`, character);
|
||||
}
|
||||
18
packages/eve/src/esi/options.ts
Normal file
18
packages/eve/src/esi/options.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface EveAuthOptions {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
callback_url: string;
|
||||
user_agent: string;
|
||||
}
|
||||
|
||||
const CLIENT_ID = process.env.EVE_CLIENT_ID || '';
|
||||
const CLIENT_SECRET = process.env.EVE_CLIENT_SECRET || '';
|
||||
const CALLBACK_URL = process.env.EVE_CALLBACK_URL || '';
|
||||
const USER_AGENT = process.env.ESI_USER_AGENT || '';
|
||||
|
||||
export const options: EveAuthOptions = {
|
||||
client_id: CLIENT_ID,
|
||||
client_secret: CLIENT_SECRET,
|
||||
callback_url: CALLBACK_URL,
|
||||
user_agent: USER_AGENT,
|
||||
};
|
||||
91
packages/eve/src/esi/scopes.ts
Normal file
91
packages/eve/src/esi/scopes.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
|
||||
export const EVE_JWKS_URL = 'https://login.eveonline.com/oauth/jwks';
|
||||
export const EVE_ISSUER = 'login.eveonline.com';
|
||||
export const EVE_AUDIENCE = 'eveonline';
|
||||
export const ESI_LATEST_URL = 'https://esi.evetech.net/latest';
|
||||
export const DATA_SOURCE = 'tranquility';
|
||||
|
||||
export function joinScopes(...scopes: string[]) {
|
||||
return scopes.join(' ');
|
||||
}
|
||||
|
||||
export enum SCOPES {
|
||||
PUBLIC_DATA = 'publicData',
|
||||
CALENDAR_RESPOND_CALENDAR_EVENTS = 'esi-calendar.respond_calendar_events.v1',
|
||||
CALENDAR_READ_CALENDAR_EVENTS = 'esi-calendar.read_calendar_events.v1',
|
||||
LOCATION_READ_LOCATION = 'esi-location.read_location.v1',
|
||||
LOCATION_READ_SHIP_TYPE = 'esi-location.read_ship_type.v1',
|
||||
MAIL_ORGANIZE_MAIL = 'esi-mail.organize_mail.v1',
|
||||
MAIL_READ_MAIL = 'esi-mail.read_mail.v1',
|
||||
MAIL_SEND_MAIL = 'esi-mail.send_mail.v1',
|
||||
SKILLS_READ_SKILLS = 'esi-skills.read_skills.v1',
|
||||
SKILLS_READ_SKILLQUEUE = 'esi-skills.read_skillqueue.v1',
|
||||
WALLET_READ_CHARACTER_WALLET = 'esi-wallet.read_character_wallet.v1',
|
||||
WALLET_READ_CORPORATION_WALLET = 'esi-wallet.read_corporation_wallet.v1',
|
||||
SEARCH_SEARCH_STRUCTURES = 'esi-search.search_structures.v1',
|
||||
CLONES_READ_CLONES = 'esi-clones.read_clones.v1',
|
||||
CHARACTERS_READ_CONTACTS = 'esi-characters.read_contacts.v1',
|
||||
UNIVERSE_READ_STRUCTURES = 'esi-universe.read_structures.v1',
|
||||
KILLMAILS_READ_KILLMAILS = 'esi-killmails.read_killmails.v1',
|
||||
CORPORATIONS_READ_CORPORATION_MEMBERSHIP = 'esi-corporations.read_corporation_membership.v1',
|
||||
ASSETS_READ_ASSETS = 'esi-assets.read_assets.v1',
|
||||
PLANETS_MANAGE_PLANETS = 'esi-planets.manage_planets.v1',
|
||||
FLEETS_READ_FLEET = 'esi-fleets.read_fleet.v1',
|
||||
FLEETS_WRITE_FLEET = 'esi-fleets.write_fleet.v1',
|
||||
UI_OPEN_WINDOW = 'esi-ui.open_window.v1',
|
||||
UI_WRITE_WAYPOINT = 'esi-ui.write_waypoint.v1',
|
||||
CHARACTERS_WRITE_CONTACTS = 'esi-characters.write_contacts.v1',
|
||||
FITTINGS_READ_FITTINGS = 'esi-fittings.read_fittings.v1',
|
||||
FITTINGS_WRITE_FITTINGS = 'esi-fittings.write_fittings.v1',
|
||||
MARKETS_STRUCTURE_MARKETS = 'esi-markets.structure_markets.v1',
|
||||
CORPORATIONS_READ_STRUCTURES = 'esi-corporations.read_structures.v1',
|
||||
CHARACTERS_READ_LOYALTY = 'esi-characters.read_loyalty.v1',
|
||||
CHARACTERS_READ_OPPORTUNITIES = 'esi-characters.read_opportunities.v1',
|
||||
CHARACTERS_READ_CHAT_CHANNELS = 'esi-characters.read_chat_channels.v1',
|
||||
CHARACTERS_READ_MEDALS = 'esi-characters.read_medals.v1',
|
||||
CHARACTERS_READ_STANDINGS = 'esi-characters.read_standings.v1',
|
||||
CHARACTERS_READ_AGENTS_RESEARCH = 'esi-characters.read_agents_research.v1',
|
||||
INDUSTRY_READ_CHARACTER_JOBS = 'esi-industry.read_character_jobs.v1',
|
||||
MARKETS_READ_CHARACTER_ORDERS = 'esi-markets.read_character_orders.v1',
|
||||
CHARACTERS_READ_BLUEPRINTS = 'esi-characters.read_blueprints.v1',
|
||||
CHARACTERS_READ_CORPORATION_ROLES = 'esi-characters.read_corporation_roles.v1',
|
||||
LOCATION_READ_ONLINE = 'esi-location.read_online.v1',
|
||||
CONTRACTS_READ_CHARACTER_CONTRACTS = 'esi-contracts.read_character_contracts.v1',
|
||||
CLONES_READ_IMPLANTS = 'esi-clones.read_implants.v1',
|
||||
CHARACTERS_READ_FATIGUE = 'esi-characters.read_fatigue.v1',
|
||||
KILLMAILS_READ_CORPORATION_KILLMAILS = 'esi-killmails.read_corporation_killmails.v1',
|
||||
CORPORATIONS_TRACK_MEMBERS = 'esi-corporations.track_members.v1',
|
||||
WALLET_READ_CORPORATION_WALLETS = 'esi-wallet.read_corporation_wallets.v1',
|
||||
CHARACTERS_READ_NOTIFICATIONS = 'esi-characters.read_notifications.v1',
|
||||
CORPORATIONS_READ_DIVISIONS = 'esi-corporations.read_divisions.v1',
|
||||
CORPORATIONS_READ_CONTACTS = 'esi-corporations.read_contacts.v1',
|
||||
ASSETS_READ_CORPORATION_ASSETS = 'esi-assets.read_corporation_assets.v1',
|
||||
CORPORATIONS_READ_TITLES = 'esi-corporations.read_titles.v1',
|
||||
CORPORATIONS_READ_BLUEPRINTS = 'esi-corporations.read_blueprints.v1',
|
||||
CONTRACTS_READ_CORPORATION_CONTRACTS = 'esi-contracts.read_corporation_contracts.v1',
|
||||
CORPORATIONS_READ_STANDINGS = 'esi-corporations.read_standings.v1',
|
||||
CORPORATIONS_READ_STARBASES = 'esi-corporations.read_starbases.v1',
|
||||
INDUSTRY_READ_CORPORATION_JOBS = 'esi-industry.read_corporation_jobs.v1',
|
||||
MARKETS_READ_CORPORATION_ORDERS = 'esi-markets.read_corporation_orders.v1',
|
||||
CORPORATIONS_READ_CONTAINER_LOGS = 'esi-corporations.read_container_logs.v1',
|
||||
INDUSTRY_READ_CHARACTER_MINING = 'esi-industry.read_character_mining.v1',
|
||||
INDUSTRY_READ_CORPORATION_MINING = 'esi-industry.read_corporation_mining.v1',
|
||||
PLANETS_READ_CUSTOMS_OFFICES = 'esi-planets.read_customs_offices.v1',
|
||||
CORPORATIONS_READ_FACILITIES = 'esi-corporations.read_facilities.v1',
|
||||
CORPORATIONS_READ_MEDALS = 'esi-corporations.read_medals.v1',
|
||||
CHARACTERS_READ_TITLES = 'esi-characters.read_titles.v1',
|
||||
ALLIANCES_READ_CONTACTS = 'esi-alliances.read_contacts.v1',
|
||||
CHARACTERS_READ_FW_STATS = 'esi-characters.read_fw_stats.v1',
|
||||
CORPORATIONS_READ_FW_STATS = 'esi-corporations.read_fw_stats.v1',
|
||||
}
|
||||
|
||||
export function tokenHasScopes(access_token: string, ...scopes: string[]) {
|
||||
let tokenScopes = getScopesFromToken(access_token);
|
||||
return scopes.every((scope) => tokenScopes.includes(scope));
|
||||
}
|
||||
|
||||
export function getScopesFromToken(access_token: string) {
|
||||
const decoded = jwtDecode(access_token) as { scp: string[] | string; };
|
||||
return typeof decoded.scp === 'string' ? [decoded.scp] : decoded.scp;
|
||||
}
|
||||
66
packages/eve/src/esi/skills.ts
Normal file
66
packages/eve/src/esi/skills.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { CharacterHelper, type Character } from '@/db/models';
|
||||
import { esiFetch } from './fetch';
|
||||
|
||||
export interface CharacterAttributes {
|
||||
charisma: number;
|
||||
intelligence: number;
|
||||
memory: number;
|
||||
perception: number;
|
||||
willpower: number;
|
||||
last_remap_date?: string;
|
||||
bonus_remaps?: number;
|
||||
accrued_remap_cooldown_date?: string;
|
||||
}
|
||||
|
||||
// required scope: esi-skills.read_skills.v1
|
||||
export function getCharacterAttributes(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-skills.read_skills.v1')) return null;
|
||||
return esiFetch<CharacterAttributes>(`/characters/${character.eveID}/attributes`, character);
|
||||
}
|
||||
|
||||
export interface SkillQueueItem {
|
||||
finish_date?: string;
|
||||
finished_level: number;
|
||||
level_end_sp?: number;
|
||||
level_start_sp?: number;
|
||||
queue_position: number;
|
||||
skill_id: number;
|
||||
start_date?: string;
|
||||
training_start_sp?: number;
|
||||
}
|
||||
|
||||
// required scope: esi-skills.read_skillqueue.v1
|
||||
export function getCharacterSkillQueue(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-skills.read_skillqueue.v1')) return null;
|
||||
return esiFetch<SkillQueueItem[]>(`/characters/${character.eveID}/skillqueue`, character);
|
||||
}
|
||||
|
||||
export interface APISkill {
|
||||
active_skill_level: number;
|
||||
skill_id: number;
|
||||
skillpoints_in_skill: number;
|
||||
trained_skill_level: number;
|
||||
}
|
||||
|
||||
export interface CharacterSkills {
|
||||
skills: APISkill[]; // max 1000
|
||||
total_sp: number;
|
||||
unallocated_sp?: number;
|
||||
}
|
||||
|
||||
// required scope: esi-skills.read_skills.v1
|
||||
export function getCharacterSkills(character: Character) {
|
||||
if (!CharacterHelper.hasScope(character, 'esi-skills.read_skills.v1')) return null;
|
||||
return esiFetch<CharacterSkills>(`/characters/${character.eveID}/skills`, character);
|
||||
}
|
||||
|
||||
export function calculateTrainingPercentage(queuedSkill: SkillQueueItem) {
|
||||
// percentage in when training started
|
||||
const trainingStartPosition = (queuedSkill.training_start_sp! - queuedSkill.level_start_sp!) / queuedSkill.level_end_sp!;
|
||||
// percentage completed between start and now
|
||||
const timePosition =
|
||||
(new Date().getTime() - new Date(queuedSkill.start_date!).getTime()) /
|
||||
(new Date(queuedSkill.finish_date!).getTime() - new Date(queuedSkill.start_date!).getTime());
|
||||
// percentage completed
|
||||
return trainingStartPosition + (1 - trainingStartPosition) * timePosition;
|
||||
}
|
||||
4
packages/eve/src/index.ts
Normal file
4
packages/eve/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './esi/index';
|
||||
export * from './db';
|
||||
export * from './ref';
|
||||
export * from './third-party';
|
||||
130
packages/eve/src/models/attribute.ts
Normal file
130
packages/eve/src/models/attribute.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Attribute {
|
||||
readonly attribute_id: number;
|
||||
readonly category_id: number;
|
||||
readonly data_type: number;
|
||||
readonly default_value: number;
|
||||
readonly description: LocalizedString;
|
||||
readonly high_is_good: boolean;
|
||||
readonly icon_id?: number;
|
||||
readonly name: string;
|
||||
readonly published: boolean;
|
||||
readonly stackable: boolean;
|
||||
readonly unit_id?: number;
|
||||
readonly display_name: LocalizedString;
|
||||
readonly tooltip_title?: LocalizedString;
|
||||
readonly tooltip_description?: LocalizedString;
|
||||
}
|
||||
|
||||
export const getAttribute = (id: number) => {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.dogma_attributes[String(id)];
|
||||
if (!data) throw new Error(`Attribute ID ${id} not found in reference data`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export enum CommonAttribute {
|
||||
// Structure
|
||||
StructureHitpoints = 9,
|
||||
CargoCapacity = 38,
|
||||
DroneCapacity = 283,
|
||||
DroneBandwidth = 1271,
|
||||
Mass = 4,
|
||||
Volume = 161,
|
||||
InertiaModifier = 70,
|
||||
StructureEMResistance = 113,
|
||||
StructureThermalResistance = 110,
|
||||
StructureKineticResistance = 109,
|
||||
StructureExplosiveResistance = 111,
|
||||
|
||||
// Armor
|
||||
ArmorHitpoints = 265,
|
||||
ArmorEMResistance = 267,
|
||||
ArmorThermalResistance = 270,
|
||||
ArmorKineticResistance = 269,
|
||||
ArmorExplosiveResistance = 268,
|
||||
|
||||
// Shield
|
||||
ShieldCapacity = 263,
|
||||
ShieldRechargeTime = 479,
|
||||
ShieldEMResistance = 271,
|
||||
ShieldThermalResistance = 274,
|
||||
ShieldKineticResistance = 273,
|
||||
ShieldExplosiveResistance = 272,
|
||||
|
||||
// Electronic Resistances
|
||||
CapacitorWarfareResistance = 2045,
|
||||
StasisWebifierResistance = 2115,
|
||||
WeaponDisruptionResistance = 2113,
|
||||
|
||||
// Capacitor
|
||||
CapacitorCapacity = 482,
|
||||
CapacitorRechargeTime = 55,
|
||||
|
||||
// Targeting
|
||||
MaxTargetRange = 76,
|
||||
MaxLockedTargets = 192,
|
||||
SignatureRadius = 552,
|
||||
ScanResolution = 564,
|
||||
RadarSensorStrength = 208,
|
||||
MagnetometricSensorStrength = 210,
|
||||
GravimetricSensorStrength = 211,
|
||||
LadarSensorStrength = 209,
|
||||
|
||||
// Jump Drive Systems
|
||||
HasJumpDrive = 861,
|
||||
JumpDriveCapacitorNeed = 898,
|
||||
MaxJumpRange = 867,
|
||||
JumpDriveFuelNeed = 866,
|
||||
JumpDriveConsumptionAmount = 868,
|
||||
FuelBayCapacity = 1549,
|
||||
ConduitJumpConsumptionAmount = 3131,
|
||||
COnduitJumpPassengerCapacity = 3133,
|
||||
|
||||
// Propulsion
|
||||
MaxVelocity = 37,
|
||||
WarpSpeed = 600,
|
||||
|
||||
// FITTING
|
||||
|
||||
// Slots
|
||||
HighSlots = 14,
|
||||
MediumSlots = 13,
|
||||
LowSlots = 12,
|
||||
|
||||
// Stats
|
||||
PowergridOutput = 11,
|
||||
CPUOutput = 48,
|
||||
TurretHardpoints = 102,
|
||||
LauncherHardpoints = 101,
|
||||
|
||||
// Rigging
|
||||
RigSlots = 1137,
|
||||
RigSize = 1547,
|
||||
Calibration = 1132,
|
||||
|
||||
// Module
|
||||
CPUUsage = 50,
|
||||
PowergridUsage = 30,
|
||||
ActivationCost = 6,
|
||||
|
||||
// EWAR
|
||||
MaxVelocityBonus = 20,
|
||||
WarpScrambleStrength = 105,
|
||||
WarpDisruptionStrength = 2425,
|
||||
WarpDisruptionRange = 103,
|
||||
|
||||
// Weapon
|
||||
DamageMultiplier = 64,
|
||||
AccuracyFalloff = 158,
|
||||
OptimalRange = 54,
|
||||
RateOfFire = 51,
|
||||
TrackingSpeed = 160,
|
||||
ReloadTime = 1795,
|
||||
ActivationTime = 73,
|
||||
UsedWithCharge1 = 604,
|
||||
UsedWithCharge2 = 605,
|
||||
ChargeSize = 128,
|
||||
}
|
||||
105
packages/eve/src/models/blueprint.ts
Normal file
105
packages/eve/src/models/blueprint.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { ActivityType, type TypeIDQuantity } from './shared-types';
|
||||
import type { Type } from './type';
|
||||
import { getType } from './type';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Activity {
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface ManufacturingActivity extends Activity {
|
||||
time: number;
|
||||
materials: { [type_id: string]: TypeIDQuantity };
|
||||
products: { [type_id: string]: TypeIDQuantity };
|
||||
}
|
||||
|
||||
export interface InventionActivity extends Activity {
|
||||
time: number;
|
||||
materials: { [type_id: string]: TypeIDQuantity };
|
||||
products: { [type_id: string]: TypeIDQuantity };
|
||||
skills: { [skill_type_id: string]: number }; // skill_type_id : level
|
||||
}
|
||||
|
||||
export interface TypeQuantity {
|
||||
type: Type;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface Blueprint {
|
||||
readonly blueprint_type_id: number;
|
||||
readonly max_production_limit: number;
|
||||
readonly activities: {
|
||||
[ActivityType.MANUFACTURING]?: ManufacturingActivity;
|
||||
[ActivityType.RESEARCH_MATERIAL]?: Activity;
|
||||
[ActivityType.RESEARCH_TIME]?: Activity;
|
||||
[ActivityType.COPYING]?: Activity;
|
||||
[ActivityType.INVENTION]?: InventionActivity;
|
||||
};
|
||||
}
|
||||
|
||||
export function getBlueprint(blueprint_type_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.blueprints[String(blueprint_type_id)];
|
||||
if (!data) throw new Error(`Blueprint Type ID ${blueprint_type_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getManufacturingMaterials(blueprint: Blueprint) {
|
||||
const manufacturing = blueprint.activities[ActivityType.MANUFACTURING];
|
||||
if (!manufacturing) return [];
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(manufacturing.materials).map(([type_id, { quantity }]) => ({
|
||||
type: getType(parseInt(type_id)),
|
||||
quantity,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
export function getManufacturingProducts(blueprint: Blueprint) {
|
||||
const manufacturing = blueprint.activities[ActivityType.MANUFACTURING];
|
||||
if (!manufacturing) return [];
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(manufacturing.products).map(([type_id, { quantity }]) => ({
|
||||
type: getType(parseInt(type_id)),
|
||||
quantity,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
export function getInventionMaterials(blueprint: Blueprint) {
|
||||
const invention = blueprint.activities[ActivityType.INVENTION];
|
||||
if (!invention) return [];
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(invention.materials).map(([type_id, { quantity }]) => ({
|
||||
type: getType(parseInt(type_id)),
|
||||
quantity,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
export function getInventionProducts(blueprint: Blueprint) {
|
||||
const invention = blueprint.activities[ActivityType.INVENTION];
|
||||
if (!invention) return [];
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(invention.products).map(([type_id, { quantity }]) => ({
|
||||
type: getType(parseInt(type_id)),
|
||||
quantity,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
export function getInventionSkills(blueprint: Blueprint) {
|
||||
const invention = blueprint.activities[ActivityType.INVENTION];
|
||||
if (!invention) return [];
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(invention.skills).map(([skill_type_id, level]) => ({
|
||||
type: getType(parseInt(skill_type_id)),
|
||||
level,
|
||||
})),
|
||||
);
|
||||
}
|
||||
35
packages/eve/src/models/category.ts
Normal file
35
packages/eve/src/models/category.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export enum CommonCategory {
|
||||
CARGO = 5,
|
||||
SHIP = 6,
|
||||
MODULE = 7,
|
||||
CHARGE = 8,
|
||||
BLUEPRINT = 9,
|
||||
SKILL = 16,
|
||||
DRONE = 18,
|
||||
IMPLANT = 20,
|
||||
APPAREL = 30,
|
||||
DEPLOYABLE = 22,
|
||||
REACTION = 24,
|
||||
SUBSYSTEM = 32,
|
||||
STRUCTURE = 65,
|
||||
STRUCTURE_MODULE = 66,
|
||||
FIGHTER = 87,
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
readonly category_id: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly published: boolean;
|
||||
readonly group_ids: number[];
|
||||
readonly icon_id?: number;
|
||||
}
|
||||
|
||||
export function getCategory(category_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.categories[String(category_id)];
|
||||
if (!data) throw new Error(`Category ID ${category_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
65
packages/eve/src/models/effect.ts
Normal file
65
packages/eve/src/models/effect.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { getAttribute } from './attribute';
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
interface Modifier {
|
||||
domain: number;
|
||||
func: number;
|
||||
group_id?: number;
|
||||
modified_attribute_id: number;
|
||||
modifying_attribute_id: number;
|
||||
skill_type_id?: number;
|
||||
operator: number;
|
||||
}
|
||||
|
||||
export interface Effect {
|
||||
readonly effect_id: number;
|
||||
readonly disallow_auto_repeat: boolean;
|
||||
readonly discharge_attribute_id?: number;
|
||||
readonly distribution?: number;
|
||||
readonly duration_attribute_id?: number;
|
||||
readonly effect_category: number;
|
||||
readonly effect_name: string;
|
||||
readonly electronic_chance: boolean;
|
||||
readonly falloff_attribute_id?: number;
|
||||
readonly guid: string;
|
||||
readonly is_assistance: boolean;
|
||||
readonly is_offensive: boolean;
|
||||
readonly is_warp_safe: boolean;
|
||||
readonly propulsion_chance: boolean;
|
||||
readonly published: boolean;
|
||||
readonly range_attribute_id?: number;
|
||||
readonly range_chance: boolean;
|
||||
readonly modifiers: Modifier[];
|
||||
readonly tracking_speed_attribute_id?: number;
|
||||
readonly description: LocalizedString;
|
||||
readonly display_name: LocalizedString;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
export function getEffect(effect_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.dogma_effects[String(effect_id)];
|
||||
if (!data) throw new Error(`Effect ID ${effect_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getDischargeAttribute(effect: Effect) {
|
||||
return effect.discharge_attribute_id && getAttribute(effect.discharge_attribute_id);
|
||||
}
|
||||
|
||||
export function getFalloffAttribute(effect: Effect) {
|
||||
return effect.falloff_attribute_id && getAttribute(effect.falloff_attribute_id);
|
||||
}
|
||||
|
||||
export function getDurationAttribute(effect: Effect) {
|
||||
return effect.duration_attribute_id && getAttribute(effect.duration_attribute_id);
|
||||
}
|
||||
|
||||
export function getRangeAttribute(effect: Effect) {
|
||||
return effect.range_attribute_id && getAttribute(effect.range_attribute_id);
|
||||
}
|
||||
|
||||
export function getTrackingSpeedAttribute(effect: Effect) {
|
||||
return effect.tracking_speed_attribute_id && getAttribute(effect.tracking_speed_attribute_id);
|
||||
}
|
||||
29
packages/eve/src/models/group.ts
Normal file
29
packages/eve/src/models/group.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Group {
|
||||
readonly group_id: number;
|
||||
readonly category_id: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly published: boolean;
|
||||
readonly icon_id?: number;
|
||||
readonly anchorable: boolean;
|
||||
readonly anchored: boolean;
|
||||
readonly fittable_non_singleton: boolean;
|
||||
readonly use_base_price: boolean;
|
||||
readonly type_ids?: number[];
|
||||
}
|
||||
export function getGroup(group_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.groups[String(group_id)];
|
||||
if (!data) throw new Error(`Group ID ${group_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function groupEveRefLink(group_id: number) {
|
||||
return `https://everef.net/groups/${group_id}`;
|
||||
}
|
||||
|
||||
export function renderGroupEveRefLink(group: Group, locale: string = 'en') {
|
||||
return `[${group.name[locale] ?? group.name.en}](${groupEveRefLink(group.group_id)})`;
|
||||
}
|
||||
41
packages/eve/src/models/icon.ts
Normal file
41
packages/eve/src/models/icon.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export enum IconSize {
|
||||
SIZE_32 = 32,
|
||||
SIZE_64 = 64,
|
||||
SIZE_128 = 128,
|
||||
SIZE_256 = 256,
|
||||
SIZE_512 = 512,
|
||||
}
|
||||
|
||||
export interface Icon {
|
||||
readonly icon_id: number;
|
||||
readonly description: string;
|
||||
readonly file: string;
|
||||
}
|
||||
|
||||
export function getIcon(icon_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.icons[String(icon_id)];
|
||||
if (!data) throw new Error(`Icon ID ${icon_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getIconUrl(
|
||||
icon_id: Icon,
|
||||
{
|
||||
size = IconSize.SIZE_64,
|
||||
isBp = false,
|
||||
isBpc = false,
|
||||
}: {
|
||||
size?: IconSize;
|
||||
isBp?: boolean;
|
||||
isBpc?: boolean;
|
||||
} = {},
|
||||
): string {
|
||||
return `https://images.evetech.net/types/${icon_id}/icon${
|
||||
isBp ? '/bp'
|
||||
: isBpc ? '/bpc'
|
||||
: ''
|
||||
}?size=${size}`;
|
||||
}
|
||||
14
packages/eve/src/models/index.ts
Normal file
14
packages/eve/src/models/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export * from './attribute';
|
||||
export * from './blueprint';
|
||||
export * from './category';
|
||||
export * from './effect';
|
||||
export * from './group';
|
||||
export * from './icon';
|
||||
export * from './market-group';
|
||||
export * from './meta-group';
|
||||
export * from './region';
|
||||
export * from './schematic';
|
||||
export * from './skill';
|
||||
export * from './solar-system';
|
||||
export * from './type';
|
||||
export * from './unit';
|
||||
53
packages/eve/src/models/loadModels.ts
Normal file
53
packages/eve/src/models/loadModels.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import fs from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import type { Unit } from './unit';
|
||||
import type { SolarSystem } from './solar-system';
|
||||
import type { Attribute } from './attribute';
|
||||
import type { Blueprint } from './blueprint';
|
||||
import type { Category } from './category';
|
||||
import type { Effect } from './effect';
|
||||
import type { Group } from './group';
|
||||
import type { Icon } from './icon';
|
||||
import type { MarketGroup } from './market-group';
|
||||
import type { MetaGroup } from './meta-group';
|
||||
import type { Region } from './region';
|
||||
import type { Schematic } from './schematic';
|
||||
import type { Skill } from './skill';
|
||||
import type { Type } from './type';
|
||||
|
||||
const dataSets = {
|
||||
loaded: false,
|
||||
dogma_attributes: {} as Record<string, Attribute>,
|
||||
blueprints: {} as Record<string, Blueprint>,
|
||||
categories: {} as Record<string, Category>,
|
||||
dogma_effects: {} as Record<string, Effect>,
|
||||
groups: {} as Record<string, Group>,
|
||||
icons: {} as Record<string, Icon>,
|
||||
market_groups: {} as Record<string, MarketGroup>,
|
||||
meta_groups: {} as Record<string, MetaGroup>,
|
||||
regions: {} as Record<string, Region>,
|
||||
schematics: {} as Record<string, Schematic>,
|
||||
skills: {} as Record<string, Skill>,
|
||||
solar_systems: {} as Record<string, SolarSystem>,
|
||||
types: {} as Record<string, Type>,
|
||||
units: {} as Record<string, Unit>,
|
||||
};
|
||||
export async function loadModels() {
|
||||
dataSets.dogma_attributes = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/dogma_attributes.json')).toString());
|
||||
dataSets.blueprints = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/blueprints.json')).toString());
|
||||
dataSets.categories = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/categories.json')).toString());
|
||||
dataSets.dogma_effects = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/dogma_effects.json')).toString());
|
||||
dataSets.groups = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/groups.json')).toString());
|
||||
dataSets.icons = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/icons.json')).toString());
|
||||
dataSets.market_groups = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/market_groups.json')).toString());
|
||||
dataSets.meta_groups = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/meta_groups.json')).toString());
|
||||
dataSets.regions = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/regions.json')).toString());
|
||||
dataSets.schematics = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/schematics.json')).toString());
|
||||
dataSets.skills = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/skills.json')).toString());
|
||||
dataSets.solar_systems = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/solar_systems.json')).toString());
|
||||
dataSets.types = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/types.json')).toString());
|
||||
dataSets.units = JSON.parse(fs.readFileSync(join(__dirname, '../../data/reference-data/units.json')).toString());
|
||||
dataSets.loaded = true;
|
||||
}
|
||||
|
||||
export { dataSets };
|
||||
24
packages/eve/src/models/market-group.ts
Normal file
24
packages/eve/src/models/market-group.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface MarketGroup {
|
||||
readonly market_group_id: number;
|
||||
readonly parent_group_id: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly description: LocalizedString;
|
||||
readonly child_market_group_ids: number[];
|
||||
readonly icon_id: number;
|
||||
readonly has_types: boolean;
|
||||
}
|
||||
|
||||
export function getMarketGroup(market_group_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.market_groups[String(market_group_id)];
|
||||
if (!data) throw new Error(`Market group ID ${market_group_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getAllChildMarketGroups(marketGroup: MarketGroup): Promise<MarketGroup[]> {
|
||||
const children = await Promise.all(marketGroup.child_market_group_ids.map((id) => getMarketGroup(id)));
|
||||
return children.concat(...(await Promise.all(children.map((child) => getAllChildMarketGroups(child)))));
|
||||
}
|
||||
17
packages/eve/src/models/meta-group.ts
Normal file
17
packages/eve/src/models/meta-group.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface MetaGroup {
|
||||
readonly meta_group_id: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly type_ids: number[];
|
||||
readonly icon_id?: number;
|
||||
readonly icon_suffix?: string;
|
||||
}
|
||||
|
||||
export function getMetaGroup(meta_group_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.meta_groups[String(meta_group_id)];
|
||||
if (!data) throw new Error(`Meta group ID ${meta_group_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
24
packages/eve/src/models/region.ts
Normal file
24
packages/eve/src/models/region.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { LocalizedString, Position } from './shared-types';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Region {
|
||||
readonly region_id: number;
|
||||
readonly center: Position;
|
||||
readonly description_id: number;
|
||||
readonly faction_id: number;
|
||||
readonly max: Position;
|
||||
readonly min: Position;
|
||||
readonly name_id: number;
|
||||
readonly wormhole_class_id?: number;
|
||||
readonly nebula_id?: number;
|
||||
readonly universe_id: string;
|
||||
readonly description: LocalizedString;
|
||||
readonly name: LocalizedString;
|
||||
}
|
||||
|
||||
export function getRegion(region_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.regions[String(region_id)];
|
||||
if (!data) throw new Error(`Region ID ${region_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
37
packages/eve/src/models/schematic.ts
Normal file
37
packages/eve/src/models/schematic.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { LocalizedString, TypeIDQuantity } from './shared-types';
|
||||
import { getType } from './type';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Schematic {
|
||||
readonly schematic_id: number;
|
||||
readonly cycle_time: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly materials: { [type_id: string]: TypeIDQuantity };
|
||||
readonly products: { [type_id: string]: TypeIDQuantity };
|
||||
readonly pin_type_ids: number[];
|
||||
}
|
||||
|
||||
export function getSchematic(schematic_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.schematics[String(schematic_id)];
|
||||
if (!data) throw new Error(`Schematic ID ${schematic_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getMaterialQuantities(schematic: Schematic) {
|
||||
return Object.entries(schematic.materials).map(([type_id, { quantity }]) => ({
|
||||
type: getType(Number(type_id)),
|
||||
quantity,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getProductQuantities(schematic: Schematic) {
|
||||
return Object.entries(schematic.products).map(([type_id, { quantity }]) => ({
|
||||
type: getType(Number(type_id)),
|
||||
quantity,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getPinTypes(schematic: Schematic) {
|
||||
return schematic.pin_type_ids.map(getType);
|
||||
}
|
||||
56
packages/eve/src/models/shared-types.ts
Normal file
56
packages/eve/src/models/shared-types.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { Type } from './type';
|
||||
|
||||
export interface LocalizedString {
|
||||
de?: string;
|
||||
en?: string;
|
||||
es?: string;
|
||||
fr?: string;
|
||||
ja?: string;
|
||||
ko?: string;
|
||||
ru?: string;
|
||||
zh?: string;
|
||||
}
|
||||
|
||||
export interface TypeIDQuantity {
|
||||
type_id: number;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface TypeQuantity {
|
||||
type: Type;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface AttributeIDValue {
|
||||
attribute_id: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface EffectIDDefault {
|
||||
effect_id: number;
|
||||
is_default: boolean;
|
||||
}
|
||||
|
||||
export interface MaterialIDQuantity {
|
||||
material_type_id: number;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface BlueprintTypeIDActivity {
|
||||
blueprint_type_id: number;
|
||||
blueprint_activity: ActivityType;
|
||||
}
|
||||
|
||||
export enum ActivityType {
|
||||
MANUFACTURING = 'manufacturing',
|
||||
RESEARCH_MATERIAL = 'research_material',
|
||||
RESEARCH_TIME = 'research_time',
|
||||
COPYING = 'copying',
|
||||
INVENTION = 'invention',
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
47
packages/eve/src/models/skill.ts
Normal file
47
packages/eve/src/models/skill.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { type Attribute, getAttribute } from './attribute';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface Skill {
|
||||
readonly type_id: number;
|
||||
readonly primary_dogma_attribute_id: number;
|
||||
readonly secondary_dogma_attribute_id: number;
|
||||
readonly primary_character_attribute_id: number;
|
||||
readonly secondary_character_attribute_id: number;
|
||||
readonly training_time_multiplier: number;
|
||||
readonly required_skills?: { [skill_type_id: string]: number }; // skill_type_id : level
|
||||
}
|
||||
|
||||
export function getSkill(type_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.skills[String(type_id)];
|
||||
if (!data) throw new Error(`Skill ID ${type_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getPrimaryDogmaAttribute(skill: Skill) {
|
||||
return getAttribute(skill.primary_dogma_attribute_id);
|
||||
}
|
||||
|
||||
export function getSecondaryDogmaAttribute(skill: Skill) {
|
||||
return getAttribute(skill.secondary_dogma_attribute_id);
|
||||
}
|
||||
|
||||
export function getPrimaryCharacterAttribute(skill: Skill) {
|
||||
return getAttribute(skill.primary_character_attribute_id);
|
||||
}
|
||||
|
||||
export function getSecondaryCharacterAttribute(skill: Skill) {
|
||||
return getAttribute(skill.secondary_character_attribute_id);
|
||||
}
|
||||
|
||||
export function getPrerequisites(skill: Skill): { skill: Skill; level: number }[] {
|
||||
if (!skill.required_skills) return [];
|
||||
return Object.entries(skill.required_skills).map(([skill_type_id, level]) => ({
|
||||
skill: getSkill(parseInt(skill_type_id)),
|
||||
level,
|
||||
}));
|
||||
}
|
||||
|
||||
export function skillpointsAtLevel(skill: Skill, level: number): number {
|
||||
return Math.pow(2, 2.5 * (level - 1)) * 250 * skill.training_time_multiplier;
|
||||
}
|
||||
42
packages/eve/src/models/solar-system.ts
Normal file
42
packages/eve/src/models/solar-system.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
export interface SolarSystem {
|
||||
readonly regionID: number;
|
||||
readonly constellationID: number;
|
||||
readonly solarSystemID: number;
|
||||
readonly solarSystemName: string;
|
||||
readonly x: number;
|
||||
readonly y: number;
|
||||
readonly z: number;
|
||||
readonly xMin: number;
|
||||
readonly xMax: number;
|
||||
readonly yMin: number;
|
||||
readonly yMax: number;
|
||||
readonly zMin: number;
|
||||
readonly zMax: number;
|
||||
readonly luminosity: number;
|
||||
readonly border: boolean;
|
||||
readonly fringe: boolean;
|
||||
readonly corridor: boolean;
|
||||
readonly hub: boolean;
|
||||
readonly international: boolean;
|
||||
readonly regional: boolean;
|
||||
readonly security: number;
|
||||
readonly factionID: number;
|
||||
readonly radius: number;
|
||||
readonly sunTypeID: number;
|
||||
readonly securityClass: string;
|
||||
}
|
||||
|
||||
export function getSolarSystem(solarSystemID: number): SolarSystem {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.solar_systems[String(solarSystemID)] as any;
|
||||
if (!data) throw new Error(`Solar System ID ${solarSystemID} not found in reference data`);
|
||||
return {
|
||||
...data,
|
||||
security: parseFloat(data.security),
|
||||
radius: parseFloat(data.radius),
|
||||
sunTypeID: parseInt(data.sun_type_id, 10),
|
||||
securityClass: data.security_class ?? 'nullsec', // Default to 'nullsec' if security_class is not present
|
||||
};
|
||||
}
|
||||
197
packages/eve/src/models/type.ts
Normal file
197
packages/eve/src/models/type.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import type { AttributeIDValue, BlueprintTypeIDActivity, EffectIDDefault, LocalizedString, MaterialIDQuantity } from './shared-types';
|
||||
import { IconSize } from './icon';
|
||||
import { getUnit, type Unit } from './unit';
|
||||
import { CommonAttribute, getAttribute } from './attribute';
|
||||
import { getGroup } from './group';
|
||||
import { getMetaGroup } from './meta-group';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
interface Masteries {
|
||||
'0': number[];
|
||||
'1': number[];
|
||||
'2': number[];
|
||||
'3': number[];
|
||||
'4': number[];
|
||||
}
|
||||
|
||||
interface Bonus {
|
||||
bonus: number;
|
||||
bonus_text: LocalizedString;
|
||||
importance: number;
|
||||
unit_id: number;
|
||||
}
|
||||
|
||||
interface Traits {
|
||||
misc_bonuses: { [level: string]: Bonus };
|
||||
role_bonuses: { [level: string]: Bonus };
|
||||
types: { [skill_type_id: string]: { [order: string]: Bonus } };
|
||||
}
|
||||
|
||||
export interface Type {
|
||||
readonly type_id: number;
|
||||
readonly name: LocalizedString;
|
||||
readonly description: LocalizedString;
|
||||
readonly published: boolean;
|
||||
readonly group_id?: number;
|
||||
readonly base_price?: number;
|
||||
readonly capacity?: number;
|
||||
readonly faction_id?: number;
|
||||
readonly graphic_id?: number;
|
||||
readonly market_group_id?: number;
|
||||
readonly mass?: number;
|
||||
readonly masteries?: Masteries;
|
||||
readonly meta_group_id?: number;
|
||||
readonly portion_size?: number;
|
||||
readonly race_id?: number;
|
||||
readonly radius?: number;
|
||||
readonly sof_faction_name?: string;
|
||||
readonly sound_id?: number;
|
||||
readonly traits?: Traits;
|
||||
readonly volume?: number;
|
||||
readonly dogma_attributes?: {
|
||||
[attribute_id: string]: AttributeIDValue;
|
||||
};
|
||||
readonly dogma_effects?: { [effect_id: string]: EffectIDDefault };
|
||||
readonly packaged_volume?: number;
|
||||
readonly type_materials?: { [type_id: string]: MaterialIDQuantity };
|
||||
readonly required_skills?: { [skill_type_id: string]: number }; // skill_type_id : level
|
||||
readonly type_variations?: { [meta_group_id: string]: number[] }; // meta_group_id : type_ids[]
|
||||
readonly produced_by_blueprints?: {
|
||||
[blueprint_type_id: string]: BlueprintTypeIDActivity;
|
||||
}; // blueprint_type_id : blueprint_activity
|
||||
readonly buildable_pin_type_ids?: number[];
|
||||
readonly is_ore?: boolean;
|
||||
readonly ore_variations?: { [variant: string]: number }; // variant : type_id
|
||||
readonly produced_by_schematic_ids?: number[];
|
||||
readonly used_by_schematic_ids?: number[];
|
||||
readonly is_blueprint?: boolean;
|
||||
}
|
||||
|
||||
export function getType(type_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const data = dataSets.types[String(type_id)];
|
||||
if (!data) throw new Error(`Type ID ${type_id} not found in reference data`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getTypeIconUrl(type: Type, size: IconSize = IconSize.SIZE_64) {
|
||||
return `https://images.evetech.net/types/${type.type_id}/icon${type.is_blueprint ? '/bp' : ''}?size=${size}`;
|
||||
}
|
||||
|
||||
export function getSkillBonuses(type: Type): {
|
||||
skill: Type;
|
||||
bonuses: {
|
||||
bonus: number;
|
||||
bonus_text: LocalizedString;
|
||||
importance: number;
|
||||
unit: Unit;
|
||||
}[];
|
||||
}[] {
|
||||
if (!type.traits) return [];
|
||||
const skillBonuses: {
|
||||
skill: Type;
|
||||
bonuses: {
|
||||
bonus: number;
|
||||
bonus_text: LocalizedString;
|
||||
importance: number;
|
||||
unit: Unit;
|
||||
}[];
|
||||
}[] = [];
|
||||
for (const skill_type_id in type.traits.types) {
|
||||
skillBonuses.push({
|
||||
skill: getType(Number(skill_type_id)),
|
||||
bonuses: Object.keys(type.traits.types[skill_type_id]).map((order) => {
|
||||
const bonus = type.traits!.types[skill_type_id][order];
|
||||
return {
|
||||
bonus: bonus.bonus,
|
||||
bonus_text: bonus.bonus_text,
|
||||
importance: bonus.importance,
|
||||
unit: getUnit(bonus.unit_id),
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
return skillBonuses;
|
||||
}
|
||||
|
||||
export function getRoleBonuses(type: Type) {
|
||||
if (!type.traits || !type.traits.role_bonuses) return [];
|
||||
return Object.values(type.traits.role_bonuses).map((bonus) => ({
|
||||
bonus: bonus.bonus,
|
||||
bonus_text: bonus.bonus_text,
|
||||
importance: bonus.importance,
|
||||
unit: bonus.unit_id ? getUnit(bonus.unit_id) : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
export function eveRefLink(type_id: number) {
|
||||
return `https://everef.net/types/${type_id}`;
|
||||
}
|
||||
|
||||
export function renderTypeEveRefLink(type: Type, locale: string = 'en') {
|
||||
return `[${type.name[locale] ?? type.name.en}](${eveRefLink(type.type_id)})`;
|
||||
}
|
||||
|
||||
export function eveTycoonLink(type_id: number) {
|
||||
return `https://evetycoon.com/market/${type_id}`;
|
||||
}
|
||||
|
||||
export function getTypeAttributes(type: Type) {
|
||||
if (!type.dogma_attributes) return [];
|
||||
Object.keys(type.dogma_attributes).map((attribute_id) => ({
|
||||
attribute: getAttribute(Number(attribute_id)),
|
||||
value: type.dogma_attributes![attribute_id].value,
|
||||
}));
|
||||
}
|
||||
|
||||
export function typeHasAnyAttribute(type: Type, attribute_ids: CommonAttribute[]) {
|
||||
if (!type.dogma_attributes) return false;
|
||||
for (const attribute_id of attribute_ids) {
|
||||
if (type.dogma_attributes[attribute_id]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getTypeSkills(type: Type) {
|
||||
if (!type.required_skills) return [];
|
||||
return Object.keys(type.required_skills).map((skill_type_id) => ({
|
||||
skill: getType(Number(skill_type_id)),
|
||||
level: type.required_skills![skill_type_id],
|
||||
}));
|
||||
}
|
||||
|
||||
export function typeGetAttribute(type: Type, attribute_id: number) {
|
||||
if (!type.dogma_attributes || !type.dogma_attributes[attribute_id]) return null;
|
||||
return {
|
||||
attribute: getAttribute(attribute_id),
|
||||
value: type.dogma_attributes[attribute_id].value,
|
||||
};
|
||||
}
|
||||
|
||||
export function getTypeBlueprints(type: Type) {
|
||||
if (!type.produced_by_blueprints) return [];
|
||||
return Object.values(type.produced_by_blueprints).map((blueprint) => ({
|
||||
blueprint: getType(blueprint.blueprint_type_id),
|
||||
activity: blueprint.blueprint_activity,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getTypeSchematics(type: Type) {
|
||||
return type.produced_by_schematic_ids?.map((schematic_id) => getType(schematic_id)) ?? [];
|
||||
}
|
||||
|
||||
export function getTypeGroup(type: Type) {
|
||||
if (!type.group_id) return null;
|
||||
return getGroup(type.group_id);
|
||||
}
|
||||
|
||||
export function getTypeVariants(type: Type) {
|
||||
return Object.entries(type.type_variations || {}).map(([meta_group_id, variant_ids]) => ({
|
||||
metaGroup: getMetaGroup(Number(meta_group_id)),
|
||||
types: variant_ids.map((type_id) => getType(type_id)),
|
||||
}));
|
||||
}
|
||||
|
||||
export function typeHasAttributes(type: Type) {
|
||||
return type.dogma_attributes && Object.keys(type.dogma_attributes).length > 0;
|
||||
}
|
||||
406
packages/eve/src/models/unit.test.ts
Normal file
406
packages/eve/src/models/unit.test.ts
Normal file
@@ -0,0 +1,406 @@
|
||||
import { test, it, expect, mock, beforeEach } from 'bun:test';
|
||||
import { getUnit, renderUnit, isUnitInversePercentage, type Unit } from './unit';
|
||||
|
||||
test('unit.ts', () => {
|
||||
test('getUnit', () => {
|
||||
it('should return unit when found', async () => {
|
||||
const mockUnit: Unit = {
|
||||
unit_id: 123,
|
||||
display_name: 'Test Unit',
|
||||
description: { en: 'Test description' },
|
||||
name: { en: 'Test Name' },
|
||||
};
|
||||
|
||||
mock.module('@star-kitten/util/json-query.js', () => ({
|
||||
queryJsonObject: () => Promise.resolve(mockUnit),
|
||||
}));
|
||||
|
||||
const result = await getUnit(123);
|
||||
|
||||
expect(result).toEqual(mockUnit);
|
||||
});
|
||||
|
||||
it('should throw error when unit not found', async () => {
|
||||
mock.module('@star-kitten/util/json-query.js', () => ({
|
||||
queryJsonObject: () => Promise.resolve(null),
|
||||
}));
|
||||
|
||||
await expect(getUnit(999)).rejects.toThrow('Unit ID 999 not found in reference data');
|
||||
});
|
||||
});
|
||||
|
||||
test('renderUnit', () => {
|
||||
const mockUnit: Unit = {
|
||||
unit_id: 0,
|
||||
display_name: 'Test Unit',
|
||||
description: { en: 'Test description' },
|
||||
name: { en: 'Test Name' },
|
||||
};
|
||||
|
||||
test('inverse percentage units', () => {
|
||||
it('should render unit 108 as inverse percentage', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 108 };
|
||||
const result = await renderUnit(unit, 0.75);
|
||||
expect(result).toBe('0.25 Test Unit');
|
||||
});
|
||||
|
||||
it('should render unit 111 as inverse percentage', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 111 };
|
||||
const result = await renderUnit(unit, 0.3);
|
||||
expect(result).toBe('0.70 Test Unit');
|
||||
});
|
||||
|
||||
it('should handle missing display_name for inverse percentage', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 108, display_name: '' };
|
||||
const result = await renderUnit(unit, 0.5);
|
||||
expect(result).toBe('0.50 ');
|
||||
});
|
||||
});
|
||||
|
||||
test('time units', () => {
|
||||
it('should render unit 3 (seconds) using convertSecondsToTimeString', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 3 };
|
||||
mock.module('@star-kitten/util/text.js', () => ({
|
||||
convertSecondsToTimeString: (seconds: number) => {
|
||||
if (seconds === 330) return '5m 30s';
|
||||
return '0s';
|
||||
},
|
||||
}));
|
||||
|
||||
const result = await renderUnit(unit, 330);
|
||||
|
||||
expect(result).toBe('5m 30s');
|
||||
});
|
||||
|
||||
it('should render unit 101 (milliseconds) using convertMillisecondsToTimeString', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 101 };
|
||||
mock.module('@star-kitten/util/text.js', () => ({
|
||||
convertMillisecondsToTimeString: (milliseconds: number) => {
|
||||
if (milliseconds === 2500) return '2.5s';
|
||||
return '0s';
|
||||
},
|
||||
}));
|
||||
|
||||
const result = await renderUnit(unit, 2500);
|
||||
|
||||
expect(result).toBe('2.5s');
|
||||
});
|
||||
});
|
||||
|
||||
test('size class unit (117)', () => {
|
||||
it('should render size class 1 as Small', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 117 };
|
||||
const result = await renderUnit(unit, 1);
|
||||
expect(result).toBe('Small');
|
||||
});
|
||||
|
||||
it('should render size class 2 as Medium', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 117 };
|
||||
const result = await renderUnit(unit, 2);
|
||||
expect(result).toBe('Medium');
|
||||
});
|
||||
|
||||
it('should render size class 3 as Large', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 117 };
|
||||
const result = await renderUnit(unit, 3);
|
||||
expect(result).toBe('Large');
|
||||
});
|
||||
|
||||
it('should render size class 4 as X-Large', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 117 };
|
||||
const result = await renderUnit(unit, 4);
|
||||
expect(result).toBe('X-Large');
|
||||
});
|
||||
|
||||
it('should render unknown size class as Unknown', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 117 };
|
||||
const result = await renderUnit(unit, 99);
|
||||
expect(result).toBe('Unknown');
|
||||
});
|
||||
});
|
||||
|
||||
test('specialized units', () => {
|
||||
it('should render unit 141 (hardpoints) as string', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 141 };
|
||||
const result = await renderUnit(unit, 8);
|
||||
expect(result).toBe('8');
|
||||
});
|
||||
|
||||
it('should render unit 120 (calibration) with pts suffix', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 120 };
|
||||
const result = await renderUnit(unit, 400);
|
||||
expect(result).toBe('400 pts');
|
||||
});
|
||||
|
||||
it('should render unit 116 (typeID) using type lookup', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 116 };
|
||||
const mockType = {
|
||||
type_id: 12345,
|
||||
name: { en: 'Test Type' },
|
||||
description: { en: 'Test description' },
|
||||
published: true,
|
||||
};
|
||||
|
||||
mock.module('./type', () => ({
|
||||
getType: (type_id: number) => {
|
||||
if (type_id === 12345) return Promise.resolve(mockType);
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
renderTypeEveRefLink: (type: any, lang: string) => {
|
||||
if (type.type_id === 12345 && lang === 'en') return 'Type Link';
|
||||
return null;
|
||||
},
|
||||
}));
|
||||
|
||||
const result = await renderUnit(unit, 12345, 'en');
|
||||
|
||||
expect(result).toBe('Type Link');
|
||||
});
|
||||
|
||||
it('should render unit 116 (typeID) as Unknown when link is null', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 116 };
|
||||
const mockType = {
|
||||
type_id: 12345,
|
||||
name: { en: 'Test Type' },
|
||||
description: { en: 'Test description' },
|
||||
published: true,
|
||||
};
|
||||
|
||||
mock.module('./type', () => ({
|
||||
getType: (type_id: number) => {
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
renderTypeEveRefLink: (type: any, lang: string) => {
|
||||
return null;
|
||||
},
|
||||
}));
|
||||
|
||||
const result = await renderUnit(unit, 12345);
|
||||
|
||||
expect(result).toBe('Unknown');
|
||||
});
|
||||
|
||||
it('should render unit 115 (groupID) using group lookup', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 115 };
|
||||
const mockGroup = {
|
||||
group_id: 67890,
|
||||
name: { en: 'Test Group' },
|
||||
category_id: 1,
|
||||
published: true,
|
||||
anchorable: false,
|
||||
anchored: false,
|
||||
fittable_non_singleton: false,
|
||||
use_base_price: false,
|
||||
};
|
||||
|
||||
mock.module('./group', () => ({
|
||||
getGroup: () => Promise.resolve(mockGroup),
|
||||
renderGroupEveRefLink: (group: any, lang: string) => {
|
||||
if (group.group_id === 67890 && lang === 'fr') return 'Group Link';
|
||||
return null;
|
||||
},
|
||||
}));
|
||||
|
||||
const result = await renderUnit(unit, 67890, 'fr');
|
||||
|
||||
expect(result).toBe('Group Link');
|
||||
});
|
||||
|
||||
it('should render unit 115 (groupID) as Unknown when link is null', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 115 };
|
||||
const mockGroup = {
|
||||
group_id: 67890,
|
||||
name: { en: 'Test Group' },
|
||||
category_id: 1,
|
||||
published: true,
|
||||
anchorable: false,
|
||||
anchored: false,
|
||||
fittable_non_singleton: false,
|
||||
use_base_price: false,
|
||||
};
|
||||
|
||||
mock.module('./group', () => ({
|
||||
getGroup: (group_id: number) => {
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
renderGroupEveRefLink: (group: any, lang: string) => {
|
||||
return null;
|
||||
},
|
||||
}));
|
||||
|
||||
const result = await renderUnit(unit, 67890);
|
||||
|
||||
expect(result).toBe('Unknown');
|
||||
});
|
||||
});
|
||||
|
||||
test('physical units', () => {
|
||||
it('should render unit 10 (m/s)', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 10 };
|
||||
const result = await renderUnit(unit, 150);
|
||||
expect(result).toBe('150 m/s');
|
||||
});
|
||||
|
||||
it('should render unit 11 (m/s²)', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 11 };
|
||||
const result = await renderUnit(unit, 9.8);
|
||||
expect(result).toBe('9.8 m/s²');
|
||||
});
|
||||
|
||||
it('should render unit 9 (m³)', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 9 };
|
||||
const result = await renderUnit(unit, 1000);
|
||||
expect(result).toBe('1000 m³');
|
||||
});
|
||||
|
||||
it('should render unit 8 (m²)', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 8 };
|
||||
const result = await renderUnit(unit, 50);
|
||||
expect(result).toBe('50 m²');
|
||||
});
|
||||
|
||||
it('should render unit 12 (m⁻¹)', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 12 };
|
||||
const result = await renderUnit(unit, 0.1);
|
||||
expect(result).toBe('0.1 m⁻¹');
|
||||
});
|
||||
|
||||
it('should render unit 128 (Mbps)', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 128 };
|
||||
const result = await renderUnit(unit, 100);
|
||||
expect(result).toBe('100 Mbps');
|
||||
});
|
||||
});
|
||||
|
||||
test('default case', () => {
|
||||
it('should render unknown unit with value and display_name', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 999, display_name: 'Custom Unit' };
|
||||
const result = await renderUnit(unit, 42);
|
||||
expect(result).toBe('42 Custom Unit');
|
||||
});
|
||||
|
||||
it('should render unknown unit with empty display_name', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 999, display_name: '' };
|
||||
const result = await renderUnit(unit, 42);
|
||||
expect(result).toBe('42 ');
|
||||
});
|
||||
|
||||
it('should handle undefined display_name', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 999, display_name: undefined as any };
|
||||
const result = await renderUnit(unit, 42);
|
||||
expect(result).toBe('42 ');
|
||||
});
|
||||
});
|
||||
|
||||
test('edge cases', () => {
|
||||
it('should handle zero values', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 10 };
|
||||
const result = await renderUnit(unit, 0);
|
||||
expect(result).toBe('0 m/s');
|
||||
});
|
||||
|
||||
it('should handle negative values', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 120 };
|
||||
const result = await renderUnit(unit, -50);
|
||||
expect(result).toBe('-50 pts');
|
||||
});
|
||||
|
||||
it('should handle very large values', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 9 };
|
||||
const result = await renderUnit(unit, 1e10);
|
||||
expect(result).toBe('10000000000 m³');
|
||||
});
|
||||
|
||||
it('should handle decimal values for inverse percentage', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 108 };
|
||||
const result = await renderUnit(unit, 0.12345);
|
||||
expect(result).toBe('0.88 Test Unit');
|
||||
});
|
||||
|
||||
it('should default to "en" locale when not specified', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 116 };
|
||||
const mockType = {
|
||||
type_id: 12345,
|
||||
name: { en: 'Test Type' },
|
||||
description: { en: 'Test description' },
|
||||
published: true,
|
||||
};
|
||||
|
||||
mock.module('./type', () => ({
|
||||
getType: (type_id: number) => {
|
||||
if (type_id === 12345) return Promise.resolve(mockType);
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
renderTypeEveRefLink: (type: any, lang: string) => {
|
||||
if (type.type_id === 12345 && lang === 'en') return 'Type Link';
|
||||
return null;
|
||||
},
|
||||
}));
|
||||
|
||||
const result = await renderUnit(unit, 12345);
|
||||
expect(result).toBe('Type Link');
|
||||
});
|
||||
});
|
||||
|
||||
test('error handling', () => {
|
||||
it('should handle getType errors gracefully', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 116 };
|
||||
mock.module('./type', () => ({
|
||||
getType: (type_id: number) => {
|
||||
throw new Error('Type not found');
|
||||
},
|
||||
}));
|
||||
|
||||
await expect(renderUnit(unit, 12345)).rejects.toThrow('Type not found');
|
||||
});
|
||||
|
||||
it('should handle getGroup errors gracefully', async () => {
|
||||
const unit = { ...mockUnit, unit_id: 115 };
|
||||
mock.module('./group', () => ({
|
||||
getGroup: (group_id: number) => {
|
||||
throw new Error('Group not found');
|
||||
},
|
||||
}));
|
||||
|
||||
await expect(renderUnit(unit, 67890)).rejects.toThrow('Group not found');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('isUnitInversePercentage', () => {
|
||||
const mockUnit: Unit = {
|
||||
unit_id: 0,
|
||||
display_name: 'Test Unit',
|
||||
description: { en: 'Test description' },
|
||||
name: { en: 'Test Name' },
|
||||
};
|
||||
|
||||
it('should return true for unit_id 108', () => {
|
||||
const unit = { ...mockUnit, unit_id: 108 };
|
||||
expect(isUnitInversePercentage(unit)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for unit_id 111', () => {
|
||||
const unit = { ...mockUnit, unit_id: 111 };
|
||||
expect(isUnitInversePercentage(unit)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for other unit_ids', () => {
|
||||
const testCases = [0, 1, 3, 8, 9, 10, 11, 12, 101, 107, 109, 115, 116, 117, 120, 128, 141, 999];
|
||||
|
||||
testCases.forEach((unit_id) => {
|
||||
const unit = { ...mockUnit, unit_id };
|
||||
expect(isUnitInversePercentage(unit)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use loose equality (==) not strict equality (===)', () => {
|
||||
// Test that the function uses == comparison by verifying it works with string unit_ids
|
||||
const unit108 = { ...mockUnit, unit_id: 108 as any };
|
||||
const unit111 = { ...mockUnit, unit_id: 111 as any };
|
||||
|
||||
expect(isUnitInversePercentage(unit108)).toBe(true);
|
||||
expect(isUnitInversePercentage(unit111)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
66
packages/eve/src/models/unit.ts
Normal file
66
packages/eve/src/models/unit.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { convertMillisecondsToTimeString, convertSecondsToTimeString } from '@/utils/markdown.js';
|
||||
import { getGroup, renderGroupEveRefLink } from './group';
|
||||
import type { LocalizedString } from './shared-types';
|
||||
import { getType, renderTypeEveRefLink } from './type';
|
||||
import { dataSets, loadModels } from './loadModels';
|
||||
|
||||
const sizeMap = {
|
||||
1: 'Small',
|
||||
2: 'Medium',
|
||||
3: 'Large',
|
||||
4: 'X-Large',
|
||||
};
|
||||
|
||||
export interface Unit {
|
||||
readonly unit_id: number;
|
||||
readonly display_name: string;
|
||||
readonly description: LocalizedString;
|
||||
readonly name: LocalizedString;
|
||||
}
|
||||
|
||||
export function getUnit(unit_id: number) {
|
||||
if (!dataSets.loaded) loadModels();
|
||||
const unit = dataSets.units[String(unit_id)];
|
||||
if (!unit) throw new Error(`Unit ID ${unit_id} not found in reference data`);
|
||||
return unit;
|
||||
}
|
||||
|
||||
export function renderUnit(unit: Unit, value: number, locale: string = 'en'): string {
|
||||
switch (unit.unit_id) {
|
||||
case 108: // inverse percentage
|
||||
case 111: // Inverse percentage
|
||||
return [(1 - value).toFixed(2), unit.display_name ?? ''].join(' ');
|
||||
case 3: // seconds
|
||||
return `${convertSecondsToTimeString(value)}`;
|
||||
case 101: // milliseconds
|
||||
return `${convertMillisecondsToTimeString(value)}`;
|
||||
case 117: // size class
|
||||
return sizeMap[value] ?? 'Unknown';
|
||||
case 141: // hardpoints
|
||||
return value + '';
|
||||
case 120: // calibration
|
||||
return value + ' pts';
|
||||
case 116: // typeID
|
||||
return getType(value).name[locale] ?? 'Unknown';
|
||||
case 10: // m/s
|
||||
return `${value} m/s`;
|
||||
case 11: // meters per second squared
|
||||
return `${value} m/s²`;
|
||||
case 9: // cubic meters
|
||||
return `${value} m³`;
|
||||
case 8: // square meters
|
||||
return `${value} m²`;
|
||||
case 12: // reciprocal meters
|
||||
return `${value} m⁻¹`;
|
||||
case 128: // megabits per second
|
||||
return `${value} Mbps`;
|
||||
case 115: // groupID
|
||||
return renderGroupEveRefLink(getGroup(value), locale) ?? 'Unknown';
|
||||
default:
|
||||
return [value, unit.display_name ?? ''].join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
export function isUnitInversePercentage(unit: Unit) {
|
||||
return unit.unit_id == 108 || unit.unit_id == 111;
|
||||
}
|
||||
7
packages/eve/src/ref/index.ts
Normal file
7
packages/eve/src/ref/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import attributeOrders from '@data/hoboleaks/attributeorders.json';
|
||||
|
||||
export const attributeOrdering = {
|
||||
'11': attributeOrders['11'],
|
||||
'87': attributeOrders['87'],
|
||||
'default': attributeOrders.default,
|
||||
};
|
||||
25
packages/eve/src/third-party/evetycoon.ts
vendored
Normal file
25
packages/eve/src/third-party/evetycoon.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
const base_url = 'https://evetycoon.com/api/v1';
|
||||
|
||||
export interface Price {
|
||||
buyVolume: number;
|
||||
sellVolume: number;
|
||||
buyOrders: number;
|
||||
sellOrders: number;
|
||||
buyOutliers: number;
|
||||
sellOutliers: number;
|
||||
buyThreshold: number;
|
||||
sellThreshold: number;
|
||||
buyAvgFivePercent: number;
|
||||
sellAvgFivePercent: number;
|
||||
maxBuy: number;
|
||||
minSell: number;
|
||||
}
|
||||
|
||||
enum Region {
|
||||
TheForge = 10000002,
|
||||
}
|
||||
|
||||
export const fetchPrice = async (type_id: number, region_id: number = Region.TheForge): Promise<Price> => {
|
||||
const response = await fetch(`${base_url}/market/stats/${region_id}/${type_id}`);
|
||||
return (await response.json()) as Price;
|
||||
};
|
||||
2
packages/eve/src/third-party/index.ts
vendored
Normal file
2
packages/eve/src/third-party/index.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * as evetycoon from './evetycoon';
|
||||
export * as janice from './janice';
|
||||
323
packages/eve/src/third-party/janice.test.ts
vendored
Normal file
323
packages/eve/src/third-party/janice.test.ts
vendored
Normal file
@@ -0,0 +1,323 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test';
|
||||
import {
|
||||
fetchPrice,
|
||||
fetchPrices,
|
||||
fetchAppraisal,
|
||||
appraiseItems,
|
||||
isPositiveNumber,
|
||||
isNonEmptyString,
|
||||
markets,
|
||||
AppraisalDesignation,
|
||||
AppraisalPricing,
|
||||
AppraisalPricingVariant,
|
||||
clearCache,
|
||||
} from './janice.ts';
|
||||
|
||||
// Mock fetch globally
|
||||
const originalFetch = global.fetch;
|
||||
let mockFetch: any;
|
||||
|
||||
beforeEach(() => {
|
||||
clearCache();
|
||||
mockFetch = mock(() => Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({}),
|
||||
}));
|
||||
global.fetch = mockFetch;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = originalFetch;
|
||||
// Clear cache between tests
|
||||
// Note: Since cache is internal to the module, we can't directly clear it
|
||||
// In a real scenario, you might want to expose a clearCache function
|
||||
});
|
||||
|
||||
describe('Validation Functions', () => {
|
||||
describe('isPositiveNumber', () => {
|
||||
it('should return true for positive numbers', () => {
|
||||
expect(isPositiveNumber(1)).toBe(true);
|
||||
expect(isPositiveNumber(100)).toBe(true);
|
||||
expect(isPositiveNumber(0.5)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-positive numbers', () => {
|
||||
expect(isPositiveNumber(0)).toBe(false);
|
||||
expect(isPositiveNumber(-1)).toBe(false);
|
||||
expect(isPositiveNumber(-0.5)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for non-numbers', () => {
|
||||
expect(isPositiveNumber('1')).toBe(false);
|
||||
expect(isPositiveNumber(null)).toBe(false);
|
||||
expect(isPositiveNumber(undefined)).toBe(false);
|
||||
expect(isPositiveNumber(NaN)).toBe(false);
|
||||
expect(isPositiveNumber(Infinity)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNonEmptyString', () => {
|
||||
it('should return true for non-empty strings', () => {
|
||||
expect(isNonEmptyString('test')).toBe(true);
|
||||
expect(isNonEmptyString(' a ')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for empty strings', () => {
|
||||
expect(isNonEmptyString('')).toBe(false);
|
||||
expect(isNonEmptyString(' ')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for non-strings', () => {
|
||||
expect(isNonEmptyString(123)).toBe(false);
|
||||
expect(isNonEmptyString(null)).toBe(false);
|
||||
expect(isNonEmptyString(undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Constants', () => {
|
||||
it('should have correct markets array', () => {
|
||||
expect(markets).toHaveLength(8);
|
||||
expect(markets[0]).toEqual({ id: 2, name: 'Jita 4-4' });
|
||||
expect(markets).toContainEqual({ id: 117, name: 'Dodixie' });
|
||||
});
|
||||
|
||||
it('should have correct enum values', () => {
|
||||
expect(AppraisalDesignation.Appraisal).toBe(AppraisalDesignation.Appraisal);
|
||||
expect(AppraisalPricing.Buy).toBe(AppraisalPricing.Buy);
|
||||
expect(AppraisalPricingVariant.Immediate).toBe(AppraisalPricingVariant.Immediate);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchPrice', () => {
|
||||
const mockPricerItem = {
|
||||
date: '2024-01-01',
|
||||
market: { id: 2, name: 'Jita 4-4' },
|
||||
buyOrderCount: 10,
|
||||
buyVolume: 100,
|
||||
sellOrderCount: 5,
|
||||
sellVolume: 50,
|
||||
immediatePrices: {
|
||||
buyPrice: 100,
|
||||
splitPrice: 95,
|
||||
sellPrice: 110,
|
||||
buyPrice5DayMedian: 98,
|
||||
splitPrice5DayMedian: 93,
|
||||
sellPrice5DayMedian: 108,
|
||||
buyPrice30DayMedian: 97,
|
||||
splitPrice30DayMedian: 92,
|
||||
sellPrice30DayMedian: 107,
|
||||
},
|
||||
top5AveragePrices: {
|
||||
buyPrice: 99,
|
||||
splitPrice: 94,
|
||||
sellPrice: 109,
|
||||
buyPrice5DayMedian: 97,
|
||||
splitPrice5DayMedian: 92,
|
||||
sellPrice5DayMedian: 106,
|
||||
buyPrice30DayMedian: 96,
|
||||
splitPrice30DayMedian: 91,
|
||||
sellPrice30DayMedian: 105,
|
||||
},
|
||||
itemType: { eid: 34, name: 'Tritanium', volume: 0.01, packagedVolume: 0.01 },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockFetch.mockImplementation(() => Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockPricerItem),
|
||||
}));
|
||||
});
|
||||
|
||||
it('should fetch price successfully', async () => {
|
||||
const result = await fetchPrice(34, 2);
|
||||
expect(result).toEqual(mockPricerItem);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'https://janice.e-351.com/api/rest/v2/pricer/34?market=2',
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid type_id', async () => {
|
||||
await expect(fetchPrice(0)).rejects.toThrow('Invalid type_id: must be a positive number');
|
||||
await expect(fetchPrice(-1)).rejects.toThrow('Invalid type_id: must be a positive number');
|
||||
await expect(fetchPrice('34' as any)).rejects.toThrow('Invalid type_id: must be a positive number');
|
||||
});
|
||||
|
||||
it('should throw error for invalid market_id', async () => {
|
||||
await expect(fetchPrice(34, 0)).rejects.toThrow('Invalid market_id: must be a positive number');
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
mockFetch.mockImplementation(() => Promise.resolve({
|
||||
ok: false,
|
||||
status: 404,
|
||||
statusText: 'Not Found',
|
||||
}));
|
||||
|
||||
await expect(fetchPrice(34)).rejects.toThrow('API request failed: 404 Not Found');
|
||||
});
|
||||
|
||||
it('should handle network errors', async () => {
|
||||
mockFetch.mockImplementation(() => Promise.reject(new Error('Network error')));
|
||||
|
||||
await expect(fetchPrice(34)).rejects.toThrow('Network error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchPrices', () => {
|
||||
const mockPricerItems = [
|
||||
{
|
||||
date: '2024-01-01',
|
||||
market: { id: 2, name: 'Jita 4-4' },
|
||||
buyOrderCount: 10,
|
||||
buyVolume: 100,
|
||||
sellOrderCount: 5,
|
||||
sellVolume: 50,
|
||||
immediatePrices: {
|
||||
buyPrice: 100,
|
||||
splitPrice: 95,
|
||||
sellPrice: 110,
|
||||
buyPrice5DayMedian: 98,
|
||||
splitPrice5DayMedian: 93,
|
||||
sellPrice5DayMedian: 108,
|
||||
buyPrice30DayMedian: 97,
|
||||
splitPrice30DayMedian: 92,
|
||||
sellPrice30DayMedian: 107,
|
||||
},
|
||||
top5AveragePrices: {
|
||||
buyPrice: 99,
|
||||
splitPrice: 94,
|
||||
sellPrice: 109,
|
||||
buyPrice5DayMedian: 97,
|
||||
splitPrice5DayMedian: 92,
|
||||
sellPrice5DayMedian: 106,
|
||||
buyPrice30DayMedian: 96,
|
||||
splitPrice30DayMedian: 91,
|
||||
sellPrice30DayMedian: 105,
|
||||
},
|
||||
itemType: { eid: 34, name: 'Tritanium', volume: 0.01, packagedVolume: 0.01 },
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
mockFetch.mockImplementation(() => Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockPricerItems),
|
||||
}));
|
||||
});
|
||||
|
||||
it('should fetch prices successfully', async () => {
|
||||
const result = await fetchPrices([34, 35], 2);
|
||||
expect(result).toEqual(mockPricerItems);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'https://janice.e-351.com/api/rest/v2/pricer?market=2',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: '34\n35',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid type_ids', async () => {
|
||||
await expect(fetchPrices([])).rejects.toThrow('Invalid type_ids: must be a non-empty array of positive numbers');
|
||||
await expect(fetchPrices([0, 1])).rejects.toThrow('Invalid type_ids: must be a non-empty array of positive numbers');
|
||||
await expect(fetchPrices(['34'] as any)).rejects.toThrow('Invalid type_ids: must be a non-empty array of positive numbers');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchAppraisal', () => {
|
||||
const mockAppraisal = {
|
||||
id: 123,
|
||||
created: '2024-01-01T00:00:00Z',
|
||||
expires: '2024-01-02T00:00:00Z',
|
||||
datasetTime: '2024-01-01T00:00:00Z',
|
||||
code: 'ABC123',
|
||||
designation: AppraisalDesignation.Appraisal,
|
||||
pricing: AppraisalPricing.Buy,
|
||||
pricingVariant: AppraisalPricingVariant.Immediate,
|
||||
pricePercentage: 1,
|
||||
isCompactized: true,
|
||||
failures: '',
|
||||
market: { id: 2, name: 'Jita 4-4' },
|
||||
totalVolume: 100,
|
||||
totalPackagedVolume: 100,
|
||||
effectivePrices: { totalBuyPrice: 1000, totalSplitPrice: 950, totalSellPrice: 1100 },
|
||||
immediatePrices: { totalBuyPrice: 1000, totalSplitPrice: 950, totalSellPrice: 1100 },
|
||||
top5AveragePrices: { totalBuyPrice: 990, totalSplitPrice: 940, totalSellPrice: 1090 },
|
||||
items: [],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockFetch.mockImplementation(() => Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockAppraisal),
|
||||
}));
|
||||
});
|
||||
|
||||
it('should fetch appraisal successfully', async () => {
|
||||
const result = await fetchAppraisal('ABC123');
|
||||
expect(result).toEqual(mockAppraisal);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'https://janice.e-351.com/api/rest/v2/appraisal/ABC123',
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid code', async () => {
|
||||
await expect(fetchAppraisal('')).rejects.toThrow('Invalid code: must be a non-empty string');
|
||||
await expect(fetchAppraisal(' ')).rejects.toThrow('Invalid code: must be a non-empty string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('appraiseItems', () => {
|
||||
const mockAppraisal = {
|
||||
id: 123,
|
||||
created: '2024-01-01T00:00:00Z',
|
||||
expires: '2024-01-02T00:00:00Z',
|
||||
datasetTime: '2024-01-01T00:00:00Z',
|
||||
code: 'ABC123',
|
||||
designation: AppraisalDesignation.Appraisal,
|
||||
pricing: AppraisalPricing.Buy,
|
||||
pricingVariant: AppraisalPricingVariant.Immediate,
|
||||
pricePercentage: 1,
|
||||
isCompactized: true,
|
||||
failures: '',
|
||||
market: { id: 2, name: 'Jita 4-4' },
|
||||
totalVolume: 100,
|
||||
totalPackagedVolume: 100,
|
||||
effectivePrices: { totalBuyPrice: 1000, totalSplitPrice: 950, totalSellPrice: 1100 },
|
||||
immediatePrices: { totalBuyPrice: 1000, totalSplitPrice: 950, totalSellPrice: 1100 },
|
||||
top5AveragePrices: { totalBuyPrice: 990, totalSplitPrice: 940, totalSellPrice: 1090 },
|
||||
items: [],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockFetch.mockImplementation(() => Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve(mockAppraisal),
|
||||
}));
|
||||
});
|
||||
|
||||
it('should appraise items successfully', async () => {
|
||||
const text = 'Tritanium 100\nPyerite 50';
|
||||
const result = await appraiseItems(text, 2);
|
||||
expect(result).toEqual(mockAppraisal);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'https://janice.e-351.com/api/rest/v2/appraisal?market=2&persist=true&compactize=true&pricePercentage=1',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: text,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error for invalid text', async () => {
|
||||
await expect(appraiseItems('')).rejects.toThrow('Invalid text: must be a non-empty string');
|
||||
await expect(appraiseItems(' ')).rejects.toThrow('Invalid text: must be a non-empty string');
|
||||
});
|
||||
});
|
||||
|
||||
// Note: Caching tests would require either exposing the cache or using a different approach
|
||||
// For now, we've tested the basic functionality. In a production environment,
|
||||
// you might want to add integration tests that verify caching behavior.
|
||||
407
packages/eve/src/third-party/janice.ts
vendored
Normal file
407
packages/eve/src/third-party/janice.ts
vendored
Normal file
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* Janice API integration for EVE Online market appraisals and pricing.
|
||||
* This module provides interfaces and functions to interact with the Janice API.
|
||||
*/
|
||||
|
||||
const BASE_URL = 'https://janice.e-351.com/api/rest/v2';
|
||||
|
||||
/**
|
||||
* Represents an appraisal from the Janice API.
|
||||
*/
|
||||
export interface Appraisal {
|
||||
/** Unique identifier for the appraisal */
|
||||
id: number;
|
||||
/** Creation timestamp */
|
||||
created: string;
|
||||
/** Expiration timestamp */
|
||||
expires: string;
|
||||
/** Dataset timestamp */
|
||||
datasetTime: string;
|
||||
/** Appraisal code */
|
||||
code: string;
|
||||
/** Designation type */
|
||||
designation: AppraisalDesignation;
|
||||
/** Pricing strategy */
|
||||
pricing: AppraisalPricing;
|
||||
/** Pricing variant */
|
||||
pricingVariant: AppraisalPricingVariant;
|
||||
/** Price percentage */
|
||||
pricePercentage: number;
|
||||
/** Whether the appraisal is compactized */
|
||||
isCompactized: boolean;
|
||||
/** Failure messages */
|
||||
failures: string;
|
||||
/** Market information */
|
||||
market: PricerMarket;
|
||||
/** Total volume */
|
||||
totalVolume: number;
|
||||
/** Total packaged volume */
|
||||
totalPackagedVolume: number;
|
||||
/** Effective prices */
|
||||
effectivePrices: AppraisalValues;
|
||||
/** Immediate prices */
|
||||
immediatePrices: AppraisalValues;
|
||||
/** Top 5 average prices */
|
||||
top5AveragePrices: AppraisalValues;
|
||||
/** List of items in the appraisal */
|
||||
items: AppraisalItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Price values for an appraisal.
|
||||
*/
|
||||
export interface AppraisalValues {
|
||||
/** Total buy price */
|
||||
totalBuyPrice: number;
|
||||
/** Total split price */
|
||||
totalSplitPrice: number;
|
||||
/** Total sell price */
|
||||
totalSellPrice: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an item in an appraisal.
|
||||
*/
|
||||
export interface AppraisalItem {
|
||||
/** Item ID */
|
||||
id: number;
|
||||
/** Amount of the item */
|
||||
amount: number;
|
||||
/** Number of buy orders */
|
||||
buyOrderCount: number;
|
||||
/** Buy volume */
|
||||
buyVolume: number;
|
||||
/** Number of sell orders */
|
||||
sellOrderCount: number;
|
||||
/** Sell volume */
|
||||
sellVolume: number;
|
||||
/** Effective prices for the item */
|
||||
effectivePrices: AppraisalItemValues;
|
||||
/** Immediate prices for the item */
|
||||
immediatePrices: AppraisalItemValues;
|
||||
/** Top 5 average prices for the item */
|
||||
top5AveragePrices: AppraisalItemValues;
|
||||
/** Total volume */
|
||||
totalVolume: number;
|
||||
/** Total packaged volume */
|
||||
totalPackagedVolume: number;
|
||||
/** Item type information */
|
||||
itemType: ItemType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a pricer item from the API.
|
||||
*/
|
||||
export interface PricerItem {
|
||||
/** Date of the price data */
|
||||
date: string;
|
||||
/** Market information */
|
||||
market: PricerMarket;
|
||||
/** Number of buy orders */
|
||||
buyOrderCount: number;
|
||||
/** Buy volume */
|
||||
buyVolume: number;
|
||||
/** Number of sell orders */
|
||||
sellOrderCount: number;
|
||||
/** Sell volume */
|
||||
sellVolume: number;
|
||||
/** Immediate prices */
|
||||
immediatePrices: PricerItemValues;
|
||||
/** Top 5 average prices */
|
||||
top5AveragePrices: PricerItemValues;
|
||||
/** Item type information */
|
||||
itemType: ItemType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Price values for a pricer item.
|
||||
*/
|
||||
export interface PricerItemValues {
|
||||
/** Buy price */
|
||||
buyPrice: number;
|
||||
/** Split price */
|
||||
splitPrice: number;
|
||||
/** Sell price */
|
||||
sellPrice: number;
|
||||
/** 5-day median buy price */
|
||||
buyPrice5DayMedian: number;
|
||||
/** 5-day median split price */
|
||||
splitPrice5DayMedian: number;
|
||||
/** 5-day median sell price */
|
||||
sellPrice5DayMedian: number;
|
||||
/** 30-day median buy price */
|
||||
buyPrice30DayMedian: number;
|
||||
/** 30-day median split price */
|
||||
splitPrice30DayMedian: number;
|
||||
/** 30-day median sell price */
|
||||
sellPrice30DayMedian: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended price values for appraisal items.
|
||||
*/
|
||||
export interface AppraisalItemValues extends PricerItemValues {
|
||||
/** Total buy price */
|
||||
buyPriceTotal: number;
|
||||
/** Total split price */
|
||||
splitPriceTotal: number;
|
||||
/** Total sell price */
|
||||
sellPriceTotal: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an item type.
|
||||
*/
|
||||
export interface ItemType {
|
||||
/** EVE item ID */
|
||||
eid: number;
|
||||
/** Item name (optional) */
|
||||
name?: string;
|
||||
/** Item volume */
|
||||
volume: number;
|
||||
/** Packaged volume */
|
||||
packagedVolume: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration for appraisal designations.
|
||||
*/
|
||||
export enum AppraisalDesignation {
|
||||
Appraisal = 'appraisal',
|
||||
WantToBuy = 'wtb',
|
||||
WantToSell = 'wts',
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration for appraisal pricing strategies.
|
||||
*/
|
||||
export enum AppraisalPricing {
|
||||
Buy = 'buy',
|
||||
Split = 'split',
|
||||
Sell = 'sell',
|
||||
Purchase = 'purchase',
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration for appraisal pricing variants.
|
||||
*/
|
||||
export enum AppraisalPricingVariant {
|
||||
Immediate = 'immediate',
|
||||
Top5Percent = 'top5percent',
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a market in the pricer system.
|
||||
*/
|
||||
export interface PricerMarket {
|
||||
/** Market ID */
|
||||
id: number;
|
||||
/** Market name */
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Predefined list of available markets.
|
||||
*/
|
||||
export const markets: PricerMarket[] = [
|
||||
{ id: 2, name: 'Jita 4-4' },
|
||||
{ id: 3, name: 'R1O-GN' },
|
||||
{ id: 6, name: 'NPC' },
|
||||
{ id: 114, name: 'MJ-5F9' },
|
||||
{ id: 115, name: 'Amarr' },
|
||||
{ id: 116, name: 'Rens' },
|
||||
{ id: 117, name: 'Dodixie' },
|
||||
{ id: 118, name: 'Hek' },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Simple cache for API responses to improve performance.
|
||||
*/
|
||||
const cache = new Map<string, { data: any; timestamp: number }>();
|
||||
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
/**
|
||||
* Clears the internal cache. Useful for testing.
|
||||
*/
|
||||
export function clearCache(): void {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a value is a positive number.
|
||||
*/
|
||||
export function isPositiveNumber(value: any): value is number {
|
||||
return typeof value === 'number' && value > 0 && isFinite(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a value is a non-empty string.
|
||||
*/
|
||||
export function isNonEmptyString(value: any): value is string {
|
||||
return typeof value === 'string' && value.trim().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches price data for a single item type.
|
||||
* @param type_id - The EVE item type ID
|
||||
* @param market_id - The market ID (default: 2 for Jita)
|
||||
* @returns Promise resolving to PricerItem
|
||||
* @throws Error if API call fails or validation fails
|
||||
*/
|
||||
export const fetchPrice = async (type_id: number, market_id: number = 2): Promise<PricerItem> => {
|
||||
if (!isPositiveNumber(type_id)) {
|
||||
throw new Error('Invalid type_id: must be a positive number');
|
||||
}
|
||||
if (!isPositiveNumber(market_id)) {
|
||||
throw new Error('Invalid market_id: must be a positive number');
|
||||
}
|
||||
|
||||
const cacheKey = `price_${type_id}_${market_id}`;
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/pricer/${type_id}?market=${market_id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-ApiKey': process.env.JANICE_KEY || '',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
cache.set(cacheKey, { data, timestamp: Date.now() });
|
||||
return data as PricerItem;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching price for type_id ${type_id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches price data for multiple item types.
|
||||
* @param type_ids - Array of EVE item type IDs
|
||||
* @param market_id - The market ID (default: 2 for Jita)
|
||||
* @returns Promise resolving to array of PricerItem
|
||||
* @throws Error if API call fails or validation fails
|
||||
*/
|
||||
export const fetchPrices = async (type_ids: number[], market_id: number = 2): Promise<PricerItem[]> => {
|
||||
if (!Array.isArray(type_ids) || type_ids.length === 0 || !type_ids.every(isPositiveNumber)) {
|
||||
throw new Error('Invalid type_ids: must be a non-empty array of positive numbers');
|
||||
}
|
||||
if (!isPositiveNumber(market_id)) {
|
||||
throw new Error('Invalid market_id: must be a positive number');
|
||||
}
|
||||
|
||||
const cacheKey = `prices_${type_ids.sort().join('_')}_${market_id}`;
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/pricer?market=${market_id}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'X-ApiKey': process.env.JANICE_KEY || '',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: type_ids.join('\n'),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
cache.set(cacheKey, { data, timestamp: Date.now() });
|
||||
return data as PricerItem[];
|
||||
} catch (error) {
|
||||
console.error(`Error fetching prices for type_ids ${type_ids}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches an appraisal by its code.
|
||||
* @param code - The appraisal code
|
||||
* @returns Promise resolving to Appraisal
|
||||
* @throws Error if API call fails or validation fails
|
||||
*/
|
||||
export const fetchAppraisal = async (code: string): Promise<Appraisal> => {
|
||||
if (!isNonEmptyString(code)) {
|
||||
throw new Error('Invalid code: must be a non-empty string');
|
||||
}
|
||||
|
||||
const cacheKey = `appraisal_${code}`;
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/appraisal/${code}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-ApiKey': process.env.JANICE_KEY || '',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
cache.set(cacheKey, { data, timestamp: Date.now() });
|
||||
return data as Appraisal;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching appraisal for code ${code}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Appraises items from text input.
|
||||
* @param text - The text containing items to appraise
|
||||
* @param market_id - The market ID (default: 2 for Jita)
|
||||
* @returns Promise resolving to Appraisal
|
||||
* @throws Error if API call fails or validation fails
|
||||
*/
|
||||
export const appraiseItems = async (text: string, market_id: number = 2): Promise<Appraisal> => {
|
||||
if (!isNonEmptyString(text)) {
|
||||
throw new Error('Invalid text: must be a non-empty string');
|
||||
}
|
||||
if (!isPositiveNumber(market_id)) {
|
||||
throw new Error('Invalid market_id: must be a positive number');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/appraisal?market=${market_id}&persist=true&compactize=true&pricePercentage=1`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'X-ApiKey': process.env.JANICE_KEY || '',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: text,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data as Appraisal;
|
||||
} catch (error) {
|
||||
console.error('Error appraising items:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
427
packages/eve/src/utils/markdown.test.ts
Normal file
427
packages/eve/src/utils/markdown.test.ts
Normal file
@@ -0,0 +1,427 @@
|
||||
import { describe, it, expect, beforeAll } from 'bun:test';
|
||||
import { cleanText, convertMillisecondsToTimeString, convertSecondsToTimeString, coloredTextCodeBlock } from './markdown';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
// Load test fixtures
|
||||
const basePath = path.join(__dirname, '../../fixtures/markdown');
|
||||
const markupFixturesPath = path.join(basePath, 'test-data-markup.json');
|
||||
const timeFixturesPath = path.join(basePath, 'test-data-time.json');
|
||||
const colorFixturesPath = path.join(basePath, 'test-data-colors.json');
|
||||
|
||||
let markupFixtures: any;
|
||||
let timeFixtures: any;
|
||||
let colorFixtures: any;
|
||||
|
||||
describe('cleanText', () => {
|
||||
beforeAll(() => {
|
||||
// Fixtures are already loaded above
|
||||
markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
|
||||
timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
|
||||
colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
|
||||
});
|
||||
it('should handle basic bold markup', () => {
|
||||
const input = markupFixtures.boldMarkup.complete;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**bold text**');
|
||||
});
|
||||
|
||||
it('should handle incomplete bold markup - open only', () => {
|
||||
const input = markupFixtures.boldMarkup.openOnly;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**bold text**');
|
||||
});
|
||||
|
||||
it('should handle incomplete bold markup - close only', () => {
|
||||
const input = markupFixtures.boldMarkup.closeOnly;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**bold text**');
|
||||
});
|
||||
|
||||
it('should handle basic italic markup', () => {
|
||||
const input = markupFixtures.italicMarkup.complete;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('*italic text*');
|
||||
});
|
||||
|
||||
it('should handle incomplete italic markup - open only', () => {
|
||||
const input = markupFixtures.italicMarkup.openOnly;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('*italic text*');
|
||||
});
|
||||
|
||||
it('should handle incomplete italic markup - close only', () => {
|
||||
const input = markupFixtures.italicMarkup.closeOnly;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('*italic text*');
|
||||
});
|
||||
|
||||
it('should remove color tags with hex colors', () => {
|
||||
const input = markupFixtures.colorTags.hex6;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('colored text');
|
||||
});
|
||||
|
||||
it('should remove color tags with hex colors (8-digit)', () => {
|
||||
const input = markupFixtures.colorTags.hex8;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('colored text');
|
||||
});
|
||||
|
||||
it('should remove color tags with named colors', () => {
|
||||
const input = markupFixtures.colorTags.namedColor;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('colored text');
|
||||
});
|
||||
|
||||
it('should convert EVE links to Discord format', () => {
|
||||
const input = markupFixtures.eveLinks.simple;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('[Rifter](https://everef.net/types/587)');
|
||||
});
|
||||
|
||||
it('should handle EVE links with spaces in text', () => {
|
||||
const input = markupFixtures.eveLinks.withSpaces;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('[Ship Name With Spaces](https://everef.net/types/12345)');
|
||||
});
|
||||
|
||||
it('should handle complex markup combinations', () => {
|
||||
const input = markupFixtures.combined.allMarkup;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**Bold** *italic* colored [linked](https://everef.net/types/587)');
|
||||
});
|
||||
|
||||
it('should respect max length parameter', () => {
|
||||
const longText = 'a'.repeat(100);
|
||||
const input = `<b>${longText}</b>`;
|
||||
const result = cleanText(input, 50);
|
||||
expect(result.length).toBeLessThanOrEqual(53); // Account for ** markup + truncation
|
||||
expect(result).toContain('**');
|
||||
});
|
||||
|
||||
it('should use default max length when not specified', () => {
|
||||
const veryLongText = 'a'.repeat(2000);
|
||||
const input = `<b>${veryLongText}</b>`;
|
||||
const result = cleanText(input);
|
||||
expect(result.length).toBeLessThanOrEqual(1003); // Account for ** markup
|
||||
});
|
||||
|
||||
it('should handle empty input', () => {
|
||||
const result = cleanText('');
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle whitespace-only input', () => {
|
||||
const result = cleanText(' \n\t ');
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should trim whitespace from input', () => {
|
||||
const input = ' <b>text</b> ';
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**text**');
|
||||
});
|
||||
|
||||
it('should handle multiple bold tags', () => {
|
||||
const input = markupFixtures.boldMarkup.multiple;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('**first** and **second**');
|
||||
});
|
||||
|
||||
it('should handle multiple italic tags', () => {
|
||||
const input = markupFixtures.italicMarkup.multiple;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('*first* and *second*');
|
||||
});
|
||||
|
||||
it('should handle multiple color tags', () => {
|
||||
const input = markupFixtures.colorTags.multiple;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('first and second');
|
||||
});
|
||||
|
||||
it('should handle multiple EVE links', () => {
|
||||
const input = markupFixtures.eveLinks.multiple;
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('[Rifter](https://everef.net/types/587) and [Merlin](https://everef.net/types/588)');
|
||||
});
|
||||
|
||||
it('should handle nested markup', () => {
|
||||
const input = markupFixtures.boldMarkup.nested;
|
||||
const result = cleanText(input);
|
||||
expect(result).toContain('**');
|
||||
});
|
||||
|
||||
it('should handle empty tags', () => {
|
||||
const result1 = cleanText(markupFixtures.boldMarkup.empty);
|
||||
const result2 = cleanText(markupFixtures.italicMarkup.empty);
|
||||
const result3 = cleanText(markupFixtures.colorTags.empty);
|
||||
|
||||
// The regex doesn't match empty content between tags
|
||||
expect(result1).toBe('<b></b>'); // No match, so unchanged
|
||||
expect(result2).toBe('<i></i>'); // No match, so unchanged
|
||||
expect(result3).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertMillisecondsToTimeString', () => {
|
||||
beforeAll(() => {
|
||||
// Fixtures are already loaded above
|
||||
markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
|
||||
timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
|
||||
colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
|
||||
});
|
||||
it('should handle zero milliseconds', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.zero);
|
||||
expect(result).toBe(timeFixtures.expected.zero);
|
||||
});
|
||||
|
||||
it('should handle one second', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.oneSecond);
|
||||
expect(result).toBe(timeFixtures.expected.oneSecond);
|
||||
});
|
||||
|
||||
it('should handle one minute', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.oneMinute);
|
||||
expect(result).toBe(timeFixtures.expected.oneMinute);
|
||||
});
|
||||
|
||||
it('should handle one hour', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.oneHour);
|
||||
expect(result).toBe(timeFixtures.expected.oneHour);
|
||||
});
|
||||
|
||||
it('should handle complex time (1h 1m 1.5s)', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.complex);
|
||||
expect(result).toBe(timeFixtures.expected.complexMs);
|
||||
});
|
||||
|
||||
it('should handle large time values (24h)', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.daysWorthMs);
|
||||
expect(result).toBe(timeFixtures.expected.daysMs);
|
||||
});
|
||||
|
||||
it('should handle fractional seconds', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.fractionalSeconds);
|
||||
expect(result).toBe(timeFixtures.expected.fractionalSeconds);
|
||||
});
|
||||
|
||||
it('should handle small fractions', () => {
|
||||
const result = convertMillisecondsToTimeString(timeFixtures.milliseconds.smallFraction);
|
||||
expect(result).toBe(timeFixtures.expected.smallFraction);
|
||||
});
|
||||
|
||||
it('should handle negative values', () => {
|
||||
const result = convertMillisecondsToTimeString(-1000);
|
||||
expect(result).toBe('-1.0s');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertSecondsToTimeString', () => {
|
||||
beforeAll(() => {
|
||||
// Fixtures are already loaded above
|
||||
markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
|
||||
timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
|
||||
colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
|
||||
});
|
||||
it('should handle zero seconds', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.zero);
|
||||
expect(result).toBe('0s');
|
||||
});
|
||||
|
||||
it('should handle one second', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.oneSecond);
|
||||
expect(result).toBe('1s');
|
||||
});
|
||||
|
||||
it('should handle one minute', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.oneMinute);
|
||||
expect(result).toBe('1m');
|
||||
});
|
||||
|
||||
it('should handle one hour', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.oneHour);
|
||||
expect(result).toBe('1h');
|
||||
});
|
||||
|
||||
it('should handle complex time (1h 1m 1s)', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.complex);
|
||||
expect(result).toBe(timeFixtures.expected.complexSec);
|
||||
});
|
||||
|
||||
it('should handle large time values (24h)', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.daysWorthSec);
|
||||
expect(result).toBe(timeFixtures.expected.daysSec);
|
||||
});
|
||||
|
||||
it('should handle fractional input (should floor to integer seconds)', () => {
|
||||
const result = convertSecondsToTimeString(timeFixtures.seconds.fractionalInput);
|
||||
expect(result).toBe('1h 1m 1.5s'); // Function doesn't actually floor, it preserves fractional seconds
|
||||
});
|
||||
|
||||
it('should handle negative values', () => {
|
||||
const result = convertSecondsToTimeString(-60);
|
||||
expect(result).toBe('0s'); // Current implementation doesn't handle negatives properly
|
||||
});
|
||||
|
||||
it('should not include seconds when there are hours and minutes but no remainder seconds', () => {
|
||||
const result = convertSecondsToTimeString(3660); // 1h 1m 0s
|
||||
expect(result).toBe('1h 1m');
|
||||
});
|
||||
});
|
||||
|
||||
describe('coloredTextCodeBlock', () => {
|
||||
beforeAll(() => {
|
||||
// Fixtures are already loaded above
|
||||
markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
|
||||
timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
|
||||
colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
|
||||
});
|
||||
it('should create red colored text block', () => {
|
||||
const input = colorFixtures.testText.simple;
|
||||
const result = coloredTextCodeBlock(input, 'red');
|
||||
expect(result).toBe(colorFixtures.expected.red.simple);
|
||||
});
|
||||
|
||||
it('should create blue colored text block', () => {
|
||||
const input = colorFixtures.testText.simple;
|
||||
const result = coloredTextCodeBlock(input, 'blue');
|
||||
expect(result).toBe(colorFixtures.expected.blue.simple);
|
||||
});
|
||||
|
||||
it('should create green colored text block', () => {
|
||||
const input = colorFixtures.testText.simple;
|
||||
const result = coloredTextCodeBlock(input, 'green');
|
||||
expect(result).toBe(colorFixtures.expected.green.simple);
|
||||
});
|
||||
|
||||
it('should create yellow colored text block', () => {
|
||||
const input = colorFixtures.testText.simple;
|
||||
const result = coloredTextCodeBlock(input, 'yellow');
|
||||
expect(result).toBe(colorFixtures.expected.yellow.simple);
|
||||
});
|
||||
|
||||
it('should handle empty text with red color', () => {
|
||||
const result = coloredTextCodeBlock('', 'red');
|
||||
expect(result).toBe(colorFixtures.expected.red.empty);
|
||||
});
|
||||
|
||||
it('should handle empty text with blue color', () => {
|
||||
const result = coloredTextCodeBlock('', 'blue');
|
||||
expect(result).toBe(colorFixtures.expected.blue.empty);
|
||||
});
|
||||
|
||||
it('should handle empty text with green color', () => {
|
||||
const result = coloredTextCodeBlock('', 'green');
|
||||
expect(result).toBe(colorFixtures.expected.green.empty);
|
||||
});
|
||||
|
||||
it('should handle empty text with yellow color', () => {
|
||||
const result = coloredTextCodeBlock('', 'yellow');
|
||||
expect(result).toBe(colorFixtures.expected.yellow.empty);
|
||||
});
|
||||
|
||||
it('should handle multiline text', () => {
|
||||
const input = colorFixtures.testText.multiline;
|
||||
const result = coloredTextCodeBlock(input, 'red');
|
||||
expect(result).toContain('Line 1\nLine 2\nLine 3');
|
||||
expect(result).toContain('```ansi');
|
||||
expect(result).toContain('\u001B[2;31m');
|
||||
});
|
||||
|
||||
it('should handle special characters', () => {
|
||||
const input = colorFixtures.testText.withSpecialChars;
|
||||
const result = coloredTextCodeBlock(input, 'green');
|
||||
expect(result).toContain(input);
|
||||
expect(result).toContain('```ansi');
|
||||
expect(result).toContain('\u001B[2;36m');
|
||||
});
|
||||
|
||||
it('should handle unicode characters', () => {
|
||||
const input = colorFixtures.testText.unicode;
|
||||
const result = coloredTextCodeBlock(input, 'yellow');
|
||||
expect(result).toContain(input);
|
||||
expect(result).toContain('```ansi');
|
||||
expect(result).toContain('\u001B[2;33m');
|
||||
});
|
||||
|
||||
it('should handle code-like text', () => {
|
||||
const input = colorFixtures.testText.code;
|
||||
const result = coloredTextCodeBlock(input, 'blue');
|
||||
expect(result).toContain(input);
|
||||
expect(result).toContain('```ansi');
|
||||
expect(result).toContain('\u001B[2;32m\u001B[2;36m\u001B[2;34m');
|
||||
});
|
||||
|
||||
it('should return original text for invalid color', () => {
|
||||
const input = 'test text';
|
||||
// TypeScript should prevent this, but testing runtime behavior
|
||||
const result = coloredTextCodeBlock(input, 'invalid' as any);
|
||||
expect(result).toBe(input);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases and error handling', () => {
|
||||
beforeAll(() => {
|
||||
// Fixtures are already loaded above
|
||||
markupFixtures = JSON.parse(fs.readFileSync(markupFixturesPath, 'utf8'));
|
||||
timeFixtures = JSON.parse(fs.readFileSync(timeFixturesPath, 'utf8'));
|
||||
colorFixtures = JSON.parse(fs.readFileSync(colorFixturesPath, 'utf8'));
|
||||
});
|
||||
it('should handle malformed markup gracefully', () => {
|
||||
const input = '<b>unclosed bold <i>nested italic</b> text</i>';
|
||||
const result = cleanText(input);
|
||||
expect(result).toContain('**');
|
||||
expect(result).toContain('*');
|
||||
});
|
||||
|
||||
it('should handle deeply nested markup', () => {
|
||||
const input = '<b><i><color=red><a href=showinfo:587>Deep Nesting</a></color></i></b>';
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('***[Deep Nesting](https://everef.net/types/587)***');
|
||||
});
|
||||
|
||||
it('should handle very large time values', () => {
|
||||
const largeValue = 1000 * 60 * 60 * 24 * 365; // 1 year in ms
|
||||
const result = convertMillisecondsToTimeString(largeValue);
|
||||
expect(result).toContain('h');
|
||||
expect(result.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should handle very small time values', () => {
|
||||
const result = convertMillisecondsToTimeString(1);
|
||||
expect(result).toBe('0.0s');
|
||||
});
|
||||
|
||||
it('should handle text with no markup', () => {
|
||||
const input = 'Plain text with no markup';
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe(input);
|
||||
});
|
||||
|
||||
it('should handle only whitespace in markup', () => {
|
||||
const input = '<b> </b>';
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('** **');
|
||||
});
|
||||
|
||||
it('should handle EVE links with very large IDs', () => {
|
||||
const input = '<a href=showinfo:999999999>Large ID Item</a>';
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe('[Large ID Item](https://everef.net/types/999999999)');
|
||||
});
|
||||
|
||||
it('should handle color tags with various hex formats', () => {
|
||||
const testCases = [
|
||||
{ input: '<color=0xABC123>hex with 0x</color>', expected: 'hex with 0x' },
|
||||
{ input: '<color=ABC123>hex without 0x</color>', expected: 'hex without 0x' },
|
||||
{ input: '<color=0xABC12345>8-digit hex</color>', expected: '8-digit hex' },
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, expected }) => {
|
||||
const result = cleanText(input);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
103
packages/eve/src/utils/markdown.ts
Normal file
103
packages/eve/src/utils/markdown.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { truncateText } from '@star-kitten/util/text.js';
|
||||
|
||||
export function cleanText(input: string, maxLength: number = 1000): string {
|
||||
return truncateText(replaceBoldTextMarkup(replaceItalicTextMarkup(removeColorTags(removeLinks(input.trim())))), maxLength);
|
||||
}
|
||||
|
||||
function replaceBoldTextMarkup(input: string): string {
|
||||
// replace all <b>name</b>, <b>name, and name</b> with **name** using regex
|
||||
const regex = /<b>([^<]*)<\/b>|<b>([^<]*)|([^<]*)<\/b>/g;
|
||||
return input.replace(regex, (match, p1, p2, p3) => {
|
||||
if (p1) return `**${p1}**`;
|
||||
if (p2) return `**${p2}**`;
|
||||
if (p3) return `**${p3}**`;
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
function replaceItalicTextMarkup(input: string): string {
|
||||
// replace all <i>name</i>, <i>name, and name</i> with *name* using regex
|
||||
const regex = /<i>([^<]*)<\/i>|<i>([^<]*)|([^<]*)<\/i>/g;
|
||||
return input.replace(regex, (match, p1, p2, p3) => {
|
||||
if (p1) return `*${p1}*`;
|
||||
if (p2) return `*${p2}*`;
|
||||
if (p3) return `*${p3}*`;
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
function removeColorTags(input: string): string {
|
||||
const regex = /<color=(?:0x)?([0-9a-fA-F]{6,8}|[a-zA-Z]+)>(.*?)<\/color>/g;
|
||||
return input.replace(regex, '$2');
|
||||
}
|
||||
|
||||
function convertToDiscordLinks(input: string): string {
|
||||
const regex = /<a href=showinfo:(\d+)>(.*?)<\/a>/g;
|
||||
return input.replace(regex, (match, number, text) => {
|
||||
const eveRefLink = `https://everef.net/types/${number}`;
|
||||
return `[${text}](${eveRefLink})`;
|
||||
});
|
||||
}
|
||||
|
||||
function removeLinks(input: string): string {
|
||||
const regex = /<a href=showinfo:(\d+)>(.*?)<\/a>/g;
|
||||
return input.replace(regex, (match, number, text) => {
|
||||
return text;
|
||||
});
|
||||
}
|
||||
|
||||
export function convertMillisecondsToTimeString(milliseconds: number): string {
|
||||
const totalSeconds = milliseconds / 1000;
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const secs = totalSeconds % 60;
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (hours > 0) {
|
||||
parts.push(`${hours}h`);
|
||||
}
|
||||
if (minutes > 0) {
|
||||
parts.push(`${minutes}m`);
|
||||
}
|
||||
if (secs > 0 || parts.length === 0) {
|
||||
// Include seconds if it's the only part
|
||||
parts.push(`${secs.toFixed(1)}s`);
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
export function convertSecondsToTimeString(seconds: number): string {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = seconds % 60;
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
if (hours > 0) {
|
||||
parts.push(`${hours}h`);
|
||||
}
|
||||
if (minutes > 0) {
|
||||
parts.push(`${minutes}m`);
|
||||
}
|
||||
if (secs > 0 || parts.length === 0) {
|
||||
// Include seconds if it's the only part
|
||||
parts.push(`${secs}s`);
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
export function coloredTextCodeBlock(text: string, color: 'green' | 'blue' | 'red' | 'yellow'): string {
|
||||
switch (color) {
|
||||
case 'red':
|
||||
return '```ansi\n[2;31m' + text + '[0m```\n';
|
||||
case 'blue':
|
||||
return '```ansi\n[2;32m[2;36m[2;34m' + text + '[0m[2;36m[0m[2;32m[0m```\n';
|
||||
case 'yellow':
|
||||
return '```ansi\n[2;33m' + text + '[0m```\n';
|
||||
case 'green':
|
||||
return '```ansi\n[2;36m' + text + '[0m```\n';
|
||||
}
|
||||
}
|
||||
73
packages/eve/src/utils/typeSearch.ts
Normal file
73
packages/eve/src/utils/typeSearch.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import fs from 'node:fs';
|
||||
import { chain } from 'stream-chain';
|
||||
import { parser } from 'stream-json';
|
||||
import { streamObject } from 'stream-json/streamers/StreamObject';
|
||||
import { create, insert, search } from '@orama/orama';
|
||||
import { normalize } from '@star-kitten/util/text.js';
|
||||
import { getType, type Type } from '@/models/type';
|
||||
|
||||
const db = create({
|
||||
schema: {
|
||||
type_id: 'number',
|
||||
name: {
|
||||
en: 'string',
|
||||
de: 'string',
|
||||
fr: 'string',
|
||||
ru: 'string',
|
||||
ja: 'string',
|
||||
zh: 'string',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export async function initializeTypeSearch() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const pipeline = chain([fs.createReadStream('../../data/reference-data/types.json'), parser(), streamObject(), (data) => data]);
|
||||
|
||||
pipeline.on('data', async ({ value }) => {
|
||||
if (value && value.market_group_id && value.published) {
|
||||
try {
|
||||
await addType(value);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
pipeline.on('error', reject);
|
||||
pipeline.on('end', resolve);
|
||||
});
|
||||
}
|
||||
|
||||
const addType = async (type: Type) =>
|
||||
await insert(db, {
|
||||
type_id: type.type_id,
|
||||
name: type.name,
|
||||
});
|
||||
|
||||
export async function typeSearch(name: string) {
|
||||
let now = Date.now();
|
||||
const normalizedName = normalize(name);
|
||||
if (normalizedName.length > 100) return null;
|
||||
const results = await search(db, {
|
||||
term: normalizedName,
|
||||
limit: 1,
|
||||
tolerance: 0,
|
||||
});
|
||||
if (!results || results.count === 0) return null;
|
||||
now = Date.now();
|
||||
const type = await getType(results.hits[0].document.type_id);
|
||||
return type;
|
||||
}
|
||||
|
||||
export async function typeSearchAutoComplete(name: string) {
|
||||
const normalizedName = normalize(name);
|
||||
if (normalizedName.length > 100) return null;
|
||||
const results = await search(db, {
|
||||
term: normalizedName,
|
||||
});
|
||||
if (!results || results.count === 0) return null;
|
||||
return results.hits.map((hit) => ({
|
||||
name: hit.document.name.en,
|
||||
value: hit.document.name.en,
|
||||
}));
|
||||
}
|
||||
28
packages/eve/tsconfig.json
Normal file
28
packages/eve/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@star-kitten/discord",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": false,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@data/*": ["./data/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
17
packages/eve/tsdown.config.ts
Normal file
17
packages/eve/tsdown.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
entry: [
|
||||
'./src/**/*.ts',
|
||||
'!./src/**/*.test.ts',
|
||||
],
|
||||
platform: 'neutral',
|
||||
dts: true,
|
||||
unbundle: true,
|
||||
external: [
|
||||
/^node:.*/,
|
||||
/^bun:.*/,
|
||||
],
|
||||
},
|
||||
]);
|
||||
Reference in New Issue
Block a user