Cloudflare D1
ormed_d1 is the Cloudflare D1 adapter for Ormed. It compiles SQLite-compatible SQL through ormed_sqlite_core and executes statements over the D1 HTTP API.
Install
dependencies:
ormed: ^0.4.0
ormed_d1: ^0.3.0
Choose a connection mode
Use the mode that matches where the D1 request runs:
| Runtime | API | Credential boundary |
|---|---|---|
| Server-side Dart with Cloudflare API access | D1Database.connect(...) | The Dart process holds the Cloudflare credentials |
| Browser or Worker client calling your application | D1Database.fromEndpoint(...) | Your application endpoint authenticates the caller; no D1 token ships to the client |
| Dart running inside a Cloudflare Worker with a native binding | D1Database.fromBinding(...) | The Worker runtime supplies the binding directly |
Direct database access
Code generation is optional for D1. The direct facade is useful for raw SQL, ad-hoc tables, migrations, and small services:
import 'package:ormed_d1/ormed_d1.dart';
Future<void> main() async {
final db = await D1Database.connect(
accountId: 'your-account-id',
databaseId: 'your-database-id',
apiToken: 'your-api-token',
);
final rows = await db.queryRaw('SELECT 1 AS ok');
print(rows.first['ok']);
await db.close();
}
Do not put the Cloudflare API token in a browser bundle. Use an application-owned endpoint instead:
final db = await D1Database.fromEndpoint(
endpoint: Uri.parse('https://example.com/api/database/query'),
batchEndpoint: Uri.parse('https://example.com/api/database/batch'),
);
The endpoint receives {sql, params}. It may return the D1 binding result
shape or the Cloudflare API result shape. When present, batchEndpoint
accepts {statements: [{sql, params}, ...]} for atomic batches.
Inside a Cloudflare Worker, use the host-neutral binding contract without an HTTP hop:
final db = await D1Database.fromBinding(binding: env.d1('DB'));
The binding types are exported from package:ormed_d1/d1_binding.dart so a
platform bridge can implement D1DatabaseBinding without importing a
Cloudflare-specific runtime into the shared package.
Use in code (DataSource)
The recommended path is generated config.dart + datasource.dart.
- From Env (recommended)
- Explicit Credentials
import 'package:your_app/src/database/datasource.dart';
Future<void> main() async {
final ds = createDataSource(connection: 'd1');
await ds.init();
await ds.dispose();
}
import 'package:ormed/ormed.dart';
import 'package:ormed_d1/ormed_d1.dart';
import 'package:your_app/src/database/config.dart';
Future<void> main() async {
final base = buildDataSourceOptions(connection: 'd1');
final ds = DataSource(
base.copyWith(
driver: D1DriverAdapter.custom(
config: const DatabaseConfig(
driver: 'd1',
options: {
'accountId': 'your-account-id',
'databaseId': 'your-database-id',
'apiToken': 'your-api-token',
},
),
),
),
);
await ds.init();
await ds.dispose();
}
Code-first Helper APIs
Use model-registry extensions when you want explicit setup in code:
registry.d1DataSourceOptions(...)registry.d1DataSource(...)registry.d1DataSourceOptionsFromEnv(...)registry.d1DataSourceFromEnv(...)
Configure (ormed.yaml)
driver:
type: d1
options:
accountId: ${DB_D1_ACCOUNT_ID}
databaseId: ${DB_D1_DATABASE_ID}
apiToken: ${DB_D1_API_TOKEN}
baseUrl: ${DB_D1_BASE_URL:-https://api.cloudflare.com/client/v4}
Environment Variables
d1DataSourceOptionsFromEnv(...) / d1DataSourceFromEnv(...) recognize:
D1_ACCOUNT_ID,CF_ACCOUNT_ID,DB_D1_ACCOUNT_IDD1_DATABASE_ID,DB_D1_DATABASE_IDD1_API_TOKEN,D1_SECRET,DB_D1_API_TOKEND1_BASE_URL,DB_D1_BASE_URLD1_RETRY_ATTEMPTS,DB_D1_RETRY_ATTEMPTSD1_REQUEST_TIMEOUT_MS,DB_D1_REQUEST_TIMEOUT_MSD1_RETRY_BASE_DELAY_MS,DB_D1_RETRY_BASE_DELAY_MSD1_RETRY_MAX_DELAY_MS,DB_D1_RETRY_MAX_DELAY_MSD1_DEBUG_LOG,DB_D1_DEBUG_LOG
Minimum required credentials:
- account id
- database id
- API token
Quick Verification
Use a simple query like SELECT 1 AS ok through your app bootstrap path. Avoid probing blocked functions (for example sqlite_version()).
Options
| Option | Type | Default | Description |
|---|---|---|---|
accountId | String | — | Cloudflare account id. |
databaseId | String | — | D1 database id. |
apiToken | String | — | Cloudflare API token with D1 access. |
baseUrl | String | https://api.cloudflare.com/client/v4 | Cloudflare API base URL. |
maxAttempts | int | 5 | Max request attempts (initial + retries). |
requestTimeoutMs | int | 30000 | Per-request timeout in milliseconds. |
retryBaseDelayMs | int | 250 | Initial retry backoff delay. |
retryMaxDelayMs | int | 3000 | Max retry backoff delay cap. |
debugLog | bool | false | Enables transport request/response logging. |
Notes
- D1 is remote and HTTP-backed, so expect higher latency than local SQLite.
- Retry/backoff is built into the transport and configurable through options/env.
Troubleshooting
Slow tests or "hanging" runs
Most long waits are retry/timeout behavior rather than deadlocks. Reduce retry envelope while debugging:
D1_RETRY_ATTEMPTS=1D1_REQUEST_TIMEOUT_MS=5000D1_DEBUG_LOG=1
This makes failures surface quickly and prints request/response timing.
not authorized to use function: sqlite_version
Cloudflare D1 blocks some SQLite functions. Avoid runtime probes like:
SELECT sqlite_version()
Use neutral checks such as:
SELECT 1 AS ok
401/403 auth errors
Verify all required values are present and mapped correctly:
- account id (
D1_ACCOUNT_ID/CF_ACCOUNT_ID/DB_D1_ACCOUNT_ID) - database id (
D1_DATABASE_ID/DB_D1_DATABASE_ID) - API token (
D1_API_TOKEN/D1_SECRET/DB_D1_API_TOKEN)
If requests still fail, lower retries/timeouts temporarily to surface root errors quickly while debugging.