diff --git a/data/.env.example b/data/.env.example index 11c53f1..0c7e6b9 100644 --- a/data/.env.example +++ b/data/.env.example @@ -5,10 +5,6 @@ # cp .env.example .env # ============================================================ -# --- 行情订阅 --- -# 逗号分隔的交易对列表(大写) -SYMBOLS=BTCUSDT,ETHUSDT - # --- TimescaleDB 连接 --- DB_HOST=localhost DB_PORT=5432 diff --git a/data/config.ts b/data/config.ts new file mode 100644 index 0000000..36023b3 --- /dev/null +++ b/data/config.ts @@ -0,0 +1,184 @@ +// ============================================================ +// 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/db/config-crud.ts b/data/db/config-crud.ts new file mode 100644 index 0000000..26b1475 --- /dev/null +++ b/data/db/config-crud.ts @@ -0,0 +1,530 @@ +// ============================================================ +// 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/index.ts b/data/db/index.ts new file mode 100644 index 0000000..18e1033 --- /dev/null +++ b/data/db/index.ts @@ -0,0 +1,121 @@ +// ============================================================ +// 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 new file mode 100644 index 0000000..241db79 --- /dev/null +++ b/data/db/pg.ts @@ -0,0 +1,364 @@ +// ============================================================ +// 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 new file mode 100644 index 0000000..5ad4431 --- /dev/null +++ b/data/db/queries.ts @@ -0,0 +1,561 @@ +// ============================================================ +// 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 new file mode 100644 index 0000000..ed939df --- /dev/null +++ b/data/db/types.ts @@ -0,0 +1,249 @@ +// ============================================================ +// 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 new file mode 100644 index 0000000..35c6c39 --- /dev/null +++ b/data/db/validators.ts @@ -0,0 +1,245 @@ +// ============================================================ +// 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/binance.ts b/data/exchanges/binance.ts new file mode 100644 index 0000000..f474a9c --- /dev/null +++ b/data/exchanges/binance.ts @@ -0,0 +1,395 @@ +// ============================================================ +// exchanges/binance.ts — 通用交易所 WebSocket 行情采集类 +// ============================================================ +// 基于 ccxt.pro 实现,不限于 Binance,支持任意 ccxt 支持的交易所。 +// 构造即启动:传入交易所 ID + 交易对列表,自动开始 WebSocket 监听。 +// +// 使用方式: +// import { ExchangeWs } from "./exchanges/binance"; +// const ws = new ExchangeWs({ +// exchangeId: "binance", +// symbols: ["BTCUSDT", "ETHUSDT"], +// interval: "1m", +// }); +// ws.on("kline", (data) => console.log(data)); +// ============================================================ + +import ccxt from "ccxt"; +import { EventEmitter } from "node:events"; + +import type { + KlineWsData, + ExchangeWsConfig, + WsConnectionState, +} 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 添加严格的事件签名 +// ============================================================ + +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; +} + +/** 带 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; + } +} + +// ============================================================ +// ExchangeWs — 主类 +// ============================================================ + +export class ExchangeWs extends EventEmitter { + // ---- 配置 ---- + private readonly exchangeId: string; + private readonly symbols: string[]; + private readonly interval: string; + private readonly ccxtOptions: Record; + + // ---- 运行时状态 ---- + 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(); + + // ============================================================ + // 构造器:传入参数,自动启动 WebSocket 监听 + // ============================================================ + + /** + * @param config.exchangeId - 交易所 ID(binance / okx / bybit / …) + * @param config.symbols - 要订阅的交易对列表 + * @param config.interval - K 线周期,默认 '1m' + * @param config.ccxtOptions - 传递给 ccxt.pro 交易所构造器的额外选项 + * + * 构造完成后 WebSocket 连接立即在后台启动, + * 监听 'ready' 事件确认全部就绪,或 'kline' 事件接收数据。 + */ + 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) { + return; + }; + + for (const symbol of toAdd) { + this.symbols.push(symbol); + const task = this.watchSymbol(symbol); + this.watchTasks.push(task); + } + } + + /** + * 停止 WebSocket 监听并释放资源 + * 调用后实例不可复用,需重新 new + */ + async close(): Promise { + // 1. 取消所有 watch 循环 + this.abortController?.abort(); + + // 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, + ); + + // 重置错误计数(成功获取数据) + consecutiveErrors = 0; + + // 标记该 symbol 已就绪 + if (!this.readySymbols.has(symbol)) { + this.readySymbols.add(symbol); + this.emit("symbolReady", symbol, this.interval); + } + + // 处理返回的 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, + ), + ); + + // 指数退避等待(可被 abort 中断) + await this.sleep(delay, signal); + } + } + } + + /** 处理 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; + }; + + 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], + }; + + this.emit("kline", data); + } + } + + /** 等待至少一个 symbol 就绪(或超时/全部失败) */ + private async waitForAnyReady(): Promise { + return new Promise((resolve, reject) => { + let resolved = false; + + const onReady = () => { + if (!resolved) { + resolved = true; + cleanup(); + resolve(); + } + }; + + 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); + } + } +} diff --git a/data/exchanges/types.ts b/data/exchanges/types.ts new file mode 100644 index 0000000..87ed617 --- /dev/null +++ b/data/exchanges/types.ts @@ -0,0 +1,45 @@ +// ============================================================ +// exchanges/types.ts — WebSocket 事件数据类型 +// ============================================================ + +/** 由 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; +} + +/** ExchangeWs 构造参数 */ +export interface ExchangeWsConfig { + /** 交易所 ID,ccxt 支持的所有交易所标识 */ + exchangeId: string; + /** 要订阅的交易对列表,如 ['BTCUSDT', 'ETHUSDT'] */ + symbols: string[]; + /** K 线周期,默认 '1m' */ + interval?: string; + /** 传递给 ccxt.pro 交易所实例的额外选项(如 agent、apiKey 等) */ + ccxtOptions?: Record; +} + +/** ExchangeWs 连接状态 */ +export type WsConnectionState = + | "idle" // 尚未启动 + | "connecting" // 正在连接 WebSocket + | "connected" // 已连接,正在接收数据 + | "disconnected" // 已断开 + | "error"; // 错误状态 diff --git a/data/init-db/001_init.sql b/data/init-db/001_init.sql index 3bab82d..eab99de 100644 --- a/data/init-db/001_init.sql +++ b/data/init-db/001_init.sql @@ -221,7 +221,7 @@ ALTER MATERIALIZED VIEW klines_1d SET (timescaledb.compress = true); -- ============================================================ DO $$ BEGIN - RAISE NOTICE 'TimescaleDB initialization complete.'; + 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'; diff --git a/data/init-db/002_config.sql b/data/init-db/002_config.sql new file mode 100644 index 0000000..550438b --- /dev/null +++ b/data/init-db/002_config.sql @@ -0,0 +1,136 @@ +-- ============================================================ +-- 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 new file mode 100644 index 0000000..a3c98d1 --- /dev/null +++ b/data/package-lock.json @@ -0,0 +1,5320 @@ +{ + "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 d88b975..0981770 100644 --- a/data/package.json +++ b/data/package.json @@ -22,6 +22,7 @@ "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", diff --git a/data/run/exchange.ts b/data/run/exchange.ts new file mode 100644 index 0000000..474b3bc --- /dev/null +++ b/data/run/exchange.ts @@ -0,0 +1,19 @@ +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 new file mode 100644 index 0000000..ee883ad --- /dev/null +++ b/data/schema/config.sql @@ -0,0 +1,177 @@ +-- ============================================================ +-- 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 new file mode 100644 index 0000000..1d15807 --- /dev/null +++ b/data/schema/klines.sql @@ -0,0 +1,206 @@ +-- ============================================================ +-- 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/docker-compose.yml b/docker-compose.yml index 58070ef..4a927a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,3 +26,9 @@ services: interval: 10s timeout: 5s retries: 5 + + adminer: + image: adminer + restart: always + ports: + - 8080:8080