widgettdc-api / apps /backend /src /platform /db /PrismaDatabaseAdapter.ts
Kraft102's picture
Update backend source
34367da verified
/**
* Prisma Database Adapter
*
* Production-grade database adapter using Prisma ORM.
* As of Prisma 5.10.0+, ARM64 Windows is fully supported.
*/
import { logger } from '../../utils/logger.js';
// Type definitions
export type PrismaClient = any;
/**
* Check if PostgreSQL is configured
*/
function hasPostgresConfig(): boolean {
return !!process.env.DATABASE_URL;
}
/**
* Database Adapter Interface
* Generic interface for database operations
*/
export interface DatabaseAdapter {
initialize(): Promise<void>;
disconnect(): Promise<void>;
query(sql: string, params?: any[]): Promise<any>;
execute(sql: string, params?: any[]): Promise<number>;
transaction<T>(fn: (tx: any) => Promise<T>): Promise<T>;
isAvailable(): boolean;
}
/**
* Stub adapter for when Prisma is unavailable
* Returns graceful no-ops and uses SQLite as fallback
*/
class StubDatabaseAdapter implements DatabaseAdapter {
private reason: string;
constructor(reason: string) {
this.reason = reason;
}
async initialize(): Promise<void> {
logger.info(`ℹ️ PostgreSQL skipped: ${this.reason}`);
logger.info(' System will use SQLite + Neo4j (fully functional)');
}
async disconnect(): Promise<void> {
// No-op
}
async query(_sql: string, _params?: any[]): Promise<any> {
throw new Error(`PostgreSQL not available: ${this.reason}. Use SQLite for local operations.`);
}
async transaction<T>(_fn: (tx: any) => Promise<T>): Promise<T> {
throw new Error(`PostgreSQL not available: ${this.reason}. Use SQLite for local operations.`);
}
async execute(_sql: string, _params?: any[]): Promise<number> {
throw new Error(`PostgreSQL not available: ${this.reason}. Use SQLite for local operations.`);
}
isAvailable(): boolean {
return false;
}
getClient(): null {
return null;
}
}
/**
* Prisma Database Adapter
* Production-grade database adapter using Prisma ORM
*
* Only loads Prisma when:
* 1. NOT on Windows ARM64 (no native binaries available)
* 2. DATABASE_URL is configured
*/
class PrismaDatabaseAdapterImpl implements DatabaseAdapter {
private prisma: any = null;
private isInitialized = false;
private loadError: string | null = null;
constructor() {
// Prisma loading is deferred to initialize()
}
async initialize(): Promise<void> {
if (this.isInitialized) return;
try {
// Dynamic import to defer binary loading
const { PrismaClient } = await import('@prisma/client');
this.prisma = new PrismaClient({
log: process.env.NODE_ENV === 'development'
? ['info', 'warn', 'error']
: ['error'],
});
await this.prisma.$connect();
logger.info('🗄️ Prisma Database connected to PostgreSQL');
// Enable pgvector extension if available (optional - we use Neo4j for vectors now)
try {
await this.prisma.$executeRaw`CREATE EXTENSION IF NOT EXISTS vector`;
logger.info('🔢 pgvector extension enabled');
} catch (_extError: any) {
// Non-critical - Railway PostgreSQL doesn't have pgvector
// We use Neo4j as primary vector store instead
logger.info('ℹ️ pgvector not available (using Neo4j for vector operations)');
}
this.isInitialized = true;
} catch (error: any) {
this.loadError = error.message;
logger.warn('⚠️ Prisma/PostgreSQL initialization failed:', { error: error.message });
logger.info(' Continuing with SQLite + Neo4j (fully functional)');
// Don't throw - allow graceful fallback
}
}
async disconnect(): Promise<void> {
if (this.prisma && this.isInitialized) {
await this.prisma.$disconnect();
logger.info('🗄️ Prisma Database disconnected');
}
}
async query(sql: string, params?: any[]): Promise<any> {
if (!this.prisma || !this.isInitialized) {
throw new Error('PostgreSQL not connected. Use SQLite for local operations.');
}
return this.prisma.$queryRawUnsafe(sql, ...(params || []));
}
async execute(sql: string, params?: any[]): Promise<number> {
if (!this.prisma || !this.isInitialized) {
throw new Error('PostgreSQL not connected. Use SQLite for local operations.');
}
return this.prisma.$executeRawUnsafe(sql, ...(params || []));
}
async transaction<T>(fn: (tx: any) => Promise<T>): Promise<T> {
if (!this.prisma || !this.isInitialized) {
throw new Error('PostgreSQL not connected. Use SQLite for local operations.');
}
return this.prisma.$transaction(fn);
}
isAvailable(): boolean {
return this.isInitialized && this.prisma !== null;
}
getClient(): any {
return this.prisma;
}
}
// Singleton instance
let dbInstance: DatabaseAdapter | null = null;
/**
* Get the database adapter (singleton)
*
* Returns:
* - StubAdapter if DATABASE_URL is not configured
* - PrismaDatabaseAdapterImpl otherwise (supports all platforms including ARM64 Windows)
*/
export function getDatabaseAdapter(): DatabaseAdapter & { getClient(): any } {
if (!dbInstance) {
if (!hasPostgresConfig()) {
dbInstance = new StubDatabaseAdapter('DATABASE_URL not configured');
} else {
dbInstance = new PrismaDatabaseAdapterImpl();
}
}
return dbInstance as DatabaseAdapter & { getClient(): any };
}
// Export type alias for compatibility
export { PrismaDatabaseAdapterImpl as PrismaDatabaseAdapter };