refactor(launcher): split history.ts into focused modules

- Extract types → history-types.ts, DB layer → history-db.ts, navigation → history-navigation.ts
- history.ts becomes a re-export barrel
- Fix findNextEpisode: deleted-file fallback now advances to next season
- Add isReadonlyWalRetryError (WAL-mode guard before read-write retry)
- Strengthen bun-sqlite.d.ts with generics on Statement/query/prepare
This commit is contained in:
2026-07-05 00:57:06 -07:00
parent f4c98278c1
commit 651e541ea8
6 changed files with 324 additions and 239 deletions
+48
View File
@@ -7,6 +7,7 @@ import { Database } from 'bun:sqlite';
import {
findNextEpisode,
groupHistoryBySeries,
isReadonlyWalRetryError,
listSeasonDirs,
queryLocalWatchHistory,
resolveSeriesRoot,
@@ -182,6 +183,18 @@ test('findNextEpisode falls back to episode numbers when file was removed', () =
}
});
test('findNextEpisode advances seasons when a deleted file was the last episode', () => {
const seriesRoot = createSeriesTree();
try {
const season1 = path.join(seriesRoot, 'Season-1');
const season2 = path.join(seriesRoot, 'Season-2');
const missing = path.join(season1, 'Show - S01E03 - Deleted Cut.mkv');
assert.equal(findNextEpisode(missing), path.join(season2, 'Show - S02E01.mkv'));
} finally {
fs.rmSync(path.dirname(seriesRoot), { recursive: true, force: true });
}
});
function createHistoryDb(dbPath: string, options: { wal?: boolean } = {}): void {
const db = new Database(dbPath);
try {
@@ -269,3 +282,38 @@ test('queryLocalWatchHistory reads a cleanly-closed WAL database', () => {
fs.rmSync(dir, { recursive: true, force: true });
}
});
test('isReadonlyWalRetryError only accepts readonly errors from WAL-mode databases', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-history-retry-'));
const walDbPath = path.join(dir, 'wal.sqlite');
const rollbackDbPath = path.join(dir, 'rollback.sqlite');
try {
createHistoryDb(walDbPath, { wal: true });
createHistoryDb(rollbackDbPath);
assert.equal(
isReadonlyWalRetryError(
Object.assign(new Error('attempt to write a readonly database'), {
code: 'SQLITE_READONLY',
}),
walDbPath,
),
true,
);
assert.equal(
isReadonlyWalRetryError(new Error('no such table: imm_sessions'), walDbPath),
false,
);
assert.equal(
isReadonlyWalRetryError(
Object.assign(new Error('attempt to write a readonly database'), {
code: 'SQLITE_READONLY',
}),
rollbackDbPath,
),
false,
);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});