mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-12 00:49:14 +08:00
- Added deprecation notices to various services and components, indicating they are scheduled for removal in v2.0.0. - Noted that feature PRs affecting these files are currently blocked, and only critical bug fixes will be accepted during the migration phase. - Provided context and status links for ongoing v2 refactoring efforts. This change is part of the preparation for the upcoming major version update.
173 lines
5.0 KiB
TypeScript
173 lines
5.0 KiB
TypeScript
/**
|
|
* @deprecated Scheduled for removal in v2.0.0
|
|
* --------------------------------------------------------------------------
|
|
* ⚠️ NOTICE: V2 DATA&UI REFACTORING (by 0xfullex)
|
|
* --------------------------------------------------------------------------
|
|
* STOP: Feature PRs affecting this file are currently BLOCKED.
|
|
* Only critical bug fixes are accepted during this migration phase.
|
|
*
|
|
* This file is being refactored to v2 standards.
|
|
* Any non-critical changes will conflict with the ongoing work.
|
|
*
|
|
* 🔗 Context & Status:
|
|
* - Contribution Hold: https://github.com/CherryHQ/cherry-studio/issues/10954
|
|
* - v2 Refactor PR : https://github.com/CherryHQ/cherry-studio/pull/10162
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
import { type Client, createClient } from '@libsql/client'
|
|
import { loggerService } from '@logger'
|
|
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
|
|
import { drizzle } from 'drizzle-orm/libsql'
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
|
|
import { dbPath } from '../drizzle.config'
|
|
import { MigrationService } from './MigrationService'
|
|
import * as schema from './schema'
|
|
|
|
const logger = loggerService.withContext('DatabaseManager')
|
|
|
|
/**
|
|
* Database initialization state
|
|
*/
|
|
enum InitState {
|
|
INITIALIZING = 'initializing',
|
|
INITIALIZED = 'initialized',
|
|
FAILED = 'failed'
|
|
}
|
|
|
|
/**
|
|
* DatabaseManager - Singleton class for managing libsql database connections
|
|
*
|
|
* Responsibilities:
|
|
* - Single source of truth for database connection
|
|
* - Thread-safe initialization with state management
|
|
* - Automatic migration handling
|
|
* - Safe connection cleanup
|
|
* - Error recovery and retry logic
|
|
* - Windows platform compatibility fixes
|
|
*/
|
|
export class DatabaseManager {
|
|
private static instance: DatabaseManager | null = null
|
|
|
|
private client: Client | null = null
|
|
private db: LibSQLDatabase<typeof schema> | null = null
|
|
private state: InitState = InitState.INITIALIZING
|
|
|
|
/**
|
|
* Get the singleton instance (database initialization starts automatically)
|
|
*/
|
|
public static async getInstance(): Promise<DatabaseManager> {
|
|
if (DatabaseManager.instance) {
|
|
return DatabaseManager.instance
|
|
}
|
|
|
|
const instance = new DatabaseManager()
|
|
await instance.initialize()
|
|
DatabaseManager.instance = instance
|
|
|
|
return instance
|
|
}
|
|
|
|
/**
|
|
* Perform the actual initialization
|
|
*/
|
|
public async initialize(): Promise<void> {
|
|
if (this.state === InitState.INITIALIZED) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
logger.info(`Initializing database at: ${dbPath}`)
|
|
|
|
// Ensure database directory exists
|
|
const dbDir = path.dirname(dbPath)
|
|
if (!fs.existsSync(dbDir)) {
|
|
logger.info(`Creating database directory: ${dbDir}`)
|
|
fs.mkdirSync(dbDir, { recursive: true })
|
|
}
|
|
|
|
// Check if database file is corrupted (Windows specific check)
|
|
if (fs.existsSync(dbPath)) {
|
|
const stats = fs.statSync(dbPath)
|
|
if (stats.size === 0) {
|
|
logger.warn('Database file is empty, removing corrupted file')
|
|
fs.unlinkSync(dbPath)
|
|
}
|
|
}
|
|
|
|
// Create client with platform-specific options
|
|
this.client = createClient({
|
|
url: `file:${dbPath}`,
|
|
// intMode: 'number' helps avoid some Windows compatibility issues
|
|
intMode: 'number'
|
|
})
|
|
|
|
// Create drizzle instance
|
|
this.db = drizzle(this.client, { schema })
|
|
|
|
// Run migrations
|
|
const migrationService = new MigrationService(this.db, this.client)
|
|
await migrationService.runMigrations()
|
|
|
|
this.state = InitState.INITIALIZED
|
|
logger.info('Database initialized successfully')
|
|
} catch (error) {
|
|
const err = error as Error
|
|
logger.error('Database initialization failed:', {
|
|
error: err.message,
|
|
stack: err.stack
|
|
})
|
|
|
|
// Clean up failed initialization
|
|
this.cleanupFailedInit()
|
|
|
|
// Set failed state
|
|
this.state = InitState.FAILED
|
|
throw new Error(`Database initialization failed: ${err.message || 'Unknown error'}`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clean up after failed initialization
|
|
*/
|
|
private cleanupFailedInit(): void {
|
|
if (this.client) {
|
|
try {
|
|
// On Windows, closing a partially initialized client can crash
|
|
// Wrap in try-catch and ignore errors during cleanup
|
|
this.client.close()
|
|
} catch (error) {
|
|
logger.warn('Failed to close client during cleanup:', error as Error)
|
|
}
|
|
}
|
|
this.client = null
|
|
this.db = null
|
|
}
|
|
|
|
/**
|
|
* Get the database instance
|
|
* Automatically waits for initialization to complete
|
|
* @throws Error if database initialization failed
|
|
*/
|
|
public getDatabase(): LibSQLDatabase<typeof schema> {
|
|
return this.db!
|
|
}
|
|
|
|
/**
|
|
* Get the raw client (for advanced operations)
|
|
* Automatically waits for initialization to complete
|
|
* @throws Error if database initialization failed
|
|
*/
|
|
public async getClient(): Promise<Client> {
|
|
return this.client!
|
|
}
|
|
|
|
/**
|
|
* Check if database is initialized
|
|
*/
|
|
public isInitialized(): boolean {
|
|
return this.state === InitState.INITIALIZED
|
|
}
|
|
}
|