Skip to content

Add Cloudflare D1 database driver#980

Open
Copilot wants to merge 3 commits into
mainfrom
copilot/add-cloudflare-workers-support
Open

Add Cloudflare D1 database driver#980
Copilot wants to merge 3 commits into
mainfrom
copilot/add-cloudflare-workers-support

Conversation

Copy link
Copy Markdown

Copilot AI commented May 22, 2026

Adds a new cloudflare_d1 database driver so ueberDB can be used inside Cloudflare Workers backed by a D1 database binding.

Driver (databases/cloudflare_d1_db.ts)

  • Accepts a D1Database runtime binding via settings.d1Database — no npm peer dependency needed, types are defined inline to mirror @cloudflare/workers-types
  • Implements all required operations (init, get, set, findKeys, remove, doBulk, close) using D1's prepared-statement API and INSERT OR REPLACE/LIKE-pattern SQL
  • doBulk uses D1Database.batch() for atomic multi-operation writes

Infrastructure

  • DatabaseType union and initDB() switch in index.ts updated
  • Settings type gains d1Database?: unknown field

Tests (test/cloudflare_d1/)

  • test/lib/mock_d1.ts: lightweight D1Database mock backed by Node.js's built-in node:sqlite — no network, no miniflare required
  • test/lib/databases.ts: cloudflare_d1 entry with a getter that produces a fresh in-memory instance per test
  • Passes the full shared test_db suite (80 tests across all read-cache × write-buffer combinations)

Usage in a Worker

import { Database } from 'ueberdb2';

export default {
  async fetch(request, env) {
    const db = new Database('cloudflare_d1', { d1Database: env.MY_DB });
    await db.init();
    await db.set('pad:abc', { text: 'hello' });
    const val = await db.get('pad:abc');
    await db.close();
    return new Response(JSON.stringify(val));
  }
};

Copilot AI linked an issue May 22, 2026 that may be closed by this pull request
Copilot AI and others added 2 commits May 22, 2026 08:55
Copilot AI changed the title [WIP] Add support for Cloudflare Workers integration Add Cloudflare D1 database driver May 22, 2026
Copilot AI requested a review from JohnMcLear May 22, 2026 08:57
@JohnMcLear JohnMcLear marked this pull request as ready for review May 22, 2026 09:05
@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Add Cloudflare D1 database driver with comprehensive test support

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds Cloudflare D1 database driver for ueberDB Workers support
• Implements all required database operations using D1's prepared-statement API
• Includes lightweight Node.js SQLite-backed mock for local testing
• Passes full shared test suite across all cache/buffer combinations
Diagram
flowchart LR
  A["ueberDB Core"] -->|"lazy load"| B["cloudflare_d1_db.ts"]
  B -->|"uses"| C["D1Database API"]
  D["test/lib/mock_d1.ts"] -->|"implements"| C
  E["test/lib/databases.ts"] -->|"provides"| D
  F["test/cloudflare_d1/test.cloudflared1.spec.ts"] -->|"runs"| E

Loading

File Changes

1. databases/cloudflare_d1_db.ts ✨ Enhancement +140/-0

Cloudflare D1 database driver implementation

• New driver class CloudflareD1DB extending AbstractDatabase with full CRUD operations
• Implements init, get, set, findKeys, remove, doBulk, and close methods
• Uses D1's prepared-statement API with INSERT OR REPLACE and LIKE-pattern SQL
• Includes inline TypeScript interfaces mirroring official D1 API without hard dependency

databases/cloudflare_d1_db.ts


2. index.ts ✨ Enhancement +5/-0

Register D1 driver in core database initialization

• Adds 'cloudflare_d1' to DatabaseType union type
• Adds case handler in initDB() switch statement for lazy-loading the D1 driver
• Properly types the D1Settings when instantiating CloudflareD1DB

index.ts


3. lib/AbstractDatabase.ts ✨ Enhancement +2/-0

Extend Settings type with D1Database binding

• Adds optional d1Database?: unknown field to Settings type
• Includes JSDoc comment explaining D1Database binding usage

