Skip to content

Data and stores

For: the moment you need to know where the game's state actually is, read it, or get a copy of it out.

There is one store, and it is not a database you can open

Every piece of game state lives inside a single Cloudflare Durable Object. There is no D1 database, no KV namespace and no R2 bucket in this product.

The object is bound as UNIVERSE and implements the Universe class. It was created under migration tag v1 as a SQLite-backed Durable Object class, so its storage is a real SQL database — but one that exists only inside the running object.

That is the fact that shapes everything else on this page. There is no connection string, no dashboard query editor, and no client you can point at it. Reading the data means asking the Worker to read it for you.

One universe, addressed by name

The Worker routes every /api request to a Durable Object looked up by name, and only one name is accepted.

The name is main. A request naming any other universe is refused with UNKNOWN_UNIVERSE and a 404, rather than creating a second object.

One deployment therefore holds exactly one universe. Staging and production are separate Workers, so they hold separate objects with separate state, and neither can see the other.

What is in it

The storage is SQL, and the schema is relational. Accounts are a table with an identifier, an email and an admin flag; colonies, buildings, research, missions and the event history sit alongside them.

The tables are read almost entirely through the SQL interface rather than through key-value storage. A small number of key-value entries remain for values that are not rows.

How to read it

Four operator routes exist on the Durable Object itself. They are the only supported way in.

RouteMethodGives
/api/admin/overviewGETa read-only summary of the universe's current state
/api/admin/exportGETa copy of the universe's data
/api/admin/pitrGETthe point-in-time recovery bookmarks available
/api/admin/pitr/restorePOSTa restore to one of those points

All four are gated behind the ADMIN_ROUTES variable being set to 1, and behind holding owner on that deployment.

Which environment answers them

This is the part that matters most, and it is a deliberate asymmetry.

EnvironmentAdmin routesConsequence
devon, locallyread and export freely
stagingon, set by the deploy scriptread and export freely
productionoffthe four routes above do not answer

Production runs with admin routes off, which is what keeps them from being a live attack surface. Turning them on for production means deploying with ADMIN_ROUTES set, which is a deliberate act with a deliberate cost, and turning them off again afterwards.

Plan investigations against staging wherever the question can be answered there.

Getting a copy out

The export route is the supported route:

bash
curl -H "Cookie: <your owner session>" https://<base url>/api/admin/export > universe-export.json

Claim owner first, through the claim-link page that owner-claim.mjs builds. Without an owner session the route refuses.

There is no scheduled backup in this repository and no export runs on a timer. A copy exists when you take one.

Point-in-time recovery

Cloudflare keeps point-in-time bookmarks for a SQLite-backed Durable Object, and the two pitr routes surface them.

Read the available points first, and only then restore. A restore replaces live state, so on production it is the most destructive operation available through any route in this product.

Resetting

Staging is wiped by deploying with a reset token it has not seen before. The universe drops every user-state table on its next boot when the value differs from the stored one:

bash
GW_STAGING_RESET=$(date +%s) npm run deploy:staging

Production has no such switch. Nothing in the deploy tooling can wipe it.

Locally, the dev loop's --fresh flag discards the local universe and boots a clean one.

Credentials this needs

NameNeeded forWhere it lives
owner sessionany admin routeclaimed through the claim-link page, per deployment
owner tokenbuilding a claim linkminted locally at ~/.grafted-wars/owner-token
ADMIN_EMAILthe worker answering at alla per-worker Cloudflare secret

Credentials and access covers where each is obtained.

Sharp edges

A minted account store holds live credentials. It is gitignored and belongs in no commit and on no ticket.

A densely seeded staging universe running at 25x under sustained load can exceed the Durable Objects free-tier row-read quota. That reads as the universe failing and is a plan limit.

Staging and production hold different objects. An export taken from staging tells you nothing about a production defect.