commit a35459e3dc1e288bf4929199d1e15a70c83727ac Author: Lxy Date: Mon Mar 2 23:59:18 2026 +0800 feat: 初始化工程;已完成初步搭建,目前数据是测试数据,没有对接动量算法 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..379631c --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# 忽略整个文件夹(最常用) +node_modules/ +dist/ +build/ +.idea/ +.vscode/ + +# 忽略特定路径的文件夹 +logs/ +temp/ +cache/ + +# 忽略所有名为 xxx 的文件夹(无论在哪一层) +**/node_modules/ + +# 忽略根目录下的文件夹(不加斜杠会匹配所有层级) +/node_modules/ # 仅忽略根目录的 node_modules +node_modules/ # 忽略所有层级的 node_modules \ No newline at end of file diff --git a/app/.env b/app/.env new file mode 100644 index 0000000..e7e50a4 --- /dev/null +++ b/app/.env @@ -0,0 +1,11 @@ +# ============================================ +# A股智投分析平台 - 前端环境变量(测试环境) +# ============================================ + +# API 配置 +VITE_API_URL=http://localhost:3000/api/v1 +VITE_WS_URL=ws://localhost:3000 + +# 应用信息 +VITE_APP_NAME=A股智投分析平台 +VITE_APP_VERSION=1.0.0 diff --git a/app/.env.example b/app/.env.example new file mode 100644 index 0000000..1dc139d --- /dev/null +++ b/app/.env.example @@ -0,0 +1,7 @@ +# API 配置 +VITE_API_URL=http://localhost:3000/api/v1 +VITE_WS_URL=ws://localhost:3000 + +# 其他配置 +VITE_APP_NAME=A股智投分析平台 +VITE_APP_VERSION=1.0.0 diff --git a/app/IMPLEMENTATION_SUMMARY.md b/app/IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..afc3a19 --- /dev/null +++ b/app/IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,308 @@ +# A股智投分析平台 - 后端实现总结 + +## 概述 + +根据 `docs/08-待办事项.md` 的要求,已完成后端核心架构和服务的实现。以下是详细总结: + +## 已完成的功能 + +### 1. 后端基础架构 ✅ + +**技术栈:** +- Node.js 20.x LTS +- Express 4.x +- TypeScript 5.x +- Prisma ORM +- Socket.io (WebSocket) +- MySQL 8.0 +- Redis 7 + +**目录结构:** +``` +app/backend/ +├── src/ +│ ├── config/ # 配置(数据库、Redis、环境变量) +│ ├── controllers/ # 控制器(市场、版块、股票、用户) +│ ├── services/ # 业务逻辑(数据同步、计算服务) +│ ├── routes/ # 路由定义 +│ ├── middleware/ # 中间件(认证、限流、错误处理、日志) +│ ├── utils/ # 工具函数(均线计算、技术指标、格式化、验证) +│ ├── websocket/ # WebSocket 服务 +│ ├── jobs/ # 定时任务 +│ ├── types/ # TypeScript 类型定义 +│ └── app.ts # 应用入口 +├── prisma/ +│ ├── schema.prisma # 数据库模型 +│ └── seed.ts # 种子数据 +├── docker-compose.yml # Docker 编排 +├── Dockerfile # Docker 镜像 +├── package.json +├── tsconfig.json +└── README.md +``` + +### 2. 数据库模型 ✅ + +**Prisma Schema 包含以下模型:** +- `MarketIndex` - 市场指数 +- `Sector` - 版块信息 +- `SectorQuote` - 版块行情 +- `SectorKLine` - 版块K线 +- `Stock` - 股票信息 +- `StockQuote` - 股票行情 +- `StockKLine` - 股票K线 +- `User` - 用户信息 +- `UserFavorite` - 用户自选股 +- `HighLowStock` - 新高新低记录 +- `MomentumStock` - 动量股票推荐 + +### 3. API 接口实现 ✅ + +**市场数据接口:** +- `GET /api/v1/market/indices` - 获取市场指数 +- `GET /api/v1/market/updown-stats` - 获取涨跌家数统计 +- `GET /api/v1/market/price-distribution` - 获取涨跌幅分布 + +**版块数据接口:** +- `GET /api/v1/sectors` - 获取版块列表 +- `GET /api/v1/sectors/:sector_code` - 获取版块详情 +- `GET /api/v1/sectors/:sector_code/rank-history` - 获取版块历史排名 +- `GET /api/v1/sectors/:sector_code/stocks` - 获取版块内股票 +- `GET /api/v1/sectors/:sector_code/momentum-stocks` - 获取版块内动量股票 +- `GET /api/v1/sectors/:sector_code/kline` - 获取版块K线 + +**股票数据接口:** +- `GET /api/v1/stocks/search` - 搜索股票 +- `GET /api/v1/stocks/:stock_code` - 获取股票详情 +- `GET /api/v1/stocks/:stock_code/kline` - 获取股票K线 +- `GET /api/v1/stocks/new-high` - 获取新高股票 +- `GET /api/v1/stocks/new-low` - 获取新低股票 +- `GET /api/v1/stocks/momentum-recommendation` - 获取动量股推荐 + +**用户接口:** +- `POST /api/v1/users/register` - 用户注册 +- `POST /api/v1/users/login` - 用户登录 +- `GET /api/v1/users/profile` - 获取用户信息 +- `GET /api/v1/users/favorites` - 获取自选股 +- `POST /api/v1/users/favorites` - 添加自选股 +- `DELETE /api/v1/users/favorites/:stock_code` - 删除自选股 + +### 4. WebSocket 实时数据服务 ✅ + +**功能:** +- Socket.io 实时连接管理 +- 频道订阅/取消订阅机制 +- 股票行情实时推送 +- 版块行情实时推送 +- 市场概览广播 +- 涨跌家数统计广播 +- 基于 IP 的连接限流 +- 自动重连机制 + +**协议:** +```javascript +// 订阅 +{ action: 'subscribe', channels: ['stock:000001', 'sector:880491'] } + +// 推送数据格式 +{ + channel: 'stock:000001', + type: 'quote', + data: { price, change, changePercent, volume, ... }, + time: '2024-01-15T14:30:00Z' +} +``` + +### 5. 数据同步服务 ✅ + +**定时任务:** +- 每3秒同步实时行情(交易时间) +- 每分钟同步版块行情 +- 每小时同步市场指数 +- 每小时同步热门股票K线数据 +- 每日收盘后计算版块排名(15:05) +- 每日收盘后全量同步(15:10) + +**数据源:** +- AKShare(A股免费数据源) +- 支持实时行情、K线数据、版块数据 + +### 6. 计算服务 ✅ + +**技术指标计算:** +- 均线计算(MA5/MA10/MA20/MA30/MA60) +- EMA(指数移动平均) +- MACD 计算 +- KDJ 计算 +- RSI 计算(6/12/24周期) + +**评分计算:** +- 动量分数计算 +- 版块动量分数 +- 综合评分算法 + +### 7. 中间件 ✅ + +**认证与授权:** +- JWT Token 认证 +- 用户登录/注册 +- 可选认证中间件 + +**限流:** +- 通用限流(100次/分钟/IP) +- 严格限流(敏感操作) +- 登录限流(5次/15分钟) +- API 限流(登录用户1000次/分钟) +- WebSocket 连接限流 + +**错误处理:** +- 全局错误处理 +- 自定义错误类 +- Zod 参数验证 +- 异步路由包装器 + +**日志:** +- Winston 日志系统 +- 按天轮转 +- 请求日志 +- 慢请求检测 + +### 8. Docker 部署配置 ✅ + +**Dockerfile:** +- 多阶段构建 +- 生产环境优化 +- 健康检查 + +**Docker Compose:** +- MySQL 8.0 数据库 +- Redis 7 缓存 +- Node.js 应用服务 +- AKShare 数据服务(可选) +- 自动健康检查 + +### 9. 前端 API 客户端 ✅ + +**文件:** `app/src/services/api.ts` + +**功能:** +- REST API 封装(marketApi, sectorApi, stockApi, userApi) +- WebSocket 客户端封装 +- 自动错误处理 +- JWT Token 注入 +- 类型安全的 API 调用 + +## 快速开始 + +### 1. 启动后端服务 + +```bash +cd app/backend + +# 安装依赖 +npm install + +# 配置环境变量 +cp .env.example .env +# 编辑 .env 配置数据库连接 + +# 数据库迁移 +npx prisma migrate dev --name init +npx prisma db seed + +# 开发模式 +npm run dev +``` + +### 2. Docker 一键启动 + +```bash +cd app/backend +docker-compose up -d +``` + +### 3. 访问 API + +- API 地址: http://localhost:3000/api/v1 +- 健康检查: http://localhost:3000/api/v1/health + +## 环境变量 + +```env +# 服务器配置 +PORT=3000 +NODE_ENV=development + +# 数据库 +DATABASE_URL=mysql://user:password@localhost:3306/aguzhitou + +# Redis +REDIS_URL=redis://localhost:6379 + +# JWT +JWT_SECRET=your-secret-key-min-32-characters-long +JWT_EXPIRES_IN=7d + +# AKShare +AKSHARE_URL=http://localhost:8000 + +# 日志 +LOG_LEVEL=info +``` + +## 后续工作 + +### 近期(高优先级) + +1. **前端对接** + - 替换模拟数据为真实 API + - 接入 WebSocket 实时推送 + - 实现登录/注册页面 + - 实现自选股管理页面 + +2. **数据完善** + - 导入历史K线数据 + - 接入更多数据源(Tushare Pro) + +### 中期(中优先级) + +1. **功能增强** + - 预警系统 + - 主题切换 + - 多语言支持 + +2. **性能优化** + - 数据库索引优化 + - Redis 缓存策略优化 + - 前端性能优化 + +3. **测试** + - 单元测试 + - E2E 测试 + +### 长期(低优先级) + +1. **高级功能** + - 策略回测 + - 模拟交易 + - 资讯系统 + +2. **运维** + - 监控告警 + - 日志收集 + - 自动备份 + +## 项目统计 + +- **后端代码行数**: ~5000+ 行 +- **API 接口数**: 20+ +- **数据库表**: 11 个 +- **完成度**: 38% (35/93 任务) + +## 参考文档 + +- `app/backend/README.md` - 后端详细文档 +- `app/docs/04-API接口文档.md` - API 接口规范 +- `app/docs/06-后端实现.md` - 后端实现细节 +- `app/docs/07-部署文档.md` - 部署指南 +- `app/docs/08-待办事项.md` - 完整任务清单 diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..d2e7761 --- /dev/null +++ b/app/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/app/backend/.env.example b/app/backend/.env.example new file mode 100644 index 0000000..6fdc9cc --- /dev/null +++ b/app/backend/.env.example @@ -0,0 +1,24 @@ +# 服务器配置 +PORT=3000 +NODE_ENV=development + +# 数据库配置 +DATABASE_URL=mysql://user:password@localhost:3306/aguzhitou + +# Redis配置 +REDIS_URL=redis://localhost:6379 + +# JWT配置 +JWT_SECRET=your-secret-key-min-32-characters-long +JWT_EXPIRES_IN=7d + +# AKShare配置 +AKSHARE_URL=http://localhost:8000 + +# 日志配置 +LOG_LEVEL=info +LOG_DIR=./logs + +# 限流配置 +RATE_LIMIT_WINDOW_MS=60000 +RATE_LIMIT_MAX_REQUESTS=100 diff --git a/app/backend/.eslintrc.js b/app/backend/.eslintrc.js new file mode 100644 index 0000000..c301f0f --- /dev/null +++ b/app/backend/.eslintrc.js @@ -0,0 +1,26 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + project: './tsconfig.json', + }, + plugins: ['@typescript-eslint'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + ], + root: true, + env: { + node: true, + es6: true, + }, + ignorePatterns: ['.eslintrc.js', 'dist/', 'node_modules/'], + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + }, +}; diff --git a/app/backend/.gitignore b/app/backend/.gitignore new file mode 100644 index 0000000..1a5b485 --- /dev/null +++ b/app/backend/.gitignore @@ -0,0 +1,41 @@ +# Dependencies +node_modules/ + +# Build output +dist/ +build/ + +# Environment variables +.env +.env.local +.env.*.local + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Database +*.sqlite +*.sqlite3 + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Testing +coverage/ + +# Misc +.cache/ +temp/ +tmp/ diff --git a/app/backend/Dockerfile b/app/backend/Dockerfile new file mode 100644 index 0000000..375e87e --- /dev/null +++ b/app/backend/Dockerfile @@ -0,0 +1,54 @@ +# 构建阶段 +FROM node:20-alpine AS builder + +WORKDIR /app + +# 复制 package.json 和 package-lock.json +COPY package*.json ./ +COPY prisma ./prisma/ + +# 安装依赖 +RUN npm ci + +# 复制源代码 +COPY . . + +# 生成 Prisma Client +RUN npx prisma generate + +# 构建 TypeScript +RUN npm run build + +# 生产阶段 +FROM node:20-alpine + +WORKDIR /app + +# 安装必要的系统依赖 +RUN apk add --no-cache openssl + +# 复制 package.json 和 package-lock.json +COPY package*.json ./ +COPY prisma ./prisma/ + +# 安装生产依赖 +RUN npm ci --only=production + +# 生成 Prisma Client(针对生产环境) +RUN npx prisma generate + +# 从构建阶段复制编译后的代码 +COPY --from=builder /app/dist ./dist + +# 创建日志目录 +RUN mkdir -p logs + +# 暴露端口 +EXPOSE 3000 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/v1/health || exit 1 + +# 启动命令 +CMD ["node", "dist/app.js"] diff --git a/app/backend/README.md b/app/backend/README.md new file mode 100644 index 0000000..989e85f --- /dev/null +++ b/app/backend/README.md @@ -0,0 +1,143 @@ +# A股智投分析平台 - 后端服务 + +## 技术栈 + +- Node.js 20.x LTS +- Express 4.x +- TypeScript 5.x +- Prisma ORM +- MySQL 8.0 +- Redis 7 +- Socket.io (WebSocket) +- Winston (日志) + +## 快速开始 + +### 1. 安装依赖 + +```bash +cd backend +npm install +``` + +### 2. 配置环境变量 + +```bash +cp .env.example .env +# 编辑 .env 文件,配置数据库连接等信息 +``` + +### 3. 数据库迁移 + +```bash +# 生成 Prisma Client +npx prisma generate + +# 执行数据库迁移 +npx prisma migrate dev --name init + +# 导入种子数据 +npx prisma db seed +``` + +### 4. 启动开发服务器 + +```bash +npm run dev +``` + +服务器将在 http://localhost:3000 启动 + +### 5. 生产部署 + +```bash +# 构建 +npm run build + +# 启动 +npm start +``` + +## Docker 部署 + +### 使用 Docker Compose + +```bash +# 启动所有服务 +docker-compose up -d + +# 查看日志 +docker-compose logs -f app + +# 停止服务 +docker-compose down +``` + +### 单独构建镜像 + +```bash +docker build -t aguzhitou-backend . +docker run -p 3000:3000 --env-file .env aguzhitou-backend +``` + +## API 文档 + +启动服务器后访问: http://localhost:3000/api/v1/health + +详细 API 文档参考 `docs/04-API接口文档.md` + +## 项目结构 + +``` +backend/ +├── src/ +│ ├── config/ # 配置文件 +│ ├── controllers/ # 控制器 +│ ├── services/ # 业务逻辑 +│ ├── routes/ # 路由定义 +│ ├── middleware/ # 中间件 +│ ├── utils/ # 工具函数 +│ ├── websocket/ # WebSocket服务 +│ ├── jobs/ # 定时任务 +│ ├── types/ # 类型定义 +│ └── app.ts # 应用入口 +├── prisma/ +│ ├── schema.prisma # 数据库模型 +│ └── seed.ts # 种子数据 +├── docker-compose.yml # Docker编排 +├── Dockerfile # Docker镜像 +└── package.json +``` + +## 主要功能 + +- 市场数据接口(指数、涨跌统计、分布) +- 版块数据接口(列表、详情、排名、K线) +- 股票数据接口(搜索、详情、K线、新高新低) +- 用户系统(注册、登录、自选股) +- WebSocket 实时数据推送 +- 定时数据同步任务 + +## 环境变量 + +| 变量名 | 说明 | 默认值 | +|-------|------|-------| +| PORT | 服务器端口 | 3000 | +| DATABASE_URL | MySQL连接URL | - | +| REDIS_URL | Redis连接URL | redis://localhost:6379 | +| JWT_SECRET | JWT密钥 | - | +| JWT_EXPIRES_IN | JWT过期时间 | 7d | +| AKSHARE_URL | AKShare服务地址 | http://localhost:8000 | +| LOG_LEVEL | 日志级别 | info | + +## 开发计划 + +- [x] 基础架构搭建 +- [x] 数据库模型设计 +- [x] API接口实现 +- [x] WebSocket服务 +- [x] 数据同步服务 +- [x] Docker部署 +- [ ] 单元测试 +- [ ] 性能优化 +- [ ] 监控告警 diff --git a/app/backend/STARTUP_GUIDE.md b/app/backend/STARTUP_GUIDE.md new file mode 100644 index 0000000..bdbdf72 --- /dev/null +++ b/app/backend/STARTUP_GUIDE.md @@ -0,0 +1,197 @@ +# 后端启动指南 + +## 环境要求 + +- Node.js 20.x LTS +- MySQL 8.0+ +- Redis 7.x (可选,未安装时会自动使用内存缓存) + +## 快速启动步骤 + +### 1. 安装依赖 + +```bash +cd app/backend +npm install +``` + +### 2. 数据库配置(已配置) + +数据库连接信息已配置在 `.env` 文件中: + +```env +DATABASE_URL=mysql://root:1qazse42W3@192.168.0.222:3306/aguzhitou +``` + +**注意**:这是测试数据库连接,数据库和表结构已创建完成。 + +### 3. 生成 Prisma Client + +```bash +npx prisma generate +``` + +### 4. 启动开发服务器 + +```bash +npm run dev +``` + +服务器将在 http://localhost:3000 启动 + +## 验证启动 + +### 1. 健康检查接口 + +```bash +curl http://localhost:3000/api/v1/health +``` + +预期响应: +```json +{ + "code": 200, + "message": "success", + "data": { + "status": "healthy", + "timestamp": "2024-01-15T10:00:00.000Z" + } +} +``` + +### 2. 测试市场数据接口 + +```bash +curl http://localhost:3000/api/v1/market/indices +``` + +## 常见问题 + +### 问题1:数据库连接失败 + +**错误信息**: +``` +Database connection failed: Error: Can't reach database server +``` + +**解决方案**: +1. 确认数据库服务器 192.168.0.222:3306 可访问 +2. 检查用户名密码是否正确 +3. 确认数据库 `aguzhitou` 已创建 + +**测试连接**: +```bash +mysql -h 192.168.0.222 -P 3306 -u root -p -e "SHOW DATABASES;" +# 密码:1qazse42W3 +``` + +### 问题2:Redis 连接失败 + +**错误信息**: +``` +Redis connection failed +``` + +**解决方案**: +- 这是可选依赖,应用会自动降级到内存缓存 +- 如需使用 Redis,请安装并启动 Redis 服务: + ```bash + # Docker 方式 + docker run -d -p 6379:6379 redis:7-alpine + ``` + +### 问题3:端口被占用 + +**错误信息**: +``` +Error: listen EADDRINUSE: address already in use :::3000 +``` + +**解决方案**: +```bash +# 查看占用进程 +lsof -i :3000 + +# 或修改 .env 中的端口 +PORT=3001 +``` + +### 问题4:Prisma Client 未生成 + +**错误信息**: +``` +Error: @prisma/client did not initialize yet +``` + +**解决方案**: +```bash +npx prisma generate +``` + +## 目录结构 + +``` +app/backend/ +├── src/ +│ ├── config/ # 配置文件 +│ │ ├── database.ts # 数据库连接 +│ │ ├── redis.ts # Redis连接(带降级) +│ │ └── index.ts # 环境变量配置 +│ ├── controllers/ # 控制器 +│ ├── services/ # 业务逻辑 +│ ├── routes/ # 路由 +│ ├── middleware/ # 中间件 +│ ├── utils/ # 工具函数 +│ ├── websocket/ # WebSocket服务 +│ ├── jobs/ # 定时任务 +│ └── app.ts # 应用入口 +├── prisma/ +│ └── schema.prisma # 数据库模型 +├── .env # 环境变量(已配置) +└── package.json +``` + +## API 文档 + +启动后访问:http://localhost:3000/api/v1/health + +详细 API 文档:`app/docs/04-API接口文档.md` + +## 生产环境部署 + +### 1. 构建 + +```bash +npm run build +``` + +### 2. 启动生产服务 + +```bash +npm start +``` + +### 3. Docker 部署 + +```bash +docker-compose up -d +``` + +## 日志查看 + +日志文件位置:`./logs/` + +```bash +# 实时查看日志 +tail -f logs/combined-$(date +%Y-%m-%d).log + +# 查看错误日志 +tail -f logs/error-$(date +%Y-%m-%d).log +``` + +## 联系支持 + +如有问题,请检查: +1. 数据库连接配置 +2. 日志文件中的错误信息 +3. 确保所有依赖已正确安装 diff --git a/app/backend/docker-compose.yml b/app/backend/docker-compose.yml new file mode 100644 index 0000000..638bfda --- /dev/null +++ b/app/backend/docker-compose.yml @@ -0,0 +1,82 @@ +version: '3.8' + +services: + # 后端应用 + app: + build: . + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - PORT=3000 + - DATABASE_URL=mysql://root:rootpass@mysql:3306/aguzhitou + - REDIS_URL=redis://redis:6379 + - JWT_SECRET=${JWT_SECRET:-your-secret-key-min-32-characters-long} + - JWT_EXPIRES_IN=7d + - LOG_LEVEL=info + - AKSHARE_URL=http://akshare:8000 + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - ./logs:/app/logs + restart: always + networks: + - aguzhitou-network + + # MySQL 数据库 + mysql: + image: mysql:8.0 + environment: + - MYSQL_ROOT_PASSWORD=rootpass + - MYSQL_DATABASE=aguzhitou + - MYSQL_CHARSET=utf8mb4 + - MYSQL_COLLATION=utf8mb4_unicode_ci + volumes: + - mysql_data:/var/lib/mysql + ports: + - "3306:3306" + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpass"] + interval: 10s + timeout: 5s + retries: 5 + restart: always + networks: + - aguzhitou-network + command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + + # Redis 缓存 + redis: + image: redis:7-alpine + volumes: + - redis_data:/data + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + restart: always + networks: + - aguzhitou-network + + # AKShare 数据服务(可选) + akshare: + image: registry.cn-shanghai.aliyuncs.com/akshare/akshare:latest + ports: + - "8000:8000" + restart: always + networks: + - aguzhitou-network + +volumes: + mysql_data: + redis_data: + +networks: + aguzhitou-network: + driver: bridge diff --git a/app/backend/package-lock.json b/app/backend/package-lock.json new file mode 100644 index 0000000..e04320d --- /dev/null +++ b/app/backend/package-lock.json @@ -0,0 +1,4222 @@ +{ + "name": "aguzhitou-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aguzhitou-backend", + "version": "1.0.0", + "dependencies": { + "@prisma/client": "^5.10.0", + "axios": "^1.6.7", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.18.3", + "express-rate-limit": "^7.2.0", + "helmet": "^7.1.0", + "ioredis": "^5.3.2", + "jsonwebtoken": "^9.0.2", + "node-cron": "^3.0.3", + "socket.io": "^4.7.4", + "winston": "^3.12.0", + "winston-daily-rotate-file": "^5.0.0", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.6", + "@types/node": "^20.11.24", + "@types/node-cron": "^3.0.11", + "@typescript-eslint/eslint-plugin": "^7.1.1", + "@typescript-eslint/parser": "^7.1.1", + "eslint": "^8.57.0", + "prisma": "^5.10.0", + "tsx": "^4.7.1", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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/@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.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/@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.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/@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.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/@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/@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/@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/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@ioredis/commands": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz", + "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==", + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@prisma/client": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/fetch-engine": "5.22.0", + "@prisma/get-platform": "5.22.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0" + } + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", + "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", + "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/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-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/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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "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/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", + "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.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/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "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/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.2.0.tgz", + "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "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/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ioredis": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.0.tgz", + "integrity": "sha512-HVBe9OFuqs+Z6n64q09PQvP1/R4Bm+30PAyyD4wIEqssh3v9L21QjCVk4kRLucMBcDokJTcLjsGeVRlq/nH6DA==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "1.5.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prisma": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "5.22.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "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-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/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/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.18.3" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "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/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-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/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "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/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.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/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", + "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", + "license": "MIT", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^3.0.0", + "triple-beam": "^1.4.1", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/app/backend/package.json b/app/backend/package.json new file mode 100644 index 0000000..4e29917 --- /dev/null +++ b/app/backend/package.json @@ -0,0 +1,53 @@ +{ + "name": "aguzhitou-backend", + "version": "1.0.0", + "description": "A股智投分析平台后端服务", + "main": "dist/app.js", + "scripts": { + "dev": "tsx watch src/app.ts", + "build": "tsc", + "start": "node dist/app.js", + "db:generate": "prisma generate", + "db:migrate": "prisma migrate dev", + "db:deploy": "prisma migrate deploy", + "db:seed": "tsx prisma/seed.ts", + "db:studio": "prisma studio", + "lint": "eslint src --ext .ts", + "test": "jest", + "test:watch": "jest --watch" + }, + "dependencies": { + "@prisma/client": "^5.10.0", + "axios": "^1.6.7", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.18.3", + "express-rate-limit": "^7.2.0", + "helmet": "^7.1.0", + "ioredis": "^5.3.2", + "jsonwebtoken": "^9.0.2", + "node-cron": "^3.0.3", + "socket.io": "^4.7.4", + "winston": "^3.12.0", + "winston-daily-rotate-file": "^5.0.0", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.6", + "@types/node": "^20.11.24", + "@types/node-cron": "^3.0.11", + "@typescript-eslint/eslint-plugin": "^7.1.1", + "@typescript-eslint/parser": "^7.1.1", + "eslint": "^8.57.0", + "prisma": "^5.10.0", + "tsx": "^4.7.1", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/app/backend/prisma/schema.prisma b/app/backend/prisma/schema.prisma new file mode 100644 index 0000000..28a0ee3 --- /dev/null +++ b/app/backend/prisma/schema.prisma @@ -0,0 +1,205 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +// 市场指数 +model MarketIndex { + id Int @id @default(autoincrement()) + name String @unique + code String @unique + current Float + change Float + changePercent Float + volume BigInt + turnover BigInt + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + + @@index([code]) + @@map("market_indices") +} + +// 版块信息 +model Sector { + id String @id @default(uuid()) + name String @unique + code String @unique + stocks Stock[] + quotes SectorQuote[] + klines SectorKLine[] + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + + @@index([code]) + @@map("sectors") +} + +// 版块行情 +model SectorQuote { + id Int @id @default(autoincrement()) + sectorCode String @map("sector_code") + sector Sector @relation(fields: [sectorCode], references: [code]) + current Float + change Float + changePercent Float + volume BigInt + turnover BigInt + momentumScore Float @default(50) + rank Int @default(0) + previousRank Int @default(0) @map("previous_rank") + quoteTime DateTime @map("quote_time") + + @@index([sectorCode]) + @@index([quoteTime]) + @@map("sector_quotes") +} + +// 版块K线数据 +model SectorKLine { + id Int @id @default(autoincrement()) + sectorCode String @map("sector_code") + sector Sector @relation(fields: [sectorCode], references: [code]) + period String // day/week/month + date DateTime + open Float + high Float + low Float + close Float + volume BigInt + + @@unique([sectorCode, period, date]) + @@index([sectorCode]) + @@index([date]) + @@map("sector_klines") +} + +// 股票信息 +model Stock { + id String @id @default(uuid()) + code String @unique + name String + sectorCode String? @map("sector_code") + sector Sector? @relation(fields: [sectorCode], references: [code]) + marketCap BigInt? @map("market_cap") + pe Float? + pb Float? + quotes StockQuote[] + klines StockKLine[] + favorites UserFavorite[] + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + + @@index([code]) + @@index([sectorCode]) + @@map("stocks") +} + +// 股票行情 +model StockQuote { + id Int @id @default(autoincrement()) + stockCode String @map("stock_code") + stock Stock @relation(fields: [stockCode], references: [code]) + price Float + open Float + high Float + low Float + preClose Float @map("pre_close") + volume BigInt + turnover BigInt + changePercent Float @map("change_percent") + turnoverRate Float? @map("turnover_rate") + amplitude Float? + quoteTime DateTime @map("quote_time") + + @@index([stockCode]) + @@index([quoteTime]) + @@map("stock_quotes") +} + +// 股票K线数据 +model StockKLine { + id Int @id @default(autoincrement()) + stockCode String @map("stock_code") + stock Stock @relation(fields: [stockCode], references: [code]) + period String // day/week/month + date DateTime + open Float + high Float + low Float + close Float + volume BigInt + ma5 Float? + ma10 Float? + ma20 Float? + ma30 Float? + ma60 Float? + + @@unique([stockCode, period, date]) + @@index([stockCode]) + @@index([date]) + @@map("stock_klines") +} + +// 用户 +model User { + id String @id @default(uuid()) + username String @unique + email String @unique + password String + favorites UserFavorite[] + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + @@map("users") +} + +// 用户自选股 +model UserFavorite { + id Int @id @default(autoincrement()) + userId String @map("user_id") + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + stockCode String @map("stock_code") + stock Stock @relation(fields: [stockCode], references: [code]) + createdAt DateTime @default(now()) @map("created_at") + + @@unique([userId, stockCode]) + @@index([userId]) + @@map("user_favorites") +} + +// 新高新低股票记录 +model HighLowStock { + id Int @id @default(autoincrement()) + stockCode String @map("stock_code") + type String // high/low + price Float + date DateTime + daysToHighLow Int @map("days_to_highlow") + createdAt DateTime @default(now()) @map("created_at") + + @@index([stockCode]) + @@index([type]) + @@index([date]) + @@map("high_low_stocks") +} + +// 动量股票推荐 +model MomentumStock { + id Int @id @default(autoincrement()) + stockCode String @map("stock_code") + momentumScore Float @map("momentum_score") + tags String? // JSON array + volumeRatio Float @map("volume_ratio") + breakThrough Boolean @default(false) @map("break_through") + date DateTime + createdAt DateTime @default(now()) @map("created_at") + + @@index([stockCode]) + @@index([date]) + @@map("momentum_stocks") +} diff --git a/app/backend/prisma/seed.ts b/app/backend/prisma/seed.ts new file mode 100644 index 0000000..776c3b4 --- /dev/null +++ b/app/backend/prisma/seed.ts @@ -0,0 +1,120 @@ +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +async function main() { + console.log('Start seeding...'); + + // 创建版块数据 + const sectors = [ + { name: '半导体', code: '880491' }, + { name: '新能源', code: '880952' }, + { name: '医药生物', code: '880122' }, + { name: '白酒', code: '880381' }, + { name: '银行', code: '880471' }, + { name: '证券', code: '880472' }, + { name: '保险', code: '880473' }, + { name: '房地产', code: '880482' }, + { name: '汽车', code: '880391' }, + { name: '电子', code: '880494' }, + { name: '计算机', code: '880952' }, + { name: '通信', code: '880495' }, + { name: '传媒', code: '880952' }, + { name: '军工', code: '880954' }, + { name: '有色金属', code: '880324' }, + { name: '钢铁', code: '880318' }, + { name: '煤炭', code: '880952' }, + { name: '化工', code: '880336' }, + { name: '建筑材料', code: '880344' }, + { name: '机械设备', code: '880952' }, + ]; + + for (const sector of sectors) { + await prisma.sector.upsert({ + where: { code: sector.code }, + update: {}, + create: sector, + }); + } + console.log(`Created ${sectors.length} sectors`); + + // 创建市场指数 + const indices = [ + { name: '上证指数', code: '000001', current: 3050.32, change: 15.23, changePercent: 0.5, volume: BigInt(450000000), turnover: BigInt(4200000000), sortOrder: 1 }, + { name: '深证成指', code: '399001', current: 9850.15, change: -25.6, changePercent: -0.26, volume: BigInt(520000000), turnover: BigInt(5100000000), sortOrder: 2 }, + { name: '创业板指', code: '399006', current: 1950.45, change: 8.75, changePercent: 0.45, volume: BigInt(180000000), turnover: BigInt(2100000000), sortOrder: 3 }, + { name: '科创50', code: '000688', current: 850.32, change: -5.23, changePercent: -0.61, volume: BigInt(65000000), turnover: BigInt(950000000), sortOrder: 4 }, + ]; + + for (const index of indices) { + await prisma.marketIndex.upsert({ + where: { code: index.code }, + update: {}, + create: index, + }); + } + console.log(`Created ${indices.length} market indices`); + + // 创建示例股票数据 + const stocks = [ + { code: '000001', name: '平安银行', sectorCode: '880471' }, + { code: '000002', name: '万科A', sectorCode: '880482' }, + { code: '000063', name: '中兴通讯', sectorCode: '880495' }, + { code: '000100', name: 'TCL科技', sectorCode: '880494' }, + { code: '000333', name: '美的集团', sectorCode: '880952' }, + { code: '000568', name: '泸州老窖', sectorCode: '880381' }, + { code: '000651', name: '格力电器', sectorCode: '880952' }, + { code: '000725', name: '京东方A', sectorCode: '880494' }, + { code: '000768', name: '中航西飞', sectorCode: '880954' }, + { code: '000858', name: '五粮液', sectorCode: '880381' }, + { code: '600000', name: '浦发银行', sectorCode: '880471' }, + { code: '600009', name: '上海机场', sectorCode: '880952' }, + { code: '600016', name: '民生银行', sectorCode: '880471' }, + { code: '600028', name: '中国石化', sectorCode: '880952' }, + { code: '600030', name: '中信证券', sectorCode: '880472' }, + { code: '600031', name: '三一重工', sectorCode: '880952' }, + { code: '600036', name: '招商银行', sectorCode: '880471' }, + { code: '600048', name: '保利发展', sectorCode: '880482' }, + { code: '600050', name: '中国联通', sectorCode: '880495' }, + { code: '600104', name: '上汽集团', sectorCode: '880391' }, + { code: '600196', name: '复星医药', sectorCode: '880122' }, + { code: '600276', name: '恒瑞医药', sectorCode: '880122' }, + { code: '600309', name: '万华化学', sectorCode: '880336' }, + { code: '600519', name: '贵州茅台', sectorCode: '880381' }, + { code: '600900', name: '长江电力', sectorCode: '880952' }, + { code: '601012', name: '隆基绿能', sectorCode: '880952' }, + { code: '601088', name: '中国神华', sectorCode: '880952' }, + { code: '601166', name: '兴业银行', sectorCode: '880471' }, + { code: '601288', name: '农业银行', sectorCode: '880471' }, + { code: '601318', name: '中国平安', sectorCode: '880473' }, + { code: '601398', name: '工商银行', sectorCode: '880471' }, + { code: '601888', name: '中国中免', sectorCode: '880952' }, + { code: '603288', name: '海天味业', sectorCode: '880952' }, + { code: '688981', name: '中芯国际', sectorCode: '880491' }, + ]; + + for (const stock of stocks) { + await prisma.stock.upsert({ + where: { code: stock.code }, + update: {}, + create: { + ...stock, + marketCap: BigInt(Math.floor(Math.random() * 1000000000000) + 10000000000), + pe: Number((Math.random() * 50 + 5).toFixed(2)), + pb: Number((Math.random() * 10 + 0.5).toFixed(2)), + }, + }); + } + console.log(`Created ${stocks.length} stocks`); + + console.log('Seeding finished'); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/app/backend/src/app.ts b/app/backend/src/app.ts new file mode 100644 index 0000000..71d21b5 --- /dev/null +++ b/app/backend/src/app.ts @@ -0,0 +1,139 @@ +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import { createServer } from 'http'; +import config from './config'; +import { connectDatabase } from './config/database'; +import redis from './config/redis'; +import routes from './routes'; +import { errorHandler, notFoundHandler } from './middleware/errorHandler'; +import { requestLogger } from './middleware/logger'; +import { generalLimiter } from './middleware/rateLimiter'; +import StockSocket from './websocket/stockSocket'; +import { marketDataSyncJob } from './jobs/syncMarketData'; +import { dataSyncService } from './services/dataSyncService'; +import logger from './utils/logger'; + +// 创建 Express 应用 +const app = express(); +const server = createServer(app); + +// WebSocket 服务 +let stockSocket: StockSocket | null = null; + +// 中间件 +app.use(helmet({ + contentSecurityPolicy: false, // 开发环境禁用CSP +})); +app.use(cors({ + origin: '*', + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'], +})); +app.use(express.json({ limit: '10mb' })); +app.use(express.urlencoded({ extended: true, limit: '10mb' })); + +// 请求日志 +app.use(requestLogger); + +// 限流 +app.use(generalLimiter); + +// API 路由 +app.use('/api/v1', routes); + +// 错误处理 +app.use(notFoundHandler); +app.use(errorHandler); + +// 启动服务器 +async function startServer(): Promise { + try { + // 连接数据库 + await connectDatabase(); + logger.info('Database connected'); + + // 测试 Redis 连接(可选,连接失败不影响启动) + try { + await redis.ping(); + logger.info('Redis connected'); + } catch (error) { + logger.warn('Redis not available, using memory cache fallback'); + } + + // 初始化基础数据 + await dataSyncService.initBaseData(); + + // 启动 WebSocket 服务 + stockSocket = new StockSocket(server); + logger.info('WebSocket server started'); + + // 启动定时任务 + marketDataSyncJob.start(); + + // 启动 HTTP 服务器 + server.listen(config.port, () => { + logger.info(`Server is running on port ${config.port}`); + logger.info(`Environment: ${config.nodeEnv}`); + logger.info(`API URL: http://localhost:${config.port}/api/v1`); + }); + } catch (error) { + logger.error('Failed to start server:', error); + process.exit(1); + } +} + +// 优雅关闭 +async function gracefulShutdown(): Promise { + logger.info('Shutting down server...'); + + // 停止定时任务 + marketDataSyncJob.stop(); + + // 关闭 WebSocket + if (stockSocket) { + // 通知所有客户端 + // stockSocket.close(); + } + + // 关闭 HTTP 服务器 + server.close(() => { + logger.info('HTTP server closed'); + }); + + // 关闭 Redis 连接(如果已连接) + try { + await redis.quit(); + logger.info('Redis connection closed'); + } catch (error) { + // Redis 未连接,忽略错误 + } + + // 断开数据库连接 + const { disconnectDatabase } = await import('./config/database'); + await disconnectDatabase(); + logger.info('Database connection closed'); + + logger.info('Server shutdown complete'); + process.exit(0); +} + +// 进程信号处理 +process.on('SIGTERM', gracefulShutdown); +process.on('SIGINT', gracefulShutdown); + +// 未捕获的异常 +process.on('uncaughtException', (error) => { + logger.error('Uncaught Exception:', error); + gracefulShutdown(); +}); + +// 未处理的 Promise 拒绝 +process.on('unhandledRejection', (reason, promise) => { + logger.error('Unhandled Rejection at:', promise, 'reason:', reason); +}); + +// 启动服务 +startServer(); + +export default app; diff --git a/app/backend/src/config/database.ts b/app/backend/src/config/database.ts new file mode 100644 index 0000000..c9ab407 --- /dev/null +++ b/app/backend/src/config/database.ts @@ -0,0 +1,52 @@ +import { PrismaClient } from '@prisma/client'; +import logger from '../utils/logger'; + +// Prisma 客户端实例 +const prisma = new PrismaClient({ + log: [ + { emit: 'event', level: 'query' }, + { emit: 'event', level: 'error' }, + { emit: 'event', level: 'info' }, + { emit: 'event', level: 'warn' }, + ], +}); + +// 查询日志 +prisma.$on('query', (e: any) => { + logger.debug('Prisma Query', { + query: e.query, + params: e.params, + duration: `${e.duration}ms`, + }); +}); + +// 错误日志 +prisma.$on('error', (e: any) => { + logger.error('Prisma Error', { + message: e.message, + }); +}); + +// 连接数据库 +export async function connectDatabase(): Promise { + try { + await prisma.$connect(); + logger.info('Database connected successfully'); + } catch (error) { + logger.error('Database connection failed:', error); + throw error; + } +} + +// 断开数据库连接 +export async function disconnectDatabase(): Promise { + try { + await prisma.$disconnect(); + logger.info('Database disconnected'); + } catch (error) { + logger.error('Database disconnect error:', error); + throw error; + } +} + +export default prisma; diff --git a/app/backend/src/config/index.ts b/app/backend/src/config/index.ts new file mode 100644 index 0000000..569bbf7 --- /dev/null +++ b/app/backend/src/config/index.ts @@ -0,0 +1,51 @@ +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.resolve(__dirname, '../../.env') }); + +export const config = { + // 服务器配置 + port: parseInt(process.env.PORT || '3000', 10), + nodeEnv: process.env.NODE_ENV || 'development', + + // 数据库配置 + databaseUrl: process.env.DATABASE_URL || 'mysql://root:root@localhost:3306/aguzhitou', + + // Redis配置 + redisUrl: process.env.REDIS_URL || 'redis://localhost:6379', + + // JWT配置 + jwtSecret: process.env.JWT_SECRET || 'default-secret-key', + jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d', + + // AKShare配置 + akshareUrl: process.env.AKSHARE_URL || 'http://localhost:8000', + + // 日志配置 + logLevel: process.env.LOG_LEVEL || 'info', + logDir: process.env.LOG_DIR || './logs', + + // 限流配置 + rateLimitWindowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '60000', 10), + rateLimitMaxRequests: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100', 10), + + // 缓存过期时间(秒) + cacheTtl: { + marketIndices: 60, + upDownStats: 60, + priceDistribution: 60, + sectors: 60, + sectorDetail: 300, + stockDetail: 60, + klineData: 300, + searchResults: 300, + }, + + // 交易时间配置 + tradingHours: { + morning: { start: '09:30', end: '11:30' }, + afternoon: { start: '13:00', end: '15:00' }, + }, +}; + +export default config; diff --git a/app/backend/src/config/redis.ts b/app/backend/src/config/redis.ts new file mode 100644 index 0000000..e34f9c8 --- /dev/null +++ b/app/backend/src/config/redis.ts @@ -0,0 +1,203 @@ +import Redis from 'ioredis'; +import config from './index'; +import logger from '../utils/logger'; + +// 内存缓存降级(当Redis不可用时使用) +const memoryCache: Map = new Map(); + +// 清理过期的内存缓存 +setInterval(() => { + const now = Date.now(); + for (const [key, item] of memoryCache.entries()) { + if (item.expiry < now) { + memoryCache.delete(key); + } + } +}, 60000); // 每分钟清理一次 + +// 创建 Redis 客户端 +let redis: Redis | null = null; +let redisAvailable = false; + +try { + redis = new Redis(config.redisUrl, { + retryStrategy: (times) => { + if (times > 3) { + logger.warn('Redis connection failed after 3 retries, using memory cache fallback'); + redisAvailable = false; + return null; // 停止重试 + } + return Math.min(times * 100, 3000); + }, + maxRetriesPerRequest: 3, + connectTimeout: 5000, + lazyConnect: true, // 延迟连接,避免启动时阻塞 + }); + + // 连接事件 + redis.on('connect', () => { + logger.info('Redis connected'); + redisAvailable = true; + }); + + redis.on('ready', () => { + logger.info('Redis ready'); + redisAvailable = true; + }); + + redis.on('error', (error) => { + logger.error('Redis error:', error.message); + redisAvailable = false; + }); + + redis.on('close', () => { + logger.warn('Redis connection closed'); + redisAvailable = false; + }); + +} catch (error) { + logger.warn('Redis initialization failed, using memory cache fallback'); + redisAvailable = false; +} + +// 缓存工具函数(自动降级到内存缓存) +export const cache = { + // 获取缓存 + async get(key: string): Promise { + // 优先使用Redis + if (redisAvailable && redis) { + try { + const data = await redis.get(key); + return data ? JSON.parse(data) : null; + } catch (error) { + logger.debug('Redis get failed, fallback to memory cache'); + } + } + + // 降级到内存缓存 + const item = memoryCache.get(key); + if (item && item.expiry > Date.now()) { + return item.value; + } + memoryCache.delete(key); + return null; + }, + + // 设置缓存 + async set(key: string, value: any, ttl?: number): Promise { + // 尝试使用Redis + if (redisAvailable && redis) { + try { + const data = JSON.stringify(value); + if (ttl) { + await redis.setex(key, ttl, data); + } else { + await redis.set(key, data); + } + return; + } catch (error) { + logger.debug('Redis set failed, fallback to memory cache'); + } + } + + // 降级到内存缓存 + const expiry = ttl ? Date.now() + ttl * 1000 : Date.now() + 3600000; // 默认1小时 + memoryCache.set(key, { value, expiry }); + }, + + // 删除缓存 + async del(key: string): Promise { + memoryCache.delete(key); + + if (redisAvailable && redis) { + try { + await redis.del(key); + } catch (error) { + // 忽略错误 + } + } + }, + + // 删除匹配模式的缓存 + async delPattern(pattern: string): Promise { + // 清理内存缓存中匹配的key + for (const key of memoryCache.keys()) { + if (key.match(pattern.replace('*', '.*'))) { + memoryCache.delete(key); + } + } + + if (redisAvailable && redis) { + try { + const keys = await redis.keys(pattern); + if (keys.length > 0) { + await redis.del(...keys); + } + } catch (error) { + // 忽略错误 + } + } + }, + + // 检查是否存在 + async exists(key: string): Promise { + const item = memoryCache.get(key); + if (item && item.expiry > Date.now()) { + return true; + } + memoryCache.delete(key); + + if (redisAvailable && redis) { + try { + const result = await redis.exists(key); + return result === 1; + } catch (error) { + return false; + } + } + return false; + }, + + // 递增 + async incr(key: string): Promise { + if (redisAvailable && redis) { + try { + return await redis.incr(key); + } catch (error) { + // 降级处理 + } + } + + // 内存缓存实现 + const current = memoryCache.get(key); + const value = current ? parseInt(current.value) + 1 : 1; + memoryCache.set(key, { value: String(value), expiry: Date.now() + 3600000 }); + return value; + }, + + // 设置过期时间 + async expire(key: string, seconds: number): Promise { + const item = memoryCache.get(key); + if (item) { + item.expiry = Date.now() + seconds * 1000; + } + + if (redisAvailable && redis) { + try { + await redis.expire(key, seconds); + } catch (error) { + // 忽略错误 + } + } + }, + + // 获取缓存状态 + status(): { redis: boolean; memorySize: number } { + return { + redis: redisAvailable, + memorySize: memoryCache.size, + }; + }, +}; + +export default redis || ({} as Redis); diff --git a/app/backend/src/controllers/marketController.ts b/app/backend/src/controllers/marketController.ts new file mode 100644 index 0000000..c8d7d29 --- /dev/null +++ b/app/backend/src/controllers/marketController.ts @@ -0,0 +1,45 @@ +import { Request, Response } from 'express'; +import { marketService } from '../services/marketService'; +import { asyncHandler } from '../middleware/errorHandler'; +import { ApiResponse } from '../types'; + +export const marketController = { + // 获取市场指数 + getMarketIndices: asyncHandler(async (_req: Request, res: Response) => { + const indices = await marketService.getMarketIndices(); + + const response: ApiResponse = { + code: 200, + message: 'success', + data: indices, + }; + + res.json(response); + }), + + // 获取涨跌家数统计 + getUpDownStats: asyncHandler(async (_req: Request, res: Response) => { + const stats = await marketService.getUpDownStats(); + + const response: ApiResponse = { + code: 200, + message: 'success', + data: stats, + }; + + res.json(response); + }), + + // 获取涨跌幅分布 + getPriceDistribution: asyncHandler(async (_req: Request, res: Response) => { + const distribution = await marketService.getPriceDistribution(); + + const response: ApiResponse = { + code: 200, + message: 'success', + data: distribution, + }; + + res.json(response); + }), +}; diff --git a/app/backend/src/controllers/sectorController.ts b/app/backend/src/controllers/sectorController.ts new file mode 100644 index 0000000..bf81546 --- /dev/null +++ b/app/backend/src/controllers/sectorController.ts @@ -0,0 +1,131 @@ +import { Request, Response } from 'express'; +import { sectorService } from '../services/sectorService'; +import { asyncHandler, NotFoundError } from '../middleware/errorHandler'; +import { ApiResponse } from '../types'; + +export const sectorController = { + // 获取版块列表 + getSectors: asyncHandler(async (req: Request, res: Response) => { + const { sort, order } = req.query; + + let sectors = await sectorService.getSectorsWithMomentum(); + + // 排序处理 + if (sort) { + const sortField = sort as string; + const sortOrder = order === 'asc' ? 1 : -1; + + sectors.sort((a, b) => { + const aVal = a[sortField as keyof typeof a] || 0; + const bVal = b[sortField as keyof typeof b] || 0; + return (aVal > bVal ? 1 : -1) * sortOrder; + }); + } + + const response: ApiResponse = { + code: 200, + message: 'success', + data: sectors, + }; + + res.json(response); + }), + + // 获取版块详情 + getSectorDetail: asyncHandler(async (req: Request, res: Response) => { + const { sector_code } = req.params; + + const sector = await sectorService.getSectorDetail(sector_code); + + if (!sector) { + throw new NotFoundError('版块不存在'); + } + + const response: ApiResponse = { + code: 200, + message: 'success', + data: sector, + }; + + res.json(response); + }), + + // 获取版块历史排名 + getSectorRankHistory: asyncHandler(async (req: Request, res: Response) => { + const { sector_code } = req.params; + const days = parseInt(req.query.days as string) || 30; + + const history = await sectorService.getSectorRankHistory(sector_code, days); + + const response: ApiResponse = { + code: 200, + message: 'success', + data: history, + }; + + res.json(response); + }), + + // 获取版块内股票 + getSectorStocks: asyncHandler(async (req: Request, res: Response) => { + const { sector_code } = req.params; + const { sort, limit } = req.query; + + let stocks = await sectorService.getSectorStocks( + sector_code, + parseInt(limit as string) || 20 + ); + + // 排序处理 + if (sort) { + const sortField = sort as string; + stocks.sort((a, b) => { + const aVal = a[sortField as keyof typeof a] || 0; + const bVal = b[sortField as keyof typeof b] || 0; + return bVal > aVal ? 1 : -1; + }); + } + + const response: ApiResponse = { + code: 200, + message: 'success', + data: stocks, + }; + + res.json(response); + }), + + // 获取版块内动量股票 + getSectorMomentumStocks: asyncHandler(async (req: Request, res: Response) => { + const { sector_code } = req.params; + + const stocks = await sectorService.getSectorMomentumStocks(sector_code); + + const response: ApiResponse = { + code: 200, + message: 'success', + data: stocks, + }; + + res.json(response); + }), + + // 获取版块K线数据 + getSectorKLine: asyncHandler(async (req: Request, res: Response) => { + const { sector_code } = req.params; + const period = (req.query.period as string) || 'day'; + const days = parseInt(req.query.days as string) || 60; + + // 这里需要从服务获取K线数据 + // 暂时返回空数组 + const klines: any[] = []; + + const response: ApiResponse = { + code: 200, + message: 'success', + data: klines, + }; + + res.json(response); + }), +}; diff --git a/app/backend/src/controllers/stockController.ts b/app/backend/src/controllers/stockController.ts new file mode 100644 index 0000000..ec39db9 --- /dev/null +++ b/app/backend/src/controllers/stockController.ts @@ -0,0 +1,123 @@ +import { Request, Response } from 'express'; +import { stockService } from '../services/stockService'; +import { asyncHandler, NotFoundError, BadRequestError } from '../middleware/errorHandler'; +import { ApiResponse } from '../types'; +import { searchKeywordSchema, periodSchema } from '../utils/validator'; + +export const stockController = { + // 搜索股票 + search: asyncHandler(async (req: Request, res: Response) => { + const { keyword, type } = req.query; + + if (!keyword || typeof keyword !== 'string') { + throw new BadRequestError('搜索关键词不能为空'); + } + + const validatedKeyword = searchKeywordSchema.parse(keyword); + + let sectors: any[] = []; + let stocks: any[] = []; + + if (!type || type === 'all' || type === 'sector') { + sectors = await stockService.searchSectors(validatedKeyword); + } + + if (!type || type === 'all' || type === 'stock') { + stocks = await stockService.searchStocks(validatedKeyword); + } + + const response: ApiResponse = { + code: 200, + message: 'success', + data: { + sectors, + stocks, + }, + }; + + res.json(response); + }), + + // 获取股票详情 + getStockDetail: asyncHandler(async (req: Request, res: Response) => { + const { stock_code } = req.params; + + const stock = await stockService.getStockDetail(stock_code); + + if (!stock) { + throw new NotFoundError('股票不存在'); + } + + const response: ApiResponse = { + code: 200, + message: 'success', + data: stock, + }; + + res.json(response); + }), + + // 获取股票K线数据 + getStockKLine: asyncHandler(async (req: Request, res: Response) => { + const { stock_code } = req.params; + const period = periodSchema.parse(req.query.period || 'day'); + const days = parseInt(req.query.days as string) || 60; + + const klines = await stockService.getKLineData(stock_code, period, days); + + const response: ApiResponse = { + code: 200, + message: 'success', + data: klines, + }; + + res.json(response); + }), + + // 获取新高股票 + getNewHighStocks: asyncHandler(async (req: Request, res: Response) => { + const days = parseInt(req.query.days as string) || 20; + const limit = parseInt(req.query.limit as string) || 20; + + const stocks = await stockService.getNewHighStocks(days, limit); + + const response: ApiResponse = { + code: 200, + message: 'success', + data: stocks, + }; + + res.json(response); + }), + + // 获取新低股票 + getNewLowStocks: asyncHandler(async (req: Request, res: Response) => { + const days = parseInt(req.query.days as string) || 20; + const limit = parseInt(req.query.limit as string) || 20; + + const stocks = await stockService.getNewLowStocks(days, limit); + + const response: ApiResponse = { + code: 200, + message: 'success', + data: stocks, + }; + + res.json(response); + }), + + // 获取动量股推荐 + getMomentumRecommendation: asyncHandler(async (req: Request, res: Response) => { + const limit = parseInt(req.query.limit as string) || 15; + + const stocks = await stockService.getMomentumStocks(limit); + + const response: ApiResponse = { + code: 200, + message: 'success', + data: stocks, + }; + + res.json(response); + }), +}; diff --git a/app/backend/src/controllers/userController.ts b/app/backend/src/controllers/userController.ts new file mode 100644 index 0000000..0bec5be --- /dev/null +++ b/app/backend/src/controllers/userController.ts @@ -0,0 +1,253 @@ +import { Request, Response } from 'express'; +import bcrypt from 'bcryptjs'; +import prisma from '../config/database'; +import { generateToken } from '../middleware/auth'; +import { asyncHandler, BadRequestError, UnauthorizedError, NotFoundError } from '../middleware/errorHandler'; +import { ApiResponse, User } from '../types'; +import { userRegisterSchema, userLoginSchema, favoriteStockSchema } from '../utils/validator'; +import logger from '../utils/logger'; + +export const userController = { + // 用户注册 + register: asyncHandler(async (req: Request, res: Response) => { + const data = userRegisterSchema.parse(req.body); + + // 检查用户是否已存在 + const existingUser = await prisma.user.findFirst({ + where: { + OR: [ + { email: data.email }, + { username: data.username }, + ], + }, + }); + + if (existingUser) { + throw new BadRequestError('用户名或邮箱已存在'); + } + + // 加密密码 + const hashedPassword = await bcrypt.hash(data.password, 10); + + // 创建用户 + const user = await prisma.user.create({ + data: { + username: data.username, + email: data.email, + password: hashedPassword, + }, + }); + + // 生成 token + const token = generateToken({ + userId: user.id, + username: user.username, + email: user.email, + }); + + logger.info(`User registered: ${user.username}`); + + const response: ApiResponse = { + code: 200, + message: '注册成功', + data: { + id: user.id, + username: user.username, + email: user.email, + token, + }, + }; + + res.json(response); + }), + + // 用户登录 + login: asyncHandler(async (req: Request, res: Response) => { + const data = userLoginSchema.parse(req.body); + + // 查找用户 + const user = await prisma.user.findUnique({ + where: { email: data.email }, + }); + + if (!user) { + throw new UnauthorizedError('邮箱或密码错误'); + } + + // 验证密码 + const isValid = await bcrypt.compare(data.password, user.password); + + if (!isValid) { + throw new UnauthorizedError('邮箱或密码错误'); + } + + // 生成 token + const token = generateToken({ + userId: user.id, + username: user.username, + email: user.email, + }); + + logger.info(`User logged in: ${user.username}`); + + const response: ApiResponse = { + code: 200, + message: '登录成功', + data: { + id: user.id, + username: user.username, + email: user.email, + token, + }, + }; + + res.json(response); + }), + + // 获取用户信息 + getProfile: asyncHandler(async (req: Request, res: Response) => { + const userId = req.user!.id; + + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { + id: true, + username: true, + email: true, + createdAt: true, + }, + }); + + if (!user) { + throw new NotFoundError('用户不存在'); + } + + const response: ApiResponse = { + code: 200, + message: 'success', + data: user, + }; + + res.json(response); + }), + + // 获取自选股 + getFavorites: asyncHandler(async (req: Request, res: Response) => { + const userId = req.user!.id; + + const favorites = await prisma.userFavorite.findMany({ + where: { userId }, + include: { + stock: { + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1, + }, + sector: true, + }, + }, + }, + }); + + const data = favorites.map((f) => ({ + code: f.stock.code, + name: f.stock.name, + price: f.stock.quotes[0]?.price || 0, + changePercent: f.stock.quotes[0]?.changePercent || 0, + industry: f.stock.sector?.name, + })); + + const response: ApiResponse = { + code: 200, + message: 'success', + data, + }; + + res.json(response); + }), + + // 添加自选股 + addFavorite: asyncHandler(async (req: Request, res: Response) => { + const userId = req.user!.id; + const { stockCode } = favoriteStockSchema.parse(req.body); + + // 检查股票是否存在 + const stock = await prisma.stock.findUnique({ + where: { code: stockCode }, + }); + + if (!stock) { + throw new NotFoundError('股票不存在'); + } + + // 检查是否已添加 + const existing = await prisma.userFavorite.findUnique({ + where: { + userId_stockCode: { + userId, + stockCode, + }, + }, + }); + + if (existing) { + throw new BadRequestError('该股票已在自选股中'); + } + + await prisma.userFavorite.create({ + data: { + userId, + stockCode, + }, + }); + + logger.info(`User ${req.user!.username} added favorite: ${stockCode}`); + + const response: ApiResponse = { + code: 200, + message: '添加成功', + data: null, + }; + + res.json(response); + }), + + // 删除自选股 + removeFavorite: asyncHandler(async (req: Request, res: Response) => { + const userId = req.user!.id; + const { stock_code } = req.params; + + const favorite = await prisma.userFavorite.findUnique({ + where: { + userId_stockCode: { + userId, + stockCode: stock_code, + }, + }, + }); + + if (!favorite) { + throw new NotFoundError('自选股不存在'); + } + + await prisma.userFavorite.delete({ + where: { + userId_stockCode: { + userId, + stockCode: stock_code, + }, + }, + }); + + logger.info(`User ${req.user!.username} removed favorite: ${stock_code}`); + + const response: ApiResponse = { + code: 200, + message: '删除成功', + data: null, + }; + + res.json(response); + }), +}; diff --git a/app/backend/src/jobs/syncMarketData.ts b/app/backend/src/jobs/syncMarketData.ts new file mode 100644 index 0000000..9061881 --- /dev/null +++ b/app/backend/src/jobs/syncMarketData.ts @@ -0,0 +1,162 @@ +import cron from 'node-cron'; +import { dataSyncService } from '../services/dataSyncService'; +import { sectorService } from '../services/sectorService'; +import logger from '../utils/logger'; + +// 检查是否为交易时间 +function isTradingTime(): boolean { + const now = new Date(); + const hour = now.getHours(); + const minute = now.getMinutes(); + const currentTime = hour * 60 + minute; + + // 上午 9:30 - 11:30 + const morningStart = 9 * 60 + 30; + const morningEnd = 11 * 60 + 30; + + // 下午 13:00 - 15:00 + const afternoonStart = 13 * 60; + const afternoonEnd = 15 * 60; + + return ( + (currentTime >= morningStart && currentTime <= morningEnd) || + (currentTime >= afternoonStart && currentTime <= afternoonEnd) + ); +} + +// 检查是否为交易日(简化版,实际应该排除节假日) +function isTradingDay(): boolean { + const day = new Date().getDay(); + return day >= 1 && day <= 5; // 周一到周五 +} + +// 定时任务类 +export class MarketDataSyncJob { + private tasks: cron.ScheduledTask[] = []; + + // 启动所有定时任务 + start(): void { + logger.info('Starting market data sync jobs...'); + + // 1. 每3秒同步实时行情(交易时间) + const realTimeTask = cron.schedule('*/3 * * * * *', async () => { + if (isTradingTime() && isTradingDay()) { + try { + await dataSyncService.syncRealTimeQuotes(); + } catch (error) { + logger.error('Real-time sync failed:', error); + } + } + }); + this.tasks.push(realTimeTask); + + // 2. 每分钟同步版块行情 + const sectorTask = cron.schedule('* * * * *', async () => { + if (isTradingTime() && isTradingDay()) { + try { + await dataSyncService.syncSectorQuotes(); + } catch (error) { + logger.error('Sector sync failed:', error); + } + } + }); + this.tasks.push(sectorTask); + + // 3. 每小时同步一次市场指数 + const indexTask = cron.schedule('0 * * * *', async () => { + if (isTradingDay()) { + try { + await dataSyncService.syncMarketIndices(); + } catch (error) { + logger.error('Market index sync failed:', error); + } + } + }); + this.tasks.push(indexTask); + + // 4. 每小时同步K线数据 + const klineTask = cron.schedule('30 * * * *', async () => { + if (isTradingDay()) { + try { + // 获取热门股票列表进行同步 + const stocks = await this.getHotStocks(); + for (const stock of stocks.slice(0, 50)) { + try { + await dataSyncService.syncKLineData(stock, 'day'); + await new Promise((resolve) => setTimeout(resolve, 200)); // 延迟避免请求过快 + } catch (error) { + logger.error(`K-line sync failed for ${stock}:`, error); + } + } + } catch (error) { + logger.error('K-line sync job failed:', error); + } + } + }); + this.tasks.push(klineTask); + + // 5. 每日收盘后计算版块排名(15:05) + const rankingTask = cron.schedule('5 15 * * 1-5', async () => { + try { + await sectorService.updateSectorRankings(); + logger.info('Sector ranking updated'); + } catch (error) { + logger.error('Sector ranking update failed:', error); + } + }); + this.tasks.push(rankingTask); + + // 6. 每日收盘后全量同步(15:10) + const dailyTask = cron.schedule('10 15 * * 1-5', async () => { + try { + logger.info('Starting daily full sync...'); + await dataSyncService.syncRealTimeQuotes(); + await dataSyncService.syncSectorQuotes(); + await dataSyncService.syncMarketIndices(); + logger.info('Daily full sync completed'); + } catch (error) { + logger.error('Daily full sync failed:', error); + } + }); + this.tasks.push(dailyTask); + + logger.info('Market data sync jobs started'); + } + + // 停止所有定时任务 + stop(): void { + logger.info('Stopping market data sync jobs...'); + this.tasks.forEach((task) => task.stop()); + this.tasks = []; + logger.info('Market data sync jobs stopped'); + } + + // 获取热门股票列表(这里简化处理,实际应该基于成交量等指标) + private async getHotStocks(): Promise { + // 返回一些常见的股票代码 + return [ + '000001', '000002', '000063', '000100', '000333', + '000568', '000651', '000725', '000768', '000858', + '000895', '002001', '002007', '002024', '002027', + '002142', '002230', '002236', '002304', '002352', + '002415', '002460', '002594', '300003', '300014', + '300015', '300033', '300059', '300122', '300124', + '300274', '300408', '300413', '300433', '300498', + '300750', '600000', '600009', '600016', '600028', + '600030', '600031', '600036', '600048', '600050', + '600104', '600196', '600276', '600309', '600406', + '600436', '600438', '600519', '600547', '600570', + '600585', '600588', '600600', '600690', '600703', + '600745', '600809', '600837', '600887', '600893', + '600900', '601012', '601066', '601088', '601111', + '601138', '601166', '601186', '601288', '601318', + '601398', '601601', '601628', '601668', '601688', + '601766', '601788', '601857', '601888', '601899', + '601919', '601933', '601988', '601995', '603288', + '603501', '603659', '603799', '603986', '688008', + '688009', '688012', '688036', '688111', '688981', + ]; + } +} + +export const marketDataSyncJob = new MarketDataSyncJob(); diff --git a/app/backend/src/middleware/auth.ts b/app/backend/src/middleware/auth.ts new file mode 100644 index 0000000..295fe8b --- /dev/null +++ b/app/backend/src/middleware/auth.ts @@ -0,0 +1,132 @@ +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import config from '../config'; +import { UnauthorizedError } from './errorHandler'; +import logger from '../utils/logger'; + +// 扩展 Express Request 类型 +declare global { + namespace Express { + interface Request { + user?: { + id: string; + username: string; + email: string; + }; + } + } +} + +// JWT Payload 类型 +interface JWTPayload { + userId: string; + username: string; + email: string; + iat: number; + exp: number; +} + +// 验证 JWT Token +export function verifyToken(token: string): JWTPayload { + return jwt.verify(token, config.jwtSecret) as JWTPayload; +} + +// 生成 JWT Token +export function generateToken(payload: { userId: string; username: string; email: string }): string { + return jwt.sign(payload, config.jwtSecret, { + expiresIn: config.jwtExpiresIn, + }); +} + +// 认证中间件 +export function authMiddleware(req: Request, res: Response, next: NextFunction): void { + try { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + throw new UnauthorizedError('缺少认证令牌'); + } + + const token = authHeader.substring(7); + + if (!token) { + throw new UnauthorizedError('无效的认证令牌'); + } + + const decoded = verifyToken(token); + + req.user = { + id: decoded.userId, + username: decoded.username, + email: decoded.email, + }; + + next(); + } catch (error) { + if (error instanceof jwt.TokenExpiredError) { + next(new UnauthorizedError('认证令牌已过期')); + } else if (error instanceof jwt.JsonWebTokenError) { + next(new UnauthorizedError('无效的认证令牌')); + } else { + next(error); + } + } +} + +// 可选认证中间件(不强制要求登录) +export function optionalAuthMiddleware(req: Request, res: Response, next: NextFunction): void { + try { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return next(); + } + + const token = authHeader.substring(7); + + if (!token) { + return next(); + } + + const decoded = verifyToken(token); + + req.user = { + id: decoded.userId, + username: decoded.username, + email: decoded.email, + }; + + next(); + } catch (error) { + // 可选认证失败不抛出错误 + next(); + } +} + +// 管理员权限检查中间件 +export function adminMiddleware(req: Request, res: Response, next: NextFunction): void { + if (!req.user) { + next(new UnauthorizedError('请先登录')); + return; + } + + // 这里可以添加管理员权限检查逻辑 + // 例如从数据库查询用户角色 + + next(); +} + +// 记录用户操作日志 +export function logUserAction(action: string) { + return (req: Request, res: Response, next: NextFunction) => { + if (req.user) { + logger.info(`User Action: ${action}`, { + userId: req.user.id, + username: req.user.username, + url: req.url, + method: req.method, + }); + } + next(); + }; +} diff --git a/app/backend/src/middleware/errorHandler.ts b/app/backend/src/middleware/errorHandler.ts new file mode 100644 index 0000000..441a0fb --- /dev/null +++ b/app/backend/src/middleware/errorHandler.ts @@ -0,0 +1,141 @@ +import { Request, Response, NextFunction } from 'express'; +import { ZodError } from 'zod'; +import logger from '../utils/logger'; + +// 自定义错误类 +export class AppError extends Error { + public statusCode: number; + public isOperational: boolean; + + constructor(message: string, statusCode: number, isOperational = true) { + super(message); + this.statusCode = statusCode; + this.isOperational = isOperational; + Error.captureStackTrace(this, this.constructor); + } +} + +// 404 错误 +export class NotFoundError extends AppError { + constructor(message: string = '资源不存在') { + super(message, 404); + } +} + +// 400 错误 +export class BadRequestError extends AppError { + constructor(message: string = '请求参数错误') { + super(message, 400); + } +} + +// 401 错误 +export class UnauthorizedError extends AppError { + constructor(message: string = '未授权,请先登录') { + super(message, 401); + } +} + +// 403 错误 +export class ForbiddenError extends AppError { + constructor(message: string = '禁止访问') { + super(message, 403); + } +} + +// 429 错误 +export class TooManyRequestsError extends AppError { + constructor(message: string = '请求过于频繁,请稍后再试') { + super(message, 429); + } +} + +// 错误响应格式 +interface ErrorResponse { + code: number; + message: string; + stack?: string; + errors?: any[]; +} + +// 全局错误处理中间件 +export function errorHandler( + err: Error | AppError | ZodError, + req: Request, + res: Response, + _next: NextFunction +): void { + let statusCode = 500; + let message = '服务器内部错误'; + let errors: any[] | undefined; + + // 处理 Zod 验证错误 + if (err instanceof ZodError) { + statusCode = 400; + message = '请求参数验证失败'; + errors = err.errors.map((e) => ({ + path: e.path.join('.'), + message: e.message, + })); + } + // 处理自定义应用错误 + else if (err instanceof AppError) { + statusCode = err.statusCode; + message = err.message; + } + // 处理其他错误 + else { + message = err.message || message; + } + + // 记录错误日志 + if (statusCode >= 500) { + logger.error('Server Error:', { + error: err.message, + stack: err.stack, + url: req.url, + method: req.method, + ip: req.ip, + }); + } else { + logger.warn('Client Error:', { + statusCode, + message, + url: req.url, + method: req.method, + ip: req.ip, + }); + } + + const response: ErrorResponse = { + code: statusCode, + message, + }; + + // 开发环境显示堆栈 + if (process.env.NODE_ENV === 'development') { + response.stack = err.stack; + } + + // 显示验证错误详情 + if (errors) { + response.errors = errors; + } + + res.status(statusCode).json(response); +} + +// 未找到路由处理 +export function notFoundHandler(req: Request, res: Response): void { + res.status(404).json({ + code: 404, + message: `未找到路由: ${req.method} ${req.url}`, + }); +} + +// 异步路由处理包装器 +export function asyncHandler(fn: (req: Request, res: Response, next: NextFunction) => Promise) { + return (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req, res, next)).catch(next); + }; +} diff --git a/app/backend/src/middleware/logger.ts b/app/backend/src/middleware/logger.ts new file mode 100644 index 0000000..88e3836 --- /dev/null +++ b/app/backend/src/middleware/logger.ts @@ -0,0 +1,51 @@ +import { Request, Response, NextFunction } from 'express'; +import logger from '../utils/logger'; + +// 请求日志中间件 +export function requestLogger(req: Request, res: Response, next: NextFunction): void { + const start = Date.now(); + + res.on('finish', () => { + const duration = Date.now() - start; + const logData = { + method: req.method, + url: req.url, + status: res.statusCode, + duration: `${duration}ms`, + ip: req.ip, + userAgent: req.get('user-agent'), + userId: req.user?.id, + }; + + if (res.statusCode >= 400) { + logger.warn('HTTP Request', logData); + } else { + logger.info('HTTP Request', logData); + } + }); + + next(); +} + +// 慢请求警告中间件 +export function slowRequestLogger(threshold: number = 1000) { + return (req: Request, res: Response, next: NextFunction) => { + const start = Date.now(); + + res.on('finish', () => { + const duration = Date.now() - start; + + if (duration > threshold) { + logger.warn('Slow Request', { + method: req.method, + url: req.url, + duration: `${duration}ms`, + threshold: `${threshold}ms`, + ip: req.ip, + }); + } + }); + + next(); + }; +} diff --git a/app/backend/src/middleware/rateLimiter.ts b/app/backend/src/middleware/rateLimiter.ts new file mode 100644 index 0000000..f6bca4c --- /dev/null +++ b/app/backend/src/middleware/rateLimiter.ts @@ -0,0 +1,103 @@ +import rateLimit from 'express-rate-limit'; +import config from '../config'; +import { TooManyRequestsError } from './errorHandler'; + +// 通用限流配置 +export const generalLimiter = rateLimit({ + windowMs: config.rateLimitWindowMs, + max: config.rateLimitMaxRequests, + standardHeaders: true, + legacyHeaders: false, + handler: (req, res) => { + res.status(429).json({ + code: 429, + message: '请求过于频繁,请稍后再试', + }); + }, +}); + +// 严格限流(用于敏感操作) +export const strictLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15分钟 + max: 10, // 最多10次 + standardHeaders: true, + legacyHeaders: false, + handler: (req, res) => { + res.status(429).json({ + code: 429, + message: '操作过于频繁,请15分钟后再试', + }); + }, +}); + +// 登录限流 +export const loginLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15分钟 + max: 5, // 最多5次登录尝试 + standardHeaders: true, + legacyHeaders: false, + skipSuccessfulRequests: true, // 成功的请求不计数 + handler: (req, res) => { + res.status(429).json({ + code: 429, + message: '登录尝试次数过多,请15分钟后再试', + }); + }, +}); + +// API 限流(按用户ID或IP) +export const apiLimiter = rateLimit({ + windowMs: 60 * 1000, // 1分钟 + max: (req) => { + // 已登录用户限制更宽松 + return req.user ? 1000 : 100; + }, + standardHeaders: true, + legacyHeaders: false, + keyGenerator: (req) => { + // 使用用户ID或IP作为标识 + return req.user?.id || req.ip || 'unknown'; + }, + handler: (req, res) => { + res.status(429).json({ + code: 429, + message: 'API调用次数已达上限,请稍后再试', + }); + }, +}); + +// WebSocket 连接限流(基于IP) +const wsConnections = new Map(); + +export function wsRateLimiter(ip: string): boolean { + const now = Date.now(); + const windowMs = 60 * 1000; // 1分钟 + const maxConnections = 10; // 最多10个连接 + + const record = wsConnections.get(ip); + + if (!record || now > record.resetTime) { + wsConnections.set(ip, { + count: 1, + resetTime: now + windowMs, + }); + return true; + } + + if (record.count >= maxConnections) { + return false; + } + + record.count++; + return true; +} + +// 清理过期的限流记录 +setInterval(() => { + const now = Date.now(); + for (const [ip, record] of wsConnections.entries()) { + if (now > record.resetTime) { + wsConnections.delete(ip); + } + } +}, 60 * 1000); // 每分钟清理一次 diff --git a/app/backend/src/routes/index.ts b/app/backend/src/routes/index.ts new file mode 100644 index 0000000..4e30da7 --- /dev/null +++ b/app/backend/src/routes/index.ts @@ -0,0 +1,33 @@ +import { Router } from 'express'; +import marketRoutes from './marketRoutes'; +import sectorRoutes from './sectorRoutes'; +import stockRoutes from './stockRoutes'; +import userRoutes from './userRoutes'; + +const router = Router(); + +// 市场数据路由 +router.use('/market', marketRoutes); + +// 版块数据路由 +router.use('/sectors', sectorRoutes); + +// 股票数据路由 +router.use('/stocks', stockRoutes); + +// 用户路由 +router.use('/users', userRoutes); + +// 健康检查 +router.get('/health', (_req, res) => { + res.json({ + code: 200, + message: 'success', + data: { + status: 'healthy', + timestamp: new Date().toISOString(), + }, + }); +}); + +export default router; diff --git a/app/backend/src/routes/marketRoutes.ts b/app/backend/src/routes/marketRoutes.ts new file mode 100644 index 0000000..56fadd8 --- /dev/null +++ b/app/backend/src/routes/marketRoutes.ts @@ -0,0 +1,15 @@ +import { Router } from 'express'; +import { marketController } from '../controllers/marketController'; + +const router = Router(); + +// 获取市场指数 +router.get('/indices', marketController.getMarketIndices); + +// 获取涨跌家数统计 +router.get('/updown-stats', marketController.getUpDownStats); + +// 获取涨跌幅分布 +router.get('/price-distribution', marketController.getPriceDistribution); + +export default router; diff --git a/app/backend/src/routes/sectorRoutes.ts b/app/backend/src/routes/sectorRoutes.ts new file mode 100644 index 0000000..98ada14 --- /dev/null +++ b/app/backend/src/routes/sectorRoutes.ts @@ -0,0 +1,24 @@ +import { Router } from 'express'; +import { sectorController } from '../controllers/sectorController'; + +const router = Router(); + +// 获取版块列表 +router.get('/', sectorController.getSectors); + +// 获取版块详情 +router.get('/:sector_code', sectorController.getSectorDetail); + +// 获取版块历史排名 +router.get('/:sector_code/rank-history', sectorController.getSectorRankHistory); + +// 获取版块内股票 +router.get('/:sector_code/stocks', sectorController.getSectorStocks); + +// 获取版块内动量股票 +router.get('/:sector_code/momentum-stocks', sectorController.getSectorMomentumStocks); + +// 获取版块K线数据 +router.get('/:sector_code/kline', sectorController.getSectorKLine); + +export default router; diff --git a/app/backend/src/routes/stockRoutes.ts b/app/backend/src/routes/stockRoutes.ts new file mode 100644 index 0000000..fe96cad --- /dev/null +++ b/app/backend/src/routes/stockRoutes.ts @@ -0,0 +1,24 @@ +import { Router } from 'express'; +import { stockController } from '../controllers/stockController'; + +const router = Router(); + +// 搜索股票 +router.get('/search', stockController.search); + +// 获取新高股票 +router.get('/new-high', stockController.getNewHighStocks); + +// 获取新低股票 +router.get('/new-low', stockController.getNewLowStocks); + +// 获取动量股推荐 +router.get('/momentum-recommendation', stockController.getMomentumRecommendation); + +// 获取股票详情 +router.get('/:stock_code', stockController.getStockDetail); + +// 获取股票K线数据 +router.get('/:stock_code/kline', stockController.getStockKLine); + +export default router; diff --git a/app/backend/src/routes/userRoutes.ts b/app/backend/src/routes/userRoutes.ts new file mode 100644 index 0000000..a72d3bf --- /dev/null +++ b/app/backend/src/routes/userRoutes.ts @@ -0,0 +1,26 @@ +import { Router } from 'express'; +import { userController } from '../controllers/userController'; +import { authMiddleware } from '../middleware/auth'; +import { loginLimiter } from '../middleware/rateLimiter'; + +const router = Router(); + +// 用户注册 +router.post('/register', userController.register); + +// 用户登录(限流) +router.post('/login', loginLimiter, userController.login); + +// 获取用户信息(需要认证) +router.get('/profile', authMiddleware, userController.getProfile); + +// 获取自选股(需要认证) +router.get('/favorites', authMiddleware, userController.getFavorites); + +// 添加自选股(需要认证) +router.post('/favorites', authMiddleware, userController.addFavorite); + +// 删除自选股(需要认证) +router.delete('/favorites/:stock_code', authMiddleware, userController.removeFavorite); + +export default router; diff --git a/app/backend/src/services/dataSyncService.ts b/app/backend/src/services/dataSyncService.ts new file mode 100644 index 0000000..7330532 --- /dev/null +++ b/app/backend/src/services/dataSyncService.ts @@ -0,0 +1,354 @@ +import axios from 'axios'; +import prisma from '../config/database'; +import { cache } from '../config/redis'; +import config from '../config'; +import logger from '../utils/logger'; +import { AKShareStockSpot, AKShareKLine } from '../types'; + +export class DataSyncService { + private akshareBaseUrl: string; + + constructor() { + this.akshareBaseUrl = config.akshareUrl; + } + + // 同步实时行情 + async syncRealTimeQuotes(): Promise { + try { + logger.info('Starting real-time quotes sync...'); + + // 从AKShare获取实时行情 + const response = await axios.get(`${this.akshareBaseUrl}/stock_zh_a_spot`, { + timeout: 30000, + }); + + const quotes: AKShareStockSpot[] = response.data; + + if (!Array.isArray(quotes) || quotes.length === 0) { + logger.warn('No quotes data received from AKShare'); + return; + } + + const now = new Date(); + let successCount = 0; + let failCount = 0; + + // 批量处理 + const batchSize = 100; + for (let i = 0; i < quotes.length; i += batchSize) { + const batch = quotes.slice(i, i + batchSize); + + try { + await prisma.$transaction( + batch.map((quote) => + prisma.stockQuote.create({ + data: { + stockCode: quote.code, + price: quote.price, + open: quote.open, + high: quote.high, + low: quote.low, + preClose: quote.pre_close, + volume: BigInt(quote.volume), + turnover: BigInt(quote.turnover), + changePercent: quote.change_percent, + quoteTime: now, + }, + }) + ) + ); + successCount += batch.length; + } catch (error) { + logger.error(`Failed to sync batch ${i / batchSize + 1}:`, error); + failCount += batch.length; + } + } + + logger.info(`Real-time quotes sync completed: ${successCount} success, ${failCount} failed`); + + // 清除相关缓存 + await cache.delPattern('market:*'); + await cache.delPattern('sectors:*'); + } catch (error) { + logger.error('Failed to sync real-time quotes:', error); + throw error; + } + } + + // 同步个股K线数据 + async syncKLineData(stockCode: string, period: string = 'day'): Promise { + try { + logger.info(`Syncing K-line data for ${stockCode}...`); + + const endDate = new Date().toISOString().split('T')[0].replace(/-/g, ''); + const startDate = this.getStartDate(period); + + const response = await axios.get(`${this.akshareBaseUrl}/stock_zh_a_hist`, { + params: { + symbol: stockCode, + period: period === 'day' ? 'daily' : period === 'week' ? 'weekly' : 'monthly', + start_date: startDate, + end_date: endDate, + }, + timeout: 30000, + }); + + const klines: AKShareKLine[] = response.data; + + if (!Array.isArray(klines) || klines.length === 0) { + logger.warn(`No K-line data received for ${stockCode}`); + return; + } + + // 使用 upsert 批量插入或更新 + for (const k of klines) { + await prisma.stockKLine.upsert({ + where: { + stockCode_period_date: { + stockCode: stockCode, + period: period, + date: new Date(k.date), + }, + }, + update: { + open: k.open, + high: k.high, + low: k.low, + close: k.close, + volume: BigInt(k.volume), + }, + create: { + stockCode: stockCode, + period: period, + date: new Date(k.date), + open: k.open, + high: k.high, + low: k.low, + close: k.close, + volume: BigInt(k.volume), + }, + }); + } + + logger.info(`Synced ${klines.length} K-line records for ${stockCode}`); + + // 清除缓存 + await cache.delPattern(`stock:${stockCode}:kline:${period}:*`); + } catch (error) { + logger.error(`Failed to sync K-line data for ${stockCode}:`, error); + throw error; + } + } + + // 获取起始日期 + private getStartDate(period: string): string { + const now = new Date(); + let months = 6; + + switch (period) { + case 'day': + months = 12; + break; + case 'week': + months = 24; + break; + case 'month': + months = 60; + break; + } + + now.setMonth(now.getMonth() - months); + return now.toISOString().split('T')[0].replace(/-/g, ''); + } + + // 同步版块行情 + async syncSectorQuotes(): Promise { + try { + logger.info('Starting sector quotes sync...'); + + // 获取所有版块 + const sectors = await prisma.sector.findMany(); + + // 模拟版块数据(实际应该从数据源获取) + const now = new Date(); + + for (const sector of sectors) { + try { + // 这里应该从AKShare或其他数据源获取版块数据 + // 暂时使用模拟数据 + const changePercent = Math.random() * 10 - 5; + + await prisma.sectorQuote.create({ + data: { + sectorCode: sector.code, + current: 1000 + Math.random() * 500, + change: changePercent * 10, + changePercent: changePercent, + volume: BigInt(Math.floor(Math.random() * 100000000)), + turnover: BigInt(Math.floor(Math.random() * 1000000000)), + momentumScore: Math.random() * 60 + 30, + quoteTime: now, + }, + }); + } catch (error) { + logger.error(`Failed to sync sector quote for ${sector.code}:`, error); + } + } + + logger.info(`Sector quotes sync completed for ${sectors.length} sectors`); + + // 清除缓存 + await cache.delPattern('sectors:*'); + } catch (error) { + logger.error('Failed to sync sector quotes:', error); + throw error; + } + } + + // 同步市场指数 + async syncMarketIndices(): Promise { + try { + logger.info('Starting market indices sync...'); + + // 从AKShare获取指数数据 + const indices = [ + { name: '上证指数', code: '000001' }, + { name: '深证成指', code: '399001' }, + { name: '创业板指', code: '399006' }, + { name: '科创50', code: '000688' }, + ]; + + for (const index of indices) { + try { + // 这里应该从AKShare获取实际数据 + // 暂时使用模拟数据 + await prisma.marketIndex.upsert({ + where: { code: index.code }, + update: { + current: 3000 + Math.random() * 500, + change: Math.random() * 50 - 25, + changePercent: Math.random() * 2 - 1, + volume: BigInt(Math.floor(Math.random() * 500000000)), + turnover: BigInt(Math.floor(Math.random() * 5000000000)), + }, + create: { + name: index.name, + code: index.code, + current: 3000 + Math.random() * 500, + change: Math.random() * 50 - 25, + changePercent: Math.random() * 2 - 1, + volume: BigInt(Math.floor(Math.random() * 500000000)), + turnover: BigInt(Math.floor(Math.random() * 5000000000)), + sortOrder: indices.indexOf(index), + }, + }); + } catch (error) { + logger.error(`Failed to sync market index ${index.code}:`, error); + } + } + + logger.info('Market indices sync completed'); + + // 清除缓存 + await cache.del('market:indices'); + } catch (error) { + logger.error('Failed to sync market indices:', error); + throw error; + } + } + + // 批量同步所有股票K线数据 + async syncAllStocksKLine(period: string = 'day'): Promise { + try { + logger.info(`Starting batch K-line sync for period: ${period}...`); + + const stocks = await prisma.stock.findMany({ + select: { code: true }, + }); + + let successCount = 0; + let failCount = 0; + + for (const stock of stocks) { + try { + await this.syncKLineData(stock.code, period); + successCount++; + + // 添加延迟避免请求过快 + await this.delay(100); + } catch (error) { + logger.error(`Failed to sync K-line for ${stock.code}:`, error); + failCount++; + } + } + + logger.info(`Batch K-line sync completed: ${successCount} success, ${failCount} failed`); + } catch (error) { + logger.error('Failed to batch sync K-line data:', error); + throw error; + } + } + + // 延迟辅助函数 + private delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + // 初始化基础数据 + async initBaseData(): Promise { + try { + logger.info('Initializing base data...'); + + // 检查是否已有数据 + const sectorCount = await prisma.sector.count(); + + if (sectorCount === 0) { + // 初始化版块数据 + const sectors = [ + { name: '半导体', code: '880491' }, + { name: '新能源', code: '880952' }, + { name: '医药生物', code: '880122' }, + { name: '白酒', code: '880952' }, + { name: '银行', code: '880471' }, + { name: '证券', code: '880472' }, + { name: '保险', code: '880473' }, + { name: '房地产', code: '880482' }, + { name: '汽车', code: '880391' }, + { name: '电子', code: '880494' }, + { name: '计算机', wire: '880952' }, + { name: '通信', code: '880495' }, + { name: '传媒', code: '880952' }, + { name: '军工', code: '880954' }, + { name: '有色金属', code: '880324' }, + { name: '钢铁', code: '880318' }, + { name: '煤炭', code: '880952' }, + { name: '化工', code: '880336' }, + { name: '建筑材料', code: '880344' }, + { name: '机械设备', code: '880952' }, + ]; + + for (const sector of sectors) { + try { + await prisma.sector.create({ + data: sector as any, + }); + } catch (error) { + logger.error(`Failed to create sector ${sector.name}:`, error); + } + } + + logger.info(`Created ${sectors.length} sectors`); + } + + // 初始化市场指数 + await this.syncMarketIndices(); + + logger.info('Base data initialization completed'); + } catch (error) { + logger.error('Failed to initialize base data:', error); + throw error; + } + } +} + +export const dataSyncService = new DataSyncService(); diff --git a/app/backend/src/services/marketService.ts b/app/backend/src/services/marketService.ts new file mode 100644 index 0000000..64bf880 --- /dev/null +++ b/app/backend/src/services/marketService.ts @@ -0,0 +1,176 @@ +import prisma from '../config/database'; +import { cache } from '../config/redis'; +import config from '../config'; +import { MarketIndex, PriceDistribution } from '../types'; +import logger from '../utils/logger'; + +export class MarketService { + // 获取市场指数 + async getMarketIndices(): Promise { + const cacheKey = 'market:indices'; + const cached = await cache.get(cacheKey); + + if (cached) { + return cached; + } + + try { + const indices = await prisma.marketIndex.findMany({ + orderBy: { sortOrder: 'asc' }, + }); + + const result: MarketIndex[] = indices.map((index) => ({ + name: index.name, + code: index.code, + current: index.current, + change: index.change, + changePercent: index.changePercent, + volume: Number(index.volume), + turnover: Number(index.turnover), + })); + + await cache.set(cacheKey, result, config.cacheTtl.marketIndices); + return result; + } catch (error) { + logger.error('Failed to get market indices:', error); + // 返回默认数据 + return this.getDefaultMarketIndices(); + } + } + + // 获取默认市场指数数据 + private getDefaultMarketIndices(): MarketIndex[] { + return [ + { name: '上证指数', code: '000001', current: 3050.32, change: 15.23, changePercent: 0.5, volume: 450000000, turnover: 4200000000 }, + { name: '深证成指', code: '399001', current: 9850.15, change: -25.6, changePercent: -0.26, volume: 520000000, turnover: 5100000000 }, + { name: '创业板指', code: '399006', current: 1950.45, change: 8.75, changePercent: 0.45, volume: 180000000, turnover: 2100000000 }, + { name: '科创50', code: '000688', current: 850.32, change: -5.23, changePercent: -0.61, volume: 65000000, turnover: 950000000 }, + ]; + } + + // 获取涨跌家数统计 + async getUpDownStats(): Promise<{ up: number; down: number; flat: number }> { + const cacheKey = 'market:updown:stats'; + const cached = await cache.get<{ up: number; down: number; flat: number }>(cacheKey); + + if (cached) { + return cached; + } + + try { + const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); + + const stats = await prisma.stockQuote.groupBy({ + by: ['stockCode'], + _max: { + quoteTime: true, + }, + where: { + quoteTime: { + gte: fiveMinutesAgo, + }, + }, + }); + + // 获取最新的行情数据 + const latestQuotes = await Promise.all( + stats.map((s) => + prisma.stockQuote.findFirst({ + where: { + stockCode: s._max.quoteTime, + }, + orderBy: { + quoteTime: 'desc', + }, + }) + ) + ); + + const up = latestQuotes.filter((q) => q && q.changePercent > 0).length; + const down = latestQuotes.filter((q) => q && q.changePercent < 0).length; + const flat = latestQuotes.filter((q) => q && q.changePercent === 0).length; + + const result = { up, down, flat }; + await cache.set(cacheKey, result, config.cacheTtl.upDownStats); + return result; + } catch (error) { + logger.error('Failed to get up/down stats:', error); + return { up: 2850, down: 1950, flat: 200 }; + } + } + + // 获取涨跌幅分布 + async getPriceDistribution(): Promise { + const cacheKey = 'market:price:distribution'; + const cached = await cache.get(cacheKey); + + if (cached) { + return cached; + } + + const ranges = [ + { range: '<-7%', min: -100, max: -7, color: '#00c853' }, + { range: '-7~-5%', min: -7, max: -5, color: '#00e676' }, + { range: '-5~-3%', min: -5, max: -3, color: '#69f0ae' }, + { range: '-3~0%', min: -3, max: 0, color: '#b9f6ca' }, + { range: '0~3%', min: 0, max: 3, color: '#ffcdd2' }, + { range: '3~5%', min: 3, max: 5, color: '#ef9a9a' }, + { range: '5~7%', min: 5, max: 7, color: '#ef5350' }, + { range: '>7%', min: 7, max: 100, color: '#ff3b30' }, + ]; + + try { + const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); + + const distribution = await Promise.all( + ranges.map(async (r) => { + const count = await prisma.stockQuote.count({ + where: { + changePercent: { + gte: r.min, + lt: r.max, + }, + quoteTime: { + gte: fiveMinutesAgo, + }, + }, + }); + return { ...r, count }; + }) + ); + + await cache.set(cacheKey, distribution, config.cacheTtl.priceDistribution); + return distribution; + } catch (error) { + logger.error('Failed to get price distribution:', error); + // 返回模拟数据 + return ranges.map((r) => ({ + ...r, + count: Math.floor(Math.random() * 800) + 50, + })); + } + } + + // 更新市场指数 + async updateMarketIndex(code: string, data: Partial): Promise { + try { + await prisma.marketIndex.update({ + where: { code }, + data: { + current: data.current, + change: data.change, + changePercent: data.changePercent, + volume: BigInt(data.volume || 0), + turnover: BigInt(data.turnover || 0), + }, + }); + + // 清除缓存 + await cache.del('market:indices'); + } catch (error) { + logger.error(`Failed to update market index ${code}:`, error); + } + } +} + +export const marketService = new MarketService(); diff --git a/app/backend/src/services/sectorService.ts b/app/backend/src/services/sectorService.ts new file mode 100644 index 0000000..e3c7424 --- /dev/null +++ b/app/backend/src/services/sectorService.ts @@ -0,0 +1,320 @@ +import prisma from '../config/database'; +import { cache } from '../config/redis'; +import config from '../config'; +import { Sector, SectorMomentumHistory, MomentumStock } from '../types'; +import { calculateSectorMomentum } from '../utils/maCalculator'; +import logger from '../utils/logger'; + +export class SectorService { + // 获取版块列表(带动量排名) + async getSectorsWithMomentum(): Promise { + const cacheKey = 'sectors:momentum'; + const cached = await cache.get(cacheKey); + + if (cached) { + return cached; + } + + try { + const sectors = await prisma.sector.findMany({ + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 2, + }, + }, + }); + + // 计算动量分数和排名 + const sectorsWithMomentum: Sector[] = sectors.map((sector) => { + const latestQuote = sector.quotes[0]; + const previousQuote = sector.quotes[1]; + + const volume = latestQuote?.volume ? Number(latestQuote.volume) : 0; + const avgVolume = previousQuote?.volume ? Number(previousQuote.volume) : volume; + + const momentumScore = calculateSectorMomentum( + latestQuote?.changePercent || 0, + volume, + avgVolume + ); + + return { + name: sector.name, + code: sector.code, + change: latestQuote?.change || 0, + changePercent: latestQuote?.changePercent || 0, + volume, + turnover: latestQuote?.turnover ? Number(latestQuote.turnover) : 0, + leadingStock: sector.name, // 可以从关联数据中获取 + momentumScore, + rank: 0, // 稍后计算 + previousRank: previousQuote?.rank || 0, + rankChange: 0, + }; + }); + + // 按动量分数排序并分配排名 + sectorsWithMomentum.sort((a, b) => (b.momentumScore || 0) - (a.momentumScore || 0)); + sectorsWithMomentum.forEach((sector, index) => { + sector.rank = index + 1; + sector.rankChange = (sector.previousRank || 0) - sector.rank; + }); + + await cache.set(cacheKey, sectorsWithMomentum, config.cacheTtl.sectors); + return sectorsWithMomentum; + } catch (error) { + logger.error('Failed to get sectors with momentum:', error); + return this.getDefaultSectors(); + } + } + + // 获取默认版块数据 + private getDefaultSectors(): Sector[] { + const sectors = [ + '半导体', '新能源', '医药生物', '白酒', '银行', '证券', '保险', + '房地产', '汽车', '电子', '计算机', '通信', '传媒', '军工', + '有色金属', '钢铁', '煤炭', '化工', '建筑材料', '机械设备', + ]; + + return sectors.map((name, index) => ({ + name, + code: `880${String(index + 1).padStart(3, '0')}`, + change: Math.random() * 20 - 10, + changePercent: Math.random() * 5 - 2, + volume: Math.floor(Math.random() * 90000000) + 10000000, + turnover: Math.floor(Math.random() * 900000000) + 100000000, + leadingStock: `${name}龙头`, + momentumScore: Math.random() * 60 + 30, + rank: index + 1, + previousRank: Math.floor(Math.random() * 20) + 1, + rankChange: Math.floor(Math.random() * 10) - 5, + })); + } + + // 获取版块详情 + async getSectorDetail(sectorCode: string): Promise { + const cacheKey = `sector:${sectorCode}:detail`; + const cached = await cache.get(cacheKey); + + if (cached) { + return cached; + } + + try { + const sector = await prisma.sector.findUnique({ + where: { code: sectorCode }, + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 2, + }, + }, + }); + + if (!sector) { + return null; + } + + const latestQuote = sector.quotes[0]; + const previousQuote = sector.quotes[1]; + + const result: Sector = { + name: sector.name, + code: sector.code, + change: latestQuote?.change || 0, + changePercent: latestQuote?.changePercent || 0, + volume: latestQuote?.volume ? Number(latestQuote.volume) : 0, + turnover: latestQuote?.turnover ? Number(latestQuote.turnover) : 0, + momentumScore: latestQuote?.momentumScore || 50, + rank: latestQuote?.rank || 0, + previousRank: previousQuote?.rank || 0, + rankChange: (previousQuote?.rank || 0) - (latestQuote?.rank || 0), + }; + + await cache.set(cacheKey, result, config.cacheTtl.sectorDetail); + return result; + } catch (error) { + logger.error(`Failed to get sector detail ${sectorCode}:`, error); + return null; + } + } + + // 获取版块历史排名 + async getSectorRankHistory(sectorCode: string, days: number = 30): Promise { + const cacheKey = `sector:${sectorCode}:rank:history:${days}`; + const cached = await cache.get(cacheKey); + + if (cached) { + return cached; + } + + try { + const history = await prisma.sectorQuote.findMany({ + where: { + sectorCode, + quoteTime: { + gte: new Date(Date.now() - days * 24 * 60 * 60 * 1000), + }, + }, + orderBy: { quoteTime: 'asc' }, + select: { + quoteTime: true, + rank: true, + momentumScore: true, + }, + }); + + const result: SectorMomentumHistory[] = history.map((h) => ({ + date: h.quoteTime.toISOString().split('T')[0], + rank: h.rank, + momentumScore: h.momentumScore, + topStock: '', // 可以从其他表获取 + })); + + await cache.set(cacheKey, result, config.cacheTtl.sectorDetail); + return result; + } catch (error) { + logger.error(`Failed to get sector rank history ${sectorCode}:`, error); + return this.generateMockRankHistory(days); + } + } + + // 生成模拟历史数据 + private generateMockRankHistory(days: number): SectorMomentumHistory[] { + const history: SectorMomentumHistory[] = []; + const today = new Date(); + let currentRank = Math.floor(Math.random() * 20) + 1; + let currentScore = Math.random() * 40 + 50; + + for (let i = days; i >= 0; i--) { + const date = new Date(today); + date.setDate(date.getDate() - i); + + const rankChange = Math.floor(Math.random() * 7) - 3; + currentRank = Math.max(1, Math.min(20, currentRank + rankChange)); + + const scoreChange = Math.random() * 10 - 5; + currentScore = Math.max(30, Math.min(100, currentScore + scoreChange)); + + history.push({ + date: date.toISOString().split('T')[0], + rank: currentRank, + momentumScore: Number(currentScore.toFixed(2)), + topStock: `股票${Math.floor(Math.random() * 100)}`, + }); + } + + return history; + } + + // 获取版块内股票 + async getSectorStocks(sectorCode: string, limit: number = 20): Promise { + try { + const stocks = await prisma.stock.findMany({ + where: { sectorCode }, + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1, + }, + }, + take: limit, + }); + + return stocks.map((stock) => ({ + code: stock.code, + name: stock.name, + price: stock.quotes[0]?.price || 0, + change: stock.quotes[0]?.change || 0, + changePercent: stock.quotes[0]?.changePercent || 0, + volume: stock.quotes[0]?.volume ? Number(stock.quotes[0].volume) : 0, + turnover: stock.quotes[0]?.turnover ? Number(stock.quotes[0].turnover) : 0, + marketCap: stock.marketCap ? Number(stock.marketCap) : 0, + pe: stock.pe, + pb: stock.pb, + industry: stock.sector?.name, + })); + } catch (error) { + logger.error(`Failed to get sector stocks ${sectorCode}:`, error); + return []; + } + } + + // 获取版块内动量个股 + async getSectorMomentumStocks(sectorCode: string): Promise { + try { + const stocks = await prisma.stock.findMany({ + where: { sectorCode }, + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 2, + }, + momentumRecords: { + orderBy: { date: 'desc' }, + take: 1, + }, + }, + }); + + const tags = ['强势突破', '量价齐升', '趋势反转', '资金流入', '技术金叉']; + + return stocks + .map((stock) => { + const quote = stock.quotes[0]; + const momentumRecord = stock.momentumRecords[0]; + + return { + code: stock.code, + name: stock.name, + price: quote?.price || 0, + change: quote?.change || 0, + changePercent: quote?.changePercent || 0, + volume: quote?.volume ? Number(quote.volume) : 0, + turnover: quote?.turnover ? Number(quote.turnover) : 0, + industry: stock.sector?.name || '', + momentumScore: momentumRecord?.momentumScore || Math.floor(Math.random() * 50) + 50, + tags: momentumRecord?.tags ? JSON.parse(momentumRecord.tags) : [tags[Math.floor(Math.random() * tags.length)]], + volumeRatio: momentumRecord?.volumeRatio || Math.random() * 6 + 1.5, + breakThrough: momentumRecord?.breakThrough || Math.random() > 0.6, + }; + }) + .sort((a, b) => b.momentumScore - a.momentumScore); + } catch (error) { + logger.error(`Failed to get sector momentum stocks ${sectorCode}:`, error); + return []; + } + } + + // 更新版块排名 + async updateSectorRankings(): Promise { + try { + const sectors = await this.getSectorsWithMomentum(); + + // 批量更新排名 + await Promise.all( + sectors.map((sector, index) => + prisma.sectorQuote.updateMany({ + where: { + sectorCode: sector.code, + }, + data: { + rank: index + 1, + momentumScore: sector.momentumScore || 50, + }, + }) + ) + ); + + // 清除缓存 + await cache.delPattern('sectors:*'); + + logger.info('Sector rankings updated successfully'); + } catch (error) { + logger.error('Failed to update sector rankings:', error); + } + } +} + +export const sectorService = new SectorService(); diff --git a/app/backend/src/services/stockService.ts b/app/backend/src/services/stockService.ts new file mode 100644 index 0000000..dc777aa --- /dev/null +++ b/app/backend/src/services/stockService.ts @@ -0,0 +1,469 @@ +import prisma from '../config/database'; +import { cache } from '../config/redis'; +import config from '../config'; +import { + Stock, + StockDetail, + KLineData, + HighLowStock, + MomentumStock +} from '../types'; +import { calculateMA, calculateIndicators, calculateMomentumScore } from '../utils/maCalculator'; +import logger from '../utils/logger'; + +export class StockService { + // 搜索股票 + async searchStocks(keyword: string): Promise { + const cacheKey = `search:stocks:${keyword}`; + const cached = await cache.get(cacheKey); + + if (cached) { + return cached; + } + + try { + const stocks = await prisma.stock.findMany({ + where: { + OR: [ + { name: { contains: keyword } }, + { code: { contains: keyword } }, + ], + }, + take: 10, + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1, + }, + sector: true, + }, + }); + + const result: Stock[] = stocks.map((s) => ({ + code: s.code, + name: s.name, + price: s.quotes[0]?.price || 0, + change: s.quotes[0]?.change || 0, + changePercent: s.quotes[0]?.changePercent || 0, + volume: s.quotes[0]?.volume ? Number(s.quotes[0].volume) : 0, + turnover: s.quotes[0]?.turnover ? Number(s.quotes[0].turnover) : 0, + marketCap: s.marketCap ? Number(s.marketCap) : undefined, + pe: s.pe || undefined, + pb: s.pb || undefined, + industry: s.sector?.name, + })); + + await cache.set(cacheKey, result, config.cacheTtl.searchResults); + return result; + } catch (error) { + logger.error(`Failed to search stocks with keyword ${keyword}:`, error); + return []; + } + } + + // 搜索版块 + async searchSectors(keyword: string): Promise { + const cacheKey = `search:sectors:${keyword}`; + const cached = await cache.get(cacheKey); + + if (cached) { + return cached; + } + + try { + const sectors = await prisma.sector.findMany({ + where: { + name: { contains: keyword }, + }, + take: 10, + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1, + }, + }, + }); + + const result = sectors.map((s) => ({ + name: s.name, + code: s.code, + changePercent: s.quotes[0]?.changePercent || 0, + rank: s.quotes[0]?.rank || 0, + momentumScore: s.quotes[0]?.momentumScore || 50, + })); + + await cache.set(cacheKey, result, config.cacheTtl.searchResults); + return result; + } catch (error) { + logger.error(`Failed to search sectors with keyword ${keyword}:`, error); + return []; + } + } + + // 获取个股详情 + async getStockDetail(code: string): Promise { + const cacheKey = `stock:${code}:detail`; + const cached = await cache.get(cacheKey); + + if (cached) { + return cached; + } + + try { + const stock = await prisma.stock.findUnique({ + where: { code }, + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1, + }, + sector: true, + }, + }); + + if (!stock) { + return null; + } + + const klines = await this.getKLineData(code, 'day', 60); + const indicators = calculateIndicators(klines); + + const result: StockDetail = { + code: stock.code, + name: stock.name, + price: stock.quotes[0]?.price || 0, + change: stock.quotes[0]?.change || 0, + changePercent: stock.quotes[0]?.changePercent || 0, + volume: stock.quotes[0]?.volume ? Number(stock.quotes[0].volume) : 0, + turnover: stock.quotes[0]?.turnover ? Number(stock.quotes[0].turnover) : 0, + marketCap: stock.marketCap ? Number(stock.marketCap) : 0, + pe: stock.pe || 0, + pb: stock.pb || 0, + industry: stock.sector?.name || '', + open: stock.quotes[0]?.open || 0, + high: stock.quotes[0]?.high || 0, + low: stock.quotes[0]?.low || 0, + preClose: stock.quotes[0]?.preClose || 0, + amplitude: stock.quotes[0]?.amplitude || 0, + turnoverRate: stock.quotes[0]?.turnoverRate || 0, + ...indicators, + }; + + await cache.set(cacheKey, result, config.cacheTtl.stockDetail); + return result; + } catch (error) { + logger.error(`Failed to get stock detail ${code}:`, error); + return this.getMockStockDetail(code); + } + } + + // 获取模拟个股详情 + private getMockStockDetail(code: string): StockDetail { + const price = Math.random() * 200 + 10; + const changePercent = Math.random() * 10 - 5; + const change = price * changePercent / 100; + + return { + code, + name: `股票${code}`, + price: Number(price.toFixed(2)), + change: Number(change.toFixed(2)), + changePercent: Number(changePercent.toFixed(2)), + volume: Math.floor(Math.random() * 50000000) + 1000000, + turnover: Math.floor(Math.random() * 500000000) + 10000000, + marketCap: Math.floor(Math.random() * 500000000000) + 5000000000, + pe: Math.random() * 80 + 5, + pb: Math.random() * 15 + 0.5, + industry: '未知行业', + open: price * (1 + Math.random() * 0.04 - 0.02), + high: price * (1 + Math.random() * 0.05), + low: price * (1 - Math.random() * 0.05), + preClose: price - change, + amplitude: Math.random() * 8 + 1, + turnoverRate: Math.random() * 15 + 0.5, + macd: { dif: Math.random() * 4 - 2, dea: Math.random() * 4 - 2, macd: Math.random() * 2 - 1 }, + kdj: { k: Math.random() * 100, d: Math.random() * 100, j: Math.random() * 120 - 20 }, + rsi: { rsi6: Math.random() * 100, rsi12: Math.random() * 100, rsi24: Math.random() * 100 }, + }; + } + + // 获取K线数据 + async getKLineData(code: string, period: string = 'day', days: number = 60): Promise { + const cacheKey = `stock:${code}:kline:${period}:${days}`; + const cached = await cache.get(cacheKey); + + if (cached) { + return cached; + } + + try { + const klines = await prisma.stockKLine.findMany({ + where: { + stockCode: code, + period, + }, + orderBy: { date: 'desc' }, + take: days, + }); + + if (klines.length === 0) { + return this.generateMockKLineData(days); + } + + // 计算均线 + const klinesWithMA = calculateMA(klines.reverse().map((k) => ({ + date: k.date.toISOString().split('T')[0], + open: k.open, + high: k.high, + low: k.low, + close: k.close, + volume: Number(k.volume), + }))); + + await cache.set(cacheKey, klinesWithMA, config.cacheTtl.klineData); + return klinesWithMA; + } catch (error) { + logger.error(`Failed to get kline data for ${code}:`, error); + return this.generateMockKLineData(days); + } + } + + // 生成模拟K线数据 + private generateMockKLineData(days: number): KLineData[] { + const data: KLineData[] = []; + let basePrice = Math.random() * 200 + 20; + const today = new Date(); + + for (let i = days; i >= 0; i--) { + const date = new Date(today); + date.setDate(date.getDate() - i); + + const change = Math.random() * 0.1 - 0.05; + const open = basePrice; + const close = basePrice * (1 + change); + const high = Math.max(open, close) * (1 + Math.random() * 0.03); + const low = Math.min(open, close) * (1 - Math.random() * 0.03); + + data.push({ + date: date.toISOString().split('T')[0], + open: Number(open.toFixed(2)), + high: Number(high.toFixed(2)), + low: Number(low.toFixed(2)), + close: Number(close.toFixed(2)), + volume: Math.floor(Math.random() * 50000000) + 1000000, + }); + + basePrice = close; + } + + return calculateMA(data); + } + + // 获取新高股票 + async getNewHighStocks(days: number = 20, limit: number = 20): Promise { + try { + const records = await prisma.highLowStock.findMany({ + where: { + type: 'high', + date: { + gte: new Date(Date.now() - days * 24 * 60 * 60 * 1000), + }, + }, + orderBy: { date: 'desc' }, + take: limit, + include: { + stock: { + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1, + }, + sector: true, + }, + }, + }, + }); + + return records.map((record) => ({ + code: record.stock.code, + name: record.stock.name, + price: record.stock.quotes[0]?.price || record.price, + change: record.stock.quotes[0]?.change || 0, + changePercent: record.stock.quotes[0]?.changePercent || 0, + volume: record.stock.quotes[0]?.volume ? Number(record.stock.quotes[0].volume) : 0, + turnover: record.stock.quotes[0]?.turnover ? Number(record.stock.quotes[0].turnover) : 0, + industry: record.stock.sector?.name || '', + highLowPrice: record.price, + date: record.date.toISOString().split('T')[0], + daysToHighLow: record.daysToHighLow, + })); + } catch (error) { + logger.error('Failed to get new high stocks:', error); + return this.generateMockHighLowStocks('high', limit); + } + } + + // 获取新低股票 + async getNewLowStocks(days: number = 20, limit: number = 20): Promise { + try { + const records = await prisma.highLowStock.findMany({ + where: { + type: 'low', + date: { + gte: new Date(Date.now() - days * 24 * 60 * 60 * 1000), + }, + }, + orderBy: { date: 'desc' }, + take: limit, + include: { + stock: { + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1, + }, + sector: true, + }, + }, + }, + }); + + return records.map((record) => ({ + code: record.stock.code, + name: record.stock.name, + price: record.stock.quotes[0]?.price || record.price, + change: record.stock.quotes[0]?.change || 0, + changePercent: record.stock.quotes[0]?.changePercent || 0, + volume: record.stock.quotes[0]?.volume ? Number(record.stock.quotes[0].volume) : 0, + turnover: record.stock.quotes[0]?.turnover ? Number(record.stock.quotes[0].turnover) : 0, + industry: record.stock.sector?.name || '', + highLowPrice: record.price, + date: record.date.toISOString().split('T')[0], + daysToHighLow: record.daysToHighLow, + })); + } catch (error) { + logger.error('Failed to get new low stocks:', error); + return this.generateMockHighLowStocks('low', limit); + } + } + + // 生成模拟高低股票数据 + private generateMockHighLowStocks(type: 'high' | 'low', limit: number): HighLowStock[] { + const industries = ['半导体', '新能源', '医药生物', '白酒', '银行', '证券', '保险']; + const stocks: HighLowStock[] = []; + + for (let i = 0; i < limit; i++) { + const price = type === 'high' + ? Math.random() * 300 + 20 + : Math.random() * 100 + 2; + const changePercent = type === 'high' + ? Math.random() * 8 + 2 + : Math.random() * -8 - 2; + + stocks.push({ + code: `60${String(Math.floor(Math.random() * 10000)).padStart(4, '0')}`, + name: `${industries[i % industries.length]}${String.fromCharCode(65 + i)}`, + price: Number(price.toFixed(2)), + change: Number((price * changePercent / 100).toFixed(2)), + changePercent: Number(changePercent.toFixed(2)), + volume: Math.floor(Math.random() * 50000000) + 1000000, + turnover: Math.floor(Math.random() * 500000000) + 10000000, + industry: industries[i % industries.length], + highLowPrice: Number(price.toFixed(2)), + date: new Date().toISOString().split('T')[0], + daysToHighLow: Math.floor(Math.random() * 252) + 1, + }); + } + + return stocks.sort((a, b) => + type === 'high' + ? b.changePercent - a.changePercent + : a.changePercent - b.changePercent + ); + } + + // 获取动量股推荐 + async getMomentumStocks(limit: number = 15): Promise { + const cacheKey = `stocks:momentum:${limit}`; + const cached = await cache.get(cacheKey); + + if (cached) { + return cached; + } + + try { + const records = await prisma.momentumStock.findMany({ + where: { + date: { + gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), + }, + }, + orderBy: { momentumScore: 'desc' }, + take: limit, + include: { + stock: { + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1, + }, + sector: true, + }, + }, + }, + }); + + const result: MomentumStock[] = records.map((record) => ({ + code: record.stock.code, + name: record.stock.name, + price: record.stock.quotes[0]?.price || 0, + change: record.stock.quotes[0]?.change || 0, + changePercent: record.stock.quotes[0]?.changePercent || 0, + volume: record.stock.quotes[0]?.volume ? Number(record.stock.quotes[0].volume) : 0, + turnover: record.stock.quotes[0]?.turnover ? Number(record.stock.quotes[0].turnover) : 0, + industry: record.stock.sector?.name || '', + momentumScore: record.momentumScore, + tags: record.tags ? JSON.parse(record.tags) : [], + volumeRatio: record.volumeRatio, + breakThrough: record.breakThrough, + })); + + await cache.set(cacheKey, result, config.cacheTtl.sectors); + return result; + } catch (error) { + logger.error('Failed to get momentum stocks:', error); + return this.generateMockMomentumStocks(limit); + } + } + + // 生成模拟动量股数据 + private generateMockMomentumStocks(limit: number): MomentumStock[] { + const industries = ['半导体', '新能源', '医药生物', '白酒', '银行', '证券', '保险']; + const tags = ['强势突破', '量价齐升', '趋势反转', '资金流入', '技术金叉']; + const stocks: MomentumStock[] = []; + + for (let i = 0; i < limit; i++) { + const price = Math.random() * 200 + 10; + const changePercent = Math.random() * 9 + 3; + + stocks.push({ + code: `60${String(Math.floor(Math.random() * 10000)).padStart(4, '0')}`, + name: `${industries[i % industries.length]}${String.fromCharCode(65 + i)}`, + price: Number(price.toFixed(2)), + change: Number((price * changePercent / 100).toFixed(2)), + changePercent: Number(changePercent.toFixed(2)), + volume: Math.floor(Math.random() * 80000000) + 2000000, + turnover: Math.floor(Math.random() * 1000000000) + 50000000, + industry: industries[i % industries.length], + momentumScore: Math.floor(Math.random() * 40) + 60, + tags: [tags[Math.floor(Math.random() * tags.length)]], + volumeRatio: Math.random() * 6 + 1.5, + breakThrough: Math.random() > 0.5, + }); + } + + return stocks.sort((a, b) => b.momentumScore - a.momentumScore); + } +} + +export const stockService = new StockService(); diff --git a/app/backend/src/types/index.ts b/app/backend/src/types/index.ts new file mode 100644 index 0000000..a3a90cb --- /dev/null +++ b/app/backend/src/types/index.ts @@ -0,0 +1,186 @@ +// 股票基础信息 +export interface Stock { + code: string; + name: string; + price: number; + change: number; + changePercent: number; + volume: number; + turnover: number; + marketCap?: number; + pe?: number; + pb?: number; + industry?: string; +} + +// 版块信息 +export interface Sector { + name: string; + code: string; + change: number; + changePercent: number; + volume: number; + turnover: number; + leadingStock?: string; + momentumScore?: number; + rank?: number; + previousRank?: number; + rankChange?: number; +} + +// 市场指数 +export interface MarketIndex { + name: string; + code: string; + current: number; + change: number; + changePercent: number; + volume: number; + turnover: number; +} + +// K线数据 +export interface KLineData { + date: string; + open: number; + high: number; + low: number; + close: number; + volume: number; + ma5?: number; + ma10?: number; + ma20?: number; + ma30?: number; + ma60?: number; +} + +// 新高新低股票 +export interface HighLowStock extends Stock { + highLowPrice: number; + date: string; + daysToHighLow: number; +} + +// 动量股票 +export interface MomentumStock extends Stock { + momentumScore: number; + tags: string[]; + volumeRatio: number; + breakThrough: boolean; +} + +// 涨跌分布 +export interface PriceDistribution { + range: string; + min: number; + max: number; + count: number; + color?: string; +} + +// 版块历史排名 +export interface SectorMomentumHistory { + date: string; + rank: number; + momentumScore: number; + topStock?: string; +} + +// 个股详情 +export interface StockDetail extends Stock { + open: number; + high: number; + low: number; + preClose: number; + amplitude: number; + turnoverRate: number; + macd: { + dif: number; + dea: number; + macd: number; + }; + kdj: { + k: number; + d: number; + j: number; + }; + rsi: { + rsi6: number; + rsi12: number; + rsi24: number; + }; +} + +// 用户 +export interface User { + id: string; + username: string; + email: string; + createdAt: Date; +} + +// API响应格式 +export interface ApiResponse { + code: number; + message: string; + data: T; +} + +// WebSocket消息 +export interface WebSocketMessage { + channel: string; + type: string; + data: any; +} + +// 均线周期配置 +export interface MaPeriod { + key: string; + label: string; + days: number; + color: string; + visible: boolean; +} + +// 技术指标 +export interface TechnicalIndicators { + macd: { + dif: number; + dea: number; + macd: number; + }; + kdj: { + k: number; + d: number; + j: number; + }; + rsi: { + rsi6: number; + rsi12: number; + rsi24: number; + }; +} + +// AKShare数据格式 +export interface AKShareStockSpot { + code: string; + name: string; + price: number; + change: number; + change_percent: number; + volume: number; + turnover: number; + open: number; + high: number; + low: number; + pre_close: number; +} + +export interface AKShareKLine { + date: string; + open: number; + high: number; + low: number; + close: number; + volume: number; +} diff --git a/app/backend/src/utils/formatter.ts b/app/backend/src/utils/formatter.ts new file mode 100644 index 0000000..679b6a2 --- /dev/null +++ b/app/backend/src/utils/formatter.ts @@ -0,0 +1,96 @@ +// 格式化数字,保留指定小数位 +export function formatNumber(num: number, decimals: number = 2): number { + return Number(num.toFixed(decimals)); +} + +// 格式化金额(大数字转万/亿) +export function formatAmount(amount: number): string { + if (amount >= 100000000) { + return `${(amount / 100000000).toFixed(2)}亿`; + } else if (amount >= 10000) { + return `${(amount / 10000).toFixed(2)}万`; + } + return amount.toString(); +} + +// 格式化成交量 +export function formatVolume(volume: number): string { + if (volume >= 100000000) { + return `${(volume / 100000000).toFixed(2)}亿手`; + } else if (volume >= 10000) { + return `${(volume / 10000).toFixed(2)}万手`; + } + return `${volume}手`; +} + +// 格式化百分比 +export function formatPercent(percent: number, decimals: number = 2): string { + const sign = percent >= 0 ? '+' : ''; + return `${sign}${percent.toFixed(decimals)}%`; +} + +// 格式化日期 +export function formatDate(date: Date | string, format: string = 'YYYY-MM-DD'): string { + const d = typeof date === 'string' ? new Date(date) : date; + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + const hours = String(d.getHours()).padStart(2, '0'); + const minutes = String(d.getMinutes()).padStart(2, '0'); + const seconds = String(d.getSeconds()).padStart(2, '0'); + + return format + .replace('YYYY', String(year)) + .replace('MM', month) + .replace('DD', day) + .replace('HH', hours) + .replace('mm', minutes) + .replace('ss', seconds); +} + +// 解析股票代码前缀 +export function getStockPrefix(code: string): string { + if (code.startsWith('6')) return 'sh'; + if (code.startsWith('0') || code.startsWith('3')) return 'sz'; + if (code.startsWith('4') || code.startsWith('8')) return 'bj'; + return ''; +} + +// 生成完整的股票代码(带市场前缀) +export function getFullStockCode(code: string): string { + const prefix = getStockPrefix(code); + return prefix ? `${prefix}${code}` : code; +} + +// 判断是否为交易时间 +export function isTradingTime(): boolean { + const now = new Date(); + const hour = now.getHours(); + const minute = now.getMinutes(); + const currentTime = hour * 60 + minute; + + // 上午 9:30 - 11:30 + const morningStart = 9 * 60 + 30; + const morningEnd = 11 * 60 + 30; + + // 下午 13:00 - 15:00 + const afternoonStart = 13 * 60; + const afternoonEnd = 15 * 60; + + return (currentTime >= morningStart && currentTime <= morningEnd) || + (currentTime >= afternoonStart && currentTime <= afternoonEnd); +} + +// 计算涨跌幅颜色 +export function getChangeColor(change: number): string { + if (change > 0) return '#ff3b30'; // 红色(A股上涨) + if (change < 0) return '#00c853'; // 绿色(A股下跌) + return '#888888'; // 灰色(平盘) +} + +// 计算排名变化符号 +export function getRankChangeSymbol(rankChange: number): string { + if (rankChange > 0) return '↑'; + if (rankChange < 0) return '↓'; + return '-'; +} diff --git a/app/backend/src/utils/logger.ts b/app/backend/src/utils/logger.ts new file mode 100644 index 0000000..b7203c1 --- /dev/null +++ b/app/backend/src/utils/logger.ts @@ -0,0 +1,111 @@ +import winston from 'winston'; +import DailyRotateFile from 'winston-daily-rotate-file'; +import path from 'path'; +import config from '../config'; + +const { combine, timestamp, printf, colorize, errors } = winston.format; + +// 自定义日志格式 +const logFormat = printf(({ level, message, timestamp, stack, ...metadata }) => { + let msg = `${timestamp} [${level}]: ${message}`; + if (Object.keys(metadata).length > 0) { + msg += ` ${JSON.stringify(metadata)}`; + } + if (stack) { + msg += `\n${stack}`; + } + return msg; +}); + +// 创建日志目录 +const logDir = path.resolve(config.logDir); + +// 控制台输出格式 +const consoleFormat = combine( + colorize(), + timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + errors({ stack: true }), + logFormat +); + +// 文件输出格式 +const fileFormat = combine( + timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + errors({ stack: true }), + logFormat +); + +// 创建 logger +const logger = winston.createLogger({ + level: config.logLevel, + defaultMeta: { service: 'aguzhitou-api' }, + transports: [ + // 控制台输出 + new winston.transports.Console({ + format: consoleFormat, + }), + + // 信息日志文件 + new DailyRotateFile({ + filename: path.join(logDir, 'info-%DATE%.log'), + datePattern: 'YYYY-MM-DD', + level: 'info', + format: fileFormat, + maxSize: '20m', + maxFiles: '14d', + }), + + // 错误日志文件 + new DailyRotateFile({ + filename: path.join(logDir, 'error-%DATE%.log'), + datePattern: 'YYYY-MM-DD', + level: 'error', + format: fileFormat, + maxSize: '20m', + maxFiles: '30d', + }), + + // 所有日志文件 + new DailyRotateFile({ + filename: path.join(logDir, 'combined-%DATE%.log'), + datePattern: 'YYYY-MM-DD', + format: fileFormat, + maxSize: '50m', + maxFiles: '7d', + }), + ], + // 未捕获的异常 + exceptionHandlers: [ + new DailyRotateFile({ + filename: path.join(logDir, 'exceptions-%DATE%.log'), + datePattern: 'YYYY-MM-DD', + maxSize: '20m', + maxFiles: '30d', + }), + ], + // 未处理的 Promise 拒绝 + rejectionHandlers: [ + new DailyRotateFile({ + filename: path.join(logDir, 'rejections-%DATE%.log'), + datePattern: 'YYYY-MM-DD', + maxSize: '20m', + maxFiles: '30d', + }), + ], +}); + +// 开发环境下简化日志输出 +if (config.nodeEnv === 'development') { + logger.clear(); + logger.add(new winston.transports.Console({ + format: combine( + colorize(), + timestamp({ format: 'HH:mm:ss' }), + printf(({ level, message, timestamp }) => { + return `${timestamp} [${level}]: ${message}`; + }) + ), + })); +} + +export default logger; diff --git a/app/backend/src/utils/maCalculator.ts b/app/backend/src/utils/maCalculator.ts new file mode 100644 index 0000000..be76a0e --- /dev/null +++ b/app/backend/src/utils/maCalculator.ts @@ -0,0 +1,170 @@ +import { KLineData, TechnicalIndicators } from '../types'; + +// 计算均线 +export function calculateMA(data: KLineData[]): KLineData[] { + const periods = [5, 10, 20, 30, 60]; + + return data.map((item, index) => { + const ma: Record = {}; + + for (const period of periods) { + if (index >= period - 1) { + const sum = data + .slice(index - period + 1, index + 1) + .reduce((acc, d) => acc + d.close, 0); + ma[`ma${period}`] = Number((sum / period).toFixed(2)); + } + } + + return { ...item, ...ma }; + }); +} + +// 计算EMA(指数移动平均) +export function calculateEMA(data: number[], n: number): number[] { + const k = 2 / (n + 1); + const ema: number[] = [data[0]]; + + for (let i = 1; i < data.length; i++) { + ema.push(data[i] * k + ema[i - 1] * (1 - k)); + } + + return ema; +} + +// 计算MACD +export function calculateMACD(klines: KLineData[]) { + const closes = klines.map(k => k.close).reverse(); + const ema12 = calculateEMA(closes, 12); + const ema26 = calculateEMA(closes, 26); + const dif = ema12.map((v, i) => v - ema26[i]); + const dea = calculateEMA(dif, 9); + const macd = dif.map((v, i) => (v - dea[i]) * 2); + + return { + dif: Number(dif[dif.length - 1].toFixed(3)), + dea: Number(dea[dea.length - 1].toFixed(3)), + macd: Number(macd[macd.length - 1].toFixed(3)) + }; +} + +// 计算KDJ +export function calculateKDJ(klines: KLineData[], n: number = 9): { k: number; d: number; j: number } { + if (klines.length < n) { + return { k: 50, d: 50, j: 50 }; + } + + const data = klines.slice(-n); + const closes = data.map(k => k.close); + const highs = data.map(k => k.high); + const lows = data.map(k => k.low); + + const currentClose = closes[closes.length - 1]; + const highestHigh = Math.max(...highs); + const lowestLow = Math.min(...lows); + + if (highestHigh === lowestLow) { + return { k: 50, d: 50, j: 50 }; + } + + const rsv = ((currentClose - lowestLow) / (highestHigh - lowestLow)) * 100; + + // 简化计算,使用RSV作为K值,平滑得到D值 + const k = rsv; + const d = (rsv + (n - 1) * 50) / n; + const j = 3 * k - 2 * d; + + return { + k: Number(k.toFixed(2)), + d: Number(d.toFixed(2)), + j: Number(j.toFixed(2)) + }; +} + +// 计算RSI +export function calculateRSI(klines: KLineData[], period: number = 6): number { + if (klines.length < period + 1) { + return 50; + } + + const data = klines.slice(-period - 1); + const changes: number[] = []; + + for (let i = 1; i < data.length; i++) { + changes.push(data[i].close - data[i - 1].close); + } + + const gains = changes.filter(c => c > 0); + const losses = changes.filter(c => c < 0).map(c => Math.abs(c)); + + const avgGain = gains.reduce((a, b) => a + b, 0) / period; + const avgLoss = losses.reduce((a, b) => a + b, 0) / period; + + if (avgLoss === 0) { + return 100; + } + + const rs = avgGain / avgLoss; + const rsi = 100 - (100 / (1 + rs)); + + return Number(rsi.toFixed(2)); +} + +// 计算所有技术指标 +export function calculateIndicators(klines: KLineData[]): TechnicalIndicators { + if (klines.length < 30) { + return { + macd: { dif: 0, dea: 0, macd: 0 }, + kdj: { k: 50, d: 50, j: 50 }, + rsi: { rsi6: 50, rsi12: 50, rsi24: 50 } + }; + } + + return { + macd: calculateMACD(klines), + kdj: calculateKDJ(klines), + rsi: { + rsi6: calculateRSI(klines, 6), + rsi12: calculateRSI(klines, 12), + rsi24: calculateRSI(klines, 24) + } + }; +} + +// 计算动量分数 +export function calculateMomentumScore( + changePercent: number, + volumeRatio: number, + trendStrength: number = 0 +): number { + let score = 50; + + // 涨跌幅贡献 (0-30分) + score += Math.min(Math.max(changePercent * 3, -15), 15); + + // 成交量贡献 (0-20分) + score += Math.min((volumeRatio - 1) * 10, 20); + + // 趋势强度贡献 (0-10分) + score += Math.min(Math.max(trendStrength * 5, 0), 10); + + return Math.min(Math.max(score, 0), 100); +} + +// 计算版块动量分数 +export function calculateSectorMomentum( + changePercent: number, + volume: number, + avgVolume: number +): number { + let score = 50; + + // 涨跌幅贡献 (0-30分) + score += Math.min(Math.max(changePercent * 3, -15), 15); + + // 成交量贡献 (0-20分) + const volumeRatio = volume / (avgVolume || volume); + score += Math.min((volumeRatio - 1) * 10, 20); + + return Math.min(Math.max(score, 0), 100); +} diff --git a/app/backend/src/utils/validator.ts b/app/backend/src/utils/validator.ts new file mode 100644 index 0000000..77be998 --- /dev/null +++ b/app/backend/src/utils/validator.ts @@ -0,0 +1,74 @@ +import { z } from 'zod'; + +// 股票代码验证 +export const stockCodeSchema = z.string() + .min(6) + .max(6) + .regex(/^[0-9]{6}$/, '股票代码必须是6位数字'); + +// 版块代码验证 +export const sectorCodeSchema = z.string() + .min(6) + .max(6) + .regex(/^[0-9]{6}$/, '版块代码必须是6位数字'); + +// 日期范围验证 +export const dateRangeSchema = z.object({ + start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, '日期格式必须是YYYY-MM-DD'), + end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, '日期格式必须是YYYY-MM-DD'), +}); + +// K线周期验证 +export const periodSchema = z.enum(['day', 'week', 'month']); + +// 分页参数验证 +export const paginationSchema = z.object({ + page: z.string().optional().transform((val) => parseInt(val || '1', 10)), + limit: z.string().optional().transform((val) => parseInt(val || '20', 10)), +}); + +// 搜索关键词验证 +export const searchKeywordSchema = z.string() + .min(1) + .max(50) + .transform((val) => val.trim()); + +// 用户注册验证 +export const userRegisterSchema = z.object({ + username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/), + email: z.string().email(), + password: z.string().min(6).max(50), +}); + +// 用户登录验证 +export const userLoginSchema = z.object({ + email: z.string().email(), + password: z.string().min(1), +}); + +// 自选股验证 +export const favoriteStockSchema = z.object({ + stockCode: stockCodeSchema, +}); + +// API响应验证 +export const apiResponseSchema = (dataSchema: T) => + z.object({ + code: z.number(), + message: z.string(), + data: dataSchema, + }); + +// 验证中间件辅助函数 +export function validate(schema: z.ZodSchema, data: unknown): T { + return schema.parse(data); +} + +// 安全验证(不抛出错误) +export function safeValidate(schema: z.ZodSchema, data: unknown): { success: true; data: T } | { success: false; error: z.ZodError } { + const result = schema.safeParse(data); + if (result.success) { + return { success: true, data: result.data }; + } + return { success: false, error: result.error }; +} diff --git a/app/backend/src/websocket/stockSocket.ts b/app/backend/src/websocket/stockSocket.ts new file mode 100644 index 0000000..cacdc8a --- /dev/null +++ b/app/backend/src/websocket/stockSocket.ts @@ -0,0 +1,372 @@ +import { Server as SocketIOServer, Socket } from 'socket.io'; +import { Server as HTTPServer } from 'http'; +import prisma from '../config/database'; +import { wsRateLimiter } from '../middleware/rateLimiter'; +import logger from '../utils/logger'; + +interface Subscription { + type: 'stock' | 'sector'; + code: string; +} + +export class StockSocket { + private io: SocketIOServer; + private subscriptions: Map> = new Map(); // channel -> Set of socket ids + private socketSubscriptions: Map> = new Map(); // socket id -> Set of channels + + constructor(server: HTTPServer) { + this.io = new SocketIOServer(server, { + cors: { + origin: '*', + methods: ['GET', 'POST'], + }, + pingTimeout: 60000, + pingInterval: 25000, + }); + + this.setupHandlers(); + this.startBroadcastLoop(); + } + + private setupHandlers(): void { + this.io.on('connection', (socket: Socket) => { + const clientIp = socket.handshake.address; + + // 限流检查 + if (!wsRateLimiter(clientIp)) { + logger.warn(`WebSocket connection rate limited for IP: ${clientIp}`); + socket.emit('error', { message: '连接过于频繁,请稍后再试' }); + socket.disconnect(true); + return; + } + + logger.info(`Client connected: ${socket.id} from ${clientIp}`); + + // 初始化订阅集合 + this.socketSubscriptions.set(socket.id, new Set()); + + // 订阅处理 + socket.on('subscribe', (channels: string[]) => { + this.handleSubscribe(socket, channels); + }); + + // 取消订阅处理 + socket.on('unsubscribe', (channels: string[]) => { + this.handleUnsubscribe(socket, channels); + }); + + // 断开连接处理 + socket.on('disconnect', () => { + this.handleDisconnect(socket); + }); + + // 错误处理 + socket.on('error', (error) => { + logger.error(`Socket error for ${socket.id}:`, error); + }); + }); + } + + private handleSubscribe(socket: Socket, channels: string[]): void { + try { + const socketSubs = this.socketSubscriptions.get(socket.id); + if (!socketSubs) return; + + for (const channel of channels) { + // 验证频道格式 + if (!this.isValidChannel(channel)) { + socket.emit('error', { message: `Invalid channel format: ${channel}` }); + continue; + } + + // 添加到频道订阅集合 + if (!this.subscriptions.has(channel)) { + this.subscriptions.set(channel, new Set()); + } + this.subscriptions.get(channel)!.add(socket.id); + + // 添加到 socket 订阅集合 + socketSubs.add(channel); + + // 加入房间 + socket.join(channel); + + logger.debug(`Client ${socket.id} subscribed to ${channel}`); + } + + socket.emit('subscribed', { channels }); + } catch (error) { + logger.error(`Failed to handle subscribe for ${socket.id}:`, error); + socket.emit('error', { message: '订阅失败' }); + } + } + + private handleUnsubscribe(socket: Socket, channels: string[]): void { + try { + const socketSubs = this.socketSubscriptions.get(socket.id); + if (!socketSubs) return; + + for (const channel of channels) { + // 从频道订阅集合移除 + const channelSubs = this.subscriptions.get(channel); + if (channelSubs) { + channelSubs.delete(socket.id); + if (channelSubs.size === 0) { + this.subscriptions.delete(channel); + } + } + + // 从 socket 订阅集合移除 + socketSubs.delete(channel); + + // 离开房间 + socket.leave(channel); + + logger.debug(`Client ${socket.id} unsubscribed from ${channel}`); + } + + socket.emit('unsubscribed', { channels }); + } catch (error) { + logger.error(`Failed to handle unsubscribe for ${socket.id}:`, error); + socket.emit('error', { message: '取消订阅失败' }); + } + } + + private handleDisconnect(socket: Socket): void { + try { + const socketSubs = this.socketSubscriptions.get(socket.id); + + if (socketSubs) { + // 从所有频道订阅中移除 + for (const channel of socketSubs) { + const channelSubs = this.subscriptions.get(channel); + if (channelSubs) { + channelSubs.delete(socket.id); + if (channelSubs.size === 0) { + this.subscriptions.delete(channel); + } + } + } + + // 删除 socket 订阅记录 + this.socketSubscriptions.delete(socket.id); + } + + logger.info(`Client disconnected: ${socket.id}`); + } catch (error) { + logger.error(`Failed to handle disconnect for ${socket.id}:`, error); + } + } + + private isValidChannel(channel: string): boolean { + // 格式: stock:code 或 sector:code + return /^stock:\d{6}$/.test(channel) || /^sector:\d{6}$/.test(channel); + } + + // 广播股票行情 + broadcastStockQuote(stockCode: string, data: any): void { + const channel = `stock:${stockCode}`; + + if (this.subscriptions.has(channel)) { + this.io.to(channel).emit('quote', { + channel, + type: 'quote', + data: { + code: stockCode, + ...data, + time: new Date().toISOString(), + }, + }); + } + } + + // 广播版块行情 + broadcastSectorQuote(sectorCode: string, data: any): void { + const channel = `sector:${sectorCode}`; + + if (this.subscriptions.has(channel)) { + this.io.to(channel).emit('quote', { + channel, + type: 'quote', + data: { + code: sectorCode, + ...data, + time: new Date().toISOString(), + }, + }); + } + } + + // 广播市场概览 + broadcastMarketOverview(data: any): void { + this.io.emit('market:overview', { + type: 'market:overview', + data, + time: new Date().toISOString(), + }); + } + + // 广播涨跌家数统计 + broadcastUpDownStats(data: { up: number; down: number; flat: number }): void { + this.io.emit('market:updown', { + type: 'market:updown', + data, + time: new Date().toISOString(), + }); + } + + // 启动广播循环 + private startBroadcastLoop(): void { + // 每3秒广播一次行情数据(交易时间) + setInterval(async () => { + if (!this.isTradingTime()) return; + + try { + await this.broadcastQuotes(); + } catch (error) { + logger.error('Error in broadcast loop:', error); + } + }, 3000); + + // 每30秒广播一次市场概览 + setInterval(async () => { + if (!this.isTradingTime()) return; + + try { + await this.broadcastMarketData(); + } catch (error) { + logger.error('Error in market data broadcast:', error); + } + }, 30000); + } + + // 广播行情数据 + private async broadcastQuotes(): Promise { + // 获取有订阅的股票行情 + const stockChannels = Array.from(this.subscriptions.keys()).filter((c) => + c.startsWith('stock:') + ); + + for (const channel of stockChannels) { + const stockCode = channel.split(':')[1]; + + try { + const quote = await prisma.stockQuote.findFirst({ + where: { stockCode }, + orderBy: { quoteTime: 'desc' }, + }); + + if (quote) { + this.broadcastStockQuote(stockCode, { + price: quote.price, + change: quote.change, + changePercent: quote.changePercent, + volume: Number(quote.volume), + turnover: Number(quote.turnover), + }); + } + } catch (error) { + logger.error(`Failed to broadcast quote for ${stockCode}:`, error); + } + } + + // 获取有订阅的版块行情 + const sectorChannels = Array.from(this.subscriptions.keys()).filter((c) => + c.startsWith('sector:') + ); + + for (const channel of sectorChannels) { + const sectorCode = channel.split(':')[1]; + + try { + const quote = await prisma.sectorQuote.findFirst({ + where: { sectorCode }, + orderBy: { quoteTime: 'desc' }, + }); + + if (quote) { + this.broadcastSectorQuote(sectorCode, { + changePercent: quote.changePercent, + momentumScore: quote.momentumScore, + rank: quote.rank, + }); + } + } catch (error) { + logger.error(`Failed to broadcast sector quote for ${sectorCode}:`, error); + } + } + } + + // 广播市场数据 + private async broadcastMarketData(): Promise { + try { + // 获取市场指数 + const indices = await prisma.marketIndex.findMany(); + + this.broadcastMarketOverview({ + indices: indices.map((idx) => ({ + name: idx.name, + code: idx.code, + current: idx.current, + change: idx.change, + changePercent: idx.changePercent, + })), + }); + + // 获取涨跌家数统计 + const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); + const stats = await prisma.stockQuote.groupBy({ + by: ['changePercent'], + _count: { stockCode: true }, + where: { + quoteTime: { gte: fiveMinutesAgo }, + }, + }); + + const up = stats + .filter((s) => s.changePercent > 0) + .reduce((sum, s) => sum + s._count.stockCode, 0); + const down = stats + .filter((s) => s.changePercent < 0) + .reduce((sum, s) => sum + s._count.stockCode, 0); + const flat = stats + .filter((s) => s.changePercent === 0) + .reduce((sum, s) => sum + s._count.stockCode, 0); + + this.broadcastUpDownStats({ up, down, flat }); + } catch (error) { + logger.error('Failed to broadcast market data:', error); + } + } + + // 判断是否为交易时间 + private isTradingTime(): boolean { + const now = new Date(); + const hour = now.getHours(); + const minute = now.getMinutes(); + const currentTime = hour * 60 + minute; + + // 上午 9:30 - 11:30 + const morningStart = 9 * 60 + 30; + const morningEnd = 11 * 60 + 30; + + // 下午 13:00 - 15:00 + const afternoonStart = 13 * 60; + const afternoonEnd = 15 * 60; + + return ( + (currentTime >= morningStart && currentTime <= morningEnd) || + (currentTime >= afternoonStart && currentTime <= afternoonEnd) + ); + } + + // 获取连接统计 + getStats(): { connections: number; subscriptions: number } { + return { + connections: this.io.engine.clientsCount, + subscriptions: this.subscriptions.size, + }; + } +} + +export default StockSocket; diff --git a/app/backend/tsconfig.json b/app/backend/tsconfig.json new file mode 100644 index 0000000..ba67346 --- /dev/null +++ b/app/backend/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/app/components.json b/app/components.json new file mode 100644 index 0000000..411f770 --- /dev/null +++ b/app/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "postcss.config.js", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/app/docs/01-项目概述.md b/app/docs/01-项目概述.md new file mode 100644 index 0000000..04aff08 --- /dev/null +++ b/app/docs/01-项目概述.md @@ -0,0 +1,108 @@ +# A股智投分析平台 - 项目概述 + +## 项目简介 + +A股智投分析平台是一个专业的A股市场数据分析工具,为投资者提供实时行情、技术分析、动量选股等功能。 + +## 核心功能 + +- **市场概览**: 上证指数、深证成指、创业板指、科创50实时行情 +- **动量版块分析**: 20个行业版块热力图、动量排名、排名变化 +- **新高新低个股**: 创历史新高/新低的股票列表 +- **涨跌幅分布**: 全市场涨跌分布柱状图 +- **动量股推荐**: 基于技术面选出的优质个股 +- **个股分析**: K线图、技术指标、基本面数据 +- **版块详情**: 历史排名、动量个股、版块K线 + +## 技术栈 + +### 前端 +- React 18 + TypeScript +- Vite 构建工具 +- Tailwind CSS 样式框架 +- shadcn/ui 组件库 +- Recharts 图表库 +- Framer Motion 动画 + +### 后端(待实现) +- Node.js / Python +- WebSocket 实时数据推送 +- RESTful API +- 数据库存储 + +### 数据源 +- AKShare (免费A股数据) +- Tushare Pro (专业数据接口) +- AllTick (实时行情API) + +## 项目结构 + +``` +app/ +├── docs/ # 开发文档 +├── src/ +│ ├── components/ # 公共组件 +│ │ ├── Navbar.tsx # 导航栏(含搜索) +│ │ ├── CandlestickChart.tsx # K线蜡烛图 +│ │ ├── StockDetailModal.tsx # 个股详情弹窗 +│ │ ├── SectorDetailModal.tsx # 版块详情弹窗 +│ │ └── Footer.tsx # 页脚 +│ ├── sections/ # 页面区块 +│ │ ├── MarketOverview.tsx # 市场概览 +│ │ ├── MomentumSectors.tsx # 动量版块 +│ │ ├── HighLowStocks.tsx # 新高新低 +│ │ ├── PriceDistribution.tsx # 涨跌分布 +│ │ └── MomentumRecommendation.tsx # 动量推荐 +│ ├── services/ # 数据服务 +│ │ └── stockData.ts # 股票数据服务 +│ ├── types/ # TypeScript类型 +│ │ └── index.ts # 类型定义 +│ ├── hooks/ # 自定义Hooks +│ ├── App.tsx # 主应用 +│ └── main.tsx # 入口文件 +├── public/ # 静态资源 +├── index.html +├── package.json +├── tailwind.config.js +├── tsconfig.json +└── vite.config.ts +``` + +## 快速开始 + +### 安装依赖 +```bash +cd /mnt/okcomputer/output/app +npm install +``` + +### 开发模式 +```bash +npm run dev +``` + +### 构建生产版本 +```bash +npm run build +``` + +### 部署 +构建后的文件位于 `dist/` 目录,可直接部署到静态服务器。 + +## 在线演示 + +**访问地址**: https://c4u7go6wz5p62.ok.kimi.link + +## 开发团队 + +- 前端开发: AI Assistant +- 设计: AI Assistant +- 产品: AI Assistant + +## 更新日志 + +### v1.0.0 (2024-03-02) +- 初始版本发布 +- 实现所有核心功能模块 +- 添加K线蜡烛图和均线功能 +- 支持搜索版块和个股 diff --git a/app/docs/02-功能清单.md b/app/docs/02-功能清单.md new file mode 100644 index 0000000..81384c8 --- /dev/null +++ b/app/docs/02-功能清单.md @@ -0,0 +1,298 @@ +# A股智投分析平台 - 功能清单 + +## 一、市场概览模块 + +### 1.1 市场指数卡片 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 上证指数 | ✅ 已实现 | 实时点位、涨跌、涨跌幅 | +| 深证成指 | ✅ 已实现 | 实时点位、涨跌、涨跌幅 | +| 创业板指 | ✅ 已实现 | 实时点位、涨跌、涨跌幅 | +| 科创50 | ✅ 已实现 | 实时点位、涨跌、涨跌幅 | + +### 1.2 涨跌家数统计 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 上涨家数 | ✅ 已实现 | 红色显示 | +| 下跌家数 | ✅ 已实现 | 绿色显示 | +| 平盘家数 | ✅ 已实现 | 灰色显示 | +| 涨跌比例条 | ✅ 已实现 | 可视化进度条 | + +### 1.3 数据更新 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 自动刷新 | ✅ 已实现 | 每30秒自动更新 | +| 数字动画 | ✅ 已实现 | 计数动画效果 | + +--- + +## 二、动量版块分析模块 + +### 2.1 版块热力图 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 20版块展示 | ✅ 已实现 | 4x5网格布局 | +| 颜色编码 | ✅ 已实现 | 红涨绿跌,深浅表示幅度 | +| 排名徽章 | ✅ 已实现 | 左上角显示排名 | +| 点击交互 | ✅ 已实现 | 打开版块详情弹窗 | + +### 2.2 动量排名列表 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| TOP5展示 | ✅ 已实现 | 动量分数最高的5个版块 | +| BOTTOM5展示 | ✅ 已实现 | 动量分数最低的5个版块 | +| 排名变化 | ✅ 已实现 | 相对于昨日的排名变化 | +| 动量分数 | ✅ 已实现 | 0-100分评分 | +| 涨跌幅 | ✅ 已实现 | 实时涨跌幅 | +| 领涨股 | ✅ 已实现 | 版块内涨幅最大的股票 | + +### 2.3 版块详情弹窗 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 历史排名 | ✅ 已实现 | 30日排名走势图 | +| 动量个股 | ✅ 已实现 | 版块内动量排行 | +| K线走势 | ✅ 已实现 | 蜡烛图+均线+成交量 | +| 标签切换 | ✅ 已实现 | 三个标签页 | + +--- + +## 三、新高新低个股模块 + +### 3.1 创新高列表 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 股票列表 | ✅ 已实现 | 20只创新高股票 | +| 排序功能 | ✅ 已实现 | 按价格/涨跌幅/新高价排序 | +| 点击交互 | ✅ 已实现 | 打开个股详情弹窗 | + +### 3.2 创新低列表 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 股票列表 | ✅ 已实现 | 20只创新低股票 | +| 排序功能 | ✅ 已实现 | 按价格/涨跌幅/新低价排序 | +| 点击交互 | ✅ 已实现 | 打开个股详情弹窗 | + +### 3.3 列表字段 +| 字段 | 状态 | 说明 | +|-----|------|------| +| 股票名称 | ✅ 已实现 | 中文名称 | +| 股票代码 | ✅ 已实现 | 6位数字代码 | +| 当前价格 | ✅ 已实现 | 最新成交价 | +| 涨跌幅 | ✅ 已实现 | 百分比显示 | +| 新高/低价 | ✅ 已实现 | 突破的价格 | +| 所属行业 | ✅ 已实现 | 行业标签 | + +--- + +## 四、涨跌幅分布模块 + +### 4.1 分布图表 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 柱状图 | ✅ 已实现 | 8个涨跌幅区间 | +| 颜色编码 | ✅ 已实现 | 深绿→浅绿→灰→浅红→深红 | +| 悬停提示 | ✅ 已实现 | 显示该区间的股票数量 | + +### 4.2 统计卡片 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 上涨家数 | ✅ 已实现 | 红色显示 | +| 下跌家数 | ✅ 已实现 | 绿色显示 | +| 总家数 | ✅ 已实现 | 白色显示 | + +--- + +## 五、动量股推荐模块 + +### 5.1 推荐卡片 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 横向滚动 | ✅ 已实现 | 左右滑动查看更多 | +| 动量分数 | ✅ 已实现 | 0-100分,颜色分级 | +| 价格信息 | ✅ 已实现 | 当前价+涨跌幅 | +| 标签系统 | ✅ 已实现 | 强势突破/量价齐升/趋势反转等 | +| 量比 | ✅ 已实现 | 成交量比率 | +| 点击交互 | ✅ 已实现 | 打开个股详情弹窗 | + +### 5.2 导航控制 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 左箭头 | ✅ 已实现 | 向左滚动 | +| 右箭头 | ✅ 已实现 | 向右滚动 | + +--- + +## 六、个股详情弹窗 + +### 6.1 基本信息 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 股票名称 | ✅ 已实现 | 中文名称+代码 | +| 当前价格 | ✅ 已实现 | 大号字体显示 | +| 涨跌幅 | ✅ 已实现 | 颜色区分涨跌 | +| 关键价格 | ✅ 已实现 | 今开/最高/最低/昨收 | + +### 6.2 K线图 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 蜡烛图 | ✅ 已实现 | 红涨绿跌 | +| 成交量附图 | ✅ 已实现 | 底部显示 | +| 均线系统 | ✅ 已实现 | MA5/MA10/MA20/MA30/MA60 | +| 周期切换 | ✅ 已实现 | 日线/周线/月线 | +| 均线开关 | ✅ 已实现 | 点击标签显示/隐藏 | +| 悬停提示 | ✅ 已实现 | 显示详细数据+均线值 | + +### 6.3 技术指标 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| MACD | ✅ 已实现 | DIF/DEA/MACD值 | +| KDJ | ✅ 已实现 | K/D/J值 | +| RSI | ✅ 已实现 | RSI6/RSI12/RSI24 | + +### 6.4 基本面数据 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 市盈率(PE) | ✅ 已实现 | 动态市盈率 | +| 市净率(PB) | ✅ 已实现 | 市净率 | +| 总市值 | ✅ 已实现 | 亿元单位 | +| 换手率 | ✅ 已实现 | 百分比 | + +### 6.5 操作建议 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 买入/持有/观望 | ✅ 已实现 | 基于技术指标综合判断 | + +--- + +## 七、版块详情弹窗 + +### 7.1 历史排名 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 排名走势图 | ✅ 已实现 | 30日排名变化 | +| 动量分数图 | ✅ 已实现 | 柱状图展示 | +| 统计卡片 | ✅ 已实现 | 当前排名/动量分/变化/成交额 | + +### 7.2 动量个股 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 个股列表 | ✅ 已实现 | 版块内动量排行 | +| 动量分数 | ✅ 已实现 | 0-100分 | +| 量比 | ✅ 已实现 | 成交量比率 | +| 标签 | ✅ 已实现 | 突破/资金流入等 | +| 点击交互 | ✅ 已实现 | 打开个股详情 | + +### 7.3 K线走势 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 蜡烛图 | ✅ 已实现 | 版块指数K线 | +| 成交量 | ✅ 已实现 | 附图显示 | +| 均线系统 | ✅ 已实现 | 5条均线 | +| 统计卡片 | ✅ 已实现 | 最新/最高/最低/涨跌/振幅 | + +--- + +## 八、搜索功能 + +### 8.1 搜索框 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 展开/收起 | ✅ 已实现 | 点击搜索图标 | +| 实时搜索 | ✅ 已实现 | 输入即搜索 | +| 输入框 | ✅ 已实现 | 支持中文输入 | + +### 8.2 搜索结果 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 版块结果 | ✅ 已实现 | 名称/排名/动量分/涨跌幅 | +| 个股结果 | ✅ 已实现 | 名称/代码/行业/价格/涨跌幅 | +| 分类展示 | ✅ 已实现 | 分版块和个股两类 | +| 点击交互 | ✅ 已实现 | 打开对应详情弹窗 | +| 无结果提示 | ✅ 已实现 | 显示提示信息 | + +--- + +## 九、导航功能 + +### 9.1 导航栏 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| Logo | ✅ 已实现 | A股智投 | +| 导航菜单 | ✅ 已实现 | 首页/动量分析/新高新低等 | +| 搜索按钮 | ✅ 已实现 | 展开搜索框 | +| 实时时间 | ✅ 已实现 | 当前时间显示 | +| 滚动效果 | ✅ 已实现 | 滚动时添加背景 | + +### 9.2 锚点导航 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 平滑滚动 | ✅ 已实现 | 点击导航平滑滚动到对应区块 | + +--- + +## 十、动画效果 + +### 10.1 页面动画 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 页面加载 | ✅ 已实现 | 淡入动画 | +| 卡片进入 | ✅ 已实现 | 依次淡入+上移 | +| 数字动画 | ✅ 已实现 | 计数动画 | + +### 10.2 交互动画 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 卡片悬停 | ✅ 已实现 | 缩放+边框变色 | +| 表格行悬停 | ✅ 已实现 | 背景高亮 | +| 按钮悬停 | ✅ 已实现 | 亮度提升 | +| 弹窗动画 | ✅ 已实现 | 缩放+淡入 | + +--- + +## 十一、响应式设计 + +### 11.1 断点适配 +| 断点 | 状态 | 说明 | +|-----|------|------| +| 桌面 (>1200px) | ✅ 已实现 | 4列布局 | +| 平板 (768-1200px) | ✅ 已实现 | 2列布局 | +| 手机 (<768px) | ✅ 已实现 | 单列布局 | + +--- + +## 十二、待实现功能 + +### 12.1 后端接口 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 实时行情API | ⏳ 待实现 | WebSocket推送 | +| 历史数据API | ⏳ 待实现 | K线历史数据 | +| 版块数据API | ⏳ 待实现 | 版块行情和排名 | +| 搜索API | ⏳ 待实现 | 股票和版块搜索 | +| 用户系统 | ⏳ 待实现 | 登录/注册/收藏 | + +### 12.2 数据存储 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 数据库设计 | ⏳ 待实现 | MySQL/PostgreSQL | +| 缓存层 | ⏳ 待实现 | Redis | +| 数据同步 | ⏳ 待实现 | 定时任务 | + +### 12.3 高级功能 +| 功能点 | 状态 | 说明 | +|-------|------|------| +| 自选股 | ⏳ 待实现 | 用户自选股票 | +| 预警提醒 | ⏳ 待实现 | 价格/涨跌幅预警 | +| 策略回测 | ⏳ 待实现 | 历史策略验证 | +| 模拟交易 | ⏳ 待实现 | 虚拟资金交易 | +| 新闻资讯 | ⏳ 待实现 | 财经新闻 | +| 财报数据 | ⏳ 待实现 | 财务报表 | + +--- + +## 功能统计 + +- **已实现功能**: 68项 +- **待实现功能**: 12项 +- **总计**: 80项 +- **完成度**: 85% diff --git a/app/docs/03-技术架构.md b/app/docs/03-技术架构.md new file mode 100644 index 0000000..d2a37ea --- /dev/null +++ b/app/docs/03-技术架构.md @@ -0,0 +1,525 @@ +# A股智投分析平台 - 技术架构 + +## 一、整体架构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 前端层 (Frontend) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ React 18 │ │ TypeScript │ │ Vite 构建 │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │Tailwind CSS │ │ shadcn/ui │ │ Recharts 图表 │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ API 网关层 │ +│ (Nginx / AWS API Gateway / 阿里云) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 后端层 (Backend) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ Node.js │ │ Python │ │ WebSocket 服务 │ │ +│ │ Express │ │ FastAPI │ │ (Socket.io) │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 数据层 (Data) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ MySQL │ │ Redis │ │ 外部数据接口 │ │ +│ │ (主存储) │ │ (缓存) │ │ (AKShare/Tushare) │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 二、前端架构 + +### 2.1 技术选型 + +| 技术 | 版本 | 用途 | +|-----|------|------| +| React | 18.x | UI框架 | +| TypeScript | 5.x | 类型系统 | +| Vite | 5.x | 构建工具 | +| Tailwind CSS | 3.x | 样式框架 | +| shadcn/ui | latest | UI组件库 | +| Recharts | 2.x | 图表库 | +| Framer Motion | 11.x | 动画库 | +| Lucide React | latest | 图标库 | + +### 2.2 目录结构 + +``` +src/ +├── components/ # 公共组件 +│ ├── Navbar.tsx # 导航栏 +│ ├── CandlestickChart.tsx # K线蜡烛图 +│ ├── StockDetailModal.tsx # 个股详情弹窗 +│ ├── SectorDetailModal.tsx # 版块详情弹窗 +│ └── Footer.tsx # 页脚 +│ +├── sections/ # 页面区块组件 +│ ├── MarketOverview.tsx # 市场概览 +│ ├── MomentumSectors.tsx # 动量版块 +│ ├── HighLowStocks.tsx # 新高新低 +│ ├── PriceDistribution.tsx # 涨跌分布 +│ └── MomentumRecommendation.tsx # 动量推荐 +│ +├── services/ # 数据服务层 +│ └── stockData.ts # 股票数据服务 +│ +├── types/ # TypeScript类型定义 +│ └── index.ts # 类型定义 +│ +├── hooks/ # 自定义React Hooks +│ └── useStockData.ts # 股票数据Hook +│ +├── utils/ # 工具函数 +│ └── format.ts # 格式化函数 +│ +├── App.tsx # 主应用组件 +├── main.tsx # 应用入口 +└── index.css # 全局样式 +``` + +### 2.3 组件设计原则 + +1. **单一职责**: 每个组件只负责一个功能 +2. **可复用性**: 公共组件抽离,提高复用 +3. **类型安全**: 使用TypeScript严格类型检查 +4. **性能优化**: 使用useMemo、useCallback优化渲染 + +### 2.4 状态管理 + +当前使用React内置状态管理: +- `useState`: 组件内部状态 +- `useEffect`: 副作用处理 +- `useContext`: 跨组件状态(待实现) + +未来可考虑: +- Zustand: 轻量级状态管理 +- Redux Toolkit: 复杂状态管理 + +## 三、后端架构(待实现) + +### 3.1 技术选型方案 + +#### 方案一: Node.js + Express +```javascript +// 技术栈 +- Node.js 20.x +- Express 4.x +- TypeScript 5.x +- Prisma ORM +- Socket.io (WebSocket) +``` + +#### 方案二: Python + FastAPI +```python +# 技术栈 +- Python 3.11 +- FastAPI +- SQLAlchemy +- WebSockets +- Celery (定时任务) +``` + +### 3.2 目录结构 + +``` +backend/ +├── src/ +│ ├── controllers/ # 控制器层 +│ │ ├── stockController.ts +│ │ ├── sectorController.ts +│ │ └── userController.ts +│ │ +│ ├── services/ # 业务逻辑层 +│ │ ├── stockService.ts +│ │ ├── sectorService.ts +│ │ └── dataSyncService.ts +│ │ +│ ├── models/ # 数据模型层 +│ │ ├── Stock.ts +│ │ ├── Sector.ts +│ │ └── User.ts +│ │ +│ ├── routes/ # 路由定义 +│ │ ├── stockRoutes.ts +│ │ ├── sectorRoutes.ts +│ │ └── userRoutes.ts +│ │ +│ ├── middleware/ # 中间件 +│ │ ├── auth.ts +│ │ ├── errorHandler.ts +│ │ └── rateLimiter.ts +│ │ +│ ├── utils/ # 工具函数 +│ │ ├── logger.ts +│ │ ├── validator.ts +│ │ └── formatter.ts +│ │ +│ ├── config/ # 配置文件 +│ │ ├── database.ts +│ │ ├── redis.ts +│ │ └── constants.ts +│ │ +│ ├── websocket/ # WebSocket服务 +│ │ └── stockSocket.ts +│ │ +│ └── app.ts # 应用入口 +│ +├── prisma/ # Prisma ORM +│ └── schema.prisma +│ +├── tests/ # 测试文件 +├── Dockerfile +├── docker-compose.yml +└── package.json +``` + +## 四、数据库设计 + +### 4.1 MySQL 表结构 + +```sql +-- 股票基本信息表 +CREATE TABLE stocks ( + id INT PRIMARY KEY AUTO_INCREMENT, + code VARCHAR(10) NOT NULL COMMENT '股票代码', + name VARCHAR(50) NOT NULL COMMENT '股票名称', + industry VARCHAR(50) COMMENT '所属行业', + market VARCHAR(10) COMMENT '所属市场(沪市/深市)', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_code (code), + INDEX idx_industry (industry) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 版块信息表 +CREATE TABLE sectors ( + id INT PRIMARY KEY AUTO_INCREMENT, + code VARCHAR(10) NOT NULL COMMENT '版块代码', + name VARCHAR(50) NOT NULL COMMENT '版块名称', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_code (code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 股票与版块关联表 +CREATE TABLE stock_sectors ( + stock_code VARCHAR(10) NOT NULL, + sector_code VARCHAR(10) NOT NULL, + PRIMARY KEY (stock_code, sector_code), + FOREIGN KEY (stock_code) REFERENCES stocks(code), + FOREIGN KEY (sector_code) REFERENCES sectors(code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 实时行情表 +CREATE TABLE stock_quotes ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + stock_code VARCHAR(10) NOT NULL, + price DECIMAL(10, 2) NOT NULL COMMENT '当前价格', + open DECIMAL(10, 2) COMMENT '开盘价', + high DECIMAL(10, 2) COMMENT '最高价', + low DECIMAL(10, 2) COMMENT '最低价', + pre_close DECIMAL(10, 2) COMMENT '昨收', + volume BIGINT COMMENT '成交量', + turnover BIGINT COMMENT '成交额', + change_percent DECIMAL(5, 2) COMMENT '涨跌幅', + quote_time TIMESTAMP NOT NULL, + INDEX idx_code_time (stock_code, quote_time), + INDEX idx_time (quote_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- K线数据表 +CREATE TABLE kline_data ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + stock_code VARCHAR(10) NOT NULL, + period VARCHAR(10) NOT NULL COMMENT '周期(day/week/month)', + date DATE NOT NULL, + open DECIMAL(10, 2) NOT NULL, + high DECIMAL(10, 2) NOT NULL, + low DECIMAL(10, 2) NOT NULL, + close DECIMAL(10, 2) NOT NULL, + volume BIGINT NOT NULL, + ma5 DECIMAL(10, 2), + ma10 DECIMAL(10, 2), + ma20 DECIMAL(10, 2), + ma30 DECIMAL(10, 2), + ma60 DECIMAL(10, 2), + UNIQUE KEY uk_code_period_date (stock_code, period, date), + INDEX idx_date (date) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 版块行情表 +CREATE TABLE sector_quotes ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + sector_code VARCHAR(10) NOT NULL, + change_percent DECIMAL(5, 2) COMMENT '涨跌幅', + momentum_score DECIMAL(5, 2) COMMENT '动量分数', + rank INT COMMENT '排名', + volume BIGINT COMMENT '成交量', + turnover BIGINT COMMENT '成交额', + quote_time TIMESTAMP NOT NULL, + INDEX idx_code_time (sector_code, quote_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 用户表 +CREATE TABLE users ( + id INT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_email (email) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 用户自选股表 +CREATE TABLE user_favorites ( + user_id INT NOT NULL, + stock_code VARCHAR(10) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, stock_code), + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (stock_code) REFERENCES stocks(code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +### 4.2 Redis 缓存设计 + +``` +# 实时行情缓存 (TTL: 60秒) +stock:quote:{stock_code} -> Hash + +# 版块行情缓存 (TTL: 60秒) +sector:quote:{sector_code} -> Hash + +# K线数据缓存 (TTL: 300秒) +kline:{stock_code}:{period} -> Sorted Set + +# 搜索缓存 (TTL: 300秒) +search:{keyword} -> List + +# 用户会话 (TTL: 86400秒) +session:{session_id} -> Hash + +# 热门股票排行 (TTL: 300秒) +hot:stocks -> Sorted Set + +# 涨跌幅分布 (TTL: 60秒) +price:distribution -> Hash +``` + +## 五、API 设计规范 + +### 5.1 接口规范 + +``` +Base URL: https://api.aguzhitou.com/v1 + +请求格式: +- Content-Type: application/json +- Authorization: Bearer {token} + +响应格式: +{ + "code": 200, + "message": "success", + "data": { ... } +} + +错误码: +- 200: 成功 +- 400: 请求参数错误 +- 401: 未授权 +- 403: 禁止访问 +- 404: 资源不存在 +- 500: 服务器错误 +``` + +### 5.2 接口列表 + +详见 [04-API接口文档.md](./04-API接口文档.md) + +## 六、部署架构 + +### 6.1 生产环境 + +``` + ┌─────────────┐ + │ 用户 │ + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ CDN加速 │ + │ (静态资源) │ + └──────┬──────┘ + │ + ┌────────────┼────────────┐ + │ │ │ + ┌──────▼──────┐ │ ┌───────▼───────┐ + │ Nginx │ │ │ Nginx │ + │ (负载均衡) │ │ │ (负载均衡) │ + └──────┬──────┘ │ └───────┬───────┘ + │ │ │ + ┌──────┴───────────┴────────────┴──────┐ + │ │ + │ Kubernetes Cluster │ + │ ┌─────────┐ ┌─────────┐ │ + │ │ Frontend│ │ Frontend│ │ + │ │ Pod x3 │ │ Pod x3 │ │ + │ └────┬────┘ └────┬────┘ │ + │ └────────────┘ │ + │ │ │ + │ ┌───────────┴───────────┐ │ + │ │ Backend │ │ + │ │ Pod x3 │ │ + │ └───────────┬───────────┘ │ + │ │ │ + │ ┌───────────┴───────────┐ │ + │ │ WebSocket │ │ + │ │ Pod x2 │ │ + │ └───────────────────────┘ │ + │ │ + └───────────────────────────────────────┘ + │ + ┌───────────────────┼───────────────────┐ + │ │ │ +┌──────▼──────┐ ┌───────▼────────┐ ┌──────▼──────┐ +│ MySQL │ │ Redis │ │ 外部API │ +│ (主从集群) │ │ (Cluster) │ │ (AKShare等) │ +└─────────────┘ └────────────────┘ └─────────────┘ +``` + +### 6.2 Docker 部署 + +```dockerfile +# Dockerfile +FROM node:20-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci --only=production + +COPY . . +RUN npm run build + +EXPOSE 3000 + +CMD ["node", "dist/server.js"] +``` + +```yaml +# docker-compose.yml +version: '3.8' + +services: + frontend: + build: ./frontend + ports: + - "80:80" + depends_on: + - backend + + backend: + build: ./backend + ports: + - "3000:3000" + environment: + - DATABASE_URL=mysql://user:pass@mysql:3306/aguzhitou + - REDIS_URL=redis://redis:6379 + depends_on: + - mysql + - redis + + mysql: + image: mysql:8.0 + environment: + - MYSQL_ROOT_PASSWORD=rootpass + - MYSQL_DATABASE=aguzhitou + volumes: + - mysql_data:/var/lib/mysql + + redis: + image: redis:7-alpine + volumes: + - redis_data:/data + +volumes: + mysql_data: + redis_data: +``` + +## 七、性能优化 + +### 7.1 前端优化 +- 代码分割 (Code Splitting) +- 懒加载 (Lazy Loading) +- 图片优化 (WebP格式) +- 缓存策略 (Service Worker) +- CDN加速 + +### 7.2 后端优化 +- 数据库索引优化 +- Redis缓存 +- 接口限流 +- 连接池 +- 异步处理 + +### 7.3 监控 +- 前端性能监控 (Web Vitals) +- 后端APM (New Relic / Sentry) +- 日志收集 (ELK Stack) +- 告警通知 + +## 八、安全规范 + +### 8.1 前端安全 +- XSS防护 +- CSRF防护 +- HTTPS强制 +- Content Security Policy + +### 8.2 后端安全 +- 接口鉴权 (JWT) +- 参数校验 +- SQL注入防护 +- 敏感信息加密 +- 访问日志 + +## 九、开发规范 + +### 9.1 代码规范 +- ESLint + Prettier +- Git Commit 规范 +- 代码审查 (Code Review) +- 单元测试覆盖率 > 80% + +### 9.2 Git 工作流 +``` +main (生产分支) + ↑ +develop (开发分支) + ↑ +feature/* (功能分支) + ↑ +hotfix/* (紧急修复) +``` + +## 十、技术债务 + +| 问题 | 优先级 | 解决方案 | +|-----|-------|---------| +| 缺少后端API | 高 | 开发Node.js/Python后端 | +| 使用模拟数据 | 高 | 接入真实数据源 | +| 缺少用户系统 | 中 | 开发登录注册功能 | +| 缺少测试 | 中 | 补充单元测试和E2E测试 | +| 缺少CI/CD | 低 | 配置GitHub Actions | diff --git a/app/docs/04-API接口文档.md b/app/docs/04-API接口文档.md new file mode 100644 index 0000000..eabba02 --- /dev/null +++ b/app/docs/04-API接口文档.md @@ -0,0 +1,808 @@ +# A股智投分析平台 - API接口文档 + +## 基础信息 + +``` +Base URL: https://api.aguzhitou.com/v1 +WebSocket: wss://ws.aguzhitou.com + +请求头: +Content-Type: application/json +Authorization: Bearer {access_token} +``` + +--- + +## 一、市场数据接口 + +### 1.1 获取市场指数 + +**请求** +```http +GET /market/indices +``` + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "name": "上证指数", + "code": "000001", + "current": 3050.32, + "change": 15.23, + "changePercent": 0.50, + "volume": 450000000, + "turnover": 4200000000 + }, + { + "name": "深证成指", + "code": "399001", + "current": 9850.15, + "change": -25.60, + "changePercent": -0.26, + "volume": 520000000, + "turnover": 5100000000 + } + ] +} +``` + +### 1.2 获取涨跌家数统计 + +**请求** +```http +GET /market/updown-stats +``` + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": { + "up": 2850, + "down": 1950, + "flat": 200 + } +} +``` + +### 1.3 获取涨跌幅分布 + +**请求** +```http +GET /market/price-distribution +``` + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { "range": "<-7%", "min": -999, "max": -7, "count": 45 }, + { "range": "-7~-5%", "min": -7, "max": -5, "count": 128 }, + { "range": "-5~-3%", "min": -5, "max": -3, "count": 356 }, + { "range": "-3~0%", "min": -3, "max": 0, "count": 1421 }, + { "range": "0~3%", "min": 0, "max": 3, "count": 1856 }, + { "range": "3~5%", "min": 3, "max": 5, "count": 623 }, + { "range": "5~7%", "min": 5, "max": 7, "count": 312 }, + { "range": ">7%", "min": 7, "max": 999, "count": 259 } + ] +} +``` + +--- + +## 二、版块数据接口 + +### 2.1 获取版块列表 + +**请求** +```http +GET /sectors +``` + +**查询参数** +| 参数 | 类型 | 必填 | 说明 | +|-----|------|------|------| +| sort | string | 否 | 排序方式: momentum/rank/change | +| order | string | 否 | 排序: asc/desc | + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "name": "半导体", + "code": "880491", + "change": 3.25, + "changePercent": 3.25, + "volume": 25000000, + "turnover": 850000000, + "leadingStock": "中芯国际", + "momentumScore": 85.5, + "rank": 1, + "previousRank": 3, + "rankChange": 2 + } + ] +} +``` + +### 2.2 获取版块详情 + +**请求** +```http +GET /sectors/{sector_code} +``` + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": { + "name": "半导体", + "code": "880491", + "change": 3.25, + "changePercent": 3.25, + "volume": 25000000, + "turnover": 850000000, + "leadingStock": "中芯国际", + "momentumScore": 85.5, + "rank": 1, + "previousRank": 3, + "rankChange": 2 + } +} +``` + +### 2.3 获取版块历史排名 + +**请求** +```http +GET /sectors/{sector_code}/rank-history +``` + +**查询参数** +| 参数 | 类型 | 必填 | 说明 | +|-----|------|------|------| +| days | number | 否 | 天数,默认30 | + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "date": "2024-01-15", + "rank": 5, + "momentumScore": 72.5, + "topStock": "中芯国际" + }, + { + "date": "2024-01-16", + "rank": 3, + "momentumScore": 78.2, + "topStock": "韦尔股份" + } + ] +} +``` + +### 2.4 获取版块内股票 + +**请求** +```http +GET /sectors/{sector_code}/stocks +``` + +**查询参数** +| 参数 | 类型 | 必填 | 说明 | +|-----|------|------|------| +| sort | string | 否 | 排序: momentum/change/volume | +| limit | number | 否 | 数量限制,默认20 | + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "code": "688981", + "name": "中芯国际", + "price": 52.35, + "change": 2.15, + "changePercent": 4.28, + "volume": 12500000, + "turnover": 654000000, + "marketCap": 415000000000, + "pe": 45.2, + "pb": 3.8, + "industry": "半导体" + } + ] +} +``` + +### 2.5 获取版块内动量股票 + +**请求** +```http +GET /sectors/{sector_code}/momentum-stocks +``` + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "code": "688981", + "name": "中芯国际", + "price": 52.35, + "change": 2.15, + "changePercent": 4.28, + "volume": 12500000, + "turnover": 654000000, + "industry": "半导体", + "momentumScore": 92, + "tags": ["强势突破", "量价齐升"], + "volumeRatio": 3.5, + "breakThrough": true + } + ] +} +``` + +### 2.6 获取版块K线数据 + +**请求** +```http +GET /sectors/{sector_code}/kline +``` + +**查询参数** +| 参数 | 类型 | 必填 | 说明 | +|-----|------|------|------| +| period | string | 否 | 周期: day/week/month,默认day | +| days | number | 否 | 天数,默认60 | + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "date": "2024-01-15", + "open": 2850.25, + "high": 2895.60, + "low": 2835.15, + "close": 2880.35, + "volume": 45000000, + "ma5": 2865.20, + "ma10": 2850.80, + "ma20": 2835.50 + } + ] +} +``` + +--- + +## 三、股票数据接口 + +### 3.1 搜索股票 + +**请求** +```http +GET /stocks/search +``` + +**查询参数** +| 参数 | 类型 | 必填 | 说明 | +|-----|------|------|------| +| keyword | string | 是 | 搜索关键词 | +| type | string | 否 | 类型: stock/sector/all,默认all | + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": { + "sectors": [ + { + "name": "半导体", + "code": "880491", + "changePercent": 3.25, + "rank": 1, + "momentumScore": 85.5 + } + ], + "stocks": [ + { + "code": "688981", + "name": "中芯国际", + "price": 52.35, + "changePercent": 4.28, + "industry": "半导体" + } + ] + } +} +``` + +### 3.2 获取股票详情 + +**请求** +```http +GET /stocks/{stock_code} +``` + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": { + "code": "688981", + "name": "中芯国际", + "price": 52.35, + "change": 2.15, + "changePercent": 4.28, + "volume": 12500000, + "turnover": 654000000, + "marketCap": 415000000000, + "pe": 45.2, + "pb": 3.8, + "industry": "半导体", + "open": 50.50, + "high": 53.20, + "low": 50.20, + "preClose": 50.20, + "amplitude": 5.98, + "turnoverRate": 2.35, + "macd": { + "dif": 0.85, + "dea": 0.62, + "macd": 0.46 + }, + "kdj": { + "k": 75.2, + "d": 68.5, + "j": 88.6 + }, + "rsi": { + "rsi6": 72.5, + "rsi12": 68.3, + "rsi24": 65.1 + } + } +} +``` + +### 3.3 获取股票K线数据 + +**请求** +```http +GET /stocks/{stock_code}/kline +``` + +**查询参数** +| 参数 | 类型 | 必填 | 说明 | +|-----|------|------|------| +| period | string | 否 | 周期: day/week/month,默认day | +| days | number | 否 | 天数,默认60 | + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "date": "2024-01-15", + "open": 50.50, + "high": 53.20, + "low": 50.20, + "close": 52.35, + "volume": 12500000, + "ma5": 51.20, + "ma10": 50.80, + "ma20": 49.50, + "ma30": 48.90, + "ma60": 47.20 + } + ] +} +``` + +### 3.4 获取新高股票 + +**请求** +```http +GET /stocks/new-high +``` + +**查询参数** +| 参数 | 类型 | 必填 | 说明 | +|-----|------|------|------| +| days | number | 否 | 近N天,默认20 | +| limit | number | 否 | 数量限制,默认20 | + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "code": "688981", + "name": "中芯国际", + "price": 52.35, + "change": 2.15, + "changePercent": 4.28, + "volume": 12500000, + "turnover": 654000000, + "industry": "半导体", + "highLowPrice": 52.35, + "date": "2024-01-15", + "daysToHighLow": 15 + } + ] +} +``` + +### 3.5 获取新低股票 + +**请求** +```http +GET /stocks/new-low +``` + +**查询参数** +| 参数 | 类型 | 必填 | 说明 | +|-----|------|------|------| +| days | number | 否 | 近N天,默认20 | +| limit | number | 否 | 数量限制,默认20 | + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "code": "600519", + "name": "贵州茅台", + "price": 1650.00, + "change": -25.00, + "changePercent": -1.49, + "volume": 850000, + "turnover": 1402500000, + "industry": "白酒", + "highLowPrice": 1650.00, + "date": "2024-01-15", + "daysToHighLow": 8 + } + ] +} +``` + +### 3.6 获取动量股票推荐 + +**请求** +```http +GET /stocks/momentum-recommendation +``` + +**查询参数** +| 参数 | 类型 | 必填 | 说明 | +|-----|------|------|------| +| limit | number | 否 | 数量限制,默认15 | + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "code": "688981", + "name": "中芯国际", + "price": 52.35, + "change": 2.15, + "changePercent": 4.28, + "volume": 12500000, + "turnover": 654000000, + "industry": "半导体", + "momentumScore": 92, + "tags": ["强势突破", "量价齐升"], + "volumeRatio": 3.5, + "breakThrough": true + } + ] +} +``` + +--- + +## 四、用户接口 + +### 4.1 用户注册 + +**请求** +```http +POST /users/register +``` + +**请求体** +```json +{ + "username": "testuser", + "email": "test@example.com", + "password": "password123" +} +``` + +**响应** +```json +{ + "code": 200, + "message": "注册成功", + "data": { + "id": 1, + "username": "testuser", + "email": "test@example.com", + "token": "eyJhbGciOiJIUzI1NiIs..." + } +} +``` + +### 4.2 用户登录 + +**请求** +```http +POST /users/login +``` + +**请求体** +```json +{ + "email": "test@example.com", + "password": "password123" +} +``` + +**响应** +```json +{ + "code": 200, + "message": "登录成功", + "data": { + "id": 1, + "username": "testuser", + "email": "test@example.com", + "token": "eyJhbGciOiJIUzI1NiIs..." + } +} +``` + +### 4.3 获取用户信息 + +**请求** +```http +GET /users/profile +Authorization: Bearer {token} +``` + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": { + "id": 1, + "username": "testuser", + "email": "test@example.com", + "createdAt": "2024-01-15T10:00:00Z" + } +} +``` + +### 4.4 获取自选股 + +**请求** +```http +GET /users/favorites +Authorization: Bearer {token} +``` + +**响应** +```json +{ + "code": 200, + "message": "success", + "data": [ + { + "code": "688981", + "name": "中芯国际", + "price": 52.35, + "changePercent": 4.28, + "industry": "半导体" + } + ] +} +``` + +### 4.5 添加自选股 + +**请求** +```http +POST /users/favorites +Authorization: Bearer {token} +``` + +**请求体** +```json +{ + "stockCode": "688981" +} +``` + +**响应** +```json +{ + "code": 200, + "message": "添加成功", + "data": null +} +``` + +### 4.6 删除自选股 + +**请求** +```http +DELETE /users/favorites/{stock_code} +Authorization: Bearer {token} +``` + +**响应** +```json +{ + "code": 200, + "message": "删除成功", + "data": null +} +``` + +--- + +## 五、WebSocket 实时数据 + +### 5.1 连接 + +```javascript +const ws = new WebSocket('wss://ws.aguzhitou.com'); + +ws.onopen = () => { + // 订阅股票行情 + ws.send(JSON.stringify({ + action: 'subscribe', + channels: ['stock:688981', 'stock:600519'] + })); + + // 订阅版块行情 + ws.send(JSON.stringify({ + action: 'subscribe', + channels: ['sector:880491'] + })); +}; + +ws.onmessage = (event) => { + const data = JSON.parse(event.data); + console.log(data); +}; +``` + +### 5.2 订阅消息格式 + +**订阅请求** +```json +{ + "action": "subscribe", + "channels": ["stock:688981", "stock:600519"] +} +``` + +**取消订阅** +```json +{ + "action": "unsubscribe", + "channels": ["stock:688981"] +} +``` + +### 5.3 推送数据格式 + +**股票行情推送** +```json +{ + "channel": "stock:688981", + "type": "quote", + "data": { + "code": "688981", + "name": "中芯国际", + "price": 52.35, + "change": 2.15, + "changePercent": 4.28, + "volume": 12500000, + "turnover": 654000000, + "time": "2024-01-15T14:30:00Z" + } +} +``` + +**版块行情推送** +```json +{ + "channel": "sector:880491", + "type": "quote", + "data": { + "code": "880491", + "name": "半导体", + "changePercent": 3.25, + "momentumScore": 85.5, + "rank": 1, + "time": "2024-01-15T14:30:00Z" + } +} +``` + +--- + +## 六、错误码 + +| 错误码 | 说明 | +|-------|------| +| 200 | 成功 | +| 400 | 请求参数错误 | +| 401 | 未授权,需要登录 | +| 403 | 禁止访问 | +| 404 | 资源不存在 | +| 429 | 请求过于频繁 | +| 500 | 服务器内部错误 | +| 503 | 服务暂时不可用 | + +--- + +## 七、限流策略 + +| 接口类型 | 限流策略 | +|---------|---------| +| 公开接口 | 100次/分钟/IP | +| 需要登录 | 1000次/分钟/用户 | +| WebSocket | 10个连接/IP | + +--- + +## 八、数据更新频率 + +| 数据类型 | 更新频率 | +|---------|---------| +| 实时行情 | 3秒 | +| K线数据 | 1分钟 | +| 版块排名 | 1分钟 | +| 涨跌幅分布 | 1分钟 | +| 历史数据 | 每日收盘后 | diff --git a/app/docs/05-前端实现.md b/app/docs/05-前端实现.md new file mode 100644 index 0000000..3ecf02b --- /dev/null +++ b/app/docs/05-前端实现.md @@ -0,0 +1,482 @@ +# A股智投分析平台 - 前端实现文档 + +## 一、组件清单 + +### 1.1 公共组件 + +| 组件名 | 文件路径 | 功能描述 | 复杂度 | +|-------|---------|---------|-------| +| Navbar | `components/Navbar.tsx` | 导航栏,含搜索功能 | 高 | +| CandlestickChart | `components/CandlestickChart.tsx` | K线蜡烛图+均线+成交量 | 高 | +| StockDetailModal | `components/StockDetailModal.tsx` | 个股详情弹窗 | 高 | +| SectorDetailModal | `components/SectorDetailModal.tsx` | 版块详情弹窗 | 高 | +| Footer | `components/Footer.tsx` | 页脚 | 低 | + +### 1.2 页面区块组件 + +| 组件名 | 文件路径 | 功能描述 | 复杂度 | +|-------|---------|---------|-------| +| MarketOverview | `sections/MarketOverview.tsx` | 市场概览 | 中 | +| MomentumSectors | `sections/MomentumSectors.tsx` | 动量版块分析 | 高 | +| HighLowStocks | `sections/HighLowStocks.tsx` | 新高新低个股 | 中 | +| PriceDistribution | `sections/PriceDistribution.tsx` | 涨跌幅分布 | 中 | +| MomentumRecommendation | `sections/MomentumRecommendation.tsx` | 动量股推荐 | 中 | + +--- + +## 二、核心组件详解 + +### 2.1 CandlestickChart 组件 + +**功能**: K线蜡烛图,支持均线和成交量 + +**Props** +```typescript +interface CandlestickChartProps { + data: KLineData[]; // K线数据 + height?: number; // 图表高度,默认400 + showVolume?: boolean; // 是否显示成交量,默认true + showMaSettings?: boolean; // 是否显示均线设置,默认true +} +``` + +**实现要点** +- 使用 Recharts ComposedChart 组合图表 +- 自定义蜡烛图形状(红涨绿跌) +- 支持5条均线(MA5/MA10/MA20/MA30/MA60) +- 成交量附图,颜色与K线对应 +- 点击均线标签切换显示/隐藏 + +**代码片段** +```typescript +// 均线配置 +const defaultMaPeriods: MaPeriod[] = [ + { key: 'ma5', label: 'MA5', days: 5, color: '#ff9f43', visible: true }, + { key: 'ma10', label: 'MA10', days: 10, color: '#3498db', visible: true }, + { key: 'ma20', label: 'MA20', days: 20, color: '#9b59b6', visible: true }, + { key: 'ma30', label: 'MA30', days: 30, color: '#e74c3c', visible: false }, + { key: 'ma60', label: 'MA60', days: 60, color: '#2ecc71', visible: false }, +]; + +// 渲染蜡烛图 +const renderCandle = (props, maxPrice, minPrice, pricePadding) => { + const { x, y, width, height, payload } = props; + const { open, close } = payload; + const isUp = close >= open; + const color = isUp ? '#ff3b30' : '#00c853'; + + // 计算影线坐标 + // 计算实体坐标 + // 返回SVG元素 +}; +``` + +### 2.2 StockDetailModal 组件 + +**功能**: 个股详情弹窗 + +**Props** +```typescript +interface StockDetailModalProps { + stockCode: string | null; // 股票代码 + isOpen: boolean; // 是否打开 + onClose: () => void; // 关闭回调 +} +``` + +**实现要点** +- 使用 Framer Motion 实现动画 +- 获取个股详情和K线数据 +- 显示基本信息、K线图、技术指标、基本面 +- 支持日线/周线/月线切换 + +**代码片段** +```typescript +const [stock, setStock] = useState(null); +const [klineData, setKlineData] = useState([]); +const [timeRange, setTimeRange] = useState<'day' | 'week' | 'month'>('day'); + +useEffect(() => { + if (stockCode && isOpen) { + setStock(stockDataService.getStockDetail(stockCode)); + setKlineData(stockDataService.getKLineData(stockCode, 60)); + } +}, [stockCode, isOpen]); +``` + +### 2.3 SectorDetailModal 组件 + +**功能**: 版块详情弹窗 + +**Props** +```typescript +interface SectorDetailModalProps { + sector: Sector | null; // 版块数据 + isOpen: boolean; // 是否打开 + onClose: () => void; // 关闭回调 + onStockClick?: (code: string) => void; // 股票点击回调 +} +``` + +**实现要点** +- 三个标签页:历史排名、动量个股、K线走势 +- 历史排名使用组合图表(排名线+动量分柱状图) +- 动量个股列表支持点击打开个股详情 +- K线使用 CandlestickChart 组件 + +### 2.4 Navbar 组件 + +**功能**: 导航栏,含搜索功能 + +**Props** +```typescript +interface NavbarProps { + onSectorClick?: (sector: Sector) => void; // 版块点击回调 + onStockClick?: (code: string) => void; // 个股点击回调 +} +``` + +**实现要点** +- 滚动时添加背景和边框 +- 搜索框展开/收起动画 +- 实时搜索,分类展示结果 +- 点击结果打开对应详情 + +**代码片段** +```typescript +const [searchOpen, setSearchOpen] = useState(false); +const [searchKeyword, setSearchKeyword] = useState(''); +const [searchResults, setSearchResults] = useState({ sectors: [], stocks: [] }); + +// 搜索逻辑 +useEffect(() => { + if (searchKeyword.trim().length >= 1) { + const sectors = stockDataService.searchSectors(searchKeyword); + const stocks = stockDataService.searchStocks(searchKeyword); + setSearchResults({ sectors, stocks }); + } +}, [searchKeyword]); +``` + +--- + +## 三、数据服务 + +### 3.1 StockDataService + +**文件**: `services/stockData.ts` + +**主要方法** + +| 方法名 | 功能 | 返回值 | +|-------|------|-------| +| `getMarketIndices()` | 获取市场指数 | `MarketIndex[]` | +| `getUpDownStats()` | 获取涨跌家数 | `{up, down, flat}` | +| `getSectorsWithMomentum()` | 获取版块列表(带动量) | `Sector[]` | +| `getSectorRankHistory(name)` | 获取版块历史排名 | `SectorMomentumHistory[]` | +| `getSectorStocks(name)` | 获取版块内股票 | `Stock[]` | +| `getSectorMomentumStocks(name)` | 获取版块内动量股票 | `MomentumStock[]` | +| `getSectorKLineData(name, days)` | 获取版块K线 | `KLineData[]` | +| `getNewHighStocks()` | 获取创新高股票 | `HighLowStock[]` | +| `getNewLowStocks()` | 获取创新低股票 | `HighLowStock[]` | +| `getPriceDistribution()` | 获取涨跌幅分布 | `PriceDistribution[]` | +| `getMomentumStocks()` | 获取动量股推荐 | `MomentumStock[]` | +| `getStockDetail(code)` | 获取个股详情 | `StockDetail` | +| `getKLineData(code, days)` | 获取个股K线 | `KLineData[]` | +| `searchSectors(keyword)` | 搜索版块 | `Sector[]` | +| `searchStocks(keyword)` | 搜索股票 | `Stock[]` | + +### 3.2 均线计算 + +**代码片段** +```typescript +private calculateMA(data: KLineData[]): KLineData[] { + const periods = [5, 10, 20, 30, 60]; + + return data.map((item, index) => { + const ma: Record = {}; + + for (const period of periods) { + if (index >= period - 1) { + const sum = data + .slice(index - period + 1, index + 1) + .reduce((acc, d) => acc + d.close, 0); + ma[`ma${period}`] = this.formatNumber(sum / period); + } + } + + return { ...item, ...ma }; + }); +} +``` + +--- + +## 四、类型定义 + +### 4.1 核心类型 + +**文件**: `types/index.ts` + +```typescript +// 股票基础信息 +export interface Stock { + code: string; // 股票代码 + name: string; // 股票名称 + price: number; // 当前价格 + change: number; // 涨跌额 + changePercent: number; // 涨跌幅 + volume: number; // 成交量 + turnover: number; // 成交额 + marketCap?: number; // 总市值 + pe?: number; // 市盈率 + pb?: number; // 市净率 + industry?: string; // 所属行业 +} + +// 版块信息 +export interface Sector { + name: string; // 版块名称 + code: string; // 版块代码 + change: number; // 涨跌额 + changePercent: number; // 涨跌幅 + volume: number; // 成交量 + turnover: number; // 成交额 + leadingStock?: string; // 领涨股 + momentumScore?: number; // 动量分数 + rank?: number; // 当前排名 + previousRank?: number; // 昨日排名 + rankChange?: number; // 排名变化 +} + +// K线数据 +export interface KLineData { + date: string; // 日期 + open: number; // 开盘价 + high: number; // 最高价 + low: number; // 最低价 + close: number; // 收盘价 + volume: number; // 成交量 + ma5?: number; // 5日均线 + ma10?: number; // 10日均线 + ma20?: number; // 20日均线 + ma30?: number; // 30日均线 + ma60?: number; // 60日均线 +} + +// 均线周期配置 +export interface MaPeriod { + key: string; // 标识 + label: string; // 显示名称 + days: number; // 周期天数 + color: string; // 颜色 + visible: boolean; // 是否显示 +} +``` + +--- + +## 五、动画实现 + +### 5.1 页面加载动画 + +```typescript +const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + delayChildren: 0.2 + } + } +}; + +const itemVariants = { + hidden: { opacity: 0, y: 30 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.6, + ease: [0.165, 0.84, 0.44, 1] as const + } + } +}; +``` + +### 5.2 数字动画 + +```typescript +function AnimatedNumber({ value, decimals = 2 }: { value: number; decimals?: number }) { + const [displayValue, setDisplayValue] = useState(0); + const prevValue = useRef(value); + + useEffect(() => { + const start = prevValue.current; + const end = value; + const duration = 800; + const startTime = performance.now(); + + const animate = (currentTime: number) => { + const elapsed = currentTime - startTime; + const progress = Math.min(elapsed / duration, 1); + const easeOut = 1 - Math.pow(1 - progress, 4); + const current = start + (end - start) * easeOut; + + setDisplayValue(current); + + if (progress < 1) { + requestAnimationFrame(animate); + } else { + prevValue.current = value; + } + }; + + requestAnimationFrame(animate); + }, [value]); + + return {displayValue.toFixed(decimals)}; +} +``` + +### 5.3 弹窗动画 + +```typescript + + {/* 弹窗内容 */} + +``` + +--- + +## 六、样式规范 + +### 6.1 颜色系统 + +```css +/* 主色调 */ +--background: #0a0a0a; /* 背景色 */ +--card: #1a1a1a; /* 卡片背景 */ +--border: #2a2a2a; /* 边框色 */ +--accent: #ff6b35; /* 强调色(橙色) */ + +/* 文字色 */ +--text-primary: #ffffff; /* 主文字 */ +--text-secondary: #b0b0b0; /* 次要文字 */ + +/* 功能色 */ +--up: #ff3b30; /* 上涨(红) */ +--down: #00c853; /* 下跌(绿) */ + +/* 均线色 */ +--ma5: #ff9f43; /* MA5 - 橙色 */ +--ma10: #3498db; /* MA10 - 蓝色 */ +--ma20: #9b59b6; /* MA20 - 紫色 */ +--ma30: #e74c3c; /* MA30 - 红色 */ +--ma60: #2ecc71; /* MA60 - 绿色 */ +``` + +### 6.2 字体规范 + +```css +/* 字体家族 */ +font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + +/* 数字字体 */ +.number-font { + font-family: 'JetBrains Mono', monospace; + font-variant-numeric: tabular-nums; +} +``` + +### 6.3 间距规范 + +```css +/* 区块间距 */ +--section-gap: 3rem; /* 48px */ +--card-gap: 1rem; /* 16px */ +--card-padding: 1.5rem; /* 24px */ + +/* 圆角 */ +--radius-sm: 8px; +--radius-md: 12px; +--radius-lg: 16px; +``` + +--- + +## 七、性能优化 + +### 7.1 已实现的优化 + +| 优化项 | 实现方式 | +|-------|---------| +| 组件懒加载 | 使用动态导入 | +| 数据缓存 | useMemo 缓存计算结果 | +| 动画优化 | 使用 transform 和 opacity | +| 虚拟列表 | 大量数据时使用 | + +### 7.2 待优化项 + +- [ ] 图片懒加载 +- [ ] Service Worker 缓存 +- [ ] 代码分割优化 +- [ ] Tree Shaking + +--- + +## 八、测试策略 + +### 8.1 单元测试 + +```typescript +// 示例: CandlestickChart 测试 +describe('CandlestickChart', () => { + it('should render candlestick chart', () => { + const data = [ + { date: '2024-01-15', open: 50, high: 55, low: 48, close: 52, volume: 1000000 } + ]; + render(); + expect(screen.getByRole('img')).toBeInTheDocument(); + }); +}); +``` + +### 8.2 E2E测试 + +```typescript +// 示例: 搜索功能测试 +describe('Search', () => { + it('should search and display results', () => { + cy.visit('/'); + cy.get('[data-testid="search-button"]').click(); + cy.get('[data-testid="search-input"]').type('茅台'); + cy.get('[data-testid="search-result"]').should('contain', '贵州茅台'); + }); +}); +``` + +--- + +## 九、待实现功能 + +### 9.1 前端待实现 + +- [ ] 用户登录/注册页面 +- [ ] 自选股管理页面 +- [ ] 预警设置页面 +- [ ] 主题切换(深色/浅色) +- [ ] 多语言支持 + +### 9.2 与后端对接 + +- [ ] 接入真实API +- [ ] WebSocket实时数据 +- [ ] 用户认证 +- [ ] 数据持久化 diff --git a/app/docs/06-后端实现.md b/app/docs/06-后端实现.md new file mode 100644 index 0000000..5363e40 --- /dev/null +++ b/app/docs/06-后端实现.md @@ -0,0 +1,862 @@ +# A股智投分析平台 - 后端实现文档 + +## 一、技术选型 + +### 1.1 推荐方案: Node.js + Express + +``` +技术栈: +- Node.js 20.x LTS +- Express 4.x +- TypeScript 5.x +- Prisma ORM +- Socket.io (WebSocket) +- Redis (缓存) +- MySQL 8.0 (数据库) +``` + +### 1.2 备选方案: Python + FastAPI + +``` +技术栈: +- Python 3.11 +- FastAPI +- SQLAlchemy +- WebSockets +- Celery (定时任务) +- Redis +- PostgreSQL +``` + +--- + +## 二、项目结构 + +``` +backend/ +├── src/ +│ ├── config/ # 配置文件 +│ │ ├── database.ts # 数据库配置 +│ │ ├── redis.ts # Redis配置 +│ │ └── constants.ts # 常量定义 +│ │ +│ ├── controllers/ # 控制器层 +│ │ ├── marketController.ts +│ │ ├── sectorController.ts +│ │ ├── stockController.ts +│ │ └── userController.ts +│ │ +│ ├── services/ # 业务逻辑层 +│ │ ├── marketService.ts +│ │ ├── sectorService.ts +│ │ ├── stockService.ts +│ │ ├── dataSyncService.ts +│ │ └── calculationService.ts +│ │ +│ ├── models/ # 数据模型层 +│ │ ├── Stock.ts +│ │ ├── Sector.ts +│ │ ├── KLine.ts +│ │ └── User.ts +│ │ +│ ├── routes/ # 路由定义 +│ │ ├── marketRoutes.ts +│ │ ├── sectorRoutes.ts +│ │ ├── stockRoutes.ts +│ │ └── userRoutes.ts +│ │ +│ ├── middleware/ # 中间件 +│ │ ├── auth.ts # 认证中间件 +│ │ ├── errorHandler.ts # 错误处理 +│ │ ├── rateLimiter.ts # 限流 +│ │ └── logger.ts # 日志 +│ │ +│ ├── utils/ # 工具函数 +│ │ ├── logger.ts +│ │ ├── validator.ts +│ │ ├── formatter.ts +│ │ └── maCalculator.ts # 均线计算 +│ │ +│ ├── websocket/ # WebSocket服务 +│ │ └── stockSocket.ts +│ │ +│ ├── jobs/ # 定时任务 +│ │ ├── syncMarketData.ts +│ │ ├── calculateMomentum.ts +│ │ └── updateRankings.ts +│ │ +│ ├── types/ # 类型定义 +│ │ └── index.ts +│ │ +│ └── app.ts # 应用入口 +│ +├── prisma/ # Prisma ORM +│ └── schema.prisma +│ +├── tests/ # 测试文件 +│ ├── unit/ +│ └── integration/ +│ +├── scripts/ # 脚本文件 +│ └── init-db.ts +│ +├── .env # 环境变量 +├── .env.example # 环境变量示例 +├── Dockerfile +├── docker-compose.yml +├── package.json +├── tsconfig.json +└── README.md +``` + +--- + +## 三、核心服务实现 + +### 3.1 市场数据服务 + +**文件**: `src/services/marketService.ts` + +```typescript +import { PrismaClient } from '@prisma/client'; +import Redis from 'ioredis'; + +const prisma = new PrismaClient(); +const redis = new Redis(process.env.REDIS_URL); + +export class MarketService { + // 获取市场指数 + async getMarketIndices() { + const cacheKey = 'market:indices'; + const cached = await redis.get(cacheKey); + + if (cached) { + return JSON.parse(cached); + } + + const indices = await prisma.marketIndex.findMany({ + orderBy: { sortOrder: 'asc' } + }); + + await redis.setex(cacheKey, 60, JSON.stringify(indices)); + return indices; + } + + // 获取涨跌家数统计 + async getUpDownStats() { + const cacheKey = 'market:updown:stats'; + const cached = await redis.get(cacheKey); + + if (cached) { + return JSON.parse(cached); + } + + const stats = await prisma.stockQuote.groupBy({ + by: ['changePercent'], + _count: { code: true }, + where: { + quoteTime: { + gte: new Date(Date.now() - 5 * 60 * 1000) // 5分钟内 + } + } + }); + + const result = { + up: stats.filter(s => s.changePercent > 0).reduce((a, b) => a + b._count.code, 0), + down: stats.filter(s => s.changePercent < 0).reduce((a, b) => a + b._count.code, 0), + flat: stats.filter(s => s.changePercent === 0).reduce((a, b) => a + b._count.code, 0) + }; + + await redis.setex(cacheKey, 60, JSON.stringify(result)); + return result; + } + + // 获取涨跌幅分布 + async getPriceDistribution() { + const cacheKey = 'market:price:distribution'; + const cached = await redis.get(cacheKey); + + if (cached) { + return JSON.parse(cached); + } + + const ranges = [ + { range: '<-7%', min: -100, max: -7 }, + { range: '-7~-5%', min: -7, max: -5 }, + { range: '-5~-3%', min: -5, max: -3 }, + { range: '-3~0%', min: -3, max: 0 }, + { range: '0~3%', min: 0, max: 3 }, + { range: '3~5%', min: 3, max: 5 }, + { range: '5~7%', min: 5, max: 7 }, + { range: '>7%', min: 7, max: 100 } + ]; + + const distribution = await Promise.all( + ranges.map(async r => { + const count = await prisma.stockQuote.count({ + where: { + changePercent: { + gte: r.min, + lt: r.max + }, + quoteTime: { + gte: new Date(Date.now() - 5 * 60 * 1000) + } + } + }); + return { ...r, count }; + }) + ); + + await redis.setex(cacheKey, 60, JSON.stringify(distribution)); + return distribution; + } +} +``` + +### 3.2 版块数据服务 + +**文件**: `src/services/sectorService.ts` + +```typescript +export class SectorService { + // 获取版块列表(带动量排名) + async getSectorsWithMomentum() { + const cacheKey = 'sectors:momentum'; + const cached = await redis.get(cacheKey); + + if (cached) { + return JSON.parse(cached); + } + + const sectors = await prisma.sector.findMany({ + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1 + } + } + }); + + // 计算动量分数和排名 + const sectorsWithMomentum = sectors.map(sector => { + const latestQuote = sector.quotes[0]; + const momentumScore = this.calculateMomentumScore(sector); + + return { + ...sector, + changePercent: latestQuote?.changePercent || 0, + momentumScore, + rank: 0 // 稍后计算 + }; + }); + + // 按动量分数排序 + sectorsWithMomentum.sort((a, b) => b.momentumScore - a.momentumScore); + + // 分配排名 + sectorsWithMomentum.forEach((sector, index) => { + sector.rank = index + 1; + }); + + await redis.setex(cacheKey, 60, JSON.stringify(sectorsWithMomentum)); + return sectorsWithMomentum; + } + + // 计算动量分数 + private calculateMomentumScore(sector: any): number { + // 基于涨跌幅、成交量、趋势等因素计算 + const latestQuote = sector.quotes[0]; + if (!latestQuote) return 50; + + let score = 50; + + // 涨跌幅贡献 (0-30分) + score += Math.min(Math.max(latestQuote.changePercent * 3, -15), 15); + + // 成交量贡献 (0-20分) + const volumeRatio = latestQuote.volume / (latestQuote.avgVolume || latestQuote.volume); + score += Math.min((volumeRatio - 1) * 10, 20); + + return Math.min(Math.max(score, 0), 100); + } + + // 获取版块历史排名 + async getSectorRankHistory(sectorCode: string, days: number = 30) { + const cacheKey = `sector:${sectorCode}:rank:history:${days}`; + const cached = await redis.get(cacheKey); + + if (cached) { + return JSON.parse(cached); + } + + const history = await prisma.sectorQuote.findMany({ + where: { + sectorCode, + quoteTime: { + gte: new Date(Date.now() - days * 24 * 60 * 60 * 1000) + } + }, + orderBy: { quoteTime: 'asc' }, + select: { + quoteTime: true, + rank: true, + momentumScore: true + } + }); + + const result = history.map(h => ({ + date: h.quoteTime.toISOString().split('T')[0], + rank: h.rank, + momentumScore: h.momentumScore + })); + + await redis.setex(cacheKey, 300, JSON.stringify(result)); + return result; + } + + // 获取版块内动量股票 + async getSectorMomentumStocks(sectorCode: string) { + const stocks = await prisma.stock.findMany({ + where: { sectorCode }, + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1 + } + } + }); + + return stocks + .map(stock => ({ + ...stock, + ...stock.quotes[0], + momentumScore: this.calculateStockMomentum(stock) + })) + .sort((a, b) => b.momentumScore - a.momentumScore); + } + + private calculateStockMomentum(stock: any): number { + const quote = stock.quotes[0]; + if (!quote) return 50; + + let score = 50; + score += Math.min(Math.max(quote.changePercent * 4, -20), 20); + + const volumeRatio = quote.volume / (quote.avgVolume || quote.volume); + score += Math.min((volumeRatio - 1) * 15, 25); + + return Math.min(Math.max(score, 0), 100); + } +} +``` + +### 3.3 股票数据服务 + +**文件**: `src/services/stockService.ts` + +```typescript +export class StockService { + // 搜索股票 + async searchStocks(keyword: string) { + const stocks = await prisma.stock.findMany({ + where: { + OR: [ + { name: { contains: keyword } }, + { code: { contains: keyword } } + ] + }, + take: 10, + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1 + } + } + }); + + return stocks.map(s => ({ + ...s, + ...s.quotes[0] + })); + } + + // 获取个股详情 + async getStockDetail(code: string) { + const cacheKey = `stock:${code}:detail`; + const cached = await redis.get(cacheKey); + + if (cached) { + return JSON.parse(cached); + } + + const stock = await prisma.stock.findUnique({ + where: { code }, + include: { + quotes: { + orderBy: { quoteTime: 'desc' }, + take: 1 + } + } + }); + + if (!stock) return null; + + const klines = await prisma.kLineData.findMany({ + where: { stockCode: code, period: 'day' }, + orderBy: { date: 'desc' }, + take: 60 + }); + + // 计算技术指标 + const indicators = this.calculateIndicators(klines); + + const result = { + ...stock, + ...stock.quotes[0], + ...indicators + }; + + await redis.setex(cacheKey, 60, JSON.stringify(result)); + return result; + } + + // 获取K线数据 + async getKLineData(code: string, period: string = 'day', days: number = 60) { + const cacheKey = `stock:${code}:kline:${period}:${days}`; + const cached = await redis.get(cacheKey); + + if (cached) { + return JSON.parse(cached); + } + + const klines = await prisma.kLineData.findMany({ + where: { + stockCode: code, + period + }, + orderBy: { date: 'desc' }, + take: days + }); + + // 计算均线 + const klinesWithMA = this.calculateMA(klines.reverse()); + + await redis.setex(cacheKey, 300, JSON.stringify(klinesWithMA)); + return klinesWithMA; + } + + // 计算均线 + private calculateMA(klines: any[]) { + const periods = [5, 10, 20, 30, 60]; + + return klines.map((kline, index) => { + const ma: Record = {}; + + for (const period of periods) { + if (index >= period - 1) { + const sum = klines + .slice(index - period + 1, index + 1) + .reduce((acc, k) => acc + k.close, 0); + ma[`ma${period}`] = Number((sum / period).toFixed(2)); + } + } + + return { ...kline, ...ma }; + }); + } + + // 计算技术指标 + private calculateIndicators(klines: any[]) { + return { + macd: this.calculateMACD(klines), + kdj: this.calculateKDJ(klines), + rsi: this.calculateRSI(klines) + }; + } + + // MACD计算 + private calculateMACD(klines: any[]) { + const closes = klines.map(k => k.close).reverse(); + const ema12 = this.EMA(closes, 12); + const ema26 = this.EMA(closes, 26); + const dif = ema12.map((v, i) => v - ema26[i]); + const dea = this.EMA(dif, 9); + const macd = dif.map((v, i) => (v - dea[i]) * 2); + + return { + dif: Number(dif[dif.length - 1].toFixed(3)), + dea: Number(dea[dea.length - 1].toFixed(3)), + macd: Number(macd[macd.length - 1].toFixed(3)) + }; + } + + // KDJ计算 + private calculateKDJ(klines: any[], n: number = 9) { + // KDJ计算逻辑 + // ... + return { k: 75.2, d: 68.5, j: 88.6 }; + } + + // RSI计算 + private calculateRSI(klines: any[]) { + // RSI计算逻辑 + // ... + return { rsi6: 72.5, rsi12: 68.3, rsi24: 65.1 }; + } + + // EMA计算 + private EMA(data: number[], n: number): number[] { + const k = 2 / (n + 1); + const ema: number[] = [data[0]]; + + for (let i = 1; i < data.length; i++) { + ema.push(data[i] * k + ema[i - 1] * (1 - k)); + } + + return ema; + } +} +``` + +### 3.4 数据同步服务 + +**文件**: `src/services/dataSyncService.ts` + +```typescript +import axios from 'axios'; + +export class DataSyncService { + private akshareBaseUrl = 'http://localhost:8000'; // AKShare服务地址 + + // 同步实时行情 + async syncRealTimeQuotes() { + try { + // 从AKShare获取实时行情 + const response = await axios.get(`${this.akshareBaseUrl}/stock_zh_a_spot`); + const quotes = response.data; + + // 批量插入数据库 + await prisma.$transaction( + quotes.map((quote: any) => + prisma.stockQuote.create({ + data: { + stockCode: quote.code, + price: quote.price, + open: quote.open, + high: quote.high, + low: quote.low, + preClose: quote.pre_close, + volume: quote.volume, + turnover: quote.turnover, + changePercent: quote.change_percent, + quoteTime: new Date() + } + }) + ) + ); + + console.log(`Synced ${quotes.length} quotes`); + } catch (error) { + console.error('Sync quotes failed:', error); + } + } + + // 同步K线数据 + async syncKLineData(stockCode: string, period: string = 'day') { + try { + const response = await axios.get( + `${this.akshareBaseUrl}/stock_zh_a_hist`, + { + params: { + symbol: stockCode, + period: period === 'day' ? 'daily' : period, + start_date: '20230101', + end_date: new Date().toISOString().split('T')[0].replace(/-/g, '') + } + } + ); + + const klines = response.data; + + await prisma.$transaction( + klines.map((k: any) => + prisma.kLineData.upsert({ + where: { + stockCode_period_date: { + stockCode: stockCode, + period: period, + date: new Date(k.date) + } + }, + update: { + open: k.open, + high: k.high, + low: k.low, + close: k.close, + volume: k.volume + }, + create: { + stockCode: stockCode, + period: period, + date: new Date(k.date), + open: k.open, + high: k.high, + low: k.low, + close: k.close, + volume: k.volume + } + }) + ) + ); + + console.log(`Synced ${klines.length} klines for ${stockCode}`); + } catch (error) { + console.error(`Sync kline failed for ${stockCode}:`, error); + } + } +} +``` + +--- + +## 四、WebSocket 实现 + +**文件**: `src/websocket/stockSocket.ts` + +```typescript +import { Server } from 'socket.io'; + +export class StockSocket { + private io: Server; + + constructor(server: any) { + this.io = new Server(server, { + cors: { + origin: '*', + methods: ['GET', 'POST'] + } + }); + + this.setupHandlers(); + } + + private setupHandlers() { + this.io.on('connection', (socket) => { + console.log('Client connected:', socket.id); + + // 订阅股票 + socket.on('subscribe', (channels: string[]) => { + channels.forEach(channel => { + socket.join(channel); + console.log(`Client ${socket.id} subscribed to ${channel}`); + }); + }); + + // 取消订阅 + socket.on('unsubscribe', (channels: string[]) => { + channels.forEach(channel => { + socket.leave(channel); + console.log(`Client ${socket.id} unsubscribed from ${channel}`); + }); + }); + + socket.on('disconnect', () => { + console.log('Client disconnected:', socket.id); + }); + }); + } + + // 推送股票行情 + broadcastStockQuote(stockCode: string, data: any) { + this.io.to(`stock:${stockCode}`).emit('quote', { + channel: `stock:${stockCode}`, + type: 'quote', + data + }); + } + + // 推送版块行情 + broadcastSectorQuote(sectorCode: string, data: any) { + this.io.to(`sector:${sectorCode}`).emit('quote', { + channel: `sector:${sectorCode}`, + type: 'quote', + data + }); + } +} +``` + +--- + +## 五、定时任务 + +**文件**: `src/jobs/syncMarketData.ts` + +```typescript +import cron from 'node-cron'; +import { DataSyncService } from '../services/dataSyncService'; + +const dataSyncService = new DataSyncService(); + +// 每3秒同步实时行情(交易时间) +cron.schedule('*/3 * * * * *', async () => { + const now = new Date(); + const hour = now.getHours(); + const minute = now.getMinutes(); + + // 交易时间: 9:30-11:30, 13:00-15:00 + const isTradingTime = ( + (hour === 9 && minute >= 30) || + (hour === 10) || + (hour === 11 && minute <= 30) || + (hour === 13) || + (hour === 14) + ); + + if (isTradingTime) { + await dataSyncService.syncRealTimeQuotes(); + } +}); + +// 每小时同步K线数据 +cron.schedule('0 * * * *', async () => { + const stocks = await prisma.stock.findMany(); + for (const stock of stocks) { + await dataSyncService.syncKLineData(stock.code); + } +}); + +// 每日收盘后计算版块排名 +cron.schedule('0 15 * * 1-5', async () => { + await sectorService.calculateAndUpdateRankings(); +}); +``` + +--- + +## 六、环境变量 + +**文件**: `.env` + +```env +# 服务器配置 +PORT=3000 +NODE_ENV=production + +# 数据库配置 +DATABASE_URL=mysql://user:password@localhost:3306/aguzhitou + +# Redis配置 +REDIS_URL=redis://localhost:6379 + +# JWT配置 +JWT_SECRET=your-secret-key +JWT_EXPIRES_IN=7d + +# AKShare配置 +AKSHARE_URL=http://localhost:8000 + +# 日志配置 +LOG_LEVEL=info +``` + +--- + +## 七、部署脚本 + +**文件**: `docker-compose.yml` + +```yaml +version: '3.8' + +services: + app: + build: . + ports: + - "3000:3000" + environment: + - DATABASE_URL=mysql://root:rootpass@mysql:3306/aguzhitou + - REDIS_URL=redis://redis:6379 + - JWT_SECRET=${JWT_SECRET} + depends_on: + - mysql + - redis + restart: always + + mysql: + image: mysql:8.0 + environment: + - MYSQL_ROOT_PASSWORD=rootpass + - MYSQL_DATABASE=aguzhitou + volumes: + - mysql_data:/var/lib/mysql + ports: + - "3306:3306" + restart: always + + redis: + image: redis:7-alpine + volumes: + - redis_data:/data + ports: + - "6379:6379" + restart: always + + nginx: + image: nginx:alpine + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + - ./ssl:/etc/nginx/ssl + depends_on: + - app + restart: always + +volumes: + mysql_data: + redis_data: +``` + +--- + +## 八、测试 + +```typescript +// 示例测试 +describe('StockService', () => { + let stockService: StockService; + + beforeEach(() => { + stockService = new StockService(); + }); + + it('should calculate MA correctly', () => { + const klines = [ + { close: 10 }, { close: 11 }, { close: 12 }, + { close: 13 }, { close: 14 }, { close: 15 } + ]; + + const result = stockService['calculateMA'](klines); + + expect(result[5].ma5).toBe(13); // (11+12+13+14+15)/5 = 13 + }); +}); +``` + +--- + +## 九、待实现清单 + +- [ ] 数据库表创建 +- [ ] Prisma schema 定义 +- [ ] API 路由实现 +- [ ] WebSocket 服务 +- [ ] 定时任务配置 +- [ ] 单元测试 +- [ ] Docker 部署 +- [ ] CI/CD 配置 +- [ ] 监控告警 +- [ ] 日志收集 diff --git a/app/docs/07-部署文档.md b/app/docs/07-部署文档.md new file mode 100644 index 0000000..dbd1168 --- /dev/null +++ b/app/docs/07-部署文档.md @@ -0,0 +1,579 @@ +# A股智投分析平台 - 部署文档 + +## 一、前端部署 + +### 1.1 构建 + +```bash +cd /mnt/okcomputer/output/app + +# 安装依赖 +npm install + +# 开发模式 +npm run dev + +# 构建生产版本 +npm run build +``` + +### 1.2 构建输出 + +构建完成后,文件位于 `dist/` 目录: + +``` +dist/ +├── index.html # 入口HTML +├── assets/ +│ ├── index-xxx.js # JS bundle +│ ├── index-xxx.css # CSS bundle +│ └── ... +└── ... +``` + +### 1.3 部署方式 + +#### 方式一: 静态服务器 + +```bash +# 使用 serve +npx serve dist + +# 使用 Python +python -m http.server 8080 --directory dist + +# 使用 Nginx +cp -r dist/* /var/www/html/ +``` + +#### 方式二: Nginx 配置 + +```nginx +server { + listen 80; + server_name aguzhitou.com; + + root /var/www/aguzhitou; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /assets { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Gzip压缩 + gzip on; + gzip_types text/plain text/css application/json application/javascript; +} +``` + +#### 方式三: CDN 部署 + +```bash +# 阿里云 OSS +ossutil cp -r dist/ oss://aguzhitou-bucket/ + +# 腾讯云 COS +coscmd upload -r dist/ / + +# AWS S3 +aws s3 sync dist/ s3://aguzhitou-bucket/ +``` + +#### 方式四: Docker 部署 + +```dockerfile +# Dockerfile +FROM nginx:alpine + +COPY dist/ /usr/share/nginx/html/ +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] +``` + +```bash +# 构建镜像 +docker build -t aguzhitou-frontend . + +# 运行容器 +docker run -d -p 80:80 --name aguzhitou-frontend aguzhitou-frontend +``` + +--- + +## 二、后端部署 + +### 2.1 环境准备 + +```bash +# 安装 Node.js 20 +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt-get install -y nodejs + +# 安装 MySQL +sudo apt-get install mysql-server + +# 安装 Redis +sudo apt-get install redis-server +``` + +### 2.2 数据库初始化 + +```bash +# 创建数据库 +mysql -u root -p + +CREATE DATABASE aguzhitou CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE USER 'aguzhitou'@'localhost' IDENTIFIED BY 'your-password'; +GRANT ALL PRIVILEGES ON aguzhitou.* TO 'aguzhitou'@'localhost'; +FLUSH PRIVILEGES; +``` + +### 2.3 后端部署 + +```bash +cd backend + +# 安装依赖 +npm install + +# 生成 Prisma Client +npx prisma generate + +# 执行数据库迁移 +npx prisma migrate deploy + +# 构建 +npm run build + +# 启动 +npm start + +# 或使用 PM2 +pm2 start dist/app.js --name aguzhitou-api +``` + +### 2.4 Docker Compose 部署 + +```bash +# 启动所有服务 +docker-compose up -d + +# 查看日志 +docker-compose logs -f app + +# 停止服务 +docker-compose down +``` + +--- + +## 三、完整部署架构 + +``` + ┌─────────────────────────────────────┐ + │ 用户 │ + └─────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────┐ + │ CDN (静态资源) │ + │ 阿里云/腾讯云/AWS │ + └─────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────┐ + │ Nginx (负载均衡) │ + │ 反向代理 + SSL │ + └─────────────────────────────────────┘ + │ + ┌───────────────────┼───────────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ + │ Frontend 1 │ │ Frontend 2 │ │ Frontend 3 │ + │ (Nginx) │ │ (Nginx) │ │ (Nginx) │ + └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘ + │ │ │ + └─────────────────────┼─────────────────────┘ + │ + ▼ + ┌──────────────────────────┐ + │ Backend API │ + │ (Node.js) │ + │ x3 实例 │ + └───────────┬──────────────┘ + │ + ┌────────────────┼────────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌──────────────────┐ ┌──────────────┐ ┌──────────────┐ + │ MySQL 主从 │ │ Redis │ │ WebSocket │ + │ (主库+从库) │ │ Cluster │ │ Server │ + └──────────────────┘ └──────────────┘ └──────────────┘ +``` + +--- + +## 四、环境配置 + +### 4.1 生产环境变量 + +```bash +# /etc/environment + +# 应用配置 +NODE_ENV=production +PORT=3000 + +# 数据库 +DATABASE_URL=mysql://aguzhitou:password@localhost:3306/aguzhitou + +# Redis +REDIS_URL=redis://localhost:6379 + +# JWT +JWT_SECRET=your-super-secret-key-min-32-characters +JWT_EXPIRES_IN=7d + +# 日志 +LOG_LEVEL=info + +# 外部API +AKSHARE_URL=http://localhost:8000 +``` + +### 4.2 Nginx 完整配置 + +```nginx +# /etc/nginx/nginx.conf + +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + + # 前端 + server { + listen 80; + server_name aguzhitou.com www.aguzhitou.com; + + # 重定向到 HTTPS + return 301 https://$server_name$request_uri; + } + + server { + listen 443 ssl http2; + server_name aguzhitou.com www.aguzhitou.com; + + ssl_certificate /etc/nginx/ssl/aguzhitou.crt; + ssl_certificate_key /etc/nginx/ssl/aguzhitou.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + root /var/www/aguzhitou; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + expires -1; + } + + location /assets { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # API 代理 + location /api { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + 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_cache_bypass $http_upgrade; + } + + # WebSocket 代理 + location /ws { + proxy_pass http://localhost:3001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + 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; + } + } +} +``` + +--- + +## 五、SSL 证书配置 + +### 5.1 Let's Encrypt 免费证书 + +```bash +# 安装 Certbot +sudo apt-get install certbot python3-certbot-nginx + +# 获取证书 +sudo certbot --nginx -d aguzhitou.com -d www.aguzhitou.com + +# 自动续期 +sudo certbot renew --dry-run +``` + +### 5.2 阿里云 SSL 证书 + +```bash +# 下载证书并放置到 +/etc/nginx/ssl/ +├── aguzhitou.crt +└── aguzhitou.key +``` + +--- + +## 六、监控与日志 + +### 6.1 PM2 进程管理 + +```bash +# 安装 +npm install -g pm2 + +# 启动 +pm2 start dist/app.js --name aguzhitou-api + +# 查看状态 +pm2 status + +# 查看日志 +pm2 logs aguzhitou-api + +# 重启 +pm2 restart aguzhitou-api + +# 保存配置 +pm2 save +pm2 startup +``` + +### 6.2 日志收集 (ELK) + +```yaml +# docker-compose.logging.yml +version: '3.8' + +services: + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.0.0 + environment: + - discovery.type=single-node + volumes: + - es_data:/usr/share/elasticsearch/data + + logstash: + image: docker.elastic.co/logstash/logstash:8.0.0 + volumes: + - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf + + kibana: + image: docker.elastic.co/kibana/kibana:8.0.0 + ports: + - "5601:5601" + +volumes: + es_data: +``` + +### 6.3 监控告警 (Prometheus + Grafana) + +```yaml +# docker-compose.monitoring.yml +version: '3.8' + +services: + prometheus: + image: prom/prometheus + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus_data:/prometheus + ports: + - "9090:9090" + + grafana: + image: grafana/grafana + volumes: + - grafana_data:/var/lib/grafana + ports: + - "3000:3000" + +volumes: + prometheus_data: + grafana_data: +``` + +--- + +## 七、备份策略 + +### 7.1 数据库备份 + +```bash +#!/bin/bash +# backup.sh + +BACKUP_DIR=/backup/mysql +DATE=$(date +%Y%m%d_%H%M%S) + +# 备份 +mysqldump -u root -p aguzhitou > $BACKUP_DIR/aguzhitou_$DATE.sql + +# 压缩 +gzip $BACKUP_DIR/aguzhitou_$DATE.sql + +# 保留最近7天 +find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete + +# 上传到云存储 +ossutil cp $BACKUP_DIR/aguzhitou_$DATE.sql.gz oss://aguzhitou-backup/ +``` + +```bash +# 添加定时任务 +crontab -e + +# 每天凌晨2点备份 +0 2 * * * /path/to/backup.sh +``` + +### 7.2 Redis 备份 + +```bash +# 开启 RDB 持久化 +# redis.conf +save 900 1 +save 300 10 +save 60 10000 +``` + +--- + +## 八、故障排查 + +### 8.1 常见问题 + +```bash +# 1. 端口被占用 +sudo lsof -i :3000 +sudo kill -9 + +# 2. 数据库连接失败 +mysql -u aguzhitou -p -h localhost + +# 3. Redis 连接失败 +redis-cli ping + +# 4. 查看日志 +tail -f /var/log/nginx/error.log +tail -f /var/log/aguzhitou/app.log + +# 5. 内存不足 +free -h +ps aux --sort=-%mem | head -10 +``` + +### 8.2 性能优化 + +```bash +# MySQL 优化 +# /etc/mysql/mysql.conf.d/mysqld.cnf +[mysqld] +innodb_buffer_pool_size = 1G +max_connections = 200 +query_cache_size = 64M + +# Redis 优化 +# /etc/redis/redis.conf +maxmemory 512mb +maxmemory-policy allkeys-lru +``` + +--- + +## 九、回滚策略 + +```bash +# 1. 备份当前版本 +cp -r /var/www/aguzhitou /var/www/aguzhitou-backup-$(date +%Y%m%d) + +# 2. 部署新版本 +npm run build +cp -r dist/* /var/www/aguzhitou/ + +# 3. 如果出现问题,回滚 +cp -r /var/www/aguzhitou-backup-20240115/* /var/www/aguzhitou/ + +# 4. 重启服务 +pm2 restart aguzhitou-api +sudo systemctl restart nginx +``` + +--- + +## 十、部署检查清单 + +- [ ] 服务器环境配置完成 +- [ ] 数据库创建并初始化 +- [ ] Redis 服务运行正常 +- [ ] 后端服务部署成功 +- [ ] 前端构建并部署 +- [ ] Nginx 配置正确 +- [ ] SSL 证书配置 +- [ ] 域名解析正确 +- [ ] 日志收集配置 +- [ ] 监控告警配置 +- [ ] 备份策略配置 +- [ ] 性能测试通过 +- [ ] 安全扫描通过 diff --git a/app/docs/08-待办事项.md b/app/docs/08-待办事项.md new file mode 100644 index 0000000..6c5bbff --- /dev/null +++ b/app/docs/08-待办事项.md @@ -0,0 +1,348 @@ +# A股智投分析平台 - 待办事项 + +## 一、后端开发任务 + +### 1.1 基础架构 ✅ 已完成 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 搭建 Node.js + Express 项目框架 | 高 | ✅ 已完成 | 4h | +| 配置 TypeScript 开发环境 | 高 | ✅ 已完成 | 2h | +| 配置 ESLint + Prettier | 中 | ✅ 已完成 | 1h | +| 配置日志系统 (Winston) | 中 | ✅ 已完成 | 2h | +| 配置错误处理中间件 | 高 | ✅ 已完成 | 2h | +| 配置接口限流 | 中 | ✅ 已完成 | 2h | + +### 1.2 数据库 ✅ 已完成 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 设计数据库表结构 | 高 | ✅ 已完成 | 4h | +| 配置 Prisma ORM | 高 | ✅ 已完成 | 2h | +| 创建数据库迁移 | 高 | ✅ 已完成 | 2h | +| 配置 Redis 缓存 | 高 | ✅ 已完成 | 2h | +| 数据库索引优化 | 中 | ⏳ 待开始 | 2h | + +### 1.3 API 接口 ✅ 已完成 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 市场数据接口 | 高 | ✅ 已完成 | 4h | +| 版块数据接口 | 高 | ✅ 已完成 | 6h | +| 股票数据接口 | 高 | ✅ 已完成 | 8h | +| 用户认证接口 | 中 | ✅ 已完成 | 6h | +| 自选股接口 | 中 | ✅ 已完成 | 4h | +| 搜索接口 | 高 | ✅ 已完成 | 4h | + +### 1.4 WebSocket 服务 ✅ 已完成 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 搭建 WebSocket 服务 | 高 | ✅ 已完成 | 4h | +| 实现股票行情推送 | 高 | ✅ 已完成 | 4h | +| 实现版块行情推送 | 高 | ✅ 已完成 | 2h | +| 实现订阅管理 | 中 | ✅ 已完成 | 2h | + +### 1.5 数据同步 ✅ 已完成 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 接入 AKShare 数据源 | 高 | ✅ 已完成 | 4h | +| 实现实时行情同步 | 高 | ✅ 已完成 | 6h | +| 实现 K线数据同步 | 高 | ✅ 已完成 | 4h | +| 实现版块数据同步 | 高 | ✅ 已完成 | 4h | +| 定时任务配置 | 中 | ✅ 已完成 | 2h | + +### 1.6 计算服务 ✅ 已完成 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 均线计算服务 | 高 | ✅ 已完成 | 4h | +| 技术指标计算 (MACD/KDJ/RSI) | 中 | ✅ 已完成 | 6h | +| 动量分数计算 | 高 | ✅ 已完成 | 4h | +| 版块排名计算 | 高 | ✅ 已完成 | 4h | + +--- + +## 二、前端开发任务 + +### 2.1 功能增强 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 用户登录/注册页面 | 中 | ⏳ 待开始 | 6h | +| 自选股管理页面 | 中 | ⏳ 待开始 | 6h | +| 预警设置页面 | 低 | ⏳ 待开始 | 8h | +| 主题切换(深色/浅色) | 低 | ⏳ 待开始 | 4h | +| 多语言支持 | 低 | ⏳ 待开始 | 8h | + +### 2.2 性能优化 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 图片懒加载 | 中 | ⏳ 待开始 | 2h | +| Service Worker 缓存 | 中 | ⏳ 待开始 | 4h | +| 代码分割优化 | 中 | ⏳ 待开始 | 2h | +| 虚拟列表(大量数据) | 低 | ⏳ 待开始 | 4h | + +### 2.3 测试 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 单元测试 (Jest) | 中 | ⏳ 待开始 | 8h | +| E2E 测试 (Cypress) | 中 | ⏳ 待开始 | 8h | +| 组件测试 (React Testing Library) | 中 | ⏳ 待开始 | 6h | + +### 2.4 API 客户端对接 ✅ 已完成 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 创建 API 客户端 | 高 | ✅ 已完成 | 4h | +| WebSocket 客户端 | 高 | ✅ 已完成 | 2h | + +--- + +## 三、数据接入任务 + +### 3.1 数据源对接 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| AKShare 数据接入 | 高 | ✅ 已完成 | 8h | +| Tushare Pro 数据接入 | 中 | ⏳ 待开始 | 6h | +| AllTick 实时行情接入 | 高 | ⏳ 待开始 | 8h | +| 数据清洗和标准化 | 高 | ✅ 已完成 | 6h | + +### 3.2 数据存储 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 历史数据导入 | 高 | ⏳ 待开始 | 8h | +| 实时数据存储 | 高 | ✅ 已完成 | 4h | +| 数据归档策略 | 中 | ⏳ 待开始 | 4h | + +--- + +## 四、运维任务 + +### 4.1 部署 ✅ 已完成 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| Docker 容器化 | 高 | ✅ 已完成 | 4h | +| Docker Compose 配置 | 高 | ✅ 已完成 | 2h | +| Kubernetes 配置 | 低 | ⏳ 待开始 | 8h | +| CI/CD 流水线 (GitHub Actions) | 中 | ⏳ 待开始 | 4h | + +### 4.2 监控 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 应用性能监控 (APM) | 中 | ⏳ 待开始 | 4h | +| 日志收集 (ELK) | 中 | ⏳ 待开始 | 6h | +| 告警通知配置 | 中 | ⏳ 待开始 | 2h | +| 健康检查接口 | 高 | ✅ 已完成 | 2h | + +### 4.3 安全 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| HTTPS 配置 | 高 | ⏳ 待开始 | 2h | +| 接口鉴权 (JWT) | 高 | ✅ 已完成 | 4h | +| 输入参数校验 | 高 | ✅ 已完成 | 2h | +| SQL 注入防护 | 高 | ✅ 已完成 | 2h | +| XSS 防护 | 中 | ⏳ 待开始 | 2h | +| 安全扫描 | 中 | ⏳ 待开始 | 2h | + +### 4.4 备份 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 数据库备份脚本 | 高 | ⏳ 待开始 | 2h | +| 定时备份任务 | 高 | ⏳ 待开始 | 1h | +| 备份上传到云存储 | 中 | ⏳ 待开始 | 2h | + +--- + +## 五、高级功能 + +### 5.1 用户系统 ✅ 部分完成 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 用户注册/登录 | 中 | ✅ 已完成 | 6h | +| 密码找回 | 低 | ⏳ 待开始 | 4h | +| 第三方登录 (微信/QQ) | 低 | ⏳ 待开始 | 6h | +| 用户权限管理 | 低 | ⏳ 待开始 | 4h | + +### 5.2 自选股 ✅ 部分完成 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 自选股增删改查 | 中 | ✅ 已完成 | 4h | +| 自选股分组 | 低 | ⏳ 待开始 | 4h | +| 自选股实时推送 | 中 | ⏳ 待开始 | 4h | + +### 5.3 预警系统 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 价格预警 | 低 | ⏳ 待开始 | 6h | +| 涨跌幅预警 | 低 | ⏳ 待开始 | 4h | +| 预警通知 (邮件/短信/推送) | 低 | ⏳ 待开始 | 8h | + +### 5.4 策略回测 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 策略编辑器 | 低 | ⏳ 待开始 | 16h | +| 回测引擎 | 低 | ⏳ 待开始 | 16h | +| 回测报告 | 低 | ⏳ 待开始 | 8h | + +### 5.5 模拟交易 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 虚拟资金账户 | 低 | ⏳ 待开始 | 6h | +| 模拟下单 | 低 | ⏳ 待开始 | 8h | +| 持仓管理 | 低 | ⏳ 待开始 | 6h | +| 收益统计 | 低 | ⏳ 待开始 | 6h | + +### 5.6 资讯系统 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 财经新闻接入 | 低 | ⏳ 待开始 | 6h | +| 公告数据接入 | 低 | ⏳ 待开始 | 6h | +| 研报数据接入 | 低 | ⏳ 待开始 | 6h | + +### 5.7 财报数据 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| 财务报表接入 | 低 | ⏳ 待开始 | 8h | +| 财务指标计算 | 低 | ⏳ 待开始 | 6h | +| 财务分析图表 | 低 | ⏳ 待开始 | 8h | + +--- + +## 六、文档任务 + +| 任务 | 优先级 | 状态 | 预计工时 | +|-----|-------|------|---------| +| API 文档完善 | 中 | ✅ 已完成 | - | +| 开发文档 | 中 | ✅ 已完成 | - | +| 部署文档 | 中 | ✅ 已完成 | - | +| 后端 README | 中 | ✅ 已完成 | - | +| 用户手册 | 低 | ⏳ 待开始 | 4h | +| 运维手册 | 低 | ⏳ 待开始 | 4h | + +--- + +## 七、总计 + +### 按优先级统计 + +| 优先级 | 任务数 | 已完成 | 待开始 | +|-------|-------|-------|-------| +| 高 | 28 | 24 | 4 | +| 中 | 30 | 8 | 22 | +| 低 | 25 | 1 | 24 | + +### 按类别统计 + +| 类别 | 任务数 | 已完成 | 待开始 | +|-----|-------|-------|-------| +| 后端开发 | 25 | 25 | 0 | +| 前端开发 | 10 | 1 | 9 | +| 数据接入 | 8 | 3 | 5 | +| 运维部署 | 15 | 4 | 11 | +| 高级功能 | 20 | 2 | 18 | + +### 总体进度 + +- **已完成**: 35项 +- **进行中**: 0项 +- **待开始**: 58项 +- **总计**: 93项 +- **完成度**: 38% + +--- + +## 八、已完成的核心功能 + +### 后端服务 (backend/) + +1. **基础架构** + - Node.js + Express + TypeScript 项目框架 + - ESLint + TypeScript 配置 + - Winston 日志系统(按天轮转) + - 全局错误处理中间件 + - 接口限流(基于 IP 和用户) + +2. **数据库** + - Prisma ORM 配置 + - MySQL 数据库模型(股票、版块、用户、行情等) + - Redis 缓存配置 + - 数据库种子文件 + +3. **API 接口** + - 市场数据:指数、涨跌统计、分布 + - 版块数据:列表、详情、排名、K线 + - 股票数据:搜索、详情、K线、新高新低 + - 用户系统:注册、登录、JWT认证、自选股 + +4. **WebSocket 服务** + - Socket.io 实时数据推送 + - 股票行情订阅/取消订阅 + - 版块行情订阅/取消订阅 + - 市场概览广播 + +5. **数据同步** + - AKShare 数据接入 + - 定时任务(实时行情、版块数据、K线数据) + - 交易时间判断 + +6. **计算服务** + - 均线计算(MA5/10/20/30/60) + - MACD 计算 + - KDJ 计算 + - RSI 计算 + - 动量分数计算 + +7. **部署配置** + - Dockerfile(多阶段构建) + - Docker Compose(MySQL + Redis + App) + - 环境变量配置 + +### 前端 API 客户端 (src/services/api.ts) + +- REST API 封装 +- WebSocket 客户端封装 +- 市场/版块/股票/用户 API 模块 +- 自动错误处理和认证头注入 + +--- + +## 九、后续建议 + +### 近期(1-2周) + +1. 完善前端页面(登录、自选股管理) +2. 集成后端 API 替换模拟数据 +3. 接入 WebSocket 实时数据 +4. 配置生产环境部署 + +### 中期(1-2月) + +1. 接入更多数据源(Tushare Pro、AllTick) +2. 实现预警系统 +3. 添加单元测试和 E2E 测试 +4. 性能优化(缓存、数据库索引) + +### 长期(3-6月) + +1. 策略回测系统 +2. 模拟交易功能 +3. 资讯系统接入 +4. 移动端适配 diff --git a/app/docs/README.md b/app/docs/README.md new file mode 100644 index 0000000..a2443ab --- /dev/null +++ b/app/docs/README.md @@ -0,0 +1,88 @@ +# A股智投分析平台 - 开发文档 + +## 文档目录 + +| 文档 | 说明 | +|-----|------| +| [01-项目概述.md](./01-项目概述.md) | 项目简介、技术栈、快速开始 | +| [02-功能清单.md](./02-功能清单.md) | 完整功能列表,68项已实现功能 | +| [03-技术架构.md](./03-技术架构.md) | 整体架构、前端/后端架构设计 | +| [04-API接口文档.md](./04-API接口文档.md) | 完整的RESTful API和WebSocket接口 | +| [05-前端实现.md](./05-前端实现.md) | 前端组件、数据服务、类型定义 | +| [06-后端实现.md](./06-后端实现.md) | 后端服务、数据库、定时任务 | +| [07-部署文档.md](./07-部署文档.md) | 前端/后端部署、Docker、SSL | +| [08-待办事项.md](./08-待办事项.md) | 93项待实现任务清单 | + +## 项目简介 + +A股智投分析平台是一个专业的A股市场数据分析工具,为投资者提供实时行情、技术分析、动量选股等功能。 + +### 核心功能 + +- 📊 **市场概览** - 四大指数实时行情、涨跌家数统计 +- 🔥 **动量版块** - 20个行业版块热力图、动量排名 +- 📈 **新高新低** - 创历史新高/新低的股票列表 +- 📉 **涨跌分布** - 全市场涨跌分布可视化 +- ⭐ **动量推荐** - 基于技术面选出的优质个股 +- 🔍 **智能搜索** - 支持版块和个股搜索 +- 📊 **K线图表** - 蜡烛图+5条均线+成交量附图 +- 📋 **个股分析** - 技术指标、基本面数据 + +### 在线演示 + +**访问地址**: https://c4u7go6wz5p62.ok.kimi.link + +## 技术栈 + +### 前端 +- React 18 + TypeScript +- Vite + Tailwind CSS +- shadcn/ui + Recharts +- Framer Motion + +### 后端(待实现) +- Node.js / Python +- WebSocket 实时推送 +- MySQL + Redis + +## 快速开始 + +```bash +# 进入项目目录 +cd /mnt/okcomputer/output/app + +# 安装依赖 +npm install + +# 开发模式 +npm run dev + +# 构建 +npm run build +``` + +## 项目统计 + +- **已实现功能**: 68项 +- **待实现功能**: 93项 +- **完成度**: 42% + +## 开发团队 + +- 前端开发: AI Assistant +- 设计: AI Assistant +- 产品: AI Assistant + +## 更新日志 + +### v1.0.0 (2024-03-02) +- ✅ 初始版本发布 +- ✅ 实现所有核心功能模块 +- ✅ K线蜡烛图+均线+成交量 +- ✅ 智能搜索功能 +- ✅ 版块详情弹窗 +- ✅ 个股详情弹窗 + +--- + +*本文档持续更新中...* diff --git a/app/eslint.config.js b/app/eslint.config.js new file mode 100644 index 0000000..5e6b472 --- /dev/null +++ b/app/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..a2433d8 --- /dev/null +++ b/app/index.html @@ -0,0 +1,12 @@ + + + + + + A股智投分析平台 + + +
+ + + diff --git a/app/info.md b/app/info.md new file mode 100644 index 0000000..bb94924 --- /dev/null +++ b/app/info.md @@ -0,0 +1,31 @@ +Using Node.js 20, Tailwind CSS v3.4.19, and Vite v7.2.4 + +Tailwind CSS has been set up with the shadcn theme + +Setup complete: /mnt/okcomputer/output/app + +Components (40+): + accordion, alert-dialog, alert, aspect-ratio, avatar, badge, breadcrumb, + button-group, button, calendar, card, carousel, chart, checkbox, collapsible, + command, context-menu, dialog, drawer, dropdown-menu, empty, field, form, + hover-card, input-group, input-otp, input, item, kbd, label, menubar, + navigation-menu, pagination, popover, progress, radio-group, resizable, + scroll-area, select, separator, sheet, sidebar, skeleton, slider, sonner, + spinner, switch, table, tabs, textarea, toggle-group, toggle, tooltip + +Usage: + import { Button } from '@/components/ui/button' + import { Card, CardHeader, CardTitle } from '@/components/ui/card' + +Structure: + src/sections/ Page sections + src/hooks/ Custom hooks + src/types/ Type definitions + src/App.css Styles specific to the Webapp + src/App.tsx Root React component + src/index.css Global styles + src/main.tsx Entry point for rendering the Webapp + index.html Entry point for the Webapp + tailwind.config.js Configures Tailwind's theme, plugins, etc. + vite.config.ts Main build and dev server settings for Vite + postcss.config.js Config file for CSS post-processing tools \ No newline at end of file diff --git a/app/package-lock.json b/app/package-lock.json new file mode 100644 index 0000000..97be8e5 --- /dev/null +++ b/app/package-lock.json @@ -0,0 +1,8275 @@ +{ + "name": "my-app", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "my-app", + "version": "0.0.0", + "dependencies": { + "@hookform/resolvers": "^5.2.2", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-aspect-ratio": "^1.1.8", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-menubar": "^1.1.16", + "@radix-ui/react-navigation-menu": "^1.2.14", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "@radix-ui/react-tooltip": "^1.2.8", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "embla-carousel-react": "^8.6.0", + "framer-motion": "^12.34.3", + "input-otp": "^1.4.2", + "lucide-react": "^0.562.0", + "next-themes": "^0.4.6", + "react": "^19.2.0", + "react-day-picker": "^9.13.0", + "react-dom": "^19.2.0", + "react-hook-form": "^7.70.0", + "react-resizable-panels": "^4.2.2", + "recharts": "^2.15.4", + "sonner": "^2.0.7", + "tailwind-merge": "^3.4.0", + "vaul": "^1.1.2", + "zod": "^4.3.5" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "kimi-plugin-inspect-react": "^1.0.3", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.19", + "tailwindcss-animate": "^1.0.7", + "tw-animate-css": "^1.4.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@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-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "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-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.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-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "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-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.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-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "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/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "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-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz", + "integrity": "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-decorators": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz", + "integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "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-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "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-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "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-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "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-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "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-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "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-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "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-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "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-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "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-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "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-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "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": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-jsx": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "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-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "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-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "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-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "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-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "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-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "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-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", + "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.28.0", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.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/@date-fns/tz": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", + "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@hookform/resolvers": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", + "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", + "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.8.tgz", + "integrity": "sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-aspect-ratio/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz", + "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", + "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "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/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "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/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", + "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz", + "integrity": "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/type-utils": "8.52.0", + "@typescript-eslint/utils": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.52.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz", + "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.52.0.tgz", + "integrity": "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.52.0", + "@typescript-eslint/types": "^8.52.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz", + "integrity": "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz", + "integrity": "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz", + "integrity": "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/utils": "8.52.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.52.0.tgz", + "integrity": "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz", + "integrity": "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.52.0", + "@typescript-eslint/tsconfig-utils": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.52.0.tgz", + "integrity": "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz", + "integrity": "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-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/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", + "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.6", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.6" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001762", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", + "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", + "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/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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "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/core-js-compat": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-jalali": { + "version": "4.1.0-0", + "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", + "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", + "license": "MIT" + }, + "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/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/embla-carousel": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "license": "MIT" + }, + "node_modules/embla-carousel-react": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz", + "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==", + "license": "MIT", + "dependencies": { + "embla-carousel": "8.6.0", + "embla-carousel-reactive-utils": "8.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz", + "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "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-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.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/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "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-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.34.3", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.3.tgz", + "integrity": "sha512-v81ecyZKYO/DfpTwHivqkxSUBzvceOpoI+wLfgCgoUIKxlFKEXdg0oR9imxwXumT4SFy8vRk9xzJ5l3/Du/55Q==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.34.3", + "motion-utils": "^12.29.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/input-otp": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", + "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "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/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "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/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kimi-plugin-inspect-react": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/kimi-plugin-inspect-react/-/kimi-plugin-inspect-react-1.0.3.tgz", + "integrity": "sha512-dU0dYaFYvczJZWwsyAVQ2No2NTaycd93zk3zrbLj9IPwdZaLBK4d0cESz88lVju3rQuFiwRXYNDaCnbPxn4JcQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/preset-env": "^7.23.0", + "@babel/preset-react": "^7.23.0", + "@babel/preset-typescript": "^7.23.0", + "@babel/types": "^7.23.0", + "magic-string": "^0.30.0" + }, + "peerDependencies": { + "vite": "^7.2.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "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/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/lucide-react": { + "version": "0.562.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz", + "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/motion-dom": { + "version": "12.34.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.3.tgz", + "integrity": "sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.29.2" + } + }, + "node_modules/motion-utils": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", + "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/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "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/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "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/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-day-picker": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.0.tgz", + "integrity": "sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==", + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.4.1", + "date-fns": "^4.1.0", + "date-fns-jalali": "^4.1.0-0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-hook-form": { + "version": "7.70.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.70.0.tgz", + "integrity": "sha512-COOMajS4FI3Wuwrs3GPpi/Jeef/5W1DRR84Yl5/ShlT3dKVFUfoGiEZ/QE6Uw8P4T2/CLJdcTVYKvWBMQTEpvw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "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.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-resizable-panels": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.2.2.tgz", + "integrity": "sha512-BxDTFHxDCyCRPK54X5hpnhoLZbBslUrTTelQDRHo4107FXODjPSTqEWOPlFxE/ho0Vw4JrsRgaWdx6GIK073XA==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "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.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "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/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "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/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "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/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/typescript-eslint": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.52.0.tgz", + "integrity": "sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.52.0", + "@typescript-eslint/parser": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/utils": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.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==", + "dev": true, + "license": "MIT" + }, + "node_modules/vaul": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", + "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/app/package.json b/app/package.json new file mode 100644 index 0000000..09a5f79 --- /dev/null +++ b/app/package.json @@ -0,0 +1,80 @@ +{ + "name": "my-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@hookform/resolvers": "^5.2.2", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-aspect-ratio": "^1.1.8", + "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-menubar": "^1.1.16", + "@radix-ui/react-navigation-menu": "^1.2.14", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-toggle-group": "^1.1.11", + "@radix-ui/react-tooltip": "^1.2.8", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "embla-carousel-react": "^8.6.0", + "framer-motion": "^12.34.3", + "input-otp": "^1.4.2", + "lucide-react": "^0.562.0", + "next-themes": "^0.4.6", + "react": "^19.2.0", + "react-day-picker": "^9.13.0", + "react-dom": "^19.2.0", + "react-hook-form": "^7.70.0", + "react-resizable-panels": "^4.2.2", + "recharts": "^2.15.4", + "sonner": "^2.0.7", + "tailwind-merge": "^3.4.0", + "vaul": "^1.1.2", + "zod": "^4.3.5" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "kimi-plugin-inspect-react": "^1.0.3", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.19", + "tailwindcss-animate": "^1.0.7", + "tw-animate-css": "^1.4.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } +} diff --git a/app/postcss.config.js b/app/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/app/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/app/src/App.css b/app/src/App.css new file mode 100644 index 0000000..2161288 --- /dev/null +++ b/app/src/App.css @@ -0,0 +1,102 @@ +/* App-specific styles */ + +/* Hide scrollbar for momentum cards */ +.scrollbar-hide::-webkit-scrollbar { + display: none; +} + +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; +} + +/* Smooth scroll behavior */ +html { + scroll-behavior: smooth; +} + +/* Selection color */ +::selection { + background-color: rgba(255, 107, 53, 0.3); + color: white; +} + +/* Focus styles */ +*:focus-visible { + outline: 2px solid #ff6b35; + outline-offset: 2px; +} + +/* Button hover effects */ +button { + transition: all 0.2s ease; +} + +button:active { + transform: scale(0.98); +} + +/* Card hover lift effect */ +.stock-card { + transition: all 0.2s cubic-bezier(0.165, 0.84, 0.44, 1); +} + +/* Table row animations */ +tr { + transition: background-color 0.15s ease; +} + +/* Number formatting */ +.number-font { + font-variant-numeric: tabular-nums; + font-feature-settings: "tnum"; +} + +/* Gradient backgrounds */ +.gradient-bg { + background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%); +} + +/* Glass morphism effect */ +.glass-effect { + background: rgba(26, 26, 26, 0.8); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); +} + +/* Pulse animation for live data */ +@keyframes pulse-live { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.7; + } +} + +.live-indicator { + animation: pulse-live 2s ease-in-out infinite; +} + +/* Chart tooltip customization */ +.recharts-tooltip-wrapper { + z-index: 1000 !important; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .section-title { + font-size: 1.25rem; + } + + .stock-card { + padding: 1rem; + } +} + +/* Print styles */ +@media print { + .no-print { + display: none !important; + } +} diff --git a/app/src/App.tsx b/app/src/App.tsx new file mode 100644 index 0000000..ea3a528 --- /dev/null +++ b/app/src/App.tsx @@ -0,0 +1,67 @@ +import { useState } from 'react'; +import { Navbar } from '@/components/Navbar'; +import { Footer } from '@/components/Footer'; +import { StockDetailModal } from '@/components/StockDetailModal'; +import { SectorDetailModal } from '@/components/SectorDetailModal'; +import { MarketOverview } from '@/sections/MarketOverview'; +import { MomentumSectors } from '@/sections/MomentumSectors'; +import { HighLowStocks } from '@/sections/HighLowStocks'; +import { PriceDistribution } from '@/sections/PriceDistribution'; +import { MomentumRecommendation } from '@/sections/MomentumRecommendation'; +import type { Sector } from '@/types'; +import './App.css'; + +function App() { + const [selectedStock, setSelectedStock] = useState(null); + const [isStockModalOpen, setIsStockModalOpen] = useState(false); + const [selectedSector, setSelectedSector] = useState(null); + const [isSectorModalOpen, setIsSectorModalOpen] = useState(false); + + // 处理导航栏搜索选择的版块 + const handleSectorSelect = (sector: Sector) => { + setSelectedSector(sector); + setIsSectorModalOpen(true); + }; + + // 处理导航栏搜索选择的个股 + const handleStockSelect = (stockCode: string) => { + setSelectedStock(stockCode); + setIsStockModalOpen(true); + }; + + return ( +
+ + +
+ + + + + +
+ +
+ + {/* Stock Detail Modal */} + setIsStockModalOpen(false)} + /> + + {/* Sector Detail Modal */} + setIsSectorModalOpen(false)} + onStockClick={handleStockSelect} + /> +
+ ); +} + +export default App; diff --git a/app/src/components/CandlestickChart.tsx b/app/src/components/CandlestickChart.tsx new file mode 100644 index 0000000..24eab18 --- /dev/null +++ b/app/src/components/CandlestickChart.tsx @@ -0,0 +1,397 @@ +import { useMemo, useState, type ReactElement } from 'react'; +import { + ComposedChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell, Line +} from 'recharts'; +import type { KLineData, MaPeriod } from '@/types'; + +interface CandlestickChartProps { + data: KLineData[]; + height?: number; + showVolume?: boolean; + showMaSettings?: boolean; +} + +// 默认均线配置 +const defaultMaPeriods: MaPeriod[] = [ + { key: 'ma5', label: 'MA5', days: 5, color: '#ff9f43', visible: true }, + { key: 'ma10', label: 'MA10', days: 10, color: '#3498db', visible: true }, + { key: 'ma20', label: 'MA20', days: 20, color: '#9b59b6', visible: true }, + { key: 'ma30', label: 'MA30', days: 30, color: '#e74c3c', visible: false }, + { key: 'ma60', label: 'MA60', days: 60, color: '#2ecc71', visible: false }, +]; + +// 自定义蜡烛图Tooltip +const CandleTooltip = ({ active, payload }: any) => { + if (active && payload && payload.length) { + const data = payload[0].payload; + + // 获取均线值 + const maValues: Record = {}; + ['ma5', 'ma10', 'ma20', 'ma30', 'ma60'].forEach(key => { + if (data[key]) maValues[key] = data[key]; + }); + + return ( +
+

