feat: use D1 binding for local database
This commit is contained in:
@@ -1,6 +1,3 @@
|
|||||||
# Drizzle
|
|
||||||
DATABASE_URL=file:local.db
|
|
||||||
|
|
||||||
ORIGIN=""
|
ORIGIN=""
|
||||||
|
|
||||||
# Better Auth
|
# Better Auth
|
||||||
|
|||||||
@@ -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 for local development, Cloudflare D1 for production, 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,8 +46,8 @@ 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
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -56,40 +55,27 @@ pnpm gen # Regenerate Cloudflare Worker types
|
|||||||
|
|
||||||
Required:
|
Required:
|
||||||
|
|
||||||
- `DATABASE_URL`: SQLite/libSQL connection URL for local development. Use `file:local.db`.
|
|
||||||
- `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`.
|
||||||
|
|
||||||
Production also requires the Cloudflare D1 binding named `DB`, configured in `wrangler.jsonc`. Do not set `DATABASE_URL` in production unless you intentionally want to use a hosted libSQL database instead of D1.
|
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`.
|
||||||
|
|
||||||
Runtime database selection is:
|
Clearity always uses Drizzle's Cloudflare D1 driver through the `DB` binding. Apply migrations to the local D1 database before starting the app:
|
||||||
|
|
||||||
- If `DATABASE_URL` is set, Clearity uses the libSQL driver.
|
|
||||||
- If `DATABASE_URL` is not set and the Cloudflare `DB` binding exists, Clearity uses Drizzle's Cloudflare D1 driver.
|
|
||||||
|
|
||||||
The fastest local database is a SQLite file:
|
|
||||||
|
|
||||||
```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
|
||||||
```
|
|
||||||
|
|
||||||
To test against Wrangler's local D1 simulator instead, leave `DATABASE_URL` unset, apply migrations locally, and run the app through the Cloudflare Worker build:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pnpm build
|
|
||||||
pnpm wrangler d1 migrations apply clearity-production --local
|
|
||||||
pnpm wrangler dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Production Deployment
|
## Production Deployment
|
||||||
@@ -143,8 +129,6 @@ pnpm wrangler secret put ORIGIN
|
|||||||
pnpm wrangler secret put BETTER_AUTH_SECRET
|
pnpm wrangler secret put BETTER_AUTH_SECRET
|
||||||
```
|
```
|
||||||
|
|
||||||
Production uses the `DB` binding by default. `DATABASE_URL` is only needed as a production secret if you intentionally deploy against a hosted libSQL database instead of D1.
|
|
||||||
|
|
||||||
Build and deploy:
|
Build and deploy:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
Generated
+16
-8
@@ -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:
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import { env } from '$env/dynamic/private';
|
|
||||||
import { getRequestEvent } from '$app/server';
|
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 drizzleD1, type DrizzleD1Database } from 'drizzle-orm/d1';
|
||||||
import { drizzle as drizzleLibSQL, type LibSQLDatabase } from 'drizzle-orm/libsql';
|
|
||||||
import * as schema from './schema';
|
import * as schema from './schema';
|
||||||
|
|
||||||
type Schema = typeof schema;
|
type Schema = typeof schema;
|
||||||
type LocalDatabase = LibSQLDatabase<Schema> & { $client: Client };
|
|
||||||
type D1DatabaseClient = DrizzleD1Database<Schema> & { $client: D1Database };
|
|
||||||
|
|
||||||
export type Database = LocalDatabase;
|
export type Database = DrizzleD1Database<Schema> & { $client: D1Database };
|
||||||
export type RuntimeDatabase = LocalDatabase | D1DatabaseClient;
|
|
||||||
|
|
||||||
let localDb: LocalDatabase | undefined;
|
const d1Databases = new WeakMap<D1Database, Database>();
|
||||||
const d1Databases = new WeakMap<D1Database, D1DatabaseClient>();
|
|
||||||
|
|
||||||
function getD1Binding() {
|
function getD1Binding() {
|
||||||
try {
|
try {
|
||||||
@@ -23,17 +16,6 @@ function getD1Binding() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
function getD1Db(binding: D1Database) {
|
||||||
const cached = d1Databases.get(binding);
|
const cached = d1Databases.get(binding);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
@@ -44,13 +26,11 @@ function getD1Db(binding: D1Database) {
|
|||||||
return database;
|
return database;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDb(): RuntimeDatabase {
|
export function getDb(): Database {
|
||||||
if (env.DATABASE_URL) return getLocalDb();
|
|
||||||
|
|
||||||
const d1Binding = getD1Binding();
|
const d1Binding = getD1Binding();
|
||||||
if (d1Binding) return getD1Db(d1Binding);
|
if (d1Binding) return getD1Db(d1Binding);
|
||||||
|
|
||||||
return getLocalDb();
|
throw new Error('Cloudflare D1 DB binding is not available');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const db = new Proxy({} as Database, {
|
export const db = new Proxy({} as Database, {
|
||||||
|
|||||||
Vendored
+3
-1
@@ -1,9 +1,11 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// Generated by Wrangler by running `wrangler types` (hash: f6e835394237f01fba038726e2d4f9ae)
|
// 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;
|
DB: D1Database;
|
||||||
ASSETS: Fetcher;
|
ASSETS: Fetcher;
|
||||||
|
ORIGIN: string;
|
||||||
|
BETTER_AUTH_SECRET: string;
|
||||||
}
|
}
|
||||||
declare namespace Cloudflare {
|
declare namespace Cloudflare {
|
||||||
interface Env extends __BaseEnv_Env {}
|
interface Env extends __BaseEnv_Env {}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
"migrations_dir": "drizzle"
|
"migrations_dir": "drizzle"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"secrets": {
|
||||||
|
"required": ["ORIGIN", "BETTER_AUTH_SECRET"]
|
||||||
|
},
|
||||||
"workers_dev": true,
|
"workers_dev": true,
|
||||||
"preview_urls": true
|
"preview_urls": true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user