Packages@downcity/city

Federation

The role of the Federation instance, runtime, router, and serve inside @downcity/city.

@downcity/city's Federation is the long-running server container of Downcity (also called the Federation in these docs). It is where the Drizzle db, services, auth, and the shared /v1/* route space come together. Product-side access uses the City client; see Federation and City for the distinction.

What this concept means

A simple way to think about Federation is:

one reusable AI infrastructure runtime process that many products can keep calling over time.

That process owns:

  • the Drizzle database connection
  • the service registry
  • service mounting
  • the HTTP route surface
  • city, token, and env infrastructure

When this page matters

  • you are about to run a real Federation for the first time
  • you want to understand why Federation only needs a Drizzle db
  • you want clarity on router(), handleRequest(), and serve()
  • you want to mount Federation into Node, Workers, or another HTTP host

Minimal runnable example

import { AIService, Federation } from "@downcity/city";
import { serve } from "@hono/node-server";
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";

const sqlite = new Database("./.base/downcity.sqlite");
sqlite.pragma("journal_mode = WAL");

const db = Object.assign(drizzle(sqlite), {
  $client: { exec: (sql: string) => sqlite.exec(sql) },
});

const base = new Federation({ db });

const ai = new AIService();

ai.use({
  id: "local-echo",
  name: "Local Echo",
  default: ["text"],
  actions: {
    text: async (ctx) => ({
      id: crypto.randomUUID(),
      role: "assistant",
      parts: [
        {
          type: "text",
          text: String(ctx.input.prompt ?? ""),
          state: "done",
        },
      ],
    }),
  },
});

base.use(ai);

await base.health();

serve({
  fetch: base.router().fetch,
  port: 43127,
  hostname: "127.0.0.1",
});

The important ideas in that example are:

  1. db decides which Drizzle database Federation uses.
  2. base.use(...) decides which capabilities Federation exposes.
  3. base.router() and base.handleRequest() decide how Federation enters your HTTP layer.

Scenario 1: Federation is the main service

If you want Federation itself to be the main HTTP entry, serve() is the clearest mental model:

import { serve } from "@hono/node-server";

await base.health();
serve({ fetch: base.router().fetch, port: 3001, hostname: "127.0.0.1" });

Scenario 2: Federation is mounted into your existing server

If you already have a server framework, handing requests into Federation is often the cleaner pattern:

export default {
  async fetch(request: Request) {
    return base.handleRequest(request);
  },
};

Why Federation only takes db

@downcity/city owns the logic that should stay stable across runtimes:

  • service lifecycle
  • action routes
  • token auth
  • city / env / store infrastructure

The host runtime only needs to create the database object:

  • Node.js can use drizzle-orm/better-sqlite3 or Drizzle pg
  • Cloudflare Workers can use drizzle-orm/d1
  • Federation writes required runtime env into the built-in env table on boot

Default Storage

When a Federation needs to move runtime files into first-party storage, register a default storage backend:

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

const base = new Federation({ db });

base.storage(R2Storage({
  bucket: env.DOWNCITY_STORAGE,
  public_url_prefix: env.DOWNCITY_STORAGE_PUBLIC_URL_PREFIX,
}));

Services can then access it through ctx.storage. Built-in AI image jobs use this storage after image_fetch succeeds to move remote file part URLs into your own bucket. If storage fails, the original upstream URL is kept and the image job still succeeds.

Common API surface

  • new Federation({ db })
  • base.use(...)
  • base.storage(...)
  • base.router()
  • base.handleRequest(request)
  • base.health()
  • base.table(name)

Common misunderstandings

Federation is the runtime, not the whole SDK

Federation is the long-running server runtime. Product-side calling uses the City client; see Client SDK.

Federation is not only an HTTP proxy

It also owns built-in tables, token and env infrastructure, service data layers, and hook lifecycle.

Federation is not one provider

Providers, models, services, and custom services are capabilities mounted into Federation, not the Federation itself.