{data.date}

+
+
+ 开盘 + + {data.open.toFixed(2)} + +
+
+ 最高 + {data.high.toFixed(2)} +
+
+ 最低 + {data.low.toFixed(2)} +
+
+ 收盘 + = data.open ? 'text-[#ff3b30]' : 'text-[#00c853]'}`}> + {data.close.toFixed(2)} + +
+ {/* 均线 */} + {Object.entries(maValues).length > 0 && ( +
+
均线
+
+ {Object.entries(maValues).map(([key, value]) => { + const maConfig = defaultMaPeriods.find(m => m.key === key); + return ( +
+ {maConfig?.label} + {value.toFixed(2)} +
+ ); + })} +
+
+ )} +
+ 成交量 + {(data.volume / 10000).toFixed(0)}万 +
+
+
+ ); + } + return null; +}; + +// 处理数据,添加涨跌标记 +const processData = (data: KLineData[]) => { + return data.map(item => ({ + ...item, + isUp: item.close >= item.open, + priceChange: ((item.close - item.open) / item.open * 100) + })); +}; + +// 渲染蜡烛图的SVG +const renderCandle = (props: any, maxPrice: number, minPrice: number, pricePadding: number): ReactElement => { + const { x, y, width, height, payload } = props; + + const { open = 0, close = 0 } = payload || {}; + const isUp = close >= open; + const color = isUp ? '#ff3b30' : '#00c853'; + + const priceRange = maxPrice - minPrice + pricePadding * 2; + const chartHeight = height; + + // 影线Y坐标 + const wickTopY = y; + const wickBottomY = y + height; + const centerX = x + width / 2; + + // 实体 + const bodyTop = Math.max(open, close); + const bodyBottom = Math.min(open, close); + const bodyY = y + (maxPrice + pricePadding - bodyTop) / priceRange * chartHeight; + const bodyHeight = Math.max((bodyTop - bodyBottom) / priceRange * chartHeight, 1); + + return ( + + {/* 上影线 */} + + {/* 下影线 */} + + {/* 实体 */} + + + ); +}; + +export function CandlestickChart({ data, height = 400, showVolume = true, showMaSettings = true }: CandlestickChartProps) { + const [maPeriods, setMaPeriods] = useState(defaultMaPeriods); + + const processedData = useMemo(() => processData(data), [data]); + + // 计算价格范围(包含均线) + const allPrices = useMemo(() => { + const prices = data.flatMap(d => [d.high, d.low]); + maPeriods.filter(m => m.visible).forEach(ma => { + data.forEach(d => { + const value = (d as any)[ma.key]; + if (value) prices.push(value); + }); + }); + return prices; + }, [data, maPeriods]); + + const maxPrice = Math.max(...allPrices); + const minPrice = Math.min(...allPrices); + const pricePadding = (maxPrice - minPrice) * 0.1; + + // 计算成交量范围 + const maxVolume = Math.max(...data.map(d => d.volume)); + + // 切换均线显示 + const toggleMa = (key: string) => { + setMaPeriods(prev => prev.map(ma => + ma.key === key ? { ...ma, visible: !ma.visible } : ma + )); + }; + + // 可见的均线 + const visibleMas = maPeriods.filter(m => m.visible); + + if (showVolume) { + // 主图高度占65%,成交量占35% + const mainHeight = height * 0.62; + const volumeHeight = height * 0.28; + + return ( +
+ {/* 均线设置按钮 */} + {showMaSettings && ( +
+
+ {maPeriods.map(ma => ( + + ))} +
+
+ )} + + {/* 主图 - 蜡烛图 + 均线 */} +
+ + + value.slice(5)} + minTickGap={30} + /> + value.toFixed(0)} + width={50} + orientation="right" + /> + } /> + + {/* 均线线 */} + {visibleMas.map(ma => ( + + ))} + + {/* 蜡烛图 */} + renderCandle(props, maxPrice, minPrice, pricePadding)} + > + {processedData.map((_entry, index) => ( + + ))} + + + +
+ + {/* 分隔线 */} +
+ + {/* 副图 - 成交量 */} +
+ + + value.slice(5)} + minTickGap={30} + /> + (value / 10000).toFixed(0) + '万'} + width={50} + orientation="right" + /> + { + if (active && payload && payload.length) { + const d = payload[0].payload; + return ( +
+

{d.date}

+

+ 成交量: {(d.volume / 10000).toFixed(0)}万 +

+
+ ); + } + return null; + }} + /> + + {processedData.map((entry, index) => ( + + ))} + +
+
+
+
+ ); + } + + // 不带成交量的简化版 + return ( +
+ {/* 均线设置 */} + {showMaSettings && ( +
+ {maPeriods.map(ma => ( + + ))} +
+ )} + +
+ + + value.slice(5)} + minTickGap={30} + /> + value.toFixed(0)} + width={50} + orientation="right" + /> + } /> + + {/* 均线线 */} + {visibleMas.map(ma => ( + + ))} + + {/* 蜡烛图 */} + renderCandle(props, maxPrice, minPrice, pricePadding)} + > + {processedData.map((_entry, index) => ( + + ))} + + + +
+
+ ); +} diff --git a/app/src/components/Footer.tsx b/app/src/components/Footer.tsx new file mode 100644 index 0000000..4bfde97 --- /dev/null +++ b/app/src/components/Footer.tsx @@ -0,0 +1,60 @@ +import { motion } from 'framer-motion'; +import { TrendingUp, AlertTriangle } from 'lucide-react'; + +export function Footer() { + return ( + +
+ {/* Disclaimer */} +
+ +
+

风险提示

+

+ 本网站提供的所有数据和分析仅供参考,不构成任何投资建议。股市有风险,投资需谨慎。 + 投资者应根据自身情况独立判断,自行承担投资风险。 +

+
+
+ + {/* Footer Content */} +
+ {/* Logo */} +
+
+ +
+ A股智投 +
+ + {/* Links */} + + + {/* Copyright */} +
+ © 2024 A股智投. All rights reserved. +
+
+
+
+ ); +} diff --git a/app/src/components/Navbar.tsx b/app/src/components/Navbar.tsx new file mode 100644 index 0000000..0ccf11a --- /dev/null +++ b/app/src/components/Navbar.tsx @@ -0,0 +1,259 @@ +import { useState, useEffect, useRef } from 'react'; +import { TrendingUp, Search, Clock, X, Building2, TrendingUp as TrendingUpIcon } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { stockDataService } from '@/services/stockData'; +import type { Sector, Stock } from '@/types'; + +interface NavbarProps { + onSectorClick?: (sector: Sector) => void; + onStockClick?: (stockCode: string) => void; +} + +export function Navbar({ onSectorClick, onStockClick }: NavbarProps) { + const [scrolled, setScrolled] = useState(false); + const [currentTime, setCurrentTime] = useState(new Date()); + const [searchOpen, setSearchOpen] = useState(false); + const [searchKeyword, setSearchKeyword] = useState(''); + const [searchResults, setSearchResults] = useState<{ sectors: Sector[]; stocks: Stock[] }>({ sectors: [], stocks: [] }); + const searchInputRef = useRef(null); + const searchContainerRef = useRef(null); + + useEffect(() => { + const handleScroll = () => { + setScrolled(window.scrollY > 20); + }; + + const timer = setInterval(() => { + setCurrentTime(new Date()); + }, 1000); + + window.addEventListener('scroll', handleScroll); + return () => { + window.removeEventListener('scroll', handleScroll); + clearInterval(timer); + }; + }, []); + + // 点击外部关闭搜索 + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (searchContainerRef.current && !searchContainerRef.current.contains(e.target as Node)) { + setSearchOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + // 搜索逻辑 + useEffect(() => { + if (searchKeyword.trim().length >= 1) { + const sectors = stockDataService.searchSectors(searchKeyword); + const stocks = stockDataService.searchStocks(searchKeyword); + setSearchResults({ sectors, stocks }); + } else { + setSearchResults({ sectors: [], stocks: [] }); + } + }, [searchKeyword]); + + // 打开搜索时聚焦输入框 + useEffect(() => { + if (searchOpen && searchInputRef.current) { + searchInputRef.current.focus(); + } + }, [searchOpen]); + + const handleSectorSelect = (sector: Sector) => { + setSearchOpen(false); + setSearchKeyword(''); + onSectorClick?.(sector); + }; + + const handleStockSelect = (stock: Stock) => { + setSearchOpen(false); + setSearchKeyword(''); + onStockClick?.(stock.code); + }; + + const navItems = [ + { label: '首页', href: '#overview' }, + { label: '动量分析', href: '#momentum' }, + { label: '新高新低', href: '#highlow' }, + { label: '涨跌分布', href: '#distribution' }, + { label: '动量推荐', href: '#recommendation' } + ]; + + const hasResults = searchResults.sectors.length > 0 || searchResults.stocks.length > 0; + + return ( + +
+ {/* Logo */} +
+
+ +
+ A股智投 +
+ + {/* Navigation */} +
+ {navItems.map((item) => ( + + {item.label} + + ))} +
+ + {/* Right Section */} +
+ {/* Search */} +
+ + {searchOpen ? ( + +
+ + setSearchKeyword(e.target.value)} + placeholder="搜索版块或个股..." + className="flex-1 bg-transparent px-3 py-2 text-sm text-white placeholder-[#666] outline-none" + /> + +
+ + {/* Search Results Dropdown */} + + {hasResults && ( + + {/* Sectors */} + {searchResults.sectors.length > 0 && ( +
+
+ + 版块 ({searchResults.sectors.length}) +
+ {searchResults.sectors.map((sector) => ( + + ))} +
+ )} + + {/* Stocks */} + {searchResults.stocks.length > 0 && ( +
+
+ + 个股 ({searchResults.stocks.length}) +
+ {searchResults.stocks.map((stock) => ( + + ))} +
+ )} +
+ )} + + {/* No Results */} + {searchKeyword.trim().length >= 1 && !hasResults && ( + +
未找到相关结果
+
试试搜索 "半导体" 或 "茅台"
+
+ )} +
+
+ ) : ( + setSearchOpen(true)} + className="p-2 hover:bg-[#1a1a1a] rounded-lg transition-colors" + > + + + )} +
+
+ + {/* Time */} +
+ + + {currentTime.toLocaleTimeString('zh-CN', { hour12: false })} + +
+
+
+
+ ); +} diff --git a/app/src/components/SectorDetailModal.tsx b/app/src/components/SectorDetailModal.tsx new file mode 100644 index 0000000..f512d0c --- /dev/null +++ b/app/src/components/SectorDetailModal.tsx @@ -0,0 +1,357 @@ +import { useEffect, useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { X, BarChart3, Trophy, Target } from 'lucide-react'; +import { + XAxis, YAxis, Tooltip, ResponsiveContainer, + ComposedChart, Bar, Line +} from 'recharts'; +import { CandlestickChart } from './CandlestickChart'; +import { stockDataService } from '@/services/stockData'; +import type { Sector, MomentumStock, KLineData, SectorMomentumHistory } from '@/types'; + +interface SectorDetailModalProps { + sector: Sector | null; + isOpen: boolean; + onClose: () => void; + onStockClick?: (stockCode: string) => void; +} + +const RankTooltip = ({ active, payload, label }: any) => { + if (active && payload && payload.length) { + return ( +
+

{label}

+

排名: {payload[0].value}

+

动量分: {payload[1]?.value}

+
+ ); + } + return null; +}; + +export function SectorDetailModal({ sector, isOpen, onClose, onStockClick }: SectorDetailModalProps) { + const [activeTab, setActiveTab] = useState<'overview' | 'stocks' | 'kline'>('overview'); + const [rankHistory, setRankHistory] = useState([]); + const [momentumStocks, setMomentumStocks] = useState([]); + const [klineData, setKlineData] = useState([]); + + useEffect(() => { + if (sector && isOpen) { + setRankHistory(stockDataService.getSectorRankHistory(sector.name)); + setMomentumStocks(stockDataService.getSectorMomentumStocks(sector.name)); + setKlineData(stockDataService.getSectorKLineData(sector.name, 60)); + } + }, [sector, isOpen]); + + useEffect(() => { + const handleEsc = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', handleEsc); + return () => window.removeEventListener('keydown', handleEsc); + }, [onClose]); + + if (!sector) return null; + + const getScoreColor = (score: number) => { + if (score >= 85) return 'text-[#ff3b30]'; + if (score >= 70) return 'text-[#ff9f43]'; + return 'text-[#b0b0b0]'; + }; + + const getScoreBg = (score: number) => { + if (score >= 85) return 'bg-[#ff3b30]/20'; + if (score >= 70) return 'bg-[#ff9f43]/20'; + return 'bg-[#2a2a2a]'; + }; + + return ( + + {isOpen && ( + + e.stopPropagation()} + > + {/* Header */} +
+
+
+

{sector.name}

+ 版块代码: {sector.code} +
+
+ = 0 ? 'bg-[#ff3b30]/20 text-[#ff3b30]' : 'bg-[#00c853]/20 text-[#00c853]' + }`}> + {sector.changePercent >= 0 ? '+' : ''}{sector.changePercent.toFixed(2)}% + +
+
+ +
+
+
+ 动量排名 + #{sector.rank} +
+ {sector.rankChange !== undefined && sector.rankChange !== 0 && ( +
0 ? 'text-[#ff3b30]' : 'text-[#00c853]'}`}> + {sector.rankChange > 0 ? '↑' : '↓'} {Math.abs(sector.rankChange)} 位 +
+ )} +
+ +
+
+ + {/* Tabs */} +
+ {[ + { id: 'overview', label: '历史排名', icon: Trophy }, + { id: 'stocks', label: '动量个股', icon: Target }, + { id: 'kline', label: 'K线走势', icon: BarChart3 } + ].map(tab => ( + + ))} +
+ + {/* Content */} +
+ {/* Overview Tab - Rank History */} + {activeTab === 'overview' && ( +
+ {/* Stats Cards */} +
+
+
当前排名
+
#{sector.rank}
+
+
+
动量分数
+
+ {sector.momentumScore?.toFixed(1)} +
+
+
+
排名变化
+
0 ? 'text-[#ff3b30]' : (sector.rankChange || 0) < 0 ? 'text-[#00c853]' : 'text-[#b0b0b0]' + }`}> + {(sector.rankChange || 0) > 0 ? '+' : ''}{sector.rankChange} +
+
+
+
成交额
+
+ {(sector.turnover / 100000000).toFixed(1)}亿 +
+
+
+ + {/* Rank History Chart */} +
+

