Dead code -- nothing in the codebase imported or called it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
106 lines
4.4 KiB
JavaScript
106 lines
4.4 KiB
JavaScript
// Persistent cache for discovered articles, keyed by each article file's own
|
|
// path. Raw IndexedDB (no wrapper library — see
|
|
// .claude/nginx-sitemap-crawler-plan.md L4/L5 on the zero-dependency,
|
|
// long-stable-API requirement). If IndexedDB is unavailable (private
|
|
// browsing, old browser, blocked), every operation below silently becomes a
|
|
// no-op so discovery.js gets live-fetch behaviour for free without needing
|
|
// its own availability branching (plan doc R6).
|
|
|
|
// Bump this whenever discovery.js's derived article record shape changes.
|
|
// The version is baked into the DB name so a code deploy invalidates every
|
|
// cached entry immediately, instead of stale-shaped records lingering until
|
|
// each file's mtime happens to change — which, on a "deploy and forget"
|
|
// site, could be years.
|
|
const CACHE_SCHEMA_VERSION = 3;
|
|
const DB_NAME = `cmjs-cache-v${CACHE_SCHEMA_VERSION}`;
|
|
const STORE_NAME = 'articleFiles';
|
|
|
|
let cacheAvailable = true;
|
|
let dbPromise = null;
|
|
|
|
function openDb() {
|
|
if (!dbPromise) {
|
|
dbPromise = new Promise((resolve, reject) => {
|
|
if (typeof indexedDB === 'undefined') { reject(new Error('IndexedDB unavailable')); return; }
|
|
const request = indexedDB.open(DB_NAME, 1);
|
|
request.onupgradeneeded = () => {
|
|
request.result.createObjectStore(STORE_NAME, { keyPath: 'path' });
|
|
};
|
|
request.onsuccess = () => resolve(request.result);
|
|
request.onerror = () => reject(request.error);
|
|
request.onblocked = () => reject(new Error('IndexedDB open blocked'));
|
|
}).catch(error => { cacheAvailable = false; throw error; });
|
|
}
|
|
return dbPromise;
|
|
}
|
|
|
|
// Opens a store and resolves/rejects with the given IDBRequest's own outcome
|
|
// — the standard pattern, so result timing never depends on transaction
|
|
// lifecycle details.
|
|
async function runRequest(mode, makeRequest) {
|
|
if (!cacheAvailable) return undefined;
|
|
let db;
|
|
try {
|
|
db = await openDb();
|
|
} catch {
|
|
return undefined; // openDb() itself already flipped cacheAvailable
|
|
}
|
|
try {
|
|
const store = db.transaction(STORE_NAME, mode).objectStore(STORE_NAME);
|
|
return await new Promise((resolve, reject) => {
|
|
const request = makeRequest(store);
|
|
request.onsuccess = () => resolve(request.result);
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
} catch {
|
|
// A single request/transaction failing (e.g. one write hitting a
|
|
// storage quota) doesn't mean IndexedDB itself is gone — only
|
|
// openDb() failing disables the cache wholesale. Leave
|
|
// cacheAvailable as-is so later calls can still try.
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
// Returns every cached { path, mtime, article } record as a Map keyed by
|
|
// path — one bulk read instead of a separate IndexedDB round-trip per
|
|
// article file, which matters once there are hundreds of them: discovery.js
|
|
// reads this once up front rather than doing a point lookup per file during
|
|
// indexing. Empty Map on a miss or if the cache is unavailable.
|
|
export async function getAllCacheEntries() {
|
|
const records = await runRequest('readonly', store => store.getAll());
|
|
return new Map((records || []).map(record => [record.path, record]));
|
|
}
|
|
|
|
export async function setCacheEntry(path, entry) {
|
|
await runRequest('readwrite', store => store.put({ ...entry, path }));
|
|
}
|
|
|
|
// Removes any cached entry whose path is no longer in the live directory
|
|
// listing (renamed/deleted slugs), so the cache doesn't grow unbounded.
|
|
export async function pruneCache(validPaths) {
|
|
if (!cacheAvailable) return;
|
|
const valid = new Set(validPaths);
|
|
let db;
|
|
try {
|
|
db = await openDb();
|
|
} catch {
|
|
return; // openDb() itself already flipped cacheAvailable
|
|
}
|
|
try {
|
|
const store = db.transaction(STORE_NAME, 'readwrite').objectStore(STORE_NAME);
|
|
await new Promise((resolve, reject) => {
|
|
const cursorRequest = store.openCursor();
|
|
cursorRequest.onsuccess = event => {
|
|
const cursor = event.target.result;
|
|
if (!cursor) { resolve(); return; }
|
|
if (!valid.has(cursor.value.path)) cursor.delete();
|
|
cursor.continue();
|
|
};
|
|
cursorRequest.onerror = () => reject(cursorRequest.error);
|
|
});
|
|
} catch {
|
|
// Same reasoning as runRequest() above — a failed prune pass doesn't
|
|
// mean IndexedDB itself is unavailable.
|
|
}
|
|
}
|