commit 2ecc23730d057951c57d0906cec7200ec3bbca1e Author: cita Date: Sat Feb 28 00:36:28 2026 +0800 chore: initialize clean metapi repository diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8b1f9f0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +node_modules +dist +data +.git +.env +*.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..25fbcb9 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +AUTH_TOKEN=change-me-admin-token +PROXY_TOKEN=change-me-proxy-sk-token +CHECKIN_CRON=0 8 * * * +BALANCE_REFRESH_CRON=0 * * * * +WEBHOOK_URL= +BARK_URL= +SERVERCHAN_KEY= +PORT=4000 +DATA_DIR=./data +NOTIFY_COOLDOWN_SEC=300 +ADMIN_IP_ALLOWLIST= +TZ=Asia/Shanghai diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a89fd30 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + +jobs: + test-and-build: + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Build + run: npm run build + + - name: Audit production dependencies + run: npm audit --omit=dev --audit-level=high diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..014e026 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +node_modules/ +dist/ +drizzle/ +data/* +!data/.gitkeep +.env +*.log +logs/ +tmp/* +!tmp/.gitkeep +.DS_Store +Thumbs.db +docker/data/ +docker/.env diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a943de9 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,26 @@ +# Code of Conduct + +## Our Commitment + +We are committed to providing a welcoming, harassment-free community for everyone. + +## Expected Behavior + +- Be respectful and constructive. +- Focus on technical discussion, not personal attacks. +- Assume good intent and ask clarifying questions before escalating. + +## Unacceptable Behavior + +- Harassment, discrimination, or abusive language +- Personal attacks or intimidation +- Publishing others' private information without consent + +## Enforcement + +Project maintainers are responsible for clarifying and enforcing this code of conduct. +They may remove, edit, or reject comments, commits, code, and contributions that do not align with this policy. + +## Scope + +This policy applies to project spaces and public/private interactions where an individual is representing the project. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..522f515 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ +# Contributing + +Thanks for contributing to Metapi. + +## Development Setup + +1. Install dependencies: + +```bash +npm install +``` + +2. Copy environment template: + +```bash +cp .env.example .env +``` + +3. Run migration and start development: + +```bash +npm run db:migrate +npm run dev +``` + +## Quality Checks + +Run before opening a PR: + +```bash +npm test +npm run build +``` + +## Pull Request Guidelines + +- Keep PRs focused and small. +- Add or update tests for behavior changes. +- Update docs when user-facing behavior/config changes. +- Avoid committing runtime data (`data/`) or temporary files (`tmp/`). + +## Commit Messages + +Use concise messages with clear scope, for example: + +- `feat: add token route health guard` +- `fix: handle empty model list in dashboard` +- `docs: clarify docker env setup` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0662b28 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Metapi contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d80c77 --- /dev/null +++ b/README.md @@ -0,0 +1,502 @@ +
+ +Metapi Logo + +# Metapi + +Metapi + +🔮 **中转站的中转站 — 将分散的 AI 中转站聚合为一个统一网关** + +

+把你在各处注册的 New API / One API / OneHub / DoneHub / Veloera / AnyRouter / Sub2API 等站点, +
+汇聚成 一个 API Key、一个入口,自动发现模型、智能路由、成本最优。 +

+ +

+ + GitHub Release + + Docker Pulls + + Docker Image + + License +Node.jsTypeScript +

+ +

+ 快速开始 • + 核心功能 • + 文档 • + 配置 • + 接入指南 • + 相关项目 +

+ +**[文档中心](docs/README.md) | [快速上手](docs/getting-started.md) | [部署指南](docs/deployment.md) | [配置说明](docs/configuration.md) | [常见问题](docs/faq.md) | [贡献指南](CONTRIBUTING.md)** + +
+ +--- + +## 📖 介绍 + +现在 AI 生态里有越来越多基于 New API / One API 系列的聚合中转站,要管理多个站点的余额、模型列表和 API 密钥,往往既分散又费时。 + +Metapi 作为这些中转站之上的聚合层,把多个站点统一到 **一个 API Key、一个入口**。当前已支持基于以下项目的中转站: + +- [New API](https://github.com/QuantumNous/new-api) +- [One API](https://github.com/songquanpeng/one-api) +- [OneHub](https://github.com/MartialBE/one-hub) +- [DoneHub](https://github.com/deanxv/done-hub) +- [Veloera](https://github.com/Veloera/Veloera) +- [AnyRouter](https://anyrouter.top) — 通用路由平台 +- [Sub2API](https://github.com/Wei-Shaw/sub2api) — 订阅制中转 + +| 痛点 | Metapi 怎么解决 | +|------|-----------------| +| 🔑 每个站点一个 Key,下游工具配置一堆 | **一个 Key 统一代理**,所有站点的模型自动聚合到 `/v1/*` | +| 💸 不知道哪个站点用某个模型最便宜 | **智能路由** 自动按成本、余额、使用率选最优通道 | +| 🔄 某个站点挂了,手动切换好麻烦 | **自动故障转移**,一个通道失败自动冷却并切到下一个 | +| 📊 余额分散在各处,不知道还剩多少 | **集中看板** 一目了然,余额不足自动告警 | +| ✅ 每天得去各站签到领额度 | **自动签到** 定时执行,奖励自动追踪 | +| 🤷 不知道哪个站有什么模型 | **自动模型发现**,上游新增模型零配置出现在你的模型列表里 | + +--- + +## ✨ 核心功能 + +### 🌐 统一代理网关 + +- 兼容 **OpenAI** 与 **Claude** 下游格式,对接所有主流客户端 +- 支持 Chat Completions / Messages / Completions / Embeddings / Images / Models 全接口 +- 完整的 SSE 流式传输支持,自动格式转换(OpenAI ⇄ Claude) + +### 🧠 智能路由引擎 + +- 自动发现所有上游站点的可用模型,**零配置**生成路由表 +- 四级成本信号:**实测成本 → 账号配置成本 → 目录参考价 → 默认兜底** +- 多通道概率分摊,基于成本(40%)、余额(30%)、使用率(30%)加权分配 +- 失败通道自动冷却与避让(默认 10 分钟冷却期) +- 请求失败自动重试,自动切换其他可用通道 +- 路由决策可视化解释,每次选择透明可审计 + +### 📡 多平台聚合管理 + +| 平台 | 适配器 | 说明 | +|------|--------|------| +| **New API** | `new-api` | 新一代大模型网关 | +| **One API** | `one-api` | 经典 OpenAI 接口聚合 | +| **OneHub** | `onehub` | One API 增强分支 | +| **DoneHub** | `donehub` | OneHub 增强分支 | +| **Veloera** | `veloera` | API 网关平台 | +| **AnyRouter** | `anyrouter` | 通用路由平台 | +| **Sub2API** | `sub2api` | 订阅制中转平台 | + +每种平台适配器均支持:账号登录、余额查询、模型枚举、Token 同步、每日签到、用户信息获取等完整生命周期管理。 + +### 👥 账号与 Token 管理 + +- **多站点多账号**:每个站点可添加多个账号,每个账号可持有多个 API Token +- **健康状态追踪**:`healthy` / `unhealthy` / `degraded` / `disabled` 四级状态机 +- **凭证加密存储**:所有敏感凭证均加密保存在本地数据库中 +- **自动续签**:Token 过期时自动重新登录获取新凭证 +- **站点联动**:禁用站点自动级联禁用所有关联账号 + +### 🏪 模型广场 + +- 跨站点模型覆盖总览:哪些模型可用、多少账号覆盖、各站定价对比 +- 延迟、成功率等实测指标展示 +- 上游模型目录缓存与品牌分类(OpenAI、Anthropic、Google、DeepSeek 等) +- 交互式模型测试器,在线验证模型可用性 + +### ✅ 自动签到 + +- Cron 定时自动签到(默认每天 08:00) +- 智能解析签到奖励金额,签到失败自动通知 +- 逐账号独立执行,支持启用/禁用控制 +- 完整的签到日志记录,支持历史查询 +- 并发锁机制,防止重复签到 + +### 💰 余额管理 + +- 定时余额刷新(默认每小时),批量更新所有活跃账号 +- 收入追踪:记录每日/累计收入,追踪额度消耗趋势 +- 余额兜底估算:API 不可用时,从代理日志推算余额变动 +- 自动重登录:凭证过期时自动刷新 + +### 🔔 告警与通知 + +支持四种通知渠道: + +| 渠道 | 说明 | +|------|------| +| **Webhook** | 自定义 HTTP 推送 | +| **Bark** | iOS 推送通知 | +| **Server酱** | 微信 / Telegram 通知 | +| **SMTP 邮件** | 标准邮件通知 | + +告警场景覆盖:余额不足预警、站点/账号异常、签到失败、代理请求失败、Token 过期提醒、每日摘要报告。支持告警冷却机制(默认 300 秒),防止相同事件重复通知。 + +### 📊 数据看板 + +- 站点余额分布饼图、每日消费趋势图表 +- 全局搜索(站点、账号、模型) +- 系统事件日志、代理请求日志(含模型、状态、延迟、Token 消耗、成本估算) + +### 📦 轻量部署 + +- **单 Docker 容器**,内置 SQLite,无外部依赖 +- Alpine 基础镜像,体积精简 +- 数据完整导入导出,迁移无忧 + +--- + +## 🖼️ 界面预览 + + + +> 📸 截图即将补充,欢迎 Star 关注更新! + +--- + +## 🚀 快速开始 + +### Docker Compose(推荐) + +```bash +mkdir metapi && cd metapi + +cat > docker-compose.yml << 'EOF' +services: + metapi: + image: 1467078763/metapi:latest + ports: + - "4000:4000" + volumes: + - ./data:/app/data + environment: + AUTH_TOKEN: ${AUTH_TOKEN:?AUTH_TOKEN is required} + PROXY_TOKEN: ${PROXY_TOKEN:?PROXY_TOKEN is required} + CHECKIN_CRON: "0 8 * * *" + BALANCE_REFRESH_CRON: "0 * * * *" + PORT: ${PORT:-4000} + DATA_DIR: /app/data + TZ: ${TZ:-Asia/Shanghai} + restart: unless-stopped +EOF + +# 设置令牌并启动 +export AUTH_TOKEN=your-admin-token +export PROXY_TOKEN=your-proxy-sk-token +docker compose up -d +``` + +
+Docker 命令一行启动 + +```bash +docker run -d --name metapi \ + -p 4000:4000 \ + -e AUTH_TOKEN=your-admin-token \ + -e PROXY_TOKEN=your-proxy-sk-token \ + -e TZ=Asia/Shanghai \ + -v ./data:/app/data \ + --restart unless-stopped \ + 1467078763/metapi:latest +``` + +
+ +🎉 启动后访问 `http://localhost:4000`,用 `AUTH_TOKEN` 登录即可! + +> [!IMPORTANT] +> 请务必修改 `AUTH_TOKEN` 和 `PROXY_TOKEN`,不要使用默认值。数据存储在 `./data` 目录中,升级不影响已有数据。 + +### 升级 + +```bash +docker compose pull && docker compose up -d && docker image prune -f +``` + +📖 更详细的部署方式请参考 [部署指南](docs/deployment.md) + +--- + +## 📚 文档中心 + +| 分类 | 链接 | 说明 | +|------|------|------| +| 📖 文档总览 | [docs/README.md](docs/README.md) | 文档导航与索引 | +| 🚀 快速上手 | [docs/getting-started.md](docs/getting-started.md) | 10 分钟启动 | +| 🚢 部署指南 | [docs/deployment.md](docs/deployment.md) | Docker Compose、反向代理、升级回滚 | +| ⚙️ 配置说明 | [docs/configuration.md](docs/configuration.md) | 全部环境变量与路由参数 | +| 🔌 客户端接入 | [docs/client-integration.md](docs/client-integration.md) | Open WebUI / Cherry Studio / Cursor 等 | +| 🔧 运维手册 | [docs/operations.md](docs/operations.md) | 备份恢复、日志排查、健康检查 | +| ❓ 常见问题 | [docs/faq.md](docs/faq.md) | 常见报错与修复路径 | + +--- + +## ⚙️ 环境变量 + +### 基础配置 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `AUTH_TOKEN` | 管理后台登录令牌(**必须修改**) | `change-me` | +| `PROXY_TOKEN` | 代理 API Bearer Token(**必须修改**) | `change-me-proxy` | +| `PORT` | 服务监听端口 | `4000` | +| `DATA_DIR` | 数据目录(SQLite 数据库) | `./data` | +| `TZ` | 时区 | `Asia/Shanghai` | +| `ACCOUNT_CREDENTIAL_SECRET` | 账号凭证加密密钥 | 默认使用 `AUTH_TOKEN` | + +### 定时任务 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `CHECKIN_CRON` | 自动签到 Cron 表达式 | `0 8 * * *` | +| `BALANCE_REFRESH_CRON` | 余额刷新 Cron 表达式 | `0 * * * *` | + +
+智能路由参数 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `ROUTING_FALLBACK_UNIT_COST` | 无成本信号时的默认单价 | `1` | +| `BASE_WEIGHT_FACTOR` | 基础权重因子 | `0.5` | +| `VALUE_SCORE_FACTOR` | 性价比评分因子 | `0.5` | +| `COST_WEIGHT` | 路由选择中成本权重 | `0.4` | +| `BALANCE_WEIGHT` | 路由选择中余额权重 | `0.3` | +| `USAGE_WEIGHT` | 路由选择中使用率权重 | `0.3` | + +
+ +
+通知渠道配置 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `WEBHOOK_ENABLED` | 启用 Webhook 通知 | `true` | +| `WEBHOOK_URL` | Webhook 推送地址 | 空 | +| `BARK_ENABLED` | 启用 Bark 推送 | `true` | +| `BARK_URL` | Bark 推送地址 | 空 | +| `SERVERCHAN_ENABLED` | 启用 Server酱 通知 | `true` | +| `SERVERCHAN_KEY` | Server酱 SendKey | 空 | +| `SMTP_ENABLED` | 启用邮件通知 | `false` | +| `SMTP_HOST` | SMTP 服务器地址 | 空 | +| `SMTP_PORT` | SMTP 端口 | `587` | +| `SMTP_SECURE` | 使用 SSL/TLS | `false` | +| `SMTP_USER` / `SMTP_PASS` | SMTP 认证 | 空 | +| `SMTP_FROM` / `SMTP_TO` | 发件/收件人 | 空 | +| `NOTIFY_COOLDOWN_SEC` | 相同告警冷却时间(秒) | `300` | + +
+ +
+安全配置 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `ADMIN_IP_ALLOWLIST` | 管理端 IP 白名单(逗号分隔) | 空(不限制) | + +
+ +📖 完整配置说明:[docs/configuration.md](docs/configuration.md) + +--- + +## 📡 代理接口 + +Metapi 对下游暴露标准 OpenAI / Claude 兼容接口: + +| 接口 | 方法 | 说明 | +|------|------|------| +| `/v1/chat/completions` | POST | OpenAI Chat Completions | +| `/v1/messages` | POST | Claude Messages | +| `/v1/completions` | POST | OpenAI Completions(Legacy) | +| `/v1/embeddings` | POST | 向量嵌入 | +| `/v1/images/generations` | POST | 图像生成 | +| `/v1/models` | GET | 获取所有可用模型列表 | + +请求头携带 `Authorization: Bearer ` 即可访问。 + +--- + +## 🔌 接入下游客户端 + +适用于所有兼容 OpenAI API 的客户端: + +| 配置项 | 值 | +|--------|-----| +| **Base URL** | `https://your-domain.com`(客户端一般会自动拼接 `/v1`) | +| **API Key** | 你设置的 `PROXY_TOKEN` 值 | +| **模型列表** | 自动从 `GET /v1/models` 获取 | + +### 已验证兼容的客户端 + +- [ChatGPT-Next-Web](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web) +- [Open WebUI](https://github.com/open-webui/open-webui) +- [Cherry Studio](https://github.com/kangfenmao/cherry-studio) +- [Cursor](https://cursor.sh) +- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) +- [Roo Code](https://github.com/RooVetGit/Roo-Code) +- [Kilo Code](https://github.com/kilocode/kilocode) +- 以及所有支持 OpenAI API 格式的客户端 + +
+常见问题:流式响应异常 + +如果非流式正常但流式异常,请排查: + +1. 反向代理是否关闭了 SSE 缓冲(Nginx 需设置 `proxy_buffering off`) +2. 中间层是否改写了 `text/event-stream` Content-Type +3. 客户端是否强制要求特定流式格式 + +**Nginx 参考配置:** + +```nginx +location / { + proxy_pass http://127.0.0.1:4000; + proxy_buffering off; + proxy_cache off; + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding off; +} +``` + +
+ +📖 更详细的接入说明:[docs/client-integration.md](docs/client-integration.md) + +--- + +## 🏗️ 技术栈 + +| 层 | 技术 | +|----|------| +| **后端框架** | [Fastify](https://fastify.dev) — 高性能 Node.js 后端框架 | +| **前端框架** | [React 18](https://react.dev) + [Vite](https://vitejs.dev) | +| **语言** | [TypeScript](https://www.typescriptlang.org) — 端到端类型安全 | +| **样式** | [Tailwind CSS v4](https://tailwindcss.com) — 原子化样式框架 | +| **数据库** | SQLite ([better-sqlite3](https://github.com/WiseLibs/better-sqlite3)) + [Drizzle ORM](https://orm.drizzle.team) | +| **数据可视化** | [VChart](https://visactor.io/vchart) (@visactor/react-vchart) | +| **定时任务** | [node-cron](https://github.com/node-cron/node-cron) | +| **容器化** | Docker (Alpine) + Docker Compose | +| **测试** | [Vitest](https://vitest.dev) | + +--- + +## 🛠️ 本地开发 + +```bash +# 安装依赖 +npm install + +# 数据库迁移 +npm run db:migrate + +# 启动开发环境(前后端热更新) +npm run dev +``` + +```bash +npm run build # 构建前端 + 后端 +npm run build:web # 仅构建前端(Vite) +npm run build:server # 仅构建后端(TypeScript) +npm test # 运行全部测试 +npm run test:watch # 监听模式 +npm run db:generate # 生成 Drizzle 迁移文件 +``` + +--- + +## 🔗 相关项目 + +### 上游兼容平台 + +| 项目 | 说明 | +|------|------| +| [New API](https://github.com/QuantumNous/new-api) | 新一代大模型网关,Metapi 的主要上游之一 | +| [One API](https://github.com/songquanpeng/one-api) | 经典 OpenAI 接口聚合管理 | +| [OneHub](https://github.com/MartialBE/one-hub) | One API 增强分支 | +| [DoneHub](https://github.com/deanxv/done-hub) | OneHub 增强分支 | +| [Veloera](https://github.com/Veloera/Veloera) | API 网关平台 | + +### 参考和使用的项目 + +| 项目 | 说明 | +|------|------| +| [All API Hub](https://github.com/qixing-jk/all-api-hub) | 浏览器扩展版 — 一站式管理中转站账号,Metapi 最初灵感来源 | +| [LLM Metadata](https://github.com/nicepkg/llm-metadata) | LLM 模型元数据库,用于模型描述参考 | +| [New API](https://github.com/QuantumNous/new-api) | 平台适配器参考实现 | + +--- + +## 🔒 数据与隐私 + +Metapi 完全自托管,所有数据(账号、令牌、路由、日志)均存储在本地 SQLite 数据库中,不会向任何第三方发送数据。代理请求仅在你的服务器与上游站点之间直连传输。 + +--- + +## 🤝 贡献 + +欢迎各种形式的贡献! + +- 🐛 报告 Bug — [提交 Issue](https://github.com/cita-777/metapi/issues) +- 💡 功能建议 — [发起讨论](https://github.com/cita-777/metapi/issues) +- 🔧 代码贡献 — [提交 Pull Request](https://github.com/cita-777/metapi/pulls) +- 📝 贡献指南 — [CONTRIBUTING.md](CONTRIBUTING.md) +- 📜 行为准则 — [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) + +--- + +## 🛡️ 安全 + +如发现安全问题,请参考 [SECURITY.md](SECURITY.md) 使用非公开方式报告。 + +--- + +## 📜 License + +[MIT](LICENSE) + +--- + +
+ +**⭐ 如果 Metapi 对你有帮助,给个 Star 就是最大的支持!** + +Built with ❤️ by the AI community + +
diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f2c81ac --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +## Supported Versions + +Only the latest `main` branch state is supported for security fixes. + +## Reporting a Vulnerability + +Please do not report security issues in public issues. + +Use one of these channels: + +- GitHub Security Advisory private report (preferred) +- Maintainer contact listed in project profile + +When reporting, include: + +- Affected endpoint/module +- Reproduction steps +- Potential impact +- Suggested fix (if available) + +We will acknowledge reports as soon as possible and coordinate a fix and disclosure timeline. diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..6a9d0bc --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,18 @@ +# Meta API Hub - Environment Variables +# Copy this file to .env and fill in your values +# cp .env.example .env + +# Admin authentication token (required, change this!) +AUTH_TOKEN=change-me-admin-token + +# Proxy API token used for OpenAI-compatible proxy endpoint (required, change this!) +PROXY_TOKEN=change-me-proxy-sk-token +PORT=4000 +NOTIFY_COOLDOWN_SEC=300 +ADMIN_IP_ALLOWLIST= + +# Auto check-in cron (default: 8:00 AM every day) +CHECKIN_CRON=0 8 * * * + +# Balance refresh cron (default: every hour) +BALANCE_REFRESH_CRON=0 * * * * diff --git a/docker/.gitkeep b/docker/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..31ba0c6 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,27 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +FROM node:20-alpine + +WORKDIR /app + +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/package.json ./ +COPY --from=builder /app/drizzle ./drizzle + +RUN mkdir -p /app/data + +EXPOSE 4000 + +ENV NODE_ENV=production +ENV DATA_DIR=/app/data + +CMD ["sh", "-c", "node dist/server/db/migrate.js && node dist/server/index.js"] diff --git a/docker/docker-compose.override.yml b/docker/docker-compose.override.yml new file mode 100644 index 0000000..6c011b1 --- /dev/null +++ b/docker/docker-compose.override.yml @@ -0,0 +1,9 @@ +# docker-compose.override.yml +# This file is automatically picked up by Docker Compose during local development. +# It overrides the image reference in docker-compose.yml with a local build. +services: + metapi: + image: ~ + build: + context: .. + dockerfile: docker/Dockerfile diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..360a8f4 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,16 @@ +services: + metapi: + image: 1467078763/metapi:latest + ports: + - "4000:4000" + volumes: + - ./data:/app/data + environment: + AUTH_TOKEN: ${AUTH_TOKEN:?AUTH_TOKEN is required} + PROXY_TOKEN: ${PROXY_TOKEN:?PROXY_TOKEN is required} + CHECKIN_CRON: "0 8 * * *" + BALANCE_REFRESH_CRON: "0 * * * *" + PORT: ${PORT:-4000} + DATA_DIR: /app/data + TZ: ${TZ:-Asia/Shanghai} + restart: unless-stopped diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..cf41d1d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,67 @@ +# 📚 Metapi 文档中心 + +
+ +**中转站的中转站 — 将分散的 AI 中转站聚合为一个统一网关** + +[返回项目主页](../README.md) + +
+ +--- + +## 快速导航 + +| 文档 | 适合谁 | 解决的问题 | +|------|--------|------------| +| 🚀 [快速上手](./getting-started.md) | 首次使用者 | 10 分钟完成部署与首次请求 | +| 🚢 [部署指南](./deployment.md) | 部署维护者 | Docker Compose、反向代理、升级回滚 | +| ⚙️ [配置说明](./configuration.md) | 管理员 | 全部环境变量、路由参数、通知渠道 | +| 🔌 [客户端接入](./client-integration.md) | 下游应用接入者 | Open WebUI、Cherry Studio、Cursor 等接入 | +| 🔧 [运维手册](./operations.md) | 运维人员 | 备份恢复、日志排查、健康检查 | +| ❓ [常见问题](./faq.md) | 所有用户 | 常见报错与修复路径 | +| 📁 [目录规范](./project-structure.md) | 开发者 | 项目目录组织与约定 | + +## 架构概览 + +```text +┌──────────────────────────────────────────────────┐ +│ 下游客户端 │ +│ Open WebUI / Cherry Studio / Cursor / Claude │ +│ Code / Roo Code / Kilo Code / ... │ +└───────────────────┬──────────────────────────────┘ + │ Authorization: Bearer + ▼ +┌──────────────────────────────────────────────────┐ +│ Metapi 网关 │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ 统一代理 │ │ 智能路由 │ │ 格式转换 │ │ +│ │ /v1/* │ │ 成本+余额 │ │ OpenAI⇄ │ │ +│ │ │ │ +使用率 │ │ Claude │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ 自动签到 │ │ 余额管理 │ │ 多渠道告警 │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +└───────────────────┬──────────────────────────────┘ + │ + ┌───────────┼───────────┐ + ▼ ▼ ▼ + ┌─────────┐ ┌─────────┐ ┌─────────┐ + │ New API │ │ One API │ │ Veloera │ ... + └─────────┘ └─────────┘ └─────────┘ +``` + +## 核心概念 + +- **站点 (Site)**:一个上游中转站实例(如 New API、OneHub 等) +- **账号 (Account)**:在某站点上注册的用户账号 +- **Token**:账号下的 API Key,用于访问该站点的 API +- **路由 (Route)**:一条模型匹配规则,如 `claude-sonnet-4-6` +- **通道 (Channel)**:路由下的一条 Token 链路,一个路由可有多个通道 +- **代理 (Proxy)**:Metapi 对下游暴露的统一 API 入口 + +## 开源协作 + +- 📝 贡献流程:[CONTRIBUTING.md](../CONTRIBUTING.md) +- 🛡️ 安全策略:[SECURITY.md](../SECURITY.md) +- 📜 行为准则:[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md) diff --git a/docs/client-integration.md b/docs/client-integration.md new file mode 100644 index 0000000..d2ecf2d --- /dev/null +++ b/docs/client-integration.md @@ -0,0 +1,127 @@ +# 🔌 客户端接入 + +本文档说明如何将下游客户端连接到 Metapi 代理网关。 + +[返回文档中心](./README.md) + +--- + +## 通用配置 + +Metapi 暴露标准 OpenAI / Claude 兼容接口,下游客户端只需配置两项: + +| 配置项 | 值 | +|--------|-----| +| **Base URL** | `https://your-domain.com`(不要拼接 `/v1`,客户端会自动加) | +| **API Key** | 你设置的 `PROXY_TOKEN` 值 | + +模型列表自动从 `GET /v1/models` 获取,无需手动配置。 + +## 支持的接口 + +| 接口 | 方法 | 说明 | +|------|------|------| +| `/v1/chat/completions` | POST | OpenAI Chat Completions | +| `/v1/messages` | POST | Claude Messages | +| `/v1/completions` | POST | OpenAI Completions(Legacy) | +| `/v1/embeddings` | POST | 向量嵌入 | +| `/v1/images/generations` | POST | 图像生成 | +| `/v1/models` | GET | 模型列表 | + +## 已验证兼容的客户端 + +### ChatGPT-Next-Web + +| 配置项 | 值 | +|--------|-----| +| Settings → Custom Endpoint | `https://your-domain.com` | +| API Key | `PROXY_TOKEN` | + +### Open WebUI + +| 配置项 | 值 | +|--------|-----| +| Settings → Connections → OpenAI API URL | `https://your-domain.com/v1` | +| API Key | `PROXY_TOKEN` | + +### Cherry Studio + +| 配置项 | 值 | +|--------|-----| +| 模型提供商 → OpenAI → API 地址 | `https://your-domain.com` | +| API Key | `PROXY_TOKEN` | + +### Cursor + +| 配置项 | 值 | +|--------|-----| +| Settings → Models → OpenAI API Key | `PROXY_TOKEN` | +| Override OpenAI Base URL | `https://your-domain.com/v1` | + +### Claude Code + +```bash +export ANTHROPIC_BASE_URL=https://your-domain.com +export ANTHROPIC_API_KEY=your-proxy-sk-token +``` + +或在配置文件中设置相应的环境变量。 + +### Roo Code / Kilo Code + +配置方式与 Cursor 类似,在设置中填入 Base URL 和 API Key。 + +### 其他客户端 + +所有支持 OpenAI API 格式的客户端均可接入,只需找到 Base URL 和 API Key 的配置位置即可。 + +## 快速自检 + +部署完成后,用以下命令验证链路: + +```bash +# 1. 检查模型列表 +curl -sS https://your-domain.com/v1/models \ + -H "Authorization: Bearer " | head -50 + +# 2. 测试对话(非流式) +curl -sS https://your-domain.com/v1/chat/completions \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}' + +# 3. 测试流式 +curl -sS https://your-domain.com/v1/chat/completions \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"stream":true}' +``` + +## 常见问题 + +### 流式响应异常 + +如果非流式正常但流式异常,原因几乎都是反向代理配置问题: + +1. Nginx 未设置 `proxy_buffering off` +2. CDN 或中间层缓存了 SSE 响应 +3. 中间层改写了 `text/event-stream` Content-Type + +参考 [部署指南 → Nginx 配置](./deployment.md#nginx) 解决。 + +### 模型列表为空 + +- 检查是否已添加站点和账号 +- 检查账号是否处于 `healthy` 状态 +- 检查是否已同步 Token +- 在管理后台手动触发「刷新模型」 + +### 客户端提示 401 / 403 + +- 确认使用的是 `PROXY_TOKEN` 而非 `AUTH_TOKEN` +- 确认反向代理透传了 `Authorization` 请求头 + +## 下一步 + +- [配置说明](./configuration.md) — 环境变量详解 +- [常见问题](./faq.md) — 更多故障排查 diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..1c1c1c7 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,137 @@ +# ⚙️ 配置说明 + +本文档列出 Metapi 的全部环境变量配置。 + +[返回文档中心](./README.md) + +--- + +## 必填配置 + +> ⚠️ 以下变量**必须修改**,不要使用默认值。 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `AUTH_TOKEN` | 管理后台登录令牌 | `change-me` | +| `PROXY_TOKEN` | 代理接口 Bearer Token(下游客户端使用此值作为 API Key) | `change-me-proxy` | + +## 基础配置 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `PORT` | 服务监听端口 | `4000` | +| `DATA_DIR` | 数据目录(SQLite 数据库存储位置) | `./data` | +| `TZ` | 时区 | `Asia/Shanghai` | +| `ACCOUNT_CREDENTIAL_SECRET` | 账号凭证加密密钥(用于加密存储的上游账号密码) | 默认使用 `AUTH_TOKEN` | + +## 定时任务 + +| 变量名 | 说明 | 默认值 | 示例 | +|--------|------|--------|------| +| `CHECKIN_CRON` | 自动签到计划 | `0 8 * * *` | 每天 8:00 | +| `BALANCE_REFRESH_CRON` | 余额刷新计划 | `0 * * * *` | 每小时整点 | + +Cron 表达式格式:`分 时 日 月 周`(标准五段式) + +常用示例: +- `0 8 * * *` — 每天 08:00 +- `0 */2 * * *` — 每 2 小时 +- `30 7,12,20 * * *` — 每天 07:30、12:30、20:30 + +## 智能路由 + +Metapi 的路由引擎按多因子加权选择最优通道。 + +### 成本信号优先级 + +``` +实测成本(代理日志) → 账号配置成本 → 目录参考价 → 兜底默认值 +``` + +### 路由权重参数 + +| 变量名 | 说明 | 默认值 | 范围 | +|--------|------|--------|------| +| `ROUTING_FALLBACK_UNIT_COST` | 无成本信号时的默认单价 | `1` | > 0 | +| `BASE_WEIGHT_FACTOR` | 基础权重因子 | `0.5` | 0~1 | +| `VALUE_SCORE_FACTOR` | 性价比评分因子 | `0.5` | 0~1 | +| `COST_WEIGHT` | 成本权重(越大越偏向低成本通道) | `0.4` | 0~1 | +| `BALANCE_WEIGHT` | 余额权重(越大越偏向余额充足的通道) | `0.3` | 0~1 | +| `USAGE_WEIGHT` | 使用率权重(越大越偏向使用较少的通道) | `0.3` | 0~1 | + +> 三个权重之和建议为 1.0,但不强制。 + +### 路由预设建议 + +| 场景 | COST_WEIGHT | BALANCE_WEIGHT | USAGE_WEIGHT | +|------|:-----------:|:--------------:|:------------:| +| **成本优先** | 0.7 | 0.2 | 0.1 | +| **均衡(默认)** | 0.4 | 0.3 | 0.3 | +| **稳定优先** | 0.2 | 0.5 | 0.3 | +| **轮转均匀** | 0.1 | 0.1 | 0.8 | + +## 安全配置 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `ADMIN_IP_ALLOWLIST` | 管理端 IP 白名单(逗号分隔) | 空(不限制) | + +示例:`ADMIN_IP_ALLOWLIST=192.168.1.0/24,10.0.0.1` + +## 通知渠道 + +### Webhook + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `WEBHOOK_ENABLED` | 启用 Webhook 通知 | `true` | +| `WEBHOOK_URL` | Webhook 推送地址 | 空 | + +### Bark(iOS 推送) + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `BARK_ENABLED` | 启用 Bark 推送 | `true` | +| `BARK_URL` | Bark 推送地址 | 空 | + +### Server酱 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `SERVERCHAN_ENABLED` | 启用 Server酱 通知 | `true` | +| `SERVERCHAN_KEY` | Server酱 SendKey | 空 | + +### SMTP 邮件 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `SMTP_ENABLED` | 启用邮件通知 | `false` | +| `SMTP_HOST` | SMTP 服务器地址 | 空 | +| `SMTP_PORT` | SMTP 端口 | `587` | +| `SMTP_SECURE` | 使用 SSL/TLS | `false` | +| `SMTP_USER` | SMTP 用户名 | 空 | +| `SMTP_PASS` | SMTP 密码 | 空 | +| `SMTP_FROM` | 发件人地址 | 空 | +| `SMTP_TO` | 收件人地址 | 空 | + +### 告警控制 + +| 变量名 | 说明 | 默认值 | +|--------|------|--------| +| `NOTIFY_COOLDOWN_SEC` | 相同告警冷却时间(秒),防止同一事件重复通知 | `300` | + +## 运行时配置 + +除环境变量外,以下参数可在管理后台「设置」页面中动态调整,无需重启: + +- 路由权重参数 +- 通知渠道地址 +- SMTP 配置 +- 告警冷却时间 + +运行时配置存储在 SQLite 数据库中,优先级高于环境变量默认值。 + +## 下一步 + +- [部署指南](./deployment.md) — Docker Compose 与反向代理 +- [客户端接入](./client-integration.md) — 对接下游应用 diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..7a75857 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,157 @@ +# 🚢 部署指南 + +[返回文档中心](./README.md) + +--- + +## Docker Compose 部署(推荐) + +### 标准步骤 + +```bash +mkdir metapi && cd metapi + +# 创建 docker-compose.yml(参见快速上手) +# 设置环境变量 +export AUTH_TOKEN=your-admin-token +export PROXY_TOKEN=your-proxy-sk-token + +# 启动 +docker compose up -d +``` + +### 使用 `.env` 文件 + +如果不想每次 export,可以创建 `.env` 文件: + +```bash +# .env +AUTH_TOKEN=your-admin-token +PROXY_TOKEN=your-proxy-sk-token +TZ=Asia/Shanghai +PORT=4000 +``` + +```bash +docker compose --env-file .env up -d +``` + +> ⚠️ `.env` 文件包含敏感信息,请勿提交到 Git 仓库。 + +## Docker 命令部署 + +```bash +docker run -d --name metapi \ + -p 4000:4000 \ + -e AUTH_TOKEN=your-admin-token \ + -e PROXY_TOKEN=your-proxy-sk-token \ + -e TZ=Asia/Shanghai \ + -v ./data:/app/data \ + --restart unless-stopped \ + 1467078763/metapi:latest +``` + +> **路径说明:** +> - `./data:/app/data` — 相对路径,数据存到当前目录下的 `data` 文件夹 +> - 也可以使用绝对路径:`/your/custom/path:/app/data` + +## 反向代理 + +### Nginx + +流式请求(SSE)需要关闭缓冲,否则流式输出会异常: + +```nginx +server { + listen 443 ssl; + server_name your-domain.com; + + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + location / { + proxy_pass http://127.0.0.1:4000; + + # SSE 关键配置 + proxy_buffering off; + proxy_cache off; + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding off; + + # 标准代理头 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 超时设置(长对话场景) + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } +} +``` + +### Caddy + +``` +your-domain.com { + reverse_proxy localhost:4000 { + flush_interval -1 + } +} +``` + +## 升级 + +```bash +# 拉取最新镜像 +docker compose pull + +# 重新启动(数据不受影响) +docker compose up -d + +# 清理旧镜像 +docker image prune -f +``` + +## 回滚 + +如果升级后出现问题: + +1. **升级前备份**(建议每次升级前执行): + +```bash +cp -r data/ data-backup-$(date +%Y%m%d)/ +``` + +2. **回滚到指定版本**: + +```bash +# 修改 docker-compose.yml 中的 image tag +# 例如:image: 1467078763/metapi:v1.0.0 + +# 恢复数据 +rm -rf data/ +cp -r data-backup-20260228/ data/ + +# 重启 +docker compose up -d +``` + +## 数据持久化 + +Metapi 的所有运行数据存储在 SQLite 数据库中,位于 `DATA_DIR`(默认 `./data`)目录下。 + +只要挂载了该目录,升级、重启都不会丢失数据。 + +### 备份策略建议 + +- 每日自动备份 `data/` 目录 +- 保留最近 7~30 天的备份 +- 重要操作前手动快照 + +## 下一步 + +- [配置说明](./configuration.md) — 详细环境变量 +- [运维手册](./operations.md) — 日志排查、健康检查 diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..f854cd5 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,131 @@ +# ❓ 常见问题(FAQ) + +[返回文档中心](./README.md) + +--- + +## 部署相关 + +### Q: 启动后无法访问管理后台 + +**A:** 排查步骤: + +1. 确认容器正常运行:`docker compose ps` +2. 确认端口映射正确:`docker compose logs | grep listening` +3. 检查防火墙是否放行了端口(默认 4000) +4. 如果使用反向代理,确认代理配置正确 + +### Q: 登录失败,提示令牌无效 + +**A:** 检查 `AUTH_TOKEN` 是否与部署环境一致。注意: + +- 使用 `.env` 文件时,确认文件路径正确 +- 不要混用 `.env.example` 的默认值 +- 环境变量中的值不需要加引号 + +### Q: Docker Compose 启动报错 `AUTH_TOKEN is required` + +**A:** 使用了 `${AUTH_TOKEN:?}` 语法,需要先设置环境变量: + +```bash +export AUTH_TOKEN=your-token +export PROXY_TOKEN=your-proxy-token +docker compose up -d +``` + +或使用 `.env` 文件。 + +--- + +## 代理相关 + +### Q: 下游客户端提示 401 / 403 + +**A:** 排查: + +- 确认使用的是 `PROXY_TOKEN`(代理令牌),而非 `AUTH_TOKEN`(管理令牌) +- 确认反向代理正确透传了 `Authorization` 请求头 +- 检查是否设置了 `ADMIN_IP_ALLOWLIST` 限制了访问 + +### Q: `GET /v1/models` 返回空列表 + +**A:** 可能原因: + +1. 未添加任何站点或账号 +2. 账号处于 `unhealthy` 状态 — 在账号管理页面检查并刷新 +3. 未同步 Token — 在 Token 管理页面点击「同步」 +4. 模型未发现 — 手动触发模型刷新 + +### Q: 非流式正常,但流式输出异常(卡住、乱码、截断) + +**A:** 几乎都是反向代理配置问题。请确认: + +1. Nginx:添加 `proxy_buffering off;` +2. 未改写 `text/event-stream` Content-Type +3. 无 CDN 或中间层缓存 SSE 响应 + +完整 Nginx 配置参考 [部署指南](./deployment.md#nginx)。 + +### Q: 某模型显示可用,但实际调用失败 + +**A:** 在管理后台的「模型测试器」中直测该模型,查看具体失败原因: + +- **上游账号状态异常**:账号凭证过期或被禁用 +- **通道处于冷却期**:近期该通道请求失败,系统自动冷却(默认 10 分钟) +- **上游模型下线**:上游站点已移除该模型 +- **余额不足**:对应账号余额已耗尽 + +### Q: 请求延迟很高 + +**A:** 排查方向: + +- 在代理日志中查看具体延迟分布 +- 检查是否因冷却导致使用了较远/较慢的上游 +- 调整路由权重,降低 `COST_WEIGHT`、提高成功率高的通道优先级 + +--- + +## 签到相关 + +### Q: 签到一直失败 + +**A:** 可能原因: + +- 上游站点不支持签到功能 +- 账号凭证已过期(系统会尝试自动重登录) +- 站点接口变更 — 检查 Metapi 是否为最新版本 + +### Q: 签到成功但奖励显示为 0 + +**A:** 部分站点的签到接口不返回奖励金额。Metapi 会尝试从收入日志推算奖励,但可能存在延迟。 + +--- + +## 数据相关 + +### Q: 数据迁移怎么做 + +**A:** 两种方式: + +1. **应用内导入导出**(推荐):在管理后台 → 导入/导出 页面操作,支持选择性导出 +2. **目录迁移**:直接拷贝 `data/` 目录到新环境 + +### Q: 如何清理历史数据 + +**A:** 代理日志和签到日志会持续增长。在管理后台对应页面可以清理历史记录。 + +### Q: 开源发布时如何避免泄露敏感信息 + +**A:** + +- 确认 `.gitignore` 包含 `.env`、`data/`、`tmp/` +- 发布前执行一次密钥轮换(上游账号密码、通知 SMTP、Webhook 地址) +- 使用全新仓库或清理 Git 历史后再公开 +- 检查备份 JSON 文件中是否包含凭证 + +--- + +## 更多帮助 + +- [提交 Issue](https://github.com/cita-777/metapi/issues) — 报告 Bug 或提出建议 +- [文档中心](./README.md) — 查看所有文档 diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..35c4a49 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,124 @@ +# 🚀 快速上手 + +本文档帮助你在 10 分钟内完成 Metapi 的首次部署。 + +[返回文档中心](./README.md) + +--- + +## 前置条件 + +- Docker 与 Docker Compose(推荐) +- 或 Node.js 20+ 与 npm(本地开发) + +## 方式一:Docker Compose 部署(推荐) + +### 1. 创建项目目录 + +```bash +mkdir metapi && cd metapi +``` + +### 2. 创建 `docker-compose.yml` + +```yaml +services: + metapi: + image: 1467078763/metapi:latest + ports: + - "4000:4000" + volumes: + - ./data:/app/data + environment: + AUTH_TOKEN: ${AUTH_TOKEN:?AUTH_TOKEN is required} + PROXY_TOKEN: ${PROXY_TOKEN:?PROXY_TOKEN is required} + CHECKIN_CRON: "0 8 * * *" + BALANCE_REFRESH_CRON: "0 * * * *" + PORT: ${PORT:-4000} + DATA_DIR: /app/data + TZ: ${TZ:-Asia/Shanghai} + restart: unless-stopped +``` + +### 3. 设置令牌并启动 + +```bash +export AUTH_TOKEN=your-admin-token +export PROXY_TOKEN=your-proxy-sk-token +docker compose up -d +``` + +### 4. 访问管理后台 + +打开 `http://localhost:4000`,使用 `AUTH_TOKEN` 的值登录。 + +## 方式二:本地开发启动 + +```bash +git clone https://github.com/cita-777/metapi.git +cd metapi +npm install +npm run db:migrate +npm run dev +``` + +- 前端地址:`http://localhost:5173`(Vite dev server) +- 后端地址:`http://localhost:4000` + +## 首次使用流程 + +完成部署后,按以下顺序配置: + +### 步骤 1:添加站点 + +进入 **站点管理**,添加你使用的上游中转站: + +- 填写站点名称和 URL +- 选择平台类型(New API / One API / OneHub 等) +- 填写站点的管理员 API Key(可选,部分功能需要) + +### 步骤 2:添加账号 + +进入 **账号管理**,为每个站点添加已注册的账号: + +- 填入用户名和访问凭证 +- 系统会自动登录并获取余额信息 +- 启用自动签到(如站点支持) + +### 步骤 3:同步 Token + +进入 **Token 管理**: + +- 点击「同步」从上游账号拉取 API Key +- 或手动添加已有的 API Key + +### 步骤 4:检查路由 + +进入 **路由管理**: + +- 系统会自动发现模型并生成路由规则 +- 可以手动调整通道的优先级和权重 + +### 步骤 5:验证代理 + +使用 curl 快速验证: + +```bash +# 检查模型列表 +curl -sS http://localhost:4000/v1/models \ + -H "Authorization: Bearer your-proxy-sk-token" + +# 测试对话 +curl -sS http://localhost:4000/v1/chat/completions \ + -H "Authorization: Bearer your-proxy-sk-token" \ + -H "Content-Type: application/json" \ + -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}' +``` + +返回正常响应,说明一切就绪。 + +## 下一步 + +- [部署指南](./deployment.md) — 反向代理、HTTPS、升级策略 +- [配置说明](./configuration.md) — 详细环境变量与路由参数 +- [客户端接入](./client-integration.md) — 对接 Open WebUI、Cherry Studio 等 diff --git a/docs/logos/drafts/logo1.png b/docs/logos/drafts/logo1.png new file mode 100644 index 0000000..6bff4e7 Binary files /dev/null and b/docs/logos/drafts/logo1.png differ diff --git a/docs/logos/drafts/logo2.png b/docs/logos/drafts/logo2.png new file mode 100644 index 0000000..953bc2f Binary files /dev/null and b/docs/logos/drafts/logo2.png differ diff --git a/docs/logos/drafts/logo3.png b/docs/logos/drafts/logo3.png new file mode 100644 index 0000000..f9a3503 Binary files /dev/null and b/docs/logos/drafts/logo3.png differ diff --git a/docs/logos/logo-banner-sm.png b/docs/logos/logo-banner-sm.png new file mode 100644 index 0000000..6bd627b Binary files /dev/null and b/docs/logos/logo-banner-sm.png differ diff --git a/docs/logos/logo-banner.png b/docs/logos/logo-banner.png new file mode 100644 index 0000000..0d12c7c Binary files /dev/null and b/docs/logos/logo-banner.png differ diff --git a/docs/logos/logo-full-sm.png b/docs/logos/logo-full-sm.png new file mode 100644 index 0000000..6747dfa Binary files /dev/null and b/docs/logos/logo-full-sm.png differ diff --git a/docs/logos/logo-full.png b/docs/logos/logo-full.png new file mode 100644 index 0000000..964162e Binary files /dev/null and b/docs/logos/logo-full.png differ diff --git a/docs/logos/logo-icon-128.png b/docs/logos/logo-icon-128.png new file mode 100644 index 0000000..fc96d14 Binary files /dev/null and b/docs/logos/logo-icon-128.png differ diff --git a/docs/logos/logo-icon-32.png b/docs/logos/logo-icon-32.png new file mode 100644 index 0000000..ac04be6 Binary files /dev/null and b/docs/logos/logo-icon-32.png differ diff --git a/docs/logos/logo-icon-512.png b/docs/logos/logo-icon-512.png new file mode 100644 index 0000000..a1ed245 Binary files /dev/null and b/docs/logos/logo-icon-512.png differ diff --git a/docs/logos/logo-icon-64.png b/docs/logos/logo-icon-64.png new file mode 100644 index 0000000..ae18e8b Binary files /dev/null and b/docs/logos/logo-icon-64.png differ diff --git a/docs/logos/logo.png b/docs/logos/logo.png new file mode 100644 index 0000000..0b72ccd Binary files /dev/null and b/docs/logos/logo.png differ diff --git a/docs/operations.md b/docs/operations.md new file mode 100644 index 0000000..a6ab49a --- /dev/null +++ b/docs/operations.md @@ -0,0 +1,145 @@ +# 🔧 运维手册 + +[返回文档中心](./README.md) + +--- + +## 数据备份 + +### 方式一:目录备份(推荐) + +Metapi 的所有数据存储在 `data/` 目录下的 SQLite 数据库中。最简单的备份方式: + +```bash +# 手动备份 +cp -r data/ data-backup-$(date +%Y%m%d)/ + +# 自动备份(crontab) +0 2 * * * cp -r /path/to/metapi/data/ /path/to/backups/metapi-$(date +\%Y\%m\%d)/ +``` + +建议: +- 每日自动备份一次 +- 保留最近 7~30 天 +- 备份文件不要提交到 Git + +### 方式二:应用内导出 + +在管理后台 → 「导入/导出」页面: + +- **全量导出**:站点、账号、Token、路由、设置 +- **仅账号**:站点和账号信息 +- **仅偏好**:设置和通知配置 + +导出为 JSON 文件,可用于跨实例迁移。 + +## 数据恢复 + +### 目录恢复 + +```bash +# 1. 停止容器 +docker compose down + +# 2. 替换数据目录 +rm -rf data/ +cp -r data-backup-20260228/ data/ + +# 3. 重新启动 +docker compose up -d +``` + +### 应用内导入 + +在管理后台 → 「导入/导出」页面上传之前导出的 JSON 文件。系统会自动校验数据完整性。 + +## 日志排查 + +### Docker 环境 + +```bash +# 查看实时日志 +docker compose logs -f + +# 查看最近 100 行 +docker compose logs --tail 100 + +# 只看错误 +docker compose logs -f 2>&1 | grep -i error +``` + +### 本地开发 + +```bash +npm run dev +# 日志直接输出到终端 +``` + +### 重点关注的日志 + +| 关键词 | 含义 | 处理方式 | +|--------|------|----------| +| `auth failed` | 上游站点鉴权失败 | 检查账号凭证是否过期 | +| `no available channel` | 路由无可用通道 | 检查 Token 是否同步、通道是否被冷却 | +| `notify failed` | 通知发送失败 | 检查通知渠道配置 | +| `checkin failed` | 签到失败 | 检查账号状态和站点连通性 | +| `balance refresh failed` | 余额刷新失败 | 检查账号凭证 | + +## 健康检查 + +### 手动检查 + +```bash +# 检查服务是否响应 +curl -sS http://localhost:4000/v1/models \ + -H "Authorization: Bearer " | head -5 + +# 检查特定模型可用性 +curl -sS http://localhost:4000/v1/chat/completions \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}' +``` + +### 自动化监控建议 + +- 定时请求 `/v1/models`,检查返回状态码和模型数量 +- 定时抽样请求 `/v1/chat/completions`,检查端到端可用性 +- 监控磁盘空间(SQLite WAL 日志可能增长) +- 监控 Docker 容器状态 + +## 常见运维操作 + +### 清理代理日志 + +代理日志会持续增长。如果磁盘空间紧张,可在管理后台 → 代理日志页面清理历史记录。 + +### 重置账号状态 + +如果账号状态异常(`unhealthy`),可以在账号管理页面: + +1. 点击「刷新」重新检测账号健康状态 +2. 如凭证过期,系统会尝试自动重登录 +3. 手动禁用/启用账号 + +### 强制刷新模型 + +在管理后台手动触发: + +- 余额刷新:立即更新所有账号余额 +- 模型刷新:重新发现所有上游模型 +- 签到:立即执行一次签到 + +## 发布前检查清单 + +如果你在本地开发并准备发布: + +- [ ] `npm test` 通过 +- [ ] `npm run build` 通过 +- [ ] `.env`、`data/`、`tmp/` 未提交到 Git +- [ ] 敏感凭证已从代码中移除 + +## 下一步 + +- [常见问题](./faq.md) — 常见报错与修复 +- [配置说明](./configuration.md) — 环境变量详解 diff --git a/docs/project-structure.md b/docs/project-structure.md new file mode 100644 index 0000000..2751be4 --- /dev/null +++ b/docs/project-structure.md @@ -0,0 +1,148 @@ +# Metapi 项目目录规范 + +本文档说明 Metapi 的目录组织方式,目标是让新功能落位更稳定、查找更直接、临时文件不污染根目录。 + +## 顶层目录 + +```text +metapi/ +├── data/ # 运行时数据(SQLite 数据库) +├── dist/ # 构建产物(前后端) +├── docker/ # 容器相关文件 +│ ├── Dockerfile # 多阶段构建(Alpine) +│ └── docker-compose.yml # Docker Compose 编排 +├── docs/ # 文档与资源 +│ ├── logos/ # Logo 素材 +│ │ └── drafts/ # Logo 草稿 +│ └── project-structure.md +├── drizzle/ # Drizzle ORM 迁移 SQL 与元数据 +├── scripts/ # 项目脚本(按场景分组) +│ └── dev/ +│ └── restart.bat # Windows 开发环境快捷重启 +├── src/ +│ ├── server/ # Fastify 后端服务 +│ └── web/ # React 前端应用 +├── tmp/ # 临时调试文件(已 gitignore) +├── package.json +├── tsconfig.json +├── tsconfig.server.json +├── vite.config.ts +└── README.md +``` + +## 后端目录约定(`src/server`) + +```text +src/server/ +├── index.ts # 服务启动入口 +├── config.ts # 环境变量与配置加载 +├── middleware/ +│ └── auth.ts # 认证中间件 +├── db/ +│ ├── index.ts # 数据库连接 +│ ├── schema.ts # Drizzle 表定义(全部 Schema) +│ └── migrate.ts # 迁移执行器 +├── routes/ +│ ├── api/ # 管理 API 路由 +│ │ ├── auth.ts # 登录 / 登出 +│ │ ├── sites.ts # 站点 CRUD +│ │ ├── accounts.ts # 账号管理 +│ │ ├── accountTokens.ts # Token 同步与管理 +│ │ ├── tokens.ts # Token 批量操作 +│ │ ├── tokenRoutes.ts # 路由规则管理 +│ │ ├── checkin.ts # 签到触发与日志 +│ │ ├── stats.ts # 仪表盘统计 +│ │ ├── search.ts # 全局搜索 +│ │ ├── events.ts # 事件日志 +│ │ ├── tasks.ts # 后台任务状态 +│ │ ├── settings.ts # 运行时配置 +│ │ ├── monitor.ts # 外部监控集成 +│ │ └── test.ts # 测试 / 验证端点 +│ └── proxy/ # 代理路由 +│ ├── router.ts # 代理路由注册 +│ ├── chat.ts # Chat Completions & Claude Messages +│ ├── completions.ts # Legacy Completions +│ ├── embeddings.ts # 向量嵌入 +│ ├── images.ts # 图像生成 +│ ├── models.ts # 模型列表 +│ └── chatFormats.ts # OpenAI <-> Claude 格式转换 +└── services/ + ├── platforms/ # 平台适配器 + │ ├── base.ts # 适配器接口定义 + │ ├── index.ts # 适配器注册表 + │ ├── newApi.ts # New API 适配器 + │ ├── oneApi.ts # One API 适配器 + │ ├── oneHub.ts # OneHub 适配器 + │ ├── doneHub.ts # DoneHub 适配器 + │ ├── veloera.ts # Veloera 适配器 + │ ├── anyrouter.ts # AnyRouter 适配器 + │ └── sub2api.ts # Sub2API 适配器 + ├── tokenRouter.ts # 智能路由引擎 + ├── checkinService.ts # 签到执行 + ├── checkinScheduler.ts # 签到调度 + ├── checkinRewardParser.ts # 奖励金额解析 + ├── balanceService.ts # 余额刷新 + ├── modelService.ts # 模型发现与管理 + ├── modelPricingService.ts # 模型定价 + ├── modelAnalysisService.ts # 使用分析 + ├── notifyService.ts # 多渠道通知 + ├── notificationThrottle.ts # 通知节流 + ├── alertService.ts # 告警事件 + ├── alertRules.ts # 告警规则 + ├── backupService.ts # 数据导入导出 + ├── backgroundTaskService.ts # 后台任务管理 + ├── accountCredentialService.ts # 凭证加密 + ├── accountHealthService.ts # 健康状态管理 + ├── accountExtraConfig.ts # 平台专属配置 + ├── proxyRetryPolicy.ts # 重试策略 + ├── proxyUsageParser.ts # Token 用量解析 + ├── proxyUsageFallbackService.ts # 余额兜底估算 + ├── failureReasonService.ts # 错误分类 + ├── siteDetector.ts # 平台自动检测 + ├── dailySummaryService.ts # 每日摘要 + ├── todayIncomeRewardService.ts # 今日收入快照 + ├── localTimeService.ts # 时区处理 + ├── upstreamModelDescriptionService.ts # 上游模型描述缓存 + ├── startupInfo.ts # 启动信息 + └── settings.ts # 运行时配置管理 +``` + +## 前端目录约定(`src/web`) + +```text +src/web/ +├── App.tsx # 应用入口与路由配置 +├── main.tsx # Vite 入口 +├── api.ts # 统一 API 请求客户端 +├── authSession.ts # 认证会话管理 +├── i18n.tsx # 国际化 +├── components/ # 通用 UI 组件 +│ ├── BrandIcon.tsx # 模型品牌图标 +│ ├── ModelAnalysisPanel.tsx # 消费分析图表 +│ ├── SearchModal.tsx # 全局搜索弹窗 +│ ├── NotificationPanel.tsx # 实时事件面板 +│ ├── Toast.tsx # 通知提示 +│ ├── SiteDistributionChart.tsx # 余额分布饼图 +│ └── SiteTrendChart.tsx # 消费趋势图 +├── pages/ # 页面组件(路由页) +│ ├── Dashboard.tsx # 仪表盘 +│ ├── Sites.tsx # 站点管理 +│ ├── Accounts.tsx # 账号管理 +│ ├── Tokens.tsx # Token 管理 +│ ├── TokenRoutes.tsx # 路由规则 +│ ├── Models.tsx # 模型广场 +│ ├── CheckinLog.tsx # 签到日志 +│ ├── ProxyLogs.tsx # 代理日志 +│ ├── Settings.tsx # 系统设置 +│ ├── About.tsx # 关于页面 +│ └── helpers/ # 页面级纯逻辑 / 工具函数(含测试) +└── public/ # 静态资源 +``` + +## 目录卫生规则 + +- 所有调试临时文件放入 `tmp/`,不要散落在项目根目录。 +- 开发脚本统一放入 `scripts//`,根目录仅保留必要入口文件。 +- 素材草稿统一归档到 `docs/logos/drafts/`,避免根目录堆积二进制文件。 +- 测试文件与被测文件同目录(`*.test.ts`),方便就近维护。 +- 平台适配器新增时,在 `services/platforms/` 中创建独立文件并注册到 `index.ts`。 diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..560fb5e --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + schema: './src/server/db/schema.ts', + out: './drizzle', + dialect: 'sqlite', + dbCredentials: { url: './data/hub.db' }, +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..136fee7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8819 @@ +{ + "name": "metapi", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "metapi", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@fastify/cors": "^11.2.0", + "@fastify/static": "^9.0.0", + "@visactor/react-vchart": "^2.0.16", + "better-sqlite3": "^11.3.0", + "dotenv": "^16.4.5", + "drizzle-orm": "^0.36.4", + "fastify": "^5.7.4", + "minimatch": "^10.2.4", + "minimist": "^1.2.8", + "node-cron": "^3.0.3", + "nodemailer": "^8.0.1", + "undici": "^6.20.1" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/better-sqlite3": "^7.6.11", + "@types/node": "^22.10.1", + "@types/node-cron": "^3.0.11", + "@types/nodemailer": "^7.0.11", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "concurrently": "^9.1.0", + "drizzle-kit": "^0.28.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "react-test-renderer": "^18.3.1", + "sharp": "^0.34.5", + "tailwindcss": "^4.0.0", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "vite": "^6.0.3", + "vitest": "^2.1.8" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "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" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", + "integrity": "sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/cors": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-11.2.0.tgz", + "integrity": "sha512-LbLHBuSAdGdSFZYTLVA3+Ch2t+sA6nq3Ejc6XLAKiQ6ViS2qFnvicpj0htsx03FyYeLs04HfRNBsz/a8SvbcUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fastify-plugin": "^5.0.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@fastify/send": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.0.tgz", + "integrity": "sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" + } + }, + "node_modules/@fastify/static": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.0.0.tgz", + "integrity": "sha512-r64H8Woe/vfilg5RTy7lwWlE8ZZcTrc3kebYFMEUBrMqlydhQyoiExQXdYAy2REVpST/G35+stAM8WYp1WGmMA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/send": "^4.0.0", + "content-disposition": "^1.0.1", + "fastify-plugin": "^5.0.0", + "fastq": "^1.17.1", + "glob": "^13.0.0" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@resvg/resvg-js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.4.1.tgz", + "integrity": "sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==", + "license": "MPL-2.0", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@resvg/resvg-js-android-arm-eabi": "2.4.1", + "@resvg/resvg-js-android-arm64": "2.4.1", + "@resvg/resvg-js-darwin-arm64": "2.4.1", + "@resvg/resvg-js-darwin-x64": "2.4.1", + "@resvg/resvg-js-linux-arm-gnueabihf": "2.4.1", + "@resvg/resvg-js-linux-arm64-gnu": "2.4.1", + "@resvg/resvg-js-linux-arm64-musl": "2.4.1", + "@resvg/resvg-js-linux-x64-gnu": "2.4.1", + "@resvg/resvg-js-linux-x64-musl": "2.4.1", + "@resvg/resvg-js-win32-arm64-msvc": "2.4.1", + "@resvg/resvg-js-win32-ia32-msvc": "2.4.1", + "@resvg/resvg-js-win32-x64-msvc": "2.4.1" + } + }, + "node_modules/@resvg/resvg-js-android-arm-eabi": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.4.1.tgz", + "integrity": "sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-android-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.4.1.tgz", + "integrity": "sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.4.1.tgz", + "integrity": "sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-darwin-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.4.1.tgz", + "integrity": "sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.4.1.tgz", + "integrity": "sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-gnu": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.4.1.tgz", + "integrity": "sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-arm64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.4.1.tgz", + "integrity": "sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-gnu": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.4.1.tgz", + "integrity": "sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-linux-x64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.4.1.tgz", + "integrity": "sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-arm64-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.4.1.tgz", + "integrity": "sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-ia32-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.4.1.tgz", + "integrity": "sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==", + "cpu": [ + "ia32" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@resvg/resvg-js-win32-x64-msvc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.4.1.tgz", + "integrity": "sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@turf/boolean-clockwise": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-6.5.0.tgz", + "integrity": "sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clone": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.5.0.tgz", + "integrity": "sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flatten": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/flatten/-/flatten-6.5.0.tgz", + "integrity": "sha512-IBZVwoNLVNT6U/bcUUllubgElzpMsNoCw8tLqBw6dfYg9ObGmpEjf9BIYLr7a2Yn5ZR4l7YIj2T7kD5uJjZADQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==", + "license": "MIT", + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", + "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", + "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rewind": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-6.5.0.tgz", + "integrity": "sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==", + "license": "MIT", + "dependencies": { + "@turf/boolean-clockwise": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", + "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", + "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/nodemailer": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.11.tgz", + "integrity": "sha512-E+U4RzR2dKrx+u3N4DlsmLaDC6mMZOM/TPROxA0UAPiTgI0y4CEFBmZE+coGWTjakDriRsXG368lNk1u9Q0a2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@visactor/react-vchart": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@visactor/react-vchart/-/react-vchart-2.0.16.tgz", + "integrity": "sha512-HOsrWBYdP/17DR+GH7Y9gVqIDV4Tb+uLldQeR5HgUjyfabxtz5GtVE/3vv1PlZUEKgCOEXCa+QC9bS/PyS459g==", + "license": "MIT", + "dependencies": { + "@visactor/vchart": "2.0.16", + "@visactor/vchart-extension": "2.0.16", + "@visactor/vrender-core": "~1.0.40", + "@visactor/vrender-kits": "~1.0.40", + "@visactor/vutils": "~1.0.22", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@visactor/vchart": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@visactor/vchart/-/vchart-2.0.16.tgz", + "integrity": "sha512-jRcMyxuq7C1lJfOH3s4Wkaz0lcspyIvaOsFXkxkHQN5h3qsSKxLKdHZCIXgHjsv/MFytZCaRKtb+gNPMZQ93Iw==", + "license": "MIT", + "dependencies": { + "@visactor/vdataset": "~1.0.22", + "@visactor/vlayouts": "~1.0.22", + "@visactor/vrender-animate": "~1.0.40", + "@visactor/vrender-components": "~1.0.40", + "@visactor/vrender-core": "~1.0.40", + "@visactor/vrender-kits": "~1.0.40", + "@visactor/vscale": "~1.0.22", + "@visactor/vutils": "~1.0.22", + "@visactor/vutils-extension": "2.0.16" + } + }, + "node_modules/@visactor/vchart-extension": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@visactor/vchart-extension/-/vchart-extension-2.0.16.tgz", + "integrity": "sha512-n88Tu6V8FFY4E4zqVeECwlFJjeIlyiF3vrlnYlR3dlnRbt/OCBC0ThGK3eCqkV9LuwD632cRFKqjQeYinYFrTA==", + "license": "MIT", + "dependencies": { + "@visactor/vchart": "2.0.16", + "@visactor/vdataset": "~1.0.22", + "@visactor/vlayouts": "~1.0.22", + "@visactor/vrender-animate": "~1.0.40", + "@visactor/vrender-components": "~1.0.40", + "@visactor/vrender-core": "~1.0.40", + "@visactor/vrender-kits": "~1.0.40", + "@visactor/vutils": "~1.0.22" + } + }, + "node_modules/@visactor/vdataset": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@visactor/vdataset/-/vdataset-1.0.22.tgz", + "integrity": "sha512-B+Xd7UuXNNLWRsH0ySd9LsvFFBz/S+lFN7ha7GU7SdTYr9P0ZExYyumQPntaa5iOMLxKysFxJw7t/Or70ZA6hQ==", + "license": "MIT", + "dependencies": { + "@turf/flatten": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/rewind": "^6.5.0", + "@visactor/vutils": "1.0.22", + "d3-dsv": "^2.0.0", + "d3-geo": "^1.12.1", + "d3-hexbin": "^0.2.2", + "d3-hierarchy": "^3.1.1", + "eventemitter3": "^4.0.7", + "geobuf": "^3.0.1", + "geojson-dissolve": "^3.1.0", + "path-browserify": "^1.0.1", + "pbf": "^3.2.1", + "point-at-length": "^1.1.0", + "simple-statistics": "^7.7.3", + "simplify-geojson": "^1.0.4", + "topojson-client": "^3.1.0" + } + }, + "node_modules/@visactor/vlayouts": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@visactor/vlayouts/-/vlayouts-1.0.22.tgz", + "integrity": "sha512-iTPJeROqmwBOBDoewDHYdEAe6MlDPgJ/1V1j+FcWr3zWGPDOSduoIDyjhB/FpkLaUQyhYCTCfCH3p2BDs3Wy4g==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@visactor/vscale": "1.0.22", + "@visactor/vutils": "1.0.22", + "eventemitter3": "^4.0.7" + } + }, + "node_modules/@visactor/vrender-animate": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/@visactor/vrender-animate/-/vrender-animate-1.0.40.tgz", + "integrity": "sha512-/OT+xKwTdRa+bhTF28mQxl+9HsaIncNFGv00G3m2EHQT7Ls8SCDAyD46J36+bU3eYKz/+V+CzL2ApwI/zXP8/A==", + "license": "MIT", + "dependencies": { + "@visactor/vrender-core": "1.0.40", + "@visactor/vutils": "~1.0.12" + } + }, + "node_modules/@visactor/vrender-components": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/@visactor/vrender-components/-/vrender-components-1.0.40.tgz", + "integrity": "sha512-+mooyFfpAaAjhBDN5XHKz1SH0vHe3IjabLJQWYwDZGvpPHieVIlZstkTIRMH63pJek0ViIZLHcxpf/i/qli+sw==", + "license": "MIT", + "dependencies": { + "@visactor/vrender-animate": "1.0.40", + "@visactor/vrender-core": "1.0.40", + "@visactor/vrender-kits": "1.0.40", + "@visactor/vscale": "~1.0.12", + "@visactor/vutils": "~1.0.12" + } + }, + "node_modules/@visactor/vrender-core": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/@visactor/vrender-core/-/vrender-core-1.0.40.tgz", + "integrity": "sha512-VNfxYGvNS2k1v2/+H0y9jIxjsFjgy3ieFGTDYz+o1rES2RY4wQIr6xJtdEkzXg6Foavp1gJj4fSf/wl2idEuTA==", + "license": "MIT", + "dependencies": { + "@visactor/vutils": "~1.0.12", + "color-convert": "2.0.1" + } + }, + "node_modules/@visactor/vrender-kits": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/@visactor/vrender-kits/-/vrender-kits-1.0.40.tgz", + "integrity": "sha512-tMD2C4vQd5kN2WyDIkel4tId1NYGclKM1exIsJY51rgOjfJsjpRzC5WPe6KYqoIcDO5xDarX8TY4dkTR9S0l6w==", + "license": "MIT", + "dependencies": { + "@resvg/resvg-js": "2.4.1", + "@visactor/vrender-core": "1.0.40", + "@visactor/vutils": "~1.0.12", + "gifuct-js": "2.1.2", + "lottie-web": "^5.12.2", + "roughjs": "4.6.6" + } + }, + "node_modules/@visactor/vscale": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@visactor/vscale/-/vscale-1.0.22.tgz", + "integrity": "sha512-DCCqBA9gBZexCsfYmUjRZM1OBp2Qr8dRIGOqWcFerINIDtpQatSS4c8Ah2r8pqkgiizSNeKgDoXOtsiPe9zqBQ==", + "license": "MIT", + "dependencies": { + "@visactor/vutils": "1.0.22" + } + }, + "node_modules/@visactor/vutils": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@visactor/vutils/-/vutils-1.0.22.tgz", + "integrity": "sha512-n+lKEeSQl1wGqRG829oQ4SwU6UysUiP9rpp0K87TwmERVMupSy6C0KcM2SGXGBLvnp1Cyjjciw5ZtYQXv3ZfKQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "eventemitter3": "^4.0.7" + } + }, + "node_modules/@visactor/vutils-extension": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@visactor/vutils-extension/-/vutils-extension-2.0.16.tgz", + "integrity": "sha512-2ioPsCZ95a8QbCNNtjZQ0ZtsDqrrwLEx2WYR9lwrz8AqlXqHKahaOFRpse1MeUf/x99KnKuPTvFV9uaToDexJQ==", + "license": "MIT", + "dependencies": { + "@visactor/vdataset": "~1.0.22", + "@visactor/vutils": "~1.0.22" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abs-svg-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", + "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==", + "license": "MIT" + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-source": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/array-source/-/array-source-0.0.4.tgz", + "integrity": "sha512-frNdc+zBn80vipY+GdcJkLEbMWj3xmzArYApmUGxoiV8uAu/ygcs9icPdsGdA26h0MkHUMW6EN2piIvVx+M5Mw==", + "license": "BSD-3-Clause" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz", + "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001774", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", + "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-dsv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-2.0.0.tgz", + "integrity": "sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==", + "license": "BSD-3-Clause", + "dependencies": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json", + "csv2tsv": "bin/dsv2dsv", + "dsv2dsv": "bin/dsv2dsv", + "dsv2json": "bin/dsv2json", + "json2csv": "bin/json2dsv", + "json2dsv": "bin/json2dsv", + "json2tsv": "bin/json2dsv", + "tsv2csv": "bin/dsv2dsv", + "tsv2json": "bin/dsv2json" + } + }, + "node_modules/d3-geo": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-hexbin": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/d3-hexbin/-/d3-hexbin-0.2.2.tgz", + "integrity": "sha512-KS3fUT2ReD4RlGCjvCEm1RgMtp2NFZumdMu4DBzQK8AZv3fXRM6Xm8I4fSU07UXvH4xxg03NwWKWdvxfS/yc4w==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/drizzle-kit": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.28.1.tgz", + "integrity": "sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.19.7", + "esbuild-register": "^3.5.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-orm": { + "version": "0.36.4", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.36.4.tgz", + "integrity": "sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=3", + "@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", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/react": ">=18", + "@types/sql.js": "*", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "react": ">=18", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "react": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.3.0.tgz", + "integrity": "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.7.4.tgz", + "integrity": "sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify-plugin": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-5.1.0.tgz", + "integrity": "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fastify/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-source": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-source/-/file-source-0.6.1.tgz", + "integrity": "sha512-1R1KneL7eTXmXfKxC10V/9NeGOdbsAXJ+lQ//fvvcHUgtaZcZDWNJNblxAoVOyV1cj45pOtUrR3vZTBwqcW8XA==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-source": "0.3" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/find-my-way": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.5.0.tgz", + "integrity": "sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/geobuf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/geobuf/-/geobuf-3.0.2.tgz", + "integrity": "sha512-ASgKwEAQQRnyNFHNvpd5uAwstbVYmiTW0Caw3fBb509tNTqXyAAPMyFs5NNihsLZhLxU1j/kjFhkhLWA9djuVg==", + "license": "ISC", + "dependencies": { + "concat-stream": "^2.0.0", + "pbf": "^3.2.1", + "shapefile": "~0.6.6" + }, + "bin": { + "geobuf2json": "bin/geobuf2json", + "json2geobuf": "bin/json2geobuf", + "shp2geobuf": "bin/shp2geobuf" + } + }, + "node_modules/geojson-dissolve": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/geojson-dissolve/-/geojson-dissolve-3.1.0.tgz", + "integrity": "sha512-JXHfn+A3tU392HA703gJbjmuHaQOAE/C1KzbELCczFRFux+GdY6zt1nKb1VMBHp4LWeE7gUY2ql+g06vJqhiwQ==", + "license": "ISC", + "dependencies": { + "@turf/meta": "^3.7.5", + "geojson-flatten": "^0.2.1", + "geojson-linestring-dissolve": "0.0.1", + "topojson-client": "^3.0.0", + "topojson-server": "^3.0.0" + } + }, + "node_modules/geojson-dissolve/node_modules/@turf/meta": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-3.14.0.tgz", + "integrity": "sha512-OtXqLQuR9hlQ/HkAF/OdzRea7E0eZK1ay8y8CBXkoO2R6v34CsDrWYLMSo0ZzMsaQDpKo76NPP2GGo+PyG1cSg==", + "license": "MIT" + }, + "node_modules/geojson-flatten": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/geojson-flatten/-/geojson-flatten-0.2.4.tgz", + "integrity": "sha512-LiX6Jmot8adiIdZ/fthbcKKPOfWjTQchX/ggHnwMZ2e4b0I243N1ANUos0LvnzepTEsj0+D4fIJ5bKhBrWnAHA==", + "license": "BSD-2-Clause", + "dependencies": { + "get-stdin": "^6.0.0", + "minimist": "1.2.0" + }, + "bin": { + "geojson-flatten": "geojson-flatten" + } + }, + "node_modules/geojson-linestring-dissolve": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/geojson-linestring-dissolve/-/geojson-linestring-dissolve-0.0.1.tgz", + "integrity": "sha512-Y8I2/Ea28R/Xeki7msBcpMvJL2TaPfaPKP8xqueJfQ9/jEhps+iOJxOR2XCBGgVb12Z6XnDb1CMbaPfLepsLaw==", + "license": "ISC" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gifuct-js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/gifuct-js/-/gifuct-js-2.1.2.tgz", + "integrity": "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==", + "license": "MIT", + "dependencies": { + "js-binary-schema-parser": "^2.0.3" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "license": "MIT" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-binary-schema-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/js-binary-schema-parser/-/js-binary-schema-parser-2.0.3.tgz", + "integrity": "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lottie-web": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.13.0.tgz", + "integrity": "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==", + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemailer": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.1.tgz", + "integrity": "sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==", + "license": "MIT" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-source": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/path-source/-/path-source-0.1.3.tgz", + "integrity": "sha512-dWRHm5mIw5kw0cs3QZLNmpUWty48f5+5v9nWD2dw3Y0Hf+s01Ag8iJEWV0Sm0kocE8kK27DrIowha03e1YR+Qw==", + "license": "BSD-3-Clause", + "dependencies": { + "array-source": "0.0", + "file-source": "0.6" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pbf": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", + "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/point-at-length": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-at-length/-/point-at-length-1.1.0.tgz", + "integrity": "sha512-nNHDk9rNEh/91o2Y8kHLzBLNpLf80RYd2gCun9ss+V0ytRSf6XhryBTx071fesktjbachRmGuUbId+JQmzhRXw==", + "license": "MIT", + "dependencies": { + "abs-svg-path": "~0.1.1", + "isarray": "~0.0.1", + "parse-svg-path": "~0.1.1" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", + "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-test-renderer": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", + "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-is": "^18.3.1", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shapefile": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/shapefile/-/shapefile-0.6.6.tgz", + "integrity": "sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw==", + "license": "BSD-3-Clause", + "dependencies": { + "array-source": "0.0", + "commander": "2", + "path-source": "0.1", + "slice-source": "0.4", + "stream-source": "0.3", + "text-encoding": "^0.6.4" + }, + "bin": { + "dbf2json": "bin/dbf2json", + "shp2json": "bin/shp2json" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-statistics": { + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/simple-statistics/-/simple-statistics-7.8.8.tgz", + "integrity": "sha512-CUtP0+uZbcbsFpqEyvNDYjJCl+612fNgjT8GaVuvMG7tBuJg8gXGpsP5M7X658zy0IcepWOZ6nPBu1Qb9ezA1w==", + "license": "ISC", + "engines": { + "node": "*" + } + }, + "node_modules/simplify-geojson": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/simplify-geojson/-/simplify-geojson-1.0.5.tgz", + "integrity": "sha512-02l1W4UipP5ivNVq6kX15mAzCRIV1oI3tz0FUEyOsNiv1ltuFDjbNhO+nbv/xhbDEtKqWLYuzpWhUsJrjR/ypA==", + "license": "BSD-2-Clause", + "dependencies": { + "concat-stream": "~1.4.1", + "minimist": "1.2.6", + "simplify-geometry": "0.0.2" + }, + "bin": { + "simplify-geojson": "cli.js" + } + }, + "node_modules/simplify-geojson/node_modules/concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "node_modules/simplify-geojson/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/simplify-geojson/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/simplify-geometry": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/simplify-geometry/-/simplify-geometry-0.0.2.tgz", + "integrity": "sha512-ZEyrplkqgCqDlL7V8GbbYgTLlcnNF+MWWUdy8s8ZeJru50bnI71rDew/I+HG36QS2mPOYAq1ZjwNXxHJ8XOVBw==", + "license": "MIT" + }, + "node_modules/slice-source": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/slice-source/-/slice-source-0.4.1.tgz", + "integrity": "sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==", + "license": "BSD-3-Clause" + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-source": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz", + "integrity": "sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g==", + "license": "BSD-3-Clause" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", + "deprecated": "no longer maintained", + "license": "Unlicense" + }, + "node_modules/thread-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz", + "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-server": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/topojson-server/-/topojson-server-3.0.1.tgz", + "integrity": "sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "geo2topo": "bin/geo2topo" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vite-node/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..64d709a --- /dev/null +++ b/package.json @@ -0,0 +1,77 @@ +{ + "name": "metapi", + "version": "1.0.0", + "description": "Meta-layer management and unified proxy for AI API aggregation platforms", + "keywords": [ + "ai", + "api-gateway", + "proxy", + "fastify", + "openai", + "claude", + "aggregator" + ], + "homepage": "https://github.com/cita-777/metapi#readme", + "bugs": { + "url": "https://github.com/cita-777/metapi/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/cita-777/metapi.git" + }, + "license": "MIT", + "type": "module", + "scripts": { + "dev": "concurrently \"tsx watch src/server/index.ts\" \"vite\"", + "build:web": "vite build", + "build:server": "tsc -p tsconfig.server.json", + "build": "npm run build:web && npm run build:server", + "start": "node dist/server/index.js", + "db:generate": "drizzle-kit generate", + "db:migrate": "tsx src/server/db/migrate.ts", + "test": "vitest run --root .", + "test:watch": "vitest --root ." + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@fastify/cors": "^11.2.0", + "@fastify/static": "^9.0.0", + "@visactor/react-vchart": "^2.0.16", + "better-sqlite3": "^11.3.0", + "dotenv": "^16.4.5", + "drizzle-orm": "^0.36.4", + "fastify": "^5.7.4", + "minimatch": "^10.2.4", + "minimist": "^1.2.8", + "node-cron": "^3.0.3", + "nodemailer": "^8.0.1", + "undici": "^6.20.1" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/better-sqlite3": "^7.6.11", + "@types/node": "^22.10.1", + "@types/node-cron": "^3.0.11", + "@types/nodemailer": "^7.0.11", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "concurrently": "^9.1.0", + "drizzle-kit": "^0.28.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "react-test-renderer": "^18.3.1", + "sharp": "^0.34.5", + "tailwindcss": "^4.0.0", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "vite": "^6.0.3", + "vitest": "^2.1.8" + }, + "overrides": { + "minimist": "^1.2.8" + } +} diff --git a/restart.bat b/restart.bat new file mode 100644 index 0000000..a04daa2 --- /dev/null +++ b/restart.bat @@ -0,0 +1,4 @@ +@echo off +setlocal EnableExtensions + +call "%~dp0scripts\dev\restart.bat" %* diff --git a/scripts/dev/restart.bat b/scripts/dev/restart.bat new file mode 100644 index 0000000..529c577 --- /dev/null +++ b/scripts/dev/restart.bat @@ -0,0 +1,46 @@ +@echo off +setlocal EnableExtensions + +rem Try to switch output to UTF-8; ignore failures. +chcp 65001 >nul 2>&1 + +set "SCRIPT_DIR=%~dp0" +set "PROJECT_ROOT=%SCRIPT_DIR%..\.." +cd /d "%PROJECT_ROOT%" + +echo [INFO] Stopping old processes... + +rem Read ports from .env with defaults. +set "BACKEND_PORT=9000" +set "PROXY_PORT=9001" +set "FRONTEND_PORT=5173" +for /f "tokens=2 delims==" %%v in ('findstr /B /C:"PORT=" .env 2^>nul') do set "BACKEND_PORT=%%v" +for /f "tokens=2 delims==" %%v in ('findstr /B /C:"PROXY_PORT=" .env 2^>nul') do set "PROXY_PORT=%%v" +for /f "tokens=2 delims==" %%v in ('findstr /B /C:"FRONTEND_PORT=" .env 2^>nul') do set "FRONTEND_PORT=%%v" + +rem Kill listeners on backend/proxy/frontend ports. +for %%p in (%BACKEND_PORT% %PROXY_PORT% %FRONTEND_PORT%) do ( + for /f "tokens=5" %%a in ('netstat -aon ^| findstr ":%%p " ^| findstr /I LISTENING 2^>nul') do ( + if not "%%a"=="0" ( + echo Killing PID %%a on port %%p + taskkill /F /PID %%a >nul 2>&1 + ) + ) +) + +ping -n 3 127.0.0.1 >nul + +echo [INFO] Syncing database schema... +call npx drizzle-kit push --force 2>nul || echo (drizzle-kit push skipped) + +echo [INFO] Starting development servers +echo Backend: http://localhost:%BACKEND_PORT% +echo Frontend: http://localhost:%FRONTEND_PORT% + +echo. +if /I "%~1"=="--no-dev" ( + echo --no-dev set; skipping npm run dev + exit /b 0 +) + +call npm run dev diff --git a/src/server/config.ts b/src/server/config.ts new file mode 100644 index 0000000..76aefbe --- /dev/null +++ b/src/server/config.ts @@ -0,0 +1,56 @@ +import 'dotenv/config'; + +function parseBoolean(value: string | undefined, fallback = false): boolean { + if (value === undefined) return fallback; + const normalized = value.trim().toLowerCase(); + return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on'; +} + +function parseNumber(value: string | undefined, fallback: number): number { + if (value === undefined) return fallback; + const parsed = Number(value); + if (!Number.isFinite(parsed)) return fallback; + return parsed; +} + +function parseCsvList(value: string | undefined): string[] { + if (!value) return []; + return value + .split(',') + .map((item) => item.trim()) + .filter((item) => item.length > 0); +} + +export const config = { + authToken: process.env.AUTH_TOKEN || 'change-me', + proxyToken: process.env.PROXY_TOKEN || 'change-me-proxy', + accountCredentialSecret: process.env.ACCOUNT_CREDENTIAL_SECRET || process.env.AUTH_TOKEN || 'change-me', + checkinCron: process.env.CHECKIN_CRON || '0 8 * * *', + balanceRefreshCron: process.env.BALANCE_REFRESH_CRON || '0 * * * *', + webhookUrl: process.env.WEBHOOK_URL || '', + barkUrl: process.env.BARK_URL || '', + webhookEnabled: parseBoolean(process.env.WEBHOOK_ENABLED, true), + barkEnabled: parseBoolean(process.env.BARK_ENABLED, true), + serverChanEnabled: parseBoolean(process.env.SERVERCHAN_ENABLED, true), + serverChanKey: process.env.SERVERCHAN_KEY || '', + smtpEnabled: parseBoolean(process.env.SMTP_ENABLED, false), + smtpHost: process.env.SMTP_HOST || '', + smtpPort: parseInt(process.env.SMTP_PORT || '587'), + smtpSecure: parseBoolean(process.env.SMTP_SECURE, false), + smtpUser: process.env.SMTP_USER || '', + smtpPass: process.env.SMTP_PASS || '', + smtpFrom: process.env.SMTP_FROM || '', + smtpTo: process.env.SMTP_TO || '', + notifyCooldownSec: Math.max(0, Math.trunc(parseNumber(process.env.NOTIFY_COOLDOWN_SEC, 300))), + adminIpAllowlist: parseCsvList(process.env.ADMIN_IP_ALLOWLIST), + port: Math.trunc(parseNumber(process.env.PORT, 4000)), + dataDir: process.env.DATA_DIR || './data', + routingFallbackUnitCost: Math.max(1e-6, parseNumber(process.env.ROUTING_FALLBACK_UNIT_COST, 1)), + routingWeights: { + baseWeightFactor: parseNumber(process.env.BASE_WEIGHT_FACTOR, 0.5), + valueScoreFactor: parseNumber(process.env.VALUE_SCORE_FACTOR, 0.5), + costWeight: parseNumber(process.env.COST_WEIGHT, 0.4), + balanceWeight: parseNumber(process.env.BALANCE_WEIGHT, 0.3), + usageWeight: parseNumber(process.env.USAGE_WEIGHT, 0.3), + }, +}; diff --git a/src/server/db/index.ts b/src/server/db/index.ts new file mode 100644 index 0000000..f393941 --- /dev/null +++ b/src/server/db/index.ts @@ -0,0 +1,106 @@ +import Database from 'better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import * as schema from './schema.js'; +import { config } from '../config.js'; +import { mkdirSync } from 'fs'; +import { dirname } from 'path'; + +const dbPath = `${config.dataDir}/hub.db`; +mkdirSync(dirname(dbPath), { recursive: true }); + +const sqlite = new Database(dbPath); +sqlite.pragma('journal_mode = WAL'); +sqlite.pragma('foreign_keys = ON'); + +function tableColumnExists(table: string, column: string): boolean { + const rows = sqlite.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name?: string }>; + return rows.some((row) => row.name === column); +} + +function ensureTokenManagementSchema() { + sqlite.exec(` + CREATE TABLE IF NOT EXISTS account_tokens ( + id integer PRIMARY KEY AUTOINCREMENT NOT NULL, + account_id integer NOT NULL, + name text NOT NULL, + token text NOT NULL, + source text DEFAULT 'manual', + enabled integer DEFAULT true, + is_default integer DEFAULT false, + created_at text DEFAULT (datetime('now')), + updated_at text DEFAULT (datetime('now')), + FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE cascade + ); + `); + + if (!tableColumnExists('route_channels', 'token_id')) { + sqlite.exec('ALTER TABLE route_channels ADD COLUMN token_id integer;'); + } + + sqlite.exec(` + INSERT INTO account_tokens (account_id, name, token, source, enabled, is_default, created_at, updated_at) + SELECT + a.id, + 'default', + a.api_token, + 'legacy', + true, + true, + datetime('now'), + datetime('now') + FROM accounts AS a + WHERE + a.api_token IS NOT NULL + AND trim(a.api_token) <> '' + AND NOT EXISTS ( + SELECT 1 FROM account_tokens AS t + WHERE t.account_id = a.id + AND t.token = a.api_token + ); + `); + + sqlite.exec(` + CREATE TABLE IF NOT EXISTS token_model_availability ( + id integer PRIMARY KEY AUTOINCREMENT NOT NULL, + token_id integer NOT NULL, + model_name text NOT NULL, + available integer, + latency_ms integer, + checked_at text DEFAULT (datetime('now')), + FOREIGN KEY (token_id) REFERENCES account_tokens(id) ON DELETE cascade + ); + `); + + sqlite.exec(` + CREATE UNIQUE INDEX IF NOT EXISTS token_model_availability_token_model_unique + ON token_model_availability(token_id, model_name); + `); +} + +function ensureSiteStatusSchema() { + if (!tableColumnExists('sites', 'status')) { + sqlite.exec(`ALTER TABLE sites ADD COLUMN status text DEFAULT 'active';`); + } + + sqlite.exec(` + UPDATE sites + SET status = lower(trim(status)) + WHERE status IS NOT NULL + AND lower(trim(status)) IN ('active', 'disabled') + AND status != lower(trim(status)); + `); + + sqlite.exec(` + UPDATE sites + SET status = 'active' + WHERE status IS NULL + OR trim(status) = '' + OR lower(trim(status)) NOT IN ('active', 'disabled'); + `); +} + +ensureTokenManagementSchema(); +ensureSiteStatusSchema(); + +export const db = drizzle(sqlite, { schema }); +export { schema }; diff --git a/src/server/db/migrate.ts b/src/server/db/migrate.ts new file mode 100644 index 0000000..b8813e5 --- /dev/null +++ b/src/server/db/migrate.ts @@ -0,0 +1,15 @@ +import Database from 'better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { migrate } from 'drizzle-orm/better-sqlite3/migrator'; +import { config } from '../config.js'; +import { mkdirSync } from 'fs'; +import { dirname } from 'path'; + +const dbPath = `${config.dataDir}/hub.db`; +mkdirSync(dirname(dbPath), { recursive: true }); + +const sqlite = new Database(dbPath); +const db = drizzle(sqlite); +migrate(db, { migrationsFolder: './drizzle' }); +sqlite.close(); +console.log('Migration complete.'); diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts new file mode 100644 index 0000000..4d68fea --- /dev/null +++ b/src/server/db/schema.ts @@ -0,0 +1,176 @@ +import { sqliteTable, text, integer, real, uniqueIndex, index } from 'drizzle-orm/sqlite-core'; +import { sql } from 'drizzle-orm'; + +export const sites = sqliteTable('sites', { + id: integer('id').primaryKey({ autoIncrement: true }), + name: text('name').notNull(), + url: text('url').notNull(), + platform: text('platform').notNull(), // 'new-api' | 'one-api' | 'veloera' | 'one-hub' | 'done-hub' | 'sub2api' + status: text('status').notNull().default('active'), // 'active' | 'disabled' + apiKey: text('api_key'), + createdAt: text('created_at').default(sql`(datetime('now'))`), + updatedAt: text('updated_at').default(sql`(datetime('now'))`), +}, (table) => ({ + statusIdx: index('sites_status_idx').on(table.status), +})); + +export const accounts = sqliteTable('accounts', { + id: integer('id').primaryKey({ autoIncrement: true }), + siteId: integer('site_id').notNull().references(() => sites.id, { onDelete: 'cascade' }), + username: text('username'), + accessToken: text('access_token').notNull(), + apiToken: text('api_token'), + balance: real('balance').default(0), + balanceUsed: real('balance_used').default(0), + quota: real('quota').default(0), + unitCost: real('unit_cost'), + valueScore: real('value_score').default(0), + status: text('status').default('active'), // 'active' | 'disabled' | 'expired' + checkinEnabled: integer('checkin_enabled', { mode: 'boolean' }).default(true), + lastCheckinAt: text('last_checkin_at'), + lastBalanceRefresh: text('last_balance_refresh'), + extraConfig: text('extra_config'), // JSON string + createdAt: text('created_at').default(sql`(datetime('now'))`), + updatedAt: text('updated_at').default(sql`(datetime('now'))`), +}, (table) => ({ + siteIdIdx: index('accounts_site_id_idx').on(table.siteId), + statusIdx: index('accounts_status_idx').on(table.status), + siteStatusIdx: index('accounts_site_status_idx').on(table.siteId, table.status), +})); + +export const accountTokens = sqliteTable('account_tokens', { + id: integer('id').primaryKey({ autoIncrement: true }), + accountId: integer('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }), + name: text('name').notNull(), + token: text('token').notNull(), + source: text('source').default('manual'), // 'manual' | 'sync' | 'legacy' + enabled: integer('enabled', { mode: 'boolean' }).default(true), + isDefault: integer('is_default', { mode: 'boolean' }).default(false), + createdAt: text('created_at').default(sql`(datetime('now'))`), + updatedAt: text('updated_at').default(sql`(datetime('now'))`), +}, (table) => ({ + accountIdIdx: index('account_tokens_account_id_idx').on(table.accountId), + accountEnabledIdx: index('account_tokens_account_enabled_idx').on(table.accountId, table.enabled), + enabledIdx: index('account_tokens_enabled_idx').on(table.enabled), +})); + +export const checkinLogs = sqliteTable('checkin_logs', { + id: integer('id').primaryKey({ autoIncrement: true }), + accountId: integer('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }), + status: text('status').notNull(), // 'success' | 'failed' | 'skipped' + message: text('message'), + reward: text('reward'), + createdAt: text('created_at').default(sql`(datetime('now'))`), +}, (table) => ({ + accountCreatedIdx: index('checkin_logs_account_created_at_idx').on(table.accountId, table.createdAt), + createdAtIdx: index('checkin_logs_created_at_idx').on(table.createdAt), + statusIdx: index('checkin_logs_status_idx').on(table.status), +})); + +export const modelAvailability = sqliteTable('model_availability', { + id: integer('id').primaryKey({ autoIncrement: true }), + accountId: integer('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }), + modelName: text('model_name').notNull(), + available: integer('available', { mode: 'boolean' }), + latencyMs: integer('latency_ms'), + checkedAt: text('checked_at').default(sql`(datetime('now'))`), +}, (table) => ({ + accountModelUnique: uniqueIndex('model_availability_account_model_unique').on(table.accountId, table.modelName), + accountAvailableIdx: index('model_availability_account_available_idx').on(table.accountId, table.available), + modelNameIdx: index('model_availability_model_name_idx').on(table.modelName), +})); + +export const tokenModelAvailability = sqliteTable('token_model_availability', { + id: integer('id').primaryKey({ autoIncrement: true }), + tokenId: integer('token_id').notNull().references(() => accountTokens.id, { onDelete: 'cascade' }), + modelName: text('model_name').notNull(), + available: integer('available', { mode: 'boolean' }), + latencyMs: integer('latency_ms'), + checkedAt: text('checked_at').default(sql`(datetime('now'))`), +}, (table) => ({ + tokenModelUnique: uniqueIndex('token_model_availability_token_model_unique').on(table.tokenId, table.modelName), + tokenAvailableIdx: index('token_model_availability_token_available_idx').on(table.tokenId, table.available), + modelNameIdx: index('token_model_availability_model_name_idx').on(table.modelName), + availableIdx: index('token_model_availability_available_idx').on(table.available), +})); + +export const tokenRoutes = sqliteTable('token_routes', { + id: integer('id').primaryKey({ autoIncrement: true }), + modelPattern: text('model_pattern').notNull(), + modelMapping: text('model_mapping'), // JSON + enabled: integer('enabled', { mode: 'boolean' }).default(true), + createdAt: text('created_at').default(sql`(datetime('now'))`), + updatedAt: text('updated_at').default(sql`(datetime('now'))`), +}, (table) => ({ + modelPatternIdx: index('token_routes_model_pattern_idx').on(table.modelPattern), + enabledIdx: index('token_routes_enabled_idx').on(table.enabled), +})); + +export const routeChannels = sqliteTable('route_channels', { + id: integer('id').primaryKey({ autoIncrement: true }), + routeId: integer('route_id').notNull().references(() => tokenRoutes.id, { onDelete: 'cascade' }), + accountId: integer('account_id').notNull().references(() => accounts.id, { onDelete: 'cascade' }), + tokenId: integer('token_id').references(() => accountTokens.id, { onDelete: 'set null' }), + priority: integer('priority').default(0), + weight: integer('weight').default(10), + enabled: integer('enabled', { mode: 'boolean' }).default(true), + manualOverride: integer('manual_override', { mode: 'boolean' }).default(false), + successCount: integer('success_count').default(0), + failCount: integer('fail_count').default(0), + totalLatencyMs: integer('total_latency_ms').default(0), + totalCost: real('total_cost').default(0), + lastUsedAt: text('last_used_at'), + lastFailAt: text('last_fail_at'), + cooldownUntil: text('cooldown_until'), +}, (table) => ({ + routeIdIdx: index('route_channels_route_id_idx').on(table.routeId), + accountIdIdx: index('route_channels_account_id_idx').on(table.accountId), + tokenIdIdx: index('route_channels_token_id_idx').on(table.tokenId), + routeEnabledIdx: index('route_channels_route_enabled_idx').on(table.routeId, table.enabled), + routeTokenIdx: index('route_channels_route_token_idx').on(table.routeId, table.tokenId), +})); + +export const proxyLogs = sqliteTable('proxy_logs', { + id: integer('id').primaryKey({ autoIncrement: true }), + routeId: integer('route_id'), + channelId: integer('channel_id'), + accountId: integer('account_id'), + modelRequested: text('model_requested'), + modelActual: text('model_actual'), + status: text('status'), // 'success' | 'failed' | 'retried' + httpStatus: integer('http_status'), + latencyMs: integer('latency_ms'), + promptTokens: integer('prompt_tokens'), + completionTokens: integer('completion_tokens'), + totalTokens: integer('total_tokens'), + estimatedCost: real('estimated_cost'), + errorMessage: text('error_message'), + retryCount: integer('retry_count').default(0), + createdAt: text('created_at').default(sql`(datetime('now'))`), +}, (table) => ({ + createdAtIdx: index('proxy_logs_created_at_idx').on(table.createdAt), + accountCreatedIdx: index('proxy_logs_account_created_at_idx').on(table.accountId, table.createdAt), + statusCreatedIdx: index('proxy_logs_status_created_at_idx').on(table.status, table.createdAt), + modelActualCreatedIdx: index('proxy_logs_model_actual_created_at_idx').on(table.modelActual, table.createdAt), +})); + +export const settings = sqliteTable('settings', { + key: text('key').primaryKey(), + value: text('value'), // JSON +}); + +export const events = sqliteTable('events', { + id: integer('id').primaryKey({ autoIncrement: true }), + type: text('type').notNull(), // 'checkin' | 'balance' | 'token' | 'proxy' | 'status' + title: text('title').notNull(), + message: text('message'), + level: text('level').notNull().default('info'), // 'info' | 'warning' | 'error' + read: integer('read', { mode: 'boolean' }).default(false), + relatedId: integer('related_id'), + relatedType: text('related_type'), // 'account' | 'site' | 'route' + createdAt: text('created_at').default(sql`(datetime('now'))`), +}, (table) => ({ + readCreatedIdx: index('events_read_created_at_idx').on(table.read, table.createdAt), + typeCreatedIdx: index('events_type_created_at_idx').on(table.type, table.createdAt), + createdAtIdx: index('events_created_at_idx').on(table.createdAt), +})); diff --git a/src/server/index.ts b/src/server/index.ts new file mode 100644 index 0000000..325e121 --- /dev/null +++ b/src/server/index.ts @@ -0,0 +1,204 @@ +import Fastify from 'fastify'; +import cors from '@fastify/cors'; +import fastifyStatic from '@fastify/static'; +import { config } from './config.js'; +import { authMiddleware } from './middleware/auth.js'; +import { sitesRoutes } from './routes/api/sites.js'; +import { accountsRoutes } from './routes/api/accounts.js'; +import { checkinRoutes } from './routes/api/checkin.js'; +import { tokensRoutes } from './routes/api/tokens.js'; +import { statsRoutes } from './routes/api/stats.js'; +import { authRoutes } from './routes/api/auth.js'; +import { settingsRoutes } from './routes/api/settings.js'; +import { accountTokensRoutes } from './routes/api/accountTokens.js'; +import { searchRoutes } from './routes/api/search.js'; +import { eventsRoutes } from './routes/api/events.js'; +import { taskRoutes } from './routes/api/tasks.js'; +import { testRoutes } from './routes/api/test.js'; +import { monitorRoutes } from './routes/api/monitor.js'; +import { proxyRoutes } from './routes/proxy/router.js'; +import { startScheduler } from './services/checkinScheduler.js'; +import { buildStartupSummaryLines } from './services/startupInfo.js'; +import { existsSync } from 'fs'; +import { normalize, resolve, sep } from 'path'; +import { db, schema } from './db/index.js'; + +// Load runtime config overrides from settings +try { + const rows = db.select().from(schema.settings).all(); + const settingsMap = new Map(rows.map((row) => [row.key, row.value])); + + const parseSetting = (key: string): T | undefined => { + const raw = settingsMap.get(key); + if (!raw) return undefined; + try { + return JSON.parse(raw) as T; + } catch { + return undefined; + } + }; + + const toStringList = (value: unknown): string[] => { + if (Array.isArray(value)) { + return value + .map((item) => (typeof item === 'string' ? item.trim() : '')) + .filter((item) => item.length > 0); + } + if (typeof value === 'string') { + return value + .split(',') + .map((item) => item.trim()) + .filter((item) => item.length > 0); + } + return []; + }; + + const authToken = parseSetting('auth_token'); + if (typeof authToken === 'string' && authToken) config.authToken = authToken; + + const proxyToken = parseSetting('proxy_token'); + if (typeof proxyToken === 'string' && proxyToken) config.proxyToken = proxyToken; + + const checkinCron = parseSetting('checkin_cron'); + if (typeof checkinCron === 'string' && checkinCron) config.checkinCron = checkinCron; + + const balanceRefreshCron = parseSetting('balance_refresh_cron'); + if (typeof balanceRefreshCron === 'string' && balanceRefreshCron) config.balanceRefreshCron = balanceRefreshCron; + + const routingWeights = parseSetting>('routing_weights'); + if (routingWeights && typeof routingWeights === 'object') { + config.routingWeights = { + ...config.routingWeights, + ...routingWeights, + }; + } + + const routingFallbackUnitCost = parseSetting('routing_fallback_unit_cost'); + if (typeof routingFallbackUnitCost === 'number' && Number.isFinite(routingFallbackUnitCost) && routingFallbackUnitCost > 0) { + config.routingFallbackUnitCost = Math.max(1e-6, routingFallbackUnitCost); + } + + const webhookUrl = parseSetting('webhook_url'); + if (typeof webhookUrl === 'string') config.webhookUrl = webhookUrl; + + const barkUrl = parseSetting('bark_url'); + if (typeof barkUrl === 'string') config.barkUrl = barkUrl; + + const serverChanKey = parseSetting('serverchan_key'); + if (typeof serverChanKey === 'string') config.serverChanKey = serverChanKey; + + const smtpEnabled = parseSetting('smtp_enabled'); + if (typeof smtpEnabled === 'boolean') config.smtpEnabled = smtpEnabled; + + const smtpHost = parseSetting('smtp_host'); + if (typeof smtpHost === 'string') config.smtpHost = smtpHost; + + const smtpPort = parseSetting('smtp_port'); + if (typeof smtpPort === 'number' && Number.isFinite(smtpPort) && smtpPort > 0) { + config.smtpPort = smtpPort; + } + + const smtpSecure = parseSetting('smtp_secure'); + if (typeof smtpSecure === 'boolean') config.smtpSecure = smtpSecure; + + const smtpUser = parseSetting('smtp_user'); + if (typeof smtpUser === 'string') config.smtpUser = smtpUser; + + const smtpPass = parseSetting('smtp_pass'); + if (typeof smtpPass === 'string') config.smtpPass = smtpPass; + + const smtpFrom = parseSetting('smtp_from'); + if (typeof smtpFrom === 'string') config.smtpFrom = smtpFrom; + + const smtpTo = parseSetting('smtp_to'); + if (typeof smtpTo === 'string') config.smtpTo = smtpTo; + + const notifyCooldownSec = parseSetting('notify_cooldown_sec'); + if (typeof notifyCooldownSec === 'number' && Number.isFinite(notifyCooldownSec) && notifyCooldownSec >= 0) { + config.notifyCooldownSec = Math.trunc(notifyCooldownSec); + } + + const adminIpAllowlist = parseSetting('admin_ip_allowlist'); + if (adminIpAllowlist !== undefined) { + config.adminIpAllowlist = toStringList(adminIpAllowlist); + } + + console.log('Loaded runtime settings overrides'); +} catch { /* first run, table may not exist */ } + +const app = Fastify({ logger: true }); + +await app.register(cors); + +// Auth middleware for /api routes +app.addHook('onRequest', async (request, reply) => { + if (request.url.startsWith('/api/')) { + await authMiddleware(request, reply); + } +}); + +// Register API routes +await app.register(sitesRoutes); +await app.register(accountsRoutes); +await app.register(checkinRoutes); +await app.register(tokensRoutes); +await app.register(statsRoutes); +await app.register(authRoutes); +await app.register(settingsRoutes); +await app.register(accountTokensRoutes); +await app.register(searchRoutes); +await app.register(eventsRoutes); +await app.register(taskRoutes); +await app.register(testRoutes); +await app.register(monitorRoutes); + +// Register OpenAI-compatible proxy routes +await app.register(proxyRoutes); + +// Serve static web frontend in production +const webDir = resolve('dist/web'); +if (existsSync(webDir)) { + await app.register(fastifyStatic, { + root: webDir, + prefix: '/', + wildcard: false, + setHeaders: (res, filePath) => { + const normalizedPath = normalize(filePath); + if (normalizedPath.includes(`${sep}assets${sep}`)) { + res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); + return; + } + if (normalizedPath.endsWith(`${sep}index.html`)) { + res.setHeader('Cache-Control', 'no-cache'); + } + }, + }); + // SPA fallback + app.setNotFoundHandler(async (request, reply) => { + if (!request.url.startsWith('/api/') && !request.url.startsWith('/v1/')) { + return reply.sendFile('index.html'); + } + reply.code(404).send({ error: 'Not found' }); + }); +} + +// Start scheduler +startScheduler(); + +// Start server +try { + const listenHost = '0.0.0.0'; + await app.listen({ port: config.port, host: listenHost }); + const summaryLines = buildStartupSummaryLines({ + port: config.port, + host: listenHost, + authToken: config.authToken, + proxyToken: config.proxyToken, + }); + for (const line of summaryLines) { + console.log(line); + } +} catch (err) { + app.log.error(err); + process.exit(1); +} diff --git a/src/server/middleware/auth.test.ts b/src/server/middleware/auth.test.ts new file mode 100644 index 0000000..c30ff1c --- /dev/null +++ b/src/server/middleware/auth.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest'; +import { extractClientIp, isIpAllowed } from './auth.js'; + +describe('auth middleware IP helpers', () => { + it('extracts first forwarded IP and normalizes ipv4-mapped address', () => { + const ip = extractClientIp('::ffff:10.0.0.1', '198.51.100.7, 203.0.113.2'); + expect(ip).toBe('198.51.100.7'); + }); + + it('allows request when allowlist is empty', () => { + expect(isIpAllowed('203.0.113.8', [])).toBe(true); + }); + + it('rejects non-allowlisted IP when allowlist is configured', () => { + expect(isIpAllowed('203.0.113.8', ['203.0.113.9'])).toBe(false); + expect(isIpAllowed('203.0.113.9', ['203.0.113.9'])).toBe(true); + }); +}); diff --git a/src/server/middleware/auth.ts b/src/server/middleware/auth.ts new file mode 100644 index 0000000..ac07e14 --- /dev/null +++ b/src/server/middleware/auth.ts @@ -0,0 +1,70 @@ +import { FastifyRequest, FastifyReply } from 'fastify'; +import { config } from '../config.js'; + +function normalizeIp(rawIp: string | null | undefined): string { + const ip = (rawIp || '').trim(); + if (!ip) return ''; + if (ip.startsWith('::ffff:')) return ip.slice('::ffff:'.length).trim(); + if (ip === '::1') return '127.0.0.1'; + return ip; +} + +export function extractClientIp(remoteIp: string | null | undefined, xForwardedFor?: string | string[] | undefined): string { + if (Array.isArray(xForwardedFor)) { + const first = xForwardedFor.find((item) => item && item.trim().length > 0); + if (first) { + return normalizeIp(first.split(',')[0]); + } + } else if (typeof xForwardedFor === 'string' && xForwardedFor.trim().length > 0) { + return normalizeIp(xForwardedFor.split(',')[0]); + } + return normalizeIp(remoteIp); +} + +export function isIpAllowed(clientIp: string, allowlist: string[]): boolean { + if (!allowlist || allowlist.length === 0) return true; + const normalizedClientIp = normalizeIp(clientIp); + if (!normalizedClientIp) return false; + return allowlist.some((item) => normalizeIp(item) === normalizedClientIp); +} + +export async function authMiddleware(request: FastifyRequest, reply: FastifyReply) { + const clientIp = extractClientIp(request.ip, request.headers['x-forwarded-for']); + if (!isIpAllowed(clientIp, config.adminIpAllowlist)) { + reply.code(403).send({ error: 'IP not allowed' }); + return; + } + + const auth = request.headers.authorization; + if (!auth) { + reply.code(401).send({ error: 'Missing Authorization header' }); + return; + } + const token = auth.replace('Bearer ', ''); + if (token !== config.authToken) { + reply.code(403).send({ error: 'Invalid token' }); + return; + } +} + +export async function proxyAuthMiddleware(request: FastifyRequest, reply: FastifyReply) { + const auth = typeof request.headers.authorization === 'string' + ? request.headers.authorization + : ''; + const apiKeyHeader = typeof request.headers['x-api-key'] === 'string' + ? request.headers['x-api-key'] + : ''; + const token = auth + ? auth.replace(/^Bearer\s+/i, '').trim() + : apiKeyHeader.trim(); + + if (!token) { + reply.code(401).send({ error: 'Missing Authorization or x-api-key header' }); + return; + } + + if (token !== config.proxyToken) { + reply.code(403).send({ error: 'Invalid API key' }); + return; + } +} diff --git a/src/server/routes/api/accountTokens.sync.test.ts b/src/server/routes/api/accountTokens.sync.test.ts new file mode 100644 index 0000000..7d509ec --- /dev/null +++ b/src/server/routes/api/accountTokens.sync.test.ts @@ -0,0 +1,378 @@ +import Fastify, { type FastifyInstance } from 'fastify'; +import { describe, expect, it, beforeAll, beforeEach, afterAll, vi } from 'vitest'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { mkdtempSync } from 'node:fs'; +import { and, eq, sql } from 'drizzle-orm'; + +const getApiTokensMock = vi.fn(); +const getApiTokenMock = vi.fn(); +const createApiTokenMock = vi.fn(); +const getUserGroupsMock = vi.fn(); +const deleteApiTokenMock = vi.fn(); + +vi.mock('../../services/platforms/index.js', () => ({ + getAdapter: () => ({ + getApiTokens: (...args: unknown[]) => getApiTokensMock(...args), + getApiToken: (...args: unknown[]) => getApiTokenMock(...args), + createApiToken: (...args: unknown[]) => createApiTokenMock(...args), + getUserGroups: (...args: unknown[]) => getUserGroupsMock(...args), + deleteApiToken: (...args: unknown[]) => deleteApiTokenMock(...args), + }), +})); + +type DbModule = typeof import('../../db/index.js'); + +describe('account tokens sync routes with site status', () => { + let app: FastifyInstance; + let db: DbModule['db']; + let schema: DbModule['schema']; + let dataDir = ''; + let seedId = 0; + + const nextSeed = () => { + seedId += 1; + return seedId; + }; + + const seedAccount = (input: { siteStatus?: 'active' | 'disabled'; accountStatus?: string; accessToken?: string | null }) => { + const id = nextSeed(); + const site = db.insert(schema.sites).values({ + name: `site-${id}`, + url: `https://site-${id}.example.com`, + platform: 'new-api', + }).returning().get(); + if (input.siteStatus === 'disabled') { + db.run(sql`update sites set status = 'disabled' where id = ${site.id}`); + } + + const account = db.insert(schema.accounts).values({ + siteId: site.id, + username: `user-${id}`, + accessToken: input.accessToken ?? `access-token-${id}`, + status: input.accountStatus ?? 'active', + }).returning().get(); + + return { site, account }; + }; + + beforeAll(async () => { + dataDir = mkdtempSync(join(tmpdir(), 'metapi-account-tokens-sync-')); + process.env.DATA_DIR = dataDir; + + await import('../../db/migrate.js'); + const dbModule = await import('../../db/index.js'); + const routesModule = await import('./accountTokens.js'); + db = dbModule.db; + schema = dbModule.schema; + + app = Fastify(); + await app.register(routesModule.accountTokensRoutes); + }); + + beforeEach(() => { + getApiTokensMock.mockReset(); + getApiTokenMock.mockReset(); + createApiTokenMock.mockReset(); + getUserGroupsMock.mockReset(); + deleteApiTokenMock.mockReset(); + seedId = 0; + + db.delete(schema.accountTokens).run(); + db.delete(schema.routeChannels).run(); + db.delete(schema.tokenRoutes).run(); + db.delete(schema.tokenModelAvailability).run(); + db.delete(schema.modelAvailability).run(); + db.delete(schema.checkinLogs).run(); + db.delete(schema.accounts).run(); + db.delete(schema.sites).run(); + }); + + afterAll(async () => { + await app.close(); + delete process.env.DATA_DIR; + }); + + it('returns skipped for single-account sync when site is disabled', async () => { + const { account } = seedAccount({ siteStatus: 'disabled' }); + + const response = await app.inject({ + method: 'POST', + url: `/api/account-tokens/sync/${account.id}`, + }); + + expect(response.statusCode).toBe(200); + expect(response.json()).toMatchObject({ + success: true, + synced: false, + status: 'skipped', + reason: 'site_disabled', + }); + expect(getApiTokensMock).not.toHaveBeenCalled(); + expect(getApiTokenMock).not.toHaveBeenCalled(); + }); + + it('returns skipped when upstream has no api tokens', async () => { + const { account } = seedAccount({ siteStatus: 'active' }); + getApiTokensMock.mockResolvedValue([]); + getApiTokenMock.mockResolvedValue(null); + + const response = await app.inject({ + method: 'POST', + url: `/api/account-tokens/sync/${account.id}`, + }); + + expect(response.statusCode).toBe(200); + expect(response.json()).toMatchObject({ + success: true, + synced: false, + status: 'skipped', + reason: 'no_upstream_tokens', + }); + + const tokenRows = db.select() + .from(schema.accountTokens) + .where(eq(schema.accountTokens.accountId, account.id)) + .all(); + expect(tokenRows.length).toBe(0); + }); + + it('sync-all skips disabled-site accounts and syncs active-site accounts', async () => { + const disabled = seedAccount({ siteStatus: 'disabled' }); + const active = seedAccount({ siteStatus: 'active' }); + + getApiTokensMock.mockResolvedValue([ + { name: 'default', key: 'sk-synced-token', enabled: true }, + ]); + + const response = await app.inject({ + method: 'POST', + url: '/api/account-tokens/sync-all', + payload: { wait: true }, + }); + + expect(response.statusCode).toBe(200); + + const body = response.json() as { + success: boolean; + summary: { + total: number; + synced: number; + skipped: number; + failed: number; + }; + results: Array<{ accountId: number; status: string; reason?: string; synced?: boolean }>; + }; + + expect(body.success).toBe(true); + expect(body.summary).toMatchObject({ + total: 2, + synced: 1, + skipped: 1, + failed: 0, + }); + + const skipped = body.results.find((item) => item.accountId === disabled.account.id); + const synced = body.results.find((item) => item.accountId === active.account.id); + + expect(skipped).toMatchObject({ + accountId: disabled.account.id, + status: 'skipped', + reason: 'site_disabled', + }); + expect(synced).toMatchObject({ + accountId: active.account.id, + status: 'synced', + synced: true, + }); + + const syncedDefaultToken = db.select() + .from(schema.accountTokens) + .where(and(eq(schema.accountTokens.accountId, active.account.id), eq(schema.accountTokens.isDefault, true))) + .get(); + expect(syncedDefaultToken?.token).toBe('sk-synced-token'); + }); + + it('creates token via upstream api and syncs into local store when manual token is omitted', async () => { + const { account, site } = seedAccount({ siteStatus: 'active' }); + createApiTokenMock.mockResolvedValue(true); + getApiTokensMock.mockResolvedValue([ + { name: 'created-from-upstream', key: 'sk-created-upstream-token', enabled: true }, + ]); + + const response = await app.inject({ + method: 'POST', + url: '/api/account-tokens', + payload: { + accountId: account.id, + name: 'created-from-upstream', + }, + }); + + expect(response.statusCode).toBe(200); + expect(response.json()).toMatchObject({ + success: true, + createdViaUpstream: true, + synced: true, + status: 'synced', + }); + expect(createApiTokenMock).toHaveBeenCalledTimes(1); + expect(createApiTokenMock.mock.calls[0][0]).toBe(site.url); + expect(createApiTokenMock.mock.calls[0][1]).toBe(account.accessToken); + + const tokenRows = db.select() + .from(schema.accountTokens) + .where(eq(schema.accountTokens.accountId, account.id)) + .all(); + + expect(tokenRows.length).toBe(1); + expect(tokenRows[0].name).toBe('created-from-upstream'); + expect(tokenRows[0].token).toBe('sk-created-upstream-token'); + expect(tokenRows[0].source).toBe('sync'); + }); + + it('passes token creation options to upstream adapter', async () => { + const { account } = seedAccount({ siteStatus: 'active' }); + createApiTokenMock.mockResolvedValue(true); + getApiTokensMock.mockResolvedValue([ + { name: 'custom-token', key: 'sk-created-upstream-token', enabled: true }, + ]); + + const response = await app.inject({ + method: 'POST', + url: '/api/account-tokens', + payload: { + accountId: account.id, + name: 'custom-token', + group: 'vip', + unlimitedQuota: false, + remainQuota: 123456, + expiredTime: 2_000_000_000, + allowIps: '1.1.1.1,2.2.2.2', + }, + }); + + expect(response.statusCode).toBe(200); + expect(createApiTokenMock).toHaveBeenCalledTimes(1); + expect(createApiTokenMock.mock.calls[0][3]).toMatchObject({ + name: 'custom-token', + group: 'vip', + unlimitedQuota: false, + remainQuota: 123456, + expiredTime: 2_000_000_000, + allowIps: '1.1.1.1,2.2.2.2', + }); + }); + + it('returns 400 when limited token misses remainQuota', async () => { + const { account } = seedAccount({ siteStatus: 'active' }); + + const response = await app.inject({ + method: 'POST', + url: '/api/account-tokens', + payload: { + accountId: account.id, + name: 'bad-token', + unlimitedQuota: false, + }, + }); + + expect(response.statusCode).toBe(400); + expect(response.json()).toMatchObject({ + success: false, + message: '有限额度令牌必须填写 remainQuota', + }); + expect(createApiTokenMock).not.toHaveBeenCalled(); + }); + + it('returns 502 when upstream token creation fails', async () => { + const { account } = seedAccount({ siteStatus: 'active' }); + createApiTokenMock.mockResolvedValue(false); + + const response = await app.inject({ + method: 'POST', + url: '/api/account-tokens', + payload: { + accountId: account.id, + name: 'created-from-upstream', + }, + }); + + expect(response.statusCode).toBe(502); + expect(response.json()).toMatchObject({ + success: false, + message: '站点创建令牌失败', + }); + }); + + it('fetches account token groups from upstream', async () => { + const { account } = seedAccount({ siteStatus: 'active' }); + getUserGroupsMock.mockResolvedValue(['default', 'vip']); + + const response = await app.inject({ + method: 'GET', + url: `/api/account-tokens/groups/${account.id}`, + }); + + expect(response.statusCode).toBe(200); + expect(response.json()).toMatchObject({ + success: true, + groups: ['default', 'vip'], + }); + expect(getUserGroupsMock).toHaveBeenCalledTimes(1); + }); + + it('deletes upstream token before removing local token', async () => { + const { account, site } = seedAccount({ siteStatus: 'active' }); + const token = db.insert(schema.accountTokens).values({ + accountId: account.id, + name: 'upstream-token', + token: 'sk-upstream-token', + source: 'sync', + enabled: true, + isDefault: false, + }).returning().get(); + deleteApiTokenMock.mockResolvedValue(true); + + const response = await app.inject({ + method: 'DELETE', + url: `/api/account-tokens/${token.id}`, + }); + + expect(response.statusCode).toBe(200); + expect(deleteApiTokenMock).toHaveBeenCalledTimes(1); + expect(deleteApiTokenMock.mock.calls[0][0]).toBe(site.url); + expect(deleteApiTokenMock.mock.calls[0][1]).toBe(account.accessToken); + expect(deleteApiTokenMock.mock.calls[0][2]).toBe('sk-upstream-token'); + + const removed = db.select().from(schema.accountTokens).where(eq(schema.accountTokens.id, token.id)).get(); + expect(removed).toBeUndefined(); + }); + + it('keeps local token when upstream deletion fails', async () => { + const { account } = seedAccount({ siteStatus: 'active' }); + const token = db.insert(schema.accountTokens).values({ + accountId: account.id, + name: 'upstream-token', + token: 'sk-upstream-token', + source: 'sync', + enabled: true, + isDefault: false, + }).returning().get(); + deleteApiTokenMock.mockResolvedValue(false); + + const response = await app.inject({ + method: 'DELETE', + url: `/api/account-tokens/${token.id}`, + }); + + expect(response.statusCode).toBe(502); + expect(response.json()).toMatchObject({ + success: false, + message: '站点删除令牌失败,本地未删除', + }); + + const existing = db.select().from(schema.accountTokens).where(eq(schema.accountTokens.id, token.id)).get(); + expect(existing).toBeDefined(); + }); +}); diff --git a/src/server/routes/api/accountTokens.ts b/src/server/routes/api/accountTokens.ts new file mode 100644 index 0000000..6f2932b --- /dev/null +++ b/src/server/routes/api/accountTokens.ts @@ -0,0 +1,726 @@ +import { FastifyInstance } from 'fastify'; +import { and, eq } from 'drizzle-orm'; +import { db, schema } from '../../db/index.js'; +import { + ensureDefaultTokenForAccount, + listTokensWithRelations, + normalizeTokenForDisplay, + maskToken, + repairDefaultToken, + setDefaultToken, + syncTokensFromUpstream, +} from '../../services/accountTokenService.js'; +import { getAdapter } from '../../services/platforms/index.js'; +import { resolvePlatformUserId } from '../../services/accountExtraConfig.js'; +import { startBackgroundTask } from '../../services/backgroundTaskService.js'; + +type AccountWithSiteRow = { + accounts: typeof schema.accounts.$inferSelect; + sites: typeof schema.sites.$inferSelect; +}; + +type SyncExecutionResult = { + accountId: number; + accountName: string; + accountStatus: string | null; + siteId: number; + siteName: string; + siteStatus: string | null; + status: 'synced' | 'skipped' | 'failed'; + reason?: string; + message?: string; + synced: boolean; + created: number; + updated: number; + total: number; + defaultTokenId?: number | null; +}; + +const TOKEN_SYNC_TIMEOUT_MS = 15_000; +const SYNC_ALL_BATCH_SIZE = 3; + +function buildSyncAccountLabel(item: SyncExecutionResult): string { + const account = (item.accountName || `#${item.accountId}`).trim(); + const site = (item.siteName || 'unknown-site').trim(); + return `${account} @ ${site}`; +} + +function buildSyncReason(item: SyncExecutionResult): string { + const message = String(item.message || item.reason || '').trim(); + if (!message) return ''; + if (message.length <= 32) return message; + return `${message.slice(0, 32)}...`; +} + +function buildTokenSyncTaskDetailMessage(results: SyncExecutionResult[]): string { + if (!Array.isArray(results) || results.length === 0) return ''; + + const synced = results.filter((item) => item.status === 'synced'); + const skipped = results.filter((item) => item.status === 'skipped'); + const failed = results.filter((item) => item.status === 'failed'); + + const renderRows = (rows: SyncExecutionResult[], withReason = false) => { + const sliced = rows.slice(0, 12).map((item) => { + const base = buildSyncAccountLabel(item); + if (!withReason) return base; + const reason = buildSyncReason(item); + return reason ? `${base}(${reason})` : base; + }); + if (rows.length > 12) sliced.push(`...等${rows.length}个`); + return sliced.join('、'); + }; + + const segments: string[] = [ + `成功(${synced.length}): ${synced.length > 0 ? renderRows(synced) : '-'}`, + `跳过(${skipped.length}): ${skipped.length > 0 ? renderRows(skipped, true) : '-'}`, + `失败(${failed.length}): ${failed.length > 0 ? renderRows(failed, true) : '-'}`, + ]; + return segments.join('\n'); +} + +function isSiteDisabled(status?: string | null): boolean { + return (status || 'active') === 'disabled'; +} + +function asTrimmedString(value: unknown): string | undefined { + if (typeof value !== 'string') return undefined; + const trimmed = value.trim(); + return trimmed || undefined; +} + +function parseOptionalBoolean(value: unknown): boolean | undefined { + if (typeof value === 'boolean') return value; + if (typeof value !== 'string') return undefined; + const normalized = value.trim().toLowerCase(); + if (normalized === 'true' || normalized === '1') return true; + if (normalized === 'false' || normalized === '0') return false; + return undefined; +} + +function parsePositiveInteger(value: unknown): number | undefined { + if (typeof value === 'number' && Number.isFinite(value)) { + const normalized = Math.trunc(value); + return normalized > 0 ? normalized : undefined; + } + if (typeof value !== 'string') return undefined; + const trimmed = value.trim(); + if (!trimmed) return undefined; + const normalized = Number.parseInt(trimmed, 10); + if (Number.isNaN(normalized) || normalized <= 0) return undefined; + return normalized; +} + +function parseExpiredTime(value: unknown): number | undefined { + if (typeof value === 'number' && Number.isFinite(value)) { + const normalized = Math.trunc(value); + return normalized > 0 ? normalized : undefined; + } + if (typeof value !== 'string') return undefined; + const trimmed = value.trim(); + if (!trimmed) return undefined; + + if (/^\d+$/.test(trimmed)) { + const numericValue = Number.parseInt(trimmed, 10); + if (!Number.isNaN(numericValue) && numericValue > 0) return numericValue; + } + + const parsedMs = Date.parse(trimmed); + if (!Number.isFinite(parsedMs)) return undefined; + const seconds = Math.trunc(parsedMs / 1000); + return seconds > 0 ? seconds : undefined; +} + +async function withTimeout(fn: () => Promise, timeoutMs: number, timeoutMessage: string): Promise { + let timer: ReturnType | null = null; + try { + return await Promise.race([ + fn(), + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs); + }), + ]); + } finally { + if (timer) clearTimeout(timer); + } +} + +async function executeAccountTokenSync(row: AccountWithSiteRow): Promise { + const accountId = row.accounts.id; + const base = { + accountId, + accountName: row.accounts.username || `account-${accountId}`, + accountStatus: row.accounts.status, + siteId: row.sites.id, + siteName: row.sites.name, + siteStatus: row.sites.status, + }; + + if (isSiteDisabled(row.sites.status)) { + return { + ...base, + status: 'skipped', + reason: 'site_disabled', + message: 'site disabled', + synced: false, + created: 0, + updated: 0, + total: 0, + defaultTokenId: null, + }; + } + + if (!row.accounts.accessToken) { + if (row.accounts.apiToken) { + ensureDefaultTokenForAccount(accountId, row.accounts.apiToken, { name: 'default', source: 'legacy' }); + } + return { + ...base, + status: 'skipped', + reason: 'missing_access_token', + synced: false, + created: 0, + updated: 0, + total: 0, + defaultTokenId: null, + }; + } + + const adapter = getAdapter(row.sites.platform); + if (!adapter) { + return { + ...base, + status: 'failed', + reason: 'unsupported_platform', + message: `不支持的平台: ${row.sites.platform}`, + synced: false, + created: 0, + updated: 0, + total: 0, + defaultTokenId: null, + }; + } + + try { + const platformUserId = resolvePlatformUserId(row.accounts.extraConfig, row.accounts.username); + let tokens = await withTimeout( + () => adapter.getApiTokens(row.sites.url, row.accounts.accessToken, platformUserId), + TOKEN_SYNC_TIMEOUT_MS, + `token sync timeout (${Math.round(TOKEN_SYNC_TIMEOUT_MS / 1000)}s)`, + ); + + if (tokens.length === 0) { + const fallback = await withTimeout( + () => adapter.getApiToken(row.sites.url, row.accounts.accessToken, platformUserId), + TOKEN_SYNC_TIMEOUT_MS, + `token sync timeout (${Math.round(TOKEN_SYNC_TIMEOUT_MS / 1000)}s)`, + ); + if (fallback) { + tokens = [{ name: 'default', key: fallback, enabled: true }]; + } + } + + if (tokens.length === 0) { + return { + ...base, + status: 'skipped', + reason: 'no_upstream_tokens', + message: 'upstream returned no api tokens', + synced: false, + created: 0, + updated: 0, + total: 0, + defaultTokenId: null, + }; + } + + const synced = syncTokensFromUpstream(accountId, tokens); + return { + ...base, + status: 'synced', + synced: true, + ...synced, + }; + } catch (error: any) { + return { + ...base, + status: 'failed', + reason: 'sync_error', + message: error?.message || 'sync failed', + synced: false, + created: 0, + updated: 0, + total: 0, + defaultTokenId: null, + }; + } +} + +function appendTokenSyncEvent(result: SyncExecutionResult) { + const title = result.status === 'synced' + ? '令牌同步成功' + : (result.status === 'skipped' ? '令牌同步跳过' : '令牌同步失败'); + const level = result.status === 'synced' + ? 'info' + : (result.status === 'skipped' ? 'warning' : 'error'); + const detail = result.status === 'synced' + ? `新增 ${result.created},更新 ${result.updated},总数 ${result.total}` + : (result.message || result.reason || 'sync skipped'); + + try { + db.insert(schema.events).values({ + type: 'token', + title, + message: `${result.accountName} @ ${result.siteName}: ${detail}`, + level, + relatedId: result.accountId, + relatedType: 'account', + }).run(); + } catch {} +} + +async function executeSyncAllAccountTokens() { + const rows = db.select().from(schema.accounts) + .innerJoin(schema.sites, eq(schema.accounts.siteId, schema.sites.id)) + .where(eq(schema.accounts.status, 'active')) + .all(); + + const results: SyncExecutionResult[] = []; + for (let offset = 0; offset < rows.length; offset += SYNC_ALL_BATCH_SIZE) { + const batch = rows.slice(offset, offset + SYNC_ALL_BATCH_SIZE); + const batchResults = await Promise.all( + batch.map(async (row) => { + const result = await executeAccountTokenSync(row); + appendTokenSyncEvent(result); + return result; + }), + ); + results.push(...batchResults); + } + + const summary = { + total: results.length, + synced: results.filter((item) => item.status === 'synced').length, + skipped: results.filter((item) => item.status === 'skipped').length, + failed: results.filter((item) => item.status === 'failed').length, + created: results.reduce((acc, item) => acc + item.created, 0), + updated: results.reduce((acc, item) => acc + item.updated, 0), + }; + + return { summary, results }; +} + +export async function accountTokensRoutes(app: FastifyInstance) { + app.get<{ Querystring: { accountId?: string } }>('/api/account-tokens', async (request) => { + const accountId = request.query.accountId ? Number.parseInt(request.query.accountId, 10) : undefined; + return listTokensWithRelations(Number.isFinite(accountId as number) ? accountId : undefined); + }); + + app.post<{ Body: { + accountId: number; + name?: string; + token?: string; + enabled?: boolean; + isDefault?: boolean; + source?: string; + group?: string; + unlimitedQuota?: boolean | string; + remainQuota?: number | string; + expiredTime?: number | string; + allowIps?: string; + modelLimitsEnabled?: boolean | string; + modelLimits?: string; + } }>('/api/account-tokens', async (request, reply) => { + const body = request.body; + const row = db.select() + .from(schema.accounts) + .innerJoin(schema.sites, eq(schema.accounts.siteId, schema.sites.id)) + .where(eq(schema.accounts.id, body.accountId)) + .get(); + if (!row) { + return reply.code(404).send({ success: false, message: '账号不存在' }); + } + + const tokenValue = (body.token || '').trim(); + if (tokenValue) { + const now = new Date().toISOString(); + const existing = db.select().from(schema.accountTokens) + .where(eq(schema.accountTokens.accountId, body.accountId)) + .all(); + + const created = db.insert(schema.accountTokens).values({ + accountId: body.accountId, + name: (body.name || '').trim() || (existing.length === 0 ? 'default' : `token-${existing.length + 1}`), + token: tokenValue, + source: body.source || 'manual', + enabled: body.enabled ?? true, + isDefault: body.isDefault ?? false, + createdAt: now, + updatedAt: now, + }).returning().get(); + + if (body.isDefault || (existing.length === 0 && (body.enabled ?? true))) { + setDefaultToken(created.id); + } else if (existing.every((token) => !token.isDefault) && (body.enabled ?? true)) { + setDefaultToken(created.id); + } + + return { success: true, token: created }; + } + + const account = row.accounts; + const site = row.sites; + + if (isSiteDisabled(site.status)) { + return reply.code(400).send({ success: false, message: '站点已禁用,无法创建令牌' }); + } + + if (!account.accessToken?.trim()) { + return reply.code(400).send({ success: false, message: '账号缺少访问令牌,无法创建站点令牌' }); + } + + const adapter = getAdapter(site.platform); + if (!adapter) { + return reply.code(400).send({ success: false, message: `不支持的平台: ${site.platform}` }); + } + + const unlimitedQuota = body.unlimitedQuota === undefined + ? undefined + : parseOptionalBoolean(body.unlimitedQuota); + if (body.unlimitedQuota !== undefined && unlimitedQuota === undefined) { + return reply.code(400).send({ success: false, message: 'unlimitedQuota 参数无效' }); + } + + const remainQuota = body.remainQuota === undefined + ? undefined + : parsePositiveInteger(body.remainQuota); + if (body.remainQuota !== undefined && remainQuota === undefined) { + return reply.code(400).send({ success: false, message: 'remainQuota 必须是正整数' }); + } + if (unlimitedQuota === false && remainQuota === undefined) { + return reply.code(400).send({ success: false, message: '有限额度令牌必须填写 remainQuota' }); + } + + const expiredTime = body.expiredTime === undefined + ? undefined + : parseExpiredTime(body.expiredTime); + if (body.expiredTime !== undefined && expiredTime === undefined) { + return reply.code(400).send({ success: false, message: 'expiredTime 参数无效' }); + } + + const modelLimitsEnabled = body.modelLimitsEnabled === undefined + ? undefined + : parseOptionalBoolean(body.modelLimitsEnabled); + if (body.modelLimitsEnabled !== undefined && modelLimitsEnabled === undefined) { + return reply.code(400).send({ success: false, message: 'modelLimitsEnabled 参数无效' }); + } + + const platformUserId = resolvePlatformUserId(account.extraConfig, account.username); + const createdViaUpstream = await adapter.createApiToken( + site.url, + account.accessToken, + platformUserId, + { + name: asTrimmedString(body.name), + group: asTrimmedString(body.group), + unlimitedQuota, + remainQuota, + expiredTime, + allowIps: asTrimmedString(body.allowIps), + modelLimitsEnabled, + modelLimits: asTrimmedString(body.modelLimits), + }, + ); + if (!createdViaUpstream) { + return reply.code(502).send({ success: false, message: '站点创建令牌失败' }); + } + + const syncResult = await executeAccountTokenSync(row); + appendTokenSyncEvent(syncResult); + + if (syncResult.status === 'failed') { + return reply.code(502).send({ success: false, message: syncResult.message || '同步站点令牌失败' }); + } + if (syncResult.status === 'skipped') { + return reply.code(502).send({ success: false, message: syncResult.message || '站点未返回可用令牌' }); + } + + const preferred = db.select().from(schema.accountTokens) + .where(and(eq(schema.accountTokens.accountId, account.id), eq(schema.accountTokens.isDefault, true))) + .get(); + const token = preferred || db.select().from(schema.accountTokens) + .where(eq(schema.accountTokens.accountId, account.id)) + .all() + .slice(-1)[0] || null; + + return { + success: true, + createdViaUpstream: true, + ...syncResult, + token, + }; + }); + + app.put<{ Params: { id: string }; Body: { name?: string; token?: string; enabled?: boolean; isDefault?: boolean; source?: string } }>('/api/account-tokens/:id', async (request, reply) => { + const tokenId = Number.parseInt(request.params.id, 10); + if (Number.isNaN(tokenId)) { + return reply.code(400).send({ success: false, message: '令牌 ID 无效' }); + } + + const existing = db.select().from(schema.accountTokens).where(eq(schema.accountTokens.id, tokenId)).get(); + if (!existing) { + return reply.code(404).send({ success: false, message: '令牌不存在' }); + } + + const body = request.body; + const updates: Record = { updatedAt: new Date().toISOString() }; + + if (body.name !== undefined) { + updates.name = (body.name || '').trim() || existing.name; + } + + if (body.token !== undefined) { + const tokenValue = body.token.trim(); + if (!tokenValue) { + return reply.code(400).send({ success: false, message: '令牌不能为空' }); + } + updates.token = tokenValue; + } + + if (body.enabled !== undefined) updates.enabled = body.enabled; + if (body.source !== undefined) updates.source = body.source; + if (body.isDefault !== undefined) updates.isDefault = body.isDefault; + + db.update(schema.accountTokens).set(updates).where(eq(schema.accountTokens.id, tokenId)).run(); + + const latest = db.select().from(schema.accountTokens).where(eq(schema.accountTokens.id, tokenId)).get(); + if (!latest) { + return reply.code(500).send({ success: false, message: '更新失败' }); + } + + if (body.isDefault === true) { + setDefaultToken(tokenId); + } else if (latest.isDefault && latest.enabled) { + setDefaultToken(tokenId); + } else if (existing.isDefault && !latest.enabled) { + repairDefaultToken(existing.accountId); + } else if (body.isDefault === false && existing.isDefault) { + repairDefaultToken(existing.accountId); + } + + return { success: true, token: latest }; + }); + + app.post<{ Params: { id: string } }>('/api/account-tokens/:id/default', async (request, reply) => { + const tokenId = Number.parseInt(request.params.id, 10); + if (Number.isNaN(tokenId)) { + return reply.code(400).send({ success: false, message: '令牌 ID 无效' }); + } + const success = setDefaultToken(tokenId); + if (!success) { + return reply.code(404).send({ success: false, message: '令牌不存在' }); + } + return { success: true }; + }); + + app.get<{ Params: { id: string } }>('/api/account-tokens/:id/value', async (request, reply) => { + const tokenId = Number.parseInt(request.params.id, 10); + if (Number.isNaN(tokenId)) { + return reply.code(400).send({ success: false, message: '令牌 ID 无效' }); + } + + const row = db.select() + .from(schema.accountTokens) + .innerJoin(schema.accounts, eq(schema.accountTokens.accountId, schema.accounts.id)) + .innerJoin(schema.sites, eq(schema.accounts.siteId, schema.sites.id)) + .where(eq(schema.accountTokens.id, tokenId)) + .get(); + if (!row) { + return reply.code(404).send({ success: false, message: '令牌不存在' }); + } + + const tokenValue = normalizeTokenForDisplay(row.account_tokens.token, row.sites.platform); + return { + success: true, + id: row.account_tokens.id, + name: row.account_tokens.name, + token: tokenValue, + tokenMasked: maskToken(row.account_tokens.token, row.sites.platform), + }; + }); + + app.get<{ Params: { accountId: string } }>('/api/account-tokens/groups/:accountId', async (request, reply) => { + const accountId = Number.parseInt(request.params.accountId, 10); + if (Number.isNaN(accountId)) { + return reply.code(400).send({ success: false, message: '账号 ID 无效' }); + } + + const row = db.select() + .from(schema.accounts) + .innerJoin(schema.sites, eq(schema.accounts.siteId, schema.sites.id)) + .where(eq(schema.accounts.id, accountId)) + .get(); + if (!row) { + return reply.code(404).send({ success: false, message: '账号不存在' }); + } + + const account = row.accounts; + const site = row.sites; + const adapter = getAdapter(site.platform); + if (!adapter) { + return reply.code(400).send({ success: false, message: `不支持的平台: ${site.platform}` }); + } + if (!account.accessToken?.trim()) { + return reply.code(400).send({ success: false, message: '账号缺少访问令牌,无法拉取分组' }); + } + + try { + const platformUserId = resolvePlatformUserId(account.extraConfig, account.username); + const groups = await adapter.getUserGroups(site.url, account.accessToken, platformUserId); + const normalized = Array.from(new Set((groups || []).map((item) => String(item || '').trim()).filter(Boolean))); + return { success: true, groups: normalized.length > 0 ? normalized : ['default'] }; + } catch (error: any) { + return reply.code(502).send({ + success: false, + message: error?.message || '拉取分组失败', + }); + } + }); + + app.delete<{ Params: { id: string } }>('/api/account-tokens/:id', async (request, reply) => { + const tokenId = Number.parseInt(request.params.id, 10); + if (Number.isNaN(tokenId)) { + return reply.code(400).send({ success: false, message: '令牌 ID 无效' }); + } + + const row = db.select() + .from(schema.accountTokens) + .innerJoin(schema.accounts, eq(schema.accountTokens.accountId, schema.accounts.id)) + .innerJoin(schema.sites, eq(schema.accounts.siteId, schema.sites.id)) + .where(eq(schema.accountTokens.id, tokenId)) + .get(); + if (!row) { + return reply.code(404).send({ success: false, message: '令牌不存在' }); + } + + const existing = row.account_tokens; + const account = row.accounts; + const site = row.sites; + const adapter = getAdapter(site.platform); + const shouldDeleteUpstream = !isSiteDisabled(site.status) && !!account.accessToken?.trim() && !!adapter; + + if (shouldDeleteUpstream) { + const platformUserId = resolvePlatformUserId(account.extraConfig, account.username); + const upstreamDeleted = await adapter!.deleteApiToken( + site.url, + account.accessToken, + existing.token, + platformUserId, + ); + if (!upstreamDeleted) { + return reply.code(502).send({ success: false, message: '站点删除令牌失败,本地未删除' }); + } + } + + db.delete(schema.accountTokens).where(eq(schema.accountTokens.id, tokenId)).run(); + + if (existing.isDefault) { + repairDefaultToken(existing.accountId); + } + + return { success: true }; + }); + + app.post<{ Params: { accountId: string } }>('/api/account-tokens/sync/:accountId', async (request, reply) => { + const accountId = Number.parseInt(request.params.accountId, 10); + if (Number.isNaN(accountId)) { + return reply.code(400).send({ success: false, message: '账号 ID 无效' }); + } + + const row = db.select().from(schema.accounts) + .innerJoin(schema.sites, eq(schema.accounts.siteId, schema.sites.id)) + .where(eq(schema.accounts.id, accountId)) + .get(); + + if (!row) { + return reply.code(404).send({ success: false, message: '账号不存在' }); + } + + const result = await executeAccountTokenSync(row); + appendTokenSyncEvent(result); + if (result.status === 'failed' && result.reason === 'unsupported_platform') { + return reply.code(400).send({ success: false, message: result.message }); + } + if (result.status === 'failed') { + return reply.code(502).send({ success: false, message: result.message || '同步失败' }); + } + return { success: true, ...result }; + }); + + app.post<{ Body?: { wait?: boolean } }>('/api/account-tokens/sync-all', async (request, reply) => { + if (request.body?.wait) { + const syncResult = await executeSyncAllAccountTokens(); + return { success: true, ...syncResult }; + } + + const { task, reused } = startBackgroundTask( + { + type: 'token', + title: '同步全部账号令牌', + dedupeKey: 'sync-all-account-tokens', + notifyOnFailure: true, + successTitle: (currentTask) => { + const summary = (currentTask.result as any)?.summary; + if (!summary) return '同步全部账号令牌已完成'; + return `同步全部账号令牌已完成(成功${summary.synced}/跳过${summary.skipped}/失败${summary.failed})`; + }, + failureTitle: () => '同步全部账号令牌失败', + successMessage: (currentTask) => { + const summary = (currentTask.result as any)?.summary; + const results = (currentTask.result as any)?.results as SyncExecutionResult[] | undefined; + if (!summary) return '全部账号令牌同步任务已完成'; + const detail = buildTokenSyncTaskDetailMessage(Array.isArray(results) ? results : []); + return detail + ? `全部账号令牌同步完成:成功 ${summary.synced},跳过 ${summary.skipped},失败 ${summary.failed}\n${detail}` + : `全部账号令牌同步完成:成功 ${summary.synced},跳过 ${summary.skipped},失败 ${summary.failed}`; + }, + failureMessage: (currentTask) => `全部账号令牌同步失败:${currentTask.error || 'unknown error'}`, + }, + async () => executeSyncAllAccountTokens(), + ); + + return reply.code(202).send({ + success: true, + queued: true, + reused, + jobId: task.id, + status: task.status, + message: reused + ? '令牌同步任务执行中,请稍后查看程序日志' + : '已开始全部账号令牌同步,请稍后查看程序日志', + }); + }); + + app.get<{ Params: { accountId: string } }>('/api/account-tokens/account/:accountId/default', async (request, reply) => { + const accountId = Number.parseInt(request.params.accountId, 10); + if (Number.isNaN(accountId)) { + return reply.code(400).send({ success: false, message: '账号 ID 无效' }); + } + + const row = db.select() + .from(schema.accountTokens) + .innerJoin(schema.accounts, eq(schema.accountTokens.accountId, schema.accounts.id)) + .innerJoin(schema.sites, eq(schema.accounts.siteId, schema.sites.id)) + .where(and(eq(schema.accountTokens.accountId, accountId), eq(schema.accountTokens.isDefault, true))) + .get(); + + return { + success: true, + token: row + ? (() => { + const { token: rawToken, ...meta } = row.account_tokens; + return { ...meta, tokenMasked: maskToken(rawToken, row.sites.platform) }; + })() + : null, + }; + }); +} diff --git a/src/server/routes/api/accounts.healthRefreshRuntimeState.test.ts b/src/server/routes/api/accounts.healthRefreshRuntimeState.test.ts new file mode 100644 index 0000000..628d4b9 --- /dev/null +++ b/src/server/routes/api/accounts.healthRefreshRuntimeState.test.ts @@ -0,0 +1,104 @@ +import Fastify, { type FastifyInstance } from 'fastify'; +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { mkdtempSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +const refreshBalanceMock = vi.fn(); + +vi.mock('../../services/balanceService.js', () => ({ + refreshBalance: (...args: unknown[]) => refreshBalanceMock(...args), +})); + +type DbModule = typeof import('../../db/index.js'); + +describe('accounts health refresh runtime state', () => { + let app: FastifyInstance; + let db: DbModule['db']; + let schema: DbModule['schema']; + let dataDir = ''; + + beforeAll(async () => { + dataDir = mkdtempSync(join(tmpdir(), 'metapi-accounts-health-refresh-')); + process.env.DATA_DIR = dataDir; + + await import('../../db/migrate.js'); + const dbModule = await import('../../db/index.js'); + const routesModule = await import('./accounts.js'); + db = dbModule.db; + schema = dbModule.schema; + + app = Fastify(); + await app.register(routesModule.accountsRoutes); + }); + + beforeEach(() => { + refreshBalanceMock.mockReset(); + db.delete(schema.proxyLogs).run(); + db.delete(schema.checkinLogs).run(); + db.delete(schema.routeChannels).run(); + db.delete(schema.tokenRoutes).run(); + db.delete(schema.tokenModelAvailability).run(); + db.delete(schema.modelAvailability).run(); + db.delete(schema.accountTokens).run(); + db.delete(schema.accounts).run(); + db.delete(schema.sites).run(); + }); + + afterAll(async () => { + await app.close(); + delete process.env.DATA_DIR; + }); + + it('keeps degraded runtime state for unsupported checkin after health refresh', async () => { + const site = db.insert(schema.sites).values({ + name: 'Wind Hub', + url: 'https://windhub.cc', + platform: 'done-hub', + }).returning().get(); + + const account = db.insert(schema.accounts).values({ + siteId: site.id, + username: 'ld6jl3djexjf', + accessToken: 'token', + status: 'active', + extraConfig: JSON.stringify({ + runtimeHealth: { + state: 'degraded', + reason: '站点不支持签到接口', + source: 'checkin', + checkedAt: '2026-02-25T18:00:00.000Z', + }, + }), + }).returning().get(); + + refreshBalanceMock.mockResolvedValueOnce({ balance: 100, used: 0, quota: 100 }); + + const response = await app.inject({ + method: 'POST', + url: '/api/accounts/health/refresh', + payload: { accountId: account.id, wait: true }, + }); + + expect(response.statusCode).toBe(200); + const body = response.json() as { + success: boolean; + summary: { + healthy: number; + degraded: number; + failed: number; + }; + results: Array<{ state: string; status: string; message: string }>; + }; + + expect(body.success).toBe(true); + expect(body.summary.degraded).toBe(1); + expect(body.summary.healthy).toBe(0); + expect(body.summary.failed).toBe(0); + expect(body.results[0]).toMatchObject({ + state: 'degraded', + status: 'success', + message: '站点不支持签到接口', + }); + }); +}); diff --git a/src/server/routes/api/accounts.todayRewardFallback.test.ts b/src/server/routes/api/accounts.todayRewardFallback.test.ts new file mode 100644 index 0000000..b09e6f8 --- /dev/null +++ b/src/server/routes/api/accounts.todayRewardFallback.test.ts @@ -0,0 +1,185 @@ +import Fastify, { type FastifyInstance } from 'fastify'; +import { describe, expect, it, beforeAll, beforeEach, afterAll } from 'vitest'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { mkdtempSync } from 'node:fs'; +import { + formatLocalDate, + formatUtcSqlDateTime, + getLocalDayRangeUtc, + parseStoredUtcDateTime, +} from '../../services/localTimeService.js'; + +type DbModule = typeof import('../../db/index.js'); + +describe('accounts api today reward fallback', () => { + let app: FastifyInstance; + let db: DbModule['db']; + let schema: DbModule['schema']; + let dataDir = ''; + + beforeAll(async () => { + dataDir = mkdtempSync(join(tmpdir(), 'metapi-accounts-reward-fallback-')); + process.env.DATA_DIR = dataDir; + + await import('../../db/migrate.js'); + const dbModule = await import('../../db/index.js'); + const routesModule = await import('./accounts.js'); + db = dbModule.db; + schema = dbModule.schema; + + app = Fastify(); + await app.register(routesModule.accountsRoutes); + }); + + beforeEach(() => { + db.delete(schema.proxyLogs).run(); + db.delete(schema.checkinLogs).run(); + db.delete(schema.routeChannels).run(); + db.delete(schema.tokenRoutes).run(); + db.delete(schema.tokenModelAvailability).run(); + db.delete(schema.modelAvailability).run(); + db.delete(schema.accountTokens).run(); + db.delete(schema.accounts).run(); + db.delete(schema.sites).run(); + }); + + afterAll(async () => { + await app.close(); + delete process.env.DATA_DIR; + }); + + it('uses today income value when checkin reward is missing', async () => { + const today = formatLocalDate(new Date()); + const site = db.insert(schema.sites).values({ + name: 'reward-site', + url: 'https://reward-site.example.com', + platform: 'new-api', + }).returning().get(); + const account = db.insert(schema.accounts).values({ + siteId: site.id, + username: 'reward-user', + accessToken: 'token', + status: 'active', + extraConfig: JSON.stringify({ + todayIncomeSnapshot: { + day: today, + baseline: 12.5, + latest: 12.5, + updatedAt: `${today}T10:00:00.000Z`, + }, + }), + }).returning().get(); + + db.insert(schema.checkinLogs).values({ + accountId: account.id, + status: 'success', + message: 'checked in', + reward: '', + createdAt: `${today} 10:01:00`, + }).run(); + + const response = await app.inject({ + method: 'GET', + url: '/api/accounts', + }); + + expect(response.statusCode).toBe(200); + const rows = response.json() as Array<{ id: number; todayReward: number }>; + const target = rows.find((row) => row.id === account.id); + expect(target?.todayReward).toBe(12.5); + }); + + it('prefers parsed checkin reward when available', async () => { + const today = formatLocalDate(new Date()); + const site = db.insert(schema.sites).values({ + name: 'reward-site', + url: 'https://reward-site.example.com', + platform: 'new-api', + }).returning().get(); + const account = db.insert(schema.accounts).values({ + siteId: site.id, + username: 'reward-user', + accessToken: 'token', + status: 'active', + extraConfig: JSON.stringify({ + todayIncomeSnapshot: { + day: today, + baseline: 10, + latest: 14, + updatedAt: `${today}T10:00:00.000Z`, + }, + }), + }).returning().get(); + + db.insert(schema.checkinLogs).values({ + accountId: account.id, + status: 'success', + message: 'checkin success', + reward: '1.2', + createdAt: `${today} 10:01:00`, + }).run(); + + const response = await app.inject({ + method: 'GET', + url: '/api/accounts', + }); + + expect(response.statusCode).toBe(200); + const rows = response.json() as Array<{ id: number; todayReward: number }>; + const target = rows.find((row) => row.id === account.id); + expect(target?.todayReward).toBe(1.2); + }); + + it('counts today spend only inside local-day range', async () => { + const site = db.insert(schema.sites).values({ + name: 'spend-site', + url: 'https://spend-site.example.com', + platform: 'new-api', + }).returning().get(); + const account = db.insert(schema.accounts).values({ + siteId: site.id, + username: 'spend-user', + accessToken: 'token', + status: 'active', + }).returning().get(); + + const { startUtc, endUtc } = getLocalDayRangeUtc(); + const startDate = parseStoredUtcDateTime(startUtc)!; + const endDate = parseStoredUtcDateTime(endUtc)!; + const beforeStart = formatUtcSqlDateTime(new Date(startDate.getTime() - 60_000)); + const inRange = formatUtcSqlDateTime(new Date(startDate.getTime() + 60_000)); + const afterEnd = formatUtcSqlDateTime(new Date(endDate.getTime() + 60_000)); + + db.insert(schema.proxyLogs).values([ + { + accountId: account.id, + status: 'success', + estimatedCost: 1, + createdAt: beforeStart, + }, + { + accountId: account.id, + status: 'success', + estimatedCost: 2, + createdAt: inRange, + }, + { + accountId: account.id, + status: 'success', + estimatedCost: 4, + createdAt: afterEnd, + }, + ]).run(); + + const response = await app.inject({ + method: 'GET', + url: '/api/accounts', + }); + + expect(response.statusCode).toBe(200); + const rows = response.json() as Array<{ id: number; todaySpend: number }>; + const target = rows.find((row) => row.id === account.id); + expect(target?.todaySpend).toBe(2); + }); +}); diff --git a/src/server/routes/api/accounts.ts b/src/server/routes/api/accounts.ts new file mode 100644 index 0000000..d8cd4d9 --- /dev/null +++ b/src/server/routes/api/accounts.ts @@ -0,0 +1,575 @@ +import { FastifyInstance } from 'fastify'; +import { db, schema } from '../../db/index.js'; +import { and, eq, gte, lt } from 'drizzle-orm'; +import { refreshBalance } from '../../services/balanceService.js'; +import { getAdapter } from '../../services/platforms/index.js'; +import { refreshModelsForAccount, rebuildTokenRoutesFromAvailability } from '../../services/modelService.js'; +import { ensureDefaultTokenForAccount, syncTokensFromUpstream } from '../../services/accountTokenService.js'; +import { guessPlatformUserIdFromUsername, mergeAccountExtraConfig } from '../../services/accountExtraConfig.js'; +import { encryptAccountPassword } from '../../services/accountCredentialService.js'; +import { startBackgroundTask } from '../../services/backgroundTaskService.js'; +import { parseCheckinRewardAmount } from '../../services/checkinRewardParser.js'; +import { estimateRewardWithTodayIncomeFallback } from '../../services/todayIncomeRewardService.js'; +import { getLocalDayRangeUtc } from '../../services/localTimeService.js'; +import { + buildRuntimeHealthForAccount, + setAccountRuntimeHealth, + type RuntimeHealthState, +} from '../../services/accountHealthService.js'; + +type AccountWithSiteRow = { + accounts: typeof schema.accounts.$inferSelect; + sites: typeof schema.sites.$inferSelect; +}; + +type AccountHealthRefreshResult = { + accountId: number; + username: string | null; + siteName: string; + status: 'success' | 'failed' | 'skipped'; + state: RuntimeHealthState; + message: string; +}; + +function summarizeAccountHealthRefresh(results: AccountHealthRefreshResult[]) { + return { + total: results.length, + healthy: results.filter((item) => item.state === 'healthy').length, + unhealthy: results.filter((item) => item.state === 'unhealthy').length, + degraded: results.filter((item) => item.state === 'degraded').length, + disabled: results.filter((item) => item.state === 'disabled').length, + unknown: results.filter((item) => item.state === 'unknown').length, + success: results.filter((item) => item.status === 'success').length, + failed: results.filter((item) => item.status === 'failed').length, + skipped: results.filter((item) => item.status === 'skipped').length, + }; +} + +async function refreshRuntimeHealthForRow(row: AccountWithSiteRow): Promise { + const accountId = row.accounts.id; + const username = row.accounts.username; + const siteName = row.sites.name; + + if ((row.accounts.status || 'active') === 'disabled' || (row.sites.status || 'active') === 'disabled') { + setAccountRuntimeHealth(accountId, { + state: 'disabled', + reason: '账号或站点已禁用', + source: 'health-refresh', + }); + return { + accountId, + username, + siteName, + status: 'skipped', + state: 'disabled', + message: '账号或站点已禁用', + }; + } + + try { + await refreshBalance(accountId); + const refreshedAccount = db.select().from(schema.accounts) + .where(eq(schema.accounts.id, accountId)) + .get(); + const runtimeHealth = buildRuntimeHealthForAccount({ + accountStatus: refreshedAccount?.status || row.accounts.status, + siteStatus: row.sites.status, + extraConfig: refreshedAccount?.extraConfig ?? row.accounts.extraConfig, + }); + + return { + accountId, + username, + siteName, + status: runtimeHealth.state === 'unhealthy' ? 'failed' : 'success', + state: runtimeHealth.state, + message: runtimeHealth.reason, + }; + } catch (error: any) { + const message = String(error?.message || '健康检查失败'); + setAccountRuntimeHealth(accountId, { + state: 'unhealthy', + reason: message, + source: 'health-refresh', + }); + return { + accountId, + username, + siteName, + status: 'failed', + state: 'unhealthy', + message, + }; + } +} + +async function executeRefreshAccountRuntimeHealth(accountId?: number) { + const rows = db.select().from(schema.accounts) + .innerJoin(schema.sites, eq(schema.accounts.siteId, schema.sites.id)) + .all(); + + const targetRows = Number.isFinite(accountId as number) + ? rows.filter((row) => row.accounts.id === accountId) + : rows; + + const results: AccountHealthRefreshResult[] = []; + for (const row of targetRows) { + results.push(await refreshRuntimeHealthForRow(row)); + } + + return { + summary: summarizeAccountHealthRefresh(results), + results, + }; +} + +export async function accountsRoutes(app: FastifyInstance) { + // List all accounts (with site info) + app.get('/api/accounts', async () => { + const rows = db.select().from(schema.accounts) + .innerJoin(schema.sites, eq(schema.accounts.siteId, schema.sites.id)).all(); + + const { localDay, startUtc, endUtc } = getLocalDayRangeUtc(); + + // Aggregate today's spend per account from proxy logs + const todayLogs = db.select().from(schema.proxyLogs) + .where(and(gte(schema.proxyLogs.createdAt, startUtc), lt(schema.proxyLogs.createdAt, endUtc))) + .all(); + const spendByAccount: Record = {}; + for (const log of todayLogs) { + if (log.accountId == null) continue; + const cost = typeof log.estimatedCost === 'number' ? log.estimatedCost : 0; + spendByAccount[log.accountId] = (spendByAccount[log.accountId] || 0) + cost; + } + + // Aggregate today's checkin rewards per account + const todayCheckins = db.select().from(schema.checkinLogs) + .where(and( + gte(schema.checkinLogs.createdAt, startUtc), + lt(schema.checkinLogs.createdAt, endUtc), + eq(schema.checkinLogs.status, 'success'), + )) + .all(); + const rewardByAccount: Record = {}; + const successCountByAccount: Record = {}; + const parsedRewardCountByAccount: Record = {}; + for (const log of todayCheckins) { + successCountByAccount[log.accountId] = (successCountByAccount[log.accountId] || 0) + 1; + const rewardNum = parseCheckinRewardAmount(log.reward) || parseCheckinRewardAmount(log.message); + if (rewardNum <= 0) continue; + rewardByAccount[log.accountId] = (rewardByAccount[log.accountId] || 0) + rewardNum; + parsedRewardCountByAccount[log.accountId] = (parsedRewardCountByAccount[log.accountId] || 0) + 1; + } + + return rows.map((r) => ({ + ...r.accounts, + site: r.sites, + todaySpend: Math.round((spendByAccount[r.accounts.id] || 0) * 1_000_000) / 1_000_000, + todayReward: Math.round(estimateRewardWithTodayIncomeFallback({ + day: localDay, + successCount: successCountByAccount[r.accounts.id] || 0, + parsedRewardCount: parsedRewardCountByAccount[r.accounts.id] || 0, + rewardSum: rewardByAccount[r.accounts.id] || 0, + extraConfig: r.accounts.extraConfig, + }) * 1_000_000) / 1_000_000, + runtimeHealth: buildRuntimeHealthForAccount({ + accountStatus: r.accounts.status, + siteStatus: r.sites.status, + extraConfig: r.accounts.extraConfig, + }), + })); + }); + + // Login to a site and auto-create account + app.post<{ Body: { siteId: number; username: string; password: string } }>('/api/accounts/login', async (request) => { + const { siteId, username, password } = request.body; + + // Get site info + const site = db.select().from(schema.sites).where(eq(schema.sites.id, siteId)).get(); + if (!site) return { success: false, message: 'site not found' }; + + // Get platform adapter + const adapter = getAdapter(site.platform); + if (!adapter) return { success: false, message: `婵炴垶鎸哥粔鐢稿极椤曗偓楠炴劖鎷呴悜妯兼殸濡ょ姷鍋涢崯鑳亹? ${site.platform}` }; + + // Login to the target site + const loginResult = await adapter.login(site.url, username, password); + if (!loginResult.success || !loginResult.accessToken) { + return { success: false, message: loginResult.message || 'login failed' }; + } + + const guessedPlatformUserId = guessPlatformUserIdFromUsername(username); + + // Auto-fetch API token(s) + let apiToken: string | null = null; + let apiTokens: Array<{ name?: string | null; key?: string | null; enabled?: boolean | null }> = []; + try { + apiToken = await adapter.getApiToken(site.url, loginResult.accessToken, guessedPlatformUserId); + } catch { } + try { + apiTokens = await adapter.getApiTokens(site.url, loginResult.accessToken, guessedPlatformUserId); + } catch { } + + const preferredApiToken = apiTokens.find((token) => token.enabled !== false && token.key)?.key || apiToken || null; + const existing = db.select().from(schema.accounts) + .where(and(eq(schema.accounts.siteId, siteId), eq(schema.accounts.username, username))) + .get(); + + const extraConfigPatch: Record = { + autoRelogin: { + username, + passwordCipher: encryptAccountPassword(password), + updatedAt: new Date().toISOString(), + }, + }; + if (guessedPlatformUserId) { + extraConfigPatch.platformUserId = guessedPlatformUserId; + } + const extraConfig = mergeAccountExtraConfig(existing?.extraConfig, extraConfigPatch); + + // Create or update account + let accountId = existing?.id; + if (existing) { + db.update(schema.accounts).set({ + accessToken: loginResult.accessToken, + apiToken: preferredApiToken || undefined, + checkinEnabled: true, + status: 'active', + extraConfig, + updatedAt: new Date().toISOString(), + }).where(eq(schema.accounts.id, existing.id)).run(); + } else { + const created = db.insert(schema.accounts).values({ + siteId, + username, + accessToken: loginResult.accessToken, + apiToken: preferredApiToken || undefined, + checkinEnabled: true, + extraConfig, + }).returning().get(); + accountId = created.id; + } + + const result = db.select().from(schema.accounts).where(eq(schema.accounts.id, accountId!)).get(); + if (!result) { + return { success: false, message: 'account create failed' }; + } + + if (apiTokens.length > 0) { + try { + syncTokensFromUpstream(result.id, apiTokens); + } catch { } + } else if (preferredApiToken) { + try { + ensureDefaultTokenForAccount(result.id, preferredApiToken, { name: 'default', source: 'sync' }); + } catch { } + } + + // Auto-refresh balance + try { await refreshBalance(result.id); } catch { } + try { + await refreshModelsForAccount(result.id); + rebuildTokenRoutesFromAvailability(); + } catch { } + + const account = db.select().from(schema.accounts).where(eq(schema.accounts.id, result.id)).get(); + return { + success: true, + account, + apiTokenFound: !!preferredApiToken, + tokenCount: apiTokens.length, + reusedAccount: !!existing, + }; + }); + + // Verify a token against a site - auto-detects token type (session vs API key) + app.post<{ Body: { siteId: number; accessToken: string; platformUserId?: number } }>('/api/accounts/verify-token', async (request) => { + const { siteId, accessToken, platformUserId } = request.body; + const site = db.select().from(schema.sites).where(eq(schema.sites.id, siteId)).get(); + if (!site) return { success: false, message: 'site not found' }; + + const adapter = getAdapter(site.platform); + if (!adapter) return { success: false, message: `婵炴垶鎸哥粔鐢稿极椤曗偓楠炴劖鎷呴悜妯兼殸濡ょ姷鍋涢崯鑳亹? ${site.platform}` }; + + const result = await adapter.verifyToken(site.url, accessToken, platformUserId); + + if (result.tokenType === 'session') { + return { + success: true, + tokenType: 'session', + userInfo: result.userInfo, + balance: result.balance, + apiToken: result.apiToken, + }; + } + + if (result.tokenType === 'apikey') { + return { + success: true, + tokenType: 'apikey', + modelCount: result.models?.length || 0, + models: result.models?.slice(0, 10), + }; + } + + // Try to explain unknown failures: missing user id vs anti-bot challenge page. + type VerifyFailureReason = 'needs-user-id' | 'shield-blocked' | null; + const detectVerifyFailureReason = async (): Promise => { + const parseFailureReason = (bodyText: string, contentType: string): VerifyFailureReason => { + const text = bodyText || ''; + const ct = (contentType || '').toLowerCase(); + if (ct.includes('text/html') && /var\s+arg1\s*=|acw_sc__v2|cdn_sec_tc| \ No newline at end of file diff --git a/src/server/services/platforms/anyrouter.ts b/src/server/services/platforms/anyrouter.ts new file mode 100644 index 0000000..0d4f2d9 --- /dev/null +++ b/src/server/services/platforms/anyrouter.ts @@ -0,0 +1,10 @@ +import { NewApiAdapter } from './newApi.js'; + +export class AnyRouterAdapter extends NewApiAdapter { + readonly platformName = 'anyrouter'; + + async detect(url: string): Promise { + const normalized = (url || '').toLowerCase(); + return normalized.includes('anyrouter'); + } +} diff --git a/src/server/services/platforms/base.ts b/src/server/services/platforms/base.ts new file mode 100644 index 0000000..3641475 --- /dev/null +++ b/src/server/services/platforms/base.ts @@ -0,0 +1,192 @@ +import type { RequestInit as UndiciRequestInit } from 'undici'; + +export interface CheckinResult { + success: boolean; + message: string; + reward?: string; +} + +export interface BalanceInfo { + balance: number; + used: number; + quota: number; + todayIncome?: number; + todayQuotaConsumption?: number; +} + +interface LoginResult { + success: boolean; + accessToken?: string; + username?: string; + message?: string; +} + +export interface UserInfo { + username: string; + displayName?: string; + email?: string; + role?: number; +} + +export interface TokenVerifyResult { + tokenType: 'session' | 'apikey' | 'unknown'; + userInfo?: UserInfo | null; + balance?: BalanceInfo | null; + apiToken?: string | null; + models?: string[]; +} + +export interface ApiTokenInfo { + name: string; + key: string; + enabled?: boolean; +} + +export interface CreateApiTokenOptions { + name?: string; + group?: string; + unlimitedQuota?: boolean; + remainQuota?: number; + expiredTime?: number; + allowIps?: string; + modelLimitsEnabled?: boolean; + modelLimits?: string; +} + +export interface PlatformAdapter { + readonly platformName: string; + detect(url: string): Promise; + login(baseUrl: string, username: string, password: string): Promise; + getUserInfo(baseUrl: string, accessToken: string, platformUserId?: number): Promise; + verifyToken(baseUrl: string, token: string, platformUserId?: number): Promise; + checkin(baseUrl: string, accessToken: string, platformUserId?: number): Promise; + getBalance(baseUrl: string, accessToken: string, platformUserId?: number): Promise; + getModels(baseUrl: string, token: string, platformUserId?: number): Promise; + getApiToken(baseUrl: string, accessToken: string, platformUserId?: number): Promise; + getApiTokens(baseUrl: string, accessToken: string, platformUserId?: number): Promise; + getUserGroups(baseUrl: string, accessToken: string, platformUserId?: number): Promise; + createApiToken(baseUrl: string, accessToken: string, platformUserId?: number, options?: CreateApiTokenOptions): Promise; + deleteApiToken(baseUrl: string, accessToken: string, tokenKey: string, platformUserId?: number): Promise; +} + +export abstract class BasePlatformAdapter implements PlatformAdapter { + abstract readonly platformName: string; + + abstract detect(url: string): Promise; + abstract checkin(baseUrl: string, accessToken: string): Promise; + abstract getBalance(baseUrl: string, accessToken: string): Promise; + abstract getModels(baseUrl: string, token: string, platformUserId?: number): Promise; + + async verifyToken(baseUrl: string, token: string, _platformUserId?: number): Promise { + // 1. Try as session/access token first (for management APIs) + const userInfo = await this.getUserInfo(baseUrl, token); + if (userInfo) { + let balance: BalanceInfo | null = null; + try { balance = await this.getBalance(baseUrl, token); } catch {} + let apiToken: string | null = null; + try { apiToken = await this.getApiToken(baseUrl, token); } catch {} + return { tokenType: 'session', userInfo, balance, apiToken }; + } + + // 2. Try as API key (for /v1/models) + try { + const models = await this.getModels(baseUrl, token); + if (models && models.length > 0) { + return { tokenType: 'apikey', models }; + } + } catch {} + + return { tokenType: 'unknown' }; + } + + async getUserInfo(baseUrl: string, accessToken: string): Promise { + try { + const res = await this.fetchJson(`${baseUrl}/api/user/self`, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + if (res?.success && res?.data) { + return { + username: res.data.username || res.data.display_name || '', + displayName: res.data.display_name, + email: res.data.email, + role: res.data.role, + }; + } + return null; + } catch { + return null; + } + } + + async login(baseUrl: string, username: string, password: string): Promise { + try { + const res = await this.fetchJson(`${baseUrl}/api/user/login`, { + method: 'POST', + body: JSON.stringify({ username, password }), + }); + if (res?.success && res?.data) { + return { + success: true, + accessToken: typeof res.data === 'string' ? res.data : res.data.token || res.data.access_token, + username, + }; + } + return { success: false, message: res?.message || '登录失败' }; + } catch (err: any) { + return { success: false, message: err.message || '登录请求失败' }; + } + } + + async getApiToken(_baseUrl: string, _accessToken: string, _platformUserId?: number): Promise { + return null; + } + + async getApiTokens(baseUrl: string, accessToken: string, platformUserId?: number): Promise { + const token = await this.getApiToken(baseUrl, accessToken, platformUserId); + if (!token) return []; + return [{ name: 'default', key: token, enabled: true }]; + } + + async createApiToken( + _baseUrl: string, + _accessToken: string, + _platformUserId?: number, + _options?: CreateApiTokenOptions, + ): Promise { + return false; + } + + async getUserGroups( + _baseUrl: string, + _accessToken: string, + _platformUserId?: number, + ): Promise { + return ['default']; + } + + async deleteApiToken( + _baseUrl: string, + _accessToken: string, + _tokenKey: string, + _platformUserId?: number, + ): Promise { + return false; + } + + protected async fetchJson(url: string, options?: UndiciRequestInit): Promise { + const { fetch } = await import('undici'); + const requestOptions: UndiciRequestInit = { + ...options, + body: options?.body ?? undefined, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + }; + const res = await fetch(url, requestOptions); + if (!res.ok) { + throw new Error(`HTTP ${res.status}: ${await res.text()}`); + } + return res.json() as Promise; + } +} diff --git a/src/server/services/platforms/doneHub.test.ts b/src/server/services/platforms/doneHub.test.ts new file mode 100644 index 0000000..419f4c8 --- /dev/null +++ b/src/server/services/platforms/doneHub.test.ts @@ -0,0 +1,76 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'; +import { AddressInfo } from 'node:net'; +import { DoneHubAdapter } from './doneHub.js'; + +describe('DoneHubAdapter', () => { + let server: ReturnType; + let baseUrl: string; + + beforeEach(async () => { + server = createServer((req: IncomingMessage, res: ServerResponse) => { + if (req.url === '/v1/models') { + res.writeHead(401, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: { message: 'unauthorized' } })); + return; + } + + if (req.url === '/api/available_model') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + 'gpt-4o': { price: { input: 0.5, output: 1.5 } }, + 'deepseek-chat': { price: { input: 0.1, output: 0.2 } }, + })); + return; + } + + if (req.url?.startsWith('/api/token/')) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + data: { + data: [ + { key: 'donehub-token-key', name: 'sys_playground', status: 1 }, + ], + }, + })); + return; + } + + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'not found' })); + }); + + await new Promise((resolve) => { + server.listen(0, '127.0.0.1', () => resolve()); + }); + const addr = server.address() as AddressInfo; + baseUrl = `http://127.0.0.1:${addr.port}`; + }); + + afterEach(async () => { + await new Promise((resolve, reject) => { + server.close((err?: Error) => (err ? reject(err) : resolve())); + }); + }); + + it('marks checkin as unsupported', async () => { + const adapter = new DoneHubAdapter(); + const result = await adapter.checkin(baseUrl, 'token'); + expect(result.success).toBe(false); + expect(result.message).toBe('checkin endpoint not found'); + }); + + it('falls back to /api/available_model when /v1/models is unavailable', async () => { + const adapter = new DoneHubAdapter(); + const models = await adapter.getModels(baseUrl, 'token'); + expect(models).toEqual(['gpt-4o', 'deepseek-chat']); + }); + + it('parses token list from nested data.data shape', async () => { + const adapter = new DoneHubAdapter(); + const tokens = await adapter.getApiTokens(baseUrl, 'token'); + expect(tokens).toEqual([ + { key: 'donehub-token-key', name: 'sys_playground', enabled: true }, + ]); + }); +}); diff --git a/src/server/services/platforms/doneHub.ts b/src/server/services/platforms/doneHub.ts new file mode 100644 index 0000000..eac84b0 --- /dev/null +++ b/src/server/services/platforms/doneHub.ts @@ -0,0 +1,39 @@ +import { OneApiAdapter } from './oneApi.js'; +import type { CheckinResult } from './base.js'; + +export class DoneHubAdapter extends OneApiAdapter { + readonly platformName = 'done-hub'; + + async detect(url: string): Promise { + const normalized = url.toLowerCase(); + return normalized.includes('donehub') || normalized.includes('done-hub'); + } + + // DoneHub deployments generally do not expose /api/user/checkin. + // Mark as unsupported so higher-level logic records it as skipped instead of failed. + override async checkin(_baseUrl: string, _accessToken: string): Promise { + return { success: false, message: 'checkin endpoint not found' }; + } + + override async getModels(baseUrl: string, apiToken: string, _platformUserId?: number): Promise { + let openAiModels: string[] = []; + try { + openAiModels = await super.getModels(baseUrl, apiToken, _platformUserId); + } catch {} + if (openAiModels.length > 0) return openAiModels; + + try { + const res = await this.fetchJson(`${baseUrl}/api/available_model`, { + headers: { Authorization: `Bearer ${apiToken}` }, + }); + + const payload = (res?.data && typeof res.data === 'object') ? res.data : res; + if (payload && typeof payload === 'object' && !Array.isArray(payload)) { + const models = Object.keys(payload).filter(Boolean); + if (models.length > 0) return models; + } + } catch {} + + return []; + } +} diff --git a/src/server/services/platforms/index.test.ts b/src/server/services/platforms/index.test.ts new file mode 100644 index 0000000..ba9b22a --- /dev/null +++ b/src/server/services/platforms/index.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest'; +import { detectPlatform, getAdapter } from './index.js'; + +describe('getAdapter platform aliases', () => { + it('returns dedicated anyrouter adapter for anyrouter alias', () => { + const adapter = getAdapter('anyrouter'); + expect(adapter?.platformName).toBe('anyrouter'); + }); + + it('handles case-insensitive platform strings', () => { + const adapter = getAdapter('Veloera'); + expect(adapter?.platformName).toBe('veloera'); + }); + + it('returns undefined for unknown platforms', () => { + expect(getAdapter('unknown-platform')).toBeUndefined(); + }); + + it('detects anyrouter URL before generic new-api adapter', async () => { + const adapter = await detectPlatform('https://anyrouter.top'); + expect(adapter?.platformName).toBe('anyrouter'); + }); + + it('detects done-hub URL before generic adapters', async () => { + const adapter = await detectPlatform('https://demo.donehub.example'); + expect(adapter?.platformName).toBe('done-hub'); + }); +}); diff --git a/src/server/services/platforms/index.ts b/src/server/services/platforms/index.ts new file mode 100644 index 0000000..9499e30 --- /dev/null +++ b/src/server/services/platforms/index.ts @@ -0,0 +1,58 @@ +import type { PlatformAdapter } from './base.js'; +import { AnyRouterAdapter } from './anyrouter.js'; +import { NewApiAdapter } from './newApi.js'; +import { OneApiAdapter } from './oneApi.js'; +import { VeloeraAdapter } from './veloera.js'; +import { OneHubAdapter } from './oneHub.js'; +import { DoneHubAdapter } from './doneHub.js'; +import { Sub2ApiAdapter } from './sub2api.js'; + +const adapters: PlatformAdapter[] = [ + // Specific forks before generic adapters for better auto-detection. + new AnyRouterAdapter(), + new DoneHubAdapter(), + new OneHubAdapter(), + new NewApiAdapter(), + new Sub2ApiAdapter(), + new OneApiAdapter(), + new VeloeraAdapter(), +]; + +const platformAliases: Record = { + // NewAPI family aliases + anyrouter: 'anyrouter', + 'wong-gongyi': 'new-api', + 'vo-api': 'new-api', + 'super-api': 'new-api', + 'rix-api': 'new-api', + 'neo-api': 'new-api', + newapi: 'new-api', + 'new api': 'new-api', + // OneAPI family aliases + oneapi: 'one-api', + 'one api': 'one-api', + // Keep canonical forms explicit for clarity + 'new-api': 'new-api', + 'one-api': 'one-api', + veloera: 'veloera', + 'one-hub': 'one-hub', + 'done-hub': 'done-hub', + sub2api: 'sub2api', +}; + +function normalizePlatform(platform: string): string { + const raw = (platform || '').trim().toLowerCase(); + return platformAliases[raw] ?? raw; +} + +export function getAdapter(platform: string): PlatformAdapter | undefined { + const normalized = normalizePlatform(platform); + return adapters.find((a) => a.platformName === normalized); +} + +export async function detectPlatform(url: string): Promise { + for (const adapter of adapters) { + if (await adapter.detect(url)) return adapter; + } + return undefined; +} diff --git a/src/server/services/platforms/newApi.test.ts b/src/server/services/platforms/newApi.test.ts new file mode 100644 index 0000000..822fa2d --- /dev/null +++ b/src/server/services/platforms/newApi.test.ts @@ -0,0 +1,414 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'; +import { AddressInfo } from 'node:net'; +import { readFileSync } from 'node:fs'; +import { NewApiAdapter } from './newApi.js'; + +interface RequestSnapshot { + method: string; + url: string; + headers: IncomingMessage['headers']; +} + +const COOKIE_SESSION_TOKEN = 'cookie-session-token'; +const COOKIE_REQUIRES_USER_TOKEN = 'cookie-requires-user'; +const CHECKIN_ALREADY_TOKEN = 'checkin-already-token'; +const CHECKIN_INVALID_URL_TOKEN = 'checkin-invalid-url-token'; +const CHECKIN_CLOUDFLARE_530_TOKEN = 'checkin-cloudflare-530-token'; +const BALANCE_FAIL_TOKEN = 'balance-fail-token'; +const COOKIE_SHIELDED_TOKEN = Buffer.from( + `1771864970|${Buffer.from('username=linuxdo_131936').toString('base64')}|sig`, +).toString('base64'); +const ANYROUTER_CHALLENGE_HTML = readFileSync( + new URL('./__fixtures__/anyrouter-challenge.html', import.meta.url), + 'utf8', +); +const ANYROUTER_CHALLENGE_ACW = '699dbedad126579b6bc0ebb91eaae8d7af3548b5'; +const CLOUDFLARE_530_HTML = ` + + + + Cloudflare Tunnel error | newapi.tanmw.top | Cloudflare + + +

