Skip to content

数据库 schema

AutoRouter 用 Drizzle ORM 维护数据库 schema,PostgreSQL 是首选生产数据库、SQLite 仅用于本地开发沙箱。所有表 / 关系 / 类型在源码里都有可靠的单一定义,转发链路、统计聚合、计费快照都从这些表读写。

这一页给出表清单、表与表的关系、JSON 列存储什么 TypeScript 类型、迁移如何管理、以及客户端怎么拿到 db 实例。所有引用都指向 master 分支源码。某张表在路由 / 计费 / 录制中是怎么被消费的,参见对应的架构页或使用文档。

两个 schema 文件,一个 barrel

数据库 schema 同时维护两份:

  • src/lib/db/schema-pg.ts —— PostgreSQL 版本,全部生产能力以这一份为准
  • src/lib/db/schema-sqlite.ts —— SQLite 版本,仅供本地沙箱
  • src/lib/db/schema.ts —— barrel,按 config.dbType 在两份之间切换

barrel 文件做的事情只有一件:

ts
// src/lib/db/schema.ts:1-5
import { config } from "../utils/config";
import * as pgSchema from "./schema-pg";
import * as sqliteSchema from "./schema-sqlite";

const schema = (config.dbType === "sqlite" ? sqliteSchema : pgSchema) as typeof pgSchema;

整个项目所有业务代码都从 @/lib/db 这个 barrel 导入表对象与类型,不直接 import schema-pgschema-sqlite,保证一份业务代码同时能跑在两套数据库上。

SQLite 不是平替

