| 51 | } |
| 52 | |
| 53 | export class DatabaseManager { |
| 54 | private cache = new Map<string, DatabaseAdapter>() |
| 55 | private nativeBinding?: string |
| 56 | private migrationDeps?: MigrationDeps |
| 57 | private runtime: RuntimeIdentity | null |
| 58 | |
| 59 | constructor( |
| 60 | private pathProvider: PathProvider, |
| 61 | options: DatabaseManagerOptions = {} |
| 62 | ) { |
| 63 | if (!options.runtime && !options.allowMissingRuntimeForTests) { |
| 64 | throw new Error('DatabaseManager runtime identity is required for data directory compatibility checks.') |
| 65 | } |
| 66 | |
| 67 | this.nativeBinding = options?.nativeBinding |
| 68 | this.migrationDeps = createMigrationDeps(options?.migrationDeps) |
| 69 | this.runtime = options?.runtime ?? null |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * Open a session DB (read-only by default, cached). |
| 74 | */ |
| 75 | open(sessionId: string, options?: { readonly?: boolean }): DatabaseAdapter | null { |
| 76 | this.assertCompatible() |
| 77 | |
| 78 | if (this.cache.has(sessionId)) { |
| 79 | return this.cache.get(sessionId)! |
| 80 | } |
| 81 | |
| 82 | const dbPath = this.getDbPath(sessionId) |
| 83 | if (!fs.existsSync(dbPath)) return null |
| 84 | |
| 85 | this.migrateIfNeeded(dbPath) |
| 86 | this.assertCompatible() |
| 87 | |
| 88 | const adapter = openBetterSqliteDatabase(dbPath, { |
| 89 | readonly: options?.readonly ?? true, |
| 90 | nativeBinding: this.nativeBinding, |
| 91 | }) |
| 92 | this.cache.set(sessionId, adapter) |
| 93 | return adapter |
| 94 | } |
| 95 | |
| 96 | private migrateIfNeeded(dbPath: string): void { |
| 97 | const readonlyAdapter = openBetterSqliteDatabase(dbPath, { |
| 98 | readonly: true, |
| 99 | nativeBinding: this.nativeBinding, |
| 100 | }) |
| 101 | |
| 102 | try { |
| 103 | if (!isChatSessionDb(readonlyAdapter)) return |
| 104 | this.raiseCompatibilityGateIfNeeded(getSchemaVersion(readonlyAdapter)) |
| 105 | if (!coreNeedsMigration(readonlyAdapter, CURRENT_SCHEMA_VERSION)) return |
| 106 | } finally { |
| 107 | readonlyAdapter.close() |
| 108 | } |
| 109 | |
| 110 | const adapter = openBetterSqliteDatabase(dbPath, { |
nothing calls this directly
no outgoing calls
no test coverage detected