参考

Federation

Federation 是服务端运行时容器,管理 Service 生命周期、鉴权和 HTTP 路由。

Federation 是 Downcity 服务端的核心实例(即文档里说的 Federation)。它封装了 HTTP 路由、鉴权、环境变量和数据库基础设施。客户端访问入口是 City,不要和它混淆。

最小使用

import { Federation } from "@downcity/city";

const base = new Federation({ db });

Service 与 Action

每个 Service 是一组 Action。通过 base.use(service) 注册:

import { Service } from "@downcity/city";

const translate = new Service({ id: "translate", name: "翻译" });

// 注册 Action
translate.action("zh2en", async (ctx) => {
  return { result: await api.translate(ctx.input.text, "en") };
});

translate.action("en2zh", async (ctx) => {
  return { result: await api.translate(ctx.input.text, "zh") };
});

base.use(translate);

Federation 自动为每个 Action 生成 HTTP 路由:

POST /v1/translate/zh2en  →  translate.action("zh2en")
POST /v1/translate/en2zh  →  translate.action("en2zh")

AI Service

AIService 是内置的 Service,用于管理 AI 模型:

import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { AIService, Provider, type OpenAICompatibleClientConfig } from "@downcity/city";

class DeepSeekProvider extends Provider {
  constructor() {
    super({
      id: "deepseek",
      baseURL: "https://api.deepseek.com/v1",
      envKey: "DEEPSEEK_API_KEY",
    });
  }

  protected createClient({ apiKey, baseURL }: OpenAICompatibleClientConfig) {
    return createOpenAICompatible({ apiKey, baseURL, name: "deepseek" });
  }
}

const deepseek = new DeepSeekProvider();

const ai = new AIService();
ai.use(deepseek.model({ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash" }));
base.use(ai);

Action 注册选项

// 默认 POST,auth=["user"]
svc.action("zh2en", fn);

// GET action
svc.action("list", fn, { method: "GET" });

// Admin action
svc.action("remove", fn, { auth: ["admin"] });

// Guest action(免登录)
svc.action("webhook", fn, { auth: [] });

Hook 三层

// Action 级 hook(只在这个 action 触发)
const zh2en = svc.action("zh2en", fn);
zh2en.before(checkBalance).after(deductFee);

// Service 级 hook(所有 action 共享)
svc.hook.before(logRequest);

// 执行顺序:
//   action.before → service.before → action.run → service.after → action.after

路由启动

import { serve } from "@hono/node-server";
serve({ fetch: (request) => base.fetch(request), port: 43127, hostname: "127.0.0.1" });

HTTP Middleware

base.middle() 用于注册 Federation 级 HTTP middleware。它们会在内部路由和 Action body 读取之前执行,因此调用方可以在 Federation 入口接入 CORS、安全响应头、body 大小限制、粗粒度限流、请求超时或其他 HTTP 策略。

@downcity/city 只定义 middleware 接口,不实现也不导出 CORS、限流、超时、安全响应头、body 限制、客户端 IP 读取等 helper。这些策略应由你的应用、平台或 HTTP 框架提供。

base.middle(async (ctx, next) => {
  const started_at = Date.now();
  const response = await next();
  const headers = new Headers(response.headers);
  headers.set("X-Downcity-Time", String(Date.now() - started_at));
  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers,
  });
}));

middleware 按注册顺序进入,按相反顺序退出。middleware 可以直接返回 Response 来短路请求,也可以 await next() 后修改响应头。

instruction 文档聚合

Federation 可以直接返回当前实例的说明文档字符串:

const text = await base.instruction();

这份文本会聚合:

  • Federation 自身怎么使用
  • 当前注册的 service
  • 每个模块声明的 env 配置
  • 服务补充的使用说明

如果你要从远程管理端读取同一份说明,可以调用:

GET /v1/base/instruction

这个接口只允许管理端访问,返回 text/plain

鉴权

Federation 自动处理 user_tokenadmin_secret_key 的校验。Action 默认要求 user_token;传 auth: ["admin"] 时只允许管理端访问,传 auth: [] 时表示免登录。

详见 信任边界