Finalize turn-role invariants and legacy migration cutover
This commit is contained in:
@@ -2,27 +2,41 @@ import { Database } from 'bun:sqlite';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { ASSISTANT_NAME, DATA_DIR, STORE_DIR } from '../config.js';
|
||||
import { logger } from '../logger.js';
|
||||
import type { RegisteredGroup } from '../types.js';
|
||||
import { readJsonFile } from '../utils.js';
|
||||
import { ASSISTANT_NAME, STORE_DIR } from '../config.js';
|
||||
import { applyBaseSchema } from './base-schema.js';
|
||||
import { applySchemaMigrations } from './schema.js';
|
||||
|
||||
export interface JsonStateMigrationHooks {
|
||||
setRouterState(key: string, value: string): void;
|
||||
setSession(groupFolder: string, sessionId: string): void;
|
||||
writeLegacyRegisteredGroupAndSyncRoomSettings(
|
||||
jid: string,
|
||||
group: RegisteredGroup,
|
||||
): void;
|
||||
function setForeignKeys(database: Database, enabled: boolean): void {
|
||||
database.exec(`PRAGMA foreign_keys = ${enabled ? 'ON' : 'OFF'}`);
|
||||
}
|
||||
|
||||
function assertNoForeignKeyViolations(database: Database): void {
|
||||
const violations = database
|
||||
.prepare('PRAGMA foreign_key_check')
|
||||
.all() as Array<{
|
||||
table: string;
|
||||
rowid: number;
|
||||
parent: string;
|
||||
fkid: number;
|
||||
}>;
|
||||
if (violations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const violation = violations[0]!;
|
||||
throw new Error(
|
||||
`Foreign key integrity check failed for ${violation.table}(rowid=${violation.rowid}) referencing ${violation.parent} [fk=${violation.fkid}]`,
|
||||
);
|
||||
}
|
||||
|
||||
export function initializeDatabaseSchema(database: Database): void {
|
||||
setForeignKeys(database, false);
|
||||
applyBaseSchema(database);
|
||||
applySchemaMigrations(database, {
|
||||
assistantName: ASSISTANT_NAME,
|
||||
});
|
||||
setForeignKeys(database, true);
|
||||
assertNoForeignKeyViolations(database);
|
||||
}
|
||||
|
||||
export function openPersistentDatabase(): Database {
|
||||
@@ -42,62 +56,3 @@ export function openInMemoryDatabase(): Database {
|
||||
export function openDatabaseFromFile(dbPath: string): Database {
|
||||
return new Database(dbPath);
|
||||
}
|
||||
|
||||
export function migrateJsonStateFromFiles(
|
||||
hooks: JsonStateMigrationHooks,
|
||||
): void {
|
||||
const migrateFile = (filename: string) => {
|
||||
const filePath = path.join(DATA_DIR, filename);
|
||||
const data = readJsonFile(filePath);
|
||||
if (data === null) return null;
|
||||
try {
|
||||
fs.renameSync(filePath, `${filePath}.migrated`);
|
||||
} catch {
|
||||
/* best effort */
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const routerState = migrateFile('router_state.json') as {
|
||||
last_timestamp?: string;
|
||||
last_agent_timestamp?: Record<string, string>;
|
||||
} | null;
|
||||
if (routerState) {
|
||||
if (routerState.last_timestamp) {
|
||||
hooks.setRouterState('last_timestamp', routerState.last_timestamp);
|
||||
}
|
||||
if (routerState.last_agent_timestamp) {
|
||||
hooks.setRouterState(
|
||||
'last_agent_timestamp',
|
||||
JSON.stringify(routerState.last_agent_timestamp),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const sessions = migrateFile('sessions.json') as Record<
|
||||
string,
|
||||
string
|
||||
> | null;
|
||||
if (sessions) {
|
||||
for (const [folder, sessionId] of Object.entries(sessions)) {
|
||||
hooks.setSession(folder, sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
const groups = migrateFile('registered_groups.json') as Record<
|
||||
string,
|
||||
RegisteredGroup
|
||||
> | null;
|
||||
if (groups) {
|
||||
for (const [jid, group] of Object.entries(groups)) {
|
||||
try {
|
||||
hooks.writeLegacyRegisteredGroupAndSyncRoomSettings(jid, group);
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
{ jid, folder: group.folder, err },
|
||||
'Skipping migrated registered group with invalid folder',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user