ナミスマート合同会社

なぜ 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' も指定できる
freepaid
Worker 1 回の呼び出しあたりのクエリ数501,000
データベースサイズ500 MB10 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 も使用している。


ナミスマート合同会社

業界最速、最安、高品質なアプリ開発。開発のご相談はお問い合わせからどうぞ。

お問い合わせ