refactor: simplify boot cache hydration to a paint-only step

This commit is contained in:
Shivam Mishra
2026-06-10 12:37:11 +05:30
parent 9fb75656d2
commit f452b88e8d
6 changed files with 121 additions and 224 deletions
@@ -5,7 +5,7 @@
// with what the client looks up.
//
// `dispatchPath` is the full Vuex dispatch path for the revalidate action.
// `setMutation` is the full commit path used by hydrateStoresFromCache to seed
// `setMutation` is the full commit path used by paintStoresFromCache to seed
// Vuex from IDB. `clearMutation` (optional) is committed BEFORE `setMutation`
// for modules whose SET_* mutation merges-by-id instead of replacing — without
// it, rows deleted server-side between sessions would survive as phantoms.
@@ -1,70 +0,0 @@
/* global axios */
import { DataManager } from './DataManager';
import { cacheableModels } from './cacheableModels';
// Seed Vuex from IndexedDB before the dashboard renders, then reconcile
// against the server's authoritative cache keys in the background.
//
// CRITICAL ORDERING: capture local cache keys BEFORE fetching server keys.
// Each per-model `revalidate` action calls `validateCacheKey(newKey)`, which
// compares `newKey` against whatever is currently persisted in IDB. If we
// wrote the server keys first, that comparison would return true against the
// just-written key and stale IDB data would be served forever.
export default async function hydrateStoresFromCache(store, accountId) {
let dm;
try {
dm = new DataManager(accountId);
await dm.initDb();
} catch {
// IDB unsupported (e.g. Firefox private mode) — silent no-op. Components
// will fetch from the network normally via the cache-enabled API client.
return;
}
// 1. Snapshot local cache keys before any server interaction.
const localKeys = {};
await Promise.all(
cacheableModels.map(async model => {
localKeys[model.name] = await dm.getCacheKey(model.name);
})
);
// 2. Stale-while-revalidate paint: commit cached data into Vuex immediately.
await Promise.all(
cacheableModels.map(async model => {
const localData = await dm.get({ modelName: model.name });
if (localData.length === 0) return;
if (model.clearMutation) store.commit(model.clearMutation);
store.commit(model.setMutation, localData);
})
);
// 3. Fetch the server's authoritative cache keys once.
let serverKeys;
try {
const { data } = await axios.get(
`/api/v1/accounts/${accountId}/cache_keys`
);
serverKeys = data.cache_keys || {};
} catch {
return;
}
// 4. For each stale model, dispatch revalidate with the NEW server key.
// Do NOT await — the UI is already painted with stale data; refetches
// swap it in as they complete.
//
// Skip models with no local cache key — they've never been fetched on
// this device. The first downstream component that dispatches
// `<store>/get` will network-fetch via the cache-enabled API client
// and populate IDB. Boot stays cheap on cold devices.
cacheableModels.forEach(model => {
const serverKey = serverKeys[model.name];
if (serverKey === undefined) return;
const localKey = localKeys[model.name];
if (localKey === undefined) return;
if (serverKey === localKey) return;
store.dispatch(model.dispatchPath, { newKey: serverKey });
});
}
@@ -0,0 +1,32 @@
import { DataManager } from './DataManager';
import { cacheableModels } from './cacheableModels';
// Seed Vuex from IndexedDB before the dashboard renders so warm boots paint
// cached config instantly. This is purely local — zero network calls.
//
// Freshness is handled entirely by the account.cache_invalidated event:
// RoomChannel transmits the current cache-key map on every (re)subscribe, and
// the server broadcasts it on every change. dispatchCacheRevalidations diffs
// those keys against IDB and refetches mismatches — the client never pulls
// cache keys itself.
export default async function paintStoresFromCache(store, accountId) {
let dm;
try {
dm = new DataManager(accountId);
await dm.initDb();
} catch {
// IDB unsupported (e.g. Firefox private mode) — silent no-op. Components
// will fetch from the network normally via the cache-enabled API client.
return;
}
// Stale-while-revalidate paint: commit cached data into Vuex immediately.
await Promise.all(
cacheableModels.map(async model => {
const localData = await dm.get({ modelName: model.name });
if (localData.length === 0) return;
if (model.clearMutation) store.commit(model.clearMutation);
store.commit(model.setMutation, localData);
})
);
}