なぜ Cloudflare D1 専用の ORM を自作したのか
更新: 2026年9月6日 CloudflareD1TypeScriptOSS
Cloudflare D1 には、他のデータベースに対応物のない制限や機能がある。移植性のための抽象を 持つ ORM では、それらは抽象の下に隠れる。D1 と Workers だけを対象にした ORM orm-d1(旧称 d1zzle)を書いた理由と、その設計を書く。
API は Drizzle と同じなので、スキーマ定義とクエリはそのまま書ける。
import { drizzle, eq, sqliteTable, text } from 'orm-d1';
const db = drizzle(env.DB);
await db.select().from(users).where(eq(users.id, 1)).get();
プランごとの上限を実行時に警告する
D1 の上限は無料プランと有料プランで異なる。どちらで動くかをライブラリに渡せる。
const db = drizzle(env.DB, { plan: 'free' }); // 'paid' も指定できる
| free | paid | |
|---|---|---|
| Worker 1 回の呼び出しあたりのクエリ数 | 50 | 1,000 |
| データベースサイズ | 500 MB | 10 GB |
どちらも文が走るまで確定しないので、開発時のみの警告として出す。クエリ数は batch() の
中身も 1 本ずつ数える(D1 がそう数えるため、batch() は上限の回避策にならない)。
サイズは、D1 が全ての文で返す meta.size_after が上限の 90% を超えたところで 1 回警告する。
固定の上限はコンパイル時に検査する
プランに依存しない上限は、クエリの組み立て時に検査する。Worker の isolate ではコンパイルが 1 回だけ走り、その過程で既にクエリを走査しているため追加コストはほぼない。
| 上限 | 値 |
|---|---|
| 1 クエリのバインドパラメータ | 100 |
| SQL 文の長さ | 100,000 バイト |
| SQL 関数の引数 | 32 |
| テーブルの列数 | 100 |
SQLite が返す too many SQL variables は原因の inArray を示さないが、組み立て時のエラーなら
呼び出し箇所を名指しできる。回避もしていて、insert は自動で分割し、inArray は json_each
に展開して長いリストをパラメータ 1 個に畳む。
D1 の仕様から決めた設計
- 読み取りは位置指定(
.raw())で受ける。.all()は行ごとにキー付きオブジェクトを 作るため、join で列名が重なると片方が失われる。 batch()を原子性の単位にする。 D1 が提供する原子性の保証はこれだけである。transaction()は提供しない。 D1 に対話的トランザクションは無い。- Sessions とブックマークに対応する。 D1 の読み取りレプリケーション用の仕組みで、 Postgres や MySQL のドライバにはない。
rows_read/rows_writtenを返す。 D1 の課金単位で、全てのレスポンスに含まれる。
orm-d1 は弊社プロダクトの本番で使っていて、認証基盤の Better Auth も使用している。
ナミスマート合同会社
業界最速、最安、高品質なアプリ開発。開発のご相談はお問い合わせからどうぞ。