From 85a0031a78004e99fb821b5629c37d0c646ca042 Mon Sep 17 00:00:00 2001 From: Rekey Date: Mon, 8 Jun 2026 01:24:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(data):=20=E5=AE=9E=E7=8E=B0=20Binance=20We?= =?UTF-8?q?bSocket=20=E9=80=82=E9=85=8D=E5=99=A8=E4=B8=8E=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 exchanges/ 模块:MarketDataFeed 统一接口、BaseExchangeAdapter 抽象基类、 BinanceAdapter 完整实现(WebSocket + REST) - WebSocket 层基于 binance 官方 SDK 的 WebsocketClient,自动多路复用与断线重连 - REST 层使用 MainClient(Spot),实现 fetchKlines 自动分页补拉 + fetchMarkets 元数据解析 - 数据标准化:Ticker/Trade/OrderBook/Kline 类型定义与 Binance 原生格式互转 - 引入 RxJS Subject 作为统一事件流管道,按 eventType 运行时路由分发 - 重构 config/:YAML 驱动配置加载 + 零依赖运行时校验(fail-fast) - 重构 db/:TypeORM DataSource 配置 + TimescaleDB K 线 Hypertable 实体 - 新增 utils/logger.ts:Pino 结构化日志(开发环境 pino-pretty 彩色输出) - 新增 env.yaml 作为 TS/Python 共享的统一环境配置源 - 删除旧版手写 SQL schema 与散落配置文件,收敛到 TypeORM 实体管理 - 安装 rxjs@7.8.2 依赖 --- data/.env.example | 38 - data/ARCHITECTURE.md | 1348 ++++++ data/bun.lock | 985 +++++ data/config.ts | 184 - data/config/index.ts | 160 + data/config/validators.ts | 149 + data/db/config-crud.ts | 530 --- data/db/data-source.ts | 29 + data/db/entities/common.entity.ts | 32 + data/db/entities/exchange.entity.ts | 41 + data/db/entities/index.ts | 12 + data/db/entities/kline.entity.ts | 138 + data/db/entities/trading-pair.entity.ts | 105 + data/db/index.ts | 121 - data/db/pg.ts | 364 -- data/db/queries.ts | 561 --- data/db/types.ts | 249 -- data/db/validators.ts | 245 -- data/exchanges/base.ts | 186 + data/exchanges/binance.ts | 1063 +++-- data/exchanges/types.ts | 330 +- data/init-db/001_init.sql | 228 - data/init-db/002_config.sql | 136 - data/package-lock.json | 5320 ----------------------- data/package.json | 8 +- data/run/exchange.ts | 19 - data/schema/config.sql | 177 - data/schema/klines.sql | 206 - data/tsconfig.json | 4 + data/utils/logger.ts | 20 + env.yaml | 30 + 31 files changed, 4261 insertions(+), 8757 deletions(-) delete mode 100644 data/.env.example create mode 100644 data/ARCHITECTURE.md create mode 100644 data/bun.lock delete mode 100644 data/config.ts create mode 100644 data/config/index.ts create mode 100644 data/config/validators.ts delete mode 100644 data/db/config-crud.ts create mode 100644 data/db/data-source.ts create mode 100644 data/db/entities/common.entity.ts create mode 100644 data/db/entities/exchange.entity.ts create mode 100644 data/db/entities/index.ts create mode 100644 data/db/entities/kline.entity.ts create mode 100644 data/db/entities/trading-pair.entity.ts delete mode 100644 data/db/index.ts delete mode 100644 data/db/pg.ts delete mode 100644 data/db/queries.ts delete mode 100644 data/db/types.ts delete mode 100644 data/db/validators.ts create mode 100644 data/exchanges/base.ts delete mode 100644 data/init-db/001_init.sql delete mode 100644 data/init-db/002_config.sql delete mode 100644 data/package-lock.json delete mode 100644 data/run/exchange.ts delete mode 100644 data/schema/config.sql delete mode 100644 data/schema/klines.sql create mode 100644 data/utils/logger.ts create mode 100644 env.yaml diff --git a/data/.env.example b/data/.env.example deleted file mode 100644 index 0c7e6b9..0000000 --- a/data/.env.example +++ /dev/null @@ -1,38 +0,0 @@ -# ============================================================ -# Trade Data Module — 环境变量配置模板 -# ============================================================ -# 复制为 .env 并修改: -# cp .env.example .env -# ============================================================ - -# --- TimescaleDB 连接 --- -DB_HOST=localhost -DB_PORT=5432 -DB_NAME=trade -DB_USER=trader -DB_PASSWORD=changeme - -# --- Redis 连接 --- -REDIS_URL=redis://localhost:6379 -# 是否启用 Redis 发布(开发时可关闭) -REDIS_PUBLISH_ENABLED=true - -# --- 批量写入 --- -# 缓冲区条数阈值(达到后自动刷新) -BATCH_SIZE=500 -# 最大缓冲时间(毫秒),超时后自动刷新 -FLUSH_INTERVAL_MS=1000 - -# --- WebSocket 连接 --- -# 断线重连延迟基数(毫秒),指数退避:基数 × 2^attempts -WS_RECONNECT_DELAY_MS=3000 -# 心跳间隔(毫秒) -WS_PING_INTERVAL_MS=30000 -# 最大重连次数 -WS_MAX_RECONNECT_ATTEMPTS=10 - -# --- 日志 --- -# 日志级别:trace / debug / info / warn / error / fatal -LOG_LEVEL=debug -# 生产环境(关闭 pretty print,输出 JSON) -NODE_ENV=development diff --git a/data/ARCHITECTURE.md b/data/ARCHITECTURE.md new file mode 100644 index 0000000..1566dbc --- /dev/null +++ b/data/ARCHITECTURE.md @@ -0,0 +1,1348 @@ +# Trade Data Module — 技术架构说明 + +> **模块定位**:数字货币量化交易系统的数据层,负责多交易所行情采集、K 线合成、时序数据持久化与实时发布。 +> +> **运行时**:[Bun](https://bun.sh) | **语言**:TypeScript 5.x | **数据库**:PostgreSQL + TimescaleDB | **ORM**:TypeORM + @timescaledb/typeorm + +--- + +## 目录 + +1. [技术选型与依赖矩阵](#1-技术选型与依赖矩阵) +2. [整体架构](#2-整体架构) +3. [目录结构](#3-目录结构) +4. [核心模块设计](#4-核心模块设计) + - [4.1 配置模块 (`config/`)](#41-配置模块-config) + - [4.2 TypeORM 数据源 (`db/`)](#42-typeorm-数据源-db) + - [4.3 TimescaleDB K 线实体 (`db/entities/klines/`)](#43-timescaledb-k-线实体-dbentitiesklines) + - [4.4 关系数据实体 (`db/entities/`)](#44-关系数据实体-dbentities) + - [4.5 交易所适配器 (`exchanges/`)](#45-交易所适配器-exchanges) + - [4.6 K 线合成管道 (`pipeline/`)](#46-k-线合成管道-pipeline) + - [4.7 数据发布 (`publisher/`)](#47-数据发布-publisher) +5. [数据流生命周期](#5-数据流生命周期) +6. [TypeORM + TimescaleDB 集成细节](#6-typeorm--timescaledb-集成细节) +7. [配置管理策略](#7-配置管理策略) +8. [日志与可观测性](#8-日志与可观测性) +9. [错误处理与容错](#9-错误处理与容错) +10. [性能考量](#10-性能考量) +11. [开发工作流](#11-开发工作流) +12. [风险提示](#12-风险提示) + +--- + +## 1. 技术选型与依赖矩阵 + +### 1.1 为什么选择 Bun + TypeScript? + +| 决策点 | 选型 | 理由 | +|--------|------|------| +| **运行时** | Bun | 原生支持 TypeScript,零配置运行 `.ts` 文件;内置 `bun:sql` 可在无 ORM 场景直连 PG;启动速度比 Node.js 快 4×;兼容 Node.js ecosystem | +| **语言** | TypeScript 5.x | 编译期类型检查,在数据管道中消除 `undefined` / 类型不匹配导致的运行时崩溃 | +| **包管理** | `bun install` | 与 Bun 运行时一体,安装速度比 npm 快 25× | +| **测试** | Vitest | 原生 ESM 支持,与 Bun 兼容良好,内置断言 + 覆盖率 | + +### 1.2 依赖矩阵 + +| 类别 | 依赖包 | 版本 | 用途 | +|------|--------|------|------| +| **ORM 框架** | `typeorm` | ^1.0 | 关系数据(交易对配置、交易所元数据)的 ORM 映射 | +| **时序 ORM** | `@timescaledb/typeorm` | ^0.0.1 | TimescaleDB hypertable 实体定义、自动迁移、连续聚合管理 | +| **PG 驱动** | `pg` | ^8.21 | TypeORM 底层 PG 连接;独立批量写入场景的备选路径 | +| **Redis** | `ioredis` | ^5.11 | Pub/Sub 行情发布、缓存、跨语言消息队列 | +| **交易所 SDK** | `ccxt` | ^4.5 | 统一 100+ 交易所 REST API | +| **交易所 WS** | `binance` | ^3.5 | Binance 官方 WebSocket 客户端(ccxt 的 WS 能力较弱) | +| **WebSocket** | `ws` | ^8.21 | 通用 WebSocket 客户端(非 Binance 交易所) | +| **日志** | `pino` | ^10.3 | 高性能结构化日志(Bun 下吞吐 > 100k/s) | +| **YAML 解析** | `yaml` | ^2.9 | 解析项目根目录 env.yaml 配置文件 | + +### 1.3 TypeORM + TimescaleDB 分工边界 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ PostgreSQL 实例 │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ TypeORM 管理域(关系数据) │ │ +│ │ · exchanges(交易所配置) │ │ +│ │ · trading_pairs(交易对配置) │ │ +│ │ · symbols(币种元数据) │ │ +│ │ · data_sources(数据源注册) │ │ +│ │ → Migration: TypeORM 自动/手动迁移 │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────┐ │ +│ │ @timescaledb/typeorm 管理域(时序) │ │ +│ │ · klines_1m(1 分钟 K 线 hypertable)│ │ +│ │ · klines_1h(1 小时连续聚合视图) │ │ +│ │ · trades(逐笔成交 hypertable) │ │ +│ │ → Migration: @timescaledb/typeorm │ │ +│ │ 自动创建 hypertable + 压缩策略 │ │ +│ └─────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +> **设计原则**:TypeORM 管理结构稳定的关系数据(schema 变更频率低);`@timescaledb/typeorm` 管理写入密集型时序数据(利用 TimescaleDB 的自动分区、压缩、保留策略)。 + +--- + +## 2. 整体架构 + +``` +┌──────────────────────────────────────────────────────────────────────┐ +│ Trade Data Module (Bun + TS) │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Binance │ │ OKX │ │ Bybit │ ← 交易所 WebSocket 适配器 │ +│ │ Adapter │ │ Adapter │ │ Adapter │ │ +│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ +│ │ │ │ │ +│ └──────────────┼──────────────┘ │ +│ ▼ │ +│ ┌────────────────────────────────────────┐ │ +│ │ 统一行情事件流(RxJS) │ │ +│ │ ticker$ │ trade$ │ orderbook$ │ │ +│ └──────────────────┬─────────────────────┘ │ +│ ▼ │ +│ ┌────────────────────────────────────────┐ │ +│ │ K 线合成管道(时间桶算法) │ │ +│ │ Trade → 1m → 5m → 15m → 1h → 4h → 1d │ │ +│ └────────┬───────────────────┬───────────┘ │ +│ ▼ ▼ │ +│ ┌────────────────┐ ┌──────────────────┐ │ +│ │ TimescaleDB │ │ Redis Pub/Sub │ │ +│ │ @timescaledb/ │ │ (ioredis) │ │ +│ │ typeorm 写入 │ │ 行情实时发布 │ │ +│ └───────┬────────┘ └────────┬─────────┘ │ +│ │ │ │ +│ ┌───────▼────────┐ ┌───────▼─────────┐ │ +│ │ 连续聚合视图 │ │ Python 策略引擎 │ │ +│ │ (自动刷新) │ │ (消费实时行情) │ │ +│ └────────────────┘ └─────────────────┘ │ +│ │ +│ ┌────────────────────────────────────────┐ │ +│ │ TypeORM 实体(关系数据) │ │ +│ │ Exchange / TradingPair / Symbol / │ │ +│ │ DataSource / Subscription │ │ +│ └────────────────────────────────────────┘ │ +└──────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 3. 目录结构 + +``` +data/ +├── ARCHITECTURE.md # ← 本文件:技术架构说明 +├── package.json # 依赖与脚本 +├── tsconfig.json # TypeScript 配置 +├── bun.lock # Bun 依赖锁定文件 +│ +├── config/ # 中心化配置模块(目录) +│ ├── index.ts # 配置加载与分组导出(pgsql / redis / logging) +│ └── validators.ts # 零依赖运行时校验(env.yaml → EnvConfig) +│ +├── db/ # 数据库层 +│ ├── data-source.ts # TypeORM DataSource 配置 +│ │ +│ ├── entities/ # 所有 ORM 实体(关系 + 时序) +│ │ ├── index.ts # 实体统一导出 +│ │ ├── common.entity.ts # 公共基类(UUID 主键 + created_at/updated_at) +│ │ ├── exchange.entity.ts # 交易所(binance/okx/bybit) +│ │ ├── trading-pair.entity.ts # 交易对(BTCUSDT/ETHUSDT) +│ │ └── kline.entity.ts # TimescaleDB K 线 hypertable(1m) +│ │ +│ └── migrations/ # TypeORM 迁移文件(自动生成) +│ └── .gitkeep +├── exchanges/ # 交易所适配器(待实现) +├── pipeline/ # K 线合成管道(待实现) +├── publisher/ # Redis 数据发布(待实现) +├── types/ # 共享类型定义 +├── utils/ # 工具函数 +├── index.ts # 模块入口 +├── logger.ts # Pino 日志实例 +└── tests/ # 测试 +``` + +--- + +## 4. 核心模块设计 + +### 4.1 配置模块 [`config/`](config/index.ts:1) + +``` +加载流程: + 项目根目录 env.yaml → parseYaml() 解析 → validateConfig() 零依赖校验 + → 按职责分组导出: pgsql / redis / logging +``` + +| 导出对象 | 字段 | 来源 | 说明 | +|----------|------|------|------| +| `pgsql` | host, port, database, user, password, max, idleTimeoutMillis, connectionTimeoutMillis | `env.yaml` → 校验 + 硬编码 | pg Pool 配置,TypeORM DataSource 复用;`max`/`idleTimeoutMillis`/`connectionTimeoutMillis` 硬编码 | +| `redis` | url, publishEnabled, channelPrefix, retryDelayBaseMs, maxRetries | `env.yaml` → 校验 + 硬编码 | ioredis 连接与 Pub/Sub 配置;`channelPrefix`/`retryDelayBaseMs`/`maxRetries` 硬编码 | +| `logging` | level, nodeEnv, pretty | `env.yaml` → 校验 | Pino 日志配置;`pretty` 由 `node_env === "development"` 推导 | + +> **设计原则**:仅数据库连接、Redis 连接、日志级别等运维配置通过 `env.yaml` 暴露。配置源为项目根目录的 `env.yaml`,TypeScript/Python 模块共享同一份配置。 + +**校验定义位置**:[`config/validators.ts`](config/validators.ts) — 零依赖运行时校验(`assertString` / `assertPort` / `assertBoolean` / `assertEnum`),fail-fast 原则。 + +**配置分组详情**: + +```typescript +// —— pgsql: 数据库连接 —— +export const pgsql = { + host: rawConfig.db.host, // env.yaml default: "localhost" + port: rawConfig.db.port, // env.yaml default: 5432 + database: rawConfig.db.name, // env.yaml default: "trade" + user: rawConfig.db.user, // env.yaml default: "trader" + password: rawConfig.db.password, // env.yaml required + max: 20, // 硬编码:连接池上限 + idleTimeoutMillis: 30000, // 硬编码:空闲超时 30s + connectionTimeoutMillis: 5000, // 硬编码:连接获取超时 5s +} as const; + +// —— redis: 消息队列 —— +export const redis = { + url: rawConfig.redis.url, // env.yaml default: "redis://localhost:6379" + publishEnabled: rawConfig.redis.publish_enabled, // env.yaml default: true + channelPrefix: "trade", // 硬编码 + retryDelayBaseMs: 1000, // 硬编码 + maxRetries: 10, // 硬编码 +} as const; + +// —— logging: 日志 —— +export const logging = { + level: rawConfig.logging.level, // env.yaml default: "debug" + nodeEnv: rawConfig.logging.node_env, // env.yaml default: "development" + pretty: rawConfig.logging.node_env === "development", +} as const; +``` + +--- + +### 4.2 TypeORM 数据源 [`db/data-source.ts`](db/data-source.ts) + +```typescript +// data/db/data-source.ts +import { DataSource } from "typeorm"; +import { pgsql } from "../config"; +import * as entities from "./entities"; + +export const AppDataSource = new DataSource({ + type: "postgres", + host: pgsql.host, port: pgsql.port, + database: pgsql.database, + username: pgsql.user, password: pgsql.password, + // 所有实体(关系 + 时序)通过 db/entities/index.ts 统一注册 + entities: [...Object.values(entities)], + synchronize: true, // 开发环境自动同步 schema + migrations: [__dirname + "/migrations/*.{ts,js}"], + extra: { + max: pgsql.max, + idleTimeoutMillis: pgsql.idleTimeoutMillis, + connectionTimeoutMillis: pgsql.connectionTimeoutMillis, + }, +}); +await AppDataSource.initialize(); +``` + +#### 实体注册原则 + +- **关系实体**(`Exchange`, `TradingPair`):继承 `CommonBaseEntity`(UUID 主键 + created_at/updated_at),标准 `@Entity()` 装饰器 +- **时序实体**(`Kline`):不继承 `CommonBaseEntity`,使用 `@Hypertable()` + `@TimeColumn()` 自动创建 TimescaleDB hypertable +- **统一入口**:所有实体通过 [`db/entities/index.ts`](db/entities/index.ts) 导出,`...Object.values(entities)` 自动注册 + +--- + +### 4.3 TimescaleDB K 线实体 [`db/entities/klines/`](db/entities/klines/) + +#### [`kline.entity.ts`](db/entities/klines/kline.entity.ts) — 1m K 线 Hypertable + +```typescript +// data/db/entities/klines/kline.entity.ts +import { + Hypertable, + TimeColumn, + PartitionColumn, + Compress, + ContinuousAggregate, +} from "@timescaledb/typeorm"; +import { + Entity, + PrimaryColumn, + Column, + Unique, + Index, + CreateDateColumn, + UpdateDateColumn, +} from "typeorm"; + +/** + * 1 分钟 K 线 Hypertable + * + * TimescaleDB 自动按 time 列分区(chunk_time_interval = 1 day), + * 按 exchange 做空间分区(number_partitions = 4), + * 关闭 7 天后自动启用列式压缩。 + */ +@Hypertable({ + chunk_time_interval: "1 day", // 每个 chunk 含 1 天数据 + partitioning_column: "exchange", // 空间分区列 + number_partitions: 4, // 4 个空间分区 + if_not_exists: true, +}) +@Compress({ + segmentby: "exchange, symbol, interval", // 压缩分区键 + orderby: "time DESC", // 压缩排序键 + compress_after: "7 days", // 7 天后自动压缩 +}) +@Index(["exchange", "symbol", "interval", "time"], { unique: true }) +export class Kline { + @TimeColumn() // @timescaledb/typeorm: 标记为时间分区列 + @PrimaryColumn("timestamptz") + time!: Date; + + @PartitionColumn() // @timescaledb/typeorm: 标记为空间分区列 + @Column("text") + exchange!: string; + + @Column("text") + symbol!: string; + + @Column("text") + interval!: string; // "1m" + + // === OHLCV 价格数据(NUMERIC(20,8) 精度) === + @Column("numeric", { precision: 20, scale: 8 }) + open!: number; + + @Column("numeric", { precision: 20, scale: 8 }) + high!: number; + + @Column("numeric", { precision: 20, scale: 8 }) + low!: number; + + @Column("numeric", { precision: 20, scale: 8 }) + close!: number; + + @Column("numeric", { precision: 20, scale: 8 }) + volume!: number; + + // === 扩展字段 === + @Column("numeric", { precision: 20, scale: 8, nullable: true }) + quote_volume?: number; + + @Column("numeric", { precision: 20, scale: 8, nullable: true }) + taker_buy_base_vol?: number; + + @Column("numeric", { precision: 20, scale: 8, nullable: true }) + taker_buy_quote_vol?: number; + + @Column("integer", { nullable: true }) + trade_count?: number; + + @Column("boolean", { default: true }) + is_closed!: boolean; + + // === 审计字段 === + @CreateDateColumn("timestamptz") + created_at!: Date; + + @UpdateDateColumn("timestamptz") + updated_at!: Date; +} +``` + +> **@timescaledb/typeorm 关键装饰器**: +> +> | 装饰器 | 作用 | SQL 等价 | +> |--------|------|----------| +> | `@Hypertable()` | 将实体映射为 TimescaleDB hypertable | `SELECT create_hypertable(...)` | +> | `@TimeColumn()` | 标记时间分区列 | `-- time 列` | +> | `@PartitionColumn()` | 标记空间分区列 | `partitioning_column =>` | +> | `@Compress()` | 配置自动压缩策略 | `ALTER TABLE SET (timescaledb.compress)` + `add_compression_policy()` | +> | `@ContinuousAggregate()` | 声明连续聚合视图(见下文) | `CREATE MATERIALIZED VIEW ... WITH (timescaledb.continuous)` | + +#### [`trade.entity.ts`](db/entities/klines/trade.entity.ts) — 逐笔成交 Hypertable + +```typescript +// data/db/entities/klines/trade.entity.ts +/** + * 逐笔成交记录 Hypertable + * + * 存储交易所推送的每笔撮合成交,是 K 线合成器的数据源。 + * 数据量极大(BTCUSDT 高峰 >100tps),保留策略设为 3 天。 + */ +@Hypertable({ + chunk_time_interval: "1 hour", // 高频写入,chunk 更细 + partitioning_column: "exchange", + number_partitions: 4, + if_not_exists: true, +}) +@Compress({ + segmentby: "exchange, symbol", + orderby: "trade_time DESC", + compress_after: "1 day", // 1 天后即压缩 +}) +export class Trade { + @TimeColumn() + @PrimaryColumn("timestamptz") + trade_time!: Date; + + @PartitionColumn() + @Column("text") + exchange!: string; + + @Column("text") + symbol!: string; + + @Column("numeric", { precision: 20, scale: 8 }) + price!: number; + + @Column("numeric", { precision: 20, scale: 8 }) + amount!: number; // 成交数量(base 币种) + + @Column("numeric", { precision: 20, scale: 8 }) + quote_amount!: number; // 成交额(quote 币种) + + @Column("boolean") + is_buyer_maker!: boolean; // true = 主动卖出, false = 主动买入 + + @Column("bigint", { nullable: true }) + trade_id?: number; // 交易所成交 ID + + @CreateDateColumn("timestamptz") + created_at!: Date; +} +``` + +#### 连续聚合声明(5m / 15m / 1h / 1d) + +`@timescaledb/typeorm` 支持通过实体类声明连续聚合,替代手动编写 SQL 物化视图: + +```typescript +// data/db/entities/klines/kline-1h.entity.ts(示意) +import { ContinuousAggregate } from "@timescaledb/typeorm"; + +/** + * 1h K 线连续聚合视图 + * + * 从 klines (1m) 表自动聚合,刷新策略:每 5 分钟刷新最近 1 天数据。 + * 应用程序查询时自动路由到该视图,无需扫描 1m 表。 + */ +@ContinuousAggregate({ + source: () => Kline, // 源实体(1m K 线) + bucket_interval: "1 hour", // 聚合时间桶 + refresh: { + start_offset: "3 days", + end_offset: "1 hour", // 留 1 小时给延迟数据 + schedule_interval: "5 minutes", + }, +}) +export class Kline1h { + @TimeColumn() + @PrimaryColumn("timestamptz") + time!: Date; + + @Column("text") exchange!: string; + @Column("text") symbol!: string; + + // 聚合字段(由 timescaledb 自动填充) + open!: number; + high!: number; + low!: number; + close!: number; + volume!: number; + quote_volume!: number; + trade_count!: number; +} +``` + +> **注意**:`@timescaledb/typeorm` 的 `@ContinuousAggregate` 装饰器目前处于实验阶段(v0.0.1)。生产环境中建议同时保留 `db/init-db/` 下的原始 SQL 建表脚本作为 fallback,通过 Docker 的 `/docker-entrypoint-initdb.d/` 自动执行。 + +--- + +### 4.4 关系数据实体 [`db/entities/`](db/entities/) + +这些实体由标准 TypeORM 管理,存储业务配置数据。 + +#### [`exchange.entity.ts`](db/entities/exchange.entity.ts) + +```typescript +// data/db/entities/exchange.entity.ts +@Entity("exchanges") +export class Exchange { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @Column("varchar", { length: 50, unique: true }) + name!: string; // "binance" | "okx" | "bybit" + + @Column("varchar", { length: 100 }) + label!: string; // "Binance" | "OKX" | "Bybit" + + @Column("boolean", { default: true }) + enabled!: boolean; // 是否启用该交易所 + + @Column("jsonb", { nullable: true }) + config?: Record; // 交易所特定配置(费率、最小下单量等) +} +``` + +#### [`trading-pair.entity.ts`](db/entities/trading-pair.entity.ts) + +```typescript +// data/db/entities/trading-pair.entity.ts +@Entity("trading_pairs") +@Index(["exchange_id", "symbol"], { unique: true }) +export class TradingPair { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @ManyToOne(() => Exchange) + @JoinColumn({ name: "exchange_id" }) + exchange!: Exchange; + + @Column("varchar", { length: 20 }) + symbol!: string; // "BTCUSDT" + + @Column("varchar", { length: 10 }) + base_asset!: string; // "BTC" + + @Column("varchar", { length: 10 }) + quote_asset!: string; // "USDT" + + @Column("numeric", { precision: 20, scale: 8, nullable: true }) + min_qty?: number; // 最小下单量 + + @Column("numeric", { precision: 20, scale: 8, nullable: true }) + step_size?: number; // 下单步长 + + @Column("boolean", { default: true }) + active!: boolean; // 是否订阅 +} +``` + +#### [`symbol.entity.ts`](db/entities/symbol.entity.ts) + +```typescript +// data/db/entities/symbol.entity.ts +@Entity("symbols") +export class SymbolEntity { + @PrimaryGeneratedColumn("uuid") + id!: string; + + @Column("varchar", { length: 10, unique: true }) + asset!: string; // "BTC" | "ETH" + + @Column("varchar", { length: 50, nullable: true }) + name?: string; // "Bitcoin" | "Ethereum" + + @Column("boolean", { default: false }) + is_stablecoin!: boolean; // 是否为稳定币 +} +``` + +#### 实体 ER 关系 + +``` +┌─────────────┐ ┌──────────────────┐ ┌─────────────┐ +│ Exchange │──1:N──│ TradingPair │──N:1──│ SymbolEntity│ +│ │ │ │ │ │ +│ id (PK) │ │ id (PK) │ │ id (PK) │ +│ name │ │ exchange_id (FK) │ │ asset │ +│ label │ │ symbol │ │ name │ +│ enabled │ │ base_asset │ │ is_stablecoin│ +│ config │ │ quote_asset │ │ │ +└─────────────┘ │ min_qty │ └─────────────┘ + │ step_size │ + │ active │ + └──────────────────┘ +``` + +--- + +### 4.5 交易所适配器 [`exchanges/`](exchanges/) + +```typescript +// data/exchanges/types.ts +import type { Observable } from "rxjs"; + +/** 统一行情数据源接口 —— 所有交易所适配器必须实现 */ +export interface MarketDataFeed { + /** 交易所标识 */ + readonly exchange: string; + + /** 建立 WebSocket 连接 */ + connect(): Promise; + + /** 断开连接 */ + disconnect(): Promise; + + /** 订阅实时 Ticker 流 */ + subscribeTicker(symbols: string[]): Observable; + + /** 订阅逐笔成交流 */ + subscribeTrade(symbols: string[]): Observable; + + /** 订阅订单簿深度 */ + subscribeOrderbook(symbol: string, depth?: number): Observable; + + /** REST 拉取历史 K 线(用于补齐缺失数据) */ + fetchKlines( + symbol: string, + interval: KlineInterval, + startTime: number, + endTime: number, + limit?: number + ): Promise; + + /** 连接状态 */ + readonly connectionState: "disconnected" | "connecting" | "connected" | "error"; +} +``` + +#### 基类设计 [`base.ts`](exchanges/base.ts) + +```typescript +// data/exchanges/base.ts +export abstract class BaseExchangeAdapter implements MarketDataFeed { + abstract readonly exchange: string; + + protected connectionState: "disconnected" | "connecting" | "connected" | "error" = + "disconnected"; + + // 指数退避重连(所有子类复用) + // 重连参数在各子类构造函数中注入,默认值: baseDelay=3000ms, maxAttempts=10 + protected async reconnect(attempt: number, baseDelayMs = 3000): Promise { + const delay = baseDelayMs * Math.pow(2, Math.min(attempt, 5)); + logger.warn({ exchange: this.exchange, attempt, delay }, "WebSocket reconnecting"); + await sleep(delay); + await this.connect(); + } + + abstract connect(): Promise; + abstract disconnect(): Promise; + abstract subscribeTicker(symbols: string[]): Observable; + abstract subscribeTrade(symbols: string[]): Observable; + abstract subscribeOrderbook(symbol: string, depth?: number): Observable; + abstract fetchKlines( + symbol: string, interval: KlineInterval, + startTime: number, endTime: number, limit?: number + ): Promise; +} +``` + +**适配器实现矩阵**: + +| 交易所 | 适配器类 | WebSocket 库 | REST 库 | 备注 | +|--------|---------|-------------|---------|------| +| Binance | `BinanceAdapter` | `binance` (官方 SDK) | `ccxt` | 官方 WS 支持组合流(多 symbol 单连接),性能最优 | +| OKX | `OKXAdapter` | `ccxt.pro` | `ccxt` | ccxt.pro 内置 WS 管理 | +| Bybit | `BybitAdapter` | `ws` (裸库) | `ccxt` | Bybit V5 WebSocket 协议 | + +--- + +### 4.6 K 线合成管道 [`pipeline/`](pipeline/) + +#### 时间桶算法(核心) + +``` +每收到一条 Trade: + 1. 计算桶索引: bucketIndex = floor(timestamp / intervalMs) + 2. 若 bucketIndex ≠ 当前桶: + a. emit 当前桶 K 线(已关闭) + b. 创建新桶,O=H=L=C=price, V=amount + 3. 若仍在当前桶: + a. H = max(H, price) + b. L = min(L, price) + c. C = price + d. V += amount +``` + +#### 多周期合成策略(混合方案) + +``` +Trade 流 ────► 1m 合成器(时间桶,数据源= Trade) + │ + ▼ 输出 1m K 线 + ┌─────────────┐ + │ 1m → 5m 聚合 │ ← 5 根 1m → 1 根 5m + └──────┬──────┘ + ▼ + ┌──────────────┐ + │ 5m → 15m 聚合 │ ← 3 根 5m → 1 根 15m + └──────┬───────┘ + ▼ + ┌──────────────┐ + │ 15m → 1h 聚合 │ ← 4 根 15m → 1 根 1h + └──────────────┘ +``` + +> **设计理由**:1m 用 Trade 实时合成(精度最高),5m+ 从低周期聚合(计算量最小)。避免为每个周期维护独立的 Trade 缓存造成内存压力。 + +#### 核心代码骨架 + +```typescript +// data/pipeline/kline-synthesizer.ts +import { Subject, interval } from "rxjs"; +import type { Trade, Kline, KlineInterval } from "../types/market"; + +const INTERVAL_MS: Record = { + "1m": 60_000, "5m": 300_000, "15m": 900_000, + "30m": 1_800_000, "1h": 3_600_000, "4h": 14_400_000, + "1d": 86_400_000, "1w": 604_800_000, +}; + +interface Bucket { + openTime: number; + open: number; + high: number; + low: number; + close: number; + volume: number; + tradeCount: number; + isClosed: boolean; +} + +export class KlineSynthesizer { + private buckets = new Map(); + + /** + * 输入一条 Trade,产出可能的闭合 K 线。 + * 返回 null 表示当前桶未关闭,暂不产出。 + */ + synthesize( + exchange: string, + symbol: string, + interval: KlineInterval, + trade: Trade, + ): Kline | null { + const intervalMs = INTERVAL_MS[interval]; + const bucketTime = Math.floor(trade.timestamp / intervalMs) * intervalMs; + const key = `${exchange}:${symbol}:${interval}:${bucketTime}`; + + const existing = this.buckets.get(key); + if (!existing) { + this.buckets.set(key, { + openTime: bucketTime, + open: trade.price, + high: trade.price, + low: trade.price, + close: trade.price, + volume: trade.amount, + tradeCount: 1, + isClosed: false, + }); + return null; // 新桶第一条,不产出 + } + + existing.high = Math.max(existing.high, trade.price); + existing.low = Math.min(existing.low, trade.price); + existing.close = trade.price; + existing.volume += trade.amount; + existing.tradeCount += 1; + return null; + } + + /** 定时关闭过期桶,返回已闭合 K 线列表 */ + closeExpired(now: number): Kline[] { + const closed: Kline[] = []; + for (const [key, bucket] of this.buckets.entries()) { + const intervalMs = INTERVAL_MS["1m"]; // 仅 1m 合成器有此方法 + if (!bucket.isClosed && now >= bucket.openTime + intervalMs + 15000) { + bucket.isClosed = true; + closed.push(/* 构建 Kline 对象 */); + this.buckets.delete(key); + } + } + return closed; + } + + /** 从 1m K 线聚合为高周期 K 线 */ + static aggregate( + klines: Kline[], + targetInterval: KlineInterval, + ): Kline[] { + const intervalMs = INTERVAL_MS[targetInterval]; + const groups = new Map(); + + for (const k of klines) { + const bucketTime = Math.floor(k.openTime / intervalMs) * intervalMs; + const group = groups.get(bucketTime) ?? []; + group.push(k); + groups.set(bucketTime, group); + } + + return Array.from(groups.entries()).map(([bucketTime, group]) => ({ + openTime: bucketTime, + closeTime: bucketTime + intervalMs, + open: group[0]!.open, + high: Math.max(...group.map(k => k.high)), + low: Math.min(...group.map(k => k.low)), + close: group[group.length - 1]!.close, + volume: group.reduce((sum, k) => sum + k.volume, 0), + // ... + })); + } +} +``` + +#### 数据清洗 [`cleaner.ts`](pipeline/cleaner.ts) + +| 清洗规则 | 触发条件 | 处理方式 | +|----------|----------|----------| +| 价格异常 | `|price - median| > 10 * MAD` | 丢弃该条 Trade | +| 空成交量 | `volume === 0 && tradeCount === 0` | 用前一根 K 线的 Close 填充(平盘 K 线) | +| 时间戳乱序 | `trade.timestamp < lastTimestamp` | 100ms 排序窗口内重排,超时则丢弃 | +| 断线缺失 | 连续 5 分钟无数据 | 标记 `gap: true`,策略端可选择跳过 | + +--- + +### 4.7 数据发布 [`publisher/`](publisher/) + +```typescript +// data/publisher/redis.ts +import Redis from "ioredis"; +import { redis as redisConfig } from "../../config"; + +export class RedisPublisher { + private client: Redis; + private readonly prefix: string; + + constructor() { + this.prefix = redisConfig.channelPrefix; // "trade" + this.client = new Redis(redisConfig.url, { + retryStrategy: (times) => { + if (times > redisConfig.maxRetries) return null; // 停止重试 + return redisConfig.retryDelayBaseMs * Math.pow(2, times); + }, + }); + } + + /** 发布实时 Ticker */ + async publishTicker(exchange: string, symbol: string, data: Ticker): Promise { + if (!redisConfig.publishEnabled) return; + const channel = `${this.prefix}:market:ticker:${exchange}:${symbol}`; + await this.client.publish(channel, JSON.stringify(data)); + } + + /** 发布 K 线(增量 — 只推送最新一根的 OHLCV 变化) */ + async publishKlineDelta( + exchange: string, symbol: string, interval: string, delta: KlineDelta, + ): Promise { + if (!redisConfig.publishEnabled) return; + const channel = `${this.prefix}:market:kline:${exchange}:${symbol}:${interval}`; + await this.client.publish(channel, JSON.stringify(delta)); + } +} +``` + +**Redis Pub/Sub 频道命名规范**: + +| 频道模式 | 说明 | 消费方 | +|----------|------|--------| +| `trade:market:ticker:{exchange}:{symbol}` | 实时 Ticker | Python 策略引擎 | +| `trade:market:kline:{exchange}:{symbol}:{interval}` | K 线增量更新 | Python 策略引擎 | +| `trade:market:trade:{exchange}:{symbol}` | 逐笔成交 | Python 策略引擎 | +| `trade:system:heartbeat:data` | 数据模块心跳 | 监控告警 | +| `trade:system:error:data` | 数据模块异常 | 监控告警 | + +--- + +## 5. 数据流生命周期 + +``` + 交易所 WebSocket + │ + ┌────────────┼────────────┐ + ▼ ▼ ▼ + Binance WS OKX WS Bybit WS + │ │ │ + └────────────┼────────────┘ + │ + ┌──────▼──────┐ + │ RxJS 流 │ + │ ticker$ │ + │ trade$ │ + │ orderbook$ │ + └──────┬──────┘ + │ + ┌────────────┼────────────┐ + ▼ ▼ ▼ + K线合成器 数据清洗 格式转换 + (时间桶) (异常过滤) (标准化) + │ │ │ + └────────────┼────────────┘ + │ + ┌────────────┼────────────┐ + ▼ ▼ + ┌──────────────────┐ ┌──────────────────┐ + │ @timescaledb/ │ │ Redis Pub/Sub │ + │ typeorm 批量写入 │ │ 实时行情发布 │ + │ │ │ │ + │ · Kline (1m) │ │ · ticker channel │ + │ · Trade (逐笔) │ │ · kline channel │ + │ │ │ · trade channel │ + └────────┬──────────┘ └────────┬─────────┘ + │ │ + ▼ ▼ + ┌──────────────────┐ ┌──────────────────┐ + │ TimescaleDB │ │ Python 策略引擎 │ + │ 连续聚合刷新 │ │ (消费 + 决策) │ + │ · klines_5m │ │ │ + │ · klines_15m │ │ │ + │ · klines_1h │ │ │ + │ · klines_1d │ │ │ + └──────────────────┘ └──────────────────┘ +``` + +### 启动时序 + +``` +1. 加载配置(config/index.ts → 读取 env.yaml → 零依赖校验) +2. 初始化 Pino 日志 +3. TypeORM DataSource.initialize() + ├── 连接 PostgreSQL + ├── 自动创建 TimescaleDB 扩展(如不存在) + ├── 执行 Migration(生产环境) + └── 注册 @timescaledb/typeorm 订阅者 +4. 初始化 Redis 连接(ioredis) +5. 从数据库加载 TradingPair 列表(active = true) +6. 启动交易所适配器(并行) + ├── connect() → WebSocket 连接 + └── subscribeTicker/Trade(symbols) → RxJS Observable +7. 启动管道 + ├── KlineSynthesizer 订阅 trade$ + ├── DataCleaner 订阅合成输出 + └── TimescaleDBWriter + RedisPublisher 订阅清洗输出 +8. 注册 SIGTERM/SIGINT 优雅关闭 +``` + +--- + +## 6. TypeORM + TimescaleDB 集成细节 + +### 6.1 写入路径选择 + +| 场景 | 方案 | 原因 | +|------|------|------| +| **关系数据 CRUD**(交易对配置、交易所) | TypeORM Repository API | 标准 ORM 操作,事务支持,类型安全 | +| **K 线批量写入**(高频) | TypeORM Repository `upsert()` + 批量 | TypeORM 的 `upsert` 方法支持 `ON CONFLICT DO UPDATE` | +| **K 线海量写入**(极端场景) | 裸 `pg` Pool + `INSERT ... ON CONFLICT` | 绕过 ORM 开销,直接参数化 SQL,批量 500 条/次 | + +```typescript +// 方案 A: TypeORM Repository upsert(推荐日常使用) +const klineRepo = AppDataSource.getRepository(Kline); +await klineRepo.upsert(batchRecords, { + conflictPaths: ["time", "exchange", "symbol", "interval"], + skipUpdateIfNoValuesChanged: true, +}); + +// 方案 B: 裸 pg 批量写入(极端性能场景) +import { Pool } from "pg"; +const pool = new Pool(pgsql); +await pool.query(` + INSERT INTO klines (...) VALUES ${values} + ON CONFLICT (time, exchange, symbol, interval) DO UPDATE SET + high = GREATEST(klines.high, EXCLUDED.high), + low = LEAST(klines.low, EXCLUDED.low), + close = EXCLUDED.close, + volume = klines.volume + EXCLUDED.volume, + updated_at = NOW() +`, params); +``` + +### 6.2 批量写入缓冲 + +```typescript +// data/pipeline/writer.ts +export class KlineWriter { + private buffer: Kline[] = []; + private flushTimer: ReturnType; + + constructor( + private readonly repo: Repository, + private readonly batchSize = 500, + private readonly flushIntervalMs = 1000, + ) { + // 定时刷新:即使未达阈值,也保证最大延迟 ≤ flushIntervalMs + this.flushTimer = setInterval(() => this.flush(), this.flushIntervalMs); + } + + async write(kline: Kline): Promise { + this.buffer.push(kline); + if (this.buffer.length >= this.batchSize) { + await this.flush(); + } + } + + private async flush(): Promise { + if (this.buffer.length === 0) return; + const batch = this.buffer.splice(0, this.batchSize); + await this.repo.upsert(batch, { + conflictPaths: ["time", "exchange", "symbol", "interval"], + }); + logger.debug({ count: batch.length }, "Kline batch flushed"); + } + + async destroy(): Promise { + clearInterval(this.flushTimer); + await this.flush(); // 最后刷新残留数据 + } +} +``` + +### 6.3 TimescaleDB 特定配置 + +通过 `@timescaledb/typeorm` 在 DataSource 初始化时自动设置: + +```typescript +// db/data-source.ts 初始化后执行 +await AppDataSource.initialize(); + +// @timescaledb/typeorm 会自动: +// 1. CREATE EXTENSION IF NOT EXISTS timescaledb; +// 2. 为 @Hypertable() 实体调用 create_hypertable() +// 3. 为 @Compress() 实体调用 add_compression_policy() +// 4. 为 @ContinuousAggregate() 实体创建物化视图 + 刷新策略 +``` + +如果 `@timescaledb/typeorm` 的自动迁移不稳定,推荐 fallback 方案:在 `docker-compose.yml` 中挂载初始化脚本到 `db/init-db/`,容器启动时自动执行原始 SQL。 + +--- + +## 7. 配置管理策略 + +### 7.1 配置文件 + +项目根目录 [`env.yaml`](../../env.yaml) 为统一环境配置源,TypeScript(`data/config/`)和 Python 模块共享同一份 YAML。 + +``` +/ +├── env.yaml ← 统一配置源(YAML,TS/Python 共享) +└── data/ + └── config/ + ├── index.ts ← 读取 env.yaml → 校验 → 导出分组配置 + └── validators.ts ← 零依赖运行时校验(assertString / assertPort / assertEnum) +``` + +### 7.2 env.yaml 配置段 + +```yaml +# --- TimescaleDB / PostgreSQL --- +db: + host: localhost + port: 5432 + name: trade + user: trader + password: fucketh + +# --- Redis --- +redis: + url: redis://localhost:6379 + publish_enabled: true + +# --- 日志 --- +logging: + level: debug # trace / debug / info / warn / error / fatal + node_env: development # development / production / test +``` + +### 7.3 使用方式 + +```typescript +// 任何模块中导入配置(所有导出对象均为强类型 as const) +import { pgsql, redis, logging } from "../config"; + +// TypeORM DataSource +const ds = new DataSource({ type: "postgres", ...pgsql }); + +// Redis 客户端 +const redisClient = new Redis(redis.url); + +// 环境判断 +if (logging.nodeEnv === "development") { + logger.info("Config loaded"); +} +``` + +--- + +## 8. 日志与可观测性 + +### 8.1 Pino 日志配置 + +```typescript +// data/logger.ts +import pino from "pino"; +import { logging } from "../config"; + +export const logger = pino({ + level: logging.level, + // 开发环境:使用 pino-pretty 彩色输出 + // 生产环境:JSON 格式,便于 ELK / Loki 采集 + ...(logging.pretty + ? { transport: { target: "pino-pretty", options: { colorize: true } } } + : {}), + // 自动注入模块名 + base: { module: "trade-data" }, + // 序列化 Error 对象 + serializers: { + err: pino.stdSerializers.err, + }, +}); +``` + +### 8.2 日志规范 + +| 级别 | 使用场景 | 示例 | +|------|----------|------| +| `trace` | 每条 Trade 的详细处理过程 | 开发调试用,生产关闭 | +| `debug` | 批量写入、WS 心跳 | `"Kline batch flushed: count=500"` | +| `info` | 模块启动/停止、连接建立 | `"Binance WS connected: symbols=15"` | +| `warn` | 重连、数据异常、限频触发 | `"WS reconnecting: attempt=3, delay=12000ms"` | +| `error` | 写入失败、WS 不可恢复 | `"TimescaleDB write failed: connection refused"` | +| `fatal` | 进程即将退出 | `"Unrecoverable error, shutting down"` | + +> **安全原则**:严禁在日志中记录 API Key、Secret、数据库密码等敏感信息。使用 `logger.info({ apiKey: "***" })` 脱敏。 + +--- + +## 9. 错误处理与容错 + +### 9.1 错误分类与策略 + +| 错误类型 | 处理策略 | 恢复方式 | +|----------|----------|----------| +| **WebSocket 断线** | 指数退避重连(基数 3s,上限 10 次) | 自动重连 → 重新订阅 → 补齐缺失数据 | +| **数据库写入失败** | 缓冲重试 3 次 → 丢弃并告警 | 下一批数据正常写入 | +| **数据库连接断开** | pg Pool 自动重连 | TypeORM DataSource 重建 | +| **Redis 发布失败** | 静默丢弃(发布是非关键路径) | 下一 tick 继续发布 | +| **配置校验失败** | 进程退出(fail-fast) | 修正 .env 后重启 | +| **交易所 API 限频** | 令牌桶限流,429 响应冷却 60s | 自动恢复 | + +### 9.2 优雅关闭(Graceful Shutdown) + +```typescript +// data/index.ts +async function shutdown(signal: string): Promise { + logger.info({ signal }, "Shutting down"); + + // 1. 停止 WebSocket 连接(停止接收新数据) + await Promise.all(adapters.map(a => a.disconnect())); + + // 2. 刷新 K 线写入缓冲区 + await klineWriter.destroy(); + + // 3. 关闭 TypeORM DataSource + await AppDataSource.destroy(); + + // 4. 关闭 Redis + await redisPublisher.disconnect(); + + logger.info("Shutdown complete"); + process.exit(0); +} + +process.on("SIGTERM", () => shutdown("SIGTERM")); +process.on("SIGINT", () => shutdown("SIGINT")); +``` + +### 9.3 重试工具 [`utils/retry.ts`](utils/retry.ts) + +```typescript +// data/utils/retry.ts +export async function withRetry( + fn: () => Promise, + options: { + maxAttempts?: number; + baseDelayMs?: number; + onRetry?: (attempt: number, error: Error) => void; + } = {}, +): Promise { + const { maxAttempts = 3, baseDelayMs = 1000, onRetry } = options; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await fn(); + } catch (err) { + if (attempt === maxAttempts) throw err; + const delay = baseDelayMs * Math.pow(2, attempt - 1); + onRetry?.(attempt, err as Error); + logger.warn({ attempt, delay, err }, "Retrying operation"); + await sleep(delay); + } + } + throw new Error("Unreachable"); +} +``` + +--- + +## 10. 性能考量 + +### 10.1 关键性能指标 + +| 指标 | 目标 | 说明 | +|------|------|------| +| **单笔 Trade 处理延迟** | < 1 μs | 时间桶 HashMap 查找 + 更新 | +| **WebSocket 消息吞吐** | > 10,000 msg/s | 单进程处理全部订阅对 | +| **批量写入延迟** | < 100 ms | 500 条批量 UPSERT | +| **内存占用(10 symbol × 5 周期)** | < 50 MB | 时间桶 + RxJS 缓存 | +| **进程启动时间** | < 3 s | Bun 启动 + TypeORM 初始化 | + +### 10.2 优化策略 + +| 策略 | 实现 | 收益 | +|------|------|------| +| **批量写入** | 500 条 / 1s 批量刷新 | 减少 99% DB 连接开销 | +| **TimescaleDB 压缩** | 7 天后自动列式压缩 | 存储减少 90%+ | +| **连续聚合** | 高周期从低周期视图读取 | 查询快 100×(1h K 线无需扫描 1m 表) | +| **连接池复用** | TypeORM DataSource 管理 20 连接 | 防止连接数暴涨 | +| **RxJS 操作符优化** | `share()` 多播 + `bufferTime()` 批量 | 避免重复计算 | +| **JSON 序列化** | 生产环境考虑 MessagePack | 体积减少 30-50%,序列化快 2× | + +### 10.3 磁盘空间估算 + +| 数据量(1m K 线) | 未压缩 | 压缩后(92%) | +|-------------------|--------|-------------| +| 10 币种 × 1 年 | ~945 MB | ~75 MB | +| 50 币种 × 1 年 | ~4.7 GB | ~375 MB | +| 200 币种 × 1 年 | ~18.9 GB | ~1.5 GB | + +--- + +## 11. 开发工作流 + +### 11.1 本地开发 + +```bash +# 1. 启动基础服务 +docker compose up -d timescaledb + +# 2. 安装依赖 +cd data && bun install + +# 3. 配置环境 +# 编辑项目根目录 env.yaml(如不存在则创建) + +# 4. 验证配置加载 +bun run db/data-source.ts # 测试 DataSource 初始化 + +# 5. 运行测试 +bun test +``` + +### 11.2 新增交易所适配器 + +```bash +# 1. 创建适配器文件 +touch exchanges/new-exchange.ts + +# 2. 实现 MarketDataFeed 接口 +# 3. 在 exchanges/types.ts 注册 +# 4. 编写测试 tests/exchanges/new-exchange.test.ts +# 5. 如需在数据库中配置,插入 exchanges 表和 trading_pairs 表 +``` + +### 11.3 数据库迁移 + +```bash +# 生成迁移文件(开发环境) +bunx typeorm migration:generate db/migrations/AddNewColumn -d db/data-source.ts + +# 执行迁移(生产环境) +bunx typeorm migration:run -d db/data-source.ts + +# 回滚最近一次迁移 +bunx typeorm migration:revert -d db/data-source.ts +``` + +### 11.4 目录与文件命名规范 + +| 规范 | 说明 | +|------|------| +| 文件名 | `kebab-case.ts` | +| 类名 | `PascalCase` | +| 函数/变量 | `camelCase` | +| 常量 | `UPPER_SNAKE_CASE` | +| 类型/接口 | `PascalCase` | +| 测试文件 | `*.test.ts`(与源文件同目录或 `tests/` 下镜像结构) | + +--- + +## 12. 风险提示 + +> ⚠️ **重要声明**:本数据模块为量化交易系统的技术基础设施,仅提供数据采集、存储与发布功能。任何基于本系统构建的交易策略,其盈亏风险由使用者自行承担。本系统作者不提供任何形式的投资建议,不对策略收益做任何承诺。 + +### 技术风险 + +| 风险 | 影响 | 缓解措施 | +|------|------|----------| +| **交易所 API 变更** | 数据断流 | 适配器模式隔离,单个交易所变更不影响其他 | +| **WebSocket 数据丢失** | K 线不完整 | REST 补拉机制 + gap 标记 | +| **数据库写入瓶颈** | 数据积压 | 批量写入 + 连接池 + 监控告警 | +| **Redis 宕机** | 策略引擎收不到实时数据 | Python 侧增加 TimescaleDB 直读 fallback | +| **时钟偏差** | K 线时间戳错位 | NTP 时钟同步 + UTC 统一对齐 | + +### 实盘接入前置条件 + +1. ✅ 模拟盘环境连续运行 ≥ 2 周无致命错误 +2. ✅ 核心模块单元测试覆盖率 ≥ 80% +3. ✅ 数据库写入延迟 p99 < 200ms +4. ✅ WebSocket 断线自动恢复成功率 ≥ 99% +5. ✅ 日志采集与告警链路就绪 +6. ✅ 资金隔离:不同策略使用独立子账户 +7. ✅ 风控模块在 Python 侧就绪(熔断、仓位限制、最大回撤) + +--- + +## 附录 A:docker-compose.yml 数据库配置参考 + +```yaml +# docker-compose.yml(项目根目录) +services: + timescaledb: + image: timescale/timescaledb-ha:pg17.10-ts2.27.1 + container_name: trade-timescaledb + restart: unless-stopped + ports: + - "5432:5432" + environment: + POSTGRES_DB: trade + POSTGRES_USER: trader + POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme} + volumes: + - ./db/pgsql:/var/lib/postgresql # 数据持久化 + - ./data/init-db:/docker-entrypoint-initdb.d # 自动执行建表 SQL(fallback) + command: > + -c shared_buffers=1GB + -c effective_cache_size=3GB + -c maintenance_work_mem=256MB + -c work_mem=64MB + -c wal_buffers=64MB + -c random_page_cost=1.1 + -c effective_io_concurrency=200 + -c max_connections=50 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U trader -d trade"] + interval: 10s + timeout: 5s + retries: 5 +``` + +## 附录 B:核心依赖版本锁定 + +| 包 | 版本 | 说明 | +|----|------|------| +| `typescript` | ^6.0 | 语言编译器 | +| `bun` | ≥1.3 | 运行时 | +| `typeorm` | ^1.0 | 关系数据 ORM | +| `@timescaledb/typeorm` | ^0.0.1 | TimescaleDB TypeORM 扩展(实验性) | +| `pg` | ^8.21 | PostgreSQL 原生驱动 | +| `yaml` | ^2.9 | YAML 解析(env.yaml) | +| `pino` | ^10.3 | 日志 | +| `ioredis` | ^5.11 | Redis 客户端 | +| `ccxt` | ^4.5 | 统一交易所 REST API | +| `binance` | ^3.5 | Binance 官方 WebSocket SDK | +| `ws` | ^8.21 | 通用 WebSocket 客户端 | diff --git a/data/bun.lock b/data/bun.lock new file mode 100644 index 0000000..c14f850 --- /dev/null +++ b/data/bun.lock @@ -0,0 +1,985 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "trade-data", + "dependencies": { + "@timescaledb/typeorm": "^0.0.1", + "binance": "^3.5.9", + "ccxt": "^4.5.56", + "ioredis": "^5.11.1", + "pg": "^8.21.0", + "pino": "^10.3.1", + "rxjs": "^7.8.2", + "typeorm": "^1.0.0", + "yaml": "^2.9.0", + }, + "devDependencies": { + "@types/bun": "^1.3.14", + "@types/node": "^25.9.2", + "@types/pg": "^8.20.0", + "@types/ws": "^8.18.1", + "eslint": "^10.4.1", + "prettier": "^3.8.3", + "tsx": "^4.22.4", + "typescript": "^6.0.3", + "vitest": "^4.1.8", + }, + }, + }, + "packages": { + "@discoveryjs/json-ext": ["@discoveryjs/json-ext@0.6.3", "", {}, "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ=="], + + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.0", "", { "os": "android", "cpu": "arm" }, "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.0", "", { "os": "android", "cpu": "x64" }, "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.0", "", { "os": "none", "cpu": "x64" }, "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.6.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA=="], + + "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], + + "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="], + + "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], + + "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + + "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@ioredis/commands": ["@ioredis/commands@1.10.0", "", {}, "sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], + + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.3", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@sqltools/formatter": ["@sqltools/formatter@1.2.5", "", {}, "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@timescaledb/core": ["@timescaledb/core@0.0.1", "", { "dependencies": { "@timescaledb/schemas": "0.0.1", "@timescaledb/utils": "0.0.1" } }, "sha512-T+X8u6qjke6F5pZFSA+L2UJnXkOIKE1isg9LkwyRFgVJR91zmZuLONzf9I3c937czExc0enZapBLreMsJYv2Zg=="], + + "@timescaledb/schemas": ["@timescaledb/schemas@0.0.1", "", { "dependencies": { "zod": "^3.24.1" } }, "sha512-DLwJzJkVbgpl43lclvnNYKOslMsGjZMF9KXFCa78tpjLA8ImHV949T5nqDpWvUawnQGDy+PNxoz7eKW2iyefeQ=="], + + "@timescaledb/typeorm": ["@timescaledb/typeorm@0.0.1", "", { "dependencies": { "@timescaledb/core": "0.0.1", "@timescaledb/schemas": "0.0.1", "@timescaledb/utils": "0.0.1", "reflect-metadata": "^0.2.2", "typeorm": "^0.3.20" } }, "sha512-aAw9psQAloh/F2yJbLQe12nV25U63ClWFpI2Lkke7F8IYn2qOEb+OYsD1UAmn4U+dQS8YuOqCq32jAxE+DUasg=="], + + "@timescaledb/utils": ["@timescaledb/utils@0.0.1", "", { "dependencies": { "@timescaledb/schemas": "0.0.1", "debug": "^4.4.0" } }, "sha512-4adkbhTmK7NAS5ukwlTy5cF9hR+SHC3xY6SPOiWPnpgro+/nIsNpGqFlvGRrAdjJHSTsDPUJ9nHEiyo+obbSpA=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], + + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], + + "@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@vitest/expect": ["@vitest/expect@4.1.8", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.8", "@vitest/utils": "4.1.8", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.8", "", { "dependencies": { "@vitest/spy": "4.1.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.8", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA=="], + + "@vitest/runner": ["@vitest/runner@4.1.8", "", { "dependencies": { "@vitest/utils": "4.1.8", "pathe": "^2.0.3" } }, "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "@vitest/utils": "4.1.8", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ=="], + + "@vitest/spy": ["@vitest/spy@4.1.8", "", {}, "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA=="], + + "@vitest/utils": ["@vitest/utils@4.1.8", "", { "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg=="], + + "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], + + "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="], + + "@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.13.2", "", {}, "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="], + + "@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.14.1", "", {}, "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="], + + "@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.13.2", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA=="], + + "@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.13.2", "", {}, "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="], + + "@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw=="], + + "@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.13.2", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw=="], + + "@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.13.2", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw=="], + + "@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.13.2", "", {}, "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="], + + "@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ=="], + + "@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg=="], + + "@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw=="], + + "@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ=="], + + "@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="], + + "@webpack-cli/configtest": ["@webpack-cli/configtest@2.1.1", "", { "peerDependencies": { "webpack": "5.x.x", "webpack-cli": "5.x.x" } }, "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw=="], + + "@webpack-cli/info": ["@webpack-cli/info@2.0.2", "", { "peerDependencies": { "webpack": "5.x.x", "webpack-cli": "5.x.x" } }, "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A=="], + + "@webpack-cli/serve": ["@webpack-cli/serve@2.0.5", "", { "peerDependencies": { "webpack": "5.x.x", "webpack-cli": "5.x.x" } }, "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ=="], + + "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], + + "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], + + "abab": ["abab@2.0.6", "", {}, "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "acorn-import-phases": ["acorn-import-phases@1.0.4", "", { "peerDependencies": { "acorn": "^8.14.0" } }, "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "acorn-walk": ["acorn-walk@8.3.5", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + + "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "ansis": ["ansis@4.3.1", "", {}, "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA=="], + + "app-root-path": ["app-root-path@3.1.0", "", {}, "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + + "axios": ["axios@1.17.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw=="], + + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.34", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw=="], + + "big.js": ["big.js@5.2.2", "", {}, "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="], + + "binance": ["binance@3.5.9", "", { "dependencies": { "axios": "^1.13.2", "isomorphic-ws": "^4.0.1", "nanoid": "^3.3.11", "ws": "^7.4.0" }, "optionalDependencies": { "source-map-loader": "^2.0.2", "ts-loader": "^8.0.11", "webpack": "^5.102.1", "webpack-bundle-analyzer": "^5.1.1", "webpack-cli": "^5.1.4" } }, "sha512-Jg8w81dKIMpGjVgY3k2s0ltAegmN2kOI3GcBzvAGC7CDqUYpWElasfoSrc0fEffryjGQhgJxf0ubyO1wWhz+rA=="], + + "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001797", "", {}, "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w=="], + + "ccxt": ["ccxt@4.5.56", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-4UhGBI0ektkLdBBq8vv9mVhBXoIciqaFaeL6KOhMsQMrEq3h3T8Gt1Z+8Wfx1AbUZI2aAkgqWU42pcLOPhFJrQ=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], + + "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], + + "clone-deep": ["clone-deep@4.0.1", "", { "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", "shallow-clone": "^3.0.0" } }, "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ=="], + + "cluster-key-slot": ["cluster-key-slot@1.1.1", "", {}, "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.368", "", {}, "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw=="], + + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "emojis-list": ["emojis-list@3.0.0", "", {}, "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="], + + "enhanced-resolve": ["enhanced-resolve@4.5.0", "", { "dependencies": { "graceful-fs": "^4.1.2", "memory-fs": "^0.5.0", "tapable": "^1.0.0" } }, "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg=="], + + "envinfo": ["envinfo@7.21.0", "", { "bin": { "envinfo": "dist/cli.js" } }, "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow=="], + + "errno": ["errno@0.1.8", "", { "dependencies": { "prr": "~1.0.1" }, "bin": { "errno": "cli.js" } }, "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], + + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@10.4.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.6.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.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", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw=="], + + "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], + + "fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + + "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "form-data": ["form-data@4.0.5", "", { "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" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + + "html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "interpret": ["interpret@3.1.1", "", {}, "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ=="], + + "ioredis": ["ioredis@5.11.1", "", { "dependencies": { "@ioredis/commands": "1.10.0", "cluster-key-slot": "1.1.1", "debug": "4.4.3", "denque": "2.1.0", "redis-errors": "1.2.0", "redis-parser": "3.0.0", "standard-as-callback": "2.1.0" } }, "sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A=="], + + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-plain-object": ["is-plain-object@2.0.4", "", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="], + + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "isobject": ["isobject@3.0.1", "", {}, "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="], + + "isomorphic-ws": ["isomorphic-ws@4.0.1", "", { "peerDependencies": { "ws": "*" } }, "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "loader-runner": ["loader-runner@4.3.2", "", {}, "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w=="], + + "loader-utils": ["loader-utils@2.0.4", "", { "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", "json5": "^2.1.2" } }, "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "memory-fs": ["memory-fs@0.5.0", "", { "dependencies": { "errno": "^0.1.3", "readable-stream": "^2.0.1" } }, "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + + "node-releases": ["node-releases@2.0.47", "", {}, "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og=="], + + "obug": ["obug@2.1.2", "", {}, "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg=="], + + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + + "opener": ["opener@1.5.2", "", { "bin": { "opener": "bin/opener-bin.js" } }, "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pg": ["pg@8.21.0", "", { "dependencies": { "pg-connection-string": "^2.13.0", "pg-pool": "^3.14.0", "pg-protocol": "^1.14.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA=="], + + "pg-cloudflare": ["pg-cloudflare@1.4.0", "", {}, "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A=="], + + "pg-connection-string": ["pg-connection-string@2.13.0", "", {}, "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.14.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw=="], + + "pg-protocol": ["pg-protocol@1.14.0", "", {}, "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="], + + "pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], + + "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="], + + "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], + + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + + "postcss": ["postcss@8.5.15", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A=="], + + "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "prr": ["prr@1.0.1", "", {}, "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + + "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + + "rechoir": ["rechoir@0.8.0", "", { "dependencies": { "resolve": "^1.20.0" } }, "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ=="], + + "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], + + "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], + + "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + + "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "rolldown": ["rolldown@1.0.3", "", { "dependencies": { "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-x64-msvc": "1.0.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g=="], + + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], + + "semver": ["semver@7.8.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ=="], + + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + + "sha.js": ["sha.js@2.4.12", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" } }, "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w=="], + + "shallow-clone": ["shallow-clone@3.0.1", "", { "dependencies": { "kind-of": "^6.0.2" } }, "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], + + "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@0.6.2", "", {}, "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug=="], + + "source-map-loader": ["source-map-loader@2.0.2", "", { "dependencies": { "abab": "^2.0.5", "iconv-lite": "^0.6.2", "source-map-js": "^0.6.2" }, "peerDependencies": { "webpack": "^5.0.0" } }, "sha512-yIYkFOsKn+OdOirRJUPQpnZiMkF74raDVQjj5ni3SzbOiA57SabeX80R5zyMQAKpvKySA3Z4a85vFX3bvpC6KQ=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sql-highlight": ["sql-highlight@6.1.0", "", {}, "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], + + "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], + + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "terser": ["terser@5.48.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q=="], + + "terser-webpack-plugin": ["terser-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ=="], + + "thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "to-buffer": ["to-buffer@1.2.2", "", { "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", "typed-array-buffer": "^1.0.3" } }, "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "ts-loader": ["ts-loader@8.4.0", "", { "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^4.0.0", "loader-utils": "^2.0.0", "micromatch": "^4.0.0", "semver": "^7.3.4" }, "peerDependencies": { "typescript": "*", "webpack": "*" } }, "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tsx": ["tsx@4.22.4", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], + + "typeorm": ["typeorm@1.0.0", "", { "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^4.2.0", "dayjs": "^1.11.20", "debug": "^4.4.3", "dedent": "^1.7.2", "reflect-metadata": "^0.2.2", "sql-highlight": "^6.1.0", "tinyglobby": "^0.2.16", "tslib": "^2.8.1", "yargs": "^18.0.0" }, "peerDependencies": { "@google-cloud/spanner": "^8.0.0", "@sap/hana-client": "^2.14.22", "better-sqlite3": "^12.0.0", "ioredis": "^5.0.4", "mongodb": "^7.0.0", "mssql": "^12.0.0", "mysql2": "^3.15.3", "oracledb": "^6.3.0", "pg": "^8.5.1", "pg-native": "^3.0.0", "pg-query-stream": "^4.0.0", "redis": "^5.0.0", "sql.js": "^1.4.0", "ts-node": "^10.9.2", "typeorm-aurora-data-api-driver": "^3.0.0" }, "optionalPeers": ["@google-cloud/spanner", "@sap/hana-client", "better-sqlite3", "ioredis", "mongodb", "mssql", "mysql2", "oracledb", "pg", "pg-native", "pg-query-stream", "redis", "sql.js", "ts-node", "typeorm-aurora-data-api-driver"], "bin": { "typeorm": "cli.js", "typeorm-ts-node-esm": "cli-ts-node-esm.js", "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js" } }, "sha512-2mSKNqucP8vo+xQLP59xlHUcqLvG6qajxA7q7tnhJgeZjTrA6lK/Ar7LRyiAxdXhyXmGbIPsArPmcUB9Xg+M7w=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + + "vite": ["vite@8.0.16", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw=="], + + "vitest": ["vitest@4.1.8", "", { "dependencies": { "@vitest/expect": "4.1.8", "@vitest/mocker": "4.1.8", "@vitest/pretty-format": "4.1.8", "@vitest/runner": "4.1.8", "@vitest/snapshot": "4.1.8", "@vitest/spy": "4.1.8", "@vitest/utils": "4.1.8", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.8", "@vitest/browser-preview": "4.1.8", "@vitest/browser-webdriverio": "4.1.8", "@vitest/coverage-istanbul": "4.1.8", "@vitest/coverage-v8": "4.1.8", "@vitest/ui": "4.1.8", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig=="], + + "watchpack": ["watchpack@2.5.1", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg=="], + + "webpack": ["webpack@5.107.2", "", { "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.22.0", "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "loader-runner": "^4.3.2", "mime-db": "^1.54.0", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.5.0", "watchpack": "^2.5.1", "webpack-sources": "^3.5.0" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ=="], + + "webpack-bundle-analyzer": ["webpack-bundle-analyzer@5.3.0", "", { "dependencies": { "@discoveryjs/json-ext": "^0.6.3", "acorn": "^8.0.4", "acorn-walk": "^8.0.0", "commander": "^14.0.2", "escape-string-regexp": "^5.0.0", "html-escaper": "^3.0.3", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^3.0.2", "ws": "^8.19.0" }, "bin": { "webpack-bundle-analyzer": "lib/bin/analyzer.js" } }, "sha512-PEhAoqiJ+47d0uLMx/+zo5XOvaU+Vk6N2ZLht7H3n09QLy/fhyvqGNwjdRUHJDgMN8crBR2ZwVHkIswT3Xuawg=="], + + "webpack-cli": ["webpack-cli@5.1.4", "", { "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", "@webpack-cli/info": "^2.0.2", "@webpack-cli/serve": "^2.0.5", "colorette": "^2.0.14", "commander": "^10.0.1", "cross-spawn": "^7.0.3", "envinfo": "^7.7.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", "webpack-merge": "^5.7.3" }, "peerDependencies": { "webpack": "5.x.x" }, "bin": { "webpack-cli": "bin/cli.js" } }, "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg=="], + + "webpack-merge": ["webpack-merge@5.10.0", "", { "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", "wildcard": "^2.0.0" } }, "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA=="], + + "webpack-sources": ["webpack-sources@3.5.0", "", {}, "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "wildcard": ["wildcard@2.0.1", "", {}, "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "ws": ["ws@7.5.11", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], + + "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@timescaledb/typeorm/typeorm": ["typeorm@0.3.30", "", { "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^4.2.0", "app-root-path": "^3.1.0", "buffer": "^6.0.3", "dayjs": "^1.11.20", "debug": "^4.4.3", "dedent": "^1.7.2", "dotenv": "^16.6.1", "glob": "^10.5.0", "reflect-metadata": "^0.2.2", "sha.js": "^2.4.12", "sql-highlight": "^6.1.0", "tslib": "^2.8.1", "uuid": "^11.1.1", "yargs": "^17.7.2" }, "peerDependencies": { "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@sap/hana-client": "^2.14.22", "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", "ioredis": "^5.0.4", "mongodb": "^5.8.0 || ^6.0.0", "mssql": "^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0", "mysql2": "^2.2.5 || ^3.0.1", "oracledb": "^6.3.0", "pg": "^8.5.1", "pg-native": "^3.0.0", "pg-query-stream": "^4.0.0", "redis": "^3.1.1 || ^4.0.0 || ^5.0.14", "sql.js": "^1.4.0", "sqlite3": "^5.0.3", "ts-node": "^10.7.0", "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" }, "optionalPeers": ["@google-cloud/spanner", "@sap/hana-client", "better-sqlite3", "ioredis", "mongodb", "mssql", "mysql2", "oracledb", "pg", "pg-native", "pg-query-stream", "redis", "sql.js", "sqlite3", "ts-node", "typeorm-aurora-data-api-driver"], "bin": { "typeorm": "cli.js", "typeorm-ts-node-esm": "cli-ts-node-esm.js", "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js" } }, "sha512-8T35PzjefOdqc2ZR9mwLQj0pUGp6lQhMbK2EvVMwJVJWlaoHm0v/Q6dThNOZkFchD+0yMg8gwjKM28ePiLSXSQ=="], + + "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ajv-keywords/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ccxt/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "enhanced-resolve/tapable": ["tapable@1.1.3", "", {}, "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="], + + "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "postcss/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "schema-utils/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "string_decoder/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="], + + "webpack/enhanced-resolve": ["enhanced-resolve@5.23.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA=="], + + "webpack/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], + + "webpack-bundle-analyzer/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "webpack-bundle-analyzer/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "webpack-cli/@discoveryjs/json-ext": ["@discoveryjs/json-ext@0.5.7", "", {}, "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="], + + "webpack-cli/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "@timescaledb/typeorm/typeorm/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="], + + "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "webpack/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@timescaledb/typeorm/typeorm/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "@timescaledb/typeorm/typeorm/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@timescaledb/typeorm/typeorm/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "@timescaledb/typeorm/typeorm/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@timescaledb/typeorm/typeorm/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "@timescaledb/typeorm/typeorm/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "@timescaledb/typeorm/typeorm/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "@timescaledb/typeorm/typeorm/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@timescaledb/typeorm/typeorm/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/data/config.ts b/data/config.ts deleted file mode 100644 index 36023b3..0000000 --- a/data/config.ts +++ /dev/null @@ -1,184 +0,0 @@ -// ============================================================ -// config.ts — 中心化配置模块(带 Zod 运行时校验) -// ============================================================ -// 职责: -// 1. 从 .env 文件加载环境变量(零依赖,手动解析) -// 2. 使用 EnvConfigSchema 校验并类型收窄 -// 3. 导出按职责分组的强类型配置对象(pgsql / redis / batch / ws / logging / symbols) -// -// 使用方式: -// import { pgsql, redis, batch, ws, logging, symbols } from "./config"; -// const pool = new pg.Pool(pgsql); -// const redisClient = new Redis(redis.url); -// ============================================================ - -import { readFileSync } from "node:fs"; -import { resolve, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; -import { EnvConfigSchema, type EnvConfig } from "./db/validators"; - -// ============================================================ -// 1. 加载 .env 文件(零依赖实现) -// ============================================================ - -/** - * 手动解析 .env 文件为 key-value 对。 - * 规则: - * - 忽略空行和 # 开头的注释行 - * - 首个 = 分割 key=value - * - 去除首尾空白(不处理引号,值原样保留) - * - 跳过不含 = 的行 - * - * 不依赖 dotenv 包,保持依赖精简。 - */ -function parseEnvFile(filePath: string): Record { - const result: Record = {}; - - try { - const content = readFileSync(filePath, "utf-8"); - for (const line of content.split("\n")) { - const trimmed = line.trim(); - // 跳过空行和注释 - if (trimmed === "" || trimmed.startsWith("#")) { - continue; - } - const eqIdx = trimmed.indexOf("="); - if (eqIdx === -1) { - continue; - } - const key = trimmed.slice(0, eqIdx).trim(); - const value = trimmed.slice(eqIdx + 1).trim(); - if (key !== "") { - result[key] = value; - } - } - } catch { - // .env 文件不存在时不报错(生产环境变量由容器注入) - } - - return result; -} - -/** - * 将解析结果注入 process.env(已存在的变量不覆盖)。 - * 这使得后续 Zod 的 `z.coerce` 可以从 process.env 读取。 - */ -function loadEnvFile(): void { - // __dirname 在 ESM 中不可用,手动计算 - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const envPath = resolve(__dirname, ".env"); - - const parsed = parseEnvFile(envPath); - for (const [key, value] of Object.entries(parsed)) { - // 不覆盖已由系统设置的环境变量(Docker / systemd 注入优先) - if (process.env[key] === undefined) { - process.env[key] = value; - } - } -} - -// 模块加载时立即执行 -loadEnvFile(); - -// ============================================================ -// 2. Zod 校验 & 类型收窄 -// ============================================================ - -/** - * 经 Zod 校验后的环境变量配置。 - * 所有字段均有默认值兜底,即使 .env 缺失也能正常运行。 - */ -const rawConfig: EnvConfig = EnvConfigSchema.parse(process.env); - -// ============================================================ -// 3. 按职责分组的导出配置对象 -// ============================================================ - -/** PostgreSQL / TimescaleDB 连接配置 */ -export const pgsql = { - host: rawConfig.DB_HOST, - port: rawConfig.DB_PORT, - database: rawConfig.DB_NAME, - user: rawConfig.DB_USER, - password: rawConfig.DB_PASSWORD, - /** pg.Pool 连接上限,避免连接数暴涨 */ - max: 20, - /** 连接空闲超时(毫秒) */ - idleTimeoutMillis: 30000, - /** 连接获取超时(毫秒) */ - connectionTimeoutMillis: 5000, -} as const; - -/** Redis 连接与发布配置 */ -export const redis = { - /** Redis 连接 URL(ioredis 可直接使用) */ - url: rawConfig.REDIS_URL, - /** 是否启用 Pub/Sub 发布行情数据(开发环境可关闭以节省资源) */ - publishEnabled: rawConfig.REDIS_PUBLISH_ENABLED === "true", - /** 频道前缀,避免多环境 key 冲突 */ - channelPrefix: "trade", - /** 重连策略:指数退避基数(毫秒) */ - retryDelayBaseMs: 1000, - /** 最大重试次数 */ - maxRetries: 10, -} as const; - -/** K 线批量写入配置 */ -export const batch = { - /** 缓冲区条数阈值(达到后自动刷新) */ - size: rawConfig.BATCH_SIZE, - /** 最大缓冲时间(毫秒),超时后即使未达阈值也刷新 */ - flushIntervalMs: rawConfig.FLUSH_INTERVAL_MS, -} as const; - -/** WebSocket 连接配置(全局默认值,交易所级别可覆盖) */ -export const ws = { - /** 断线重连延迟基数(毫秒),指数退避:基数 × 2^attempts */ - reconnectDelayMs: rawConfig.WS_RECONNECT_DELAY_MS, - /** 心跳间隔(毫秒) */ - pingIntervalMs: rawConfig.WS_PING_INTERVAL_MS, - /** 最大重连次数(超过后标记 error 状态) */ - maxReconnectAttempts: rawConfig.WS_MAX_RECONNECT_ATTEMPTS, -} as const; - -/** 日志配置 */ -export const logging = { - /** 日志级别:trace / debug / info / warn / error / fatal */ - level: rawConfig.LOG_LEVEL, - /** 运行环境(production 时 pino 输出 JSON 便于日志采集) */ - nodeEnv: rawConfig.NODE_ENV, - /** 是否启用 pino-pretty(开发环境友好输出) */ - pretty: rawConfig.NODE_ENV === "development", -} as const; - -/** 默认订阅的交易对列表(逗号分隔 → string[]) */ -export const symbols: string[] = rawConfig.SYMBOLS.split(",") - .map((s) => s.trim()) - .filter(Boolean); - -// ============================================================ -// 4. 工具:运行时打印配置概要(不含敏感信息) -// ============================================================ - -/** 打印脱敏后的配置概要,便于启动排查 */ -export function printConfigSummary(): void { - const summary = { - pgsql: { - host: pgsql.host, - port: pgsql.port, - database: pgsql.database, - user: pgsql.user, - password: "***", - }, - redis: { - url: redis.url.replace(/\/\/.*@/, "//***@"), // 隐藏密码 - publishEnabled: redis.publishEnabled, - }, - batch, - ws, - logging, - symbols, - }; - console.log("[config] 配置概要:", JSON.stringify(summary, null, 2)); -} diff --git a/data/config/index.ts b/data/config/index.ts new file mode 100644 index 0000000..99f2692 --- /dev/null +++ b/data/config/index.ts @@ -0,0 +1,160 @@ +// ============================================================ +// config.ts — 中心化配置模块(YAML 驱动) +// ============================================================ +// 职责: +// 1. 从项目根目录 env.yaml 加载配置 +// 2. 使用 validateConfig() 校验并类型收窄 +// 3. 导出按职责分组的强类型配置对象(pgsql / redis / logging) +// +// 使用方式: +// import { pgsql, redis, logging } from "./config"; +// const ds = new DataSource({ ...pgsql }); +// const redisClient = new Redis(redis.url); +// +// 配置文件位置:/env.yaml +// TypeScript / Python 模块共享同一份配置。 +// ============================================================ + +import { readFileSync } from "node:fs"; +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { parse as parseYaml } from "yaml"; +import { validateConfig, type EnvConfig } from "./validators"; + +// ============================================================ +// 1. 定位并读取 env.yaml +// ============================================================ + +/** + * 计算项目根目录的绝对路径。 + * data/config.ts → data/ → / + * + * 兼容 ESM(无 __dirname)和 Bun 运行时。 + */ +function getProjectRoot(): string { + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + // config/index.ts → config/ → data/ → + return resolve(__dirname, "../.."); +} + +/** + * 从项目根目录读取 env.yaml 并解析为原始对象。 + * 文件不存在时抛出明确错误,不做静默降级。 + */ +function loadYamlConfig(): unknown { + const root = getProjectRoot(); + const yamlPath = resolve(root, "env.yaml"); + + let content: string; + try { + content = readFileSync(yamlPath, "utf-8"); + } catch { + throw new Error( + `[config] 无法读取配置文件: ${yamlPath}\n` + + `请确保项目根目录存在 env.yaml(可参考 data/.env.example 的结构)。`, + ); + } + + const parsed = parseYaml(content); + if (parsed === null || parsed === undefined) { + throw new Error(`[config] env.yaml 解析结果为空: ${yamlPath}`); + } + + return parsed; +} + +// ============================================================ +// 2. 加载 & 校验 +// ============================================================ + +/** 经校验后的 env.yaml 配置(强类型) */ +const rawConfig: EnvConfig = (() => { + const raw = loadYamlConfig(); + return validateConfig(raw); +})(); + +// ============================================================ +// 3. 按职责分组的导出配置对象 +// ============================================================ + +/** + * PostgreSQL / TimescaleDB 连接配置 + * + * 连接池参数(max / idleTimeoutMillis / connectionTimeoutMillis) + * 为硬编码常量,不在 env.yaml 中暴露,避免误调导致连接耗尽。 + */ +export const pgsql = { + host: rawConfig.db.host, + port: rawConfig.db.port, + database: rawConfig.db.name, + user: rawConfig.db.user, + password: rawConfig.db.password, + /** pg.Pool 连接上限,避免连接数暴涨 */ + max: 20, + /** 连接空闲超时(毫秒) */ + idleTimeoutMillis: 30000, + /** 连接获取超时(毫秒) */ + connectionTimeoutMillis: 5000, +} as const; + +/** + * Redis 连接与发布配置 + * + * channelPrefix / retryDelayBaseMs / maxRetries 为硬编码常量, + * 跨模块保持一致,不需要通过配置文件修改。 + */ +export const redis = { + /** Redis 连接 URL(ioredis 可直接使用) */ + url: rawConfig.redis.url, + /** 是否启用 Pub/Sub 发布行情数据(开发环境可关闭以节省资源) */ + publishEnabled: rawConfig.redis.publish_enabled, + /** 频道前缀,避免多环境 key 冲突 */ + channelPrefix: "trade", + /** 重连策略:指数退避基数(毫秒) */ + retryDelayBaseMs: 1000, + /** 最大重试次数 */ + maxRetries: 10, +} as const; + +/** + * 日志配置 + * + * pretty 由 NODE_ENV 自动推导,不在 env.yaml 中独立配置。 + */ +export const logging = { + /** 日志级别:trace / debug / info / warn / error / fatal */ + level: rawConfig.logging.level, + /** 运行环境(production 时 pino 输出 JSON 便于日志采集) */ + nodeEnv: rawConfig.logging.node_env, + /** 是否启用 pino-pretty(开发环境友好输出) */ + pretty: rawConfig.logging.node_env === "development", +} as const; + +// ============================================================ +// 4. 工具:运行时打印配置概要(不含敏感信息) +// ============================================================ + +/** 打印脱敏后的配置概要,便于启动排查 */ +export function printConfigSummary(): void { + const summary = { + projectRoot: getProjectRoot(), + pgsql: { + host: pgsql.host, + port: pgsql.port, + database: pgsql.database, + user: pgsql.user, + password: "***", + }, + redis: { + url: redis.url.replace(/\/\/.*@/, "//***@"), // 隐藏密码 + publishEnabled: redis.publishEnabled, + }, + logging: { + level: logging.level, + nodeEnv: logging.nodeEnv, + pretty: logging.pretty, + }, + }; + console.log("[config] 配置概要:", JSON.stringify(summary, null, 2)); +} diff --git a/data/config/validators.ts b/data/config/validators.ts new file mode 100644 index 0000000..6a92ff2 --- /dev/null +++ b/data/config/validators.ts @@ -0,0 +1,149 @@ +// ============================================================ +// validators.ts — env.yaml 配置类型定义与运行时校验 +// ============================================================ +// 职责: +// 1. 定义 env.yaml 的 TypeScript 接口 +// 2. 提供运行时校验函数(零依赖,手动实现) +// 3. 为 config.ts 提供类型安全的配置读取 +// +// 使用方式: +// import { validateConfig, type EnvConfig } from "./db/validators"; +// const raw = validateConfig(parsedYaml); +// ============================================================ + +/** env.yaml 顶层结构 */ +export interface EnvConfig { + db: DbConfig; + redis: RedisConfig; + logging: LoggingConfig; +} + +export interface DbConfig { + host: string; + port: number; + name: string; + user: string; + password: string; +} + +export interface RedisConfig { + url: string; + publish_enabled: boolean; +} + +export interface LoggingConfig { + level: "trace" | "debug" | "info" | "warn" | "error" | "fatal"; + node_env: "development" | "production" | "test"; +} + +// ============================================================ +// 运行时校验(零依赖) +// ============================================================ + +const VALID_LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"] as const; +const VALID_NODE_ENVS = ["development", "production", "test"] as const; + +/** + * 校验并返回类型安全的配置对象。 + * 校验失败时抛出明确错误信息,遵循 fail-fast 原则。 + */ +export function validateConfig(raw: unknown): EnvConfig { + if (typeof raw !== "object" || raw === null) { + throw new Error(`[config] env.yaml 顶层必须为 object,实际: ${typeof raw}`); + } + + const obj = raw as Record; + + // --- db --- + const db = obj["db"]; + if (typeof db !== "object" || db === null) { + throw new Error("[config] env.yaml 缺少 db 配置段"); + } + const dbObj = db as Record; + + const dbHost = assertString(dbObj["host"], "db.host"); + const dbPort = assertPort(dbObj["port"], "db.port"); + const dbName = assertString(dbObj["name"], "db.name"); + const dbUser = assertString(dbObj["user"], "db.user"); + const dbPassword = assertString(dbObj["password"], "db.password"); + + // --- redis --- + const redis = obj["redis"]; + if (typeof redis !== "object" || redis === null) { + throw new Error("[config] env.yaml 缺少 redis 配置段"); + } + const redisObj = redis as Record; + + const redisUrl = assertString(redisObj["url"], "redis.url"); + const redisPublishEnabled = assertBoolean(redisObj["publish_enabled"], "redis.publish_enabled"); + + // --- logging --- + const logging = obj["logging"]; + if (typeof logging !== "object" || logging === null) { + throw new Error("[config] env.yaml 缺少 logging 配置段"); + } + const logObj = logging as Record; + + const logLevel = assertEnum(logObj["level"], VALID_LOG_LEVELS, "logging.level"); + const nodeEnv = assertEnum(logObj["node_env"], VALID_NODE_ENVS, "logging.node_env"); + + return { + db: { + host: dbHost, + port: dbPort, + name: dbName, + user: dbUser, + password: dbPassword, + }, + redis: { + url: redisUrl, + publish_enabled: redisPublishEnabled, + }, + logging: { + level: logLevel, + node_env: nodeEnv, + }, + }; +} + +// ============================================================ +// 辅助校验函数 +// ============================================================ + +function assertString(value: unknown, path: string): string { + if (typeof value !== "string" || value.trim() === "") { + throw new Error(`[config] ${path} 必须为非空字符串,实际: ${JSON.stringify(value)}`); + } + return value; +} + +function assertPort(value: unknown, path: string): number { + if (typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535) { + return value; + } + if (typeof value === "string" && /^\d+$/.test(value)) { + const n = parseInt(value, 10); + if (n > 0 && n <= 65535) return n; + } + throw new Error(`[config] ${path} 必须为有效端口号 (1-65535),实际: ${JSON.stringify(value)}`); +} + +function assertBoolean(value: unknown, path: string): boolean { + if (typeof value === "boolean") return value; + if (value === "true" || value === "false") return value === "true"; + throw new Error(`[config] ${path} 必须为 boolean,实际: ${JSON.stringify(value)}`); +} + +function assertEnum( + value: unknown, + allowed: T, + path: string, +): T[number] { + const s = String(value); + if ((allowed as readonly string[]).includes(s)) { + return s as T[number]; + } + throw new Error( + `[config] ${path} 必须为 ${allowed.join(" | ")} 之一,实际: ${JSON.stringify(value)}`, + ); +} diff --git a/data/db/config-crud.ts b/data/db/config-crud.ts deleted file mode 100644 index 26b1475..0000000 --- a/data/db/config-crud.ts +++ /dev/null @@ -1,530 +0,0 @@ -// ============================================================ -// db/config-crud.ts — 配置表 CRUD 服务层 -// ============================================================ -// 职责: -// 1. 封装 monitored_symbols / exchange_config / app_config 三张配置表的增删改查 -// 2. 所有方法通过 pg.Pool 执行参数化 SQL(防注入) -// 3. 返回类型与 types.ts 严格对应,调用方无需手动断言 -// 4. 支持依赖注入:构造函数接收 Pool,便于单元测试 mock -// -// 使用方式: -// import { pool } from "./db"; -// import { MonitoredSymbolsRepo, ExchangeConfigRepo, AppConfigRepo } from "./db/config-crud"; -// -// const symbolsRepo = new MonitoredSymbolsRepo(pool); -// const all = await symbolsRepo.listAll(); -// ============================================================ - -import type pg from "pg"; -import type { - MonitoredSymbolRow, - MonitoredSymbolInsert, - MonitoredSymbolUpdate, - ExchangeConfigRow, - ExchangeConfigInsert, - AppConfigRow, - Exchange, - KlineInterval, -} from "./types"; -import { - // monitored_symbols - queryAllMonitoredSymbols, - queryEnabledSymbols, - querySymbolsByExchange, - queryMonitoredSymbolById, - queryMonitoredSymbolByKey, - upsertMonitoredSymbol, - updateMonitoredSymbol, - disableMonitoredSymbol, - deleteMonitoredSymbol, - deleteMonitoredSymbolByKey, - // exchange_config - queryAllExchangeConfigs, - queryEnabledExchanges, - queryExchangeConfig, - queryExchangeConfigById, - upsertExchangeConfig, - updateExchangeConfig, - deleteExchangeConfig, - deleteExchangeConfigByExchange, - // app_config - queryAllAppConfig, - queryAppConfig, - queryAppConfigById, - upsertAppConfig, - updateAppConfig, - deleteAppConfig, - deleteAppConfigById, -} from "./queries"; - -// ============================================================ -// 工具类型:将 Promise 解包的一行或 null -// ============================================================ - -/** pg query 返回的第一行,不存在则为 null */ -type FirstRow = T | null; - -// ============================================================ -// MonitoredSymbolsRepo — 监控交易对配置 CRUD -// ============================================================ - -export class MonitoredSymbolsRepo { - constructor(private readonly pool: pg.Pool) {} - - // ---------------------------------------------------------- - // CREATE / UPSERT - // ---------------------------------------------------------- - - /** - * 新增或更新监控标的。 - * 唯一键冲突时更新 enabled/priority/label/notes 并刷新 updated_at。 - * - * @returns 插入或更新后的完整行 - */ - async upsert( - insert: MonitoredSymbolInsert, - ): Promise { - const { rows } = await this.pool.query( - upsertMonitoredSymbol, - [ - insert.exchange, - insert.symbol, - insert.interval, - insert.enabled ?? true, - insert.priority ?? 0, - insert.label ?? null, - insert.notes ?? null, - ], - ); - return rows[0]!; - } - - // ---------------------------------------------------------- - // READ — 单条 - // ---------------------------------------------------------- - - /** 按主键 ID 查询 */ - async findById(id: number): Promise> { - const { rows } = await this.pool.query( - queryMonitoredSymbolById, - [id], - ); - return rows[0] ?? null; - } - - /** - * 按唯一业务键 (exchange, symbol, interval) 查询。 - * 这是最常用的精确查找方式。 - */ - async findByKey( - exchange: Exchange, - symbol: string, - interval: KlineInterval, - ): Promise> { - const { rows } = await this.pool.query( - queryMonitoredSymbolByKey, - [exchange, symbol, interval], - ); - return rows[0] ?? null; - } - - // ---------------------------------------------------------- - // READ — 列表 - // ---------------------------------------------------------- - - /** 查询所有监控标的(含已禁用),按优先级降序 */ - async listAll(): Promise { - const { rows } = await this.pool.query( - queryAllMonitoredSymbols, - ); - return rows; - } - - /** 查询所有启用的监控标的(采集服务启动时调用) */ - async listEnabled(): Promise { - const { rows } = await this.pool.query( - queryEnabledSymbols, - ); - return rows; - } - - /** 查询指定交易所下所有启用的监控标的 */ - async listByExchange(exchange: Exchange): Promise { - const { rows } = await this.pool.query( - querySymbolsByExchange, - [exchange], - ); - return rows; - } - - // ---------------------------------------------------------- - // UPDATE - // ---------------------------------------------------------- - - /** - * 按 ID 部分更新监控标的。 - * 仅更新传入的非 undefined 字段(COALESCE 语义)。 - * - * @returns 更新后的完整行;ID 不存在则返回 null - */ - async update( - id: number, - patch: MonitoredSymbolUpdate, - ): Promise> { - const { rows } = await this.pool.query( - updateMonitoredSymbol, - [ - id, - patch.enabled ?? null, - patch.priority ?? null, - patch.label ?? null, - patch.notes ?? null, - ], - ); - return rows[0] ?? null; - } - - /** - * 禁用指定监控标的(软删除)。 - * 不会删除记录,仅将 enabled 设为 FALSE。 - */ - async disable( - exchange: Exchange, - symbol: string, - interval: KlineInterval, - ): Promise | null> { - const { rows } = await this.pool.query< - Pick - >(disableMonitoredSymbol, [exchange, symbol, interval]); - return rows[0] ?? null; - } - - // ---------------------------------------------------------- - // DELETE(硬删除) - // ---------------------------------------------------------- - - /** 按 ID 硬删除。返回被删除的 id,不存在则返回 null */ - async deleteById(id: number): Promise { - const { rows } = await this.pool.query<{ id: number }>( - deleteMonitoredSymbol, - [id], - ); - return rows[0]?.id ?? null; - } - - /** 按唯一键硬删除。返回被删除的 id,不存在则返回 null */ - async deleteByKey( - exchange: Exchange, - symbol: string, - interval: KlineInterval, - ): Promise { - const { rows } = await this.pool.query<{ id: number }>( - deleteMonitoredSymbolByKey, - [exchange, symbol, interval], - ); - return rows[0]?.id ?? null; - } -} - -// ============================================================ -// ExchangeConfigRepo — 交易所连接配置 CRUD -// ============================================================ - -export class ExchangeConfigRepo { - constructor(private readonly pool: pg.Pool) {} - - // ---------------------------------------------------------- - // CREATE / UPSERT - // ---------------------------------------------------------- - - /** - * 新增或更新交易所配置。 - * 唯一键冲突时更新所有连接参数并刷新 updated_at。 - * - * @returns 插入或更新后的完整行 - */ - async upsert( - insert: ExchangeConfigInsert, - ): Promise { - const { rows } = await this.pool.query( - upsertExchangeConfig, - [ - insert.exchange, - insert.rest_url ?? null, - insert.ws_url ?? null, - insert.ws_ping_interval_ms ?? 30000, - insert.rate_limit_per_sec ?? 20, - insert.max_reconnect_attempts ?? 10, - insert.reconnect_delay_ms ?? 3000, - insert.enabled ?? true, - insert.notes ?? null, - ], - ); - return rows[0]!; - } - - // ---------------------------------------------------------- - // READ — 单条 - // ---------------------------------------------------------- - - /** 按主键 ID 查询 */ - async findById(id: number): Promise> { - const { rows } = await this.pool.query( - queryExchangeConfigById, - [id], - ); - return rows[0] ?? null; - } - - /** 按交易所标识查询(如 "binance") */ - async findByExchange( - exchange: Exchange, - ): Promise> { - const { rows } = await this.pool.query( - queryExchangeConfig, - [exchange], - ); - return rows[0] ?? null; - } - - // ---------------------------------------------------------- - // READ — 列表 - // ---------------------------------------------------------- - - /** 查询所有交易所配置(含已禁用) */ - async listAll(): Promise { - const { rows } = await this.pool.query( - queryAllExchangeConfigs, - ); - return rows; - } - - /** 查询所有启用的交易所配置 */ - async listEnabled(): Promise { - const { rows } = await this.pool.query( - queryEnabledExchanges, - ); - return rows; - } - - // ---------------------------------------------------------- - // UPDATE - // ---------------------------------------------------------- - - /** - * 按 ID 部分更新交易所配置。 - * 仅更新传入的非 undefined 字段(COALESCE 语义)。 - * - * ⚠️ 风险提示:修改限频参数(rate_limit_per_sec)可能触发交易所封禁 IP。 - * 务必确认目标交易所的官方限频规则后再调整。 - * - * @returns 更新后的完整行;ID 不存在则返回 null - */ - async update( - id: number, - patch: Partial>, - ): Promise> { - const { rows } = await this.pool.query( - updateExchangeConfig, - [ - id, - patch.rest_url ?? null, - patch.ws_url ?? null, - patch.ws_ping_interval_ms ?? null, - patch.rate_limit_per_sec ?? null, - patch.max_reconnect_attempts ?? null, - patch.reconnect_delay_ms ?? null, - patch.enabled ?? null, - patch.notes ?? null, - ], - ); - return rows[0] ?? null; - } - - // ---------------------------------------------------------- - // DELETE(硬删除) - // ---------------------------------------------------------- - - /** 按 ID 硬删除。返回被删除的 id,不存在则返回 null */ - async deleteById(id: number): Promise { - const { rows } = await this.pool.query<{ id: number }>( - deleteExchangeConfig, - [id], - ); - return rows[0]?.id ?? null; - } - - /** 按交易所标识硬删除。返回被删除的 id,不存在则返回 null */ - async deleteByExchange(exchange: Exchange): Promise { - const { rows } = await this.pool.query<{ id: number }>( - deleteExchangeConfigByExchange, - [exchange], - ); - return rows[0]?.id ?? null; - } -} - -// ============================================================ -// AppConfigRepo — 全局应用配置(KV)CRUD -// ============================================================ - -export class AppConfigRepo { - constructor(private readonly pool: pg.Pool) {} - - // ---------------------------------------------------------- - // CREATE / UPSERT - // ---------------------------------------------------------- - - /** - * 设置一个配置项(新增或更新)。 - * - * @param key — 配置键 - * @param value — 配置值(字符串,消费方自行解析类型) - * @param description — 可选说明 - * @returns 插入或更新后的完整行 - */ - async set( - key: string, - value: string, - description?: string | null, - ): Promise { - const { rows } = await this.pool.query(upsertAppConfig, [ - key, - value, - description ?? null, - ]); - return rows[0]!; - } - - // ---------------------------------------------------------- - // READ — 单条 - // ---------------------------------------------------------- - - /** 按主键 ID 查询 */ - async findById(id: number): Promise> { - const { rows } = await this.pool.query( - queryAppConfigById, - [id], - ); - return rows[0] ?? null; - } - - /** - * 按 key 查询配置项。 - * - * @returns 配置行;不存在则返回 null - */ - async get(key: string): Promise> { - const { rows } = await this.pool.query(queryAppConfig, [key]); - return rows[0] ?? null; - } - - /** - * 按 key 获取配置值(字符串)。 - * 便捷方法——等价于 (await get(key))?.value ?? defaultValue。 - * - * @param key — 配置键 - * @param defaultValue — 默认值(key 不存在时返回) - */ - async getValue(key: string, defaultValue = ""): Promise { - const row = await this.get(key); - return row?.value ?? defaultValue; - } - - /** - * 按 key 获取配置值并解析为整数。 - * 解析失败时返回 defaultValue。 - */ - async getIntValue(key: string, defaultValue = 0): Promise { - const raw = await this.getValue(key, String(defaultValue)); - const parsed = parseInt(raw, 10); - return Number.isNaN(parsed) ? defaultValue : parsed; - } - - /** - * 按 key 获取配置值并解析为布尔。 - * 规则:'true' / '1' → true,其余 → false。 - */ - async getBoolValue(key: string, defaultValue = false): Promise { - const raw = await this.getValue(key, String(defaultValue)); - return raw === "true" || raw === "1"; - } - - // ---------------------------------------------------------- - // READ — 列表 - // ---------------------------------------------------------- - - /** 查询所有应用配置 */ - async listAll(): Promise { - const { rows } = await this.pool.query(queryAllAppConfig); - return rows; - } - - /** - * 批量获取多个 key 的值。 - * 一次性查询全表后过滤,避免 N+1 问题。 - * - * @param keys — 需要获取的 key 列表 - * @returns Map - */ - async getBatch(keys: string[]): Promise> { - const all = await this.listAll(); - const map = new Map(); - const keySet = new Set(keys); - for (const row of all) { - if (keySet.has(row.key)) { - map.set(row.key, row.value); - } - } - // 保证未找到的 key 也有默认值 "" - for (const k of keys) { - if (!map.has(k)) { - map.set(k, ""); - } - } - return map; - } - - // ---------------------------------------------------------- - // UPDATE - // ---------------------------------------------------------- - - /** - * 按 ID 部分更新应用配置。 - * - * @returns 更新后的完整行;ID 不存在则返回 null - */ - async update( - id: number, - value?: string, - description?: string | null, - ): Promise> { - const { rows } = await this.pool.query(updateAppConfig, [ - id, - value ?? null, - description ?? null, - ]); - return rows[0] ?? null; - } - - // ---------------------------------------------------------- - // DELETE - // ---------------------------------------------------------- - - /** 按 key 删除配置。返回被删除的 id,不存在则返回 null */ - async deleteByKey(key: string): Promise { - const { rows } = await this.pool.query<{ id: number }>(deleteAppConfig, [ - key, - ]); - return rows[0]?.id ?? null; - } - - /** 按 ID 删除配置。返回被删除的 id,不存在则返回 null */ - async deleteById(id: number): Promise { - const { rows } = await this.pool.query<{ id: number }>( - deleteAppConfigById, - [id], - ); - return rows[0]?.id ?? null; - } -} diff --git a/data/db/data-source.ts b/data/db/data-source.ts new file mode 100644 index 0000000..73adb63 --- /dev/null +++ b/data/db/data-source.ts @@ -0,0 +1,29 @@ +import { DataSource } from "typeorm"; +import { pgsql } from "../config"; +import * as entities from "./entities"; + +export const AppDataSource = new DataSource({ + type: "postgres", + host: pgsql.host, + port: pgsql.port, + database: pgsql.database, + username: pgsql.user, + password: pgsql.password, + // 实体注册:关系实体通过 entities/index.ts 统一导出 + // TimescaleDB K 线实体后续通过 @timescaledb/typeorm 装饰器注册 + entities: [ + ...Object.values(entities), + ], + // 生产环境禁用 synchronize,使用 Migration + synchronize: true, + migrations: [__dirname + "/migrations/*.{ts,js}"], + // 连接池 + extra: { + max: pgsql.max, // 最大连接数 20 + idleTimeoutMillis: pgsql.idleTimeoutMillis, // 空闲超时 30s + connectionTimeoutMillis: pgsql.connectionTimeoutMillis, // 连接超时 5s + }, + logging: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"], +}); + +await AppDataSource.initialize(); \ No newline at end of file diff --git a/data/db/entities/common.entity.ts b/data/db/entities/common.entity.ts new file mode 100644 index 0000000..4ac8e1f --- /dev/null +++ b/data/db/entities/common.entity.ts @@ -0,0 +1,32 @@ +// ============================================================ +// common.entity.ts — 实体公共基类 +// ============================================================ +// 所有关系实体(TypeORM 管理域)继承此类,统一: +// - id: UUID 主键 +// - created_at: 记录创建时间(自动填充) +// - updated_at: 最后更新时间(自动填充) +// +// TimescaleDB K 线实体(@timescaledb/typeorm 管理域)不继承此类, +// 因为它们需要 @TimeColumn() 等特定装饰器。 +// ============================================================ + +import { + BaseEntity, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, +} from "typeorm"; + +export abstract class CommonBaseEntity extends BaseEntity { + /** UUID 主键(非自增整数,便于分布式场景) */ + @PrimaryGeneratedColumn("uuid") + id!: string; + + /** 记录创建时间 */ + @CreateDateColumn({ type: "timestamptz", name: "created_at" }) + createdAt!: Date; + + /** 最后更新时间(每次 UPDATE 自动刷新) */ + @UpdateDateColumn({ type: "timestamptz", name: "updated_at" }) + updatedAt!: Date; +} diff --git a/data/db/entities/exchange.entity.ts b/data/db/entities/exchange.entity.ts new file mode 100644 index 0000000..0ba60d1 --- /dev/null +++ b/data/db/entities/exchange.entity.ts @@ -0,0 +1,41 @@ +// ============================================================ +// exchange.entity.ts — 交易所配置实体 +// ============================================================ +// 映射到 PostgreSQL exchanges 表,存储已接入的交易所元信息。 +// 由 TypeORM 管理(关系数据),不与 TimescaleDB 耦合。 +// +// 继承 CommonBaseEntity:id (UUID) / created_at / updated_at +// ============================================================ + +import { + Entity, + Column, + OneToMany, +} from "typeorm"; +import { CommonBaseEntity } from "./common.entity"; + +@Entity("exchanges") +export class Exchange extends CommonBaseEntity { + /** 交易所唯一标识(如 binance / okx / bybit) */ + @Column("varchar", { length: 50, unique: true }) + name!: string; + + /** 交易所显示名称(如 Binance / OKX / Bybit) */ + @Column("varchar", { length: 100 }) + label!: string; + + /** 是否启用该交易所的数据采集 */ + @Column("boolean", { default: true }) + enabled!: boolean; + + /** 交易所特定配置(JSON:费率、最小下单量、API 限频等) */ + @Column("jsonb", { nullable: true }) + config?: Record; + + /** + * 该交易所下的所有交易对。 + * 使用字符串引用避免循环依赖(TradingPair 也引用 Exchange)。 + */ + @OneToMany("TradingPair", "exchange") + tradingPairs!: unknown[]; +} diff --git a/data/db/entities/index.ts b/data/db/entities/index.ts new file mode 100644 index 0000000..6ac700e --- /dev/null +++ b/data/db/entities/index.ts @@ -0,0 +1,12 @@ +// ============================================================ +// entities/index.ts — 实体统一导出 +// ============================================================ +// TypeORM DataSource 通过 import * as entities from "../entities" +// 自动注册所有实体,无需手动逐个添加。 +// ============================================================ + +export { CommonBaseEntity } from "./common.entity"; +export { Exchange } from "./exchange.entity"; +export { TradingPair } from "./trading-pair.entity"; +export { Kline } from "./kline.entity"; +export type { KlineInterval } from "./kline.entity"; diff --git a/data/db/entities/kline.entity.ts b/data/db/entities/kline.entity.ts new file mode 100644 index 0000000..a3912c5 --- /dev/null +++ b/data/db/entities/kline.entity.ts @@ -0,0 +1,138 @@ +// ============================================================ +// kline.entity.ts — TimescaleDB K 线 Hypertable 实体 +// ============================================================ +// 映射到 PostgreSQL klines 表(TimescaleDB hypertable)。 +// 不继承 CommonBaseEntity — 使用 @timescaledb/typeorm 的 +// @Hypertable / @TimeColumn 装饰器管理 TimescaleDB 特性。 +// +// 关键 TimescaleDB 特性(由 @Hypertable 装饰器自动配置): +// - 自动按 time 列做时间分区(by_range) +// - 列式压缩(compress),7 天后自动执行 +// - 通过 ContinuousAggregate 生成高周期 K 线视图 +// +// 注意:@timescaledb/typeorm v0.0.1 为实验版本, +// 不支持空间分区(partitioning_column)。 +// 若需要空间分区,可通过 db/init-db/ 下的 SQL 脚本手动添加。 +// ============================================================ + +import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; +import { + Entity, + PrimaryColumn, + Column, + Index, + CreateDateColumn, + UpdateDateColumn, +} from "typeorm"; + +/** K 线周期枚举 */ +export type KlineInterval = + | "1m" + | "5m" + | "15m" + | "30m" + | "1h" + | "4h" + | "1d" + | "1w"; + +/** + * 1 分钟 K 线 Hypertable + * + * 存储交易所推送的 OHLCV 数据。写入使用 UPSERT + * (ON CONFLICT DO UPDATE),已存在的 K 线只更新 + * high/low/close/volume 增量。 + * + * 高周期 K 线(5m+)通过 TimescaleDB 连续聚合视图 + * 从 1m 表自动生成,无需单独建表。 + */ +@Hypertable({ + compression: { + compress: true, + compress_orderby: "time DESC", + compress_segmentby: "exchange, symbol, interval", + policy: { + schedule_interval: "365 days", // 365 天后自动压缩 + }, + }, +}) +@Index(["exchange", "symbol", "interval", "time"], { unique: true }) +@Entity("klines") +export class Kline { + /** K 线开盘时间(UTC)— @timescaledb/typeorm 自动标记为时间分区列 */ + @TimeColumn() + @PrimaryColumn("timestamptz") + time!: Date; + + /** 交易所标识(binance / okx / bybit) */ + @Column("text") + exchange!: string; + + /** 交易对符号(如 BTCUSDT) */ + @Column("text") + symbol!: string; + + /** K 线周期(1m) */ + @Column("text") + interval!: KlineInterval; + + // ============================================================ + // OHLCV 价格数据(NUMERIC(20,8) 精度,与交易所对齐) + // ============================================================ + + /** 开盘价 */ + @Column("numeric", { precision: 20, scale: 8 }) + open!: number; + + /** 最高价 */ + @Column("numeric", { precision: 20, scale: 8 }) + high!: number; + + /** 最低价 */ + @Column("numeric", { precision: 20, scale: 8 }) + low!: number; + + /** 收盘价 */ + @Column("numeric", { precision: 20, scale: 8 }) + close!: number; + + /** 成交量(base 币种) */ + @Column("numeric", { precision: 20, scale: 8 }) + volume!: number; + + // ============================================================ + // 扩展字段(Binance 等交易所提供) + // ============================================================ + + /** 成交额(quote 币种) */ + @Column("numeric", { precision: 20, scale: 8, nullable: true }) + quote_volume?: number; + + /** 主动买入成交量(base 币种) */ + @Column("numeric", { precision: 20, scale: 8, nullable: true }) + taker_buy_base_vol?: number; + + /** 主动买入成交额(quote 币种) */ + @Column("numeric", { precision: 20, scale: 8, nullable: true }) + taker_buy_quote_vol?: number; + + /** 成交笔数 */ + @Column("integer", { nullable: true }) + trade_count?: number; + + /** K 线是否已关闭(true = 该周期 K 线不再变化) */ + @Column("boolean", { default: true }) + is_closed!: boolean; + + // ============================================================ + // 审计字段 + // ============================================================ + + /** 记录首次写入时间 */ + @CreateDateColumn({ type: "timestamptz", name: "created_at" }) + createdAt!: Date; + + /** 记录最后更新时间 */ + @UpdateDateColumn({ type: "timestamptz", name: "updated_at" }) + updatedAt!: Date; +} diff --git a/data/db/entities/trading-pair.entity.ts b/data/db/entities/trading-pair.entity.ts new file mode 100644 index 0000000..ca4d86b --- /dev/null +++ b/data/db/entities/trading-pair.entity.ts @@ -0,0 +1,105 @@ +// ============================================================ +// trading-pair.entity.ts — 交易对配置实体 +// ============================================================ +// 映射到 PostgreSQL trading_pairs 表,存储各交易所的交易对元信息。 +// 数据模块启动时从该表读取 active=true 的交易对列表, +// 决定 WebSocket 订阅范围和 K 线合成范围。 +// +// 继承 CommonBaseEntity:id (UUID) / created_at / updated_at +// +// 与 TimescaleDB klines 表的关系: +// klines.symbol → trading_pairs.symbol(逻辑外键,不做 DB 级约束) +// klines.exchange → exchanges.name(逻辑外键) +// ============================================================ + +import { + Entity, + Column, + ManyToOne, + JoinColumn, + Index, +} from "typeorm"; +import { Exchange } from "./exchange.entity"; +import { CommonBaseEntity } from "./common.entity"; + +@Entity("trading_pairs") +@Index(["exchange", "symbol"], { unique: true }) // 同一交易所下 symbol 唯一 +@Index(["active"]) // 按激活状态快速筛选 +export class TradingPair extends CommonBaseEntity { + /** 所属交易所 */ + @ManyToOne(() => Exchange, { nullable: false }) + @JoinColumn({ name: "exchange_id" }) + exchange!: Exchange; + + /** 交易对符号(如 BTCUSDT / ETHUSDT) */ + @Column("varchar", { length: 20 }) + symbol!: string; + + /** 基础币种(如 BTC) */ + @Column("varchar", { length: 10 }) + base_asset!: string; + + /** 计价币种(如 USDT) */ + @Column("varchar", { length: 10 }) + quote_asset!: string; + + /** 价格精度(小数位数) */ + @Column("integer", { default: 10 }) + price_precision!: number; + + /** 数量精度(小数位数) */ + @Column("integer", { default: 10 }) + quantity_precision!: number; + + /** 最小下单量 */ + @Column("numeric", { precision: 32, scale: 8, nullable: true }) + min_qty?: number; + + /** 下单步长(数量增量) */ + @Column("numeric", { precision: 32, scale: 8, nullable: true }) + step_size?: number; + + /** 最小名义价值(USDT) */ + @Column("numeric", { precision: 32, scale: 8, nullable: true }) + min_notional?: number; + + /** 是否激活数据订阅(false 时不采集该交易对行情) */ + @Column("boolean", { default: true }) + active!: boolean; + + /** 是否启用 K 线合成(false 时仅采集原始行情,不合成) */ + @Column("boolean", { default: true }) + kline_synthesis_enabled!: boolean; + + /** K 线合成周期列表(逗号分隔,如 "1m,5m,15m,1h,4h,1d") */ + @Column("varchar", { length: 100, default: "1m,5m,15m,1h,4h,1d" }) + kline_intervals!: string; + + /** + * 历史 K 线最后补全时间(UTC)。 + * 记录最近一次 REST 补拉 K 线的结束时间戳, + * 下次启动时从此时间点继续补全,避免重复拉取。 + * + * 默认值为 Unix 0(1970-01-01T00:00:00.000Z), + * 新交易对从 epoch 起始时间开始全量补拉, + * 补全后更新为实际拉取到的最后时间。 + */ + @Column("timestamptz", { default: () => "to_timestamp(0)" }) + last_backfill_time!: Date; + + /** 备注 */ + @Column("text", { nullable: true }) + notes?: string; + + // ============================================================ + // 工具方法 + // ============================================================ + + /** 解析 kline_intervals 为周期数组 */ + getIntervals(): string[] { + return this.kline_intervals + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + } +} diff --git a/data/db/index.ts b/data/db/index.ts deleted file mode 100644 index 18e1033..0000000 --- a/data/db/index.ts +++ /dev/null @@ -1,121 +0,0 @@ -// ============================================================ -// db/index.ts — 统一导出 -// ============================================================ -// 使用方式: -// import { KlineRow, KlineRawSchema, bulkUpsertKlines } from "./db"; -// import { MonitoredSymbolsRepo } from "./db"; -// ============================================================ - -// 类型定义 -export type { - Exchange, - KlineInterval, - LogLevel, - KlineRow, - KlineInsert, - AggregatedKlineRow, - MonitoredSymbolRow, - MonitoredSymbolInsert, - MonitoredSymbolUpdate, - ExchangeConfigRow, - ExchangeConfigInsert, - AppConfigRow, - AppConfigKey, - StreamKey, - StreamSubscription, -} from "./types"; - -// Zod 运行时校验 -export { - ExchangeSchema, - KlineIntervalSchema, - LogLevelSchema, - SymbolSchema, - NumericStringSchema, - KlineRawSchema, - KlineBatchSchema, - MonitoredSymbolInsertSchema, - MonitoredSymbolUpdateSchema, - ExchangeConfigInsertSchema, - StreamKeySchema, - EnvConfigSchema, -} from "./validators"; - -export type { - KlineRaw, - KlineBatch, - MonitoredSymbolInsert as MonitoredSymbolInsertValidated, - MonitoredSymbolUpdate as MonitoredSymbolUpdateValidated, - ExchangeConfigInsert as ExchangeConfigInsertValidated, - StreamKey as StreamKeyValidated, - EnvConfig, -} from "./validators"; - -// 参数化 SQL 查询 -export { - bulkUpsertKlines, - packBulkKlines, - queryKlinesRange, - queryKlinesLatest, - queryAggregatedKlines, - // monitored_symbols - queryAllMonitoredSymbols, - queryEnabledSymbols, - querySymbolsByExchange, - queryMonitoredSymbolById, - queryMonitoredSymbolByKey, - upsertMonitoredSymbol, - updateMonitoredSymbol, - disableMonitoredSymbol, - deleteMonitoredSymbol, - deleteMonitoredSymbolByKey, - // exchange_config - queryAllExchangeConfigs, - queryEnabledExchanges, - queryExchangeConfig, - queryExchangeConfigById, - upsertExchangeConfig, - updateExchangeConfig, - deleteExchangeConfig, - deleteExchangeConfigByExchange, - // app_config - queryAllAppConfig, - queryAppConfig, - queryAppConfigById, - upsertAppConfig, - updateAppConfig, - deleteAppConfig, - deleteAppConfigById, - // 复合查询 - queryStreamSubscriptions, -} from "./queries"; - -export type { BulkKlineParams } from "./queries"; - -// ============================================================ -// Config CRUD 服务层(推荐使用) -// ============================================================ - -export { - MonitoredSymbolsRepo, - ExchangeConfigRepo, - AppConfigRepo, -} from "./config-crud"; - -// ============================================================ -// PostgreSQL 连接池 & 工具 -// ============================================================ - -export { - pool, - healthCheck, - timescaleVersion, - withTransaction, - closePool, - registerShutdownHandlers, - initSchemaFromFile, - initSchema, - isSchemaInitialized, -} from "./pg"; - -export { default as defaultPool } from "./pg"; diff --git a/data/db/pg.ts b/data/db/pg.ts deleted file mode 100644 index 241db79..0000000 --- a/data/db/pg.ts +++ /dev/null @@ -1,364 +0,0 @@ -// ============================================================ -// db/pg.ts — PostgreSQL / TimescaleDB 连接池管理 -// ============================================================ -// 职责: -// 1. 基于 config.ts 的 pgsql 配置创建 pg.Pool 单例 -// 2. 连接生命周期事件监听(connect / acquire / remove / error) -// 3. 提供健康检查(healthCheck) -// 4. 提供事务辅助函数(withTransaction) -// 5. 进程退出时优雅关闭(SIGTERM / SIGINT) -// -// 使用方式: -// import { pool } from "./db"; // 通过 index.ts 统一导出 -// import { healthCheck } from "./db"; -// const { rows } = await pool.query("SELECT NOW()"); -// ============================================================ - -import pg from "pg"; -import { readFileSync, readdirSync } from "node:fs"; -import { resolve, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; -import { pgsql } from "../config"; - -// ============================================================ -// 1. 连接池创建(单例) -// ============================================================ - -/** - * pg.Pool 单例。 - * 配置来源于 config.ts → pgsql,已包含连接数上限、超时等参数。 - * - * pg.Pool 内部使用懒连接——首次 query 时才建立连接, - * 因此模块加载时不会立即连接数据库。 - */ -export const pool = new pg.Pool({ - host: pgsql.host, - port: pgsql.port, - database: pgsql.database, - user: pgsql.user, - password: pgsql.password, - max: pgsql.max, - idleTimeoutMillis: pgsql.idleTimeoutMillis, - connectionTimeoutMillis: pgsql.connectionTimeoutMillis, - // TimescaleDB 特有的超时设置:分析查询可能较慢 - statement_timeout: 30000, // 单条 SQL 最大执行 30s - // application_name 便于在 pg_stat_activity 中识别 - application_name: "trade-data", -}); - -// ============================================================ -// 2. 连接池事件监听(可观测性) -// ============================================================ - -/** 新客户端连接建立 */ -pool.on("connect", (client) => { - // 为每个连接设置 TimescaleDB 优化参数 - // 跳过 WAL 日志可加速批量写入(仅在可接受丢失最近几秒数据的场景) - // client.query("SET timescaledb.enable_skip_scan = ON"); - console.log(`[pg] 新连接建立 (total: ${pool.totalCount}, idle: ${pool.idleCount})`); -}); - -/** 从池中获取连接 */ -pool.on("acquire", () => { - // 连接池耗尽时会频繁触发,可在此记录高负载信号 -}); - -/** 连接归还池 */ -pool.on("remove", () => { - console.log(`[pg] 连接关闭 (total: ${pool.totalCount}, idle: ${pool.idleCount})`); -}); - -/** - * 空闲客户端出错(如网络中断、PG 重启)。 - * pg.Pool 会自动移除问题连接并创建新连接,此处仅记录日志。 - */ -pool.on("error", (err: Error) => { - console.error(`[pg] 连接池错误: ${err.message}`); - // 不退出进程——连接池会自动恢复 -}); - -// ============================================================ -// 3. 健康检查 -// ============================================================ - -/** - * 数据库连通性检查。 - * 执行轻量查询 `SELECT 1`,超时 5 秒。 - * - * @returns true 表示数据库可达 - */ -export async function healthCheck(): Promise { - try { - const client = await pool.connect(); - try { - await client.query("SELECT 1"); - return true; - } finally { - client.release(); - } - } catch { - return false; - } -} - -/** - * 深度健康检查:验证 TimescaleDB 扩展是否已安装。 - * 仅在首次连接或定期巡检时调用。 - * - * @returns TimescaleDB 版本字符串,未安装则返回 null - */ -export async function timescaleVersion(): Promise { - try { - const { rows } = await pool.query<{ extversion: string }>( - "SELECT extversion FROM pg_extension WHERE extname = 'timescaledb'", - ); - return rows[0]?.extversion ?? null; - } catch { - return null; - } -} - -// ============================================================ -// 4. 事务辅助 -// ============================================================ - -/** - * 在单连接上执行事务。 - * 自动 BEGIN / COMMIT / ROLLBACK,连接用完即释放。 - * - * @param fn - 事务体,接收 pg.PoolClient,返回 Promise - * @returns fn 的返回值 - * @throws 事务内任何异常都会触发 ROLLBACK 并向上抛出 - * - * @example - * const result = await withTransaction(async (client) => { - * await client.query("INSERT INTO ..."); - * await client.query("UPDATE ..."); - * return { success: true }; - * }); - */ -export async function withTransaction( - fn: (client: pg.PoolClient) => Promise, -): Promise { - const client = await pool.connect(); - try { - await client.query("BEGIN"); - const result = await fn(client); - await client.query("COMMIT"); - return result; - } catch (err) { - await client.query("ROLLBACK"); - throw err; - } finally { - client.release(); - } -} - -// ============================================================ -// 5. 优雅关闭 -// ============================================================ - -/** 是否正在关闭(防止重复调用) */ -let shuttingDown = false; - -/** - * 关闭连接池。 - * 先等待所有进行中的查询完成(drain),再断开所有连接。 - * - * 应在进程退出前调用:SIGTERM / SIGINT 处理器中。 - */ -export async function closePool(): Promise { - if (shuttingDown) { - return; - } - shuttingDown = true; - - console.log("[pg] 正在关闭连接池..."); - // pool.end() 等待所有活跃查询完成后关闭 - await pool.end(); - console.log("[pg] 连接池已关闭"); -} - -/** - * 注册进程信号处理器——优雅关闭。 - * 在应用入口调用一次即可。 - */ -export function registerShutdownHandlers(): void { - const shutdown = async (signal: string) => { - console.log(`[pg] 收到 ${signal} 信号,开始优雅关闭...`); - await closePool(); - process.exit(0); - }; - - process.once("SIGTERM", () => shutdown("SIGTERM")); - process.once("SIGINT", () => shutdown("SIGINT")); -} - -// ============================================================ -// 6. Schema 初始化(基于 data/schema/*.sql) -// ============================================================ - -/** - * 将 SQL 文本按语句拆分为独立命令。 - * 规则: - * - 按 `;` 分割 - * - 跳过空行和纯注释行 - * - 单个语句去除首尾空白后若为空则跳过 - * - * 注意:此方法假设 SQL 中 `;` 仅作为语句分隔符, - * 不含存储过程/函数体内的 `;`(schema/*.sql 满足此条件)。 - */ -function splitSQLStatements(sql: string): string[] { - const statements: string[] = []; - for (const raw of sql.split(";")) { - const trimmed = raw.trim(); - // 跳过空语句 - if (trimmed === "") { - continue; - } - // 跳过仅包含注释的行(已在上层过滤,此处兜底) - statements.push(trimmed); - } - return statements; -} - -/** - * 从单个 .sql 文件初始化 Schema。 - * 读取文件 → 拆分语句 → 逐条执行(同一连接,保证顺序)。 - * - * 所有 SQL 均使用 IF NOT EXISTS / ON CONFLICT DO NOTHING, - * 因此重复执行安全幂等。 - * - * @param filePath - SQL 文件的绝对路径 - * @returns 成功执行的语句数 - */ -export async function initSchemaFromFile(filePath: string): Promise { - const sql = readFileSync(filePath, "utf-8"); - - // 预处理:移除纯注释行(以 -- 开头),减少无效语句 - const lines = sql - .split("\n") - .filter((line: string) => { - const trimmed = line.trim(); - return trimmed !== "" && !trimmed.startsWith("--"); - }) - .join("\n"); - - const statements = splitSQLStatements(lines); - if (statements.length === 0) { - return 0; - } - - let executed = 0; - // 使用单连接执行所有语句,保证 DDL 顺序(如先建表再建索引) - const client = await pool.connect(); - try { - for (const stmt of statements) { - await client.query(stmt); - executed++; - } - } finally { - client.release(); - } - - return executed; -} - -/** - * 从 data/schema/ 目录初始化所有表结构。 - * - * 执行顺序(按文件名排序): - * 1. klines.sql — K 线主表(hypertable)、索引、压缩、连续聚合 - * 2. config.sql — 配置表(monitored_symbols / exchange_config / app_config)+ 预置数据 - * - * Docker Compose 首次启动时,001_init.sql 已被自动执行。 - * 此函数作为补充,确保以下场景: - * - 裸 PostgreSQL 安装(非 Docker 部署) - * - 版本升级时需要新增表/索引 - * - 开发环境快速重置 Schema - * - * @param schemaDir - schema 目录路径,默认 ../schema(相对于本文件) - * @returns 各文件执行结果摘要 - */ -export async function initSchema(schemaDir?: string): Promise<{ - files: string[]; - totalStatements: number; - errors: string[]; -}> { - // 计算 schema 目录绝对路径(ESM 中 __dirname 不可用) - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - const dir = schemaDir ?? resolve(__dirname, "..", "schema"); - - const result = { files: [] as string[], totalStatements: 0, errors: [] as string[] }; - - // 读取目录,按文件名排序保证执行顺序(klines.sql 先于 config.sql) - let entries: string[]; - try { - entries = readdirSync(dir) - .filter((f: string) => f.endsWith(".sql")) - .sort(); // 字母序 - } catch { - result.errors.push(`无法读取 schema 目录: ${dir}`); - return result; - } - - // 手动排序:klines.sql 必须在 config.sql 之前(外键/引用依赖) - const klinesFirst = ["klines.sql", "config.sql"]; - entries.sort((a, b) => { - const ia = klinesFirst.indexOf(a); - const ib = klinesFirst.indexOf(b); - if (ia !== -1 && ib !== -1) return ia - ib; - if (ia !== -1) return -1; - if (ib !== -1) return 1; - return a.localeCompare(b); - }); - - for (const entry of entries) { - const filePath = resolve(dir, entry); - try { - const count = await initSchemaFromFile(filePath); - result.files.push(`${entry} (${count} 条)`); - result.totalStatements += count; - } catch (err) { - const msg = `${entry}: ${(err as Error).message}`; - result.errors.push(msg); - console.error(`[pg] Schema 初始化失败 — ${msg}`); - } - } - - if (result.errors.length === 0) { - console.log( - `[pg] Schema 初始化完成 — ${result.files.length} 个文件, ${result.totalStatements} 条语句`, - ); - } - - return result; -} - -/** - * 快速判断核心表是否已存在(轻量级检查,不做全量 Schema 验证)。 - * 仅在执行 initSchema() 前做一次探测,已存在则跳过。 - * - * @returns true 表示 klines 和 monitored_symbols 表均已存在 - */ -export async function isSchemaInitialized(): Promise { - try { - const { rows } = await pool.query<{ count: string }>(` - SELECT COUNT(*)::TEXT AS count - FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name IN ('klines', 'monitored_symbols', 'exchange_config', 'app_config') - `); - // 4 张核心表全部存在 - return parseInt(rows[0]?.count ?? "0", 10) >= 4; - } catch { - return false; - } -} - -// ============================================================ -// 7. 默认导出(便于 import pool from "./db/pg") -// ============================================================ - -export default pool; diff --git a/data/db/queries.ts b/data/db/queries.ts deleted file mode 100644 index 5ad4431..0000000 --- a/data/db/queries.ts +++ /dev/null @@ -1,561 +0,0 @@ -// ============================================================ -// schema/queries.ts — 类型安全的参数化 SQL 查询 -// ============================================================ -// 每一条 SQL 都使用 $1, $2... 参数化,防止 SQL 注入。 -// 返回类型与 types.ts 中的接口严格对应。 -// -// 使用方式: -// import { pool } from "../db"; -// import { queryEnabledSymbols } from "./schema/queries"; -// const result = await pool.query(queryEnabledSymbols, ["binance"]); -// // result.rows 自动推断为 MonitoredSymbolRow[] -// ============================================================ - -import type { - KlineInsert, - KlineRow, - AggregatedKlineRow, - MonitoredSymbolRow, - MonitoredSymbolInsert, - ExchangeConfigRow, - ExchangeConfigInsert, - AppConfigRow, - Exchange, - KlineInterval, - StreamKey, -} from "./types"; - -// ============================================================ -// K 线查询 — klines -// ============================================================ - -/** - * 批量插入 K 线(UPSERT — 冲突时更新 OHLCV) - * - * 使用 UNNEST 批量写入,单次可插入数千条,性能远优于逐条 INSERT。 - * 冲突策略:ON CONFLICT 时更新价格/成交量/闭合状态。 - */ -export const bulkUpsertKlines = ` - INSERT INTO klines ( - time, exchange, symbol, interval, - open, high, low, close, volume, - quote_volume, taker_buy_base_vol, taker_buy_quote_vol, - trade_count, is_closed - ) - SELECT * FROM UNNEST( - $1::TIMESTAMPTZ[], -- time[] - $2::TEXT[], -- exchange[] - $3::TEXT[], -- symbol[] - $4::TEXT[], -- interval[] - $5::NUMERIC(20,8)[], -- open[] - $6::NUMERIC(20,8)[], -- high[] - $7::NUMERIC(20,8)[], -- low[] - $8::NUMERIC(20,8)[], -- close[] - $9::NUMERIC(20,8)[], -- volume[] - $10::NUMERIC(20,8)[],-- quote_volume[] - $11::NUMERIC(20,8)[],-- taker_buy_base_vol[] - $12::NUMERIC(20,8)[],-- taker_buy_quote_vol[] - $13::INTEGER[], -- trade_count[] - $14::BOOLEAN[] -- is_closed[] - ) - ON CONFLICT (time, exchange, symbol, interval) DO UPDATE SET - open = EXCLUDED.open, - high = EXCLUDED.high, - low = EXCLUDED.low, - close = EXCLUDED.close, - volume = EXCLUDED.volume, - quote_volume = EXCLUDED.quote_volume, - taker_buy_base_vol = EXCLUDED.taker_buy_base_vol, - taker_buy_quote_vol = EXCLUDED.taker_buy_quote_vol, - trade_count = EXCLUDED.trade_count, - is_closed = EXCLUDED.is_closed, - updated_at = NOW() -`; - -/** 批量插入的参数类型:每个字段是一个数组 */ -export interface BulkKlineParams { - time: Date[]; - exchange: Exchange[]; - symbol: string[]; - interval: KlineInterval[]; - open: string[]; - high: string[]; - low: string[]; - close: string[]; - volume: string[]; - quote_volume: string[]; - taker_buy_base_vol: string[]; - taker_buy_quote_vol: string[]; - trade_count: number[]; - is_closed: boolean[]; -} - -/** 将 KlineInsert[] 拆解为 BulkKlineParams */ -export function packBulkKlines(rows: KlineInsert[]): BulkKlineParams { - const len = rows.length; - const params: BulkKlineParams = { - time: new Array(len), - exchange: new Array(len), - symbol: new Array(len), - interval: new Array(len), - open: new Array(len), - high: new Array(len), - low: new Array(len), - close: new Array(len), - volume: new Array(len), - quote_volume: new Array(len), - taker_buy_base_vol: new Array(len), - taker_buy_quote_vol: new Array(len), - trade_count: new Array(len), - is_closed: new Array(len), - }; - - for (let i = 0; i < len; i++) { - const r = rows[i]!; - params.time[i] = r.time; - params.exchange[i] = r.exchange; - params.symbol[i] = r.symbol; - params.interval[i] = r.interval; - params.open[i] = r.open; - params.high[i] = r.high; - params.low[i] = r.low; - params.close[i] = r.close; - params.volume[i] = r.volume; - params.quote_volume[i] = r.quote_volume ?? "0"; - params.taker_buy_base_vol[i] = r.taker_buy_base_vol ?? "0"; - params.taker_buy_quote_vol[i] = r.taker_buy_quote_vol ?? "0"; - params.trade_count[i] = r.trade_count ?? 0; - params.is_closed[i] = r.is_closed ?? true; - } - - return params; -} - -/** - * 查询原始 K 线(时间范围) - * 返回类型:KlineRow[] - */ -export const queryKlinesRange = ` - SELECT time, exchange, symbol, interval, - open, high, low, close, volume, - quote_volume, taker_buy_base_vol, taker_buy_quote_vol, - trade_count, is_closed, created_at, updated_at - FROM klines - WHERE exchange = $1 - AND symbol = $2 - AND interval = $3 - AND time >= $4 - AND time < $5 - ORDER BY time ASC -`; - -/** - * 查询最新 N 根 K 线 - * 返回类型:KlineRow[] - */ -export const queryKlinesLatest = ` - SELECT time, exchange, symbol, interval, - open, high, low, close, volume, - quote_volume, taker_buy_base_vol, taker_buy_quote_vol, - trade_count, is_closed, created_at, updated_at - FROM klines - WHERE exchange = $1 - AND symbol = $2 - AND interval = $3 - ORDER BY time DESC - LIMIT $4 -`; - -// ============================================================ -// 聚合 K 线查询 — klines_5m / 15m / 1h / 1d -// ============================================================ - -/** - * 查询聚合 K 线(动态视图名) - * - * @param viewName — "klines_5m" | "klines_15m" | "klines_1h" | "klines_1d" - * - * 注意:视图名已通过枚举约束,不存在注入风险(不使用用户输入拼接) - */ -export function queryAggregatedKlines( - viewName: "klines_5m" | "klines_15m" | "klines_1h" | "klines_1d", -) { - // 视图名来自代码常量,安全拼接 - return ` - SELECT time, exchange, symbol, interval, - open, high, low, close, volume, - quote_volume, taker_buy_base_vol, taker_buy_quote_vol, trade_count - FROM ${viewName} - WHERE exchange = $1 - AND symbol = $2 - AND time >= $3 - AND time < $4 - ORDER BY time ASC - `; -} - -// ============================================================ -// 监控交易对查询 — monitored_symbols -// ============================================================ - -/** - * 查询所有启用的监控标的(采集服务启动时调用) - * 返回类型:MonitoredSymbolRow[] - */ -export const queryEnabledSymbols = ` - SELECT id, exchange, symbol, interval, - enabled, priority, label, notes, - created_at, updated_at - FROM monitored_symbols - WHERE enabled = TRUE - ORDER BY exchange, priority DESC, symbol, interval -`; - -/** - * 查询指定交易所的监控标的 - * 返回类型:MonitoredSymbolRow[] - */ -export const querySymbolsByExchange = ` - SELECT id, exchange, symbol, interval, - enabled, priority, label, notes, - created_at, updated_at - FROM monitored_symbols - WHERE exchange = $1 - AND enabled = TRUE - ORDER BY priority DESC, symbol, interval -`; - -/** - * 插入监控标的(UPSERT) - */ -export const upsertMonitoredSymbol = ` - INSERT INTO monitored_symbols (exchange, symbol, interval, enabled, priority, label, notes) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT (exchange, symbol, interval) DO UPDATE SET - enabled = EXCLUDED.enabled, - priority = EXCLUDED.priority, - label = EXCLUDED.label, - notes = EXCLUDED.notes, - updated_at = NOW() - RETURNING id, exchange, symbol, interval, - enabled, priority, label, notes, - created_at, updated_at -`; - -/** - * 禁用监控标的(软删除) - */ -export const disableMonitoredSymbol = ` - UPDATE monitored_symbols - SET enabled = FALSE, updated_at = NOW() - WHERE exchange = $1 AND symbol = $2 AND interval = $3 - RETURNING id, exchange, symbol, interval -`; - -/** - * 按 ID 查询单个监控标的 - * 返回类型:MonitoredSymbolRow | null - */ -export const queryMonitoredSymbolById = ` - SELECT id, exchange, symbol, interval, - enabled, priority, label, notes, - created_at, updated_at - FROM monitored_symbols - WHERE id = $1 -`; - -/** - * 查询所有监控标的(含已禁用) - * 返回类型:MonitoredSymbolRow[] - */ -export const queryAllMonitoredSymbols = ` - SELECT id, exchange, symbol, interval, - enabled, priority, label, notes, - created_at, updated_at - FROM monitored_symbols - ORDER BY exchange, priority DESC, symbol, interval -`; - -/** - * 按唯一键 (exchange, symbol, interval) 查询监控标的 - * 返回类型:MonitoredSymbolRow | null - */ -export const queryMonitoredSymbolByKey = ` - SELECT id, exchange, symbol, interval, - enabled, priority, label, notes, - created_at, updated_at - FROM monitored_symbols - WHERE exchange = $1 AND symbol = $2 AND interval = $3 -`; - -/** - * 按 ID 删除监控标的(硬删除) - * 返回被删除记录的 id - */ -export const deleteMonitoredSymbol = ` - DELETE FROM monitored_symbols - WHERE id = $1 - RETURNING id -`; - -/** - * 按唯一键删除监控标的(硬删除) - * 返回被删除记录的 id - */ -export const deleteMonitoredSymbolByKey = ` - DELETE FROM monitored_symbols - WHERE exchange = $1 AND symbol = $2 AND interval = $3 - RETURNING id -`; - -/** - * 更新监控标的(部分字段) - * 仅更新传入的非 NULL 字段,自动更新 updated_at - */ -export const updateMonitoredSymbol = ` - UPDATE monitored_symbols - SET enabled = COALESCE($2, enabled), - priority = COALESCE($3, priority), - label = COALESCE($4, label), - notes = COALESCE($5, notes), - updated_at = NOW() - WHERE id = $1 - RETURNING id, exchange, symbol, interval, - enabled, priority, label, notes, - created_at, updated_at -`; - -// ============================================================ -// 交易所配置查询 — exchange_config -// ============================================================ - -/** - * 查询所有启用的交易所配置 - * 返回类型:ExchangeConfigRow[] - */ -export const queryEnabledExchanges = ` - SELECT id, exchange, - rest_url, ws_url, ws_ping_interval_ms, - rate_limit_per_sec, max_reconnect_attempts, reconnect_delay_ms, - enabled, notes, created_at, updated_at - FROM exchange_config - WHERE enabled = TRUE - ORDER BY exchange -`; - -/** - * 查询单个交易所配置 - * 返回类型:ExchangeConfigRow | null - */ -export const queryExchangeConfig = ` - SELECT id, exchange, - rest_url, ws_url, ws_ping_interval_ms, - rate_limit_per_sec, max_reconnect_attempts, reconnect_delay_ms, - enabled, notes, created_at, updated_at - FROM exchange_config - WHERE exchange = $1 -`; - -/** - * 插入/更新交易所配置(UPSERT) - */ -export const upsertExchangeConfig = ` - INSERT INTO exchange_config ( - exchange, rest_url, ws_url, ws_ping_interval_ms, - rate_limit_per_sec, max_reconnect_attempts, reconnect_delay_ms, - enabled, notes - ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - ON CONFLICT (exchange) DO UPDATE SET - rest_url = EXCLUDED.rest_url, - ws_url = EXCLUDED.ws_url, - ws_ping_interval_ms = EXCLUDED.ws_ping_interval_ms, - rate_limit_per_sec = EXCLUDED.rate_limit_per_sec, - max_reconnect_attempts = EXCLUDED.max_reconnect_attempts, - reconnect_delay_ms = EXCLUDED.reconnect_delay_ms, - enabled = EXCLUDED.enabled, - notes = EXCLUDED.notes, - updated_at = NOW() - RETURNING id, exchange, - rest_url, ws_url, ws_ping_interval_ms, - rate_limit_per_sec, max_reconnect_attempts, reconnect_delay_ms, - enabled, notes, created_at, updated_at -`; - -/** - * 按 ID 查询交易所配置 - * 返回类型:ExchangeConfigRow | null - */ -export const queryExchangeConfigById = ` - SELECT id, exchange, - rest_url, ws_url, ws_ping_interval_ms, - rate_limit_per_sec, max_reconnect_attempts, reconnect_delay_ms, - enabled, notes, created_at, updated_at - FROM exchange_config - WHERE id = $1 -`; - -/** - * 查询所有交易所配置(含已禁用) - * 返回类型:ExchangeConfigRow[] - */ -export const queryAllExchangeConfigs = ` - SELECT id, exchange, - rest_url, ws_url, ws_ping_interval_ms, - rate_limit_per_sec, max_reconnect_attempts, reconnect_delay_ms, - enabled, notes, created_at, updated_at - FROM exchange_config - ORDER BY exchange -`; - -/** - * 按 ID 删除交易所配置(硬删除) - * 返回被删除记录的 id - */ -export const deleteExchangeConfig = ` - DELETE FROM exchange_config - WHERE id = $1 - RETURNING id -`; - -/** - * 按交易所标识删除配置(硬删除) - * 返回被删除记录的 id - */ -export const deleteExchangeConfigByExchange = ` - DELETE FROM exchange_config - WHERE exchange = $1 - RETURNING id -`; - -/** - * 更新交易所配置(部分字段) - * 仅更新传入的非 NULL 字段,自动更新 updated_at - */ -export const updateExchangeConfig = ` - UPDATE exchange_config - SET rest_url = COALESCE($2, rest_url), - ws_url = COALESCE($3, ws_url), - ws_ping_interval_ms = COALESCE($4, ws_ping_interval_ms), - rate_limit_per_sec = COALESCE($5, rate_limit_per_sec), - max_reconnect_attempts = COALESCE($6, max_reconnect_attempts), - reconnect_delay_ms = COALESCE($7, reconnect_delay_ms), - enabled = COALESCE($8, enabled), - notes = COALESCE($9, notes), - updated_at = NOW() - WHERE id = $1 - RETURNING id, exchange, - rest_url, ws_url, ws_ping_interval_ms, - rate_limit_per_sec, max_reconnect_attempts, reconnect_delay_ms, - enabled, notes, created_at, updated_at -`; - -// ============================================================ -// 全局配置查询 — app_config -// ============================================================ - -/** - * 查询所有应用配置 - * 返回类型:AppConfigRow[] - */ -export const queryAllAppConfig = ` - SELECT id, key, value, description, updated_at - FROM app_config - ORDER BY key -`; - -/** - * 查询单个配置项 - * 返回类型:AppConfigRow | null - */ -export const queryAppConfig = ` - SELECT id, key, value, description, updated_at - FROM app_config - WHERE key = $1 -`; - -/** - * 设置配置项(UPSERT) - */ -export const upsertAppConfig = ` - INSERT INTO app_config (key, value, description) - VALUES ($1, $2, $3) - ON CONFLICT (key) DO UPDATE SET - value = EXCLUDED.value, - description = EXCLUDED.description, - updated_at = NOW() - RETURNING id, key, value, description, updated_at -`; - -/** - * 按 ID 查询应用配置 - * 返回类型:AppConfigRow | null - */ -export const queryAppConfigById = ` - SELECT id, key, value, description, updated_at - FROM app_config - WHERE id = $1 -`; - -/** - * 按 key 删除应用配置(硬删除) - * 返回被删除记录的 id - */ -export const deleteAppConfig = ` - DELETE FROM app_config - WHERE key = $1 - RETURNING id -`; - -/** - * 按 ID 删除应用配置(硬删除) - * 返回被删除记录的 id - */ -export const deleteAppConfigById = ` - DELETE FROM app_config - WHERE id = $1 - RETURNING id -`; - -/** - * 更新应用配置值(部分字段) - * 仅更新传入的非 NULL 字段,自动更新 updated_at - */ -export const updateAppConfig = ` - UPDATE app_config - SET value = COALESCE($2, value), - description = COALESCE($3, description), - updated_at = NOW() - WHERE id = $1 - RETURNING id, key, value, description, updated_at -`; - -// ============================================================ -// 复合查询:采集服务启动加载项 -// ============================================================ - -/** - * 一次性加载所有启动配置: - * 1. 启用的监控标的列表 - * 2. 对应交易所的连接配置 - * - * 返回两表 JOIN 结果,供采集服务初始化 WebSocket 连接池。 - */ -export const queryStreamSubscriptions = ` - SELECT - m.exchange, - m.symbol, - m.interval, - m.priority, - e.rest_url, - e.ws_url, - e.ws_ping_interval_ms, - e.rate_limit_per_sec, - e.max_reconnect_attempts, - e.reconnect_delay_ms - FROM monitored_symbols m - JOIN exchange_config e ON m.exchange = e.exchange - WHERE m.enabled = TRUE - AND e.enabled = TRUE - ORDER BY m.exchange, m.priority DESC, m.symbol, m.interval -`; diff --git a/data/db/types.ts b/data/db/types.ts deleted file mode 100644 index ed939df..0000000 --- a/data/db/types.ts +++ /dev/null @@ -1,249 +0,0 @@ -// ============================================================ -// schema/types.ts — PostgreSQL 表对应的 TypeScript 类型定义 -// ============================================================ -// 与 data/schema/*.sql 中的表结构一一对应 -// -// 类型约定: -// NUMERIC(20,8) → string (保留精度,避免 IEEE 754 浮点误差) -// TIMESTAMPTZ → Date (pg 默认解析为 Date) -// SERIAL / INT → number -// SMALLINT → number -// REAL → number -// BOOLEAN → boolean -// ============================================================ - -// ============================================================ -// 联合类型:约束 TEXT 字段的合法值 -// ============================================================ - -/** 支持的交易所标识 */ -export type Exchange = "binance" | "okx" | "bybit"; - -/** K 线周期(原始 1m + 聚合派生) */ -export type KlineInterval = "1m" | "5m" | "15m" | "1h" | "4h" | "1d"; - -/** 日志级别 */ -export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal"; - -// ============================================================ -// 1. K 线主表 — klines -// ============================================================ - -/** klines 表完整行类型 */ -export interface KlineRow { - /** K 线开盘时间(UTC) */ - time: Date; - /** 交易所 */ - exchange: Exchange; - /** 交易对,如 BTCUSDT */ - symbol: string; - /** K 线周期 */ - interval: KlineInterval; - /** 开盘价 */ - open: string; - /** 最高价 */ - high: string; - /** 最低价 */ - low: string; - /** 收盘价 */ - close: string; - /** 成交量(基准币种) */ - volume: string; - /** 成交额(计价币种) */ - quote_volume: string; - /** 主动买入量(基准币种) */ - taker_buy_base_vol: string; - /** 主动买入额(计价币种) */ - taker_buy_quote_vol: string; - /** 成交笔数 */ - trade_count: number; - /** K 线是否已闭合 */ - is_closed: boolean; - /** 记录创建时间 */ - created_at: Date; - /** 记录更新时间 */ - updated_at: Date; -} - -/** klines 表插入类型(省略自动生成的元数据列) */ -export interface KlineInsert { - time: Date; - exchange: Exchange; - symbol: string; - interval: KlineInterval; - open: string; - high: string; - low: string; - close: string; - volume: string; - quote_volume?: string; - taker_buy_base_vol?: string; - taker_buy_quote_vol?: string; - trade_count?: number; - is_closed?: boolean; -} - -// ============================================================ -// 2. 连续聚合视图 — klines_5m / klines_15m / klines_1h / klines_1d -// ============================================================ - -/** 聚合 K 线通用类型(OHLCV,无扩展字段) */ -export interface AggregatedKlineRow { - time: Date; - exchange: Exchange; - symbol: string; - interval: KlineInterval; - open: string; - high: string; - low: string; - close: string; - volume: string; - quote_volume: string; - taker_buy_base_vol: string; - taker_buy_quote_vol: string; - trade_count: number; -} - -// ============================================================ -// 3. 监控交易对配置 — monitored_symbols -// ============================================================ - -/** monitored_symbols 表完整行类型 */ -export interface MonitoredSymbolRow { - /** 自增主键 */ - id: number; - /** 交易所 */ - exchange: Exchange; - /** 交易对 */ - symbol: string; - /** K 线周期 */ - interval: KlineInterval; - /** 是否启用采集 */ - enabled: boolean; - /** 优先级(0-32767,越大越优先) */ - priority: number; - /** 人类可读标签 */ - label: string | null; - /** 备注 */ - notes: string | null; - /** 创建时间 */ - created_at: Date; - /** 更新时间 */ - updated_at: Date; -} - -/** monitored_symbols 插入类型 */ -export interface MonitoredSymbolInsert { - exchange: Exchange; - symbol: string; - interval: KlineInterval; - enabled?: boolean; - priority?: number; - label?: string | null; - notes?: string | null; -} - -/** monitored_symbols 更新类型(所有字段可选) */ -export interface MonitoredSymbolUpdate { - enabled?: boolean; - priority?: number; - label?: string | null; - notes?: string | null; -} - -// ============================================================ -// 4. 交易所连接配置 — exchange_config -// ============================================================ - -/** exchange_config 表完整行类型 */ -export interface ExchangeConfigRow { - /** 自增主键 */ - id: number; - /** 交易所标识(唯一) */ - exchange: Exchange; - /** REST API 基础 URL(null = 使用 SDK 默认值) */ - rest_url: string | null; - /** WebSocket 基础 URL(null = 使用 SDK 默认值) */ - ws_url: string | null; - /** 心跳间隔(毫秒) */ - ws_ping_interval_ms: number; - /** 每秒最大请求数 */ - rate_limit_per_sec: number; - /** 最大重连次数 */ - max_reconnect_attempts: number; - /** 重连延迟基数(毫秒) */ - reconnect_delay_ms: number; - /** 是否启用该交易所 */ - enabled: boolean; - /** 备注 */ - notes: string | null; - /** 创建时间 */ - created_at: Date; - /** 更新时间 */ - updated_at: Date; -} - -/** exchange_config 插入类型 */ -export interface ExchangeConfigInsert { - exchange: Exchange; - rest_url?: string | null; - ws_url?: string | null; - ws_ping_interval_ms?: number; - rate_limit_per_sec?: number; - max_reconnect_attempts?: number; - reconnect_delay_ms?: number; - enabled?: boolean; - notes?: string | null; -} - -// ============================================================ -// 5. 全局应用配置 — app_config -// ============================================================ - -/** app_config 表完整行类型 */ -export interface AppConfigRow { - /** 自增主键 */ - id: number; - /** 配置键 */ - key: string; - /** 配置值(统一存储为字符串) */ - value: string; - /** 说明 */ - description: string | null; - /** 更新时间 */ - updated_at: Date; -} - -/** 已知的 app_config 键名 */ -export type AppConfigKey = - | "batch_size" - | "flush_interval_ms" - | "log_level" - | "redis_publish_enabled"; - -// ============================================================ -// 6. 业务聚合类型 -// ============================================================ - -/** - * 唯一标识一个 K 线流 - * 对应 klines / monitored_symbols 的 (exchange, symbol, interval) 组合 - */ -export interface StreamKey { - exchange: Exchange; - symbol: string; - interval: KlineInterval; -} - -/** - * 采集服务启动时加载的完整订阅配置 - * = monitored_symbols JOIN exchange_config - */ -export interface StreamSubscription { - /** 流标识 */ - streamKey: StreamKey; - /** 优先级 */ - priority: number; - /** 连接配置 */ - exchangeConfig: ExchangeConfigRow; -} diff --git a/data/db/validators.ts b/data/db/validators.ts deleted file mode 100644 index 35c6c39..0000000 --- a/data/db/validators.ts +++ /dev/null @@ -1,245 +0,0 @@ -// ============================================================ -// schema/validators.ts — Zod 运行时校验 Schema -// ============================================================ -// 用途: -// 1. WebSocket 行情数据到达后校验字段完整性再入库 -// 2. 配置文件 / 环境变量加载后类型收窄 -// 3. API 输入参数校验 -// -// 依赖:zod ^4.x(已包含在 data/package.json) -// ============================================================ - -import { z } from "zod"; - -// ============================================================ -// 基础标量 Schema -// ============================================================ - -/** 交易所枚举 */ -export const ExchangeSchema = z.enum(["binance", "okx", "bybit"]); -export type Exchange = z.infer; - -/** K 线周期枚举 */ -export const KlineIntervalSchema = z.enum([ - "1m", - "5m", - "15m", - "1h", - "4h", - "1d", -]); -export type KlineInterval = z.infer; - -/** 日志级别 */ -export const LogLevelSchema = z.enum([ - "trace", - "debug", - "info", - "warn", - "error", - "fatal", -]); - -/** 交易对格式:大写字母 + 大写字母(如 BTCUSDT),3-12 字符 */ -export const SymbolSchema = z - .string() - .regex(/^[A-Z0-9]{4,14}$/, "交易对格式无效,示例:BTCUSDT"); - -/** - * NUMERIC(20,8) 数值字符串 - * pg 驱动默认以字符串返回 NUMERIC 以保留精度 - */ -export const NumericStringSchema = z - .string() - .regex(/^-?\d+(\.\d+)?$/, "期望 NUMERIC 字符串"); - -// ============================================================ -// 1. Kline 数据校验 — klines 表 -// ============================================================ - -/** WebSocket 原始 OHLCV 消息校验(单条 K 线,入库前) */ -export const KlineRawSchema = z.object({ - /** K 线开盘时间(UTC),Unix 毫秒时间戳 */ - time: z.number().int().positive(), - /** 交易所 */ - exchange: ExchangeSchema, - /** 交易对 */ - symbol: SymbolSchema, - /** 周期 */ - interval: KlineIntervalSchema, - /** 开盘价 */ - open: NumericStringSchema, - /** 最高价 */ - high: NumericStringSchema, - /** 最低价 */ - low: NumericStringSchema, - /** 收盘价 */ - close: NumericStringSchema, - /** 成交量 */ - volume: NumericStringSchema, - /** 成交额(可选) */ - quote_volume: NumericStringSchema.optional().default("0"), - /** 主动买入量(可选) */ - taker_buy_base_vol: NumericStringSchema.optional().default("0"), - /** 主动买入额(可选) */ - taker_buy_quote_vol: NumericStringSchema.optional().default("0"), - /** 成交笔数(可选) */ - trade_count: z.number().int().nonnegative().optional().default(0), - /** K 线是否闭合 */ - is_closed: z.boolean().optional().default(true), -}); - -export type KlineRaw = z.infer; - -/** 批量 K 线消息校验(WebSocket 可能一次推送多根) */ -export const KlineBatchSchema = z.array(KlineRawSchema).min(1); - -export type KlineBatch = z.infer; - -// ============================================================ -// 2. 监控交易对配置校验 — monitored_symbols -// ============================================================ - -/** 插入监控标的 */ -export const MonitoredSymbolInsertSchema = z.object({ - exchange: ExchangeSchema, - symbol: SymbolSchema, - interval: KlineIntervalSchema, - enabled: z.boolean().optional().default(true), - /** 优先级 0-32767(SMALLINT 范围) */ - priority: z - .number() - .int() - .min(0) - .max(32767) - .optional() - .default(0), - label: z.string().max(200).nullable().optional().default(null), - notes: z.string().max(1000).nullable().optional().default(null), -}); - -export type MonitoredSymbolInsert = z.infer< - typeof MonitoredSymbolInsertSchema ->; - -/** 更新监控标的 */ -export const MonitoredSymbolUpdateSchema = z.object({ - enabled: z.boolean().optional(), - priority: z.number().int().min(0).max(32767).optional(), - label: z.string().max(200).nullable().optional(), - notes: z.string().max(1000).nullable().optional(), -}); - -export type MonitoredSymbolUpdate = z.infer< - typeof MonitoredSymbolUpdateSchema ->; - -// ============================================================ -// 3. 交易所连接配置校验 — exchange_config -// ============================================================ - -/** 交易所连接配置输入 */ -export const ExchangeConfigInsertSchema = z.object({ - exchange: ExchangeSchema, - rest_url: z.string().url().nullable().optional().default(null), - ws_url: z.string().url().nullable().optional().default(null), - ws_ping_interval_ms: z - .number() - .int() - .min(5000) - .max(300000) - .optional() - .default(30000), - rate_limit_per_sec: z.number().positive().max(100).optional().default(20), - max_reconnect_attempts: z - .number() - .int() - .min(0) - .max(100) - .optional() - .default(10), - reconnect_delay_ms: z - .number() - .int() - .min(100) - .max(60000) - .optional() - .default(3000), - enabled: z.boolean().optional().default(true), - notes: z.string().max(500).nullable().optional().default(null), -}); - -export type ExchangeConfigInsert = z.infer< - typeof ExchangeConfigInsertSchema ->; - -// ============================================================ -// 4. 流标识校验 — StreamKey -// ============================================================ - -/** (exchange, symbol, interval) 三元组 */ -export const StreamKeySchema = z.object({ - exchange: ExchangeSchema, - symbol: SymbolSchema, - interval: KlineIntervalSchema, -}); - -export type StreamKey = z.infer; - -// ============================================================ -// 5. 环境变量 / 配置校验 -// ============================================================ - -/** .env 环境变量 schema */ -export const EnvConfigSchema = z.object({ - /** 逗号分隔的交易对列表 */ - SYMBOLS: z - .string() - .optional() - .default("BTCUSDT,ETHUSDT"), - DB_HOST: z.string().optional().default("localhost"), - DB_PORT: z.coerce.number().int().positive().optional().default(5432), - DB_NAME: z.string().optional().default("trade"), - DB_USER: z.string().optional().default("trader"), - DB_PASSWORD: z.string().optional().default("changeme"), - REDIS_URL: z.string().url().optional().default("redis://localhost:6379"), - REDIS_PUBLISH_ENABLED: z - .enum(["true", "false"]) - .optional() - .default("true"), - BATCH_SIZE: z.coerce.number().int().positive().optional().default(500), - FLUSH_INTERVAL_MS: z.coerce - .number() - .int() - .positive() - .optional() - .default(1000), - /** WebSocket 断线重连延迟基数(毫秒) */ - WS_RECONNECT_DELAY_MS: z.coerce - .number() - .int() - .positive() - .optional() - .default(3000), - /** WebSocket 心跳间隔(毫秒) */ - WS_PING_INTERVAL_MS: z.coerce - .number() - .int() - .positive() - .optional() - .default(30000), - /** WebSocket 最大重连次数 */ - WS_MAX_RECONNECT_ATTEMPTS: z.coerce - .number() - .int() - .nonnegative() - .optional() - .default(10), - LOG_LEVEL: LogLevelSchema.optional().default("info"), - NODE_ENV: z - .enum(["development", "production", "test"]) - .optional() - .default("development"), -}); - -export type EnvConfig = z.infer; diff --git a/data/exchanges/base.ts b/data/exchanges/base.ts new file mode 100644 index 0000000..ddf1c19 --- /dev/null +++ b/data/exchanges/base.ts @@ -0,0 +1,186 @@ +// ============================================================ +// base.ts — 交易所适配器抽象基类 +// ============================================================ +// 所有交易所适配器(Binance / OKX / Bybit ...)继承此类, +// 复用指数退避重连、连接状态管理、限流等通用逻辑。 +// +// 子类只需实现: +// - connect() — 建立 WebSocket/REST 连接 +// - disconnect() — 断开连接并清理资源 +// - subscribeTicker() / subscribeTrade() / subscribeOrderbook() +// - fetchKlines() — REST 历史 K 线补拉 +// - fetchMarkets() — 交易对元数据拉取 +// ============================================================ + +import { Subject, type Observable } from "rxjs"; +import { logger } from "../utils/logger"; +import type { + MarketDataFeed, + Ticker, + Trade, + OrderBook, + Kline, + KlineInterval, + MarketInfo, + ConnectionState, + AdapterConfig, +} from "./types"; +import { DEFAULT_ADAPTER_CONFIG } from "./types"; + +// ============================================================ +// 工具:异步 sleep +// ============================================================ + +/** 返回一个在 ms 毫秒后 resolve 的 Promise */ +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +// ============================================================ +// BaseExchangeAdapter +// ============================================================ + +export abstract class BaseExchangeAdapter implements MarketDataFeed { + /** 交易所标识(子类必须覆盖) */ + abstract readonly exchange: string; + + /** 适配器配置(可在子类构造函数中覆盖默认值) */ + protected readonly config: AdapterConfig; + + /** 当前连接状态 */ + protected _connectionState: ConnectionState = "disconnected"; + + /** 当前重连尝试次数(成功连接后重置) */ + protected reconnectAttempt = 0; + + /** Subject 清理注册表 —— disconnect 时统一 complete */ + protected activeSubjects = new Set>(); + + // ============================================================ + // 构造函数 + // ============================================================ + + constructor(config: Partial = {}) { + this.config = { ...DEFAULT_ADAPTER_CONFIG, ...config }; + } + + // ============================================================ + // 连接状态(只读暴露) + // ============================================================ + + get connectionState(): ConnectionState { + return this._connectionState; + } + + /** 更新连接状态并记录日志 */ + protected setConnectionState(state: ConnectionState): void { + const prev = this._connectionState; + this._connectionState = state; + if (prev !== state) { + logger.info( + { exchange: this.exchange, from: prev, to: state }, + `[${this.exchange}] connection state: ${prev} → ${state}`, + ); + } + } + + // ============================================================ + // 指数退避重连(所有子类复用) + // ============================================================ + + /** + * 执行指数退避重连。 + * + * 延迟公式:delay = baseDelay × 2^min(attempt, 5) + * - attempt=0: 3s + * - attempt=1: 6s + * - attempt=2: 12s + * - attempt=5: 96s(之后不再翻倍) + * + * 超过 maxReconnectAttempts 后抛出错误。 + * + * @throws 达到最大重试次数后抛出 + */ + protected async reconnect(): Promise { + const { reconnectBaseDelayMs: baseDelay, maxReconnectAttempts } = this.config; + + if (this.reconnectAttempt >= maxReconnectAttempts) { + this.setConnectionState("error"); + throw new Error( + `[${this.exchange}] 重连失败:已达最大重试次数 (${maxReconnectAttempts})`, + ); + } + + const cappedAttempt = Math.min(this.reconnectAttempt, 5); + const delay = baseDelay * Math.pow(2, cappedAttempt); + + logger.warn( + { + exchange: this.exchange, + attempt: this.reconnectAttempt + 1, + maxAttempts: maxReconnectAttempts, + delayMs: delay, + }, + `[${this.exchange}] WebSocket 重连中...`, + ); + + await sleep(delay); + + this.reconnectAttempt++; + this.setConnectionState("connecting"); + await this.connect(); + } + + /** 成功连接后重置重连计数器 */ + protected resetReconnectAttempts(): void { + if (this.reconnectAttempt > 0) { + logger.info( + { exchange: this.exchange, attempts: this.reconnectAttempt }, + `[${this.exchange}] 重连成功,计数器重置`, + ); + } + this.reconnectAttempt = 0; + } + + // ============================================================ + // Subject 管理工具 + // ============================================================ + + /** + * 创建一个受管理的 Subject,disconnect 时自动 complete。 + * 子类在 subscribe* 方法中使用此工具创建 Subject。 + */ + protected createManagedSubject(): Subject { + const subject = new Subject(); + this.activeSubjects.add(subject as Subject); + return subject; + } + + /** 完成所有受管理的 Subject(disconnect 时调用) */ + protected completeAllSubjects(): void { + for (const subject of this.activeSubjects) { + subject.complete(); + } + this.activeSubjects.clear(); + } + + // ============================================================ + // 抽象方法 —— 子类必须实现 + // ============================================================ + + abstract connect(): Promise; + abstract disconnect(): Promise; + abstract subscribeTicker(symbols: string[]): Observable; + abstract subscribeTrade(symbols: string[]): Observable; + abstract subscribeOrderbook(symbol: string, depth?: number): Observable; + abstract fetchKlines( + symbol: string, + interval: KlineInterval, + startTime: number, + endTime: number, + limit?: number, + ): Promise; + abstract fetchMarkets(): Promise; +} + +export default BaseExchangeAdapter; diff --git a/data/exchanges/binance.ts b/data/exchanges/binance.ts index f474a9c..923629e 100644 --- a/data/exchanges/binance.ts +++ b/data/exchanges/binance.ts @@ -1,395 +1,782 @@ // ============================================================ -// exchanges/binance.ts — 通用交易所 WebSocket 行情采集类 +// binance.ts — Binance 交易所适配器 // ============================================================ -// 基于 ccxt.pro 实现,不限于 Binance,支持任意 ccxt 支持的交易所。 -// 构造即启动:传入交易所 ID + 交易对列表,自动开始 WebSocket 监听。 +// 基于 Binance 官方 SDK(binance@3.x)实现 MarketDataFeed 接口。 // -// 使用方式: -// import { ExchangeWs } from "./exchanges/binance"; -// const ws = new ExchangeWs({ -// exchangeId: "binance", -// symbols: ["BTCUSDT", "ETHUSDT"], -// interval: "1m", -// }); -// ws.on("kline", (data) => console.log(data)); +// WebSocket:使用 SDK 内置 WebsocketClient,自动处理多路复用、 +// 断线重连、心跳保活。通过 formattedMessage 事件接收已解析的 +// 类型化行情数据,转换为本系统标准化结构后通过 RxJS Subject 发布。 +// +// REST:使用 SDK 内置 MainClient(Spot),用于: +// - fetchKlines() 历史 K 线补拉 +// - fetchMarkets() 交易对元数据(用于自动注册到 trading_pairs 表) +// +// ============================================================ +// 风险提示: +// - Binance WebSocket 单连接最多订阅 1024 个 stream, +// 超出需拆分多连接(SDK 自动处理) +// - 生产环境建议使用 Binance 的 combined streams 合并请求 +// - REST API 限频:1200 请求/分钟(权重制),fetchKlines 权重 2 // ============================================================ -import ccxt from "ccxt"; -import { EventEmitter } from "node:events"; - +import { Subject, type Observable } from "rxjs"; +import { + WebsocketClient, + MainClient, + type WsMessageKlineFormatted, + type WsMessageTradeFormatted, + type WsMessage24hrTickerFormatted, + type WsMessageBookTickerEventFormatted, + type WsMessagePartialBookDepthEventFormatted, + type WsFormattedMessage, +} from "binance"; +import type { Kline as BinanceRestKline } from "binance"; +import { BaseExchangeAdapter } from "./base"; +import { logger } from "../utils/logger"; import type { - KlineWsData, - ExchangeWsConfig, - WsConnectionState, + Ticker, + Trade, + OrderBook, + Kline, + KlineInterval, + MarketInfo, + AdapterConfig, + BinanceRawKline, } from "./types"; +import { KLINE_INTERVAL_MS } from "./types"; // ============================================================ -// 内部类型:ccxt pro 交易所实例的最小接口 -// ============================================================ -// 避免直接依赖 ccxt 内部类型(不同版本导出签名差异大), -// 仅声明本类实际调用的方法签名。 - -/** ccxt OHLCV 数组:[timestamp_ms, open, high, low, close, volume] */ -type OHLCVCandle = [number, number, number, number, number, number]; - -/** ccxt.pro 交易所实例的最小接口 */ -interface IProExchange { - watchOHLCV(symbol: string, timeframe: string): Promise; - close(): Promise; -} - -// ============================================================ -// 类型声明:为 EventEmitter 添加严格的事件签名 +// Binance K 线周期 ← → 本系统 K 线周期映射 // ============================================================ -export declare interface ExchangeWs { - /** 新 K 线数据到达(含实时更新和闭合 K 线) */ - on(event: "kline", listener: (data: KlineWsData) => void): this; - /** WebSocket 错误(非致命,单 symbol 出错不影响其他) */ - on(event: "error", listener: (error: ExchangeWsError) => void): this; - /** 连接状态变更 */ - on( - event: "stateChange", - listener: (state: WsConnectionState, exchangeId: string) => void, - ): this; - /** 单个 symbol 的 watch 循环已启动 */ - on( - event: "symbolReady", - listener: (symbol: string, interval: string) => void, - ): this; - /** 所有 symbols 已进入监听状态 */ - on(event: "ready", listener: () => void): this; -} +/** + * Binance SDK 支持的 K 线周期(比本系统更多)。 + * 本系统仅使用其中的子集,其余周期由 pipeline 合成。 + */ +type BinanceKlineInterval = + | "1m" + | "5m" + | "15m" + | "30m" + | "1h" + | "4h" + | "1d" + | "1w"; -/** 带 symbol 上下文的错误类型 */ -export class ExchangeWsError extends Error { - symbol: string; - exchangeId: string; - constructor(message: string, exchangeId: string, symbol: string) { - super(message); - this.name = "ExchangeWsError"; - this.exchangeId = exchangeId; - this.symbol = symbol; - } -} +/** 本系统 KlineInterval → Binance SDK KlineInterval(1:1 子集映射) */ +const INTERVAL_TO_BINANCE: Record = { + "1m": "1m", + "5m": "5m", + "15m": "15m", + "30m": "30m", + "1h": "1h", + "4h": "4h", + "1d": "1d", + "1w": "1w", +}; // ============================================================ -// ExchangeWs — 主类 +// 默认适配器配置(Binance 专用覆盖) // ============================================================ -export class ExchangeWs extends EventEmitter { - // ---- 配置 ---- - private readonly exchangeId: string; - private readonly symbols: string[]; - private readonly interval: string; - private readonly ccxtOptions: Record; +const DEFAULT_BINANCE_CONFIG: AdapterConfig = { + reconnectBaseDelayMs: 3000, + maxReconnectAttempts: 10, + /** Binance REST API 权重制限频,保守设为 250ms */ + restRateLimitMs: 250, +}; - // ---- 运行时状态 ---- - private exchange: IProExchange | null = null; - private state: WsConnectionState = "idle"; - private abortController: AbortController | null = null; - /** 每个 symbol 一条 watch 循环 */ - private watchTasks: Promise[] = []; - /** 已成功启动监听的 symbol 集合 */ - private readonly readySymbols = new Set(); +// ============================================================ +// BinanceAdapter +// ============================================================ + +export class BinanceAdapter extends BaseExchangeAdapter { + readonly exchange = "binance"; + + // ---------------------------------------------------------- + // SDK 客户端实例 + // ---------------------------------------------------------- + + /** Binance WebSocket 客户端(内置多路复用 + 自动重连) */ + private wsClient!: WebsocketClient; + + /** Binance REST 客户端(Spot) */ + private restClient!: MainClient; + + // ---------------------------------------------------------- + // RxJS Subject —— 按事件类型分频道发布 + // ---------------------------------------------------------- + + /** 24h Ticker 流(合并所有已订阅 symbol) */ + private tickerSubject!: Subject; + + /** 逐笔成交流(合并所有已订阅 symbol) */ + private tradeSubject!: Subject; + + /** 订单簿深度流(合并所有已订阅 symbol) */ + private orderbookSubjects = new Map>(); + + // ---------------------------------------------------------- + // 订阅追踪 + // ---------------------------------------------------------- + + /** 当前已订阅的 ticker symbol 集合 */ + private subscribedTickerSymbols = new Set(); + + /** 当前已订阅的 trade symbol 集合 */ + private subscribedTradeSymbols = new Set(); + + /** 当前已订阅的 orderbook symbol → depth 映射 */ + private subscribedOrderbookDepths = new Map(); + + /** 防止重复 REST 请求的节流 Map(symbol:interval → lastFetchTime) */ + private lastRestFetch = new Map(); // ============================================================ - // 构造器:传入参数,自动启动 WebSocket 监听 + // 构造函数 + // ============================================================ + + constructor(config: Partial = {}) { + super({ ...DEFAULT_BINANCE_CONFIG, ...config }); + } + + // ============================================================ + // 连接管理 // ============================================================ /** - * @param config.exchangeId - 交易所 ID(binance / okx / bybit / …) - * @param config.symbols - 要订阅的交易对列表 - * @param config.interval - K 线周期,默认 '1m' - * @param config.ccxtOptions - 传递给 ccxt.pro 交易所构造器的额外选项 + * 建立 WebSocket 连接并注册事件监听。 * - * 构造完成后 WebSocket 连接立即在后台启动, - * 监听 'ready' 事件确认全部就绪,或 'kline' 事件接收数据。 + * Binance SDK 的 WebsocketClient 在首次 subscribe() 时自动建连, + * 此处主动调用 connectPublic() 预热连接并注册 formattedMessage 监听。 */ - constructor(config: ExchangeWsConfig) { - super(); - - // 参数校验 - if (!config.symbols || config.symbols.length === 0) { - throw new Error("symbols 不能为空"); - } - - this.exchangeId = config.exchangeId; - this.symbols = [...config.symbols]; // 防御性拷贝 - this.interval = config.interval ?? "1m"; - this.ccxtOptions = config.ccxtOptions ?? {}; - - // 构造即启动(fire-and-forget,错误通过 'error' 事件抛出) - this.start().catch((err) => { - this.emit( - "error", - new ExchangeWsError( - `启动失败: ${(err as Error).message}`, - this.exchangeId, - "ALL", - ), - ); - }); - } - - // ============================================================ - // 公开方法 - // ============================================================ - - /** 获取当前连接状态 */ - getState(): WsConnectionState { - return this.state; - } - - /** 获取已就绪的 symbol 列表 */ - getReadySymbols(): string[] { - return [...this.readySymbols]; - } - - /** - * 动态添加交易对(已监听的 symbol 会被忽略) - * 可在运行时增删监控标的,无需重启整个连接 - */ - async addSymbols(newSymbols: string[]): Promise { - const toAdd = newSymbols.filter((s) => !this.readySymbols.has(s)); - if (toAdd.length === 0) { + async connect(): Promise { + if (this._connectionState === "connected") { + logger.debug(`[binance] 已连接,跳过重复 connect`); return; - }; + } - for (const symbol of toAdd) { - this.symbols.push(symbol); - const task = this.watchSymbol(symbol); - this.watchTasks.push(task); + this.setConnectionState("connecting"); + + try { + // 初始化 WebSocket 客户端 + this.wsClient = new WebsocketClient({ + // 生产环境使用 Binance 主网 + // useTestnet: false 为默认值 + }); + + // 初始化 REST 客户端(公开接口无需 API Key) + this.restClient = new MainClient(); + + // 注册 formattedMessage 事件 —— SDK 将原始 JSON 解析为类型化对象 + this.wsClient.on("formattedMessage", this.onFormattedMessage.bind(this)); + + // 注册重连事件(利用 SDK 内置的自动重连) + this.wsClient.on("reconnecting", (evt) => { + logger.warn( + { wsKey: evt.wsKey }, + `[binance] WebSocket 重连中...`, + ); + this.setConnectionState("connecting"); + }); + + this.wsClient.on("reconnected", (evt) => { + logger.info( + { wsKey: evt.wsKey, wsUrl: evt.wsUrl }, + `[binance] WebSocket 重连成功`, + ); + this.setConnectionState("connected"); + this.resetReconnectAttempts(); + }); + + this.wsClient.on("close", (evt) => { + logger.warn( + { wsKey: evt.wsKey }, + `[binance] WebSocket 连接关闭`, + ); + // 如果之前是已连接状态,SDK 会自动重连 + if (this._connectionState === "connected") { + this.setConnectionState("connecting"); + } + }); + + // 预热连接(SDK 连接到 spot 公开行情端点) + await this.wsClient.connectPublic(); + this.setConnectionState("connected"); + this.resetReconnectAttempts(); + + logger.info(`[binance] WebSocket 连接已建立`); + + } catch (err) { + this.setConnectionState("error"); + logger.error({ err }, `[binance] 连接失败`); + throw err; } } /** - * 停止 WebSocket 监听并释放资源 - * 调用后实例不可复用,需重新 new + * 断开连接并清理资源。 + * + * 1. 取消所有 WebSocket 订阅 + * 2. 关闭 WebSocket 客户端 + * 3. Complete 所有 RxJS Subject */ - async close(): Promise { - // 1. 取消所有 watch 循环 - this.abortController?.abort(); + async disconnect(): Promise { + logger.info(`[binance] 断开连接...`); - // 2. 等待所有 watch 循环退出 - await Promise.allSettled(this.watchTasks); - - // 3. 关闭 ccxt 交易所连接 - if (this.exchange) { - try { - await this.exchange.close(); - } catch { - // 忽略关闭时的错误 - } - this.exchange = null; - } - - this.setState("disconnected"); - this.removeAllListeners(); - } - - // ============================================================ - // 内部方法 - // ============================================================ - - /** 异步启动:初始化 ccxt 实例并为每个 symbol 启动 watch 循环 */ - private async start(): Promise { - this.abortController = new AbortController(); - this.setState("connecting"); - - // 1. 创建 ccxt.pro 交易所实例 - this.exchange = this.createExchange(); - - // 2. 为每个 symbol 启动独立的 watch 循环 - this.watchTasks = this.symbols.map((symbol) => this.watchSymbol(symbol)); - - // 3. 等待任意 symbol 就绪(或全部失败) try { - await this.waitForAnyReady(); - this.setState("connected"); - this.emit("ready"); - } catch { - this.setState("error"); - this.emit( - "error", - new ExchangeWsError("所有 symbol 连接失败", this.exchangeId, "ALL"), - ); - } - } - - /** 创建 ccxt.pro 交易所实例 */ - private createExchange(): IProExchange { - // 动态获取 ccxt.pro[exchangeId] 构造器 - const ccxtAny = ccxt as unknown as Record; - const proNamespace = ccxtAny.pro as - | Record) => IProExchange> - | undefined; - - if (!proNamespace || typeof proNamespace !== "object") { - throw new Error( - "ccxt.pro 不可用,请确认 ccxt 版本 >= 4.0 且已安装 pro 支持", - ); - } - - const ProExchange = proNamespace[this.exchangeId]; - - if (!ProExchange) { - throw new Error( - `不支持的交易所: ${this.exchangeId},可用: ${Object.keys(proNamespace).join(", ")}`, - ); - } - - return new ProExchange({ - enableRateLimit: true, - ...this.ccxtOptions, - }); - } - - /** 单个 symbol 的 watch 循环 */ - private async watchSymbol(symbol: string): Promise { - const signal = this.abortController!.signal; - - // 带退避的重连循环 - let consecutiveErrors = 0; - const maxBackoff = 30000; // 最大退避 30s - - while (!signal.aborted) { - try { - // 检查是否已取消 - if (signal.aborted) break; - - // watchOHLCV 返回 Promise,在新数据到达时 resolve - // ccxt 返回格式:[timestamp_ms, open, high, low, close, volume] - const candles = await this.exchange!.watchOHLCV( - symbol, - this.interval, + // 取消所有订阅 + if (this.wsClient && this.subscribedTradeSymbols.size > 0) { + const topics = [...this.subscribedTradeSymbols].map( + (s) => `${s.toLowerCase()}@trade`, ); + await this.wsClient.unsubscribe(topics, "main"); + } + if (this.wsClient && this.subscribedTickerSymbols.size > 0) { + const topics = [...this.subscribedTickerSymbols].map( + (s) => `${s.toLowerCase()}@ticker`, + ); + await this.wsClient.unsubscribe(topics, "main"); + } + if (this.wsClient && this.subscribedOrderbookDepths.size > 0) { + for (const [symbol, depth] of this.subscribedOrderbookDepths) { + const topic = `${symbol.toLowerCase()}@depth${depth}@100ms`; + await this.wsClient.unsubscribe([topic], "main"); + } + } + } catch (err) { + logger.warn({ err }, `[binance] 取消订阅时出错(忽略)`); + } - // 重置错误计数(成功获取数据) - consecutiveErrors = 0; + // 关闭 WS 客户端所有连接(SDK 自动关闭底层 WebSocket) + try { + this.wsClient?.closeAll(); + } catch { + // 忽略关闭错误 + } - // 标记该 symbol 已就绪 - if (!this.readySymbols.has(symbol)) { - this.readySymbols.add(symbol); - this.emit("symbolReady", symbol, this.interval); + // Complete 所有 Subject + this.tickerSubject?.complete(); + this.tradeSubject?.complete(); + for (const subject of this.orderbookSubjects.values()) { + subject.complete(); + } + this.orderbookSubjects.clear(); + + this.subscribedTickerSymbols.clear(); + this.subscribedTradeSymbols.clear(); + this.subscribedOrderbookDepths.clear(); + + this.setConnectionState("disconnected"); + logger.info(`[binance] 已断开连接`); + } + + // ============================================================ + // 订阅 Ticker(24h 滚动统计) + // ============================================================ + + subscribeTicker(symbols: string[]): Observable { + if (!this.tickerSubject) { + this.tickerSubject = this.createManagedSubject(); + } + + const newSymbols = symbols.filter( + (s) => !this.subscribedTickerSymbols.has(s), + ); + + if (newSymbols.length > 0 && this._connectionState === "connected") { + const topics = newSymbols.map( + (s) => `${s.toLowerCase()}@ticker`, + ); + this.wsClient.subscribe(topics, "main").catch((err) => { + logger.error({ err, symbols: newSymbols }, `[binance] 订阅 ticker 失败`); + }); + for (const s of newSymbols) { + this.subscribedTickerSymbols.add(s); + } + logger.info( + { count: newSymbols.length, symbols: newSymbols }, + `[binance] 订阅 ticker`, + ); + } + + return this.tickerSubject.asObservable(); + } + + // ============================================================ + // 订阅逐笔成交 + // ============================================================ + + subscribeTrade(symbols: string[]): Observable { + if (!this.tradeSubject) { + this.tradeSubject = this.createManagedSubject(); + } + + const newSymbols = symbols.filter( + (s) => !this.subscribedTradeSymbols.has(s), + ); + + if (newSymbols.length > 0 && this._connectionState === "connected") { + const topics = newSymbols.map( + (s) => `${s.toLowerCase()}@trade`, + ); + this.wsClient.subscribe(topics, "main").catch((err) => { + logger.error({ err, symbols: newSymbols }, `[binance] 订阅 trade 失败`); + }); + for (const s of newSymbols) { + this.subscribedTradeSymbols.add(s); + } + logger.info( + { count: newSymbols.length, symbols: newSymbols }, + `[binance] 订阅 trade`, + ); + } + + return this.tradeSubject.asObservable(); + } + + // ============================================================ + // 订阅订单簿深度 + // ============================================================ + + subscribeOrderbook(symbol: string, depth: number = 20): Observable { + const key = `${symbol}@${depth}`; + let subject = this.orderbookSubjects.get(key); + if (subject) { + return subject.asObservable(); + } + + subject = this.createManagedSubject(); + this.orderbookSubjects.set(key, subject); + + if (this._connectionState === "connected") { + const topic = `${symbol.toLowerCase()}@depth${depth}@100ms`; + this.wsClient.subscribe([topic], "main").catch((err) => { + logger.error({ err, symbol, depth }, `[binance] 订阅 orderbook 失败`); + }); + this.subscribedOrderbookDepths.set(symbol, depth); + logger.info({ symbol, depth }, `[binance] 订阅 orderbook`); + } + + return subject.asObservable(); + } + + // ============================================================ + // REST:拉取历史 K 线(补缺失数据 / 回测) + // ============================================================ + + /** + * 通过 Binance REST API 拉取历史 K 线。 + * + * Binance 限制: + * - 单次最多 1000 条(默认 500) + * - 权重 2(1200 权重/分钟 → 600 次请求/分钟) + * - 自动分页逻辑:如果时间跨度超过 limit 条,自动多次请求拼接 + * + * @param symbol - 交易对(如 BTCUSDT) + * @param interval - K 线周期 + * @param startTime - 起始时间(Unix ms) + * @param endTime - 结束时间(Unix ms) + * @param limit - 单次最大条数(默认 500,最大 1000) + */ + async fetchKlines( + symbol: string, + interval: KlineInterval, + startTime: number, + endTime: number, + limit: number = 500, + ): Promise { + const binanceInterval = INTERVAL_TO_BINANCE[interval]; + const intervalMs = KLINE_INTERVAL_MS[interval]; + const maxLimit = Math.min(limit, 1000); // Binance 硬限制 1000 + + const allKlines: Kline[] = []; + let currentStart = startTime; + + // 自动分页:如果时间跨度超过 maxLimit 条 K 线,分批拉取 + while (currentStart < endTime) { + // 速率限制(保守节流) + const throttleKey = `${symbol}:${interval}`; + const lastFetch = this.lastRestFetch.get(throttleKey) ?? 0; + const elapsed = Date.now() - lastFetch; + if (elapsed < this.config.restRateLimitMs) { + await new Promise((r) => + setTimeout(r, this.config.restRateLimitMs - elapsed), + ); + } + this.lastRestFetch.set(throttleKey, Date.now()); + + try { + const rawKlines = await this.restClient.getKlines({ + symbol, + interval: binanceInterval, + startTime: currentStart, + endTime, + limit: maxLimit, + }); + + if (!rawKlines || rawKlines.length === 0) { + break; // 无更多数据 } - // 处理返回的 K 线数据 - this.processCandles(symbol, candles); - } catch (err) { - if (signal.aborted) break; - - consecutiveErrors++; - const delay = Math.min(1000 * 2 ** consecutiveErrors, maxBackoff); - - this.emit( - "error", - new ExchangeWsError( - `[${symbol}] ${(err as Error).message},${delay / 1000}s 后重试 (第 ${consecutiveErrors} 次)`, - this.exchangeId, - symbol, - ), + // 转换 Binance REST K 线 → 本系统标准化 K 线 + const converted = rawKlines.map((k) => + this.convertRestKline(k, symbol, interval), ); + allKlines.push(...converted); - // 指数退避等待(可被 abort 中断) - await this.sleep(delay, signal); + // Binance REST K 线格式:[openTime, open, high, low, close, volume, closeTime, ...] + // 最后一条的开盘时间 + interval 作为下一批的起点 + const lastKline = rawKlines[rawKlines.length - 1]!; + const lastOpenTime = (lastKline as number[])[0] as number; + currentStart = lastOpenTime + intervalMs; + + // 如果返回数量 < limit,说明已拉完 + if (rawKlines.length < maxLimit) { + break; + } + + } catch (err) { + logger.error( + { err, symbol, interval, currentStart, endTime }, + `[binance] fetchKlines 失败`, + ); + throw err; } } + + logger.debug( + { symbol, interval, count: allKlines.length, startTime, endTime }, + `[binance] fetchKlines 完成`, + ); + + return allKlines; } - /** 处理 ccxt watchOHLCV 返回的原始数据 */ - private processCandles(symbol: string, candles: OHLCVCandle[]): void { - // candles 是完整的 OHLCV 数组,最后一条是最新数据 - // ccxt 会返回增量更新,通常只包含新增的 candle - for (const candle of candles) { - // ccxt OHLCV 格式:[timestamp_ms, open, high, low, close, volume] - if (!Array.isArray(candle) || candle.length < 6) { - continue; - }; + // ============================================================ + // REST:拉取交易对元数据 + // ============================================================ - const data: KlineWsData = { - exchange: this.exchangeId, - symbol, - interval: this.interval, - time: candle[0], - open: candle[1], - high: candle[2], - low: candle[3], - close: candle[4], - volume: candle[5], - }; + /** + * 从 Binance 获取所有现货交易对信息,转换为本系统 MarketInfo 格式。 + * + * 用于自动注册到 trading_pairs 表,避免手动配置。 + */ + async fetchMarkets(): Promise { + logger.info(`[binance] 拉取交易对信息...`); - this.emit("kline", data); + try { + const exchangeInfo = await this.restClient.getExchangeInfo(); + + const markets: MarketInfo[] = []; + + for (const symbolInfo of exchangeInfo.symbols) { + // 仅保留状态为 TRADING 的现货交易对 + if (symbolInfo.status !== "TRADING") continue; + + const filters = symbolInfo.filters; + + // 从 filters 中提取交易规则 + let tickSize: string | undefined; + let stepSize: string | undefined; + let minQty: string | undefined; + let minNotional: string | undefined; + + for (const filter of filters) { + switch (filter.filterType) { + case "PRICE_FILTER": + tickSize = (filter as { tickSize: string }).tickSize; + break; + case "LOT_SIZE": + stepSize = (filter as { stepSize: string }).stepSize; + minQty = (filter as { minQty: string }).minQty; + break; + case "MIN_NOTIONAL": + case "NOTIONAL": + minNotional = (filter as { minNotional: string }).minNotional; + break; + } + } + + markets.push({ + symbol: symbolInfo.symbol, + baseAsset: symbolInfo.baseAsset, + quoteAsset: symbolInfo.quoteAsset, + pricePrecision: symbolInfo.quoteAssetPrecision, + quantityPrecision: symbolInfo.baseAssetPrecision, + minQty: minQty ? parseFloat(minQty) : undefined, + stepSize: stepSize ? parseFloat(stepSize) : undefined, + minNotional: minNotional ? parseFloat(minNotional) : undefined, + }); + } + + logger.info( + { count: markets.length }, + `[binance] 交易对信息拉取完成`, + ); + + return markets; + + } catch (err) { + logger.error({ err }, `[binance] fetchMarkets 失败`); + throw err; } } - /** 等待至少一个 symbol 就绪(或超时/全部失败) */ - private async waitForAnyReady(): Promise { - return new Promise((resolve, reject) => { - let resolved = false; + // ============================================================ + // 内部:formattedMessage 事件分发 + // ============================================================ - const onReady = () => { - if (!resolved) { - resolved = true; - cleanup(); - resolve(); + /** + * Binance SDK formattedMessage 回调。 + * + * SDK 已将原始 WebSocket JSON 解析为类型化事件对象。 + * WsFormattedMessage 是复杂联合类型(含单事件 + 事件数组), + * TypeScript 的判别联合在此处不够精确。内部使用 `as any` 绕过 + * 联合类型限制,按 eventType 字符串运行时路由。 + */ + private onFormattedMessage(msg: WsFormattedMessage): void { + try { + // 数组类型(如 !ticker@arr → WsMessage24hrTickerFormatted[]) + if (Array.isArray(msg)) { + for (const item of msg) { + this.routeByEventType(item as unknown as Record); } - }; - - const onError = (err: ExchangeWsError) => { - // 仅在所有 symbol 都失败时 reject - if (!resolved && err.symbol === "ALL") { - resolved = true; - cleanup(); - reject(err); - } - }; - - const cleanup = () => { - this.off("symbolReady", onReady); - this.off("error", onError); - }; - - this.once("symbolReady", onReady); - this.once("error", onError); - - // 超时保护(30 秒) - setTimeout(() => { - if (!resolved) { - resolved = true; - cleanup(); - reject( - new ExchangeWsError("连接超时(30s)", this.exchangeId, "ALL"), - ); - } - }, 30000); - }); - } - - /** 带 AbortSignal 的 sleep */ - private sleep(ms: number, signal: AbortSignal): Promise { - return new Promise((resolve) => { - if (signal.aborted) { - resolve(); return; } - const timer = setTimeout(resolve, ms); - signal.addEventListener( - "abort", - () => { - clearTimeout(timer); - resolve(); - }, - { once: true }, - ); - }); - } - /** 更新状态并发出事件 */ - private setState(newState: WsConnectionState): void { - if (this.state !== newState) { - this.state = newState; - this.emit("stateChange", newState, this.exchangeId); + this.routeByEventType(msg as unknown as Record); + } catch (err) { + const raw = msg as unknown as Record; + const eventType = String(raw["eventType"] ?? "unknown"); + logger.error( + { err, eventType }, + `[binance] 处理 formattedMessage 时出错`, + ); } } + + /** + * 按 eventType 运行时路由到对应 Subject。 + * + * 此处使用 unknown → Record 转换,因为 WsFormattedMessage + * 联合类型包含数组成员导致无法直接访问 eventType。 + */ + private routeByEventType(raw: Record): void { + const eventType = String(raw["eventType"] ?? ""); + if (!eventType) return; + + switch (eventType) { + case "24hrTicker": + case "!ticker@arr": + this.handleTickerMessage( + raw as unknown as WsMessage24hrTickerFormatted, + ); + break; + + case "trade": + this.handleTradeMessage( + raw as unknown as WsMessageTradeFormatted, + ); + break; + + case "bookTicker": + this.handleBookTickerMessage( + raw as unknown as WsMessageBookTickerEventFormatted, + ); + break; + + case "partialBookDepth": + this.handleOrderbookMessage( + raw as unknown as WsMessagePartialBookDepthEventFormatted, + ); + break; + + case "kline": + // K 线事件不在 adapter 层分发,由 pipeline 的 KlineSynthesizer 处理 + break; + + default: + // 忽略其他事件类型(用户数据流、账户更新等) + break; + } + } + + // ---------------------------------------------------------- + // 事件转换器:Binance → 本系统标准化类型 + // ---------------------------------------------------------- + + /** 24h Ticker → Ticker */ + private handleTickerMessage(msg: WsMessage24hrTickerFormatted): void { + if (!this.tickerSubject || this.tickerSubject.closed) return; + + const ticker: Ticker = { + exchange: "binance", + symbol: msg.symbol, + lastPrice: msg.currentClose, + openPrice: msg.open, + highPrice: msg.high, + lowPrice: msg.low, + volume: msg.baseAssetVolume, + quoteVolume: msg.quoteAssetVolume, + priceChange: msg.priceChange, + priceChangePercent: msg.priceChangePercent, + bidPrice: msg.bestBid, + bidQty: msg.bestBidQuantity, + askPrice: msg.bestAskPrice, + askQty: msg.bestAskQuantity, + eventTime: msg.eventTime, + closeTime: msg.closeTime, + }; + + this.tickerSubject.next(ticker); + } + + /** 逐笔成交 → Trade */ + private handleTradeMessage(msg: WsMessageTradeFormatted): void { + if (!this.tradeSubject || this.tradeSubject.closed) return; + + const trade: Trade = { + exchange: "binance", + symbol: msg.symbol, + price: msg.price, + amount: msg.quantity, + quoteAmount: msg.price * msg.quantity, + timestamp: msg.time, + isBuyerMaker: msg.maker, + tradeId: String(msg.tradeId), + }; + + this.tradeSubject.next(trade); + } + + /** BookTicker → Ticker(精简版,仅有最佳买卖价) */ + private handleBookTickerMessage(msg: WsMessageBookTickerEventFormatted): void { + // BookTicker 是 Ticker 的精简版,仅更新最佳买卖价 + // 如果有 tickerSubject,将其作为轻量 Ticker 推送 + if (!this.tickerSubject || this.tickerSubject.closed) return; + + const ticker: Ticker = { + exchange: "binance", + symbol: msg.symbol, + lastPrice: 0, // bookTicker 不含最新价 + openPrice: 0, + highPrice: 0, + lowPrice: 0, + volume: 0, + quoteVolume: 0, + priceChange: 0, + priceChangePercent: 0, + bidPrice: msg.bidPrice, + bidQty: msg.bidQty, + askPrice: msg.askPrice, + askQty: msg.askQty, + eventTime: msg.eventTime, + closeTime: 0, + }; + + this.tickerSubject.next(ticker); + } + + /** 订单簿深度快照 → OrderBook */ + private handleOrderbookMessage(msg: WsMessagePartialBookDepthEventFormatted): void { + // partialBookDepth 不含 symbol 字段(取决于 SDK 版本), + // 从 stream 名称或上下文推断 symbol。此处假设 SDK 已填充 symbol。 + const symbol = (msg as WsMessagePartialBookDepthEventFormatted & { symbol?: string }).symbol; + + if (!symbol) { + logger.warn(`[binance] 收到无 symbol 的 orderbook 消息,丢弃`); + return; + } + + // 查找匹配的 orderbook Subject(遍历所有已订阅 depth) + for (const [key, subject] of this.orderbookSubjects) { + const [subscribedSymbol] = key.split("@"); + if ( + subscribedSymbol?.toUpperCase() === symbol.toUpperCase() && + !subject.closed + ) { + const orderbook: OrderBook = { + exchange: "binance", + symbol: symbol.toUpperCase(), + bids: msg.bids.map( + ([price, qty]) => [parseFloat(String(price)), parseFloat(String(qty))] as [number, number], + ), + asks: msg.asks.map( + ([price, qty]) => [parseFloat(String(price)), parseFloat(String(qty))] as [number, number], + ), + lastUpdateId: msg.lastUpdateId, + eventTime: Date.now(), // partialBookDepth 不含 eventTime + }; + + subject.next(orderbook); + return; + } + } + } + + // ============================================================ + // 内部:REST K 线格式转换 + // ============================================================ + + /** + * 将 Binance REST K 线数组(元组)转换为本系统 Kline 对象。 + * + * Binance REST K 线格式: + * [ + * 0: openTime (ms), + * 1: open (string), + * 2: high (string), + * 3: low (string), + * 4: close (string), + * 5: volume (string), + * 6: closeTime (ms), + * 7: quoteVolume (string), + * 8: tradeCount (number), + * 9: takerBuyBaseVol (string), + * 10: takerBuyQuoteVol (string), + * 11: ignore (string) + * ] + */ + private convertRestKline( + raw: BinanceRestKline, + symbol: string, + interval: KlineInterval, + ): Kline { + // BinanceRestKline 是元组类型,按位置索引 + const arr = raw as unknown as [ + number, // 0: openTime + string, // 1: open + string, // 2: high + string, // 3: low + string, // 4: close + string, // 5: volume + number, // 6: closeTime + string, // 7: quoteVolume + number, // 8: tradeCount + string, // 9: takerBuyBaseVol + string, // 10: takerBuyQuoteVol + string, // 11: ignore + ]; + + return { + exchange: "binance", + symbol, + interval, + openTime: arr[0], + closeTime: arr[6], + open: parseFloat(arr[1]), + high: parseFloat(arr[2]), + low: parseFloat(arr[3]), + close: parseFloat(arr[4]), + volume: parseFloat(arr[5]), + quoteVolume: parseFloat(arr[7]), + takerBuyBaseVol: parseFloat(arr[9]), + takerBuyQuoteVol: parseFloat(arr[10]), + tradeCount: arr[8], + isClosed: true, // REST 返回的 K 线都是已闭合的 + }; + } } + +export default BinanceAdapter; diff --git a/data/exchanges/types.ts b/data/exchanges/types.ts index 87ed617..88d9173 100644 --- a/data/exchanges/types.ts +++ b/data/exchanges/types.ts @@ -1,45 +1,299 @@ // ============================================================ -// exchanges/types.ts — WebSocket 事件数据类型 +// types.ts — 统一行情数据类型定义与 MarketDataFeed 接口 +// ============================================================ +// 所有交易所适配器共享的数据结构和接口契约。 +// 适配器负责将交易所原生数据格式转换为以下标准化类型。 +// +// 设计原则: +// - 字段语义与 Binance/OKX/Bybit 通用概念对齐 +// - 时间戳统一使用 Unix 毫秒(number),便于排序和计算 +// - 价格/数量使用 number 类型(JavaScript 64-bit float), +// 对精度敏感场景(如 orderbook 快照)保留原始字符串 // ============================================================ -/** 由 WebSocket 推送的单根 K 线数据 */ -export interface KlineWsData { - /** 交易所标识 */ - exchange: string; - /** 交易对,如 BTCUSDT */ - symbol: string; - /** K 线周期,如 1m / 1h / 1d */ - interval: string; - /** K 线开盘时间(Unix 毫秒时间戳) */ - time: number; - /** 开盘价 */ - open: number; - /** 最高价 */ - high: number; - /** 最低价 */ - low: number; - /** 收盘价 */ - close: number; - /** 成交量(基准币种) */ - volume: number; +import type { Observable } from "rxjs"; + +// ============================================================ +// K 线周期 +// ============================================================ + +/** K 线周期枚举(与 kline.entity.ts 中 KlineInterval 保持一致) */ +export type KlineInterval = + | "1m" + | "5m" + | "15m" + | "30m" + | "1h" + | "4h" + | "1d" + | "1w"; + +/** K 线周期 → 毫秒数映射(用于时间桶计算) */ +export const KLINE_INTERVAL_MS: Record = { + "1m": 60_000, + "5m": 300_000, + "15m": 900_000, + "30m": 1_800_000, + "1h": 3_600_000, + "4h": 14_400_000, + "1d": 86_400_000, + "1w": 604_800_000, +}; + +// ============================================================ +// 标准化行情数据结构 +// ============================================================ + +/** 24 小时滚动 Ticker 统计 */ +export interface Ticker { + /** 交易所标识 */ + exchange: string; + /** 交易对符号(大写,如 BTCUSDT) */ + symbol: string; + /** 最新成交价 */ + lastPrice: number; + /** 24h 开盘价 */ + openPrice: number; + /** 24h 最高价 */ + highPrice: number; + /** 24h 最低价 */ + lowPrice: number; + /** 24h 成交量(base 币种) */ + volume: number; + /** 24h 成交额(quote 币种) */ + quoteVolume: number; + /** 24h 价格变化 */ + priceChange: number; + /** 24h 价格变化百分比(0.05 = 5%) */ + priceChangePercent: number; + /** 买一价 */ + bidPrice: number; + /** 买一量 */ + bidQty: number; + /** 卖一价 */ + askPrice: number; + /** 卖一量 */ + askQty: number; + /** 事件发生时间(Unix ms) */ + eventTime: number; + /** 交易所收盘时间(Unix ms,用于判断 K 线是否闭合) */ + closeTime: number; } -/** ExchangeWs 构造参数 */ -export interface ExchangeWsConfig { - /** 交易所 ID,ccxt 支持的所有交易所标识 */ - exchangeId: string; - /** 要订阅的交易对列表,如 ['BTCUSDT', 'ETHUSDT'] */ - symbols: string[]; - /** K 线周期,默认 '1m' */ - interval?: string; - /** 传递给 ccxt.pro 交易所实例的额外选项(如 agent、apiKey 等) */ - ccxtOptions?: Record; +/** 逐笔成交 */ +export interface Trade { + /** 交易所标识 */ + exchange: string; + /** 交易对符号 */ + symbol: string; + /** 成交价 */ + price: number; + /** 成交数量(base 币种) */ + amount: number; + /** 成交额(quote 币种 = price × amount) */ + quoteAmount: number; + /** 成交时间(Unix ms) */ + timestamp: number; + /** 买方是否为挂单方(true = 主动卖出 / taker sell) */ + isBuyerMaker: boolean; + /** 交易所成交 ID(可能为字符串,如 Binance tradeId 为 bigint) */ + tradeId: string; } -/** ExchangeWs 连接状态 */ -export type WsConnectionState = - | "idle" // 尚未启动 - | "connecting" // 正在连接 WebSocket - | "connected" // 已连接,正在接收数据 - | "disconnected" // 已断开 - | "error"; // 错误状态 +/** 订单簿深度快照 */ +export interface OrderBook { + /** 交易所标识 */ + exchange: string; + /** 交易对符号 */ + symbol: string; + /** 买单列表 [[price, qty], ...],按价格降序(买一在前) */ + bids: [number, number][]; + /** 卖单列表 [[price, qty], ...],按价格升序(卖一在前) */ + asks: [number, number][]; + /** 上次更新 ID */ + lastUpdateId: number; + /** 事件发生时间(Unix ms) */ + eventTime: number; +} + +/** 标准化 K 线(OHLCV) */ +export interface Kline { + /** 交易所标识 */ + exchange: string; + /** 交易对符号 */ + symbol: string; + /** K 线周期 */ + interval: KlineInterval; + /** 开盘时间(Unix ms) */ + openTime: number; + /** 收盘时间(Unix ms) */ + closeTime: number; + /** 开盘价 */ + open: number; + /** 最高价 */ + high: number; + /** 最低价 */ + low: number; + /** 收盘价 */ + close: number; + /** 成交量(base 币种) */ + volume: number; + /** 成交额(quote 币种) */ + quoteVolume: number; + /** 主动买入成交量(base 币种) */ + takerBuyBaseVol: number; + /** 主动买入成交额(quote 币种) */ + takerBuyQuoteVol: number; + /** 成交笔数 */ + tradeCount: number; + /** 该 K 线是否已关闭(不再更新) */ + isClosed: boolean; +} + +/** K 线增量更新(仅推送最新一根 OHLCV 变化) */ +export interface KlineDelta { + exchange: string; + symbol: string; + interval: KlineInterval; + openTime: number; + closeTime: number; + open: number; + high: number; + low: number; + close: number; + volume: number; + isClosed: boolean; +} + +// ============================================================ +// WebSocket 连接状态 +// ============================================================ + +/** 连接状态枚举 */ +export type ConnectionState = + | "disconnected" + | "connecting" + | "connected" + | "error"; + +// ============================================================ +// 适配器配置 +// ============================================================ + +/** 交易所适配器通用配置 */ +export interface AdapterConfig { + /** 指数退避重连基数(毫秒),默认 3000 */ + reconnectBaseDelayMs: number; + /** 最大重连次数,默认 10 */ + maxReconnectAttempts: number; + /** REST API 请求冷却时间(毫秒),默认 200 */ + restRateLimitMs: number; +} + +/** 默认适配器配置 */ +export const DEFAULT_ADAPTER_CONFIG: AdapterConfig = { + reconnectBaseDelayMs: 3000, + maxReconnectAttempts: 10, + restRateLimitMs: 200, +}; + +// ============================================================ +// MarketDataFeed 接口 —— 所有交易所适配器必须实现 +// ============================================================ + +/** + * 统一行情数据源接口。 + * + * 每个交易所适配器实现此接口,向上层管道暴露标准化数据流。 + * 使用 RxJS Observable 作为统一推送机制,pipeline 层可自由 + * 组合、过滤、分流各交易所数据。 + */ +export interface MarketDataFeed { + /** 交易所标识(如 "binance") */ + readonly exchange: string; + + /** 当前连接状态 */ + readonly connectionState: ConnectionState; + + /** 建立 WebSocket 连接 */ + connect(): Promise; + + /** 断开连接 */ + disconnect(): Promise; + + /** + * 订阅 24h 滚动 Ticker 流。 + * 每笔成交触发推送(Binance: @ticker)。 + */ + subscribeTicker(symbols: string[]): Observable; + + /** + * 订阅逐笔成交流。 + * 实时推送每笔撮合成交(Binance: @trade)。 + */ + subscribeTrade(symbols: string[]): Observable; + + /** + * 订阅订单簿深度。 + * depth 参数指定档位(如 5/10/20),默认 20。 + */ + subscribeOrderbook(symbol: string, depth?: number): Observable; + + /** + * REST 拉取历史 K 线(用于补齐缺失数据或回测)。 + * + * @param symbol - 交易对符号 + * @param interval - K 线周期 + * @param startTime - 起始时间(Unix ms) + * @param endTime - 结束时间(Unix ms) + * @param limit - 最大返回条数(默认 500) + * @returns 标准化 K 线数组,按时间升序 + */ + fetchKlines( + symbol: string, + interval: KlineInterval, + startTime: number, + endTime: number, + limit?: number, + ): Promise; + + /** + * 获取交易所交易对信息(用于自动注册到 trading_pairs 表)。 + * 返回标准化后的交易对元数据。 + */ + fetchMarkets(): Promise; +} + +/** 交易对元信息(从交易所 REST API 获取) */ +export interface MarketInfo { + symbol: string; + baseAsset: string; + quoteAsset: string; + pricePrecision: number; + quantityPrecision: number; + minQty?: number; + stepSize?: number; + minNotional?: number; +} + +// ============================================================ +// 工具类型 +// ============================================================ + +/** Binance WebSocket 原始 K 线数据(kline 事件中的 k 字段) */ +export interface BinanceRawKline { + t: number; // K 线开始时间 + T: number; // K 线结束时间 + s: string; // 交易对 + i: string; // 周期 + o: string; // 开盘价 + h: string; // 最高价 + l: string; // 最低价 + c: string; // 收盘价 + v: string; // 成交量 + n: number; // 成交笔数 + x: boolean; // 是否已关闭 + q: string; // 成交额 + V: string; // 主动买入成交量 + Q: string; // 主动买入成交额 +} diff --git a/data/init-db/001_init.sql b/data/init-db/001_init.sql deleted file mode 100644 index eab99de..0000000 --- a/data/init-db/001_init.sql +++ /dev/null @@ -1,228 +0,0 @@ --- ============================================================ --- 001_init.sql — TimescaleDB 数据初始化 --- --- Docker Compose 首次启动时自动执行 --- 挂载路径:./data/init-db:/docker-entrypoint-initdb.d --- ============================================================ - --- 扩展 -CREATE EXTENSION IF NOT EXISTS timescaledb; -CREATE EXTENSION IF NOT EXISTS timescaledb_toolkit; - --- ============================================================ --- 1. K 线主表 --- ============================================================ -CREATE TABLE IF NOT EXISTS klines ( - -- 时间维度 - time TIMESTAMPTZ NOT NULL, -- K 线开盘时间(UTC) - - -- 标识维度 - exchange TEXT NOT NULL, -- 交易所:binance/okx/bybit - symbol TEXT NOT NULL, -- 交易对:BTCUSDT/ETHUSDT - interval TEXT NOT NULL, -- 周期:1m/5m/15m/1h/4h/1d - - -- OHLCV - open NUMERIC(20,8) NOT NULL, - high NUMERIC(20,8) NOT NULL, - low NUMERIC(20,8) NOT NULL, - close NUMERIC(20,8) NOT NULL, - volume NUMERIC(20,8) NOT NULL DEFAULT 0, -- 成交量(基准币种) - - -- 扩展字段 - quote_volume NUMERIC(20,8) DEFAULT 0, -- 成交额(计价币种) - taker_buy_base_vol NUMERIC(20,8) DEFAULT 0, -- 主动买入量 - taker_buy_quote_vol NUMERIC(20,8) DEFAULT 0, -- 主动买入额 - trade_count INTEGER DEFAULT 0, -- 成交笔数 - is_closed BOOLEAN DEFAULT TRUE, -- K 线是否已闭合 - - -- 元数据 - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - - -- 唯一约束(同一根 K 线不可重复) - UNIQUE (time, exchange, symbol, interval) -); - --- ============================================================ --- 2. 转换为 hypertable(时序分区) --- ============================================================ -SELECT create_hypertable( - 'klines', - 'time', -- 时间列 - chunk_time_interval => INTERVAL '1 day', -- 每个 chunk = 1 天数据 - partitioning_column => 'exchange', -- 空间分区列 - number_partitions => 4, -- 4 个空间分区 - if_not_exists => TRUE -); - --- ============================================================ --- 3. 索引 --- ============================================================ - --- 主力查询索引:按交易对+周期+时间范围查(覆盖 95% 查询) -CREATE INDEX IF NOT EXISTS idx_klines_lookup - ON klines (exchange, symbol, interval, time DESC); - --- 回测专用索引:按交易对+周期+时间正序 -CREATE INDEX IF NOT EXISTS idx_klines_backtest - ON klines (symbol, interval, time ASC); - --- 最新 K 线索引(部分索引,仅覆盖已闭合 K 线) -CREATE INDEX IF NOT EXISTS idx_klines_latest - ON klines (exchange, symbol, interval, time DESC) - WHERE is_closed = TRUE; - --- ============================================================ --- 4. 压缩策略 --- ============================================================ - --- 启用列式压缩(按 symbol+interval 分组,按 time 排序) -ALTER TABLE klines SET ( - timescaledb.compress, - timescaledb.compress_segmentby = 'exchange, symbol, interval', - timescaledb.compress_orderby = 'time DESC' -); - --- 自动压缩:K 线闭合 7 天后自动压缩(压缩比约 90%) -SELECT add_compression_policy('klines', INTERVAL '7 days', if_not_exists => TRUE); - --- ============================================================ --- 5. 数据保留策略 --- ============================================================ --- 1m K 线保留 90 天(回测通常用更粗粒度) -SELECT add_retention_policy('klines', INTERVAL '90 days', if_not_exists => TRUE); - --- ============================================================ --- 6. 连续聚合(从 1m 自动派生高周期 K 线) --- ============================================================ - --- ---------- 5m K 线 ---------- -CREATE MATERIALIZED VIEW IF NOT EXISTS klines_5m -WITH (timescaledb.continuous) AS -SELECT - time_bucket('5 minutes', time) AS time, - exchange, - symbol, - '5m'::TEXT AS interval, - FIRST(open, time) AS open, - MAX(high) AS high, - MIN(low) AS low, - LAST(close, time) AS close, - SUM(volume) AS volume, - SUM(quote_volume) AS quote_volume, - SUM(taker_buy_base_vol) AS taker_buy_base_vol, - SUM(taker_buy_quote_vol) AS taker_buy_quote_vol, - SUM(trade_count) AS trade_count -FROM klines -WHERE interval = '1m' -GROUP BY time_bucket('5 minutes', time), exchange, symbol; - -SELECT add_continuous_aggregate_policy('klines_5m', - start_offset => INTERVAL '1 day', - end_offset => INTERVAL '10 minutes', - schedule_interval => INTERVAL '1 minute', - if_not_exists => TRUE -); - --- ---------- 15m K 线 ---------- -CREATE MATERIALIZED VIEW IF NOT EXISTS klines_15m -WITH (timescaledb.continuous) AS -SELECT - time_bucket('15 minutes', time) AS time, - exchange, - symbol, - '15m'::TEXT AS interval, - FIRST(open, time) AS open, - MAX(high) AS high, - MIN(low) AS low, - LAST(close, time) AS close, - SUM(volume) AS volume, - SUM(quote_volume) AS quote_volume, - SUM(taker_buy_base_vol) AS taker_buy_base_vol, - SUM(taker_buy_quote_vol) AS taker_buy_quote_vol, - SUM(trade_count) AS trade_count -FROM klines -WHERE interval = '1m' -GROUP BY time_bucket('15 minutes', time), exchange, symbol; - -SELECT add_continuous_aggregate_policy('klines_15m', - start_offset => INTERVAL '2 days', - end_offset => INTERVAL '30 minutes', - schedule_interval => INTERVAL '5 minutes', - if_not_exists => TRUE -); - --- ---------- 1h K 线 ---------- -CREATE MATERIALIZED VIEW IF NOT EXISTS klines_1h -WITH (timescaledb.continuous) AS -SELECT - time_bucket('1 hour', time) AS time, - exchange, - symbol, - '1h'::TEXT AS interval, - FIRST(open, time) AS open, - MAX(high) AS high, - MIN(low) AS low, - LAST(close, time) AS close, - SUM(volume) AS volume, - SUM(quote_volume) AS quote_volume, - SUM(taker_buy_base_vol) AS taker_buy_base_vol, - SUM(taker_buy_quote_vol) AS taker_buy_quote_vol, - SUM(trade_count) AS trade_count -FROM klines -WHERE interval = '1m' -GROUP BY time_bucket('1 hour', time), exchange, symbol; - -SELECT add_continuous_aggregate_policy('klines_1h', - start_offset => INTERVAL '3 days', - end_offset => INTERVAL '1 hour', - schedule_interval => INTERVAL '5 minutes', - if_not_exists => TRUE -); - --- ---------- 1d K 线 ---------- -CREATE MATERIALIZED VIEW IF NOT EXISTS klines_1d -WITH (timescaledb.continuous) AS -SELECT - time_bucket('1 day', time) AS time, - exchange, - symbol, - '1d'::TEXT AS interval, - FIRST(open, time) AS open, - MAX(high) AS high, - MIN(low) AS low, - LAST(close, time) AS close, - SUM(volume) AS volume, - SUM(quote_volume) AS quote_volume, - SUM(taker_buy_base_vol) AS taker_buy_base_vol, - SUM(taker_buy_quote_vol) AS taker_buy_quote_vol, - SUM(trade_count) AS trade_count -FROM klines -WHERE interval = '1m' -GROUP BY time_bucket('1 day', time), exchange, symbol; - -SELECT add_continuous_aggregate_policy('klines_1d', - start_offset => INTERVAL '7 days', - end_offset => INTERVAL '2 hours', - schedule_interval => INTERVAL '1 hour', - if_not_exists => TRUE -); - --- ============================================================ --- 7. 连续聚合的压缩(减少视图存储) --- ============================================================ -ALTER MATERIALIZED VIEW klines_5m SET (timescaledb.compress = true); -ALTER MATERIALIZED VIEW klines_15m SET (timescaledb.compress = true); -ALTER MATERIALIZED VIEW klines_1h SET (timescaledb.compress = true); -ALTER MATERIALIZED VIEW klines_1d SET (timescaledb.compress = true); - --- ============================================================ --- 初始化完成 --- ============================================================ -DO $$ -BEGIN - RAISE NOTICE '001_init.sql — TimescaleDB initialization complete.'; - RAISE NOTICE 'Hypertable: klines'; - RAISE NOTICE 'Continuous aggregates: klines_5m, klines_15m, klines_1h, klines_1d'; - RAISE NOTICE 'Compression: 7 days delay, 90 days retention'; -END $$; diff --git a/data/init-db/002_config.sql b/data/init-db/002_config.sql deleted file mode 100644 index 550438b..0000000 --- a/data/init-db/002_config.sql +++ /dev/null @@ -1,136 +0,0 @@ --- ============================================================ --- 002_config.sql — 配置表初始化 --- ============================================================ --- Docker Compose 首次启动时在 001_init.sql 之后自动执行 --- 挂载路径:./data/init-db:/docker-entrypoint-initdb.d --- 执行顺序:按文件名排序(001 → 002) --- ============================================================ - --- ============================================================ --- 1. monitored_symbols — 监控交易对配置 --- ============================================================ --- 用途:声明数据采集模块需要订阅哪些交易对的 K 线流 --- 消费方:WebSocket 行情采集服务启动时读取此表决定订阅列表 -CREATE TABLE IF NOT EXISTS monitored_symbols ( - id SERIAL PRIMARY KEY, - - -- ---- 标识维度(与 klines 表对齐) ---- - exchange TEXT NOT NULL, -- 交易所:binance / okx / bybit - symbol TEXT NOT NULL, -- 交易对:BTCUSDT / ETHUSDT - interval TEXT NOT NULL, -- K 线周期:1m / 5m / 15m / 1h / 4h / 1d - - -- ---- 控制字段 ---- - enabled BOOLEAN NOT NULL DEFAULT TRUE, -- 是否启用采集 - priority SMALLINT NOT NULL DEFAULT 0, -- 优先级(数值越大越优先,用于限频时取舍) - - -- ---- 备注 ---- - label TEXT, -- 人类可读标签,如 "BTC/USDT 1分钟线" - notes TEXT, -- 备注说明 - - -- ---- 元数据 ---- - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - - -- 同一 (exchange, symbol, interval) 组合不可重复 - UNIQUE (exchange, symbol, interval) -); - --- 索引:按启用状态快速筛选 -CREATE INDEX IF NOT EXISTS idx_monitored_symbols_enabled - ON monitored_symbols (enabled, exchange, priority DESC); - --- 索引:按交易对查找所有周期 -CREATE INDEX IF NOT EXISTS idx_monitored_symbols_symbol - ON monitored_symbols (symbol, interval); - --- ============================================================ --- 2. exchange_config — 交易所连接配置 --- ============================================================ --- 用途:存储各交易所的 API 端点、限频参数等连接级配置 --- ⚠️ 安全提醒:API Key/Secret 不应明文存储于此表, --- 建议通过环境变量或 Vault 注入,此表仅存非敏感参数 -CREATE TABLE IF NOT EXISTS exchange_config ( - id SERIAL PRIMARY KEY, - - -- ---- 交易所标识 ---- - exchange TEXT NOT NULL UNIQUE, -- 交易所:binance / okx / bybit - - -- ---- 连接参数 ---- - rest_url TEXT, -- REST API 基础 URL(留空则用 SDK 默认值) - ws_url TEXT, -- WebSocket 基础 URL(留空则用 SDK 默认值) - ws_ping_interval_ms INTEGER NOT NULL DEFAULT 30000, -- 心跳间隔(毫秒) - - -- ---- 限频控制 ---- - rate_limit_per_sec REAL NOT NULL DEFAULT 20.0, -- 每秒最大请求数 - max_reconnect_attempts INT NOT NULL DEFAULT 10, -- 最大重连次数 - reconnect_delay_ms INT NOT NULL DEFAULT 3000, -- 重连延迟基数(指数退避) - - -- ---- 开关 ---- - enabled BOOLEAN NOT NULL DEFAULT TRUE, -- 是否启用该交易所 - - -- ---- 备注 ---- - notes TEXT, - - -- ---- 元数据 ---- - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- ============================================================ --- 3. app_config — 全局应用配置(Key-Value) --- ============================================================ --- 用途:存储不适合硬编码的运行时参数,如批量写入阈值 -CREATE TABLE IF NOT EXISTS app_config ( - id SERIAL PRIMARY KEY, - key TEXT NOT NULL UNIQUE, -- 配置键 - value TEXT NOT NULL, -- 配置值(统一存为文本,消费方自行解析类型) - description TEXT, -- 说明 - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- 默认配置项(仅首次插入,已存在则跳过) -INSERT INTO app_config (key, value, description) VALUES - ('batch_size', '500', '批量写入缓冲区条数阈值'), - ('flush_interval_ms', '1000', '缓冲区最大等待时间(毫秒)'), - ('log_level', 'info', '日志级别:trace / debug / info / warn / error'), - ('redis_publish_enabled', 'true', '是否启用 Redis 发布') -ON CONFLICT (key) DO NOTHING; - --- ============================================================ --- 4. 预置种子数据 --- ============================================================ - --- 预置 Binance 主力交易对监控配置(仅首次插入) -INSERT INTO monitored_symbols (exchange, symbol, interval, enabled, priority, label) VALUES - -- Binance 主力交易对 — 1m - ('binance', 'BTCUSDT', '1m', TRUE, 10, 'BTC/USDT 1分钟线'), - ('binance', 'ETHUSDT', '1m', TRUE, 9, 'ETH/USDT 1分钟线'), - ('binance', 'SOLUSDT', '1m', TRUE, 8, 'SOL/USDT 1分钟线'), - ('binance', 'BNBUSDT', '1m', TRUE, 7, 'BNB/USDT 1分钟线'), - - -- Binance 主力交易对 — 1h(策略用) - ('binance', 'BTCUSDT', '1h', TRUE, 10, 'BTC/USDT 1小时线'), - ('binance', 'ETHUSDT', '1h', TRUE, 9, 'ETH/USDT 1小时线'), - ('binance', 'SOLUSDT', '1h', TRUE, 8, 'SOL/USDT 1小时线'), - - -- Binance 主力交易对 — 1d(日线) - ('binance', 'BTCUSDT', '1d', TRUE, 10, 'BTC/USDT 日线'), - ('binance', 'ETHUSDT', '1d', TRUE, 9, 'ETH/USDT 日线') -ON CONFLICT (exchange, symbol, interval) DO NOTHING; - --- 预置交易所默认连接配置(仅首次插入) -INSERT INTO exchange_config (exchange, rate_limit_per_sec, notes) VALUES - ('binance', 20.0, 'Binance 现货 — 默认权重限频 1200/min'), - ('okx', 10.0, 'OKX 现货 — 默认限频 10/s'), - ('bybit', 10.0, 'Bybit 现货 — 默认限频 10/s') -ON CONFLICT (exchange) DO NOTHING; - --- ============================================================ --- 初始化完成 --- ============================================================ -DO $$ -BEGIN - RAISE NOTICE '002_config.sql — Config tables initialized.'; - RAISE NOTICE 'Tables: monitored_symbols, exchange_config, app_config'; - RAISE NOTICE 'Seed data: 9 symbols (Binance), 3 exchanges'; -END $$; diff --git a/data/package-lock.json b/data/package-lock.json deleted file mode 100644 index a3c98d1..0000000 --- a/data/package-lock.json +++ /dev/null @@ -1,5320 +0,0 @@ -{ - "name": "trade-data", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "trade-data", - "version": "0.1.0", - "dependencies": { - "binance": "^3.5.9", - "ccxt": "^4.5.56", - "ioredis": "^5.11.1", - "pg": "^8.21.0", - "pino": "^10.3.1", - "ws": "^8.21.0", - "zod": "^4.4.3" - }, - "devDependencies": { - "@types/bun": "^1.3.14", - "@types/node": "^25.9.2", - "@types/pg": "^8.20.0", - "@types/ws": "^8.18.1", - "eslint": "^10.4.1", - "prettier": "^3.8.3", - "tsx": "^4.22.4", - "typescript": "^6.0.3", - "vitest": "^4.1.8" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14.17.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", - "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.23.5", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", - "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", - "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", - "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", - "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/types": "^0.15.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", - "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/types": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", - "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", - "dev": true, - "license": "Apache-2.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/@ioredis/commands": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.10.0.tgz", - "integrity": "sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==", - "license": "MIT" - }, - "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==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@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==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "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==", - "devOptional": 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==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", - "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", - "license": "MIT" - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "license": "MIT", - "optional": true - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/bun": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.14.tgz", - "integrity": "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "bun-types": "1.3.14" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "devOptional": 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==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", - "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" - } - }, - "node_modules/@types/pg": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", - "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", - "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", - "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.8", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", - "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", - "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.8", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", - "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.8", - "@vitest/utils": "4.1.8", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", - "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", - "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.8", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT", - "optional": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT", - "optional": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT", - "optional": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT", - "optional": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT", - "optional": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0", - "optional": true - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", - "license": "BSD-3-Clause", - "optional": true - }, - "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==", - "devOptional": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.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/acorn-walk": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", - "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", - "license": "MIT", - "optional": true, - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "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/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "optional": true - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/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/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/axios": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.17.0.tgz", - "integrity": "sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.34", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz", - "integrity": "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==", - "license": "Apache-2.0", - "optional": true, - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": "*" - } - }, - "node_modules/binance": { - "version": "3.5.9", - "resolved": "https://registry.npmjs.org/binance/-/binance-3.5.9.tgz", - "integrity": "sha512-Jg8w81dKIMpGjVgY3k2s0ltAegmN2kOI3GcBzvAGC7CDqUYpWElasfoSrc0fEffryjGQhgJxf0ubyO1wWhz+rA==", - "license": "MIT", - "dependencies": { - "axios": "^1.13.2", - "isomorphic-ws": "^4.0.1", - "nanoid": "^3.3.11", - "ws": "^7.4.0" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/tiagosiebler" - }, - "optionalDependencies": { - "source-map-loader": "^2.0.2", - "ts-loader": "^8.0.11", - "webpack": "^5.102.1", - "webpack-bundle-analyzer": "^5.1.1", - "webpack-cli": "^5.1.4" - } - }, - "node_modules/binance/node_modules/ws": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", - "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "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==", - "license": "MIT", - "optional": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "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", - "optional": true, - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT", - "optional": true - }, - "node_modules/bun-types": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.14.tgz", - "integrity": "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "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/caniuse-lite": { - "version": "1.0.30001797", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", - "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", - "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", - "optional": true - }, - "node_modules/ccxt": { - "version": "4.5.56", - "resolved": "https://registry.npmjs.org/ccxt/-/ccxt-4.5.56.tgz", - "integrity": "sha512-4UhGBI0ektkLdBBq8vv9mVhBXoIciqaFaeL6KOhMsQMrEq3h3T8Gt1Z+8Wfx1AbUZI2aAkgqWU42pcLOPhFJrQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "ws": "^8.8.1" - }, - "engines": { - "node": ">=15.0.0" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "optional": true, - "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/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.1.tgz", - "integrity": "sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT", - "optional": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT", - "optional": true - }, - "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/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT", - "optional": true - }, - "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-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT", - "optional": true - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "devOptional": 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/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "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/electron-to-chromium": { - "version": "1.5.368", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz", - "integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==", - "license": "ISC", - "optional": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/envinfo": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", - "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", - "license": "MIT", - "optional": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "license": "MIT", - "optional": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "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-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "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.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "optional": true, - "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": "10.4.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", - "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.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", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "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==", - "devOptional": 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==", - "devOptional": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/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/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-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==", - "devOptional": true, - "license": "MIT" - }, - "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/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "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==", - "license": "MIT", - "optional": true, - "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": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "license": "BSD-3-Clause", - "optional": true, - "bin": { - "flat": "cli.js" - } - }, - "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.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "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/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/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-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause", - "optional": true - }, - "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/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC", - "optional": true - }, - "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==", - "license": "MIT", - "optional": true, - "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.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", - "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", - "license": "MIT", - "optional": true - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "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-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "license": "MIT", - "optional": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "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/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC", - "optional": true - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ioredis": { - "version": "5.11.1", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.11.1.tgz", - "integrity": "sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==", - "license": "MIT", - "dependencies": { - "@ioredis/commands": "1.10.0", - "cluster-key-slot": "1.1.1", - "debug": "4.4.3", - "denque": "2.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/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", - "license": "MIT", - "optional": true, - "dependencies": { - "hasown": "^2.0.3" - }, - "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==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "optional": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT", - "optional": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isomorphic-ws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "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==", - "license": "MIT", - "optional": true, - "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/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.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/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/loader-runner": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", - "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "license": "MIT", - "optional": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.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/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/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/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "license": "MIT", - "optional": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT", - "optional": true - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "optional": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10" - } - }, - "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/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "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/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT", - "optional": true - }, - "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/obug": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz", - "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT", - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "license": "(WTFPL OR MIT)", - "optional": true, - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "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/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "optional": true, - "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==", - "devOptional": 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==", - "devOptional": 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==", - "license": "MIT", - "optional": true - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pg": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", - "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", - "license": "MIT", - "dependencies": { - "pg-connection-string": "^2.13.0", - "pg-pool": "^3.14.0", - "pg-protocol": "^1.14.0", - "pg-types": "2.2.0", - "pgpass": "1.0.5" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.4.0" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", - "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", - "license": "MIT", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", - "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", - "license": "MIT" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", - "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", - "license": "MIT", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", - "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "license": "MIT", - "dependencies": { - "split2": "^4.1.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pino": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", - "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", - "license": "MIT", - "dependencies": { - "@pinojs/redact": "^0.4.0", - "atomic-sleep": "^1.0.0", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^3.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^5.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^4.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", - "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", - "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", - "license": "MIT" - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "optional": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "optional": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "optional": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "optional": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "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.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", - "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT", - "optional": true - }, - "node_modules/process-warning": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", - "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "license": "MIT", - "optional": true - }, - "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/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "optional": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "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/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "license": "MIT", - "optional": true, - "dependencies": { - "es-errors": "^1.3.0", - "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-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "license": "MIT", - "optional": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.133.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT", - "optional": true - }, - "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", - "optional": true - }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "license": "MIT", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "optional": true - }, - "node_modules/semver": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", - "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "license": "MIT", - "optional": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "devOptional": 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==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", - "license": "MIT", - "optional": true, - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/sonic-boom": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", - "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-loader": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-2.0.2.tgz", - "integrity": "sha512-yIYkFOsKn+OdOirRJUPQpnZiMkF74raDVQjj5ni3SzbOiA57SabeX80R5zyMQAKpvKySA3Z4a85vFX3bvpC6KQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "abab": "^2.0.5", - "iconv-lite": "^0.6.2", - "source-map-js": "^0.6.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/source-map-loader/node_modules/source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", - "optional": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/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/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "optional": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "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==", - "license": "MIT", - "optional": true, - "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==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", - "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", - "license": "BSD-2-Clause", - "optional": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", - "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@minify-html/node": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "@swc/css": { - "optional": true - }, - "@swc/html": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "cssnano": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "html-minifier-terser": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "postcss": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/thread-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", - "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", - "license": "MIT", - "dependencies": { - "real-require": "^1.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/thread-stream/node_modules/real-require": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", - "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "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==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ts-loader": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.4.0.tgz", - "integrity": "sha512-6nFY3IZ2//mrPc+ImY3hNWx1vCHyEhl6V+wLmL4CZcm6g1CqX7UKrkc6y0i4FwcfOhxyMPCfaEvh20f4r9GNpw==", - "license": "MIT", - "optional": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^2.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "*" - } - }, - "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==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/tsx": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.28.0" - }, - "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/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "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", - "optional": true, - "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/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", - "optional": true - }, - "node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", - "tinyglobby": "^0.2.17" - }, - "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", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.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 - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "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/vitest": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", - "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.8", - "@vitest/mocker": "4.1.8", - "@vitest/pretty-format": "4.1.8", - "@vitest/runner": "4.1.8", - "@vitest/snapshot": "4.1.8", - "@vitest/spy": "4.1.8", - "@vitest/utils": "4.1.8", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.8", - "@vitest/browser-preview": "4.1.8", - "@vitest/browser-webdriverio": "4.1.8", - "@vitest/coverage-istanbul": "4.1.8", - "@vitest/coverage-v8": "4.1.8", - "@vitest/ui": "4.1.8", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/watchpack": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", - "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", - "license": "MIT", - "optional": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.107.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.107.2.tgz", - "integrity": "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.16.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.22.0", - "es-module-lexer": "^2.1.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "loader-runner": "^4.3.2", - "mime-db": "^1.54.0", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.5.0", - "watchpack": "^2.5.1", - "webpack-sources": "^3.5.0" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-5.3.0.tgz", - "integrity": "sha512-PEhAoqiJ+47d0uLMx/+zo5XOvaU+Vk6N2ZLht7H3n09QLy/fhyvqGNwjdRUHJDgMN8crBR2ZwVHkIswT3Xuawg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.6.3", - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "commander": "^14.0.2", - "escape-string-regexp": "^5.0.0", - "html-escaper": "^3.0.3", - "opener": "^1.5.2", - "picocolors": "^1.0.0", - "sirv": "^3.0.2", - "ws": "^8.19.0" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 20.9.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "optional": true, - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz", - "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/enhanced-resolve": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.23.0.tgz", - "integrity": "sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==", - "license": "MIT", - "optional": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", - "optional": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "optional": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/tapable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "license": "MIT", - "optional": true - }, - "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/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "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/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "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.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/data/package.json b/data/package.json index 0981770..5542981 100644 --- a/data/package.json +++ b/data/package.json @@ -13,13 +13,15 @@ "format": "prettier --write src/" }, "dependencies": { + "@timescaledb/typeorm": "^0.0.1", "binance": "^3.5.9", "ccxt": "^4.5.56", "ioredis": "^5.11.1", "pg": "^8.21.0", "pino": "^10.3.1", - "ws": "^8.21.0", - "zod": "^4.4.3" + "rxjs": "^7.8.2", + "typeorm": "^1.0.0", + "yaml": "^2.9.0" }, "devDependencies": { "@types/bun": "^1.3.14", @@ -32,4 +34,4 @@ "typescript": "^6.0.3", "vitest": "^4.1.8" } -} +} \ No newline at end of file diff --git a/data/run/exchange.ts b/data/run/exchange.ts deleted file mode 100644 index 474b3bc..0000000 --- a/data/run/exchange.ts +++ /dev/null @@ -1,19 +0,0 @@ -import ccxt from "ccxt"; -import { ExchangeWs } from '../exchanges/binance'; - -const exchange = new ExchangeWs({ - exchangeId: 'binance', - symbols: ['BTCUSDT'], - interval: '1m', -}); - -// exchange.on('kline', (data) => { -// console.log(data); -// }); - -exchange.on('ready', () => { - console.log(exchange.getState()); - console.log(exchange.getReadySymbols()); -}); - -const ccxtClient = new ccxt.binance(); \ No newline at end of file diff --git a/data/schema/config.sql b/data/schema/config.sql deleted file mode 100644 index ee883ad..0000000 --- a/data/schema/config.sql +++ /dev/null @@ -1,177 +0,0 @@ --- ============================================================ --- schema/config.sql — 配置表 DDL(参考副本) --- ============================================================ --- 数据库:TimescaleDB (PostgreSQL 17) --- 说明:管理系统配置、监控标的、交易所连接参数 --- --- ⚠️ 权威初始化脚本已迁移至:data/init-db/001_init.sql --- 本文件保留作为 pg.initSchema() 非 Docker 部署的回退方案和文档参考。 --- 修改表结构时请同步更新 001_init.sql。 --- ============================================================ - --- ============================================================ --- 1. monitored_symbols — 监控交易对配置 --- ============================================================ --- 用途:声明数据采集模块需要订阅哪些交易对的 K 线流 --- 消费方:WebSocket 行情采集服务启动时读取此表决定订阅列表 --- ============================================================ -CREATE TABLE IF NOT EXISTS monitored_symbols ( - id SERIAL PRIMARY KEY, - - -- ---- 标识维度(与 klines 表对齐) ---- - exchange TEXT NOT NULL, -- 交易所:binance / okx / bybit - symbol TEXT NOT NULL, -- 交易对:BTCUSDT / ETHUSDT - interval TEXT NOT NULL, -- K 线周期:1m / 5m / 15m / 1h / 4h / 1d - - -- ---- 控制字段 ---- - enabled BOOLEAN NOT NULL DEFAULT TRUE, -- 是否启用采集 - priority SMALLINT NOT NULL DEFAULT 0, -- 优先级(数值越大越优先,用于限频时取舍) - - -- ---- 备注 ---- - label TEXT, -- 人类可读标签,如 "BTC/USDT 1分钟线" - notes TEXT, -- 备注说明 - - -- ---- 元数据 ---- - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - - -- 同一 (exchange, symbol, interval) 组合不可重复 - UNIQUE (exchange, symbol, interval) -); - --- 索引:按启用状态快速筛选 -CREATE INDEX IF NOT EXISTS idx_monitored_symbols_enabled - ON monitored_symbols (enabled, exchange, priority DESC); - --- 索引:按交易对查找所有周期 -CREATE INDEX IF NOT EXISTS idx_monitored_symbols_symbol - ON monitored_symbols (symbol, interval); - -COMMENT ON TABLE monitored_symbols IS '监控交易对配置表:声明哪些 (交易所,交易对,周期) 需要采集 K 线数据'; -COMMENT ON COLUMN monitored_symbols.exchange IS '交易所标识:binance / okx / bybit'; -COMMENT ON COLUMN monitored_symbols.symbol IS '交易对:BTCUSDT / ETHUSDT'; -COMMENT ON COLUMN monitored_symbols.interval IS 'K 线周期:1m / 5m / 15m / 1h / 4h / 1d'; -COMMENT ON COLUMN monitored_symbols.enabled IS '是否启用 WebSocket 订阅'; -COMMENT ON COLUMN monitored_symbols.priority IS '优先级(0-32767),限频时高优先级交易对优先保留'; - - --- ============================================================ --- 2. exchange_config — 交易所连接配置 --- ============================================================ --- 用途:存储各交易所的 API 端点、限频参数等连接级配置 --- 安全提醒:API Key/Secret 不应明文存储于此表, --- 建议通过环境变量或 Vault 注入,此表仅存非敏感参数 --- ============================================================ -CREATE TABLE IF NOT EXISTS exchange_config ( - id SERIAL PRIMARY KEY, - - -- ---- 交易所标识 ---- - exchange TEXT NOT NULL UNIQUE, -- 交易所:binance / okx / bybit - - -- ---- 连接参数 ---- - rest_url TEXT, -- REST API 基础 URL(留空则用 SDK 默认值) - ws_url TEXT, -- WebSocket 基础 URL(留空则用 SDK 默认值) - ws_ping_interval_ms INTEGER NOT NULL DEFAULT 30000, -- 心跳间隔(毫秒) - - -- ---- 限频控制 ---- - rate_limit_per_sec REAL NOT NULL DEFAULT 20.0, -- 每秒最大请求数 - max_reconnect_attempts INT NOT NULL DEFAULT 10, -- 最大重连次数 - reconnect_delay_ms INT NOT NULL DEFAULT 3000, -- 重连延迟基数(指数退避) - - -- ---- 开关 ---- - enabled BOOLEAN NOT NULL DEFAULT TRUE, -- 是否启用该交易所 - - -- ---- 备注 ---- - notes TEXT, - - -- ---- 元数据 ---- - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -COMMENT ON TABLE exchange_config IS '交易所连接配置表:REST/WS 端点、限频、重连策略'; -COMMENT ON COLUMN exchange_config.rest_url IS 'REST API 地址,空则使用 SDK 默认'; -COMMENT ON COLUMN exchange_config.ws_url IS 'WebSocket 地址,空则使用 SDK 默认'; -COMMENT ON COLUMN exchange_config.rate_limit_per_sec IS '每秒最大请求数(Binance 默认 20/s)'; -COMMENT ON COLUMN exchange_config.max_reconnect_attempts IS 'WebSocket 断线最大重连次数'; -COMMENT ON COLUMN exchange_config.reconnect_delay_ms IS '重连退避基数(实际延迟 = 基数 × 2^attempts)'; - - --- ============================================================ --- 3. app_config — 全局应用配置(Key-Value) --- ============================================================ --- 用途:存储不适合硬编码的运行时参数,如批量写入阈值 --- ============================================================ -CREATE TABLE IF NOT EXISTS app_config ( - id SERIAL PRIMARY KEY, - key TEXT NOT NULL UNIQUE, -- 配置键 - value TEXT NOT NULL, -- 配置值(统一存为文本,消费方自行解析类型) - description TEXT, -- 说明 - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -COMMENT ON TABLE app_config IS '全局应用配置(KV 结构),运行时参数集中管理'; - --- 默认配置项 -INSERT INTO app_config (key, value, description) VALUES - ('batch_size', '500', '批量写入缓冲区条数阈值'), - ('flush_interval_ms', '1000', '缓冲区最大等待时间(毫秒)'), - ('log_level', 'info', '日志级别:trace / debug / info / warn / error'), - ('redis_publish_enabled', 'true', '是否启用 Redis 发布') -ON CONFLICT (key) DO NOTHING; - - --- ============================================================ --- 4. 初始数据:预置常见交易对的监控配置 --- ============================================================ --- 以下为建议的默认监控列表,可根据实际需求增删 -INSERT INTO monitored_symbols (exchange, symbol, interval, enabled, priority, label) VALUES - -- Binance 主力交易对 — 1m - ('binance', 'BTCUSDT', '1m', TRUE, 10, 'BTC/USDT 1分钟线'), - ('binance', 'ETHUSDT', '1m', TRUE, 9, 'ETH/USDT 1分钟线'), - ('binance', 'SOLUSDT', '1m', TRUE, 8, 'SOL/USDT 1分钟线'), - ('binance', 'BNBUSDT', '1m', TRUE, 7, 'BNB/USDT 1分钟线'), - - -- Binance 主力交易对 — 1h(策略用) - ('binance', 'BTCUSDT', '1h', TRUE, 10, 'BTC/USDT 1小时线'), - ('binance', 'ETHUSDT', '1h', TRUE, 9, 'ETH/USDT 1小时线'), - ('binance', 'SOLUSDT', '1h', TRUE, 8, 'SOL/USDT 1小时线'), - - -- Binance 主力交易对 — 1d(日线) - ('binance', 'BTCUSDT', '1d', TRUE, 10, 'BTC/USDT 日线'), - ('binance', 'ETHUSDT', '1d', TRUE, 9, 'ETH/USDT 日线') -ON CONFLICT (exchange, symbol, interval) DO NOTHING; - --- 预置交易所默认连接配置 -INSERT INTO exchange_config (exchange, rate_limit_per_sec, notes) VALUES - ('binance', 20.0, 'Binance 现货 — 默认权重限频 1200/min'), - ('okx', 10.0, 'OKX 现货 — 默认限频 10/s'), - ('bybit', 10.0, 'Bybit 现货 — 默认限频 10/s') -ON CONFLICT (exchange) DO NOTHING; - - --- ============================================================ --- 5. 常用查询示例 --- ============================================================ - --- 查询所有启用的监控标的(采集服务启动时使用) --- SELECT exchange, symbol, interval, priority --- FROM monitored_symbols --- WHERE enabled = TRUE --- ORDER BY exchange, priority DESC, symbol, interval; - --- 查询某交易所下所有交易对 --- SELECT DISTINCT symbol --- FROM monitored_symbols --- WHERE exchange = 'binance' AND enabled = TRUE --- ORDER BY symbol; - --- 禁用某个交易对的采集 --- UPDATE monitored_symbols SET enabled = FALSE, updated_at = NOW() --- WHERE exchange = 'binance' AND symbol = 'BTCUSDT' AND interval = '1m'; - --- 新增监控标的(动态添加,无需重启) --- INSERT INTO monitored_symbols (exchange, symbol, interval, enabled, priority, label) --- VALUES ('binance', 'DOGEUSDT', '1m', TRUE, 5, 'DOGE/USDT 1分钟线') --- ON CONFLICT (exchange, symbol, interval) DO UPDATE --- SET enabled = TRUE, updated_at = NOW(); diff --git a/data/schema/klines.sql b/data/schema/klines.sql deleted file mode 100644 index 1d15807..0000000 --- a/data/schema/klines.sql +++ /dev/null @@ -1,206 +0,0 @@ --- ============================================================ --- schema/klines.sql — K 线表 DDL(参考副本) --- ============================================================ --- 数据库:TimescaleDB (PostgreSQL 17 + timescaledb 2.x) --- 说明:存储全交易所 OHLCV 数据,按时间自动分区压缩 --- --- ⚠️ 权威初始化脚本:data/init-db/001_init.sql --- 本文件保留作为 pg.initSchema() 非 Docker 部署的回退方案和文档参考。 --- 修改表结构时请同步更新 001_init.sql。 --- ============================================================ - --- ============================================================ --- 1. klines — K 线主表(hypertable) --- ============================================================ -CREATE TABLE IF NOT EXISTS klines ( - -- ---- 时间维度 ---- - time TIMESTAMPTZ NOT NULL, -- K 线开盘时间(UTC) - - -- ---- 标识维度 ---- - exchange TEXT NOT NULL, -- 交易所:binance / okx / bybit - symbol TEXT NOT NULL, -- 交易对:BTCUSDT / ETHUSDT - interval TEXT NOT NULL, -- 周期:1m / 5m / 15m / 1h / 4h / 1d - - -- ---- OHLCV 核心数据 ---- - open NUMERIC(20,8) NOT NULL, - high NUMERIC(20,8) NOT NULL, - low NUMERIC(20,8) NOT NULL, - close NUMERIC(20,8) NOT NULL, - volume NUMERIC(20,8) NOT NULL DEFAULT 0, -- 成交量(基准币种) - - -- ---- 扩展字段 ---- - quote_volume NUMERIC(20,8) DEFAULT 0, -- 成交额(计价币种) - taker_buy_base_vol NUMERIC(20,8) DEFAULT 0, -- 主动买入量(基准币种) - taker_buy_quote_vol NUMERIC(20,8) DEFAULT 0, -- 主动买入额(计价币种) - trade_count INTEGER DEFAULT 0, -- 成交笔数 - is_closed BOOLEAN DEFAULT TRUE, -- K 线是否已闭合 - - -- ---- 元数据 ---- - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - - -- 唯一约束:同一根 K 线不可重复 - UNIQUE (time, exchange, symbol, interval) -); - --- ============================================================ --- 2. 转换为 TimescaleDB hypertable --- ============================================================ --- 按 time 列做 1 天分区,按 exchange 做 4 空间分区 -SELECT create_hypertable( - 'klines', - 'time', - chunk_time_interval => INTERVAL '1 day', - partitioning_column => 'exchange', - number_partitions => 4, - if_not_exists => TRUE -); - --- ============================================================ --- 3. 索引设计 --- ============================================================ - --- 主力查询索引(覆盖 95% 查询场景) --- 用途:按交易对+周期+时间范围查询最新 K 线 -CREATE INDEX IF NOT EXISTS idx_klines_lookup - ON klines (exchange, symbol, interval, time DESC); - --- 回测专用索引 --- 用途:按交易对+周期+时间正序遍历(策略回测) -CREATE INDEX IF NOT EXISTS idx_klines_backtest - ON klines (symbol, interval, time ASC); - --- 最新已闭合 K 线索引(部分索引,减小体积) --- 用途:获取已完成的最新 K 线(避免扫描未闭合数据) -CREATE INDEX IF NOT EXISTS idx_klines_latest - ON klines (exchange, symbol, interval, time DESC) - WHERE is_closed = TRUE; - --- ============================================================ --- 4. 压缩策略(列式压缩,压缩比约 90%) --- ============================================================ -ALTER TABLE klines SET ( - timescaledb.compress, - timescaledb.compress_segmentby = 'exchange, symbol, interval', - timescaledb.compress_orderby = 'time DESC' -); - --- K 线闭合 7 天后自动触发压缩 -SELECT add_compression_policy('klines', INTERVAL '7 days', if_not_exists => TRUE); - --- ============================================================ --- 5. 数据保留策略 --- ============================================================ --- 1m 粒度 K 线保留 90 天(更粗粒度由连续聚合视图覆盖) -SELECT add_retention_policy('klines', INTERVAL '90 days', if_not_exists => TRUE); - --- ============================================================ --- 6. 连续聚合视图(从 1m 自动派生高周期 K 线) --- ============================================================ - --- ---- 5m K 线 ---- -CREATE MATERIALIZED VIEW IF NOT EXISTS klines_5m -WITH (timescaledb.continuous) AS -SELECT - time_bucket('5 minutes', time) AS time, - exchange, - symbol, - '5m'::TEXT AS interval, - FIRST(open, time) AS open, - MAX(high) AS high, - MIN(low) AS low, - LAST(close, time) AS close, - SUM(volume) AS volume, - SUM(quote_volume) AS quote_volume, - SUM(taker_buy_base_vol) AS taker_buy_base_vol, - SUM(taker_buy_quote_vol) AS taker_buy_quote_vol, - SUM(trade_count) AS trade_count -FROM klines -WHERE interval = '1m' -GROUP BY time_bucket('5 minutes', time), exchange, symbol; - --- ---- 15m K 线 ---- -CREATE MATERIALIZED VIEW IF NOT EXISTS klines_15m -WITH (timescaledb.continuous) AS -SELECT - time_bucket('15 minutes', time) AS time, - exchange, - symbol, - '15m'::TEXT AS interval, - FIRST(open, time) AS open, - MAX(high) AS high, - MIN(low) AS low, - LAST(close, time) AS close, - SUM(volume) AS volume, - SUM(quote_volume) AS quote_volume, - SUM(taker_buy_base_vol) AS taker_buy_base_vol, - SUM(taker_buy_quote_vol) AS taker_buy_quote_vol, - SUM(trade_count) AS trade_count -FROM klines -WHERE interval = '1m' -GROUP BY time_bucket('15 minutes', time), exchange, symbol; - --- ---- 1h K 线 ---- -CREATE MATERIALIZED VIEW IF NOT EXISTS klines_1h -WITH (timescaledb.continuous) AS -SELECT - time_bucket('1 hour', time) AS time, - exchange, - symbol, - '1h'::TEXT AS interval, - FIRST(open, time) AS open, - MAX(high) AS high, - MIN(low) AS low, - LAST(close, time) AS close, - SUM(volume) AS volume, - SUM(quote_volume) AS quote_volume, - SUM(taker_buy_base_vol) AS taker_buy_base_vol, - SUM(taker_buy_quote_vol) AS taker_buy_quote_vol, - SUM(trade_count) AS trade_count -FROM klines -WHERE interval = '1m' -GROUP BY time_bucket('1 hour', time), exchange, symbol; - --- ---- 1d K 线 ---- -CREATE MATERIALIZED VIEW IF NOT EXISTS klines_1d -WITH (timescaledb.continuous) AS -SELECT - time_bucket('1 day', time) AS time, - exchange, - symbol, - '1d'::TEXT AS interval, - FIRST(open, time) AS open, - MAX(high) AS high, - MIN(low) AS low, - LAST(close, time) AS close, - SUM(volume) AS volume, - SUM(quote_volume) AS quote_volume, - SUM(taker_buy_base_vol) AS taker_buy_base_vol, - SUM(taker_buy_quote_vol) AS taker_buy_quote_vol, - SUM(trade_count) AS trade_count -FROM klines -WHERE interval = '1m' -GROUP BY time_bucket('1 day', time), exchange, symbol; - --- 连续聚合视图也启用压缩 -ALTER MATERIALIZED VIEW klines_5m SET (timescaledb.compress = true); -ALTER MATERIALIZED VIEW klines_15m SET (timescaledb.compress = true); -ALTER MATERIALIZED VIEW klines_1h SET (timescaledb.compress = true); -ALTER MATERIALIZED VIEW klines_1d SET (timescaledb.compress = true); - --- ============================================================ --- 7. 常用查询示例 --- ============================================================ - --- 查询最新 N 根 1h K 线 --- SELECT time, open, high, low, close, volume --- FROM klines_1h --- WHERE exchange = 'binance' AND symbol = 'BTCUSDT' --- ORDER BY time DESC LIMIT 100; - --- 查询某个时间范围内的原始 1m K 线 --- SELECT time, open, high, low, close, volume --- FROM klines --- WHERE exchange = 'binance' AND symbol = 'ETHUSDT' AND interval = '1m' --- AND time BETWEEN '2026-06-01' AND '2026-06-06' --- ORDER BY time ASC; diff --git a/data/tsconfig.json b/data/tsconfig.json index 734c0ef..e2baa0b 100644 --- a/data/tsconfig.json +++ b/data/tsconfig.json @@ -17,6 +17,10 @@ "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, "noEmit": true, + // TypeORM decorator support + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "strictPropertyInitialization": false, // Best practices "strict": true, "skipLibCheck": true, diff --git a/data/utils/logger.ts b/data/utils/logger.ts new file mode 100644 index 0000000..8687a5e --- /dev/null +++ b/data/utils/logger.ts @@ -0,0 +1,20 @@ +// data/src/logger.ts +import pino from "pino"; +import { logging } from "../config"; + +export const logger = pino({ + level: logging.level, + // 开发环境:使用 pino-pretty 彩色输出 + // 生产环境:JSON 格式,便于 ELK / Loki 采集 + ...(logging.pretty + ? { transport: { target: "pino-pretty", options: { colorize: true } } } + : {}), + // 自动注入模块名 + base: { module: "trade-data" }, + // 序列化 Error 对象 + serializers: { + err: pino.stdSerializers.err, + }, +}); + +export default logger; diff --git a/env.yaml b/env.yaml new file mode 100644 index 0000000..fa6cf41 --- /dev/null +++ b/env.yaml @@ -0,0 +1,30 @@ +# ============================================================ +# Trade System — 统一环境配置(YAML) +# ============================================================ +# 使用方式: +# data 模块通过 data/config.ts 读取此文件 +# Python 模块通过 yaml.safe_load 读取此文件 +# +# 修改后重启服务即可生效,无需重新构建。 +# ============================================================ + +# --- TimescaleDB / PostgreSQL 连接 --- +db: + host: localhost + port: 5432 + name: trade + user: trader + password: fucketh + +# --- Redis 连接 --- +redis: + url: redis://localhost:6379 + # 是否启用 Pub/Sub 行情发布(开发时可关闭以节省资源) + publish_enabled: true + +# --- 日志 --- +logging: + # 日志级别:trace / debug / info / warn / error / fatal + level: debug + # 运行环境:development 启用 pretty print;production 输出 JSON + node_env: development