Error1033

+

Cloudflare Tunnel error

+ + +`; + +describe('NewApiAdapter', () => { + let server: ReturnType; + let baseUrl: string; + let requests: RequestSnapshot[] = []; + + beforeEach(async () => { + requests = []; + server = createServer((req: IncomingMessage, res: ServerResponse) => { + requests.push({ + method: req.method || 'GET', + url: req.url || '/', + headers: req.headers, + }); + + if (req.url === '/v1/models') { + res.writeHead(401, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: { message: 'invalid token' } })); + return; + } + + if (req.url === '/api/user/models') { + if (req.headers['new-api-user'] !== '11494') { + res.writeHead(401, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'missing New-Api-User' })); + return; + } + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ data: ['gpt-4o', 'gpt-4.1'] })); + return; + } + + if (req.url?.startsWith('/api/token/')) { + if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${COOKIE_SHIELDED_TOKEN}`) { + res.writeHead(401, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'unauthorized' })); + return; + } + + if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${COOKIE_SHIELDED_TOKEN}`)) { + if (!req.headers.cookie.includes(`acw_sc__v2=${ANYROUTER_CHALLENGE_ACW}`)) { + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(ANYROUTER_CHALLENGE_HTML); + return; + } + if (req.headers['new-api-user'] !== '131936') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'missing New-Api-User' })); + return; + } + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + data: { + items: [{ key: 'shielded-cookie-key' }], + }, + })); + return; + } + + if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${COOKIE_SESSION_TOKEN}`) { + res.writeHead(401, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'unauthorized' })); + return; + } + if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${COOKIE_REQUIRES_USER_TOKEN}`) { + res.writeHead(401, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'unauthorized' })); + return; + } + + if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${COOKIE_SESSION_TOKEN}`)) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + data: { + items: [{ key: 'cookie-api-key' }], + }, + })); + return; + } + + if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${COOKIE_REQUIRES_USER_TOKEN}`)) { + if (req.headers['new-api-user'] !== '8899') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'missing New-Api-User' })); + return; + } + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + data: { + items: [{ key: 'cookie-user-key' }], + }, + })); + return; + } + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + data: { + items: [{ key: 'api-key-from-token-list' }], + }, + })); + return; + } + + if (req.url === '/api/user/self') { + if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${BALANCE_FAIL_TOKEN}`) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: '閺冪姵娼堟潻娑滎攽濮濄倖鎼锋担婊愮礉閺堫亞娅ヨぐ鏇氱瑬閺堫亝褰佹笟?access token' })); + return; + } + + if ( + typeof req.headers.cookie === 'string' && + ( + req.headers.cookie.includes(`session=${BALANCE_FAIL_TOKEN}`) || + req.headers.cookie.includes(`token=${BALANCE_FAIL_TOKEN}`) + ) + ) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: '閺冪姵娼堟潻娑滎攽濮濄倖鎼锋担婊愮礉閺堫亞娅ヨぐ鏇氱瑬閺堫亝褰佹笟?access token' })); + return; + } + + if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${COOKIE_SHIELDED_TOKEN}`)) { + if (!req.headers.cookie.includes(`acw_sc__v2=${ANYROUTER_CHALLENGE_ACW}`)) { + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(ANYROUTER_CHALLENGE_HTML); + return; + } + if (req.headers['new-api-user'] !== '131936') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'missing New-Api-User' })); + return; + } + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + success: true, + data: { id: 131936, username: 'linuxdo_131936', quota: 3000000, used_quota: 1200000 }, + })); + return; + } + + if (typeof req.headers.authorization === 'string' && req.headers.authorization === 'Bearer session-token') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + success: true, + data: { id: 11494, username: 'demo-user', quota: 1000000, used_quota: 1000 }, + })); + return; + } + + if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${COOKIE_SESSION_TOKEN}`) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'invalid token' })); + return; + } + if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${COOKIE_REQUIRES_USER_TOKEN}`) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'invalid token' })); + return; + } + if (typeof req.headers.authorization === 'string') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'invalid token' })); + return; + } + + if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${COOKIE_SESSION_TOKEN}`)) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + success: true, + data: { id: 7788, username: 'cookie-user', quota: 2000000, used_quota: 500000 }, + })); + return; + } + + if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${COOKIE_REQUIRES_USER_TOKEN}`)) { + if (req.headers['new-api-user'] !== '8899') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'missing New-Api-User' })); + return; + } + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + success: true, + data: { id: 8899, username: 'cookie-user-id-required', quota: 1500000, used_quota: 100000 }, + })); + return; + } + + res.writeHead(401, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'unauthorized' })); + return; + } + + if (req.url === '/api/user/checkin') { + if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${CHECKIN_CLOUDFLARE_530_TOKEN}`) { + res.writeHead(530, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(CLOUDFLARE_530_HTML); + return; + } + if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${CHECKIN_INVALID_URL_TOKEN}`) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: { message: 'Invalid URL (POST /api/user/checkin)' } })); + return; + } + if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_INVALID_URL_TOKEN}`)) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: { message: 'Invalid URL (POST /api/user/checkin)' } })); + return; + } + if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${CHECKIN_ALREADY_TOKEN}`) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: '浠婂ぉ宸茬粡绛惧埌杩囧暒' })); + return; + } + if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_ALREADY_TOKEN}`)) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: '鏃犳潈杩涜姝ゆ搷浣滐紝鏈櫥褰曚笖鏈彁渚?access token' })); + return; + } + if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${COOKIE_SHIELDED_TOKEN}`) { + res.writeHead(401, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'unauthorized' })); + return; + } + if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${COOKIE_SHIELDED_TOKEN}`)) { + if (!req.headers.cookie.includes(`acw_sc__v2=${ANYROUTER_CHALLENGE_ACW}`)) { + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(ANYROUTER_CHALLENGE_HTML); + return; + } + if (req.headers['new-api-user'] !== '131936') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: 'missing New-Api-User' })); + return; + } + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: true, message: 'checked-in-ok' })); + return; + } + } + + if (req.url === '/api/user/sign_in') { + if (typeof req.headers.cookie === 'string' && req.headers.cookie.includes(`session=${CHECKIN_ALREADY_TOKEN}`)) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: false, message: '鏃犳潈杩涜姝ゆ搷浣滐紝鏈櫥褰曚笖鏈彁渚?access token' })); + return; + } + } + + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'not found' })); + }); + + await new Promise((resolve) => { + server.listen(0, '127.0.0.1', () => resolve()); + }); + const addr = server.address() as AddressInfo; + baseUrl = `http://127.0.0.1:${addr.port}`; + }); + + afterEach(async () => { + await new Promise((resolve, reject) => { + server.close((err?: Error) => (err ? reject(err) : resolve())); + }); + }); + + it('falls back to session model endpoint when /v1/models rejects token', async () => { + const adapter = new NewApiAdapter(); + const models = await adapter.getModels(baseUrl, 'session-token', 11494); + + expect(models).toEqual(['gpt-4o', 'gpt-4.1']); + expect(requests.some((r) => r.url === '/v1/models')).toBe(true); + expect( + requests.some((r) => r.url === '/api/user/models' && r.headers['new-api-user'] === '11494'), + ).toBe(true); + }); + + it('parses token list response with data.items[] shape', async () => { + const adapter = new NewApiAdapter(); + const token = await adapter.getApiToken(baseUrl, 'session-token', 11494); + + expect(token).toBe('api-key-from-token-list'); + }); + + it('detects cookie session values as session tokens for anyrouter-like deployments', async () => { + const adapter = new NewApiAdapter(); + const result = await adapter.verifyToken(baseUrl, COOKIE_SESSION_TOKEN); + + expect(result.tokenType).toBe('session'); + expect(result.userInfo?.username).toBe('cookie-user'); + expect(result.apiToken).toBe('cookie-api-key'); + expect( + requests.some((r) => r.url === '/api/user/self' && typeof r.headers.cookie === 'string' && r.headers.cookie.includes(`session=${COOKIE_SESSION_TOKEN}`)), + ).toBe(true); + }); + + it('auto-probes New-Api-User for cookie sessions when header is required', async () => { + const adapter = new NewApiAdapter(); + const result = await adapter.verifyToken(baseUrl, COOKIE_REQUIRES_USER_TOKEN); + + expect(result.tokenType).toBe('session'); + expect(result.userInfo?.username).toBe('cookie-user-id-required'); + expect(result.apiToken).toBe('cookie-user-key'); + expect( + requests.some((r) => r.url === '/api/user/self' && r.headers['new-api-user'] === '8899'), + ).toBe(true); + }); + + it('solves anyrouter acw challenge and probes user id from session payload', async () => { + const adapter = new NewApiAdapter(); + const result = await adapter.verifyToken(baseUrl, COOKIE_SHIELDED_TOKEN); + + expect(result.tokenType).toBe('session'); + expect(result.userInfo?.username).toBe('linuxdo_131936'); + expect(typeof result.apiToken === 'string' && result.apiToken.length > 0).toBe(true); + expect( + requests.some( + (r) => + r.url === '/api/user/self' && + typeof r.headers.cookie === 'string' && + r.headers.cookie.includes(`acw_sc__v2=${ANYROUTER_CHALLENGE_ACW}`), + ), + ).toBe(true); + expect( + requests.some((r) => r.url === '/api/user/self' && r.headers['new-api-user'] === '131936'), + ).toBe(true); + }); + + it('uses shielded cookie flow for balance and checkin', async () => { + const adapter = new NewApiAdapter(); + const balance = await adapter.getBalance(baseUrl, COOKIE_SHIELDED_TOKEN); + const checkin = await adapter.checkin(baseUrl, COOKIE_SHIELDED_TOKEN); + + expect(balance).toEqual({ + quota: 8.4, + used: 2.4, + balance: 6, + }); + expect(checkin.success).toBe(true); + expect( + requests.some((r) => r.url === '/api/user/checkin' && r.headers['new-api-user'] === '131936'), + ).toBe(true); + }); + + it('preserves upstream balance failure message for UI feedback', async () => { + const adapter = new NewApiAdapter(); + + await expect(adapter.getBalance(baseUrl, BALANCE_FAIL_TOKEN)).rejects.toThrow('access token'); + }); + + it('preserves nested checkin error message instead of generic fallback', async () => { + const adapter = new NewApiAdapter(); + const result = await adapter.checkin(baseUrl, CHECKIN_INVALID_URL_TOKEN, 11494); + + expect(result.success).toBe(false); + expect(result.message).toContain('Invalid URL'); + }); + + it('summarizes cloudflare tunnel HTML failures to concise checkin error', async () => { + const adapter = new NewApiAdapter(); + const result = await adapter.checkin(baseUrl, CHECKIN_CLOUDFLARE_530_TOKEN, 11494); + + expect(result.success).toBe(false); + expect(result.message).toBe('HTTP 530: Cloudflare Tunnel error (Error 1033)'); + }); + + it('preserves already-checked-in message instead of overriding with cookie fallback error', async () => { + const adapter = new NewApiAdapter(); + const result = await adapter.checkin(baseUrl, CHECKIN_ALREADY_TOKEN, 11494); + + expect(result.success).toBe(false); + expect(result.message).toBe('浠婂ぉ宸茬粡绛惧埌杩囧暒'); + }); +}); diff --git a/src/server/services/platforms/newApi.ts b/src/server/services/platforms/newApi.ts new file mode 100644 index 0000000..5ef0da0 --- /dev/null +++ b/src/server/services/platforms/newApi.ts @@ -0,0 +1,1011 @@ +import { ApiTokenInfo, BasePlatformAdapter, CheckinResult, BalanceInfo, UserInfo, TokenVerifyResult, CreateApiTokenOptions } from './base.js'; +import type { RequestInit as UndiciRequestInit } from 'undici'; +import { createContext, runInContext } from 'node:vm'; + +export class NewApiAdapter extends BasePlatformAdapter { + readonly platformName: string = 'new-api'; + + async detect(url: string): Promise { + try { + const res = await this.fetchJson(`${url}/api/status`); + return res?.success === true && typeof res?.data?.system_name === 'string'; + } catch { + return false; + } + } + + private tryDecodeUserId(token: string): number | null { + try { + const parts = token.split('.'); + if (parts.length !== 3) return null; + const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString()); + if (typeof payload?.id === 'number') return payload.id; + if (typeof payload?.sub === 'string' || typeof payload?.sub === 'number') { + const n = Number.parseInt(String(payload.sub), 10); + if (!Number.isNaN(n)) return n; + } + } catch {} + return null; + } + + private authHeaders(accessToken: string, userId?: number): Record { + const headers: Record = { Authorization: `Bearer ${accessToken}` }; + if (userId) headers['New-Api-User'] = String(userId); + return headers; + } + + private buildCookieCandidates(token: string): string[] { + const trimmed = (token || '').trim(); + if (!trimmed) return []; + + const raw = trimmed.startsWith('Bearer ') ? trimmed.slice(7).trim() : trimmed; + const candidates: string[] = []; + + if (raw.includes('=')) { + candidates.push(raw); + } + + candidates.push(`session=${raw}`); + candidates.push(`token=${raw}`); + + return Array.from(new Set(candidates)); + } + + private decodeBase64Loose(value: string): string | null { + if (!value) return null; + try { + return Buffer.from(value, 'base64').toString('utf8'); + } catch {} + try { + const normalized = value.replace(/-/g, '+').replace(/_/g, '/'); + return Buffer.from(normalized, 'base64').toString('utf8'); + } catch {} + return null; + } + + private extractLikelyUserIds(token: string): number[] { + const ids: number[] = []; + const push = (value: unknown) => { + const n = Number.parseInt(String(value), 10); + if (Number.isNaN(n)) return; + if (n <= 0 || n > 10_000_000) return; + if (!ids.includes(n)) ids.push(n); + }; + + const raw = (token || '').trim(); + if (!raw) return ids; + + const cookieCandidates = this.buildCookieCandidates(raw); + const sessionValues = new Set(); + for (const candidate of cookieCandidates) { + const match = candidate.match(/(?:^|;\s*)session=([^;]+)/i); + if (match?.[1]) sessionValues.add(match[1].trim()); + } + + if (raw && !raw.includes('=')) { + sessionValues.add(raw.startsWith('Bearer ') ? raw.slice(7).trim() : raw); + } + + for (const sessionValue of sessionValues) { + const decoded = this.decodeBase64Loose(sessionValue); + if (!decoded) continue; + + const payloadCandidates: string[] = [decoded]; + const parts = decoded.split('|'); + if (parts.length >= 2) { + const middlePayload = this.decodeBase64Loose(parts[1]); + if (middlePayload) payloadCandidates.push(middlePayload); + } + + for (const payload of payloadCandidates) { + for (const m of payload.matchAll(/_(\d{4,8})(?!\d)/g)) { + push(m[1]); + } + for (const m of payload.matchAll(/(?:user(?:name)?|uid|id)[^\d]{0,16}(\d{4,8})(?!\d)/gi)) { + push(m[1]); + } + } + } + + return ids; + } + + private buildUserIdProbeCandidates(token: string): number[] { + const candidates: number[] = []; + const push = (value: number | null) => { + if (typeof value !== 'number' || Number.isNaN(value) || value <= 0) return; + if (!candidates.includes(value)) candidates.push(value); + }; + + push(this.tryDecodeUserId(token)); + for (const guessed of this.extractLikelyUserIds(token)) { + push(guessed); + } + for (const id of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 50, 100, 8899, 11494]) { + push(id); + } + + return candidates; + } + + private parseTokenItems(payload: any): any[] { + if (Array.isArray(payload?.data)) return payload.data; + if (Array.isArray(payload?.data?.items)) return payload.data.items; + if (Array.isArray(payload?.data?.data)) return payload.data.data; + if (Array.isArray(payload?.items)) return payload.items; + if (Array.isArray(payload?.list)) return payload.list; + if (Array.isArray(payload?.data?.list)) return payload.data.list; + return []; + } + + private normalizeTokenKeyForCompare(value?: string | null): string { + const trimmed = (value || '').trim(); + return trimmed.startsWith('Bearer ') ? trimmed.slice(7).trim() : trimmed; + } + + private parseGroupKeys(payload: any): string[] { + const source = payload?.data ?? payload; + if (Array.isArray(source)) { + return source + .map((item) => String(item || '').trim()) + .filter(Boolean); + } + + if (source && typeof source === 'object') { + return Object.keys(source) + .map((key) => key.trim()) + .filter(Boolean); + } + + return []; + } + + private normalizeTokenItems(items: any[]): ApiTokenInfo[] { + const normalized: ApiTokenInfo[] = []; + for (let index = 0; index < items.length; index += 1) { + const item = items[index]; + const key = typeof item?.key === 'string' ? item.key.trim() : ''; + if (!key) continue; + const rawName = typeof item?.name === 'string' ? item.name.trim() : ''; + const status = typeof item?.status === 'number' ? item.status : undefined; + normalized.push({ + name: rawName || (index === 0 ? 'default' : `token-${index + 1}`), + key, + enabled: status === undefined ? true : status === 1, + }); + } + return normalized; + } + + private parseUserInfo(data: any): UserInfo { + return { + username: data?.username || data?.display_name || '', + displayName: data?.display_name, + email: data?.email, + role: data?.role, + }; + } + + private parseBalance(data: any): BalanceInfo { + const quota = (data?.quota || 0) / 500000; + const used = (data?.used_quota || 0) / 500000; + const total = quota + used; + const todayIncome = Number.isFinite(data?.today_income) ? (data.today_income / 500000) : undefined; + const todayQuotaConsumption = Number.isFinite(data?.today_quota_consumption) ? (data.today_quota_consumption / 500000) : undefined; + return { balance: quota, used, quota: total, todayIncome, todayQuotaConsumption }; + } + + private buildDefaultTokenPayload(options?: CreateApiTokenOptions): Record { + const normalizedName = (options?.name || '').trim() || 'metapi'; + const unlimitedQuota = options?.unlimitedQuota ?? true; + const remainQuota = Number.isFinite(options?.remainQuota) + ? Math.max(0, Math.trunc(options?.remainQuota as number)) + : 0; + const expiredTime = Number.isFinite(options?.expiredTime) + ? Math.trunc(options?.expiredTime as number) + : -1; + return { + name: normalizedName, + unlimited_quota: unlimitedQuota, + expired_time: expiredTime, + remain_quota: remainQuota, + allow_ips: (options?.allowIps || '').trim(), + model_limits_enabled: options?.modelLimitsEnabled ?? false, + model_limits: (options?.modelLimits || '').trim(), + group: (options?.group || '').trim(), + }; + } + + private parseChallengeArg1(html: string): string | null { + const match = html.match(/var\s+arg1\s*=\s*['"]([0-9a-fA-F]+)['"]/); + return match?.[1]?.toUpperCase() || null; + } + + private parseChallengeMapping(html: string): number[] | null { + const match = html.match(/for\(var m=\[([^\]]+)\],p=L\(0x115\)/); + if (!match?.[1]) return null; + + const values = match[1].split(',').map((raw) => { + const v = raw.trim().toLowerCase(); + if (!v) return Number.NaN; + if (v.startsWith('0x')) return Number.parseInt(v.slice(2), 16); + return Number.parseInt(v, 10); + }); + if (values.some((v) => Number.isNaN(v))) return null; + return values; + } + + private parseChallengeXorSeed(html: string): string | null { + const fnStart = html.indexOf('function a0i()'); + const bStart = html.indexOf('function b('); + const rotateStart = html.indexOf('(function(a,c){'); + const rotateEnd = html.indexOf('),!(function', rotateStart); + if (fnStart < 0 || bStart < 0 || bStart <= fnStart || rotateStart < 0 || rotateEnd < 0) { + return null; + } + + const helperCode = html.slice(fnStart, bStart); + const rotateCode = `${html.slice(rotateStart, rotateEnd + 1)})`; + + try { + const sandbox: Record = { decodeURIComponent }; + createContext(sandbox); + runInContext(helperCode, sandbox, { timeout: 100 }); + runInContext(rotateCode, sandbox, { timeout: 100 }); + const decoder = sandbox['a0j']; + if (typeof decoder !== 'function') return null; + const seed = (decoder as (idx: number) => unknown)(0x115); + if (typeof seed !== 'string' || !/^[0-9a-f]+$/i.test(seed)) return null; + return seed; + } catch { + return null; + } + } + + private solveAcwScV2(html: string): string | null { + const arg1 = this.parseChallengeArg1(html); + const mapping = this.parseChallengeMapping(html); + const xorSeed = this.parseChallengeXorSeed(html); + if (!arg1 || !mapping || !xorSeed) return null; + + const q: string[] = []; + for (let i = 0; i < arg1.length; i += 1) { + const ch = arg1[i]; + for (let j = 0; j < mapping.length; j += 1) { + if (mapping[j] === i + 1) { + q[j] = ch; + } + } + } + + const reordered = q.join(''); + let out = ''; + for (let i = 0; i < reordered.length && i < xorSeed.length; i += 2) { + const left = Number.parseInt(reordered.slice(i, i + 2), 16); + const right = Number.parseInt(xorSeed.slice(i, i + 2), 16); + if (Number.isNaN(left) || Number.isNaN(right)) return null; + out += (left ^ right).toString(16).padStart(2, '0'); + } + + return out || null; + } + + private upsertCookie(cookieHeader: string, name: string, value: string): string { + const parts = cookieHeader.split(';').map((part) => part.trim()).filter(Boolean); + let replaced = false; + const next = parts.map((part) => { + const eq = part.indexOf('='); + if (eq < 0) return part; + const key = part.slice(0, eq).trim(); + if (key !== name) return part; + replaced = true; + return `${name}=${value}`; + }); + if (!replaced) next.push(`${name}=${value}`); + return next.join('; '); + } + + private mergeSetCookiePairs(cookieHeader: string, setCookieHeaders: string[]): string { + let merged = cookieHeader; + for (const raw of setCookieHeaders) { + if (!raw) continue; + const firstPair = raw.split(';')[0]?.trim(); + if (!firstPair) continue; + const eq = firstPair.indexOf('='); + if (eq <= 0) continue; + const name = firstPair.slice(0, eq).trim(); + const value = firstPair.slice(eq + 1); + merged = this.upsertCookie(merged, name, value); + } + return merged; + } + + private parseJsonSafe(text: string): T | null { + try { + return JSON.parse(text) as T; + } catch { + return null; + } + } + + private extractHtmlErrorSummary(payloadRaw: string): string | null { + const text = (payloadRaw || '').trim(); + if (!text || !/\s*([^<]+?)\s*<\/title>/i); + let title = titleMatch?.[1]?.trim() || ''; + if (title.includes('|')) { + title = title.split('|')[0]?.trim() || title; + } + if (!title && /cloudflare tunnel error/i.test(text)) { + title = 'Cloudflare Tunnel error'; + } + if (!title) return null; + + const codeMatch = text.match(/]*>\s*Error\s*<\/span>\s*]*>\s*(\d{3,4})\s*<\/span>/i) + || text.match(/\bError\s*(\d{3,4})\b/i); + const code = codeMatch?.[1]; + return code ? `${title} (Error ${code})` : title; + } + + private formatRequestErrorMessage(err: unknown): string | null { + const raw = typeof (err as { message?: unknown })?.message === 'string' + ? (err as { message: string }).message.trim() + : ''; + if (!raw) return null; + + const httpMatch = raw.match(/^(HTTP\s+\d+):\s*([\s\S]+)$/); + if (!httpMatch) return raw; + + const [, prefix, payloadRaw] = httpMatch; + const payload = this.parseJsonSafe(payloadRaw); + const bodyMessage = this.extractResponseMessage(payload); + if (bodyMessage) return `${prefix}: ${bodyMessage}`; + const htmlSummary = this.extractHtmlErrorSummary(payloadRaw); + if (htmlSummary) return `${prefix}: ${htmlSummary}`; + return raw; + } + + private extractResponseMessage(payload: any): string { + if (typeof payload?.message === 'string' && payload.message.trim()) { + return payload.message.trim(); + } + if (typeof payload?.error?.message === 'string' && payload.error.message.trim()) { + return payload.error.message.trim(); + } + if (typeof payload?.msg === 'string' && payload.msg.trim()) { + return payload.msg.trim(); + } + return ''; + } + + private isShieldChallenge(contentType: string, text: string): boolean { + const ct = (contentType || '').toLowerCase(); + if (ct.includes('text/html') && /var\s+arg1\s*=|acw_sc__v2|cdn_sec_tc| +
+ + + diff --git a/src/web/main.tsx b/src/web/main.tsx new file mode 100644 index 0000000..7998f04 --- /dev/null +++ b/src/web/main.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App.js'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + +); diff --git a/src/web/pages/About.tsx b/src/web/pages/About.tsx new file mode 100644 index 0000000..798ba22 --- /dev/null +++ b/src/web/pages/About.tsx @@ -0,0 +1,143 @@ +import { tr } from '../i18n.js'; + +const VERSION = '1.0.0'; + +const FEATURES = [ + { icon: '🌐', title: '统一代理网关', desc: '一个 Key、一个入口,兼容 OpenAI / Claude 下游格式' }, + { icon: '🧠', title: '智能路由引擎', desc: '按成本、延迟、成功率自动选择最优通道,故障自动转移' }, + { icon: '📡', title: '多站点聚合', desc: '集中管理 New API / One API / OneHub / DoneHub / Veloera 等' }, + { icon: '🔍', title: '自动模型发现', desc: '上游新增模型自动出现在模型列表,零配置路由生成' }, + { icon: '🏪', title: '模型广场', desc: '跨站模型覆盖、定价对比、延迟与成功率实测数据' }, + { icon: '✅', title: '自动签到', desc: '定时签到 + 余额刷新,不再手动操心' }, + { icon: '🔔', title: '多渠道告警', desc: 'Webhook / Bark / Server酱 / 邮件,余额不足及时提醒' }, + { icon: '📦', title: '轻量部署', desc: '单 Docker 容器,内置 SQLite,无外部依赖' }, +]; + +const TECH_STACK = [ + { name: 'Fastify', desc: '高性能 Node.js 后端框架' }, + { name: 'React', desc: '用户界面库' }, + { name: 'TypeScript', desc: '端到端类型安全' }, + { name: 'Tailwind CSS v4', desc: '原子化样式框架' }, + { name: 'Drizzle ORM', desc: '轻量 TypeScript ORM' }, + { name: 'SQLite', desc: '零配置嵌入式数据库' }, +]; + +const LINKS = [ + { label: 'GitHub', href: 'https://github.com/cita-777/metapi', icon: '📂' }, + { label: 'Docker Hub', href: 'https://hub.docker.com/r/1467078763/metapi', icon: '🐳' }, +]; + +export default function About() { + return ( +
+ {/* Header */} +
+

{tr('关于 Metapi')}

+
+ + {/* Hero card */} +
+
+ Metapi +
+
Metapi
+
v{VERSION}
+
+
+
+ {tr('中转站的中转站 — 将你在各处注册的 New API / One API / OneHub 等 AI 中转站聚合为一个统一网关。一个 API Key、一个入口,自动发现模型、智能路由、成本最优。')} +
+
+ + {/* Features */} +
+

{tr('核心特色')}

+
+ {FEATURES.map((f) => ( +
+ {f.icon} +
+
{tr(f.title)}
+
+ {tr(f.desc)} +
+
+
+ ))} +
+
+ + {/* Tech Stack */} +
+

{tr('技术栈')}

+
+ {TECH_STACK.map((t) => ( +
+
{t.name}
+
+ {tr(t.desc)} +
+
+ ))} +
+
+ + {/* Links */} + + + {/* Privacy */} +
+

{tr('数据与隐私')}

+
+ {tr('Metapi 完全自托管,所有数据(账号、令牌、路由、日志)均存储在本地 SQLite 数据库中,不会向任何第三方发送数据。代理请求仅在你的服务器与上游站点之间直连传输。')} +
+
+
+ ); +} diff --git a/src/web/pages/Accounts.tsx b/src/web/pages/Accounts.tsx new file mode 100644 index 0000000..95c0930 --- /dev/null +++ b/src/web/pages/Accounts.tsx @@ -0,0 +1,464 @@ +import React, { useEffect, useState } from 'react'; +import { api } from '../api.js'; +import { useToast } from '../components/Toast.js'; +import ModernSelect from '../components/ModernSelect.js'; +import { getAccountsAddPanelStyle } from './helpers/accountsPanelStyle.js'; +import { tr } from '../i18n.js'; + +export default function Accounts() { + const [accounts, setAccounts] = useState([]); + const [sites, setSites] = useState([]); + const [showAdd, setShowAdd] = useState(false); + const [addMode, setAddMode] = useState<'token' | 'login'>('token'); + const [loginForm, setLoginForm] = useState({ siteId: 0, username: '', password: '' }); + const [tokenForm, setTokenForm] = useState({ siteId: 0, accessToken: '', platformUserId: '' }); + const [verifyResult, setVerifyResult] = useState(null); + const [verifying, setVerifying] = useState(false); + const [saving, setSaving] = useState(false); + const [actionLoading, setActionLoading] = useState>({}); + const toast = useToast(); + + const load = () => { + api.getAccounts().then(setAccounts).catch(() => toast.error('加载账号列表失败')); + api.getSites().then(setSites); + }; + useEffect(() => { load(); }, []); + + const handleLoginAdd = async () => { + if (!loginForm.siteId || !loginForm.username || !loginForm.password) return; + setSaving(true); + try { + const result = await api.loginAccount(loginForm); + if (result.success) { + setShowAdd(false); + setLoginForm({ siteId: 0, username: '', password: '' }); + const msg = result.apiTokenFound + ? `账号 "${loginForm.username}" 已添加,API Key 已自动获取` + : `账号 "${loginForm.username}" 已添加(未找到 API Key,请手动设置)`; + toast.success(msg); + load(); + } else { + toast.error(result.message || '登录失败'); + } + } catch (e: any) { + toast.error(e.message || '登录请求失败'); + } finally { + setSaving(false); + } + }; + + const handleVerifyToken = async () => { + if (!tokenForm.siteId || !tokenForm.accessToken) return; + setVerifying(true); + setVerifyResult(null); + try { + const result = await api.verifyToken({ + siteId: tokenForm.siteId, + accessToken: tokenForm.accessToken, + platformUserId: tokenForm.platformUserId ? parseInt(tokenForm.platformUserId) : undefined, + }); + setVerifyResult(result); + if (result.success) { + toast.success(`验证成功: ${result.userInfo?.username || '未知用户'}`); + } else { + toast.error(result.message || 'Token 无效'); + } + } catch (e: any) { + toast.error(e.message || '验证失败'); + setVerifyResult({ success: false, message: e.message }); + } finally { + setVerifying(false); + } + }; + + const handleTokenAdd = async () => { + if (!tokenForm.siteId || !tokenForm.accessToken) return; + setSaving(true); + try { + const result = await api.addAccount({ + siteId: tokenForm.siteId, + accessToken: tokenForm.accessToken, + platformUserId: tokenForm.platformUserId ? parseInt(tokenForm.platformUserId) : undefined, + }); + setShowAdd(false); + setTokenForm({ siteId: 0, accessToken: '', platformUserId: '' }); + setVerifyResult(null); + if (result.tokenType === 'apikey') { + toast.success('已添加为 API Key 账号(可用于代理转发)'); + } else { + const parts: string[] = []; + if (result.usernameDetected) parts.push('用户名已自动识别'); + if (result.apiTokenFound) parts.push('API Key 已自动获取'); + const extra = parts.length ? `(${parts.join(',')})` : ''; + toast.success(`账号已添加${extra}`); + } + load(); + } catch (e: any) { + toast.error(e.message || '添加失败'); + } finally { + setSaving(false); + } + }; + + const withLoading = async (key: string, fn: () => Promise, successMsg?: string) => { + setActionLoading(s => ({ ...s, [key]: true })); + try { await fn(); if (successMsg) toast.success(successMsg); load(); } + catch (e: any) { toast.error(e.message || '操作失败'); } + finally { setActionLoading(s => ({ ...s, [key]: false })); } + }; + + const inputStyle: React.CSSProperties = { + width: '100%', padding: '10px 14px', border: '1px solid var(--color-border)', + borderRadius: 'var(--radius-sm)', fontSize: 13, outline: 'none', + background: 'var(--color-bg)', color: 'var(--color-text-primary)', + }; + + const runtimeHealthMap: Record = { + healthy: { label: '健康', cls: 'badge-success', dotClass: 'status-dot-success', pulse: true }, + unhealthy: { label: '异常', cls: 'badge-error', dotClass: 'status-dot-error', pulse: true }, + degraded: { label: '降级', cls: 'badge-warning', dotClass: 'status-dot-pending', pulse: true }, + disabled: { label: '已禁用', cls: 'badge-muted', dotClass: 'status-dot-muted', pulse: false }, + unknown: { label: '未知', cls: 'badge-muted', dotClass: 'status-dot-pending', pulse: false }, + }; + + const resolveRuntimeHealth = (account: any) => { + const fallbackState = account.status === 'disabled' || account.site?.status === 'disabled' + ? 'disabled' + : (account.status === 'expired' ? 'unhealthy' : 'unknown'); + const state = account.runtimeHealth?.state || fallbackState; + const cfg = runtimeHealthMap[state] || runtimeHealthMap.unknown; + const reason = account.runtimeHealth?.reason + || (state === 'disabled' + ? '账号或站点已禁用' + : (state === 'unhealthy' ? '最近健康检查失败' : '尚未获取运行健康信息')); + return { state, reason, ...cfg }; + }; + + const handleRefreshRuntimeHealth = async () => { + setActionLoading((s) => ({ ...s, 'health-refresh': true })); + try { + const res = await api.refreshAccountHealth(); + if (res?.queued) { + toast.info(res.message || '账号状态刷新任务已提交,完成后会自动更新。'); + } else { + toast.success(res?.message || '账号状态已刷新'); + } + load(); + } catch (e: any) { + toast.error(e.message || '刷新账号状态失败'); + } finally { + setActionLoading((s) => ({ ...s, 'health-refresh': false })); + } + }; + + const handleToggleCheckin = async (account: any) => { + const key = `checkin-toggle-${account.id}`; + const nextEnabled = !account.checkinEnabled; + setActionLoading((s) => ({ ...s, [key]: true })); + try { + await api.updateAccount(account.id, { checkinEnabled: nextEnabled }); + toast.success(nextEnabled ? '已开启签到' : '已关闭签到(全部签到会忽略此账号)'); + load(); + } catch (e: any) { + toast.error(e.message || '切换签到状态失败'); + } finally { + setActionLoading((s) => ({ ...s, [key]: false })); + } + }; + + return ( +
+
+

{tr('账号管理')}

+
+ + + +
+
+ + {/* Add Panel */} + {showAdd && ( +
+ {/* Mode tabs */} +
+ + +
+ + {addMode === 'token' ? ( +
+
+
+
支持两种 Token 类型,系统自动识别
+
API Key(在站点「令牌」页面生成)→ 用于代理转发
+
Session Token(从浏览器获取)→ 支持签到、余额查询等全部功能
+
+ 获取 Session Token: F12 → Application → Local Storage
+
+
+ { + const nextSiteId = Number.parseInt(nextValue, 10) || 0; + setTokenForm((f) => ({ ...f, siteId: nextSiteId })); + setVerifyResult(null); + }} + options={[ + { value: '0', label: '选择站点' }, + ...sites.map((s: any) => ({ + value: String(s.id), + label: `${s.name} (${s.platform})`, + })), + ]} + placeholder="选择站点" + /> +