File size: 6,028 Bytes
34367da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/**

 * 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 };