117 lines
3.6 KiB
TypeScript
117 lines
3.6 KiB
TypeScript
import { Database } from 'bun:sqlite';
|
|
|
|
import { DATA_DIR, normalizeServiceId, SERVICE_ID } from '../config.js';
|
|
import { listUnexpectedDataStateFiles } from '../data-state-files.js';
|
|
import { StartupPreconditionError } from '../startup-preconditions.js';
|
|
import {
|
|
openDatabaseFromFile,
|
|
openInMemoryDatabase,
|
|
openPersistentDatabase,
|
|
initializeDatabaseSchema,
|
|
} from './bootstrap.js';
|
|
import {
|
|
clearExpiredPairedTaskExecutionLeasesInDatabase,
|
|
clearPairedTaskExecutionLeasesForServiceInDatabase,
|
|
} from './paired-state.js';
|
|
import { getUnsupportedRouterStateKeysFromDatabase } from './router-state.js';
|
|
|
|
function finalizeDatabaseInitialization(database: Database): void {
|
|
clearPairedTaskExecutionLeasesForServiceInDatabase(
|
|
database,
|
|
normalizeServiceId(SERVICE_ID),
|
|
);
|
|
clearExpiredPairedTaskExecutionLeasesInDatabase(database);
|
|
}
|
|
|
|
function getUnexpectedLegacyRoomBindingTables(database: Database): string[] {
|
|
const tableRows = database
|
|
.prepare(
|
|
`SELECT name
|
|
FROM sqlite_master
|
|
WHERE type = 'table'
|
|
AND name NOT LIKE 'sqlite_%'
|
|
ORDER BY name`,
|
|
)
|
|
.all() as Array<{ name: string }>;
|
|
const matches: string[] = [];
|
|
|
|
for (const row of tableRows) {
|
|
const columns = database
|
|
.prepare(`PRAGMA table_info("${row.name.replaceAll('"', '""')}")`)
|
|
.all() as Array<{ name: string }>;
|
|
const columnNames = new Set(columns.map((column) => column.name));
|
|
const looksLikeLegacyRoomBindingTable =
|
|
columnNames.has('jid') &&
|
|
columnNames.has('name') &&
|
|
columnNames.has('folder') &&
|
|
columnNames.has('trigger_pattern') &&
|
|
columnNames.has('added_at');
|
|
const isBackupTable = columnNames.has('backup_at');
|
|
|
|
if (looksLikeLegacyRoomBindingTable && !isBackupTable) {
|
|
matches.push(row.name);
|
|
}
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
|
|
function assertNoPendingLegacyRoomMigration(database: Database): void {
|
|
const pendingTables = getUnexpectedLegacyRoomBindingTables(database);
|
|
if (pendingTables.length === 0) {
|
|
return;
|
|
}
|
|
|
|
throw new StartupPreconditionError(
|
|
`Legacy room migration required before startup (tables=${pendingTables.join(',')})`,
|
|
);
|
|
}
|
|
|
|
function assertNoUnexpectedDataStateFiles(): void {
|
|
const pendingFiles = listUnexpectedDataStateFiles(DATA_DIR);
|
|
if (pendingFiles.length === 0) {
|
|
return;
|
|
}
|
|
|
|
throw new StartupPreconditionError(
|
|
`Unexpected data state files detected before startup (files=${pendingFiles.join(',')})`,
|
|
);
|
|
}
|
|
|
|
function assertNoUnsupportedRouterStateDbKeys(database: Database): void {
|
|
const unsupportedKeys = getUnsupportedRouterStateKeysFromDatabase(database);
|
|
if (unsupportedKeys.length === 0) {
|
|
return;
|
|
}
|
|
|
|
throw new StartupPreconditionError(
|
|
`Unsupported router_state DB keys remain before startup (keys=${unsupportedKeys.join(',')})`,
|
|
);
|
|
}
|
|
|
|
export function openInitializedPersistentDatabase(): Database {
|
|
const database = openPersistentDatabase();
|
|
initializeDatabaseSchema(database);
|
|
assertNoPendingLegacyRoomMigration(database);
|
|
assertNoUnexpectedDataStateFiles();
|
|
assertNoUnsupportedRouterStateDbKeys(database);
|
|
finalizeDatabaseInitialization(database);
|
|
return database;
|
|
}
|
|
|
|
export function openInitializedInMemoryDatabase(): Database {
|
|
const database = openInMemoryDatabase();
|
|
initializeDatabaseSchema(database);
|
|
finalizeDatabaseInitialization(database);
|
|
return database;
|
|
}
|
|
|
|
export function openInitializedDatabaseFromFile(dbPath: string): Database {
|
|
const database = openDatabaseFromFile(dbPath);
|
|
initializeDatabaseSchema(database);
|
|
assertNoPendingLegacyRoomMigration(database);
|
|
assertNoUnsupportedRouterStateDbKeys(database);
|
|
finalizeDatabaseInitialization(database);
|
|
return database;
|
|
}
|