From b1d0267bf1be05cc1f66793d2438337cff481157 Mon Sep 17 00:00:00 2001 From: Daniel Kirby Date: Thu, 25 Jun 2026 11:40:56 +0100 Subject: [PATCH] feat: add D1 runtime database binding --- src/lib/server/auth.ts | 17 +++++++-- src/lib/server/db/index.ts | 76 +++++++++++++++++++++++++++++++++++--- worker-configuration.d.ts | 6 +-- wrangler.jsonc | 8 ++++ 4 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 460b3ad..6270893 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -3,13 +3,24 @@ import { drizzleAdapter } from 'better-auth/adapters/drizzle'; import { admin } from 'better-auth/plugins'; import { sveltekitCookies } from 'better-auth/svelte-kit'; import { env } from '$env/dynamic/private'; +import { building } from '$app/environment'; import { getRequestEvent } from '$app/server'; 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({ - baseURL: env.ORIGIN, - secret: env.BETTER_AUTH_SECRET, - database: drizzleAdapter(db, { provider: 'sqlite' }), + baseURL: origin, + secret, + database: drizzleAdapter(db, { provider: 'sqlite', schema }), emailAndPassword: { enabled: true, disableSignUp: true diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index 662fc2d..a20616e 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -1,10 +1,74 @@ -import { drizzle } from 'drizzle-orm/libsql'; -import { createClient } from '@libsql/client'; -import * as schema from './schema'; import { env } from '$env/dynamic/private'; +import { getRequestEvent } from '$app/server'; +import { createClient, type Client } from '@libsql/client'; +import { drizzle as drizzleD1, type DrizzleD1Database } from 'drizzle-orm/d1'; +import { drizzle as drizzleLibSQL, type LibSQLDatabase } from 'drizzle-orm/libsql'; +import * as schema from './schema'; -if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); +type Schema = typeof schema; +type LocalDatabase = LibSQLDatabase & { $client: Client }; +type D1DatabaseClient = DrizzleD1Database & { $client: D1Database }; -const client = createClient({ url: env.DATABASE_URL }); +export type Database = LocalDatabase; +export type RuntimeDatabase = LocalDatabase | D1DatabaseClient; -export const db = drizzle(client, { schema }); +let localDb: LocalDatabase | undefined; +const d1Databases = new WeakMap(); + +function getD1Binding() { + try { + return getRequestEvent().platform?.env.DB; + } catch { + return undefined; + } +} + +function getLocalDb() { + if (localDb) return localDb; + if (!env.DATABASE_URL) + throw new Error('DATABASE_URL is not set and no Cloudflare D1 DB binding is available'); + + const client = createClient({ url: env.DATABASE_URL }); + localDb = drizzleLibSQL(client, { schema }); + + return localDb; +} + +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(): RuntimeDatabase { + if (env.DATABASE_URL) return getLocalDb(); + + const d1Binding = getD1Binding(); + if (d1Binding) return getD1Db(d1Binding); + + return getLocalDb(); +} + +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()); + } +}); diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index b28b382..eb711af 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,11 +1,9 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 61c64c9cd1c2465ff3a92bc5ac82d57d) +// Generated by Wrangler by running `wrangler types` (hash: f6e835394237f01fba038726e2d4f9ae) // Runtime types generated with workerd@1.20260521.1 2026-05-23 nodejs_als interface __BaseEnv_Env { + DB: D1Database; ASSETS: Fetcher; - DATABASE_URL: string; - ORIGIN: string; - BETTER_AUTH_SECRET: string; } declare namespace Cloudflare { interface Env extends __BaseEnv_Env {} diff --git a/wrangler.jsonc b/wrangler.jsonc index bd168bd..35b7afb 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -8,6 +8,14 @@ "binding": "ASSETS", "directory": ".svelte-kit/cloudflare" }, + "d1_databases": [ + { + "binding": "DB", + "database_name": "clearity-production", + "database_id": "", + "migrations_dir": "drizzle" + } + ], "workers_dev": true, "preview_urls": true }