Files
archived-linsa/packages/web/src/db/connection.ts
Nikita 8cd4b943a5 .
2025-12-21 13:37:19 -08:00

76 lines
2.5 KiB
TypeScript

import postgres from "postgres"
import { drizzle } from "drizzle-orm/postgres-js"
import * as schema from "./schema"
type Hyperdrive = {
connectionString: string
}
type CloudflareEnv = {
DATABASE_URL?: string
HYPERDRIVE?: Hyperdrive
}
// Note: NO caching - Cloudflare Workers don't allow sharing I/O objects across requests
// Get the database connection string, preferring DATABASE_URL over Hyperdrive
const getConnectionString = (env?: CloudflareEnv): string => {
// Prefer DATABASE_URL if set (direct connection, bypasses Hyperdrive)
if (env?.DATABASE_URL) {
return env.DATABASE_URL
}
// Fall back to Hyperdrive if available
if (env?.HYPERDRIVE?.connectionString) {
return env.HYPERDRIVE.connectionString
}
// Fall back to process.env (local dev)
if (process.env.DATABASE_URL) {
return process.env.DATABASE_URL
}
throw new Error("No database connection available. Set DATABASE_URL or configure Hyperdrive.")
}
// Helper to get Cloudflare env from server context
const getCloudflareEnv = (): CloudflareEnv | undefined => {
try {
const { getServerContext } = require("@tanstack/react-start/server") as {
getServerContext: () => { cloudflare?: { env?: CloudflareEnv } } | null
}
return getServerContext()?.cloudflare?.env
} catch {
return undefined
}
}
// Convenience function to get db using server context
export const db = () => getDb(getConnectionString(getCloudflareEnv()))
export const authDb = () => getAuthDb(getConnectionString(getCloudflareEnv()))
// Main db with snake_case casing for app tables (chat_threads, chat_messages)
export const getDb = (databaseUrlOrHyperdrive: string | Hyperdrive) => {
const connectionString =
typeof databaseUrlOrHyperdrive === "string"
? databaseUrlOrHyperdrive
: databaseUrlOrHyperdrive.connectionString
// Create fresh connection per request for Cloudflare Workers compatibility
const sql = postgres(connectionString, { prepare: false })
return drizzle(sql, { schema, casing: "snake_case" })
}
// Auth db WITHOUT casing transform for better-auth tables (users, sessions, etc.)
// better-auth uses camelCase columns and manages its own naming
export const getAuthDb = (databaseUrlOrHyperdrive: string | Hyperdrive) => {
const connectionString =
typeof databaseUrlOrHyperdrive === "string"
? databaseUrlOrHyperdrive
: databaseUrlOrHyperdrive.connectionString
// Create fresh connection per request for Cloudflare Workers compatibility
const sql = postgres(connectionString, { prepare: false })
return drizzle(sql, { schema })
}