import type Database from 'better-sqlite3'; const initialized = new WeakSet(); export function ensureGameFileSchema(handle: Database.Database): void { if (initialized.has(handle)) return; // Worker readers open only after the controller has initialized the schema. if (handle.readonly) { initialized.add(handle); return; } handle.exec(` CREATE TABLE IF NOT EXISTS game_file_libraries ( id TEXT PRIMARY KEY, name TEXT NOT NULL COLLATE NOCASE UNIQUE, game TEXT NOT NULL CHECK(game IN ('t5','t6')), description TEXT NOT NULL, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS game_file_versions ( id TEXT PRIMARY KEY, library_id TEXT NOT NULL REFERENCES game_file_libraries(id), version TEXT NOT NULL, description TEXT NOT NULL, dlc TEXT NOT NULL, state TEXT NOT NULL CHECK(state IN ('importing','ready','failed')), size_bytes INTEGER NOT NULL DEFAULT 0, manifest TEXT NOT NULL DEFAULT '[]', manifest_sha256 TEXT, created_at TEXT NOT NULL, published_at TEXT, storage_location TEXT NOT NULL, source_host_id TEXT NOT NULL, source_volume TEXT NOT NULL, error TEXT, operation_id TEXT, UNIQUE(library_id, version) ); CREATE TABLE IF NOT EXISTS host_library_caches ( host_id TEXT NOT NULL, version_id TEXT NOT NULL REFERENCES game_file_versions(id), status TEXT NOT NULL CHECK(status IN ('missing','downloading','verifying','ready','failed','outdated')), bytes_downloaded INTEGER NOT NULL DEFAULT 0, bytes_total INTEGER NOT NULL DEFAULT 0, manifest_sha256 TEXT, error TEXT, updated_at TEXT NOT NULL, operation_id TEXT, PRIMARY KEY(host_id, version_id) ); CREATE TABLE IF NOT EXISTS game_file_entries ( version_id TEXT NOT NULL REFERENCES game_file_versions(id), path TEXT NOT NULL, size INTEGER NOT NULL, sha256 TEXT NOT NULL, PRIMARY KEY(version_id,path) ); CREATE TABLE IF NOT EXISTS game_file_upload_entries ( version_id TEXT NOT NULL REFERENCES game_file_versions(id), path TEXT NOT NULL, size INTEGER NOT NULL, last_modified INTEGER NOT NULL, PRIMARY KEY(version_id,path) ); CREATE TABLE IF NOT EXISTS game_file_cleanup ( version_id TEXT PRIMARY KEY REFERENCES game_file_versions(id), started_at TEXT NOT NULL, removed_at TEXT ); INSERT OR IGNORE INTO game_file_entries(version_id,path,size,sha256) SELECT v.id,json_extract(f.value,'$.path'),json_extract(f.value,'$.size'),json_extract(f.value,'$.sha256') FROM game_file_versions v,json_each(v.manifest) f; CREATE TRIGGER IF NOT EXISTS game_file_versions_immutable BEFORE UPDATE ON game_file_versions WHEN OLD.state = 'ready' BEGIN SELECT RAISE(ABORT, 'Published game-file versions are immutable'); END; CREATE INDEX IF NOT EXISTS idx_host_library_caches_version ON host_library_caches(version_id); `); const versionColumns = handle.prepare('PRAGMA table_info(game_file_versions)').all() as { name: string; }[]; if (!versionColumns.some((column) => column.name === 'last_activity_at')) handle.exec('ALTER TABLE game_file_versions ADD COLUMN last_activity_at TEXT'); const cacheColumns = handle.prepare('PRAGMA table_info(host_library_caches)').all() as { name: string; }[]; if (!cacheColumns.some((column) => column.name === 'bytes_verified')) handle.exec('ALTER TABLE host_library_caches ADD COLUMN bytes_verified INTEGER'); if (!cacheColumns.some((column) => column.name === 'transfer_progress')) handle.exec('ALTER TABLE host_library_caches ADD COLUMN transfer_progress TEXT'); initialized.add(handle); }