Packages@downcity/city

Store and Table

The Federation data layer, table capabilities, and why city, env, and usage facts eventually live in the store layer.

Downcity is often seen through AI calls, but its long-term reliability depends on a clear data layer.

What this concept means

Many Federation capabilities are not just in-memory state. They must persist as data facts:

  • whether a city is active
  • which token belongs to whom
  • what the current env value is
  • whether a call has been recorded as usage
  • whether a user currently has entitlement

That is why @downcity/city has not only routes and services, but also a store and table layer.

When you need to care about this layer directly

  • you are checking whether a service actually wrote its tables
  • you want your own business data to interact with Federation data
  • you need controlled trusted-side reads or writes
  • you are debugging operations and need facts, not only HTTP responses

What base.table() represents

base.table(name) is Federation's controlled exposure of table capability inside one runtime and lifecycle.

Minimal example: reading and writing tables

const cities = await base.table("cities");
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",
});

What tables you usually encounter

Built-in Federation tables

  • cities
  • env

Official service tables

Different services bring their own tables, such as accounts-related user or session tables, usage event tables, and Stripe entitlement or webhook event tables.

Your own business tables

If your services define their own tables, they also live in the same runtime-governed data layer.

Common scenarios

Scenario 1: confirm whether data really landed

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

Scenario 2: controlled trusted-side updates

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

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

Scenario 3: deletion and cleanup

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

Common API surface

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

Common misunderstandings

Federation is not only an API proxy

Many management and city capabilities depend on persistent data facts behind the route layer.

table() is not a reason to skip good service design

You should still prefer services, and Admin City for city-facing or managed entry points.

Frontends should not treat tables as their API

City-side callers should continue to use User City and Admin City, not raw table access.

  • For how the database is wired in, read Federation
  • For terminal inspection of these tables, read CLI
  • For service-specific data expectations, read the relevant service package docs together with this page