feat: add Cloudflare D1 runtime support #4

Merged
kdaniel2410 merged 3 commits from codex/d1-production-runtime into main 2026-06-25 11:10:45 +00:00
9 changed files with 111 additions and 50 deletions
-3
View File
@@ -1,6 +1,3 @@
# Drizzle
DATABASE_URL=file:local.db
ORIGIN="" ORIGIN=""
# Better Auth # Better Auth
+16 -22
View File
@@ -2,7 +2,7 @@
Clearity is a workspace, client, and contract management platform for managed office operations. It tracks clients, contacts, addresses, rooms, services, bookings, contracts, invoices, and workspace admins from a SvelteKit dashboard. Clearity is a workspace, client, and contract management platform for managed office operations. It tracks clients, contacts, addresses, rooms, services, bookings, contracts, invoices, and workspace admins from a SvelteKit dashboard.
The app is built with SvelteKit, Svelte 5, shadcn-svelte, Superforms, Better Auth, Drizzle ORM, SQLite/libSQL, and Cloudflare Workers. The app is built with SvelteKit, Svelte 5, shadcn-svelte, Superforms, Better Auth, Drizzle ORM, Cloudflare D1, and Cloudflare Workers.
## Development ## Development
@@ -15,7 +15,6 @@ pnpm install
Create a local `.env` file: Create a local `.env` file:
```sh ```sh
DATABASE_URL=file:local.db
ORIGIN=http://localhost:5173 ORIGIN=http://localhost:5173
BETTER_AUTH_SECRET=replace-with-a-long-random-secret BETTER_AUTH_SECRET=replace-with-a-long-random-secret
``` ```
@@ -29,7 +28,7 @@ openssl rand -base64 32
Apply database migrations: Apply database migrations:
```sh ```sh
DATABASE_URL=file:local.db pnpm db:migrate pnpm db:migrate:local
``` ```
Start the development server: Start the development server:
@@ -47,34 +46,36 @@ pnpm check # Wrangler types, SvelteKit sync, and svelte-check
pnpm lint # Prettier check and ESLint pnpm lint # Prettier check and ESLint
pnpm format # Format the codebase pnpm format # Format the codebase
pnpm db:generate # Generate Drizzle migrations after schema changes pnpm db:generate # Generate Drizzle migrations after schema changes
pnpm db:migrate # Apply Drizzle migrations to DATABASE_URL pnpm db:migrate:local # Apply migrations to the local D1 database
pnpm db:studio # Open Drizzle Studio pnpm db:migrate:remote # Apply migrations to the remote D1 database
pnpm gen # Regenerate Cloudflare Worker types pnpm gen # Regenerate Cloudflare Worker types
``` ```
## Environment Variables ## Environment Variables And Bindings
Required: Required:
- `DATABASE_URL`: SQLite/libSQL connection URL. Use `file:local.db` for local development.
- `ORIGIN`: Public app origin, for example `http://localhost:5173` locally or `https://clearity.example.com` in production. - `ORIGIN`: Public app origin, for example `http://localhost:5173` locally or `https://clearity.example.com` in production.
- `BETTER_AUTH_SECRET`: Secret used by Better Auth. - `BETTER_AUTH_SECRET`: Secret used by Better Auth.
- Cloudflare D1 binding `DB`, configured in `wrangler.jsonc`.
Local development uses the same `DB` binding shape as production. SvelteKit's Cloudflare adapter populates `platform.env.DB` from `wrangler.jsonc` during dev and preview, and Wrangler stores local D1 data in its local state directory.
## Database ## Database
The application schema lives in `src/lib/server/db`. Drizzle migration files live in `drizzle`. The application schema lives in `src/lib/server/db`. Drizzle migration files live in `drizzle`.
Local development currently uses the libSQL client through `DATABASE_URL`, so the fastest local database is a SQLite file: Clearity always uses Drizzle's Cloudflare D1 driver through the `DB` binding. Apply migrations to the local D1 database before starting the app:
```sh ```sh
DATABASE_URL=file:local.db pnpm db:migrate pnpm db:migrate:local
``` ```
For schema changes: For schema changes:
```sh ```sh
pnpm db:generate pnpm db:generate
DATABASE_URL=file:local.db pnpm db:migrate pnpm db:migrate:local
``` ```
## Production Deployment ## Production Deployment
@@ -91,10 +92,10 @@ pnpm wrangler whoami
Create a production D1 database: Create a production D1 database:
```sh ```sh
pnpm wrangler d1 create clearity-production pnpm wrangler d1 create clearity-production --binding DB
``` ```
Wrangler prints a `database_id`. Add it to `wrangler.jsonc`: Wrangler prints a `database_id`. Replace the placeholder in `wrangler.jsonc`:
```jsonc ```jsonc
{ {
@@ -102,7 +103,8 @@ Wrangler prints a `database_id`. Add it to `wrangler.jsonc`:
{ {
"binding": "DB", "binding": "DB",
"database_name": "clearity-production", "database_name": "clearity-production",
"database_id": "<database_id>" "database_id": "<database_id>",
"migrations_dir": "drizzle"
} }
] ]
} }
@@ -117,9 +119,7 @@ pnpm gen
Apply SQL migrations to the remote D1 database: Apply SQL migrations to the remote D1 database:
```sh ```sh
for file in drizzle/*.sql; do pnpm wrangler d1 migrations apply clearity-production --remote
pnpm wrangler d1 execute clearity-production --remote --file "$file"
done
``` ```
Set production secrets: Set production secrets:
@@ -129,12 +129,6 @@ pnpm wrangler secret put ORIGIN
pnpm wrangler secret put BETTER_AUTH_SECRET pnpm wrangler secret put BETTER_AUTH_SECRET
``` ```
This codebase currently reads the database through `DATABASE_URL`. If production is deployed against Cloudflare D1, wire the runtime database client to the `DB` D1 binding before deploying. If production is deployed against a hosted libSQL database instead, set `DATABASE_URL` as a Worker secret:
```sh
pnpm wrangler secret put DATABASE_URL
```
Build and deploy: Build and deploy:
```sh ```sh
-3
View File
@@ -1,11 +1,8 @@
import { defineConfig } from 'drizzle-kit'; import { defineConfig } from 'drizzle-kit';
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
export default defineConfig({ export default defineConfig({
schema: './src/lib/server/db/schema.ts', schema: './src/lib/server/db/schema.ts',
dialect: 'sqlite', dialect: 'sqlite',
dbCredentials: { url: process.env.DATABASE_URL },
verbose: true, verbose: true,
strict: true strict: true
}); });
+2 -3
View File
@@ -13,9 +13,9 @@
"lint": "prettier --check . && eslint .", "lint": "prettier --check . && eslint .",
"format": "prettier --write .", "format": "prettier --write .",
"gen": "wrangler types", "gen": "wrangler types",
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate", "db:migrate:local": "wrangler d1 migrations apply clearity-production --local",
"db:migrate:remote": "wrangler d1 migrations apply clearity-production --remote",
"auth:schema": "better-auth generate --config src/lib/server/auth.ts --output src/lib/server/db/auth.schema.ts --yes" "auth:schema": "better-auth generate --config src/lib/server/auth.ts --output src/lib/server/db/auth.schema.ts --yes"
}, },
"devDependencies": { "devDependencies": {
@@ -24,7 +24,6 @@
"@eslint/js": "latest", "@eslint/js": "latest",
"@fontsource-variable/geist": "^5.2.9", "@fontsource-variable/geist": "^5.2.9",
"@internationalized/date": "^3.12.1", "@internationalized/date": "^3.12.1",
"@libsql/client": "^0.17.2",
"@lucide/svelte": "^1.16.0", "@lucide/svelte": "^1.16.0",
"@sveltejs/adapter-cloudflare": "^7.2.8", "@sveltejs/adapter-cloudflare": "^7.2.8",
"@sveltejs/kit": "^2.57.0", "@sveltejs/kit": "^2.57.0",
+16 -8
View File
@@ -23,9 +23,6 @@ importers:
'@internationalized/date': '@internationalized/date':
specifier: ^3.12.1 specifier: ^3.12.1
version: 3.12.1 version: 3.12.1
'@libsql/client':
specifier: ^0.17.2
version: 0.17.3
'@lucide/svelte': '@lucide/svelte':
specifier: ^1.16.0 specifier: ^1.16.0
version: 1.16.0(svelte@5.55.9(@typescript-eslint/types@8.59.4)) version: 1.16.0(svelte@5.55.9(@typescript-eslint/types@8.59.4))
@@ -4759,10 +4756,12 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
optional: true
'@libsql/core@0.17.3': '@libsql/core@0.17.3':
dependencies: dependencies:
js-base64: 3.7.8 js-base64: 3.7.8
optional: true
'@libsql/darwin-arm64@0.5.29': '@libsql/darwin-arm64@0.5.29':
optional: true optional: true
@@ -4777,6 +4776,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
optional: true
'@libsql/isomorphic-ws@0.1.5': '@libsql/isomorphic-ws@0.1.5':
dependencies: dependencies:
@@ -4785,6 +4785,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
optional: true
'@libsql/linux-arm-gnueabihf@0.5.29': '@libsql/linux-arm-gnueabihf@0.5.29':
optional: true optional: true
@@ -4823,7 +4824,8 @@ snapshots:
'@tybys/wasm-util': 0.10.2 '@tybys/wasm-util': 0.10.2
optional: true optional: true
'@neon-rs/load@0.0.4': {} '@neon-rs/load@0.0.4':
optional: true
'@noble/ciphers@2.2.0': {} '@noble/ciphers@2.2.0': {}
@@ -5072,6 +5074,7 @@ snapshots:
'@types/ws@8.18.1': '@types/ws@8.18.1':
dependencies: dependencies:
'@types/node': 24.12.4 '@types/node': 24.12.4
optional: true
'@typeschema/class-validator@0.3.0(@types/json-schema@7.0.15)(class-validator@0.14.4)': '@typeschema/class-validator@0.3.0(@types/json-schema@7.0.15)(class-validator@0.14.4)':
dependencies: dependencies:
@@ -5557,7 +5560,8 @@ snapshots:
destr@2.0.5: {} destr@2.0.5: {}
detect-libc@2.0.2: {} detect-libc@2.0.2:
optional: true
detect-libc@2.1.2: {} detect-libc@2.1.2: {}
@@ -5976,7 +5980,8 @@ snapshots:
jose@6.2.3: {} jose@6.2.3: {}
js-base64@3.7.8: {} js-base64@3.7.8:
optional: true
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
@@ -6066,6 +6071,7 @@ snapshots:
'@libsql/linux-x64-gnu': 0.5.29 '@libsql/linux-x64-gnu': 0.5.29
'@libsql/linux-x64-musl': 0.5.29 '@libsql/linux-x64-musl': 0.5.29
'@libsql/win32-x64-msvc': 0.5.29 '@libsql/win32-x64-msvc': 0.5.29
optional: true
lightningcss-android-arm64@1.32.0: lightningcss-android-arm64@1.32.0:
optional: true optional: true
@@ -6356,7 +6362,8 @@ snapshots:
prettier@3.8.3: {} prettier@3.8.3: {}
promise-limit@2.7.0: {} promise-limit@2.7.0:
optional: true
prompts@2.4.2: prompts@2.4.2:
dependencies: dependencies:
@@ -6878,7 +6885,8 @@ snapshots:
ws@8.20.1: {} ws@8.20.1: {}
ws@8.21.0: {} ws@8.21.0:
optional: true
wsl-utils@0.1.0: wsl-utils@0.1.0:
dependencies: dependencies:
+14 -3
View File
@@ -3,13 +3,24 @@ import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { admin } from 'better-auth/plugins'; import { admin } from 'better-auth/plugins';
import { sveltekitCookies } from 'better-auth/svelte-kit'; import { sveltekitCookies } from 'better-auth/svelte-kit';
import { env } from '$env/dynamic/private'; import { env } from '$env/dynamic/private';
import { building } from '$app/environment';
import { getRequestEvent } from '$app/server'; import { getRequestEvent } from '$app/server';
import { db } from '$lib/server/db'; import { db } from '$lib/server/db';
import * as schema from '$lib/server/db/schema';
const buildTimeOrigin = 'http://localhost:5173';
const buildTimeSecret = 'clearity-build-time-placeholder-secret';
const origin = env.ORIGIN ?? (building ? buildTimeOrigin : undefined);
const secret = env.BETTER_AUTH_SECRET ?? (building ? buildTimeSecret : undefined);
if (!origin) throw new Error('ORIGIN is not set');
if (!secret) throw new Error('BETTER_AUTH_SECRET is not set');
export const auth = betterAuth({ export const auth = betterAuth({
baseURL: env.ORIGIN, baseURL: origin,
secret: env.BETTER_AUTH_SECRET, secret,
database: drizzleAdapter(db, { provider: 'sqlite' }), database: drizzleAdapter(db, { provider: 'sqlite', schema }),
emailAndPassword: { emailAndPassword: {
enabled: true, enabled: true,
disableSignUp: true disableSignUp: true
+50 -6
View File
@@ -1,10 +1,54 @@
import { drizzle } from 'drizzle-orm/libsql'; import { getRequestEvent } from '$app/server';
import { createClient } from '@libsql/client'; import { drizzle as drizzleD1, type DrizzleD1Database } from 'drizzle-orm/d1';
import * as schema from './schema'; import * as schema from './schema';
import { env } from '$env/dynamic/private';
if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); type Schema = typeof schema;
const client = createClient({ url: env.DATABASE_URL }); export type Database = DrizzleD1Database<Schema> & { $client: D1Database };
export const db = drizzle(client, { schema }); const d1Databases = new WeakMap<D1Database, Database>();
function getD1Binding() {
try {
return getRequestEvent().platform?.env.DB;
} catch {
return undefined;
}
}
function getD1Db(binding: D1Database) {
const cached = d1Databases.get(binding);
if (cached) return cached;
const database = drizzleD1(binding, { schema });
d1Databases.set(binding, database);
return database;
}
export function getDb(): Database {
const d1Binding = getD1Binding();
if (d1Binding) return getD1Db(d1Binding);
throw new Error('Cloudflare D1 DB binding is not available');
}
export const db = new Proxy({} as Database, {
get(_target, property) {
const database = getDb();
const value = Reflect.get(database, property);
return typeof value === 'function' ? value.bind(database) : value;
},
getOwnPropertyDescriptor(_target, property) {
return Reflect.getOwnPropertyDescriptor(getDb(), property);
},
getPrototypeOf() {
return Reflect.getPrototypeOf(getDb());
},
has(_target, property) {
return property in getDb();
},
ownKeys() {
return Reflect.ownKeys(getDb());
}
});
+2 -2
View File
@@ -1,9 +1,9 @@
/* eslint-disable */ /* eslint-disable */
// Generated by Wrangler by running `wrangler types` (hash: 61c64c9cd1c2465ff3a92bc5ac82d57d) // Generated by Wrangler by running `wrangler types` (hash: 36a638c2dc42c34e7d5ddc208fd03581)
// Runtime types generated with workerd@1.20260521.1 2026-05-23 nodejs_als // Runtime types generated with workerd@1.20260521.1 2026-05-23 nodejs_als
interface __BaseEnv_Env { interface __BaseEnv_Env {
DB: D1Database;
ASSETS: Fetcher; ASSETS: Fetcher;
DATABASE_URL: string;
ORIGIN: string; ORIGIN: string;
BETTER_AUTH_SECRET: string; BETTER_AUTH_SECRET: string;
} }
+11
View File
@@ -8,6 +8,17 @@
"binding": "ASSETS", "binding": "ASSETS",
"directory": ".svelte-kit/cloudflare" "directory": ".svelte-kit/cloudflare"
}, },
"d1_databases": [
{
"binding": "DB",
"database_name": "clearity-production",
"database_id": "<replace-with-d1-database-id>",
"migrations_dir": "drizzle"
}
],
"secrets": {
"required": ["ORIGIN", "BETTER_AUTH_SECRET"]
},
"workers_dev": true, "workers_dev": true,
"preview_urls": true "preview_urls": true
} }