Skip to content

Storage

tCAD's two storage backends behind one domain contract: the JSON file backend, SQL via tDB or oxmysql, the atomic write path, the first-boot seed, and how to back the data up.

tCAD has two storage backends and one domain contract. Nothing above that layer mentions SQL, and nothing inside it accepts a request, a header or a cookie.

BackendNeedsSuitable for
JSONNothing at allA small server, a test server, or a server with no database
SQLtDB (recommended — no MySQL server required) or oxmysqlAnything larger

Which one you get is decided by Config.storage; see Configuration.

They are substitutable, and that is measured

The contract file is normative, both adapters implement it, and a completeness check runs at boot: a missing operation is named at startup, by name, rather than turning up as a nil call mid-shift.

Both adapters pass the same 35-assertion self-test, 35 of 35, in a real FXServer — the JSON one against a real data.json, the SQL one against real SQLite through tDB. That is the substitutability claim demonstrated rather than asserted. Getting there took five rounds of execution finding what reading the code could not, including one worth naming because of its shape: two read operations were SELECT *, which cannot alias, so every call field came back in the database's column case where the UI reads another. No error, no exception — just a board rendering blanks on one backend and not the other.

The JSON backend

One file, data.json, in the resource folder, plus a data.json.bak. It holds everything — agencies, departments, calls, units, records.

It is gitignored and never shipped. A shipped data.json would silently overwrite a server owner's data on update.

The write path

Every write is: write a temporary file → fsync → rename over the target, with the previous version rotated to .bak. rename within a filesystem is the atomic primitive, so a reader never sees a half-written document.

Prove it on your own server:

tcad_writeprobe

in the server console. It reports whether a write actually landed, independently of whether anything else looked healthy.

Recovery

If data.json is unreadable or truncated at startup, tCAD recovers from data.json.bak rather than starting empty. Starting empty on a corrupt file would reseed over a server's history.

The one thing that cannot be proven from inside a process is crash consistency: demonstrating atomicity under power loss needs a crash, not a test. The .bak rotation is therefore kept as the recovery path rather than replaced by confidence in the rename.

Backing it up

bash
# With the server stopped, or at least between writes.
cp data.json data.json.$(date +%F)

data.json and data.json.bak are the whole of your data on this backend. Two things to know:

  • They are in the resource folder, so a deployment process that replaces the resource folder wholesale deletes them. They are escrow_ignore-adjacent in intent but not in mechanism: nothing in the update path protects a file that is not in the release archive, because it is not in the release archive at all.
  • A copy taken mid-write is a copy of the previous version, not a torn one — that is what the temp-file-and-rename scheme buys. It does not mean a copy is a consistent snapshot across several writes.

The SQL backend

Fourteen tables, created on first boot. The DDL is deliberately inside the intersection of the tDB and oxmysql dialects: no RETURNING, no DATE_ADD/INTERVAL, no GROUP_CONCAT … SEPARATOR, and explicit collation. A statement one backend accepts and the other refuses is the failure that lands on a customer rather than on a developer.

started is not ready

A database resource becomes able to answer asynchronously. Measured: tDB read as started about 2.5 seconds before it could serve a query. So tCAD probes a real export and waits, and until a backend is committed every operation answers starting.

The repair that measurement produced is worth stating because its absence was worse than the race: a latch on "no database yet" turned a 2.5-second startup race into a permanently unavailable schema. Schema creation therefore retries while the host is absent and latches only on a statement the database actually rejected.

Which resource is serving it

tcad_sqlprobe

reports which export names resolve and through which resource. That matters because provide 'oxmysql' satisfies a dependency and sets the resource state, and it does not alias the export namespace — measured, all 22 export names resolve via tDB's own namespace and none via oxmysql's. tCAD resolves the host resource name at runtime for that reason.

Backing it up

Use your database's own tooling. tCAD has no export or dump command of its own, and a CAD-shaped export would be a second format to keep correct.

The first-boot seed

Config.seed decides which world is created, and both backends seed the same world from one shared table. Seeding happens only into empty tables, so changing the value later does nothing — a staff member's edit to a label survives every restart.

tcad_reseed

re-runs it. See Configuration for what each value creates and why 'full' is the default.

Switching backends

There is no migration path between the two. The JSON document and the SQL schema hold the same domain, and nothing converts one into the other.

So a backend choice is effectively made once, at install. Switching means starting from a fresh seed, with the previous data readable only through whatever tooling that backend has. If you expect to need SQL, install tDB before you have data worth keeping.

Was this page helpful?