lib/AbstractDatabase.ts


View more (3)
4. test/lib/mock_d1.ts 🧪 Tests +81/-0

Lightweight D1 API mock for local testing

• Implements MockD1PreparedStatement class wrapping Node.js DatabaseSync
• Implements MockD1Database class providing prepare(), batch(), and exec() methods
• Uses in-memory SQLite (:memory:) for isolated test instances
• Mirrors official D1 API interfaces for type-safe testing without Cloudflare runtime

test/lib/mock_d1.ts


5. test/lib/databases.ts 🧪 Tests +8/-0

Configure D1 mock for test suite

• Adds cloudflare_d1 entry to databases configuration object
• Uses getter pattern to provide fresh MockD1Database instance per test
• Ensures test isolation with independent in-memory SQLite instances

test/lib/databases.ts


6. test/cloudflare_d1/test.cloudflared1.spec.ts 🧪 Tests +6/-0

Add D1 driver test suite

• New test file running full shared test_db suite for cloudflare_d1 driver
• Covers all read-cache and write-buffer combinations (80 tests total)

test/cloudflare_d1/test.cloudflared1.spec.ts


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 22, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0)

Grey Divider


Action required

1. Bulk bypasses key limit 🐞 Bug ≡ Correctness
Description
CloudflareD1DB.doBulk() writes keys without enforcing the 100-character limit, so oversized keys can
be persisted when write buffering triggers bulk flushes. This creates inconsistent behavior vs
immediate writes (set() throws) and vs other drivers that constrain keys at the schema level.
Code

databases/cloudflare_d1_db.ts[R118-135]

Evidence
The D1 driver validates key length only in set(), but doBulk() directly binds and writes op.key with
no check; the store schema uses TEXT without length constraints, so the database won’t reject
oversized keys. The cache layer uses doBulk() for buffered multi-op flushes, so buffered writes
bypass the set() validation entirely.

