Initial Commit

This commit is contained in:
JB
2025-10-06 23:31:31 -04:00
commit 0c8630b8ba
243 changed files with 166945 additions and 0 deletions

View 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 }}

View 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
View File

@@ -0,0 +1,6 @@
node_modules
dist
*.log
.DS_Store
data
coverage

View 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
View File

@@ -0,0 +1,6 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"oven.bun-vscode"
]
}

3
packages/eve/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}

23
packages/eve/README.md Normal file
View 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
View 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
View File

@@ -0,0 +1,7 @@
[test]
coverage = true
coverageSkipTestFiles = true
coverageReporter = ["text", "lcov"]
[run]
bun = true

View File

@@ -0,0 +1,7 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "sqlite",
schema: "./src/db/schema.ts",
out: "./drizzle",
});

View 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"
}
}
}

View 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."
}
}

View 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

File diff suppressed because it is too large Load Diff

70
packages/eve/package.json Normal file
View 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"
}
}

View 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();

View 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/`);
}

View 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]);
}

View 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);
}

View 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),
});
}

View 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;
}
}

View 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';

View 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);
}

View 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,
};

View 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;
}

View 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;
}

View File

@@ -0,0 +1,4 @@
export * from './esi/index';
export * from './db';
export * from './ref';
export * from './third-party';

View 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,
}

View 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,
})),
);
}

View 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;
}

View 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);
}

View 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)})`;
}

View 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}`;
}

View 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';

View 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 };

View 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)))));
}

View 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;
}

View 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;
}

View 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);
}

View 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;
}

View 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;
}

View 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
};
}

View 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;
}

View 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);
});
});
});

View 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}`;
case 8: // square meters
return `${value}`;
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;
}

View File

@@ -0,0 +1,7 @@
import attributeOrders from '@data/hoboleaks/attributeorders.json';
export const attributeOrdering = {
'11': attributeOrders['11'],
'87': attributeOrders['87'],
'default': attributeOrders.default,
};

View 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
View File

@@ -0,0 +1,2 @@
export * as evetycoon from './evetycoon';
export * as janice from './janice';

View 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
View 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;
}
};

View 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);
});
});
});

View 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' + text + '```\n';
case 'blue':
return '```ansi\n' + text + '```\n';
case 'yellow':
return '```ansi\n' + text + '```\n';
case 'green':
return '```ansi\n' + text + '```\n';
}
}

View 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,
}));
}

View 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/*"]
}
}
}

View 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:.*/,
],
},
]);