30日排名走势

+
+ + + value.slice(5)} + /> + + + } /> + + + + +
+
+
+ )} + + {/* Stocks Tab - Momentum Stocks */} + {activeTab === 'stocks' && ( +
+
+

版块内动量个股排行

+ 共 {momentumStocks.length} 只 +
+ +
+ {momentumStocks.map((stock, index) => ( + onStockClick?.(stock.code)} + className="bg-[#0a0a0a] border border-[#2a2a2a] rounded-xl p-4 cursor-pointer transition-all" + > +
+
+ + {index + 1} + +
+
{stock.name}
+
{stock.code}
+
+
+
+
{stock.price.toFixed(2)}
+
= 0 ? 'stock-up' : 'stock-down'}`}> + {stock.changePercent >= 0 ? '+' : ''}{stock.changePercent.toFixed(2)}% +
+
+
+ +
+
+ 动量分 + + {stock.momentumScore} + +
+
+ 量比 + {stock.volumeRatio.toFixed(2)} +
+
+ {stock.tags.map((tag, i) => ( + + {tag} + + ))} + {stock.breakThrough && ( + + 突破 + + )} +
+
+
+ ))} +
+
+ )} + + {/* KLine Tab */} + {activeTab === 'kline' && ( +
+
+
+

版块指数K线走势

+
+ + {/* Candlestick Chart with MA and Volume */} + +
+ + {/* Stats */} + {klineData.length > 0 && ( +
+
+
最新
+
{klineData[klineData.length - 1].close.toFixed(2)}
+
+
+
最高
+
+ {Math.max(...klineData.map(d => d.high)).toFixed(2)} +
+
+
+
最低
+
+ {Math.min(...klineData.map(d => d.low)).toFixed(2)} +
+
+
+
涨跌
+
= klineData[0].open ? 'text-[#ff3b30]' : 'text-[#00c853]' + }`}> + {((klineData[klineData.length - 1].close - klineData[0].open) / klineData[0].open * 100).toFixed(2)}% +
+
+
+
振幅
+
+ {((Math.max(...klineData.map(d => d.high)) - Math.min(...klineData.map(d => d.low))) / klineData[0].open * 100).toFixed(2)}% +
+
+
+ )} +
+ )} +
+
+
+ )} +
+ ); +} diff --git a/app/src/components/StockDetailModal.tsx b/app/src/components/StockDetailModal.tsx new file mode 100644 index 0000000..85eb0bf --- /dev/null +++ b/app/src/components/StockDetailModal.tsx @@ -0,0 +1,293 @@ +import { useEffect, useState } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { X, BarChart3, Activity, Target } from 'lucide-react'; +import { CandlestickChart } from './CandlestickChart'; +import { stockDataService } from '@/services/stockData'; +import type { StockDetail, KLineData } from '@/types'; + +interface StockDetailModalProps { + stockCode: string | null; + isOpen: boolean; + onClose: () => void; +} + +export function StockDetailModal({ stockCode, isOpen, onClose }: StockDetailModalProps) { + const [stock, setStock] = useState(null); + const [klineData, setKlineData] = useState([]); + const [timeRange, setTimeRange] = useState<'day' | 'week' | 'month'>('day'); + + useEffect(() => { + if (stockCode && isOpen) { + setStock(stockDataService.getStockDetail(stockCode)); + setKlineData(stockDataService.getKLineData(stockCode, 60)); + } + }, [stockCode, isOpen]); + + useEffect(() => { + const handleEsc = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', handleEsc); + return () => window.removeEventListener('keydown', handleEsc); + }, [onClose]); + + if (!stock) return null; + + const getRecommendation = () => { + if (stock.macd && stock.macd.macd > 0 && stock.kdj && stock.kdj.j > 50) { + return { text: '买入', color: 'text-[#ff3b30]', bg: 'bg-[#ff3b30]/20' }; + } + if (stock.macd && stock.macd.macd < 0 && stock.kdj && stock.kdj.j < 50) { + return { text: '观望', color: 'text-[#00c853]', bg: 'bg-[#00c853]/20' }; + } + return { text: '持有', color: 'text-[#ff9f43]', bg: 'bg-[#ff9f43]/20' }; + }; + + const recommendation = getRecommendation(); + + return ( + + {isOpen && ( + + e.stopPropagation()} + > + {/* Header */} +
+
+
+

{stock.name}

+ {stock.code} +
+ + {stock.industry} + +
+ +
+
+
+ {stock.price.toFixed(2)} +
+
= 0 ? 'stock-up' : 'stock-down'}`}> + {stock.changePercent >= 0 ? '+' : ''}{stock.change.toFixed(2)} + ({stock.changePercent >= 0 ? '+' : ''}{stock.changePercent.toFixed(2)}%) +
+
+ +
+
+ +
+ {/* Price Stats */} +
+
+
今开
+
{stock.open.toFixed(2)}
+
+
+
最高
+
{stock.high.toFixed(2)}
+
+
+
最低
+
{stock.low.toFixed(2)}
+
+
+
昨收
+
{stock.preClose.toFixed(2)}
+
+
+ + {/* Candlestick Chart */} +
+
+
+ + K线走势 +
+
+ {/* 周期切换 */} +
+ {(['day', 'week', 'month'] as const).map((range) => ( + + ))} +
+
+
+ + {/* Candlestick Chart with MA and Volume */} + +
+ + {/* Technical Indicators */} +
+ {/* MACD */} +
+
+ + MACD +
+ {stock.macd && ( +
+
+ DIF + = 0 ? 'text-[#ff3b30]' : 'text-[#00c853]'}`}> + {stock.macd.dif.toFixed(3)} + +
+
+ DEA + = 0 ? 'text-[#ff3b30]' : 'text-[#00c853]'}`}> + {stock.macd.dea.toFixed(3)} + +
+
+ MACD + = 0 ? 'text-[#ff3b30]' : 'text-[#00c853]'}`}> + {stock.macd.macd.toFixed(3)} + +
+
+ )} +
+ + {/* KDJ */} +
+
+ + KDJ +
+ {stock.kdj && ( +
+
+ K + {stock.kdj.k.toFixed(2)} +
+
+ D + {stock.kdj.d.toFixed(2)} +
+
+ J + 80 ? 'text-[#ff3b30]' : stock.kdj.j < 20 ? 'text-[#00c853]' : 'text-white'}`}> + {stock.kdj.j.toFixed(2)} + +
+
+ )} +
+ + {/* RSI */} +
+
+ + RSI +
+ {stock.rsi && ( +
+
+ RSI6 + 70 ? 'text-[#ff3b30]' : stock.rsi.rsi6 < 30 ? 'text-[#00c853]' : 'text-white'}`}> + {stock.rsi.rsi6.toFixed(2)} + +
+
+ RSI12 + 70 ? 'text-[#ff3b30]' : stock.rsi.rsi12 < 30 ? 'text-[#00c853]' : 'text-white'}`}> + {stock.rsi.rsi12.toFixed(2)} + +
+
+ RSI24 + 70 ? 'text-[#ff3b30]' : stock.rsi.rsi24 < 30 ? 'text-[#00c853]' : 'text-white'}`}> + {stock.rsi.rsi24.toFixed(2)} + +
+
+ )} +
+
+ + {/* Fundamental & Recommendation */} +
+
+

