import { drizzle, type NodePgDatabase } from "drizzle-orm/node-postgres"; import { Pool, type PoolConfig } from "pg"; import { identityAdapterSchema } from "./schema.js"; export type IdentityDrizzleDatabase = NodePgDatabase; export type IdentityDatabaseErrorCode = "IDENTITY_DATABASE_CONFIGURATION_INVALID"; /** Never carries the offending connection string, host, or underlying driver error. */ export class IdentityDatabaseError extends Error { readonly code: IdentityDatabaseErrorCode; constructor(code: IdentityDatabaseErrorCode) { super("Identity database configuration is invalid."); this.name = "IdentityDatabaseError"; this.code = code; this.stack = `${this.name}: ${this.message}`; Object.freeze(this); } } export interface IdentityDatabaseOptions { readonly connectionTimeoutMs?: number; } export interface IdentityDatabaseHandle { readonly close: () => Promise; readonly db: IdentityDrizzleDatabase; } function validateConnectionString(connectionString: string): string { if (connectionString.trim().length === 0) { throw new IdentityDatabaseError("IDENTITY_DATABASE_CONFIGURATION_INVALID"); } let protocol: string; try { protocol = new URL(connectionString).protocol; } catch { throw new IdentityDatabaseError("IDENTITY_DATABASE_CONFIGURATION_INVALID"); } if (protocol !== "postgres:" && protocol !== "postgresql:") { throw new IdentityDatabaseError("IDENTITY_DATABASE_CONFIGURATION_INVALID"); } return connectionString; } /** * Explicitly constructs the Core-owned Drizzle database instance used by the identity factory. * Building the `pg.Pool` performs no network I/O until a query is issued, and this function is * only ever invoked by explicit caller code — never during module import. */ export function createIdentityDrizzleDatabase( connectionString: string, options: IdentityDatabaseOptions = {}, ): IdentityDatabaseHandle { const validatedConnectionString = validateConnectionString(connectionString); const poolConfig: PoolConfig = { connectionString: validatedConnectionString }; if (options.connectionTimeoutMs !== undefined) { poolConfig.connectionTimeoutMillis = options.connectionTimeoutMs; } const pool = new Pool(poolConfig); const db = drizzle(pool, { schema: identityAdapterSchema }); return Object.freeze({ close: () => pool.end(), db, }); }