Files
sofa/lib/db/client.ts
T
jakeandClaude Opus 4.6 f36ca0cbf8 Optimize performance: batch DB ops, N+1 fixes, Suspense streaming, and Jotai best practices
- Add composite indexes on userEpisodeWatches and userMovieWatches for hot queries
- Batch episode tracking: wrap season/batch watches in single transaction (~8 queries vs 8*N)
- Fix N+1 patterns in credits, recommendations, and filmography with batch prefetch+insert
- Stream TV season hydration via Suspense instead of blocking page render
- Optimize webhook logs (per-connection LIMIT 10) and system health queries
- Merge genre filter waterfalls into single Promise.all fetch
- Migrate deprecated Jotai loadable() to unwrap()
- Replace isolated createStore()+Provider with useHydrateAtoms on root store
- Remove unnecessary atomWithStorage SSR guards (handled by Jotai internally)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:33:27 -05:00

69 lines
2.1 KiB
TypeScript

import { Database } from "bun:sqlite";
import path from "node:path";
import type { Logger } from "drizzle-orm";
import { drizzle } from "drizzle-orm/bun-sqlite";
import { createLogger } from "@/lib/logger";
import * as schema from "./schema";
const log = createLogger("drizzle");
const drizzleLogger: Logger = {
logQuery(query: string, params: unknown[]) {
log.debug(query, params.length ? params : "");
},
};
// Lazy-init singleton via globalThis. Next.js evaluates module-level code at
// build time (when no database exists) and re-imports modules on HMR in dev
// (which would create duplicate connections). Stashing the instances on
// globalThis and wrapping `db` in a Proxy defers all real work to the first
// property access at runtime, sidestepping both problems.
//
// Only the Drizzle instance (`db`) is exported as a Proxy — the raw bun:sqlite
// Database is kept internal because its native C++ methods lose their `this`
// binding when accessed through Reflect.get, so a Proxy around it would break.
// Use `closeDatabase()` for graceful shutdown instead.
const globalForDb = globalThis as unknown as {
_db: ReturnType<typeof drizzle> | undefined;
_client: Database | undefined;
};
const DATABASE_URL =
process.env.DATABASE_URL ||
path.join(process.env.DATA_DIR || "./data", "sqlite.db");
function getClient() {
if (!globalForDb._client) {
globalForDb._client = new Database(DATABASE_URL);
globalForDb._client.run("PRAGMA journal_mode = WAL");
globalForDb._client.run("PRAGMA foreign_keys = ON");
globalForDb._client.run("PRAGMA busy_timeout = 5000");
}
return globalForDb._client;
}
function getDb() {
if (!globalForDb._db) {
globalForDb._db = drizzle({
client: getClient(),
schema,
logger: drizzleLogger,
});
}
return globalForDb._db;
}
export const db = new Proxy({} as ReturnType<typeof drizzle>, {
get(_, prop) {
return Reflect.get(getDb(), prop);
},
});
/** Close the current connection, and clear singletons so the Proxy re-initializes on next access. */
export function closeDatabase() {
globalForDb._client?.close();
globalForDb._client = undefined;
globalForDb._db = undefined;
}