Packages 包@downcity/city

Store 与 Table

City 的数据层、表能力和为什么 town、env、usage 最终都会落到 store 层。

Downcity 最常被看见的是 AI 调用,但它能长期稳定运行,靠的是一套明确的数据层。

这个概念在说什么

很多 City 能力都不是纯内存状态,它们最终都必须落到数据事实:

  • town 是否 active
  • 某个 token 属于谁
  • 某个 env key 当前值是什么
  • 某次 service 调用是否已记录 usage
  • 某个用户是否已经拿到 entitlement

这就是为什么 @downcity/city 里不仅有 HTTP 路由,还有 store / table 这一层。

什么时候你需要直接关心这一层

  • 你在排查 service 有没有真的写表
  • 你要把 City 数据和自己的业务表联动
  • 你想从代码里读写某些受控表
  • 你在做运维排查,不想只看 HTTP 返回

base.table() 代表什么

base.table(name) 不是“随便暴露数据库”,而是 City 对底层数据库能力的受控暴露。

它给你的不是随意 SQL 字符串,而是围绕 City 生命周期统一管理的表接口。

最小示例:读写 City 表

const towns = await base.table("towns");
const env = await base.table("env");
const notes = await base.table("notes.notes");

await notes.insert({
  id: "note_1",
  title: "First note",
  status: "draft",
});

const draftNotes = await notes.select({
  status: "draft",
});

这段代码说明三件事:

  1. City 内置表可以直接拿
  2. 你自己的 service 表也可以通过统一入口拿
  3. 数据操作最终受 runtime 和 City 生命周期治理

你通常会碰到哪些表

City 内置表

  • towns
  • env

它们支撑的是:

  • 产品状态
  • 运行时环境变量
  • City 的管理基础设施

service 表

不同 service 会带自己的表,例如:

  • accounts 相关用户 / session 表
  • usage 事件表
  • Stripe entitlement / webhook 事件表

你自己的业务表

如果你在 service 里定义了业务表,也会进入同一个统一 runtime 数据层。

常见场景

场景一:确认数据到底有没有落进去

const usageEvents = await base.table("service_usage_events");
const rows = await usageEvents.select();

这类场景常见于:

  • usage 为什么看起来没记上
  • Stripe webhook 进来了没有
  • 账号注册后表里有没有数据

场景二:程序里做受控更新

const envTable = await base.table("env");

await envTable.update({
  where: { key: "OPENAI_API_KEY" },
  values: { value: "new-secret" },
});

适合:

  • 可信环境里的受控维护
  • 内部脚本
  • 管理后台后端逻辑

场景三:删除或清理某类数据

const notes = await base.table("notes.notes");
await notes.delete({ id: "note_1" });

适合:

  • 业务清理
  • 测试环境重置
  • 服务联动逻辑

常用 API / 入口

这一层最重要的 API 很少:

  • await base.table(name)
  • table.select(where?)
  • table.insert(values)
  • table.update({ where, values })
  • table.delete(where)

如果你想看更底层方法签名,去读 Runtime APICity API

常见误解

City 不只是 API 代理

它后面还有长期存在的数据层,很多管理能力都建立在这些表上。

table() 不是让你跳过 City 设计

更推荐先通过:

  • service
  • service
  • Admin City

来表达业务入口;table() 更适合可信环境和受控排查。

不是所有表都应该直接给前端碰

前端产品应该继续走 User City / Admin City,而不是直接访问底层表。

继续阅读

  • 想知道数据库是怎么接进来的,读 City 运行时
  • 想在终端里查这些表,读 City CLI
  • 想看 service 会落哪些表,结合对应 package 页一起读