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
citiesenv
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.
Read next
- 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