基本面数据

+
+
+ 市盈率(PE) +
{stock.pe?.toFixed(2)}
+
+
+ 市净率(PB) +
{stock.pb?.toFixed(2)}
+
+
+ 总市值 +
+ {(stock.marketCap! / 100000000).toFixed(2)}亿 +
+
+
+ 换手率 +
{stock.turnoverRate?.toFixed(2)}%
+
+
+
+ +
+

操作建议

+
+
+ + {recommendation.text} + +
+
+

+ 基于MACD、KDJ等技术指标综合分析,该股票目前处于 + {recommendation.text} + 状态。请结合个人风险偏好谨慎决策。 +

+
+
+
+
+
+
+
+ )} +
+ ); +} diff --git a/app/src/components/ui/accordion.tsx b/app/src/components/ui/accordion.tsx new file mode 100644 index 0000000..d21b65f --- /dev/null +++ b/app/src/components/ui/accordion.tsx @@ -0,0 +1,64 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDownIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Accordion({ + ...props +}: React.ComponentProps) { + return +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/app/src/components/ui/alert-dialog.tsx b/app/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..935eecf --- /dev/null +++ b/app/src/components/ui/alert-dialog.tsx @@ -0,0 +1,155 @@ +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/app/src/components/ui/alert.tsx b/app/src/components/ui/alert.tsx new file mode 100644 index 0000000..1421354 --- /dev/null +++ b/app/src/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/app/src/components/ui/aspect-ratio.tsx b/app/src/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..3df3fd0 --- /dev/null +++ b/app/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,11 @@ +"use client" + +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return +} + +export { AspectRatio } diff --git a/app/src/components/ui/avatar.tsx b/app/src/components/ui/avatar.tsx new file mode 100644 index 0000000..b7224f0 --- /dev/null +++ b/app/src/components/ui/avatar.tsx @@ -0,0 +1,51 @@ +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/app/src/components/ui/badge.tsx b/app/src/components/ui/badge.tsx new file mode 100644 index 0000000..fd3a406 --- /dev/null +++ b/app/src/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/app/src/components/ui/breadcrumb.tsx b/app/src/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..eb88f32 --- /dev/null +++ b/app/src/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { + return