Skip to content
Pitlane

@pitlane/data-table-d1

A Cloudflare D1 driver for Remix 3's data-table.

D1 is SQLite, so the SQL is SQLite's, but the execution model is not: statements are prepared and awaited over an RPC binding, and the transaction verbs are rejected outright. @remix-run/data-table-sqlite builds on a synchronous client and cannot bridge that gap, so this package pairs the SQLite SQL compiler with a driver written against D1's async API.

Classes

D1Database

A Database bound to Cloudflare D1.

The same shape SqliteDatabase and PostgresDatabase have: a Database subclass that supplies its own driver, so every query, persistence and migration method comes from remix/data-table unchanged.

Extends

  • Database<"sqlite">

Constructors

Constructor
ts
new D1Database(binding, options?): D1Database;
Parameters
binding

D1Binding

options?

D1DatabaseOptions

Returns

D1Database

Overrides
ts
Database<"sqlite">.constructor

Accessors

capabilities
Get Signature
ts
get capabilities(): DatabaseCapabilities;

Immutable feature flags used by shared query and migration behavior.

Returns

DatabaseCapabilities

Inherited from
ts
Database.capabilities
dialect
Get Signature
ts
get dialect(): dialect;

Stable identifier for the SQL dialect.

Returns

dialect

Inherited from
ts
Database.dialect

Methods

batch()
ts
batch(statements): Promise<D1BatchResult[]>;

Runs statements together, atomically.

D1 has no transactions, so transaction() refuses. batch() is what it offers instead, and this is it without reaching for the raw binding:

ts
import { sql } from "remix/data-table";

await db.batch([
    sql`insert into post (title) values (${title})`,
    sql`update counter set posts = posts + 1`,
]);

The statements are SqlStatements rather than query-builder calls, because data-table exposes no way to build an operation without running it. sql still parameterises the values.

Parameters
statements

SqlStatement[]

Returns

Promise<D1BatchResult[]>

close()
ts
close(): Promise<void>;

Closes resources owned by this database.

Returns

Promise<void>

A promise that resolves when owned resources have been released.

Inherited from
ts
Database.close
count()
ts
count<table>(table, options?): Promise<number>;
Type Parameters
table

table extends AnyTable

Parameters
table

table

options?

CountOptions<table>

Returns

Promise<number>

Inherited from
ts
Database.count
create()
Call Signature
ts
create<table>(
   table, 
   values, 
options?): Promise<WriteResult>;
Type Parameters
table

table extends AnyTable

Parameters
table

table

values

Partial<TableRow<table>>

options?

CreateResultOptions

Returns

Promise<WriteResult>

Inherited from
ts
Database.create
Call Signature
ts
create<table, relations>(
   table, 
   values, 
options): Promise<{ [key in string | number | symbol]: ({ [key in string]: { [column in string]: ColumnOutput<TableColumns<table>[column]> }[key] } & { [key in string | number | symbol]: { [name in string | number | symbol]: RelationResult<relations[name]> }[key] })[key] }>;
Type Parameters
table

table extends AnyTable

relations

relations extends RelationMapForSourceName<TableName<table>> = { }

Parameters
table

table

values

Partial<TableRow<table>>

options

CreateRowOptions<table, relations>

Returns

Promise<{ [key in string | number | symbol]: ({ [key in string]: { [column in string]: ColumnOutput<TableColumns<table>[column]> }[key] } & { [key in string | number | symbol]: { [name in string | number | symbol]: RelationResult<relations[name]> }[key] })[key] }>

Inherited from
ts
Database.create
createMany()
Call Signature
ts
createMany<table>(
   table, 
   values, 
options?): Promise<WriteResult>;
Type Parameters
table

table extends AnyTable

Parameters
table

table

values

Partial<{ [key in string]: { [column in string]: ColumnOutput<TableColumns<table>[column]> }[key] }>[]

options?

CreateManyResultOptions

Returns

Promise<WriteResult>

Inherited from
ts
Database.createMany
Call Signature
ts
createMany<table>(
   table, 
   values, 
options): Promise<{ [key in string]: { [column in string]: ColumnOutput<TableColumns<table>[column]> }[key] }[]>;
Type Parameters
table

table extends AnyTable

Parameters
table

table

values

Partial<{ [key in string]: { [column in string]: ColumnOutput<TableColumns<table>[column]> }[key] }>[]

options

CreateManyRowsOptions

Returns

Promise<{ [key in string]: { [column in string]: ColumnOutput<TableColumns<table>[column]> }[key] }[]>

Inherited from
ts
Database.createMany
delete()
ts
delete<table>(table, value): Promise<boolean>;
Type Parameters
table

table extends AnyTable

Parameters
table

table

value

PrimaryKeyInput<table>

Returns

Promise<boolean>

Inherited from
ts
Database.delete
deleteMany()
ts
deleteMany<table>(table, options): Promise<WriteResult>;
Type Parameters
table

table extends AnyTable

Parameters
table

table

options

DeleteManyOptions<table>

Returns

Promise<WriteResult>

Inherited from
ts
Database.deleteMany
exec()
Call Signature
ts
exec(statement, values?): Promise<DataManipulationResult>;
Parameters
statement

string | SqlStatement

values?

unknown[]

Returns

Promise<DataManipulationResult>

Inherited from
ts
Database.exec
Call Signature
ts
exec<input>(input): Promise<QueryExecutionResult<input>>;
Type Parameters
input

input extends AnyQuery

Parameters
input

input

Returns

Promise<QueryExecutionResult<input>>

Inherited from
ts
Database.exec
executeScript()
ts
executeScript(sql): Promise<void>;

Executes a migration or raw multi-statement SQL script.

Parameters
sql

string

SQL script to execute.

Returns

Promise<void>

A promise that resolves when execution completes.

Inherited from
ts
Database.executeScript
find()
ts
find<table, relations>(
   table, 
   value, 
   options?): Promise<
  | { [key in string | number | symbol]: ({ [key in string]: { [column in string]: ColumnOutput<TableColumns<(...)>[column]> }[key] } & { [key in string | number | symbol]: { [name in string | number | symbol]: RelationResult<relations[name]> }[key] })[key] }
| null>;
Type Parameters
table

table extends AnyTable

relations

relations extends RelationMapForSourceName<TableName<table>> = { }

Parameters
table

table

value

PrimaryKeyInput<table>

options?
with?

relations

Returns

Promise< | { [key in string | number | symbol]: ({ [key in string]: { [column in string]: ColumnOutput<TableColumns<(...)>[column]> }[key] } & { [key in string | number | symbol]: { [name in string | number | symbol]: RelationResult<relations[name]> }[key] })[key] } | null>

Inherited from
ts
Database.find
findMany()
ts
findMany<table, relations>(table, options?): Promise<{ [key in string | number | symbol]: ({ [key in string]: { [column in string]: ColumnOutput<TableColumns<(...)>[column]> }[key] } & { [key in string | number | symbol]: { [name in string | number | symbol]: RelationResult<relations[name]> }[key] })[key] }[]>;
Type Parameters
table

table extends AnyTable

relations

relations extends RelationMapForSourceName<TableName<table>> = { }

Parameters
table

table

options?

FindManyOptions<table, relations>

Returns

Promise<{ [key in string | number | symbol]: ({ [key in string]: { [column in string]: ColumnOutput<TableColumns<(...)>[column]> }[key] } & { [key in string | number | symbol]: { [name in string | number | symbol]: RelationResult<relations[name]> }[key] })[key] }[]>

Inherited from
ts
Database.findMany
findOne()
ts
findOne<table, relations>(table, options): Promise<
  | { [key in string | number | symbol]: ({ [key in string]: { [column in string]: ColumnOutput<TableColumns<(...)>[column]> }[key] } & { [key in string | number | symbol]: { [name in string | number | symbol]: RelationResult<relations[name]> }[key] })[key] }
| null>;
Type Parameters
table

table extends AnyTable

relations

relations extends RelationMapForSourceName<TableName<table>> = { }

Parameters
table

table

options

FindOneOptions<table, relations>

Returns

Promise< | { [key in string | number | symbol]: ({ [key in string]: { [column in string]: ColumnOutput<TableColumns<(...)>[column]> }[key] } & { [key in string | number | symbol]: { [name in string | number | symbol]: RelationResult<relations[name]> }[key] })[key] } | null>

Inherited from
ts
Database.findOne
hasColumn()
ts
hasColumn(table, column): Promise<boolean>;

Reports whether a column exists on a table.

Parameters
table

TableRef

Table to inspect.

column

string

Column name to inspect.

Returns

Promise<boolean>

A promise that resolves to true when the column exists.

Inherited from
ts
Database.hasColumn
hasTable()
ts
hasTable(table): Promise<boolean>;

Reports whether a table exists.

Parameters
table

TableRef

Table to inspect.

Returns

Promise<boolean>

A promise that resolves to true when the table exists.

Inherited from
ts
Database.hasTable
migrate()
ts
migrate(migrations, options?): Promise<MigrateResult>;

Applies or reverts migrations in order.

Parameters
migrations

Migrations

Migration descriptors or registry to apply.

options?

DatabaseMigrateOptions

Migration direction, bound, dry-run, and journal configuration.

Returns

Promise<MigrateResult>

The migrations applied or reverted by this run and their SQL scripts.

Inherited from
ts
Database.migrate
migrationStatus()
ts
migrationStatus(migrations, options?): Promise<MigrationStatusEntry[]>;

Reports the current state of the provided migrations.

Parameters
migrations

Migrations

Migration descriptors or registry to inspect.

options?

DatabaseMigrationStatusOptions

Migration journal configuration.

Returns

Promise<MigrationStatusEntry[]>

Status entries for the provided migrations.

Inherited from
ts
Database.migrationStatus
now()
ts
now(): unknown;
Returns

unknown

Inherited from
ts
Database.now
query()
ts
query<tableName, row, primaryKey>(table): Query<QueryTableInput<tableName, row, primaryKey>, { [key in string]: QueryColumnTypeMapFromRow<tableName, row>[key] }, row, {
}, BoundQueryPhase<"all">>;
Type Parameters
tableName

tableName extends string

row

row extends Record<string, unknown>

primaryKey

primaryKey extends readonly keyof row & string[]

Parameters
table

QueryTableInput<tableName, row, primaryKey>

Returns

Query<QueryTableInput<tableName, row, primaryKey>, { [key in string]: QueryColumnTypeMapFromRow<tableName, row>[key] }, row, { }, BoundQueryPhase<"all">>

Inherited from
ts
Database.query
reset()
ts
reset(options): Promise<void>;

Wipes the database, applies migrations, and optionally seeds data.

Parameters
options

DatabaseResetOptions

Migrations and optional seed function used to rebuild the database.

Returns

Promise<void>

A promise that resolves when the database has been rebuilt.

Inherited from
ts
Database.reset
transaction()
ts
transaction<result>(callback, options?): Promise<result>;
Type Parameters
result

result

Parameters
callback

(database) => Promise<result>

options?

TransactionOptions

Returns

Promise<result>

Inherited from
ts
Database.transaction
update()
ts
update<table, relations>(
   table, 
   value, 
   changes, 
options?): Promise<{ [key in string | number | symbol]: ({ [key in string]: { [column in string]: ColumnOutput<TableColumns<table>[column]> }[key] } & { [key in string | number | symbol]: { [name in string | number | symbol]: RelationResult<relations[name]> }[key] })[key] }>;
Type Parameters
table

table extends AnyTable

relations

relations extends RelationMapForSourceName<TableName<table>> = { }

Parameters
table

table

value

PrimaryKeyInput<table>

changes

Partial<TableRow<table>>

options?

UpdateOptions<table, relations>

Returns

Promise<{ [key in string | number | symbol]: ({ [key in string]: { [column in string]: ColumnOutput<TableColumns<table>[column]> }[key] } & { [key in string | number | symbol]: { [name in string | number | symbol]: RelationResult<relations[name]> }[key] })[key] }>

Inherited from
ts
Database.update
updateMany()
ts
updateMany<table>(
   table, 
   changes, 
options): Promise<WriteResult>;
Type Parameters
table

table extends AnyTable

Parameters
table

table

changes

Partial<TableRow<table>>

options

UpdateManyOptions<table>

Returns

Promise<WriteResult>

Inherited from
ts
Database.updateMany
wipe()
ts
wipe(): Promise<void>;

Destructively recreates the configured database.

Returns

Promise<void>

A promise that resolves when the database is ready for use.

Inherited from
ts
Database.wipe

D1DatabaseDriver

A DatabaseDriver backed by a Cloudflare D1 binding.

Statements are compiled by the same SQLite compiler @remix-run/data-table uses, then executed through D1's async prepared-statement API. Pass it to Database, or use createD1Database to get one already wired.

Implements

  • DatabaseDriver<"sqlite">

Constructors

Constructor
ts
new D1DatabaseDriver(d1, options?): D1DatabaseDriver;
Parameters
d1

D1Binding

options?

D1DriverOptions = {}

Returns

D1DatabaseDriver

Accessors

capabilities
Get Signature
ts
get capabilities(): Readonly<{
  migrationLock: false;
  returning: true;
  savepoints: false;
  transactionalDdl: false;
  upsert: true;
}>;

Immutable feature flags used by shared query and migration behavior.

Returns

Readonly<{ migrationLock: false; returning: true; savepoints: false; transactionalDdl: false; upsert: true; }>

Implementation of
ts
DatabaseDriver.capabilities
dialect
Get Signature
ts
get dialect(): "sqlite";

Stable identifier for the SQL dialect.

Returns

"sqlite"

Implementation of
ts
DatabaseDriver.dialect

Methods

batch()
ts
batch(statements): Promise<D1BatchResult[]>;

Runs statements together, atomically, through D1's batch().

This is the answer to "several writes must land together" on a database with no transactions. batch() is D1's only atomic primitive: it takes every statement up front and commits them as a unit, which is why it cannot back transaction() but can back this.

Statements are SqlStatements, so sql from remix/data-table parameterises them and there is no reaching for the raw binding:

ts
await db.batch([
    sql`insert into post (title) values (${title})`,
    sql`update counter set posts = posts + 1`,
]);
Parameters
statements

SqlStatement[]

The statements to run, in order.

Returns

Promise<D1BatchResult[]>

One result per statement, in the same order.

beginTransaction()
ts
beginTransaction(_options?): Promise<TransactionToken>;

Opens a transaction, if the caller accepted that it will not be one.

No BEGIN is sent, because D1 rejects it. The token exists so Database has something to carry; statements inside the scope run and commit exactly as they would outside it.

Parameters
_options?

TransactionOptions

Returns

Promise<TransactionToken>

Implementation of
ts
DatabaseDriver.beginTransaction
close()
ts
close(): void;

A binding is owned by the runtime; there is no connection to release.

Returns

void

Implementation of
ts
DatabaseDriver.close
commitTransaction()
ts
commitTransaction(_token): Promise<void>;

Nothing to commit: every statement in the scope already did.

Parameters
_token

TransactionToken

Returns

Promise<void>

Implementation of
ts
DatabaseDriver.commitTransaction
createSavepoint()
ts
createSavepoint(_token, _name): Promise<void>;

Creates a savepoint inside an open transaction.

Parameters
_token

TransactionToken

_name

string

Returns

Promise<void>

Implementation of
ts
DatabaseDriver.createSavepoint
execute()
ts
execute(request): Promise<DataManipulationResult>;

Executes a data-manipulation request.

Parameters
request

DataManipulationRequest

Returns

Promise<DataManipulationResult>

Implementation of
ts
DatabaseDriver.execute
executeScript()
ts
executeScript(sql, _transaction?): Promise<void>;

Executes a raw SQL script that may contain multiple statements.

Parameters
sql

string

_transaction?

TransactionToken

Returns

Promise<void>

Implementation of
ts
DatabaseDriver.executeScript
hasColumn()
ts
hasColumn(
   table, 
   column, 
_transaction?): Promise<boolean>;

Checks whether a column exists on a table.

Parameters
table

TableRef

column

string

_transaction?

TransactionToken

Returns

Promise<boolean>

Implementation of
ts
DatabaseDriver.hasColumn
hasTable()
ts
hasTable(table, _transaction?): Promise<boolean>;

Checks whether a table exists.

Parameters
table

TableRef

_transaction?

TransactionToken

Returns

Promise<boolean>

Implementation of
ts
DatabaseDriver.hasTable
releaseSavepoint()
ts
releaseSavepoint(_token, _name): Promise<void>;

Releases a previously created savepoint.

Parameters
_token

TransactionToken

_name

string

Returns

Promise<void>

Implementation of
ts
DatabaseDriver.releaseSavepoint
rollbackToSavepoint()
ts
rollbackToSavepoint(_token, _name): Promise<void>;

Rolls back to a previously created savepoint.

Parameters
_token

TransactionToken

_name

string

Returns

Promise<void>

Implementation of
ts
DatabaseDriver.rollbackToSavepoint
rollbackTransaction()
ts
rollbackTransaction(_token): Promise<void>;

Nothing to roll back. This is the cost of unsafe-nonatomic, and it is silent by necessity: Database calls this while unwinding a failed callback, and throwing here would replace the caller's error with an AggregateError about a rollback that was never possible.

Parameters
_token

TransactionToken

Returns

Promise<void>

Implementation of
ts
DatabaseDriver.rollbackTransaction
wipe()
ts
wipe(): Promise<void>;

Drops every table the application owns.

D1 keeps its own bookkeeping in _cf_* tables and SQLite keeps sqlite_*; dropping either breaks the binding, so both are left alone. There is no file to delete the way the SQLite driver deletes one.

Returns

Promise<void>

Implementation of
ts
DatabaseDriver.wipe