databases/cloudflare_d1_db.ts[78-80]
databases/cloudflare_d1_db.ts[106-112]
databases/cloudflare_d1_db.ts[118-135]
lib/CacheAndBufferLayer.ts[489-555]
databases/mysql_db.ts[75-80]
databases/mysql_db.ts[158-161]
databases/postgres_db.ts[44-48]
databases/postgres_db.ts[154-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`CloudflareD1DB.set()` rejects keys longer than 100 chars, but `CloudflareD1DB.doBulk()` does not. Because the cache/buffer layer prefers `doBulk()` for multi-op flushes, long keys can be persisted successfully (and without throwing) whenever buffering is enabled.

## Issue Context
- In this driver, the `store` table schema does not enforce a 100-char limit (`key TEXT ...`).
- The cache/buffer layer uses `wrappedDB.doBulk(ops)` for multi-op flushes, bypassing `wrappedDB.set()`.

## Fix Focus Areas
- databases/cloudflare_d1_db.ts[72-135]
- lib/CacheAndBufferLayer.ts[489-555]

## What to change
1. Enforce the same key length validation in `doBulk()` for `set` operations (and consider validating `remove` too for consistency).
  - If any key is >100 chars, throw an Error so the cache layer falls back to individual writes (which will then consistently fail via `set()`), rather than silently persisting invalid keys.
2. (Optional but recommended) Align the D1 schema with other drivers by adding a DB-level constraint, e.g. `CHECK(length(key) <= 100)` on the key column in the CREATE TABLE statement. (Note: `VARCHAR(100)` is not sufficient on SQLite for enforcement.)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. D1 binding types not exported 🐞 Bug ⚙ Maintainability
Description
The driver defines D1Database/D1Settings types but the public Settings type uses d1Database?:
unknown and the package export map only exports the root entrypoint, preventing consumers from
importing the D1 types for type-checking. This reduces TypeScript safety and allows invalid bindings
to reach runtime where init() only checks non-null (not shape), leading to less actionable errors.
Code

lib/AbstractDatabase.ts[R12-13]

Evidence
Settings exposes d1Database as unknown, and the package export map only exposes the root entrypoint,
so consumers can’t import the D1Database/D1Settings types for compile-time validation. The driver
comments promise type-safety, but init() only checks that the binding is non-null before calling
methods on it.

lib/AbstractDatabase.ts[8-15]
package.json[25-30]
databases/cloudflare_d1_db.ts[26-31]
databases/cloudflare_d1_db.ts[46-55]
databases/cloudflare_d1_db.ts[72-80]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR claims users get type-safety for real D1 bindings, but:
- `Settings.d1Database` is typed as `unknown`, so `new Database('cloudflare_d1', {d1Database: ...})` is not type-checked.
- `package.json` only exports `.` so consumers cannot reliably import `D1Database` / `D1Settings` from `databases/cloudflare_d1_db` to type their settings.
- Runtime validation in `init()` only checks for null, not that the binding implements the needed methods.

## Issue Context
This is primarily a TS ergonomics / maintainability issue, but it also impacts reliability by making misconfiguration easier and producing less clear runtime failures.

## Fix Focus Areas
- lib/AbstractDatabase.ts[8-15]
- package.json[25-30]
- index.ts[18-47]
- databases/cloudflare_d1_db.ts[26-55]
- databases/cloudflare_d1_db.ts[72-80]

## What to change
1. Re-export D1 types from the package root (type-only export) so they are available via the only exported entrypoint:
  - In `index.ts` add:
    - `export type {D1Database, D1Settings} from './databases/cloudflare_d1_db';`
2. Consider changing `Settings.d1Database?: unknown` to a better type (or to `D1Database | unknown` if you want to preserve broad compatibility) so TS users get checking when using the root `Settings` type.
3. Add a lightweight runtime shape check in `CloudflareD1DB.init()` for clearer errors (e.g., verify `prepare`, `batch`, and `exec` are functions) before using the binding.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +118 to +135
async doBulk(bulk: BulkObject[]): Promise<void> {
if (bulk.length === 0) return;
const statements: D1PreparedStatement[] = [];
for (const op of bulk) {
if (op.type === 'set') {
statements.push(
this._d1db!
.prepare('INSERT OR REPLACE INTO store (key, value) VALUES (?, ?)')
.bind(op.key, op.value),
);
} else if (op.type === 'remove') {
statements.push(
this._d1db!.prepare('DELETE FROM store WHERE key = ?').bind(op.key),
);
}
}
await this._d1db!.batch(statements);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Bulk bypasses key limit 🐞 Bug ≡ Correctness

CloudflareD1DB.doBulk() writes keys without enforcing the 100-character limit, so oversized keys can
be persisted when write buffering triggers bulk flushes. This creates inconsistent behavior vs
immediate writes (set() throws) and vs other drivers that constrain keys at the schema level.
Agent Prompt
## Issue description
`CloudflareD1DB.set()` rejects keys longer than 100 chars, but `CloudflareD1DB.doBulk()` does not. Because the cache/buffer layer prefers `doBulk()` for multi-op flushes, long keys can be persisted successfully (and without throwing) whenever buffering is enabled.

## Issue Context
- In this driver, the `store` table schema does not enforce a 100-char limit (`key TEXT ...`).
- The cache/buffer layer uses `wrappedDB.doBulk(ops)` for multi-op flushes, bypassing `wrappedDB.set()`.

## Fix Focus Areas
- databases/cloudflare_d1_db.ts[72-135]
- lib/CacheAndBufferLayer.ts[489-555]

## What to change
1. Enforce the same key length validation in `doBulk()` for `set` operations (and consider validating `remove` too for consistency).
   - If any key is >100 chars, throw an Error so the cache layer falls back to individual writes (which will then consistently fail via `set()`), rather than silently persisting invalid keys.
2. (Optional but recommended) Align the D1 schema with other drivers by adding a DB-level constraint, e.g. `CHECK(length(key) <= 100)` on the key column in the CREATE TABLE statement. (Note: `VARCHAR(100)` is not sufficient on SQLite for enforcement.)

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for Cloudflare Workers

2 participants