Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

Cloudflare D1

SQLite driver for Cloudflare D1 using Worker bindings

Requirements

  • Cloudflare Workers environment (no nodejs_compat flag needed)

Configuration

import { createClient } from "viborm/d1";

export default {
  async fetch(request: Request, env: Env) {
    const client = createClient({
      database: env.DB, // D1 binding from wrangler.toml
      schema,
    });

    const users = await client.user.findMany();
    return Response.json(users);
  },
};

Options

Option Type Description
database D1Database D1 database binding from Worker env

Binary values

Blob fields bind Uint8Array values directly to D1, and D1’s byte-array results are normalized back to a plain Uint8Array — no Node Buffer or nodejs_compat flag involved.

wrangler.toml

[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Transactions and batching

D1 does not support an interactive callback transaction. VibORM uses the binding’s native batch() primitive for work that can be planned up front.

Atomic array transactions

The array API is one atomic D1 batch:

const [user, post] = await client.$transaction([
  client.user.create({ data: { name: "Alice", email: "alice@example.com" } }),
  client.post.create({ data: { title: "Hello", authorId: "preset-id" } }),
]);

Every operation in this array must be statically batchable. A dynamic record series is refused before the batch is submitted because $transaction([...]) promises one atomic commit.

Interactive transactions

Dynamic transactions (callback API) are unsupported and reject:

// Throws TransactionError: D1 does not support callback transactions
await client.$transaction(async (tx) => {
  const user = await tx.user.create({ data: { name: "Alice" } });
  await tx.post.create({ data: { title: "Hello", authorId: user.id } });
});

Relation-bearing bulk writes

Root createMany and updateMany calls whose records write relations need left-to-right decisions. Nested relation-bearing createMany and updateMany can need the same pause at their exact position in a record tree. Outside $transaction([...]), D1 executes supported series as ordered committed segments:

  • one submitted segment batch is atomic;
  • a later member observes earlier committed members;
  • successful results match transaction-capable drivers;
  • if a later segment fails, earlier segments stay committed;
  • the error reports the exact durable prefix;
  • VibORM never retries an already committed prefix.
import { isVibORMError } from "viborm";

try {
  await client.post.createMany({
    data: [
      { id: "p1", author: { create: { id: "a1", name: "first" } } },
      { id: "p2", author: { create: { id: "a2", name: "occupied" } } },
    ],
  });
} catch (error) {
  if (isVibORMError(error)) {
    console.log(error.toJSON().meta.recordSeriesProgress);
    // {
    //   atomicity: "segment",
    //   phase: "member",
    //   committedSegments: 1,
    //   completedMembers: 1,
    //   committedWriteMembers: 1,
    //   memberPath: [1],
    //   totalMembers: 2
    // }
  }
}

completedMembers is the completed input prefix. committedWriteMembers counts members whose user-table writes are durable. committedSegments counts committed D1 batches. The phase says whether the failure happened during capture, planning, an enclosing prefix, a member, an enclosing suffix, the final result read, or cache invalidation.

This progressive route applies to root relation-bearing createMany and updateMany. A nested relation-bearing createMany or updateMany also runs at its exact tree position when VibORM can re-assert the complete parent row or relation membership inside every later write batch.

For one inserted row with a database-generated integer key, the D1 driver normalizes the binding’s official meta.last_row_id as that statement’s generated identity. A later segment can therefore use the concrete key. VibORM never derives a range of generated keys from it and never assumes adjacent values for a multi-row insert.

The following shapes remain unavailable:

  • a relation-bearing createMany member with a write or nested series before its skippable root, because a root conflict would leave that earlier effect committed. A root-first member is supported: VibORM isolates the root batch and dispatches no descendants when it skips;
  • a nested series whose compiler cannot provide an exact complete-parent and membership guard. It is refused before the containing member writes. Earlier progressive root members can already be committed and are reported;
  • any dynamic record series inside explicit $transaction([...]).

Scalar bulk operations and other statically planned nested writes continue to use one atomic D1 batch. Inside that batch, a splittable createMany statement is divided into the largest chunks that fit D1’s conservative 100-bound-value budget. The same applies to a many-to-many connect/set junction insert over an already captured target-key list. The complete operation still succeeds or rolls back as one D1 batch.

Migrations

Migration V1 permits offline generation and read-only inspection on D1, but refuses every effectful push, apply, down, reset, and verify command before SQL. D1’s native batch alone does not prove table recreation, foreign-key handling, migration locking, and marker compare-and-swap together. Apply live schema changes with wrangler d1 migrations instead.

Capabilities

Interactive transactions reject. Static work uses one native atomic batch. Root dynamic relation-bearing bulk uses ordered segment-atomic batches with reported progress. See the feature matrix for the full comparison.

Limitations

  • Operations inside $transaction([...]) cannot read sibling results.
  • Nested dynamic writes require an exact compiler-provided parent/membership guard; shapes that cannot provide one reject.
  • Only available in Cloudflare Workers
  • SQLite dialect - no LATERAL joins, limited FULL OUTER JOIN
  • JSON columns are parsed automatically
  • Boolean values stored as integers (0/1)

Was this page helpful?