Concepts

Last updated 29 October 2026

View .md

Read this before you build: it names the parts of a database that every other page refers to, in the order your program meets them.

A Tonbo database is SQLite: your program runs SQL on a SQLite file on its own disk, at the speed of SQLite. The difference is that the file is not owned by this machine: Tonbo holds the database, the file is a copy of it, and any machine that opens the database by name gets that copy.

How it differs from the SQLite or Postgres you know

  • Against a SQLite file: losing the machine loses only the commits synced() has not confirmed, and the program carries on from another machine.
  • Against a hosted Postgres: no server stands between a statement and the data. A statement runs on the local copy and returns before its commit leaves the machine.
  • One writer, on every machine: SQLite lets one connection write a file at a time, and Tonbo lets one process write a database at a time, wherever it runs.
  • The same SQL, transactions and indexes. Leave the journal mode and the page size alone (see Rules: SQLite).

A database

A SQLite database that Tonbo keeps for your account, in one region, under a name unique in the account. You create it in the console, with the CLI or through the Management API, the HTTPS API at https://api.tonbo.dev that creates, lists and deletes databases and streams; your program opens it by that name with an API key, from any machine (see Manage databases). A database lives until you delete it, or until the expiry it was created with passes (see Expiry).

The local copy

The process that opens a database works on a local copy: a SQLite file in a cache directory on its own disk (the dir option of open(), also called the cache root), which @tonbo/sqlite-ursula, Tonbo’s Node.js package, builds and keeps up to date. Statements run on it at the speed of SQLite, through a node:sqlite DatabaseSync. The files belong to the package: other SQLite tools cannot open them, so to read the data elsewhere, export a copy. Keep the cache directory on a disk that outlives the process, so a restart resumes instead of rebuilding (see Requirements).

A commit and synced()

A commit returns as soon as it is on this machine, and Tonbo takes it in the background. await db.synced() resolves once Tonbo holds every commit made before the call, in two of three replicas in different zones, and from then on the commit is durable. Until then a commit is lost with the machine, so await synced() before anything outside the process acts on one (see When a commit is durable).

The writer, and readers

One process writes a database at a time: the last one to open it. The process before it is refused from then on, even if it is still running: its commits no longer reach Tonbo and its next synced() throws UrsulaResetError with the kind fenced, the package’s word for a writer that another one replaced, so it should stop (see When two instances run). Inside the writing process, any number of connections share the database. Another process that only reads can open it read-only, as it was when it opened, without taking it from the writer (see Read-only opens).

The region and the URL

A database lives in one region, chosen when it is created and fixed for its life. Its URL names that region’s origin, your account’s id and the database’s name; opening by URL skips the lookup of the name (see Regions and URLs).

Keys and tokens

An API key, which starts with tbo_, belongs to one account and acts as you or as an Agent of that account. Your program gives it to open(), which exchanges it for short-lived access tokens and keeps one fresh. Give each program a key of its own, narrowed to the databases it uses (see What a key can reach).

What Tonbo keeps

Tonbo keeps each database as its latest snapshot, a compressed copy of the whole database, and the log of commits since the snapshot before it. The size limit and the storage price are stated in those terms: a database must fit a snapshot of 256 MiB compressed (Limits), and its storage is that snapshot and that log (Pricing).

Private and public

A new database is private: reads and writes need a key of the account. An owner or admin can make it public, so anyone with its URL reads it without a key; writes still need one (see Public and private).

How it reaches your program

Your program opens a database by name. Where its disk has no copy, the copy is built from Tonbo’s latest snapshot and the commits after it; where it has one, a restart starts from it instead of rebuilding. From then on statements run on the copy, and each commit goes to Tonbo in the background.

  YOUR MACHINE
  +----------------------------------------+
  |  your program --SQL--> the local copy  |       a commit returns here
  +-------------------+--------------------+
                      |  in the background
                      v
  TONBO, in the database's region
  +------------+  +------------+  +------------+
  |   zone a   |  |   zone b   |  |   zone c   |   two of three hold it:
  +------------+  +------------+  +------------+   synced() resolves
                      |
                      |  open by name: the latest snapshot,
                      v  then the commits after it
  ANOTHER MACHINE, later
  +----------------------------------------+
  |  your program --SQL--> a copy, rebuilt |       it writes now; the one
  +----------------------------------------+       before it is refused

A commit lands on this machine first; once synced() resolves, two of three replicas, each in its own zone of the region, hold it, which is why another machine that opens the database by name finds every commit synced() confirmed.

A database, a stream and a Machine

A databaseA streamA Machine (Not open yet)
Question it answersWhere does my program’s state live, so another machine carries on from it?How do many programs share one ordered log?Where does my service run, asleep until a request comes?
Your program reaches itSQL on a local copy, opened by nameHTTPS requests to its URL, from any languageHTTPS requests to its name
Who writesOne process at a time: the last one to open itAny number of producers, each retried append onceYour program, writing its own database
Durable whensynced() resolvesThe append answers 200In its database, once synced() resolves; never in memory