Interfaces

D1BatchResult

What one statement in a D1DatabaseDriver.batch produced.

Properties

affectedRows
ts
affectedRows: number;
insertId
ts
insertId: number;
rows
ts
rows: Record<string, unknown>[];

Rows the statement returned. Empty for a write with no returning.


D1Binding

The slice of Cloudflare's D1 API this driver uses.

Declared structurally rather than imported from @cloudflare/workers-types, so the package adds no dependency and no ambient global types to a consumer that does not already have them. A real D1Database binding satisfies it; so does a test double.

Methods

batch()
ts
batch(statements): Promise<D1Result[]>;
Parameters
statements

D1PreparedStatement[]

Returns

Promise<D1Result[]>

exec()
ts
exec(query): Promise<unknown>;
Parameters
query

string

Returns

Promise<unknown>

prepare()
ts
prepare(query): D1PreparedStatement;
Parameters
query

string

Returns

D1PreparedStatement


D1DatabaseOptions

Extends

  • DatabaseOptions

Properties

now?
ts
optional now?: () => unknown;

Clock function used for auto-managed timestamps.

Returns

unknown

Inherited from
ts
DatabaseOptions.now
onStatement?
ts
optional onStatement?: D1StatementObserver;

Called after each statement, with the rows read, rows written, and duration D1 reported for it. See D1StatementObserver.

transactions?
ts
optional transactions?: D1TransactionMode;

What transaction() does. Defaults to throw, because D1 has no transactions; unsafe-nonatomic accepts the call and gives up atomicity. See D1TransactionMode.


D1DriverOptions

Properties

onStatement?
ts
optional onStatement?: D1StatementObserver;
transactions?
ts
optional transactions?: D1TransactionMode;

D1Meta

Properties

changes
ts
changes: number;

Rows written by the statement. D1 reports 0 for reads.

duration?
ts
optional duration?: number;

Wall time D1 spent on the statement, in milliseconds.

last_row_id
ts
last_row_id: number;

Rowid of the last inserted row, meaningful only after an insert.

rows_read?
ts
optional rows_read?: number;

Rows D1 scanned. Billed, and absent on some responses.

rows_written?
ts
optional rows_written?: number;

Rows D1 persisted. Billed, and absent on some responses.


D1PreparedStatement

Methods

all()
ts
all(): Promise<D1Result>;
Returns

Promise<D1Result>

bind()
ts
bind(...values): D1PreparedStatement;
Parameters
values

...unknown[]

Returns

D1PreparedStatement


D1Result

Properties

meta
ts
meta: D1Meta;
results
ts
results: Record<string, unknown>[];

D1StatementReport

What one executed statement cost, as D1 reported it.

Properties

durationMs
ts
durationMs: number;

Wall time D1 spent, in milliseconds. 0 when D1 omits it.

kind
ts
kind: string;

The operation that produced it: select, insert, update, and so on.

rowsRead
ts
rowsRead: number;

Rows D1 scanned. 0 when D1 omits the figure, never estimated.

rowsWritten
ts
rowsWritten: number;

Rows D1 persisted. 0 when D1 omits the figure, never estimated.

table
ts
table: string | undefined;

The table it targeted, absent for a raw statement.

Type Aliases

D1StatementObserver

ts
type D1StatementObserver = (report) => void;

Called after each statement the driver executes.

D1 bills on rows read and written, and its analytics report per database rather than per query, so these numbers are the only way to attribute cost to the query or the request that caused it. They ride along on responses the driver already reads, so observing them costs no extra statement and no extra billable operation.

It runs on the hot path, once per statement, so keep it cheap.

Parameters

report

D1StatementReport

Returns

void


D1TransactionMode

ts
type D1TransactionMode = "throw" | "unsafe-nonatomic";

How the driver answers a transaction() call.

  • throw refuses, because D1 cannot honour it. The default.
  • unsafe-nonatomic accepts and runs each statement immediately, each committing on its own. A failure part-way leaves the earlier writes persisted, with no rollback. For code shared with a backend that does have transactions, where the alternative is not running at all.

Functions

createD1Database()

ts
function createD1Database(binding, options?): D1Database;

Wraps a D1 binding in a Database.

ts
import { createD1Database } from "@pitlane/data-table-d1";
import { env } from "cloudflare:workers";

let db = createD1Database(env.DB);
let posts = await db.query(Post).all();

Parameters

binding

D1Binding

The D1 binding, e.g. env.DB.

options?

D1DatabaseOptions

Database options, plus onStatement and transactions.

Returns

D1Database