Files
trade/data/config/validators.ts
T
Rekey b4c7636731 添加 USDT-M 合约数据支持(配置层 + 清理多余字段)
- 配置层:env.yaml 新增 binance_futures API Key 段,validators + config 同步
- 清理 TradingPair 实体:删除 kline_interval、kline_intervals、kline_synthesis_enabled
- 删除 fetchKlines 系列函数的 interval 参数,硬编码为 1m
- 更新 SQL seed 数据、example、base_rest 接口、types 接口
- 新增 AGENTS/08-boundaries.md 执行纪律
- 新增 PLAN-add-futures-data.md 方案文档
2026-06-15 23:24:21 +08:00

200 lines
6.8 KiB
TypeScript

// ============================================================
// validators.ts — env.yaml 配置类型定义与运行时校验
// ============================================================
// 职责:
// 1. 定义 env.yaml 的 TypeScript 接口
// 2. 提供运行时校验函数(零依赖,手动实现)
// 3. 为 config.ts 提供类型安全的配置读取
//
// 使用方式:
// import { validateConfig, type EnvConfig } from "./db/validators";
// const raw = validateConfig(parsedYaml);
// ============================================================
/** env.yaml 顶层结构 */
export interface EnvConfig {
db: DbConfig;
redis: RedisConfig;
exchange: ExchangeConfig;
logging: LoggingConfig;
}
export interface DbConfig {
host: string;
port: number;
name: string;
user: string;
password: string;
}
export interface RedisConfig {
url: string;
publish_enabled: boolean;
}
/** 交易所 API 密钥配置(按交易所 ID 索引) */
export interface ExchangeConfig {
/** 现货市场 API Key */
binance: ExchangeApiKeys;
/** USDT-M 永续合约 API Key */
binance_futures: ExchangeApiKeys;
// 未来扩展:okx、bybit 等
[exchangeId: string]: ExchangeApiKeys | undefined;
}
export interface ExchangeApiKeys {
api_key: string;
api_secret: string;
}
export interface LoggingConfig {
level: "trace" | "debug" | "info" | "warn" | "error" | "fatal";
node_env: "development" | "production" | "test";
}
// ============================================================
// 运行时校验(零依赖)
// ============================================================
const VALID_LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"] as const;
const VALID_NODE_ENVS = ["development", "production", "test"] as const;
/**
* 校验并返回类型安全的配置对象。
* 校验失败时抛出明确错误信息,遵循 fail-fast 原则。
*/
export function validateConfig(raw: unknown): EnvConfig {
if (typeof raw !== "object" || raw === null) {
throw new Error(`[config] env.yaml 顶层必须为 object,实际: ${typeof raw}`);
}
const obj = raw as Record<string, unknown>;
// --- db ---
const db = obj["db"];
if (typeof db !== "object" || db === null) {
throw new Error("[config] env.yaml 缺少 db 配置段");
}
const dbObj = db as Record<string, unknown>;
const dbHost = assertString(dbObj["host"], "db.host");
const dbPort = assertPort(dbObj["port"], "db.port");
const dbName = assertString(dbObj["name"], "db.name");
const dbUser = assertString(dbObj["user"], "db.user");
const dbPassword = assertString(dbObj["password"], "db.password");
// --- redis ---
const redis = obj["redis"];
if (typeof redis !== "object" || redis === null) {
throw new Error("[config] env.yaml 缺少 redis 配置段");
}
const redisObj = redis as Record<string, unknown>;
const redisUrl = assertString(redisObj["url"], "redis.url");
const redisPublishEnabled = assertBoolean(redisObj["publish_enabled"], "redis.publish_enabled");
// --- exchange ---
const exchange = obj["exchange"];
if (typeof exchange !== "object" || exchange === null) {
throw new Error("[config] env.yaml 缺少 exchange 配置段");
}
const exObj = exchange as Record<string, unknown>;
const binance = exObj["binance"];
if (typeof binance !== "object" || binance === null) {
throw new Error("[config] env.yaml exchange 缺少 binance 配置");
}
const binanceObj = binance as Record<string, unknown>;
const binanceApiKey = assertString(binanceObj["api_key"], "exchange.binance.api_key");
const binanceApiSecret = assertString(binanceObj["api_secret"], "exchange.binance.api_secret");
// --- binance_futures ---
const binanceFutures = exObj["binance_futures"];
if (typeof binanceFutures !== "object" || binanceFutures === null) {
throw new Error("[config] env.yaml exchange 缺少 binance_futures 配置");
}
const futuresObj = binanceFutures as Record<string, unknown>;
const futuresApiKey = assertString(futuresObj["api_key"], "exchange.binance_futures.api_key");
const futuresApiSecret = assertString(futuresObj["api_secret"], "exchange.binance_futures.api_secret");
// --- logging ---
const logging = obj["logging"];
if (typeof logging !== "object" || logging === null) {
throw new Error("[config] env.yaml 缺少 logging 配置段");
}
const logObj = logging as Record<string, unknown>;
const logLevel = assertEnum(logObj["level"], VALID_LOG_LEVELS, "logging.level");
const nodeEnv = assertEnum(logObj["node_env"], VALID_NODE_ENVS, "logging.node_env");
return {
db: {
host: dbHost,
port: dbPort,
name: dbName,
user: dbUser,
password: dbPassword,
},
redis: {
url: redisUrl,
publish_enabled: redisPublishEnabled,
},
exchange: {
binance: {
api_key: binanceApiKey,
api_secret: binanceApiSecret,
},
binance_futures: {
api_key: futuresApiKey,
api_secret: futuresApiSecret,
},
},
logging: {
level: logLevel,
node_env: nodeEnv,
},
};
}
// ============================================================
// 辅助校验函数
// ============================================================
function assertString(value: unknown, path: string): string {
if (typeof value !== "string" || value.trim() === "") {
throw new Error(`[config] ${path} 必须为非空字符串,实际: ${JSON.stringify(value)}`);
}
return value;
}
function assertPort(value: unknown, path: string): number {
if (typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535) {
return value;
}
if (typeof value === "string" && /^\d+$/.test(value)) {
const n = parseInt(value, 10);
if (n > 0 && n <= 65535) return n;
}
throw new Error(`[config] ${path} 必须为有效端口号 (1-65535),实际: ${JSON.stringify(value)}`);
}
function assertBoolean(value: unknown, path: string): boolean {
if (typeof value === "boolean") return value;
if (value === "true" || value === "false") return value === "true";
throw new Error(`[config] ${path} 必须为 boolean,实际: ${JSON.stringify(value)}`);
}
function assertEnum<T extends readonly string[]>(
value: unknown,
allowed: T,
path: string,
): T[number] {
const s = String(value);
if ((allowed as readonly string[]).includes(s)) {
return s as T[number];
}
throw new Error(
`[config] ${path} 必须为 ${allowed.join(" | ")} 之一,实际: ${JSON.stringify(value)}`,
);
}