How it works
CellB has one runtime core that does not know where it runs, plus an adapter for each platform. The same core runs a cell in a tab, in a Node process, or as one node of a celld fleet.
One core, ports for what differs
The core reaches the platform only through a small set of ports. Each platform supplies its own implementation:
| Port | What it is | Implementations |
|---|---|---|
OpenCellSql | A synchronous SQLite database per cell | node:sqlite, SQLite WASM on OPFS, SQLite WASM in memory |
CellStorage | Durability, backup, ownership and R2 objects | celldBucket, filesApiLtxLog, filesApiChangeLog |
Lease | Per-cell ownership with an epoch and a "lost" signal | celldBucket(…).ownership |
ObjectStore | A bucket with conditional writes | S3, celld dev's SQLite store, memory, OPFS |
The SQL port is synchronous on purpose. ctx.storage.sql.exec() returns rows at once, as on Cloudflare, and both drivers are synchronous underneath.
The path of a request
Request ─► runtime.fetch ─► Worker's fetch(req, env, ctx)
│ env.COUNTER.getByName("a")
▼
stub ─┬─ local: the object's host ─► instance
├─ another shard (HTTP over a MessagePort)
└─ the owner node (cells-http or celld's tunnel)Inside a cell
- Implicit transaction. The first SQL statement opens a transaction, and it commits when the current turn of the event loop ends.
- Input gate. Events are queued, so a read-modify-write across storage awaits is safe.
- Output gate. A reply leaves only after the commit it depends on is durable.
- Reset. If a commit fails or the lease is lost, the object breaks, and the next call builds a fresh instance.
Ownership depends on the deployment
| Deployment | Unit owned | Mechanism |
|---|---|---|
| One process | nothing to share | none needed |
| A browser origin, sharded | a shard | one Web Lock per shard, held by one shard worker |
| A fleet bucket (celld, CellB, browsers) | a cell | celld's own.json per cell, a node lease per node, epochs and self-fencing |
Durability
With an LTX log (celldBucket or filesApiLtxLog) every acknowledged write is restorable: the RPO is 0. The first commit of a generation is a snapshot, and later commits carry only the changed pages. In a browser, SQLite WASM reports those pages through a hook on its database file. A periodic backup alone is cheaper, but it can lose up to one interval of writes.
Cells on LTX files in a browser
With bucket: { ltx, node }, a browser shard keeps each cell exactly as celld keeps it: a snapshot and one LTX file per commit, in an OPFS directory. SQLite never holds the database. It reads pages through a VFS from a store worker, with an LRU page cache and read-ahead, so a cell's memory is its page cache, whatever its size. Each commit becomes one LTX file, written under a temporary name and moved into place. The store compacts a cell into a new snapshot when its files grow, and holds a Web Lock per cell, so only one tab or worker writes a cell at a time.
Loaded Workers get a realm of their own
A Worker Loader binding runs each loaded Worker in its own isolate: a worker thread in Node, a Dedicated Worker in a browser. Plain values in its env are copied, and bindings become stubs back to the parent. This is how a host runs plugin or agent-written code apart from its own globals.