注释(src/lib/db/index.ts:14)明确说明:SQLite 在结构上对常规 CRUD 兼容,但 PERCENTILE_CONT 等 PG 专用 SQL 在 SQLite 上不可用。统计聚合(/api/admin/stats/*)在 SQLite 上会有部分查询直接报错。线上务必用 PostgreSQL。

表清单

schema-pg.ts 内总共定义 20 张表,按用途分四组:

客户端 Key 与上游

行号用途
api_keys44-69下游客户端 Key 与限额规则
upstreams74-128上游 provider 配置(详见 上游模型
upstream_health133-152上游健康状态与探测结果(一对一)
upstream_probe_results157-195协议能力 / 客户端 profile 的诊断探测结果
api_key_upstreams200-217Key ↔ Upstream 多对多授权

熔断器与失败规则

行号用途
circuit_breaker_states222-251每个上游一行的熔断器状态机(详见 失败转移与熔断
upstream_failure_rules257-274命中后免于触发熔断的失败规则(含全局与上游局部)

请求日志与录制

行号用途
request_logs279-342每次请求一行的审计日志(详见 请求日志与统计
traffic_recording_settings347-355流量录制全局单例配置
traffic_recordings360-390录制文件索引(详见 请求录制

计费与价格

行号用途
billing_model_prices395-417自动同步的模型价格目录(openrouter / litellm)
billing_manual_price_overrides422-436管理后台手动覆盖的价格
billing_tier_rules443-469按上下文长度分档计费规则
billing_price_sync_history474-486价格同步任务运行历史
request_billing_snapshots544-587每条 request_logs 一份的计费快照(一对一)

后台任务与扩展

行号用途
background_sync_tasks491-514后台任务调度状态(单例 per task name)
background_sync_task_runs519-539后台任务每次运行的历史
compensation_rules592-607出站 header 补偿 / 改写规则
cliproxy_instances718-738CLIProxyAPI 实例注册(详见 CLIProxyAPI 集成位置
cliproxy_auth_accounts744-771从 CLIProxyAPI 缓存的 OAuth 账号元数据

没有 users 表

api_keys.user_id 列保留为 nullable,无外键约束,源码 schema-pg.ts:53 注释 // Reserved for future user system。当前认证只有「客户端 API Key」与「Admin Bearer Token」两种身份,没有完整的用户系统。

外键与级联策略

子表父表onDelete
upstreamscliproxy_instance_idcliproxy_instancesset null
upstream_healthupstream_id (UNIQUE)upstreamscascade
upstream_probe_resultsupstream_idupstreamscascade
api_key_upstreamsapi_key_idapi_keyscascade
api_key_upstreamsupstream_idupstreamscascade
circuit_breaker_statesupstream_id (UNIQUE)upstreamscascade
upstream_failure_rulesupstream_id (nullable)upstreamscascade
request_logsapi_key_idapi_keysset null
request_logsupstream_idupstreamsset null
traffic_recordingsrequest_log_idrequest_logsset null
traffic_recordingsapi_key_idapi_keysset null
traffic_recordingsupstream_idupstreamsset null
request_billing_snapshotsrequest_log_id (UNIQUE)request_logscascade
request_billing_snapshotsapi_key_idapi_keysset null
request_billing_snapshotsupstream_idupstreamsset null
cliproxy_auth_accountsinstance_idcliproxy_instancescascade

一对一约束:靠 UNIQUE 字段实现,上面表里标 (UNIQUE) 的三行——upstream_health.upstream_idcircuit_breaker_states.upstream_idrequest_billing_snapshots.request_log_id——每条父记录最多对应一条子记录。

级联与设空的语义

  • cascade:父记录被删,子记录跟着被物理删除。删除一个 upstreams 行会同时清掉它的健康状态、探测结果、熔断器、授权关系等。
  • set null:父记录被删,子记录的外键列被设为 NULL,子记录本身保留。删除一个 upstreams 后,历史 request_logsrequest_billing_snapshots 仍然存在,但 upstream_id 变成 NULL,统计页面会显示「未知上游」。
  • cliproxy_instance_idset null 是为了允许「删除 CLIProxyAPI 实例时不影响上游配置本身」——但应用层在 deleteCliproxyInstance 会先做引用检查并抛 409,FK set null 实际只在绕过应用层删除时才会触发(详见 使用 / CLIProxyAPI 外部 vs sidecar)。

JSON 列与 TypeScript 类型

Drizzle 的 json() 列通过 .$type<T>() 注解绑定 TypeScript 类型,但数据库层不做运行时校验,类型安全只在编译期成立。所有 JSON 列:

注解类型
api_keysallowed_modelsstring[] | null
api_keysspending_rules{period_type, limit, period_hours?}[] | null
upstreamsroute_capabilitiesstring[] | null
upstreamsallowed_modelsstring[] | null
upstreamsmodel_redirectsRecord<string,string> | null
upstreamsmodel_discoveryUpstreamModelDiscoveryConfig | null
upstreamsmodel_catalogUpstreamModelCatalogEntry[] | null
upstreamsmodel_rulesUpstreamModelRule[] | null
upstreamsqueue_policyUpstreamQueuePolicy | null
upstreamsfailure_rule_config{useGlobalRules: boolean} | null
upstreamsaffinity_migration{enabled, metric, threshold} | null
upstreamsspending_rules{period_type, limit, period_hours?}[] | null
upstream_failure_rulesmatchUpstreamFailureRuleMatch
circuit_breaker_statesconfig{failureThreshold?,…,streamIdleTimeout?} | null
request_logsheader_diffHeaderDiff | null
compensation_rulescapabilitiesstring[]
compensation_rulessourcesstring[]
cliproxy_auth_accountsraw_metadataRecord<string, unknown> | null

UpstreamModelDiscoveryConfig / UpstreamModelCatalogEntry / UpstreamModelRule 等导入自 @/lib/services/upstream-model-typesschema-pg.ts:16-21)。UpstreamFailureRuleMatchUpstreamFailureRuleConfig 是 schema 文件内的本地类型(schema-pg.ts:29-39)。

request_logs 的 JSON 实际存为 text

request_logs.failover_historyrouting_decisionthinking_config 三列在 schema 中是 text 而不是 jsonschema-pg.ts:310-311),写入时调用 JSON.stringify、读取时手动 JSON.parse。这是历史选择,目的是兼容 SQLite 与避免某些 PG 版本对大 JSON 文档的索引问题。新加字段应优先用 json(),并补 $type<>() 注解。

索引

显式定义的非 PK / 非 UNIQUE 单列索引按表汇总:

索引列
api_keyskey_hash, is_active
upstreamsname, is_active, priority
upstream_healthupstream_id, is_healthy
upstream_probe_resultsupstream_id, status, checked_at
api_key_upstreamsapi_key_id, upstream_id
circuit_breaker_statesupstream_id, state
upstream_failure_rulesupstream_id, enabled, priority
request_logsapi_key_id, upstream_id, created_at, routing_type
traffic_recordingsrequest_log_id, api_key_id, upstream_id, status_code, model, created_at
billing_model_pricesmodel, source
billing_manual_price_overridesmodel
billing_tier_rulesmodel, source
billing_price_sync_historycreated_at
background_sync_tasksenabled, next_run_at
background_sync_task_runstask_name, started_at, status
request_billing_snapshotsrequest_log_id, billing_status, model, created_at
compensation_rulesenabled
cliproxy_instancesname, enabled
cliproxy_auth_accountsinstance_id

另有复合 UNIQUE 索引(同时充当复合查询索引):

复合唯一键
upstream_probe_results(upstream_id, route_capability, client_profile, probe_template_id)
api_key_upstreams(api_key_id, upstream_id)
billing_model_prices(model, source)
billing_tier_rules(model, source, threshold_input_tokens)
cliproxy_auth_accounts(instance_id, auth_file_name)

关系定义

Drizzle 的 relations() 没有放在独立文件,而是直接写在 schema-pg.ts:609-782 的尾部。每张表的关联关系(含一对多 / 多对一 / 多对多)都声明在那里,可以直接在 db.query.api_keys.findFirst({ with: { upstreams: true } }) 这样的查询里使用。

迁移目录

PostgreSQL 与 SQLite 各自有独立的迁移目录:

目录用途文件数
drizzle/PostgreSQL 迁移当前 40 个 SQL(最高编号 0037)
drizzle-sqlite/SQLite 迁移当前 16 个 SQL

两套迁移并不严格一一对应,因为某些 PG 特定能力(json 类型、gen_random_uuid()timestamptz)在 SQLite 上需要不同的表达方式甚至跳过。每次给 schema-pg.ts 加字段后,标准流程:

bash
# 1. 生成 PG 迁移
pnpm db:generate

# 2. 手动同步改 schema-sqlite.ts,并单独生成 SQLite 迁移
pnpm exec drizzle-kit generate --config=drizzle-sqlite.config.ts

drizzle/meta/_journal.json 记录 PG 迁移的应用顺序。最近五次 PG 迁移示例:

idxtag大致改动
330033_shocking_emma_frost创建 traffic_recording_settingstraffic_recordings
340034_youthful_sally_floyd创建 cliproxy_instances
350035_powerful_nightcrawler创建 cliproxy_auth_accounts
360036_furry_warhawkcliproxy_auth_accounts.provider / status 列拓宽为 text
370037_familiar_nico_minoruupstreamscliproxy_instance_id / cliproxy_auth_file_name / cliproxy_provider 列 + FK

客户端单例与连接池

源码:src/lib/db/index.ts

db 通过懒加载 Proxy 暴露给业务代码(index.ts:82),第一次访问时按 config.dbType 选择底层驱动:

  • PostgreSQL(生产):用 postgres 库(postgres.js)建立连接池
    • max: 10(最多 10 个连接)
    • idle_timeout: 20
    • connect_timeout: 10
    • 源码 index.ts:38-43
  • SQLite(开发):动态 require('@libsql/client')index.ts:59-63),按 SQLITE_DB_PATH 指向本地文件

db 是单例,跨整个 Node.js 进程共享。导出还包括 closeDatabase()index.ts:92)用于优雅停机时关闭连接池,主要在测试 setup / teardown 里调用。

类型导出

每张表都同时导出 $inferSelect(读类型)和 $inferInsert(写类型),命名约定 Xxx / NewXxx

ts
export type ApiKey = typeof apiKeys.$inferSelect;
export type NewApiKey = typeof apiKeys.$inferInsert;

类型定义在 schema-pg.ts:784-821,barrel 在 schema.ts:41-78 重新命名导出,业务代码统一从 @/lib/db 导入。src/types/api.ts 用这些基础类型组合出 API 请求 / 响应 DTO,所有 admin 路由(src/app/api/admin/**)和服务层都消费它们。

Released under the MIT License.