CellB

D1

env.<BINDING> of a d1_databases entry: prepared statements, exec and sessions (withSession). CellB runs Miniflare’s D1 simulator with workerd’s D1 client, both vendored; the database is a cell, with the application’s tables directly in its SQLite, as on celld.

{
  "name": "d1",
  "main": "index.js",
  "compatibility_date": "2026-01-01",
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "guestbook",
      "database_id": "00000000-0000-0000-0000-000000000000"
    }
  ]
}
const SCHEMA = `
CREATE TABLE IF NOT EXISTS entries (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  message TEXT NOT NULL,
  at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS entries_at ON entries (at DESC);
`;

export default {
  async fetch(request, env) {
    await env.DB.exec(SCHEMA);
    const url = new URL(request.url);

    if (request.method === "POST") {
      const { name, message } = await request.json();
      const written = await env.DB
        .prepare("INSERT INTO entries (name, message, at) VALUES (?, ?, ?)")
        .bind(name, message, Date.now())
        .run();
      return Response.json({
        id: written.meta.last_row_id,
        changes: written.meta.changes,
      }, { status: 201 });
    }

    const count = await env.DB
      .prepare("SELECT count(*) AS n FROM entries")
      .first("n");
    if (url.pathname === "/count") return Response.json({ count });

    const { results } = await env.DB
      .prepare(
        "SELECT id, name, message, at FROM entries ORDER BY at DESC LIMIT ?",
      )
      .bind(20)
      .all();
    return Response.json({ count, entries: results });
  },
};

This is celld’s d1 example (conformance/celld-examples/d1), copied unchanged and run by the conformance suite on Node and in Chromium.

A database is a cell named after its id

A database lives in a cell __D1Database:<id>, whose key does not depend on the script, so projects naming the same database id share it, as on celld. Sessions return the bookmark celld:primary: there is one copy, the cell’s.

Migrations are a function, not a CLI

applyNodeD1Migrations(dir, runtime, database) (@statewalker/cellb/node) does what celld d1 migrations apply does: it reads migrations_dir (default migrations/) of the d1_databases entry, applies the NNNN_description.sql files not applied yet in the order of their numbers, and records each in migrations_table (default d1_migrations, the table wrangler d1 migrations uses).

How it is checked

celld’s d1 example, D1 migrations, and CellB’s d1-details project, on Node and in Chromium.

What breaks

ATTACH, TEMP objects and VACUUM are refused, as celld refuses them.