fix(library): read the catalogue the disk holds, not the version this build asks for

This commit is contained in:
2026-10-11 11:59:38 +07:00
parent 17624945f9
commit fbc13d437d
+56 -1
View File
@@ -270,6 +270,44 @@ export function onLibraryBlocked(fn: ((blocked: boolean) => void) | null): void
blockedSink = fn;
}
// The stores this build reads and writes. A catalogue that has been through the
// newer build has all of them — it is only a version number that separates the
// two.
const STORES = [FOLDERS, PHOTOS, THUMBS, EDITS, DIRS];
// The catalogue as the disk already holds it, whatever version that is. Asked for
// with no version on purpose: a build that names a version older than the one on
// the disk — which is what a test server and a release server behind one domain
// are, two builds against the one database the origin owns, with the reader moving
// between them — is answered with `VersionError` and nothing else, every read of
// the catalogue falls back on an empty list, and the LIBRARY draws the same empty
// column a folder-less visit draws. No note, no error, nothing to read off the
// screen. Taking the database as it stands is what lets the older build read what
// the newer one wrote; a store it needs and does not find is the one case that is
// still refused, and it is refused by name.
function openAsIs(): Promise<IDBDatabase> {
return new Promise<IDBDatabase>((resolve, reject) => {
const req = indexedDB.open(DB_NAME);
req.onblocked = () => blockedSink?.(true);
req.onsuccess = () => {
blockedSink?.(false);
const db = req.result;
const missing = STORES.filter((s) => !db.objectStoreNames.contains(s));
if (missing.length) {
// A catalogue that is missing a store is not one this build can read, and
// guessing at it is how a write lands on the wrong shape. The name goes to
// the console; the screen keeps its own empty column, as before.
console.error('catalogue: the database is missing', missing.join(', '));
db.close();
reject(new Error('catalogue-incomplete'));
return;
}
resolve(db);
};
req.onerror = () => reject(req.error);
});
}
function openDb(): Promise<IDBDatabase> {
dbPromise ??= new Promise<IDBDatabase>((resolve, reject) => {
const req = indexedDB.open(DB_NAME, DB_VERSION);
@@ -300,7 +338,24 @@ function openDb(): Promise<IDBDatabase> {
const db = req.result;
void migrateLegacyDatabase(db).then(() => resolve(db));
};
req.onerror = () => reject(req.error);
req.onerror = () => {
const err = req.error;
// The one refusal that is not a failure: the catalogue on the disk was
// written by a newer build of this app and this build is asking for an older
// version of it. Every other way in is closed and this one is open.
if (err && err.name === 'VersionError') {
void openAsIs().then(resolve, reject);
return;
}
reject(err);
};
}).catch((err: unknown) => {
// A failed open is a fact about the ask, not about the visit: put nothing in
// its place, so the read that follows the reader closing the other tab — or
// reloading the build that holds the pen — opens the database again instead of
// being answered forever by the one rejection.
dbPromise = null;
throw err;
});
return dbPromise;
}