mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
Harden local store cache invalidation for storage mode switching
This commit is contained in:
99
index.js
99
index.js
@@ -3630,6 +3630,41 @@ function resolveSnapshotGraphStorePresentation(
|
|||||||
return buildIndexedDbStorePresentation();
|
return buildIndexedDbStorePresentation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildGraphLocalStoreSelectorKey(
|
||||||
|
presentation = buildIndexedDbStorePresentation(),
|
||||||
|
) {
|
||||||
|
const normalizedPresentation =
|
||||||
|
presentation && typeof presentation === "object"
|
||||||
|
? presentation
|
||||||
|
: buildIndexedDbStorePresentation();
|
||||||
|
const storagePrimary =
|
||||||
|
normalizedPresentation.storagePrimary === "opfs" ||
|
||||||
|
isGraphLocalStorageModeOpfs(normalizedPresentation.storageMode)
|
||||||
|
? "opfs"
|
||||||
|
: "indexeddb";
|
||||||
|
const storageMode =
|
||||||
|
storagePrimary === "opfs"
|
||||||
|
? normalizeGraphLocalStorageMode(
|
||||||
|
normalizedPresentation.storageMode,
|
||||||
|
BME_GRAPH_LOCAL_STORAGE_MODE_OPFS_SHADOW,
|
||||||
|
)
|
||||||
|
: BME_GRAPH_LOCAL_STORAGE_MODE_INDEXEDDB;
|
||||||
|
return `${storagePrimary}:${storageMode}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGraphLocalStorePresentationCompatible(left, right) {
|
||||||
|
return (
|
||||||
|
buildGraphLocalStoreSelectorKey(left) ===
|
||||||
|
buildGraphLocalStoreSelectorKey(right)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCachedIndexedDbSnapshotCompatible(snapshot = null, expectedStore = null) {
|
||||||
|
if (!expectedStore || typeof expectedStore !== "object") return true;
|
||||||
|
const snapshotStore = resolveSnapshotGraphStorePresentation(snapshot, expectedStore);
|
||||||
|
return isGraphLocalStorePresentationCompatible(snapshotStore, expectedStore);
|
||||||
|
}
|
||||||
|
|
||||||
async function getGraphLocalStoreCapability(forceRefresh = false) {
|
async function getGraphLocalStoreCapability(forceRefresh = false) {
|
||||||
if (!forceRefresh && bmeLocalStoreCapabilitySnapshot.checked) {
|
if (!forceRefresh && bmeLocalStoreCapabilitySnapshot.checked) {
|
||||||
return bmeLocalStoreCapabilitySnapshot;
|
return bmeLocalStoreCapabilitySnapshot;
|
||||||
@@ -3787,6 +3822,10 @@ function ensureBmeChatManager() {
|
|||||||
bmeChatManager = new BmeChatManager({
|
bmeChatManager = new BmeChatManager({
|
||||||
databaseFactory: async (chatId) =>
|
databaseFactory: async (chatId) =>
|
||||||
await createPreferredGraphLocalStore(chatId),
|
await createPreferredGraphLocalStore(chatId),
|
||||||
|
selectorKeyResolver: async () =>
|
||||||
|
buildGraphLocalStoreSelectorKey(
|
||||||
|
await resolvePreferredGraphLocalStorePresentation(),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return bmeChatManager;
|
return bmeChatManager;
|
||||||
@@ -3916,19 +3955,33 @@ function isIndexedDbSnapshotMeaningful(snapshot = null) {
|
|||||||
function cacheIndexedDbSnapshot(chatId, snapshot = null) {
|
function cacheIndexedDbSnapshot(chatId, snapshot = null) {
|
||||||
const normalizedChatId = normalizeChatIdCandidate(chatId);
|
const normalizedChatId = normalizeChatIdCandidate(chatId);
|
||||||
if (!normalizedChatId || !snapshot || typeof snapshot !== "object") return;
|
if (!normalizedChatId || !snapshot || typeof snapshot !== "object") return;
|
||||||
|
const snapshotStore = resolveSnapshotGraphStorePresentation(snapshot);
|
||||||
bmeIndexedDbSnapshotCacheByChatId.set(normalizedChatId, {
|
bmeIndexedDbSnapshotCacheByChatId.set(normalizedChatId, {
|
||||||
chatId: normalizedChatId,
|
chatId: normalizedChatId,
|
||||||
revision: normalizeIndexedDbRevision(snapshot?.meta?.revision),
|
revision: normalizeIndexedDbRevision(snapshot?.meta?.revision),
|
||||||
|
selectorKey: buildGraphLocalStoreSelectorKey(snapshotStore),
|
||||||
snapshot,
|
snapshot,
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCachedIndexedDbSnapshot(chatId) {
|
function readCachedIndexedDbSnapshot(chatId, expectedStore = null) {
|
||||||
const normalizedChatId = normalizeChatIdCandidate(chatId);
|
const normalizedChatId = normalizeChatIdCandidate(chatId);
|
||||||
if (!normalizedChatId) return null;
|
if (!normalizedChatId) return null;
|
||||||
const cacheEntry = bmeIndexedDbSnapshotCacheByChatId.get(normalizedChatId);
|
const cacheEntry = bmeIndexedDbSnapshotCacheByChatId.get(normalizedChatId);
|
||||||
if (!cacheEntry?.snapshot) return null;
|
if (!cacheEntry?.snapshot) return null;
|
||||||
|
if (expectedStore && typeof expectedStore === "object") {
|
||||||
|
const expectedSelectorKey = buildGraphLocalStoreSelectorKey(expectedStore);
|
||||||
|
if (cacheEntry.selectorKey && cacheEntry.selectorKey !== expectedSelectorKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!cacheEntry.selectorKey &&
|
||||||
|
!isCachedIndexedDbSnapshotCompatible(cacheEntry.snapshot, expectedStore)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
return cacheEntry.snapshot;
|
return cacheEntry.snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7400,11 +7453,15 @@ function syncGraphLoadFromLiveContext(options = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedSnapshot = readCachedIndexedDbSnapshot(chatId);
|
const cachedPreferredLocalStore = getPreferredGraphLocalStorePresentationSync();
|
||||||
|
const cachedSnapshot = readCachedIndexedDbSnapshot(
|
||||||
|
chatId,
|
||||||
|
cachedPreferredLocalStore,
|
||||||
|
);
|
||||||
if (isIndexedDbSnapshotMeaningful(cachedSnapshot)) {
|
if (isIndexedDbSnapshotMeaningful(cachedSnapshot)) {
|
||||||
const cachedStore = resolveSnapshotGraphStorePresentation(
|
const cachedStore = resolveSnapshotGraphStorePresentation(
|
||||||
cachedSnapshot,
|
cachedSnapshot,
|
||||||
getPreferredGraphLocalStorePresentationSync(),
|
cachedPreferredLocalStore,
|
||||||
);
|
);
|
||||||
const result = applyIndexedDbSnapshotToRuntime(chatId, cachedSnapshot, {
|
const result = applyIndexedDbSnapshotToRuntime(chatId, cachedSnapshot, {
|
||||||
source: `${source}:indexeddb-cache`,
|
source: `${source}:indexeddb-cache`,
|
||||||
@@ -8232,6 +8289,9 @@ function updateModuleSettings(patch = {}) {
|
|||||||
const previousCloudStorageMode = String(
|
const previousCloudStorageMode = String(
|
||||||
settings.cloudStorageMode || "automatic",
|
settings.cloudStorageMode || "automatic",
|
||||||
);
|
);
|
||||||
|
const previousGraphLocalStorageMode = getRequestedGraphLocalStorageMode(
|
||||||
|
settings,
|
||||||
|
);
|
||||||
Object.assign(settings, patch);
|
Object.assign(settings, patch);
|
||||||
extension_settings[MODULE_NAME] = settings;
|
extension_settings[MODULE_NAME] = settings;
|
||||||
globalThis.__stBmeDebugLoggingEnabled = Boolean(
|
globalThis.__stBmeDebugLoggingEnabled = Boolean(
|
||||||
@@ -8298,6 +8358,21 @@ function updateModuleSettings(patch = {}) {
|
|||||||
refreshVisibleStageNotices();
|
refreshVisibleStageNotices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentGraphLocalStorageMode = getRequestedGraphLocalStorageMode(
|
||||||
|
settings,
|
||||||
|
);
|
||||||
|
if (previousGraphLocalStorageMode !== currentGraphLocalStorageMode) {
|
||||||
|
clearAllCachedIndexedDbSnapshots();
|
||||||
|
scheduleBmeIndexedDbTask(async () => {
|
||||||
|
if (bmeChatManager && typeof bmeChatManager.closeAll === "function") {
|
||||||
|
await bmeChatManager.closeAll();
|
||||||
|
}
|
||||||
|
await syncBmeChatManagerWithCurrentChat(
|
||||||
|
"settings:graph-local-storage-mode-changed",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const currentCloudStorageMode = String(
|
const currentCloudStorageMode = String(
|
||||||
settings.cloudStorageMode || "automatic",
|
settings.cloudStorageMode || "automatic",
|
||||||
);
|
);
|
||||||
@@ -8468,17 +8543,29 @@ function loadGraphFromChat(options = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedSnapshot = readCachedIndexedDbSnapshot(chatId);
|
const preferredLocalStore = getPreferredGraphLocalStorePresentationSync();
|
||||||
|
const cachedSnapshot = readCachedIndexedDbSnapshot(
|
||||||
|
chatId,
|
||||||
|
preferredLocalStore,
|
||||||
|
);
|
||||||
if (isIndexedDbSnapshotMeaningful(cachedSnapshot)) {
|
if (isIndexedDbSnapshotMeaningful(cachedSnapshot)) {
|
||||||
|
const cachedStore = resolveSnapshotGraphStorePresentation(
|
||||||
|
cachedSnapshot,
|
||||||
|
preferredLocalStore,
|
||||||
|
);
|
||||||
const cachedResult = applyIndexedDbSnapshotToRuntime(
|
const cachedResult = applyIndexedDbSnapshotToRuntime(
|
||||||
chatId,
|
chatId,
|
||||||
cachedSnapshot,
|
cachedSnapshot,
|
||||||
{
|
{
|
||||||
source: `${source}:indexeddb-cache`,
|
source: `${source}:indexeddb-cache`,
|
||||||
attemptIndex,
|
attemptIndex,
|
||||||
|
storagePrimary: cachedStore.storagePrimary,
|
||||||
|
storageMode: cachedStore.storageMode,
|
||||||
|
statusLabel: cachedStore.statusLabel,
|
||||||
|
reasonPrefix: cachedStore.reasonPrefix,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (cachedResult?.reason === "indexeddb-stale-runtime") {
|
if (cachedResult?.reason === `${cachedStore.reasonPrefix}-stale-runtime`) {
|
||||||
clearPendingGraphLoadRetry();
|
clearPendingGraphLoadRetry();
|
||||||
refreshPanelLiveState();
|
refreshPanelLiveState();
|
||||||
return {
|
return {
|
||||||
@@ -8818,7 +8905,7 @@ async function saveGraphToIndexedDb(
|
|||||||
localStore = resolveDbGraphStorePresentation(db);
|
localStore = resolveDbGraphStorePresentation(db);
|
||||||
const currentIdentity = resolveCurrentChatIdentity(getContext());
|
const currentIdentity = resolveCurrentChatIdentity(getContext());
|
||||||
const baseSnapshot =
|
const baseSnapshot =
|
||||||
readCachedIndexedDbSnapshot(normalizedChatId) ||
|
readCachedIndexedDbSnapshot(normalizedChatId, localStore) ||
|
||||||
(await db.exportSnapshot());
|
(await db.exportSnapshot());
|
||||||
const requestedRevision = resolvePersistRevisionFloor(revision, graph);
|
const requestedRevision = resolvePersistRevisionFloor(revision, graph);
|
||||||
const snapshot = buildSnapshotFromGraph(graph, {
|
const snapshot = buildSnapshotFromGraph(graph, {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const chatIdsForCleanup = new Set([
|
|||||||
"chat-b",
|
"chat-b",
|
||||||
"chat-manager-a",
|
"chat-manager-a",
|
||||||
"chat-manager-b",
|
"chat-manager-b",
|
||||||
|
"chat-manager-selector",
|
||||||
"chat-replace-reset",
|
"chat-replace-reset",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -418,6 +419,54 @@ async function testChatIsolationAndManager() {
|
|||||||
assert.equal(manager.getCurrentChatId(), "");
|
assert.equal(manager.getCurrentChatId(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function testManagerRecreatesDbWhenSelectorKeyChanges() {
|
||||||
|
let selectorKey = "indexeddb:indexeddb";
|
||||||
|
let instanceCounter = 0;
|
||||||
|
const closeLog = [];
|
||||||
|
const manager = new BmeChatManager({
|
||||||
|
selectorKeyResolver: async () => selectorKey,
|
||||||
|
databaseFactory: async (chatId) => {
|
||||||
|
instanceCounter += 1;
|
||||||
|
const instanceId = instanceCounter;
|
||||||
|
return {
|
||||||
|
chatId,
|
||||||
|
instanceId,
|
||||||
|
openCount: 0,
|
||||||
|
closed: false,
|
||||||
|
async open() {
|
||||||
|
this.openCount += 1;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
async close() {
|
||||||
|
this.closed = true;
|
||||||
|
closeLog.push(instanceId);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const dbA = await manager.getCurrentDb("chat-manager-selector");
|
||||||
|
assert.equal(dbA.instanceId, 1);
|
||||||
|
assert.equal(dbA.openCount, 1);
|
||||||
|
|
||||||
|
const reopenedSameSelector = await manager.getCurrentDb("chat-manager-selector");
|
||||||
|
assert.equal(reopenedSameSelector, dbA);
|
||||||
|
assert.equal(dbA.openCount, 2);
|
||||||
|
assert.deepEqual(closeLog, []);
|
||||||
|
|
||||||
|
selectorKey = "opfs:opfs-shadow";
|
||||||
|
const dbB = await manager.getCurrentDb("chat-manager-selector");
|
||||||
|
assert.notEqual(dbB, dbA);
|
||||||
|
assert.equal(dbB.instanceId, 2);
|
||||||
|
assert.equal(dbB.openCount, 1);
|
||||||
|
assert.equal(dbA.closed, true);
|
||||||
|
assert.deepEqual(closeLog, [1]);
|
||||||
|
|
||||||
|
await manager.closeAll();
|
||||||
|
assert.equal(dbB.closed, true);
|
||||||
|
assert.deepEqual(closeLog, [1, 2]);
|
||||||
|
}
|
||||||
|
|
||||||
async function testGraphSnapshotConverters() {
|
async function testGraphSnapshotConverters() {
|
||||||
const graph = createEmptyGraph();
|
const graph = createEmptyGraph();
|
||||||
graph.historyState.chatId = "chat-a";
|
graph.historyState.chatId = "chat-a";
|
||||||
@@ -548,6 +597,7 @@ async function main() {
|
|||||||
await testRevisionMonotonicity();
|
await testRevisionMonotonicity();
|
||||||
await testTombstonePrune();
|
await testTombstonePrune();
|
||||||
await testChatIsolationAndManager();
|
await testChatIsolationAndManager();
|
||||||
|
await testManagerRecreatesDbWhenSelectorKeyChanges();
|
||||||
await testGraphSnapshotConverters();
|
await testGraphSnapshotConverters();
|
||||||
|
|
||||||
await cleanupDatabases();
|
await cleanupDatabases();
|
||||||
|
|||||||
Reference in New Issue
Block a user