mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
2584 lines
111 KiB
JavaScript
2584 lines
111 KiB
JavaScript
// Extracted graph persistence IndexedDB I/O controllers.
|
|
// Dependencies are supplied by index.js/test harnesses through runtime.
|
|
|
|
function createGraphPersistenceStateProxy(runtime = {}) {
|
|
return new Proxy({}, {
|
|
get(_target, prop) {
|
|
return runtime.getGraphPersistenceState?.()?.[prop];
|
|
},
|
|
set(_target, prop, value) {
|
|
const state = runtime.getGraphPersistenceState?.();
|
|
if (state && typeof state === "object") {
|
|
state[prop] = value;
|
|
} else {
|
|
runtime.setGraphPersistenceState?.({ [prop]: value });
|
|
}
|
|
return true;
|
|
},
|
|
has(_target, prop) {
|
|
return prop in (runtime.getGraphPersistenceState?.() || {});
|
|
},
|
|
});
|
|
}
|
|
|
|
function createNativeHydrateInstallPromiseRef(runtime = {}) {
|
|
return {
|
|
get value() {
|
|
return typeof runtime.getNativeHydrateInstallPromise === "function"
|
|
? runtime.getNativeHydrateInstallPromise()
|
|
: undefined;
|
|
},
|
|
set value(nextValue) {
|
|
if (typeof runtime.setNativeHydrateInstallPromise === "function") {
|
|
runtime.setNativeHydrateInstallPromise(nextValue);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
function createNativePersistDeltaInstallPromiseRef(runtime = {}) {
|
|
return {
|
|
get value() {
|
|
return typeof runtime.getNativePersistDeltaInstallPromise === "function"
|
|
? runtime.getNativePersistDeltaInstallPromise()
|
|
: undefined;
|
|
},
|
|
set value(nextValue) {
|
|
if (typeof runtime.setNativePersistDeltaInstallPromise === "function") {
|
|
runtime.setNativePersistDeltaInstallPromise(nextValue);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
function importNativeCore(runtime = {}) {
|
|
return typeof runtime.importNativeCore === "function"
|
|
? runtime.importNativeCore()
|
|
: import("../vendor/wasm/stbme_core.js");
|
|
}
|
|
|
|
export async function loadGraphFromIndexedDbImpl(runtime,
|
|
chatId,
|
|
{
|
|
source = "indexeddb-probe",
|
|
attemptIndex = 0,
|
|
allowOverride = false,
|
|
applyEmptyState = false,
|
|
} = {},
|
|
) {
|
|
const graphPersistenceState = createGraphPersistenceStateProxy(runtime);
|
|
const currentGraph = runtime.getCurrentGraph?.() || null;
|
|
const nativeHydrateInstallPromiseRef = createNativeHydrateInstallPromiseRef(runtime);
|
|
const nativePersistDeltaInstallPromiseRef = createNativePersistDeltaInstallPromiseRef(runtime);
|
|
const bmeIndexedDbLatestQueuedRevisionByChatId = runtime.bmeIndexedDbLatestQueuedRevisionByChatId;
|
|
const bmeIndexedDbWriteInFlightByChatId = runtime.bmeIndexedDbWriteInFlightByChatId;
|
|
const updateGraphPersistenceState = runtime.updateGraphPersistenceState || ((patch = {}) => runtime.setGraphPersistenceState?.({ ...(runtime.getGraphPersistenceState?.() || {}), ...(patch || {}) }));
|
|
const AUTHORITY_GRAPH_STORE_KIND = runtime.AUTHORITY_GRAPH_STORE_KIND;
|
|
const BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET = runtime.BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET;
|
|
const GRAPH_LOAD_STATES = runtime.GRAPH_LOAD_STATES;
|
|
const applyAcceptedPendingPersistState = runtime.applyAcceptedPendingPersistState;
|
|
const applyGraphLoadState = runtime.applyGraphLoadState;
|
|
const applyIndexedDbEmptyToRuntime = runtime.applyIndexedDbEmptyToRuntime;
|
|
const applyIndexedDbSnapshotToRuntime = runtime.applyIndexedDbSnapshotToRuntime;
|
|
const applyPersistDeltaToSnapshot = runtime.applyPersistDeltaToSnapshot;
|
|
const applyShadowSnapshotToRuntime = runtime.applyShadowSnapshotToRuntime;
|
|
const areChatIdsEquivalentForResolvedIdentity = runtime.areChatIdsEquivalentForResolvedIdentity;
|
|
const buildBmeSyncRuntimeOptions = runtime.buildBmeSyncRuntimeOptions;
|
|
const buildGraphLocalStoreSelectorKey = runtime.buildGraphLocalStoreSelectorKey;
|
|
const buildGraphPersistResult = runtime.buildGraphPersistResult;
|
|
const buildPersistDelta = runtime.buildPersistDelta;
|
|
const buildPersistDeltaFromGraphDirtyState = runtime.buildPersistDeltaFromGraphDirtyState;
|
|
const buildPersistObservabilitySummary = runtime.buildPersistObservabilitySummary;
|
|
const buildPersistenceEnvironment = runtime.buildPersistenceEnvironment;
|
|
const buildSnapshotFromGraph = runtime.buildSnapshotFromGraph;
|
|
const cacheIndexedDbSnapshot = runtime.cacheIndexedDbSnapshot;
|
|
const canPersistGraphToMetadataFallback = runtime.canPersistGraphToMetadataFallback;
|
|
const clearPendingGraphPersistRetry = runtime.clearPendingGraphPersistRetry;
|
|
const cloneGraphForPersistence = runtime.cloneGraphForPersistence;
|
|
const cloneRuntimeDebugValue = runtime.cloneRuntimeDebugValue;
|
|
const createShadowComparisonGraph = runtime.createShadowComparisonGraph;
|
|
const detectIndexedDbSnapshotCommitMarkerMismatch = runtime.detectIndexedDbSnapshotCommitMarkerMismatch;
|
|
const detectStaleIndexedDbSnapshotAgainstRuntime = runtime.detectStaleIndexedDbSnapshotAgainstRuntime;
|
|
const ensureBmeChatManager = runtime.ensureBmeChatManager;
|
|
const ensureCurrentGraphRuntimeState = runtime.ensureCurrentGraphRuntimeState;
|
|
const evaluateNativeHydrateGate = runtime.evaluateNativeHydrateGate;
|
|
const evaluatePersistNativeDeltaGate = runtime.evaluatePersistNativeDeltaGate;
|
|
const getChatMetadataIntegrity = runtime.getChatMetadataIntegrity;
|
|
const getContext = runtime.getContext;
|
|
const getCurrentChatId = runtime.getCurrentChatId;
|
|
const getGraphPersistedRevision = runtime.getGraphPersistedRevision;
|
|
const getPreferredGraphLocalStorePresentationSync = runtime.getPreferredGraphLocalStorePresentationSync;
|
|
const getRequestedGraphLocalStorageMode = runtime.getRequestedGraphLocalStorageMode;
|
|
const getSettings = runtime.getSettings;
|
|
const hasMeaningfulRuntimeGraphForChat = runtime.hasMeaningfulRuntimeGraphForChat;
|
|
const isAuthorityGraphStorePresentation = runtime.isAuthorityGraphStorePresentation;
|
|
const isGraphLocalStorageModeOpfs = runtime.isGraphLocalStorageModeOpfs;
|
|
const isIndexedDbSnapshotMeaningful = runtime.isIndexedDbSnapshotMeaningful;
|
|
const isRestoreLockActive = runtime.isRestoreLockActive;
|
|
const maybeCaptureGraphShadowSnapshot = runtime.maybeCaptureGraphShadowSnapshot;
|
|
const maybeClearAcceptedPendingPersistState = runtime.maybeClearAcceptedPendingPersistState;
|
|
const maybeImportLegacyIndexedDbSnapshotToLocalStore = runtime.maybeImportLegacyIndexedDbSnapshotToLocalStore;
|
|
const maybeImportLegacyOpfsSnapshotToLocalStore = runtime.maybeImportLegacyOpfsSnapshotToLocalStore;
|
|
const maybeMigrateLegacyGraphToIndexedDb = runtime.maybeMigrateLegacyGraphToIndexedDb;
|
|
const maybeRecoverIndexedDbGraphFromStableIdentity = runtime.maybeRecoverIndexedDbGraphFromStableIdentity;
|
|
const maybeResolveOrphanAcceptedCommitMarker = runtime.maybeResolveOrphanAcceptedCommitMarker;
|
|
const maybeResumePendingAutoExtraction = runtime.maybeResumePendingAutoExtraction;
|
|
const normalizeChatIdCandidate = runtime.normalizeChatIdCandidate;
|
|
const normalizeGraphRuntimeState = runtime.normalizeGraphRuntimeState;
|
|
const normalizeIndexedDbRevision = runtime.normalizeIndexedDbRevision;
|
|
const normalizeLoadDiagnosticsMs = runtime.normalizeLoadDiagnosticsMs;
|
|
const normalizePersistDeltaDiagnosticsMs = runtime.normalizePersistDeltaDiagnosticsMs;
|
|
const persistGraphToChatMetadata = runtime.persistGraphToChatMetadata;
|
|
const persistGraphToConfiguredDurableTier = runtime.persistGraphToConfiguredDurableTier;
|
|
const pruneGraphPersistDirtyState = runtime.pruneGraphPersistDirtyState;
|
|
const queueGraphPersist = runtime.queueGraphPersist;
|
|
const queueRuntimeGraphLocalStoreRepair = runtime.queueRuntimeGraphLocalStoreRepair;
|
|
const readCachedIndexedDbSnapshot = runtime.readCachedIndexedDbSnapshot;
|
|
const readLoadDiagnosticsNow = runtime.readLoadDiagnosticsNow;
|
|
const readLocalStoreDiagnosticsSync = runtime.readLocalStoreDiagnosticsSync;
|
|
const readPersistDeltaDiagnosticsNow = runtime.readPersistDeltaDiagnosticsNow;
|
|
const recordLocalPersistEarlyFailure = runtime.recordLocalPersistEarlyFailure;
|
|
const recordPersistMismatchDiagnostic = runtime.recordPersistMismatchDiagnostic;
|
|
const refreshCurrentChatLocalStoreBinding = runtime.refreshCurrentChatLocalStoreBinding;
|
|
const rememberResolvedGraphIdentityAlias = runtime.rememberResolvedGraphIdentityAlias;
|
|
const resolveCompatibleGraphShadowSnapshot = runtime.resolveCompatibleGraphShadowSnapshot;
|
|
const resolveCurrentChatIdentity = runtime.resolveCurrentChatIdentity;
|
|
const resolveDbGraphStorePresentation = runtime.resolveDbGraphStorePresentation;
|
|
const resolveLocalStoreTierFromPresentation = runtime.resolveLocalStoreTierFromPresentation;
|
|
const resolvePendingPersistGraphSource = runtime.resolvePendingPersistGraphSource;
|
|
const resolvePendingPersistLastProcessedAssistantFloor = runtime.resolvePendingPersistLastProcessedAssistantFloor;
|
|
const resolvePersistRevisionFloor = runtime.resolvePersistRevisionFloor;
|
|
const resolveSnapshotGraphStorePresentation = runtime.resolveSnapshotGraphStorePresentation;
|
|
const schedulePendingGraphPersistRetry = runtime.schedulePendingGraphPersistRetry;
|
|
const scheduleUpload = runtime.scheduleUpload;
|
|
const shouldPreferShadowSnapshotOverOfficial = runtime.shouldPreferShadowSnapshotOverOfficial;
|
|
const stampGraphPersistenceMeta = runtime.stampGraphPersistenceMeta;
|
|
const syncCommitMarkerToPersistenceState = runtime.syncCommitMarkerToPersistenceState;
|
|
const updateLoadDiagnostics = runtime.updateLoadDiagnostics;
|
|
const updatePersistDeltaDiagnostics = runtime.updatePersistDeltaDiagnostics;
|
|
const console = runtime.console || globalThis.console;
|
|
|
|
const normalizedChatId = normalizeChatIdCandidate(chatId);
|
|
const commitMarker = syncCommitMarkerToPersistenceState(getContext());
|
|
const loadStartedAt = readLoadDiagnosticsNow();
|
|
const recordLoadDiagnostics = (patch = {}) =>
|
|
updateLoadDiagnostics({
|
|
stage: "load-indexeddb",
|
|
source: String(source || "indexeddb-probe"),
|
|
chatId: normalizedChatId || "",
|
|
attemptIndex: Number.isFinite(Number(attemptIndex))
|
|
? Math.max(0, Math.floor(Number(attemptIndex)))
|
|
: 0,
|
|
...cloneRuntimeDebugValue(patch, {}),
|
|
totalMs: normalizeLoadDiagnosticsMs(readLoadDiagnosticsNow() - loadStartedAt),
|
|
});
|
|
let exportSnapshotMs = 0;
|
|
let exportProbeMs = 0;
|
|
let preApplyMs = 0;
|
|
let exportSnapshotSource = "";
|
|
const currentSettings = getSettings();
|
|
if (!normalizedChatId) {
|
|
const result = {
|
|
success: false,
|
|
loaded: false,
|
|
reason: "indexeddb-missing-chat-id",
|
|
chatId: "",
|
|
attemptIndex,
|
|
};
|
|
recordLoadDiagnostics({
|
|
success: false,
|
|
loaded: false,
|
|
reason: result.reason,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
let localStore = getPreferredGraphLocalStorePresentationSync();
|
|
try {
|
|
const manager = ensureBmeChatManager();
|
|
if (!manager) {
|
|
const result = {
|
|
success: false,
|
|
loaded: false,
|
|
reason: "indexeddb-manager-unavailable",
|
|
chatId: normalizedChatId,
|
|
attemptIndex,
|
|
};
|
|
recordLoadDiagnostics({
|
|
success: false,
|
|
loaded: false,
|
|
reason: result.reason,
|
|
storagePrimary: localStore.storagePrimary,
|
|
storageMode: localStore.storageMode,
|
|
});
|
|
return result;
|
|
}
|
|
const db = await manager.getCurrentDb(normalizedChatId);
|
|
localStore = resolveDbGraphStorePresentation(db);
|
|
|
|
const identityRecoveryResult =
|
|
await maybeRecoverIndexedDbGraphFromStableIdentity(
|
|
normalizedChatId,
|
|
getContext(),
|
|
{
|
|
source,
|
|
db,
|
|
},
|
|
);
|
|
|
|
if (identityRecoveryResult?.migrated) {
|
|
const recoveredStore = resolveSnapshotGraphStorePresentation(
|
|
identityRecoveryResult?.snapshot,
|
|
localStore,
|
|
);
|
|
const recoveredAuthorityStore =
|
|
isAuthorityGraphStorePresentation(recoveredStore);
|
|
const recoveredRevision = normalizeIndexedDbRevision(
|
|
identityRecoveryResult?.snapshot?.meta?.revision,
|
|
);
|
|
updateGraphPersistenceState({
|
|
storagePrimary: recoveredStore.storagePrimary,
|
|
storageMode: recoveredStore.storageMode,
|
|
resolvedLocalStore: buildGraphLocalStoreSelectorKey(recoveredStore),
|
|
localStoreFormatVersion:
|
|
recoveredStore.storagePrimary === "opfs" ? 2 : 1,
|
|
localStoreMigrationState: "completed",
|
|
authorityMigrationState: recoveredAuthorityStore ? "completed" : graphPersistenceState.authorityMigrationState,
|
|
authorityMigrationSource: recoveredAuthorityStore
|
|
? String(identityRecoveryResult?.source || "identity-recovery")
|
|
: graphPersistenceState.authorityMigrationSource,
|
|
authorityMigrationRevision: recoveredAuthorityStore
|
|
? recoveredRevision
|
|
: graphPersistenceState.authorityMigrationRevision,
|
|
authorityMigrationLastError: recoveredAuthorityStore
|
|
? ""
|
|
: graphPersistenceState.authorityMigrationLastError,
|
|
lastAuthorityMigrationResult: recoveredAuthorityStore
|
|
? cloneRuntimeDebugValue(identityRecoveryResult, null)
|
|
: graphPersistenceState.lastAuthorityMigrationResult,
|
|
indexedDbRevision: recoveredRevision,
|
|
indexedDbLastError: "",
|
|
lastSyncError: "",
|
|
dualWriteLastResult: {
|
|
action: "identity-recovery",
|
|
source: String(identityRecoveryResult?.source || recoveredStore.reasonPrefix),
|
|
success: true,
|
|
chatId: normalizedChatId,
|
|
legacyChatId: String(identityRecoveryResult?.legacyChatId || ""),
|
|
revision: recoveredRevision,
|
|
reason: String(
|
|
identityRecoveryResult?.reason || "identity-recovery",
|
|
),
|
|
at: Date.now(),
|
|
syncResult: cloneRuntimeDebugValue(
|
|
identityRecoveryResult?.syncResult,
|
|
null,
|
|
),
|
|
},
|
|
});
|
|
}
|
|
|
|
const opfsMigrationResult = identityRecoveryResult?.migrated
|
|
? {
|
|
migrated: false,
|
|
reason: "identity-recovery-already-applied",
|
|
chatId: normalizedChatId,
|
|
}
|
|
: await maybeImportLegacyOpfsSnapshotToLocalStore(
|
|
normalizedChatId,
|
|
db,
|
|
{
|
|
source,
|
|
},
|
|
);
|
|
|
|
const localStoreMigrationResult =
|
|
identityRecoveryResult?.migrated || opfsMigrationResult?.migrated
|
|
? {
|
|
migrated: false,
|
|
reason: opfsMigrationResult?.migrated
|
|
? "opfs-migration-already-applied"
|
|
: "identity-recovery-already-applied",
|
|
chatId: normalizedChatId,
|
|
}
|
|
: await maybeImportLegacyIndexedDbSnapshotToLocalStore(
|
|
normalizedChatId,
|
|
db,
|
|
{
|
|
source,
|
|
},
|
|
);
|
|
|
|
const migrationResult =
|
|
identityRecoveryResult?.migrated ||
|
|
opfsMigrationResult?.migrated ||
|
|
localStoreMigrationResult?.migrated ||
|
|
localStoreMigrationResult?.reason === "migration-local-store-failed"
|
|
? localStoreMigrationResult
|
|
: await maybeMigrateLegacyGraphToIndexedDb(
|
|
normalizedChatId,
|
|
getContext(),
|
|
{
|
|
source,
|
|
db,
|
|
},
|
|
);
|
|
|
|
if (migrationResult?.migrated) {
|
|
const migratedStore = resolveSnapshotGraphStorePresentation(
|
|
migrationResult?.snapshot,
|
|
localStore,
|
|
);
|
|
const migratedAuthorityStore =
|
|
isAuthorityGraphStorePresentation(migratedStore);
|
|
const migratedRevision = normalizeIndexedDbRevision(
|
|
migrationResult?.snapshot?.meta?.revision ||
|
|
migrationResult?.migrationResult?.revision,
|
|
);
|
|
updateGraphPersistenceState({
|
|
storagePrimary: migratedStore.storagePrimary,
|
|
storageMode: migratedStore.storageMode,
|
|
resolvedLocalStore: buildGraphLocalStoreSelectorKey(migratedStore),
|
|
localStoreFormatVersion:
|
|
migratedStore.storagePrimary === "opfs" ? 2 : 1,
|
|
localStoreMigrationState: "completed",
|
|
authorityMigrationState: migratedAuthorityStore ? "completed" : graphPersistenceState.authorityMigrationState,
|
|
authorityMigrationSource: migratedAuthorityStore
|
|
? String(migrationResult?.source || migrationResult?.reason || "")
|
|
: graphPersistenceState.authorityMigrationSource,
|
|
authorityMigrationRevision: migratedAuthorityStore
|
|
? migratedRevision
|
|
: graphPersistenceState.authorityMigrationRevision,
|
|
authorityMigrationLastError: migratedAuthorityStore
|
|
? ""
|
|
: graphPersistenceState.authorityMigrationLastError,
|
|
lastAuthorityMigrationResult: migratedAuthorityStore
|
|
? cloneRuntimeDebugValue(migrationResult, null)
|
|
: graphPersistenceState.lastAuthorityMigrationResult,
|
|
indexedDbRevision: migratedRevision,
|
|
indexedDbLastError: "",
|
|
lastSyncError: "",
|
|
dualWriteLastResult: {
|
|
action: "migration",
|
|
source: String(migrationResult?.source || "chat_metadata"),
|
|
success: true,
|
|
chatId: normalizedChatId,
|
|
revision: migratedRevision,
|
|
reason: migrationResult?.reason || "migration-completed",
|
|
at: Date.now(),
|
|
syncResult: cloneRuntimeDebugValue(migrationResult?.syncResult, null),
|
|
},
|
|
});
|
|
} else if (
|
|
migrationResult?.reason === "migration-failed" ||
|
|
migrationResult?.reason === "migration-local-store-failed"
|
|
) {
|
|
updateGraphPersistenceState({
|
|
indexedDbLastError: String(
|
|
migrationResult?.error || "migration-failed",
|
|
),
|
|
localStoreMigrationState: "failed",
|
|
authorityMigrationState:
|
|
localStore.storagePrimary === AUTHORITY_GRAPH_STORE_KIND
|
|
? "failed"
|
|
: graphPersistenceState.authorityMigrationState,
|
|
authorityMigrationLastError:
|
|
localStore.storagePrimary === AUTHORITY_GRAPH_STORE_KIND
|
|
? String(migrationResult?.error || migrationResult?.reason || "migration-failed")
|
|
: graphPersistenceState.authorityMigrationLastError,
|
|
lastAuthorityMigrationResult:
|
|
localStore.storagePrimary === AUTHORITY_GRAPH_STORE_KIND
|
|
? cloneRuntimeDebugValue(migrationResult, null)
|
|
: graphPersistenceState.lastAuthorityMigrationResult,
|
|
dualWriteLastResult: {
|
|
action: "migration",
|
|
source: "chat_metadata",
|
|
success: false,
|
|
error: String(migrationResult?.error || "migration-failed"),
|
|
at: Date.now(),
|
|
},
|
|
});
|
|
}
|
|
let snapshot = null;
|
|
let inspectionSnapshot = null;
|
|
if (identityRecoveryResult?.snapshot) {
|
|
snapshot = identityRecoveryResult.snapshot;
|
|
inspectionSnapshot = snapshot;
|
|
exportSnapshotSource = "identity-recovery";
|
|
} else if (localStoreMigrationResult?.snapshot) {
|
|
snapshot = localStoreMigrationResult.snapshot;
|
|
inspectionSnapshot = snapshot;
|
|
exportSnapshotSource = "local-store-migration";
|
|
} else if (migrationResult?.snapshot) {
|
|
snapshot = migrationResult.snapshot;
|
|
inspectionSnapshot = snapshot;
|
|
exportSnapshotSource = "legacy-migration";
|
|
} else {
|
|
if (typeof db.exportSnapshotProbe === "function") {
|
|
const probeStartedAt = readLoadDiagnosticsNow();
|
|
inspectionSnapshot = await db.exportSnapshotProbe({ includeTombstones: false });
|
|
exportProbeMs = readLoadDiagnosticsNow() - probeStartedAt;
|
|
exportSnapshotSource = "indexeddb-probe";
|
|
}
|
|
if (!inspectionSnapshot) {
|
|
const exportStartedAt = readLoadDiagnosticsNow();
|
|
snapshot = await db.exportSnapshot({ includeTombstones: false });
|
|
exportSnapshotMs = readLoadDiagnosticsNow() - exportStartedAt;
|
|
inspectionSnapshot = snapshot;
|
|
exportSnapshotSource = "indexeddb-export";
|
|
}
|
|
}
|
|
const shadowSnapshot = resolveCompatibleGraphShadowSnapshot(
|
|
resolveCurrentChatIdentity(getContext()),
|
|
);
|
|
|
|
const snapshotStore = resolveSnapshotGraphStorePresentation(
|
|
inspectionSnapshot || snapshot,
|
|
localStore,
|
|
);
|
|
|
|
const commitMarkerMismatch = detectIndexedDbSnapshotCommitMarkerMismatch(
|
|
inspectionSnapshot,
|
|
commitMarker,
|
|
);
|
|
let commitMarkerDiagnostic = null;
|
|
if (!isIndexedDbSnapshotMeaningful(inspectionSnapshot)) {
|
|
if (commitMarkerMismatch.mismatched) {
|
|
commitMarkerDiagnostic = recordPersistMismatchDiagnostic(
|
|
commitMarkerMismatch,
|
|
{
|
|
source: `${source}:indexeddb-empty`,
|
|
},
|
|
);
|
|
if (
|
|
shadowSnapshot &&
|
|
Number(shadowSnapshot.revision || 0) >=
|
|
Number(commitMarkerMismatch.markerRevision || 0)
|
|
) {
|
|
const shadowRestoreResult = applyShadowSnapshotToRuntime(
|
|
normalizedChatId,
|
|
shadowSnapshot,
|
|
{
|
|
source: `${source}:shadow-indexeddb-empty`,
|
|
attemptIndex,
|
|
},
|
|
);
|
|
if (shadowRestoreResult?.loaded) {
|
|
updateGraphPersistenceState({
|
|
persistMismatchReason: commitMarkerDiagnostic.reason,
|
|
});
|
|
return shadowRestoreResult;
|
|
}
|
|
}
|
|
}
|
|
if (shadowSnapshot) {
|
|
const shadowRestoreResult = applyShadowSnapshotToRuntime(
|
|
normalizedChatId,
|
|
shadowSnapshot,
|
|
{
|
|
source: `${source}:shadow-indexeddb-empty`,
|
|
attemptIndex,
|
|
},
|
|
);
|
|
if (shadowRestoreResult?.loaded) {
|
|
return shadowRestoreResult;
|
|
}
|
|
}
|
|
if (commitMarkerDiagnostic?.reason) {
|
|
const orphanMarkerResolution =
|
|
await maybeResolveOrphanAcceptedCommitMarker(normalizedChatId, {
|
|
source,
|
|
attemptIndex,
|
|
commitMarker,
|
|
migrationResult,
|
|
shadowSnapshot,
|
|
applyEmptyState,
|
|
});
|
|
if (orphanMarkerResolution?.result) {
|
|
if (
|
|
!orphanMarkerResolution.orphanCleared &&
|
|
orphanMarkerResolution.result?.loaded
|
|
) {
|
|
updateGraphPersistenceState({
|
|
persistMismatchReason: commitMarkerDiagnostic.reason,
|
|
});
|
|
}
|
|
return orphanMarkerResolution.result;
|
|
}
|
|
}
|
|
const runtimeRepair = queueRuntimeGraphLocalStoreRepair(normalizedChatId, {
|
|
source: `${source}:empty-local-store`,
|
|
scheduleCloudUpload: false,
|
|
});
|
|
if (runtimeRepair.queued) {
|
|
return {
|
|
success: true,
|
|
loaded: false,
|
|
repairQueued: true,
|
|
loadState: GRAPH_LOAD_STATES.LOADING,
|
|
reason: `${snapshotStore.reasonPrefix}-repair-queued`,
|
|
chatId: normalizedChatId,
|
|
attemptIndex,
|
|
revision: Number(runtimeRepair.revision || 0),
|
|
};
|
|
}
|
|
if (
|
|
applyEmptyState &&
|
|
!commitMarkerDiagnostic?.reason &&
|
|
getCurrentChatId() === normalizedChatId
|
|
) {
|
|
return applyIndexedDbEmptyToRuntime(normalizedChatId, {
|
|
source,
|
|
attemptIndex,
|
|
});
|
|
}
|
|
return {
|
|
success: false,
|
|
loaded: false,
|
|
reason: commitMarkerDiagnostic?.reason || `${snapshotStore.reasonPrefix}-empty`,
|
|
chatId: normalizedChatId,
|
|
attemptIndex,
|
|
};
|
|
}
|
|
|
|
const snapshotRevision = normalizeIndexedDbRevision(
|
|
inspectionSnapshot?.meta?.revision,
|
|
);
|
|
const snapshotIntegrity = String(inspectionSnapshot?.meta?.integrity || "").trim();
|
|
const shadowDecision = shouldPreferShadowSnapshotOverOfficial(
|
|
createShadowComparisonGraph({
|
|
chatId: normalizedChatId,
|
|
revision: snapshotRevision,
|
|
integrity: snapshotIntegrity,
|
|
}),
|
|
shadowSnapshot,
|
|
);
|
|
if (shadowSnapshot && shadowDecision?.reason) {
|
|
updateGraphPersistenceState({
|
|
dualWriteLastResult: {
|
|
action: "shadow-compare",
|
|
source: `${source}:indexeddb-shadow-compare`,
|
|
success: Boolean(shadowDecision.prefer),
|
|
reason: shadowDecision.reason,
|
|
resultCode: String(shadowDecision.resultCode || ""),
|
|
shadowRevision: Number(shadowSnapshot.revision || 0),
|
|
officialRevision: snapshotRevision,
|
|
at: Date.now(),
|
|
},
|
|
});
|
|
}
|
|
if (shadowSnapshot && shadowDecision?.prefer) {
|
|
return applyShadowSnapshotToRuntime(
|
|
normalizedChatId,
|
|
shadowSnapshot,
|
|
{
|
|
source: `${source}:shadow-newer-than-indexeddb`,
|
|
attemptIndex,
|
|
},
|
|
);
|
|
}
|
|
if (commitMarkerMismatch.mismatched) {
|
|
commitMarkerDiagnostic = recordPersistMismatchDiagnostic(
|
|
{
|
|
...commitMarkerMismatch,
|
|
marker: commitMarkerMismatch.marker || commitMarker,
|
|
},
|
|
{
|
|
source: `${source}:indexeddb-commit-marker`,
|
|
},
|
|
);
|
|
if (
|
|
shadowSnapshot &&
|
|
Number(shadowSnapshot.revision || 0) >=
|
|
Number(commitMarkerMismatch.markerRevision || 0)
|
|
) {
|
|
const shadowResult = applyShadowSnapshotToRuntime(
|
|
normalizedChatId,
|
|
shadowSnapshot,
|
|
{
|
|
source: `${source}:shadow-beats-commit-marker`,
|
|
attemptIndex,
|
|
},
|
|
);
|
|
if (shadowResult?.loaded && commitMarkerDiagnostic?.reason) {
|
|
updateGraphPersistenceState({
|
|
persistMismatchReason: commitMarkerDiagnostic.reason,
|
|
});
|
|
}
|
|
return shadowResult;
|
|
}
|
|
}
|
|
const shouldAllowOverride =
|
|
allowOverride ||
|
|
BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET.has(
|
|
graphPersistenceState.loadState,
|
|
) ||
|
|
graphPersistenceState.storagePrimary === snapshotStore.storagePrimary ||
|
|
snapshotRevision >=
|
|
normalizeIndexedDbRevision(graphPersistenceState.revision);
|
|
|
|
if (!shouldAllowOverride) {
|
|
return {
|
|
success: false,
|
|
loaded: false,
|
|
reason: `${snapshotStore.reasonPrefix}-stale`,
|
|
chatId: normalizedChatId,
|
|
attemptIndex,
|
|
revision: snapshotRevision,
|
|
};
|
|
}
|
|
|
|
if (getCurrentChatId() !== normalizedChatId) {
|
|
return {
|
|
success: false,
|
|
loaded: false,
|
|
reason: `${snapshotStore.reasonPrefix}-chat-switched`,
|
|
chatId: normalizedChatId,
|
|
attemptIndex,
|
|
revision: snapshotRevision,
|
|
};
|
|
}
|
|
|
|
const staleDecision = detectStaleIndexedDbSnapshotAgainstRuntime(
|
|
normalizedChatId,
|
|
inspectionSnapshot,
|
|
);
|
|
if (staleDecision.stale) {
|
|
const result = {
|
|
success: false,
|
|
loaded: false,
|
|
reason: `${snapshotStore.reasonPrefix}-stale-runtime`,
|
|
chatId: normalizedChatId,
|
|
attemptIndex,
|
|
revision: snapshotRevision,
|
|
staleDetail: cloneRuntimeDebugValue(staleDecision, null),
|
|
};
|
|
updateGraphPersistenceState({
|
|
storagePrimary: snapshotStore.storagePrimary,
|
|
storageMode: snapshotStore.storageMode,
|
|
indexedDbLastError: "",
|
|
dualWriteLastResult: {
|
|
action: "load",
|
|
source: String(source || snapshotStore.reasonPrefix),
|
|
success: false,
|
|
rejected: true,
|
|
reason: result.reason,
|
|
revision: snapshotRevision,
|
|
staleDetail: cloneRuntimeDebugValue(staleDecision, null),
|
|
at: Date.now(),
|
|
},
|
|
});
|
|
recordLoadDiagnostics({
|
|
success: false,
|
|
loaded: false,
|
|
reason: result.reason,
|
|
revision: snapshotRevision,
|
|
storagePrimary: snapshotStore.storagePrimary,
|
|
storageMode: snapshotStore.storageMode,
|
|
exportSnapshotSource: exportSnapshotSource || "snapshot-probe",
|
|
exportProbeMs: normalizeLoadDiagnosticsMs(exportProbeMs),
|
|
exportSnapshotMs: normalizeLoadDiagnosticsMs(exportSnapshotMs),
|
|
preApplyMs: normalizeLoadDiagnosticsMs(readLoadDiagnosticsNow() - loadStartedAt),
|
|
preApplyOtherMs: normalizeLoadDiagnosticsMs(
|
|
Math.max(
|
|
0,
|
|
readLoadDiagnosticsNow() - loadStartedAt - exportSnapshotMs - exportProbeMs,
|
|
),
|
|
),
|
|
staleDetail: cloneRuntimeDebugValue(staleDecision, null),
|
|
});
|
|
return result;
|
|
}
|
|
|
|
if (!snapshot) {
|
|
const exportStartedAt = readLoadDiagnosticsNow();
|
|
snapshot = await db.exportSnapshot({ includeTombstones: false });
|
|
exportSnapshotMs += readLoadDiagnosticsNow() - exportStartedAt;
|
|
exportSnapshotSource =
|
|
exportSnapshotSource === "indexeddb-probe"
|
|
? "indexeddb-probe+indexeddb-export"
|
|
: exportSnapshotSource || "indexeddb-export";
|
|
}
|
|
cacheIndexedDbSnapshot(normalizedChatId, snapshot);
|
|
|
|
const nativeHydrateRequested = currentSettings.loadUseNativeHydrate === true;
|
|
const nativeHydrateForceDisabled =
|
|
currentSettings.graphNativeForceDisable === true;
|
|
const nativeHydrateGate = evaluateNativeHydrateGate(snapshot, currentSettings);
|
|
const shouldUseNativeHydrate =
|
|
nativeHydrateRequested &&
|
|
nativeHydrateForceDisabled !== true &&
|
|
nativeHydrateGate.allowed;
|
|
let nativeHydrateModuleStatus = null;
|
|
let nativeHydratePreloadStatus = nativeHydrateRequested
|
|
? nativeHydrateForceDisabled
|
|
? "force-disabled"
|
|
: nativeHydrateGate.allowed
|
|
? "pending"
|
|
: "gated-out"
|
|
: "not-requested";
|
|
let nativeHydratePreloadError = "";
|
|
let nativeHydratePreloadMs = 0;
|
|
if (shouldUseNativeHydrate) {
|
|
const preloadStartedAt = readLoadDiagnosticsNow();
|
|
try {
|
|
if (!nativeHydrateInstallPromiseRef.value) {
|
|
nativeHydrateInstallPromiseRef.value = importNativeCore(runtime)
|
|
.then((module) => module?.installNativeHydrateHook?.())
|
|
.catch((error) => {
|
|
nativeHydrateInstallPromiseRef.value = null;
|
|
throw error;
|
|
});
|
|
}
|
|
nativeHydrateModuleStatus = await nativeHydrateInstallPromiseRef.value;
|
|
nativeHydratePreloadStatus = nativeHydrateModuleStatus?.loaded
|
|
? "loaded"
|
|
: "not-loaded";
|
|
nativeHydratePreloadMs =
|
|
readLoadDiagnosticsNow() - preloadStartedAt;
|
|
} catch (error) {
|
|
nativeHydratePreloadStatus = "failed";
|
|
nativeHydratePreloadMs =
|
|
readLoadDiagnosticsNow() - preloadStartedAt;
|
|
nativeHydratePreloadError = error?.message || String(error);
|
|
if (currentSettings.nativeEngineFailOpen !== false) {
|
|
console.warn(
|
|
"[ST-BME] native hydrate preload failed, fallback to JS hydrate:",
|
|
error,
|
|
);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
preApplyMs = readLoadDiagnosticsNow() - loadStartedAt;
|
|
const applyInvokeStartedAt = readLoadDiagnosticsNow();
|
|
const loadResult = applyIndexedDbSnapshotToRuntime(normalizedChatId, snapshot, {
|
|
source,
|
|
attemptIndex,
|
|
storagePrimary: snapshotStore.storagePrimary,
|
|
storageMode: snapshotStore.storageMode,
|
|
statusLabel: snapshotStore.statusLabel,
|
|
reasonPrefix: snapshotStore.reasonPrefix,
|
|
currentSettings,
|
|
nativeHydrateRequested,
|
|
nativeHydrateForceDisabled,
|
|
nativeHydrateGate,
|
|
nativeHydratePreloadStatus,
|
|
nativeHydratePreloadMs,
|
|
nativeHydratePreloadError,
|
|
nativeHydrateModuleStatus,
|
|
});
|
|
const applyInvokeMs = readLoadDiagnosticsNow() - applyInvokeStartedAt;
|
|
const totalLoadMs = readLoadDiagnosticsNow() - loadStartedAt;
|
|
const loadAccountedMs = preApplyMs + applyInvokeMs;
|
|
if (commitMarkerDiagnostic?.reason && loadResult?.loaded) {
|
|
updateGraphPersistenceState({
|
|
persistMismatchReason: commitMarkerDiagnostic.reason,
|
|
});
|
|
}
|
|
recordLoadDiagnostics({
|
|
success: loadResult?.success === true,
|
|
loaded: loadResult?.loaded === true,
|
|
reason: String(loadResult?.reason || ""),
|
|
revision: Number.isFinite(Number(loadResult?.revision))
|
|
? Number(loadResult.revision)
|
|
: snapshotRevision,
|
|
storagePrimary: snapshotStore.storagePrimary,
|
|
storageMode: snapshotStore.storageMode,
|
|
commitMarkerMismatched: commitMarkerMismatch.mismatched === true,
|
|
exportSnapshotSource: exportSnapshotSource || "snapshot-prepared",
|
|
exportProbeMs: normalizeLoadDiagnosticsMs(exportProbeMs),
|
|
exportSnapshotMs: normalizeLoadDiagnosticsMs(exportSnapshotMs),
|
|
preApplyMs: normalizeLoadDiagnosticsMs(preApplyMs),
|
|
preApplyOtherMs: normalizeLoadDiagnosticsMs(
|
|
Math.max(0, preApplyMs - exportSnapshotMs - exportProbeMs),
|
|
),
|
|
hydrateNativeRequested: loadResult?.nativeHydrateRequested === true,
|
|
hydrateNativeForceDisabled: loadResult?.nativeHydrateForceDisabled === true,
|
|
hydrateNativeGateAllowed: loadResult?.nativeHydrateGate?.allowed === true,
|
|
hydrateNativeGateReasons: cloneRuntimeDebugValue(
|
|
loadResult?.nativeHydrateGate?.reasons,
|
|
[],
|
|
),
|
|
hydrateNativePreloadStatus: String(
|
|
loadResult?.nativeHydratePreloadStatus || nativeHydratePreloadStatus || "",
|
|
),
|
|
hydrateNativePreloadMs: normalizeLoadDiagnosticsMs(
|
|
loadResult?.nativeHydratePreloadMs,
|
|
),
|
|
hydrateNativePreloadError: String(
|
|
loadResult?.nativeHydratePreloadError || "",
|
|
),
|
|
hydrateNativeModuleLoaded: Boolean(
|
|
loadResult?.nativeHydrateModuleStatus?.loaded,
|
|
),
|
|
hydrateNativeModuleSource: String(
|
|
loadResult?.nativeHydrateModuleStatus?.source || "",
|
|
),
|
|
hydrateNativeModuleError: String(
|
|
loadResult?.nativeHydrateModuleStatus?.error || "",
|
|
),
|
|
hydrateNativeUsed: loadResult?.hydrateDiagnostics?.nativeUsed === true,
|
|
hydrateNativeStatus: String(
|
|
loadResult?.hydrateDiagnostics?.nativeStatus || "",
|
|
),
|
|
hydrateNativeError: String(loadResult?.hydrateDiagnostics?.nativeError || ""),
|
|
hydrateNativeRecordsMs: normalizeLoadDiagnosticsMs(
|
|
loadResult?.hydrateDiagnostics?.nativeRecordsMs,
|
|
),
|
|
applyInvokeMs: normalizeLoadDiagnosticsMs(applyInvokeMs),
|
|
untrackedMs: normalizeLoadDiagnosticsMs(
|
|
Math.max(0, totalLoadMs - loadAccountedMs),
|
|
),
|
|
});
|
|
return loadResult;
|
|
} catch (error) {
|
|
console.warn(`[ST-BME] ${localStore.statusLabel} 读取失败,回退 metadata:`, error);
|
|
updateGraphPersistenceState({
|
|
storagePrimary: localStore.storagePrimary,
|
|
storageMode: localStore.storageMode,
|
|
indexedDbLastError: error?.message || String(error),
|
|
dualWriteLastResult: {
|
|
action: "load",
|
|
source: String(source || localStore.reasonPrefix),
|
|
success: false,
|
|
error: error?.message || String(error),
|
|
at: Date.now(),
|
|
},
|
|
});
|
|
const result = {
|
|
success: false,
|
|
loaded: false,
|
|
reason: `${localStore.reasonPrefix}-read-failed`,
|
|
chatId: normalizedChatId,
|
|
attemptIndex,
|
|
error,
|
|
};
|
|
recordLoadDiagnostics({
|
|
success: false,
|
|
loaded: false,
|
|
reason: result.reason,
|
|
storagePrimary: localStore.storagePrimary,
|
|
storageMode: localStore.storageMode,
|
|
error: error?.message || String(error),
|
|
exportSnapshotSource: exportSnapshotSource || "unknown",
|
|
exportProbeMs: normalizeLoadDiagnosticsMs(exportProbeMs),
|
|
exportSnapshotMs: normalizeLoadDiagnosticsMs(exportSnapshotMs),
|
|
preApplyMs: normalizeLoadDiagnosticsMs(
|
|
preApplyMs || (readLoadDiagnosticsNow() - loadStartedAt),
|
|
),
|
|
preApplyOtherMs: normalizeLoadDiagnosticsMs(
|
|
Math.max(
|
|
0,
|
|
(preApplyMs || (readLoadDiagnosticsNow() - loadStartedAt)) -
|
|
exportSnapshotMs -
|
|
exportProbeMs,
|
|
),
|
|
),
|
|
});
|
|
return result;
|
|
}
|
|
|
|
}
|
|
|
|
export function maybeFlushQueuedGraphPersistImpl(runtime, reason = "queued-graph-persist") {
|
|
const graphPersistenceState = createGraphPersistenceStateProxy(runtime);
|
|
const currentGraph = runtime.getCurrentGraph?.() || null;
|
|
const nativeHydrateInstallPromiseRef = createNativeHydrateInstallPromiseRef(runtime);
|
|
const nativePersistDeltaInstallPromiseRef = createNativePersistDeltaInstallPromiseRef(runtime);
|
|
const bmeIndexedDbLatestQueuedRevisionByChatId = runtime.bmeIndexedDbLatestQueuedRevisionByChatId;
|
|
const bmeIndexedDbWriteInFlightByChatId = runtime.bmeIndexedDbWriteInFlightByChatId;
|
|
const updateGraphPersistenceState = runtime.updateGraphPersistenceState || ((patch = {}) => runtime.setGraphPersistenceState?.({ ...(runtime.getGraphPersistenceState?.() || {}), ...(patch || {}) }));
|
|
const AUTHORITY_GRAPH_STORE_KIND = runtime.AUTHORITY_GRAPH_STORE_KIND;
|
|
const BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET = runtime.BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET;
|
|
const GRAPH_LOAD_STATES = runtime.GRAPH_LOAD_STATES;
|
|
const applyAcceptedPendingPersistState = runtime.applyAcceptedPendingPersistState;
|
|
const applyGraphLoadState = runtime.applyGraphLoadState;
|
|
const applyIndexedDbEmptyToRuntime = runtime.applyIndexedDbEmptyToRuntime;
|
|
const applyIndexedDbSnapshotToRuntime = runtime.applyIndexedDbSnapshotToRuntime;
|
|
const applyPersistDeltaToSnapshot = runtime.applyPersistDeltaToSnapshot;
|
|
const applyShadowSnapshotToRuntime = runtime.applyShadowSnapshotToRuntime;
|
|
const areChatIdsEquivalentForResolvedIdentity = runtime.areChatIdsEquivalentForResolvedIdentity;
|
|
const buildBmeSyncRuntimeOptions = runtime.buildBmeSyncRuntimeOptions;
|
|
const buildGraphLocalStoreSelectorKey = runtime.buildGraphLocalStoreSelectorKey;
|
|
const buildGraphPersistResult = runtime.buildGraphPersistResult;
|
|
const buildPersistDelta = runtime.buildPersistDelta;
|
|
const buildPersistDeltaFromGraphDirtyState = runtime.buildPersistDeltaFromGraphDirtyState;
|
|
const buildPersistObservabilitySummary = runtime.buildPersistObservabilitySummary;
|
|
const buildPersistenceEnvironment = runtime.buildPersistenceEnvironment;
|
|
const buildSnapshotFromGraph = runtime.buildSnapshotFromGraph;
|
|
const cacheIndexedDbSnapshot = runtime.cacheIndexedDbSnapshot;
|
|
const canPersistGraphToMetadataFallback = runtime.canPersistGraphToMetadataFallback;
|
|
const clearPendingGraphPersistRetry = runtime.clearPendingGraphPersistRetry;
|
|
const cloneGraphForPersistence = runtime.cloneGraphForPersistence;
|
|
const cloneRuntimeDebugValue = runtime.cloneRuntimeDebugValue;
|
|
const createShadowComparisonGraph = runtime.createShadowComparisonGraph;
|
|
const detectIndexedDbSnapshotCommitMarkerMismatch = runtime.detectIndexedDbSnapshotCommitMarkerMismatch;
|
|
const detectStaleIndexedDbSnapshotAgainstRuntime = runtime.detectStaleIndexedDbSnapshotAgainstRuntime;
|
|
const ensureBmeChatManager = runtime.ensureBmeChatManager;
|
|
const ensureCurrentGraphRuntimeState = runtime.ensureCurrentGraphRuntimeState;
|
|
const evaluateNativeHydrateGate = runtime.evaluateNativeHydrateGate;
|
|
const evaluatePersistNativeDeltaGate = runtime.evaluatePersistNativeDeltaGate;
|
|
const getChatMetadataIntegrity = runtime.getChatMetadataIntegrity;
|
|
const getContext = runtime.getContext;
|
|
const getCurrentChatId = runtime.getCurrentChatId;
|
|
const getGraphPersistedRevision = runtime.getGraphPersistedRevision;
|
|
const getPreferredGraphLocalStorePresentationSync = runtime.getPreferredGraphLocalStorePresentationSync;
|
|
const getRequestedGraphLocalStorageMode = runtime.getRequestedGraphLocalStorageMode;
|
|
const getSettings = runtime.getSettings;
|
|
const hasMeaningfulRuntimeGraphForChat = runtime.hasMeaningfulRuntimeGraphForChat;
|
|
const isAuthorityGraphStorePresentation = runtime.isAuthorityGraphStorePresentation;
|
|
const isGraphLocalStorageModeOpfs = runtime.isGraphLocalStorageModeOpfs;
|
|
const isIndexedDbSnapshotMeaningful = runtime.isIndexedDbSnapshotMeaningful;
|
|
const isRestoreLockActive = runtime.isRestoreLockActive;
|
|
const maybeCaptureGraphShadowSnapshot = runtime.maybeCaptureGraphShadowSnapshot;
|
|
const maybeClearAcceptedPendingPersistState = runtime.maybeClearAcceptedPendingPersistState;
|
|
const maybeImportLegacyIndexedDbSnapshotToLocalStore = runtime.maybeImportLegacyIndexedDbSnapshotToLocalStore;
|
|
const maybeImportLegacyOpfsSnapshotToLocalStore = runtime.maybeImportLegacyOpfsSnapshotToLocalStore;
|
|
const maybeMigrateLegacyGraphToIndexedDb = runtime.maybeMigrateLegacyGraphToIndexedDb;
|
|
const maybeRecoverIndexedDbGraphFromStableIdentity = runtime.maybeRecoverIndexedDbGraphFromStableIdentity;
|
|
const maybeResolveOrphanAcceptedCommitMarker = runtime.maybeResolveOrphanAcceptedCommitMarker;
|
|
const maybeResumePendingAutoExtraction = runtime.maybeResumePendingAutoExtraction;
|
|
const normalizeChatIdCandidate = runtime.normalizeChatIdCandidate;
|
|
const normalizeGraphRuntimeState = runtime.normalizeGraphRuntimeState;
|
|
const normalizeIndexedDbRevision = runtime.normalizeIndexedDbRevision;
|
|
const normalizeLoadDiagnosticsMs = runtime.normalizeLoadDiagnosticsMs;
|
|
const normalizePersistDeltaDiagnosticsMs = runtime.normalizePersistDeltaDiagnosticsMs;
|
|
const persistGraphToChatMetadata = runtime.persistGraphToChatMetadata;
|
|
const persistGraphToConfiguredDurableTier = runtime.persistGraphToConfiguredDurableTier;
|
|
const pruneGraphPersistDirtyState = runtime.pruneGraphPersistDirtyState;
|
|
const queueGraphPersist = runtime.queueGraphPersist;
|
|
const queueRuntimeGraphLocalStoreRepair = runtime.queueRuntimeGraphLocalStoreRepair;
|
|
const readCachedIndexedDbSnapshot = runtime.readCachedIndexedDbSnapshot;
|
|
const readLoadDiagnosticsNow = runtime.readLoadDiagnosticsNow;
|
|
const readLocalStoreDiagnosticsSync = runtime.readLocalStoreDiagnosticsSync;
|
|
const readPersistDeltaDiagnosticsNow = runtime.readPersistDeltaDiagnosticsNow;
|
|
const recordLocalPersistEarlyFailure = runtime.recordLocalPersistEarlyFailure;
|
|
const recordPersistMismatchDiagnostic = runtime.recordPersistMismatchDiagnostic;
|
|
const refreshCurrentChatLocalStoreBinding = runtime.refreshCurrentChatLocalStoreBinding;
|
|
const rememberResolvedGraphIdentityAlias = runtime.rememberResolvedGraphIdentityAlias;
|
|
const resolveCompatibleGraphShadowSnapshot = runtime.resolveCompatibleGraphShadowSnapshot;
|
|
const resolveCurrentChatIdentity = runtime.resolveCurrentChatIdentity;
|
|
const resolveDbGraphStorePresentation = runtime.resolveDbGraphStorePresentation;
|
|
const resolveLocalStoreTierFromPresentation = runtime.resolveLocalStoreTierFromPresentation;
|
|
const resolvePendingPersistGraphSource = runtime.resolvePendingPersistGraphSource;
|
|
const resolvePendingPersistLastProcessedAssistantFloor = runtime.resolvePendingPersistLastProcessedAssistantFloor;
|
|
const resolvePersistRevisionFloor = runtime.resolvePersistRevisionFloor;
|
|
const resolveSnapshotGraphStorePresentation = runtime.resolveSnapshotGraphStorePresentation;
|
|
const schedulePendingGraphPersistRetry = runtime.schedulePendingGraphPersistRetry;
|
|
const scheduleUpload = runtime.scheduleUpload;
|
|
const shouldPreferShadowSnapshotOverOfficial = runtime.shouldPreferShadowSnapshotOverOfficial;
|
|
const stampGraphPersistenceMeta = runtime.stampGraphPersistenceMeta;
|
|
const syncCommitMarkerToPersistenceState = runtime.syncCommitMarkerToPersistenceState;
|
|
const updateLoadDiagnostics = runtime.updateLoadDiagnostics;
|
|
const updatePersistDeltaDiagnostics = runtime.updatePersistDeltaDiagnostics;
|
|
const console = runtime.console || globalThis.console;
|
|
|
|
const context = getContext();
|
|
if (!currentGraph || !canPersistGraphToMetadataFallback(context)) {
|
|
return buildGraphPersistResult({
|
|
queued: graphPersistenceState.pendingPersist,
|
|
blocked: !canPersistGraphToMetadataFallback(context),
|
|
reason: canPersistGraphToMetadataFallback(context)
|
|
? "missing-current-graph"
|
|
: "write-protected",
|
|
});
|
|
}
|
|
|
|
if (
|
|
!graphPersistenceState.pendingPersist &&
|
|
graphPersistenceState.queuedPersistRevision <=
|
|
graphPersistenceState.lastPersistedRevision
|
|
) {
|
|
return buildGraphPersistResult({
|
|
saved: false,
|
|
reason: "no-queued-persist",
|
|
});
|
|
}
|
|
|
|
const activeChatId = getCurrentChatId();
|
|
const queuedChatId = String(graphPersistenceState.queuedPersistChatId || "");
|
|
if (queuedChatId && activeChatId && queuedChatId !== activeChatId) {
|
|
return buildGraphPersistResult({
|
|
saved: false,
|
|
queued: graphPersistenceState.pendingPersist,
|
|
blocked: true,
|
|
reason: "queued-chat-mismatch",
|
|
revision: graphPersistenceState.queuedPersistRevision,
|
|
saveMode: graphPersistenceState.queuedPersistMode,
|
|
});
|
|
}
|
|
|
|
const targetRevision = Math.max(
|
|
graphPersistenceState.revision || 0,
|
|
graphPersistenceState.queuedPersistRevision || 0,
|
|
);
|
|
if (targetRevision > (graphPersistenceState.revision || 0)) {
|
|
updateGraphPersistenceState({
|
|
revision: targetRevision,
|
|
});
|
|
}
|
|
|
|
return persistGraphToChatMetadata(context, {
|
|
reason,
|
|
revision: targetRevision,
|
|
immediate: graphPersistenceState.queuedPersistMode !== "debounced",
|
|
});
|
|
|
|
}
|
|
|
|
export async function retryPendingGraphPersistImpl(runtime, {
|
|
reason = "pending-graph-persist-retry",
|
|
retryAttempt = 0,
|
|
scheduleRetryOnFailure = false,
|
|
ignoreRestoreLock = false,
|
|
} = {}) {
|
|
const graphPersistenceState = createGraphPersistenceStateProxy(runtime);
|
|
const currentGraph = runtime.getCurrentGraph?.() || null;
|
|
const nativeHydrateInstallPromiseRef = createNativeHydrateInstallPromiseRef(runtime);
|
|
const nativePersistDeltaInstallPromiseRef = createNativePersistDeltaInstallPromiseRef(runtime);
|
|
const bmeIndexedDbLatestQueuedRevisionByChatId = runtime.bmeIndexedDbLatestQueuedRevisionByChatId;
|
|
const bmeIndexedDbWriteInFlightByChatId = runtime.bmeIndexedDbWriteInFlightByChatId;
|
|
const updateGraphPersistenceState = runtime.updateGraphPersistenceState || ((patch = {}) => runtime.setGraphPersistenceState?.({ ...(runtime.getGraphPersistenceState?.() || {}), ...(patch || {}) }));
|
|
const AUTHORITY_GRAPH_STORE_KIND = runtime.AUTHORITY_GRAPH_STORE_KIND;
|
|
const BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET = runtime.BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET;
|
|
const GRAPH_LOAD_STATES = runtime.GRAPH_LOAD_STATES;
|
|
const applyAcceptedPendingPersistState = runtime.applyAcceptedPendingPersistState;
|
|
const applyGraphLoadState = runtime.applyGraphLoadState;
|
|
const applyIndexedDbEmptyToRuntime = runtime.applyIndexedDbEmptyToRuntime;
|
|
const applyIndexedDbSnapshotToRuntime = runtime.applyIndexedDbSnapshotToRuntime;
|
|
const applyPersistDeltaToSnapshot = runtime.applyPersistDeltaToSnapshot;
|
|
const applyShadowSnapshotToRuntime = runtime.applyShadowSnapshotToRuntime;
|
|
const areChatIdsEquivalentForResolvedIdentity = runtime.areChatIdsEquivalentForResolvedIdentity;
|
|
const buildBmeSyncRuntimeOptions = runtime.buildBmeSyncRuntimeOptions;
|
|
const buildGraphLocalStoreSelectorKey = runtime.buildGraphLocalStoreSelectorKey;
|
|
const buildGraphPersistResult = runtime.buildGraphPersistResult;
|
|
const buildPersistDelta = runtime.buildPersistDelta;
|
|
const buildPersistDeltaFromGraphDirtyState = runtime.buildPersistDeltaFromGraphDirtyState;
|
|
const buildPersistObservabilitySummary = runtime.buildPersistObservabilitySummary;
|
|
const buildPersistenceEnvironment = runtime.buildPersistenceEnvironment;
|
|
const buildSnapshotFromGraph = runtime.buildSnapshotFromGraph;
|
|
const cacheIndexedDbSnapshot = runtime.cacheIndexedDbSnapshot;
|
|
const canPersistGraphToMetadataFallback = runtime.canPersistGraphToMetadataFallback;
|
|
const clearPendingGraphPersistRetry = runtime.clearPendingGraphPersistRetry;
|
|
const cloneGraphForPersistence = runtime.cloneGraphForPersistence;
|
|
const cloneRuntimeDebugValue = runtime.cloneRuntimeDebugValue;
|
|
const createShadowComparisonGraph = runtime.createShadowComparisonGraph;
|
|
const detectIndexedDbSnapshotCommitMarkerMismatch = runtime.detectIndexedDbSnapshotCommitMarkerMismatch;
|
|
const detectStaleIndexedDbSnapshotAgainstRuntime = runtime.detectStaleIndexedDbSnapshotAgainstRuntime;
|
|
const ensureBmeChatManager = runtime.ensureBmeChatManager;
|
|
const ensureCurrentGraphRuntimeState = runtime.ensureCurrentGraphRuntimeState;
|
|
const evaluateNativeHydrateGate = runtime.evaluateNativeHydrateGate;
|
|
const evaluatePersistNativeDeltaGate = runtime.evaluatePersistNativeDeltaGate;
|
|
const getChatMetadataIntegrity = runtime.getChatMetadataIntegrity;
|
|
const getContext = runtime.getContext;
|
|
const getCurrentChatId = runtime.getCurrentChatId;
|
|
const getGraphPersistedRevision = runtime.getGraphPersistedRevision;
|
|
const getPreferredGraphLocalStorePresentationSync = runtime.getPreferredGraphLocalStorePresentationSync;
|
|
const getRequestedGraphLocalStorageMode = runtime.getRequestedGraphLocalStorageMode;
|
|
const getSettings = runtime.getSettings;
|
|
const hasMeaningfulRuntimeGraphForChat = runtime.hasMeaningfulRuntimeGraphForChat;
|
|
const isAuthorityGraphStorePresentation = runtime.isAuthorityGraphStorePresentation;
|
|
const isGraphLocalStorageModeOpfs = runtime.isGraphLocalStorageModeOpfs;
|
|
const isIndexedDbSnapshotMeaningful = runtime.isIndexedDbSnapshotMeaningful;
|
|
const isRestoreLockActive = runtime.isRestoreLockActive;
|
|
const maybeCaptureGraphShadowSnapshot = runtime.maybeCaptureGraphShadowSnapshot;
|
|
const maybeClearAcceptedPendingPersistState = runtime.maybeClearAcceptedPendingPersistState;
|
|
const maybeImportLegacyIndexedDbSnapshotToLocalStore = runtime.maybeImportLegacyIndexedDbSnapshotToLocalStore;
|
|
const maybeImportLegacyOpfsSnapshotToLocalStore = runtime.maybeImportLegacyOpfsSnapshotToLocalStore;
|
|
const maybeMigrateLegacyGraphToIndexedDb = runtime.maybeMigrateLegacyGraphToIndexedDb;
|
|
const maybeRecoverIndexedDbGraphFromStableIdentity = runtime.maybeRecoverIndexedDbGraphFromStableIdentity;
|
|
const maybeResolveOrphanAcceptedCommitMarker = runtime.maybeResolveOrphanAcceptedCommitMarker;
|
|
const maybeResumePendingAutoExtraction = runtime.maybeResumePendingAutoExtraction;
|
|
const normalizeChatIdCandidate = runtime.normalizeChatIdCandidate;
|
|
const normalizeGraphRuntimeState = runtime.normalizeGraphRuntimeState;
|
|
const normalizeIndexedDbRevision = runtime.normalizeIndexedDbRevision;
|
|
const normalizeLoadDiagnosticsMs = runtime.normalizeLoadDiagnosticsMs;
|
|
const normalizePersistDeltaDiagnosticsMs = runtime.normalizePersistDeltaDiagnosticsMs;
|
|
const persistGraphToChatMetadata = runtime.persistGraphToChatMetadata;
|
|
const persistGraphToConfiguredDurableTier = runtime.persistGraphToConfiguredDurableTier;
|
|
const pruneGraphPersistDirtyState = runtime.pruneGraphPersistDirtyState;
|
|
const queueGraphPersist = runtime.queueGraphPersist;
|
|
const queueRuntimeGraphLocalStoreRepair = runtime.queueRuntimeGraphLocalStoreRepair;
|
|
const readCachedIndexedDbSnapshot = runtime.readCachedIndexedDbSnapshot;
|
|
const readLoadDiagnosticsNow = runtime.readLoadDiagnosticsNow;
|
|
const readLocalStoreDiagnosticsSync = runtime.readLocalStoreDiagnosticsSync;
|
|
const readPersistDeltaDiagnosticsNow = runtime.readPersistDeltaDiagnosticsNow;
|
|
const recordLocalPersistEarlyFailure = runtime.recordLocalPersistEarlyFailure;
|
|
const recordPersistMismatchDiagnostic = runtime.recordPersistMismatchDiagnostic;
|
|
const refreshCurrentChatLocalStoreBinding = runtime.refreshCurrentChatLocalStoreBinding;
|
|
const rememberResolvedGraphIdentityAlias = runtime.rememberResolvedGraphIdentityAlias;
|
|
const resolveCompatibleGraphShadowSnapshot = runtime.resolveCompatibleGraphShadowSnapshot;
|
|
const resolveCurrentChatIdentity = runtime.resolveCurrentChatIdentity;
|
|
const resolveDbGraphStorePresentation = runtime.resolveDbGraphStorePresentation;
|
|
const resolveLocalStoreTierFromPresentation = runtime.resolveLocalStoreTierFromPresentation;
|
|
const resolvePendingPersistGraphSource = runtime.resolvePendingPersistGraphSource;
|
|
const resolvePendingPersistLastProcessedAssistantFloor = runtime.resolvePendingPersistLastProcessedAssistantFloor;
|
|
const resolvePersistRevisionFloor = runtime.resolvePersistRevisionFloor;
|
|
const resolveSnapshotGraphStorePresentation = runtime.resolveSnapshotGraphStorePresentation;
|
|
const schedulePendingGraphPersistRetry = runtime.schedulePendingGraphPersistRetry;
|
|
const scheduleUpload = runtime.scheduleUpload;
|
|
const shouldPreferShadowSnapshotOverOfficial = runtime.shouldPreferShadowSnapshotOverOfficial;
|
|
const stampGraphPersistenceMeta = runtime.stampGraphPersistenceMeta;
|
|
const syncCommitMarkerToPersistenceState = runtime.syncCommitMarkerToPersistenceState;
|
|
const updateLoadDiagnostics = runtime.updateLoadDiagnostics;
|
|
const updatePersistDeltaDiagnostics = runtime.updatePersistDeltaDiagnostics;
|
|
const console = runtime.console || globalThis.console;
|
|
|
|
ensureCurrentGraphRuntimeState();
|
|
|
|
if (!ignoreRestoreLock && isRestoreLockActive()) {
|
|
return buildGraphPersistResult({
|
|
saved: false,
|
|
blocked: true,
|
|
accepted: false,
|
|
reason: "restore-lock-active",
|
|
revision: graphPersistenceState.revision,
|
|
saveMode: graphPersistenceState.lastPersistMode,
|
|
storageTier: "none",
|
|
});
|
|
}
|
|
|
|
if (!graphPersistenceState.pendingPersist) {
|
|
clearPendingGraphPersistRetry();
|
|
return buildGraphPersistResult({
|
|
saved: false,
|
|
blocked: false,
|
|
accepted: false,
|
|
reason: "no-pending-persist",
|
|
revision: graphPersistenceState.revision,
|
|
saveMode: graphPersistenceState.lastPersistMode,
|
|
storageTier: "none",
|
|
});
|
|
}
|
|
|
|
if (maybeClearAcceptedPendingPersistState(reason)) {
|
|
return buildGraphPersistResult({
|
|
saved: true,
|
|
blocked: false,
|
|
accepted: true,
|
|
reason: `${String(reason || "pending-graph-persist-retry")}:accepted-revision`,
|
|
revision: Math.max(
|
|
Number(graphPersistenceState.lastAcceptedRevision || 0),
|
|
Number(graphPersistenceState.revision || 0),
|
|
),
|
|
saveMode: "accepted-revision-reconcile",
|
|
storageTier: String(graphPersistenceState.acceptedStorageTier || "none"),
|
|
acceptedBy: String(graphPersistenceState.acceptedStorageTier || "none"),
|
|
});
|
|
}
|
|
|
|
const context = getContext();
|
|
const activeChatId = normalizeChatIdCandidate(getCurrentChatId(context));
|
|
const queuedChatId = normalizeChatIdCandidate(
|
|
graphPersistenceState.queuedPersistChatId ||
|
|
graphPersistenceState.chatId ||
|
|
activeChatId,
|
|
);
|
|
const currentIdentity = resolveCurrentChatIdentity(context);
|
|
if (!currentGraph || !context || !activeChatId || !queuedChatId) {
|
|
if (scheduleRetryOnFailure) {
|
|
schedulePendingGraphPersistRetry(reason, Number(retryAttempt) + 1);
|
|
}
|
|
return buildGraphPersistResult({
|
|
saved: false,
|
|
queued: true,
|
|
blocked: true,
|
|
accepted: false,
|
|
reason: "pending-persist-context-unavailable",
|
|
revision: Math.max(
|
|
Number(graphPersistenceState.queuedPersistRevision || 0),
|
|
Number(graphPersistenceState.revision || 0),
|
|
),
|
|
saveMode: graphPersistenceState.queuedPersistMode,
|
|
storageTier: "none",
|
|
});
|
|
}
|
|
|
|
if (
|
|
!areChatIdsEquivalentForResolvedIdentity(
|
|
queuedChatId,
|
|
activeChatId,
|
|
currentIdentity,
|
|
) &&
|
|
!areChatIdsEquivalentForResolvedIdentity(
|
|
activeChatId,
|
|
queuedChatId,
|
|
currentIdentity,
|
|
)
|
|
) {
|
|
if (scheduleRetryOnFailure) {
|
|
schedulePendingGraphPersistRetry(reason, Number(retryAttempt) + 1);
|
|
}
|
|
return buildGraphPersistResult({
|
|
saved: false,
|
|
queued: true,
|
|
blocked: true,
|
|
accepted: false,
|
|
reason: "queued-chat-mismatch",
|
|
revision: Math.max(
|
|
Number(graphPersistenceState.queuedPersistRevision || 0),
|
|
Number(graphPersistenceState.revision || 0),
|
|
),
|
|
saveMode: graphPersistenceState.queuedPersistMode,
|
|
storageTier: "none",
|
|
});
|
|
}
|
|
|
|
const requestedLocalStoreMode = getRequestedGraphLocalStorageMode(
|
|
getSettings(),
|
|
);
|
|
if (
|
|
requestedLocalStoreMode === "auto" ||
|
|
isGraphLocalStorageModeOpfs(requestedLocalStoreMode)
|
|
) {
|
|
await refreshCurrentChatLocalStoreBinding({
|
|
chatId: activeChatId,
|
|
forceCapabilityRefresh: true,
|
|
reopenCurrentDb: true,
|
|
source: reason,
|
|
});
|
|
}
|
|
|
|
const pendingPersistGraphSource = resolvePendingPersistGraphSource(
|
|
queuedChatId,
|
|
);
|
|
const pendingPersistGraph = pendingPersistGraphSource?.graph || currentGraph;
|
|
const pendingPersistGraphDetached =
|
|
Boolean(pendingPersistGraph) &&
|
|
typeof pendingPersistGraph === "object" &&
|
|
pendingPersistGraph !== currentGraph;
|
|
const targetRevision = Math.max(
|
|
Number(graphPersistenceState.queuedPersistRevision || 0),
|
|
Number(graphPersistenceState.revision || 0),
|
|
Number(graphPersistenceState.lastPersistedRevision || 0),
|
|
Number(pendingPersistGraphSource?.revision || 0),
|
|
Number(getGraphPersistedRevision(pendingPersistGraph) || 0),
|
|
);
|
|
const lastProcessedAssistantFloor =
|
|
resolvePendingPersistLastProcessedAssistantFloor();
|
|
const acceptedPersistResult = await persistGraphToConfiguredDurableTier(
|
|
context,
|
|
pendingPersistGraph,
|
|
{
|
|
chatId: activeChatId,
|
|
revision: targetRevision,
|
|
reason,
|
|
lastProcessedAssistantFloor,
|
|
graphDetached: pendingPersistGraphDetached,
|
|
},
|
|
);
|
|
if (acceptedPersistResult?.accepted) {
|
|
applyAcceptedPendingPersistState(acceptedPersistResult, {
|
|
lastProcessedAssistantFloor,
|
|
persistedGraph: pendingPersistGraph,
|
|
});
|
|
void maybeResumePendingAutoExtraction(
|
|
`pending-persist-resolved:${acceptedPersistResult.acceptedBy || acceptedPersistResult.storageTier || "accepted"}`,
|
|
);
|
|
return acceptedPersistResult;
|
|
}
|
|
|
|
let recoverableTier = "none";
|
|
if (canPersistGraphToMetadataFallback(context, pendingPersistGraph)) {
|
|
const metadataReason = `${reason}:metadata-full-fallback`;
|
|
const metadataResult = persistGraphToChatMetadata(context, {
|
|
reason: metadataReason,
|
|
revision: targetRevision,
|
|
immediate: true,
|
|
graph: pendingPersistGraph,
|
|
});
|
|
if (metadataResult?.saved) {
|
|
recoverableTier = "metadata-full";
|
|
}
|
|
}
|
|
|
|
if (
|
|
recoverableTier === "none" &&
|
|
maybeCaptureGraphShadowSnapshot(`${reason}:shadow-fallback`, {
|
|
graph: pendingPersistGraph,
|
|
chatId: activeChatId,
|
|
revision: targetRevision,
|
|
})
|
|
) {
|
|
recoverableTier = "shadow";
|
|
}
|
|
|
|
const queuedReason = `${reason}:still-pending`;
|
|
const queuedResult = queueGraphPersist(queuedReason, targetRevision, {
|
|
immediate: graphPersistenceState.queuedPersistMode !== "debounced",
|
|
graph: pendingPersistGraph,
|
|
chatId: activeChatId,
|
|
captureShadow: recoverableTier === "none",
|
|
recoverableTier,
|
|
});
|
|
if (recoverableTier !== "none") {
|
|
updateGraphPersistenceState({
|
|
lastPersistReason: queuedReason,
|
|
lastRecoverableStorageTier: recoverableTier,
|
|
});
|
|
}
|
|
if (scheduleRetryOnFailure && recoverableTier === "none") {
|
|
schedulePendingGraphPersistRetry(reason, Number(retryAttempt) + 1);
|
|
}
|
|
return buildGraphPersistResult({
|
|
saved: false,
|
|
queued: true,
|
|
blocked: true,
|
|
accepted: false,
|
|
recoverable:
|
|
recoverableTier !== "none" || queuedResult?.recoverable === true,
|
|
reason: queuedReason,
|
|
revision: Number(queuedResult?.revision || targetRevision),
|
|
saveMode: String(
|
|
queuedResult?.saveMode || graphPersistenceState.queuedPersistMode || "immediate",
|
|
),
|
|
storageTier:
|
|
recoverableTier !== "none"
|
|
? recoverableTier
|
|
: String(queuedResult?.storageTier || "none"),
|
|
});
|
|
|
|
}
|
|
|
|
export async function saveGraphToIndexedDbImpl(runtime,
|
|
chatId,
|
|
graph,
|
|
{
|
|
revision = 0,
|
|
reason = "graph-save",
|
|
persistRole = "primary",
|
|
scheduleCloudUpload: scheduleCloudUploadOption = undefined,
|
|
persistDelta = null,
|
|
graphSnapshot = null,
|
|
persistSnapshot = null,
|
|
sourceGraph = null,
|
|
} = {},
|
|
) {
|
|
const graphPersistenceState = createGraphPersistenceStateProxy(runtime);
|
|
const currentGraph = runtime.getCurrentGraph?.() || null;
|
|
const nativeHydrateInstallPromiseRef = createNativeHydrateInstallPromiseRef(runtime);
|
|
const nativePersistDeltaInstallPromiseRef = createNativePersistDeltaInstallPromiseRef(runtime);
|
|
const bmeIndexedDbLatestQueuedRevisionByChatId = runtime.bmeIndexedDbLatestQueuedRevisionByChatId;
|
|
const bmeIndexedDbWriteInFlightByChatId = runtime.bmeIndexedDbWriteInFlightByChatId;
|
|
const updateGraphPersistenceState = runtime.updateGraphPersistenceState || ((patch = {}) => runtime.setGraphPersistenceState?.({ ...(runtime.getGraphPersistenceState?.() || {}), ...(patch || {}) }));
|
|
const AUTHORITY_GRAPH_STORE_KIND = runtime.AUTHORITY_GRAPH_STORE_KIND;
|
|
const BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET = runtime.BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET;
|
|
const GRAPH_LOAD_STATES = runtime.GRAPH_LOAD_STATES;
|
|
const applyAcceptedPendingPersistState = runtime.applyAcceptedPendingPersistState;
|
|
const applyGraphLoadState = runtime.applyGraphLoadState;
|
|
const applyIndexedDbEmptyToRuntime = runtime.applyIndexedDbEmptyToRuntime;
|
|
const applyIndexedDbSnapshotToRuntime = runtime.applyIndexedDbSnapshotToRuntime;
|
|
const applyPersistDeltaToSnapshot = runtime.applyPersistDeltaToSnapshot;
|
|
const applyShadowSnapshotToRuntime = runtime.applyShadowSnapshotToRuntime;
|
|
const areChatIdsEquivalentForResolvedIdentity = runtime.areChatIdsEquivalentForResolvedIdentity;
|
|
const buildBmeSyncRuntimeOptions = runtime.buildBmeSyncRuntimeOptions;
|
|
const buildGraphLocalStoreSelectorKey = runtime.buildGraphLocalStoreSelectorKey;
|
|
const buildGraphPersistResult = runtime.buildGraphPersistResult;
|
|
const buildPersistDelta = runtime.buildPersistDelta;
|
|
const buildPersistDeltaFromGraphDirtyState = runtime.buildPersistDeltaFromGraphDirtyState;
|
|
const buildPersistObservabilitySummary = runtime.buildPersistObservabilitySummary;
|
|
const buildPersistenceEnvironment = runtime.buildPersistenceEnvironment;
|
|
const buildSnapshotFromGraph = runtime.buildSnapshotFromGraph;
|
|
const cacheIndexedDbSnapshot = runtime.cacheIndexedDbSnapshot;
|
|
const canPersistGraphToMetadataFallback = runtime.canPersistGraphToMetadataFallback;
|
|
const clearPendingGraphPersistRetry = runtime.clearPendingGraphPersistRetry;
|
|
const cloneGraphForPersistence = runtime.cloneGraphForPersistence;
|
|
const cloneRuntimeDebugValue = runtime.cloneRuntimeDebugValue;
|
|
const createShadowComparisonGraph = runtime.createShadowComparisonGraph;
|
|
const detectIndexedDbSnapshotCommitMarkerMismatch = runtime.detectIndexedDbSnapshotCommitMarkerMismatch;
|
|
const detectStaleIndexedDbSnapshotAgainstRuntime = runtime.detectStaleIndexedDbSnapshotAgainstRuntime;
|
|
const ensureBmeChatManager = runtime.ensureBmeChatManager;
|
|
const ensureCurrentGraphRuntimeState = runtime.ensureCurrentGraphRuntimeState;
|
|
const evaluateNativeHydrateGate = runtime.evaluateNativeHydrateGate;
|
|
const evaluatePersistNativeDeltaGate = runtime.evaluatePersistNativeDeltaGate;
|
|
const getChatMetadataIntegrity = runtime.getChatMetadataIntegrity;
|
|
const getContext = runtime.getContext;
|
|
const getCurrentChatId = runtime.getCurrentChatId;
|
|
const getGraphPersistedRevision = runtime.getGraphPersistedRevision;
|
|
const getPreferredGraphLocalStorePresentationSync = runtime.getPreferredGraphLocalStorePresentationSync;
|
|
const getRequestedGraphLocalStorageMode = runtime.getRequestedGraphLocalStorageMode;
|
|
const getSettings = runtime.getSettings;
|
|
const hasMeaningfulRuntimeGraphForChat = runtime.hasMeaningfulRuntimeGraphForChat;
|
|
const isAuthorityGraphStorePresentation = runtime.isAuthorityGraphStorePresentation;
|
|
const isGraphLocalStorageModeOpfs = runtime.isGraphLocalStorageModeOpfs;
|
|
const isIndexedDbSnapshotMeaningful = runtime.isIndexedDbSnapshotMeaningful;
|
|
const isRestoreLockActive = runtime.isRestoreLockActive;
|
|
const maybeCaptureGraphShadowSnapshot = runtime.maybeCaptureGraphShadowSnapshot;
|
|
const maybeClearAcceptedPendingPersistState = runtime.maybeClearAcceptedPendingPersistState;
|
|
const maybeImportLegacyIndexedDbSnapshotToLocalStore = runtime.maybeImportLegacyIndexedDbSnapshotToLocalStore;
|
|
const maybeImportLegacyOpfsSnapshotToLocalStore = runtime.maybeImportLegacyOpfsSnapshotToLocalStore;
|
|
const maybeMigrateLegacyGraphToIndexedDb = runtime.maybeMigrateLegacyGraphToIndexedDb;
|
|
const maybeRecoverIndexedDbGraphFromStableIdentity = runtime.maybeRecoverIndexedDbGraphFromStableIdentity;
|
|
const maybeResolveOrphanAcceptedCommitMarker = runtime.maybeResolveOrphanAcceptedCommitMarker;
|
|
const maybeResumePendingAutoExtraction = runtime.maybeResumePendingAutoExtraction;
|
|
const normalizeChatIdCandidate = runtime.normalizeChatIdCandidate;
|
|
const normalizeGraphRuntimeState = runtime.normalizeGraphRuntimeState;
|
|
const normalizeIndexedDbRevision = runtime.normalizeIndexedDbRevision;
|
|
const normalizeLoadDiagnosticsMs = runtime.normalizeLoadDiagnosticsMs;
|
|
const normalizePersistDeltaDiagnosticsMs = runtime.normalizePersistDeltaDiagnosticsMs;
|
|
const persistGraphToChatMetadata = runtime.persistGraphToChatMetadata;
|
|
const persistGraphToConfiguredDurableTier = runtime.persistGraphToConfiguredDurableTier;
|
|
const pruneGraphPersistDirtyState = runtime.pruneGraphPersistDirtyState;
|
|
const queueGraphPersist = runtime.queueGraphPersist;
|
|
const queueRuntimeGraphLocalStoreRepair = runtime.queueRuntimeGraphLocalStoreRepair;
|
|
const readCachedIndexedDbSnapshot = runtime.readCachedIndexedDbSnapshot;
|
|
const readLoadDiagnosticsNow = runtime.readLoadDiagnosticsNow;
|
|
const readLocalStoreDiagnosticsSync = runtime.readLocalStoreDiagnosticsSync;
|
|
const readPersistDeltaDiagnosticsNow = runtime.readPersistDeltaDiagnosticsNow;
|
|
const recordLocalPersistEarlyFailure = runtime.recordLocalPersistEarlyFailure;
|
|
const recordPersistMismatchDiagnostic = runtime.recordPersistMismatchDiagnostic;
|
|
const refreshCurrentChatLocalStoreBinding = runtime.refreshCurrentChatLocalStoreBinding;
|
|
const rememberResolvedGraphIdentityAlias = runtime.rememberResolvedGraphIdentityAlias;
|
|
const resolveCompatibleGraphShadowSnapshot = runtime.resolveCompatibleGraphShadowSnapshot;
|
|
const resolveCurrentChatIdentity = runtime.resolveCurrentChatIdentity;
|
|
const resolveDbGraphStorePresentation = runtime.resolveDbGraphStorePresentation;
|
|
const resolveLocalStoreTierFromPresentation = runtime.resolveLocalStoreTierFromPresentation;
|
|
const resolvePendingPersistGraphSource = runtime.resolvePendingPersistGraphSource;
|
|
const resolvePendingPersistLastProcessedAssistantFloor = runtime.resolvePendingPersistLastProcessedAssistantFloor;
|
|
const resolvePersistRevisionFloor = runtime.resolvePersistRevisionFloor;
|
|
const resolveSnapshotGraphStorePresentation = runtime.resolveSnapshotGraphStorePresentation;
|
|
const schedulePendingGraphPersistRetry = runtime.schedulePendingGraphPersistRetry;
|
|
const scheduleUpload = runtime.scheduleUpload;
|
|
const shouldPreferShadowSnapshotOverOfficial = runtime.shouldPreferShadowSnapshotOverOfficial;
|
|
const stampGraphPersistenceMeta = runtime.stampGraphPersistenceMeta;
|
|
const syncCommitMarkerToPersistenceState = runtime.syncCommitMarkerToPersistenceState;
|
|
const updateLoadDiagnostics = runtime.updateLoadDiagnostics;
|
|
const updatePersistDeltaDiagnostics = runtime.updatePersistDeltaDiagnostics;
|
|
const console = runtime.console || globalThis.console;
|
|
|
|
const normalizedChatId = normalizeChatIdCandidate(chatId);
|
|
if (!normalizedChatId || (!graph && !persistDelta)) {
|
|
recordLocalPersistEarlyFailure("indexeddb-missing-chat-graph-or-delta", {
|
|
chatId: normalizedChatId,
|
|
revision,
|
|
});
|
|
return {
|
|
saved: false,
|
|
chatId: normalizedChatId,
|
|
reason: "indexeddb-missing-chat-graph-or-delta",
|
|
revision: normalizeIndexedDbRevision(revision),
|
|
};
|
|
}
|
|
|
|
const context = getContext();
|
|
let db = null;
|
|
let localStore = getPreferredGraphLocalStorePresentationSync();
|
|
try {
|
|
const manager = ensureBmeChatManager();
|
|
if (!manager) {
|
|
recordLocalPersistEarlyFailure("indexeddb-manager-unavailable", {
|
|
chatId: normalizedChatId,
|
|
revision,
|
|
});
|
|
return {
|
|
saved: false,
|
|
chatId: normalizedChatId,
|
|
reason: "indexeddb-manager-unavailable",
|
|
revision: normalizeIndexedDbRevision(revision),
|
|
};
|
|
}
|
|
db = await manager.getCurrentDb(normalizedChatId);
|
|
if (!db) {
|
|
recordLocalPersistEarlyFailure("indexeddb-db-unavailable", {
|
|
chatId: normalizedChatId,
|
|
revision,
|
|
});
|
|
return {
|
|
saved: false,
|
|
chatId: normalizedChatId,
|
|
reason: "indexeddb-db-unavailable",
|
|
revision: normalizeIndexedDbRevision(revision),
|
|
};
|
|
}
|
|
localStore = resolveDbGraphStorePresentation(db);
|
|
const persistenceEnvironment = buildPersistenceEnvironment(context, localStore);
|
|
const localStoreTier = resolveLocalStoreTierFromPresentation(localStore);
|
|
const currentIdentity = resolveCurrentChatIdentity(context);
|
|
const requestedRevision = resolvePersistRevisionFloor(revision, graph);
|
|
const currentSettings = getSettings();
|
|
const shouldScheduleCloudUpload =
|
|
scheduleCloudUploadOption != null
|
|
? scheduleCloudUploadOption === true
|
|
: persistenceEnvironment.primaryStorageTier !== "authority-sql" &&
|
|
persistenceEnvironment.hostProfile !== "luker" &&
|
|
persistRole !== "cache-mirror";
|
|
const directPersistDelta =
|
|
persistDelta &&
|
|
typeof persistDelta === "object" &&
|
|
!Array.isArray(persistDelta)
|
|
? cloneRuntimeDebugValue(persistDelta, persistDelta)
|
|
: null;
|
|
const detachedGraphSnapshot =
|
|
graphSnapshot &&
|
|
typeof graphSnapshot === "object" &&
|
|
!Array.isArray(graphSnapshot)
|
|
? graphSnapshot
|
|
: null;
|
|
const prebuiltPersistSnapshot =
|
|
persistSnapshot &&
|
|
typeof persistSnapshot === "object" &&
|
|
!Array.isArray(persistSnapshot)
|
|
? persistSnapshot
|
|
: null;
|
|
const sourceGraphInput =
|
|
sourceGraph && typeof sourceGraph === "object" && !Array.isArray(sourceGraph)
|
|
? sourceGraph
|
|
: null;
|
|
const persistGraphInput = detachedGraphSnapshot || graph;
|
|
let baseSnapshot = null;
|
|
let snapshot = prebuiltPersistSnapshot;
|
|
let delta = directPersistDelta;
|
|
let persistDeltaBuildDiagnostics = null;
|
|
let dirtyPersistDeltaVersion = 0;
|
|
let dirtyPersistUsed = false;
|
|
let nativePersistModuleStatus = null;
|
|
let nativePersistPreloadStatus = "not-requested";
|
|
let nativePersistPreloadError = "";
|
|
let nativePersistPreloadMs = 0;
|
|
let baseSnapshotReadMs = 0;
|
|
let graphSnapshotBuildMs = 0;
|
|
let snapshotBuildDiagnostics = null;
|
|
const persistDeltaStartedAt = readPersistDeltaDiagnosticsNow();
|
|
|
|
if (!delta) {
|
|
const baseSnapshotReadStartedAt = readPersistDeltaDiagnosticsNow();
|
|
baseSnapshot = readCachedIndexedDbSnapshot(normalizedChatId, localStore);
|
|
if (!baseSnapshot) {
|
|
baseSnapshot = await db.exportSnapshot();
|
|
}
|
|
baseSnapshotReadMs =
|
|
readPersistDeltaDiagnosticsNow() - baseSnapshotReadStartedAt;
|
|
if (persistGraphInput) {
|
|
delta = buildPersistDeltaFromGraphDirtyState(baseSnapshot, persistGraphInput, {
|
|
chatId: normalizedChatId,
|
|
revision: requestedRevision,
|
|
lastModified: Date.now(),
|
|
meta: {
|
|
storagePrimary: localStore.storagePrimary,
|
|
storageMode: localStore.storageMode,
|
|
lastMutationReason: String(reason || "graph-save"),
|
|
integrity:
|
|
currentIdentity.integrity || graphPersistenceState.metadataIntegrity,
|
|
hostChatId: currentIdentity.hostChatId || "",
|
|
},
|
|
onDiagnostics(snapshotValue) {
|
|
persistDeltaBuildDiagnostics =
|
|
snapshotValue &&
|
|
typeof snapshotValue === "object" &&
|
|
!Array.isArray(snapshotValue)
|
|
? snapshotValue
|
|
: null;
|
|
},
|
|
});
|
|
dirtyPersistUsed = Boolean(delta);
|
|
dirtyPersistDeltaVersion = Math.max(
|
|
0,
|
|
Math.floor(Number(persistDeltaBuildDiagnostics?.dirtyStateVersion || 0)),
|
|
);
|
|
if (dirtyPersistUsed) {
|
|
snapshot = applyPersistDeltaToSnapshot(baseSnapshot, delta, {
|
|
chatId: normalizedChatId,
|
|
revision: requestedRevision,
|
|
lastModified: Date.now(),
|
|
reason: String(reason || "graph-save"),
|
|
});
|
|
}
|
|
}
|
|
if (!snapshot) {
|
|
const graphSnapshotBuildStartedAt = readPersistDeltaDiagnosticsNow();
|
|
snapshot = buildSnapshotFromGraph(persistGraphInput, {
|
|
chatId: normalizedChatId,
|
|
revision: requestedRevision,
|
|
baseSnapshot,
|
|
lastModified: Date.now(),
|
|
meta: {
|
|
storagePrimary: localStore.storagePrimary,
|
|
storageMode: localStore.storageMode,
|
|
lastMutationReason: String(reason || "graph-save"),
|
|
integrity:
|
|
currentIdentity.integrity || graphPersistenceState.metadataIntegrity,
|
|
hostChatId: currentIdentity.hostChatId || "",
|
|
},
|
|
onDiagnostics(snapshotValue) {
|
|
snapshotBuildDiagnostics =
|
|
snapshotValue &&
|
|
typeof snapshotValue === "object" &&
|
|
!Array.isArray(snapshotValue)
|
|
? snapshotValue
|
|
: null;
|
|
},
|
|
});
|
|
graphSnapshotBuildMs =
|
|
readPersistDeltaDiagnosticsNow() - graphSnapshotBuildStartedAt;
|
|
}
|
|
}
|
|
const nativePersistBridgeMode = String(
|
|
currentSettings.persistNativeDeltaBridgeMode || "json",
|
|
);
|
|
const nativePersistRequested =
|
|
!directPersistDelta && !dirtyPersistUsed && currentSettings.persistUseNativeDelta === true;
|
|
const nativePersistForceDisabled = currentSettings.graphNativeForceDisable === true;
|
|
const nativePersistGate =
|
|
!delta && baseSnapshot && snapshot
|
|
? evaluatePersistNativeDeltaGate(baseSnapshot, snapshot, currentSettings)
|
|
: {
|
|
allowed: false,
|
|
reasons: [
|
|
directPersistDelta
|
|
? "direct-delta"
|
|
: dirtyPersistUsed
|
|
? "dirty-runtime"
|
|
: "delta-prebuilt",
|
|
],
|
|
minSnapshotRecords: Number(
|
|
currentSettings.persistNativeDeltaThresholdRecords || 0,
|
|
),
|
|
minStructuralDelta: Number(
|
|
currentSettings.persistNativeDeltaThresholdStructuralDelta || 0,
|
|
),
|
|
minCombinedSerializedChars: Number(
|
|
currentSettings.persistNativeDeltaThresholdSerializedChars || 0,
|
|
),
|
|
beforeRecordCount: 0,
|
|
afterRecordCount: 0,
|
|
maxSnapshotRecords: 0,
|
|
structuralDelta: 0,
|
|
};
|
|
const shouldUseNativePersistDelta =
|
|
nativePersistRequested &&
|
|
nativePersistForceDisabled !== true &&
|
|
nativePersistGate.allowed;
|
|
if (!directPersistDelta) {
|
|
nativePersistPreloadStatus = nativePersistRequested
|
|
? nativePersistForceDisabled
|
|
? "force-disabled"
|
|
: nativePersistGate.allowed
|
|
? "pending"
|
|
: "gated-out"
|
|
: "not-requested";
|
|
}
|
|
updatePersistDeltaDiagnostics({
|
|
chatId: normalizedChatId,
|
|
saveReason: String(reason || "graph-save"),
|
|
requestedRevision,
|
|
requestedNative: nativePersistRequested,
|
|
requestedBridgeMode: directPersistDelta
|
|
? "direct-delta"
|
|
: dirtyPersistUsed
|
|
? "dirty-runtime"
|
|
: nativePersistBridgeMode,
|
|
nativeForceDisabled: nativePersistForceDisabled,
|
|
nativeFailOpen: currentSettings.nativeEngineFailOpen !== false,
|
|
gateAllowed: directPersistDelta || dirtyPersistUsed ? true : nativePersistGate.allowed,
|
|
gateReasons: cloneRuntimeDebugValue(
|
|
directPersistDelta
|
|
? ["direct-delta"]
|
|
: dirtyPersistUsed
|
|
? ["dirty-runtime"]
|
|
: nativePersistGate.reasons,
|
|
[],
|
|
),
|
|
preloadGateAllowed:
|
|
directPersistDelta || dirtyPersistUsed ? true : nativePersistGate.allowed,
|
|
preloadGateReasons: cloneRuntimeDebugValue(
|
|
directPersistDelta
|
|
? ["direct-delta"]
|
|
: dirtyPersistUsed
|
|
? ["dirty-runtime"]
|
|
: nativePersistGate.reasons,
|
|
[],
|
|
),
|
|
minSnapshotRecords: nativePersistGate.minSnapshotRecords,
|
|
minStructuralDelta: nativePersistGate.minStructuralDelta,
|
|
minCombinedSerializedChars: nativePersistGate.minCombinedSerializedChars,
|
|
beforeRecordCount: nativePersistGate.beforeRecordCount,
|
|
afterRecordCount: nativePersistGate.afterRecordCount,
|
|
maxSnapshotRecords: nativePersistGate.maxSnapshotRecords,
|
|
structuralDelta: nativePersistGate.structuralDelta,
|
|
preloadStatus: nativePersistPreloadStatus,
|
|
preloadMs: 0,
|
|
preloadError: "",
|
|
status: "building",
|
|
path: directPersistDelta
|
|
? "direct-delta"
|
|
: dirtyPersistUsed
|
|
? "dirty-runtime"
|
|
: undefined,
|
|
});
|
|
if (!directPersistDelta && shouldUseNativePersistDelta) {
|
|
const preloadStartedAt = readPersistDeltaDiagnosticsNow();
|
|
try {
|
|
if (!nativePersistDeltaInstallPromiseRef.value) {
|
|
nativePersistDeltaInstallPromiseRef.value = importNativeCore(runtime)
|
|
.then((module) => module?.installNativePersistDeltaHook?.())
|
|
.catch((error) => {
|
|
nativePersistDeltaInstallPromiseRef.value = null;
|
|
throw error;
|
|
});
|
|
}
|
|
nativePersistModuleStatus = await nativePersistDeltaInstallPromiseRef.value;
|
|
nativePersistPreloadStatus = nativePersistModuleStatus?.loaded
|
|
? "loaded"
|
|
: "not-loaded";
|
|
nativePersistPreloadMs =
|
|
readPersistDeltaDiagnosticsNow() - preloadStartedAt;
|
|
} catch (error) {
|
|
nativePersistPreloadStatus = "failed";
|
|
nativePersistPreloadMs =
|
|
readPersistDeltaDiagnosticsNow() - preloadStartedAt;
|
|
nativePersistPreloadError = error?.message || String(error);
|
|
if (currentSettings.nativeEngineFailOpen !== false) {
|
|
console.warn(
|
|
"[ST-BME] native persist delta preload failed, fallback to JS delta:",
|
|
error,
|
|
);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
if (!delta) {
|
|
delta = buildPersistDelta(baseSnapshot, snapshot, {
|
|
useNativeDelta: shouldUseNativePersistDelta,
|
|
nativeFailOpen: currentSettings.nativeEngineFailOpen !== false,
|
|
persistNativeDeltaThresholdRecords:
|
|
currentSettings.persistNativeDeltaThresholdRecords,
|
|
persistNativeDeltaThresholdStructuralDelta:
|
|
currentSettings.persistNativeDeltaThresholdStructuralDelta,
|
|
persistNativeDeltaThresholdSerializedChars:
|
|
currentSettings.persistNativeDeltaThresholdSerializedChars,
|
|
persistNativeDeltaBridgeMode: nativePersistBridgeMode,
|
|
onDiagnostics(snapshotValue) {
|
|
persistDeltaBuildDiagnostics = snapshotValue;
|
|
},
|
|
});
|
|
} else if (!persistDeltaBuildDiagnostics) {
|
|
persistDeltaBuildDiagnostics = {
|
|
requestedNative: false,
|
|
requestedBridgeMode: directPersistDelta
|
|
? "direct-delta"
|
|
: dirtyPersistUsed
|
|
? "dirty-runtime"
|
|
: "prebuilt-delta",
|
|
usedNative: false,
|
|
path: directPersistDelta
|
|
? "direct-delta"
|
|
: dirtyPersistUsed
|
|
? "dirty-runtime"
|
|
: "prebuilt-delta",
|
|
gateAllowed: true,
|
|
gateReasons: [
|
|
directPersistDelta
|
|
? "direct-delta"
|
|
: dirtyPersistUsed
|
|
? "dirty-runtime"
|
|
: "prebuilt-delta",
|
|
],
|
|
nativeAttemptStatus: "not-requested",
|
|
nativeError: "",
|
|
beforeRecordCount: Number(
|
|
delta?.countDelta?.previous?.nodes || 0,
|
|
) + Number(delta?.countDelta?.previous?.edges || 0),
|
|
afterRecordCount: Number(
|
|
delta?.countDelta?.next?.nodes || 0,
|
|
) + Number(delta?.countDelta?.next?.edges || 0),
|
|
maxSnapshotRecords: Math.max(
|
|
Number(delta?.countDelta?.previous?.nodes || 0) +
|
|
Number(delta?.countDelta?.previous?.edges || 0),
|
|
Number(delta?.countDelta?.next?.nodes || 0) +
|
|
Number(delta?.countDelta?.next?.edges || 0),
|
|
),
|
|
structuralDelta:
|
|
Number(delta?.upsertNodes?.length || 0) +
|
|
Number(delta?.upsertEdges?.length || 0) +
|
|
Number(delta?.deleteNodeIds?.length || 0) +
|
|
Number(delta?.deleteEdgeIds?.length || 0),
|
|
beforeSerializedChars: 0,
|
|
afterSerializedChars: 0,
|
|
combinedSerializedChars: 0,
|
|
prepareMs: 0,
|
|
nativeAttemptMs: 0,
|
|
lookupMs: 0,
|
|
jsDiffMs: 0,
|
|
hydrateMs: 0,
|
|
serializationCacheObjectHits: 0,
|
|
serializationCacheTokenHits: 0,
|
|
serializationCacheMisses: 0,
|
|
serializationCacheHits: 0,
|
|
preparedRecordSetCacheHits: 0,
|
|
preparedRecordSetCacheMisses: 0,
|
|
minCombinedSerializedChars: 0,
|
|
upsertNodeCount: Number(delta?.upsertNodes?.length || 0),
|
|
upsertEdgeCount: Number(delta?.upsertEdges?.length || 0),
|
|
deleteNodeCount: Number(delta?.deleteNodeIds?.length || 0),
|
|
deleteEdgeCount: Number(delta?.deleteEdgeIds?.length || 0),
|
|
tombstoneCount: Number(delta?.tombstones?.length || 0),
|
|
dirtyStateVersion: dirtyPersistDeltaVersion,
|
|
};
|
|
}
|
|
const commitResult = await db.commitDelta(delta, {
|
|
reason,
|
|
requestedRevision,
|
|
markSyncDirty: true,
|
|
committedSnapshot: snapshot,
|
|
});
|
|
const commitDiagnostics =
|
|
commitResult?.diagnostics &&
|
|
typeof commitResult.diagnostics === "object" &&
|
|
!Array.isArray(commitResult.diagnostics)
|
|
? cloneRuntimeDebugValue(commitResult.diagnostics, {})
|
|
: null;
|
|
const committedRevision = normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
);
|
|
const committedLastModified = Number(commitResult?.lastModified || Date.now());
|
|
|
|
let scheduleUploadWarning = "";
|
|
if (persistGraphInput) {
|
|
if (!snapshot) {
|
|
const graphSnapshotBuildStartedAt = readPersistDeltaDiagnosticsNow();
|
|
snapshot = buildSnapshotFromGraph(persistGraphInput, {
|
|
chatId: normalizedChatId,
|
|
revision: committedRevision,
|
|
baseSnapshot: baseSnapshot || undefined,
|
|
lastModified: committedLastModified,
|
|
meta: {
|
|
storagePrimary: localStore.storagePrimary,
|
|
storageMode: localStore.storageMode,
|
|
lastMutationReason: String(reason || "graph-save"),
|
|
integrity:
|
|
currentIdentity.integrity || graphPersistenceState.metadataIntegrity,
|
|
hostChatId: currentIdentity.hostChatId || "",
|
|
},
|
|
onDiagnostics(snapshotValue) {
|
|
snapshotBuildDiagnostics =
|
|
snapshotValue &&
|
|
typeof snapshotValue === "object" &&
|
|
!Array.isArray(snapshotValue)
|
|
? snapshotValue
|
|
: null;
|
|
},
|
|
});
|
|
graphSnapshotBuildMs +=
|
|
readPersistDeltaDiagnosticsNow() - graphSnapshotBuildStartedAt;
|
|
}
|
|
if (!snapshot.meta || typeof snapshot.meta !== "object" || Array.isArray(snapshot.meta)) {
|
|
snapshot.meta = {};
|
|
}
|
|
snapshot.meta.revision = committedRevision;
|
|
snapshot.meta.lastModified = committedLastModified;
|
|
snapshot.meta.lastMutationReason = String(reason || "graph-save");
|
|
snapshot.meta.storagePrimary = localStore.storagePrimary;
|
|
snapshot.meta.storageMode = localStore.storageMode;
|
|
if (localStore.storagePrimary !== AUTHORITY_GRAPH_STORE_KIND) {
|
|
cacheIndexedDbSnapshot(normalizedChatId, snapshot);
|
|
}
|
|
}
|
|
|
|
if (dirtyPersistDeltaVersion > 0) {
|
|
pruneGraphPersistDirtyState(graph, dirtyPersistDeltaVersion);
|
|
if (sourceGraphInput && sourceGraphInput !== graph) {
|
|
pruneGraphPersistDirtyState(sourceGraphInput, dirtyPersistDeltaVersion);
|
|
}
|
|
}
|
|
|
|
if (graph === currentGraph) {
|
|
stampGraphPersistenceMeta(currentGraph, {
|
|
revision: committedRevision,
|
|
reason: String(reason || "graph-save"),
|
|
chatId: normalizedChatId,
|
|
integrity:
|
|
currentIdentity.integrity ||
|
|
getChatMetadataIntegrity(context) ||
|
|
graphPersistenceState.metadataIntegrity,
|
|
});
|
|
}
|
|
|
|
if (shouldScheduleCloudUpload) {
|
|
try {
|
|
scheduleUpload(
|
|
normalizedChatId,
|
|
buildBmeSyncRuntimeOptions({
|
|
trigger: `graph-mutation:${String(reason || "graph-save")}`,
|
|
}),
|
|
);
|
|
} catch (error) {
|
|
scheduleUploadWarning =
|
|
error?.message || String(error) || "schedule-upload-failed";
|
|
console.warn(
|
|
`[ST-BME] ${localStore.statusLabel} 已写入,但同步上传调度失败:`,
|
|
error,
|
|
);
|
|
}
|
|
}
|
|
|
|
const persistTotalMs = readPersistDeltaDiagnosticsNow() - persistDeltaStartedAt;
|
|
const persistAccountedMs =
|
|
Number(nativePersistPreloadMs || 0) +
|
|
Number(baseSnapshotReadMs || 0) +
|
|
Number(graphSnapshotBuildMs || 0) +
|
|
Number(persistDeltaBuildDiagnostics?.buildMs || 0) +
|
|
Number(commitDiagnostics?.queueWaitMs || 0) +
|
|
Number(commitDiagnostics?.commitMs || 0);
|
|
const persistDeltaDiagnostics = {
|
|
...cloneRuntimeDebugValue(persistDeltaBuildDiagnostics, {}),
|
|
chatId: normalizedChatId,
|
|
saveReason: String(reason || "graph-save"),
|
|
requestedRevision,
|
|
requestedNative: nativePersistRequested,
|
|
requestedBridgeMode:
|
|
persistDeltaBuildDiagnostics?.requestedBridgeMode ||
|
|
(directPersistDelta ? "direct-delta" : nativePersistBridgeMode),
|
|
buildRequestedNative: Boolean(persistDeltaBuildDiagnostics?.requestedNative),
|
|
nativeForceDisabled: nativePersistForceDisabled,
|
|
nativeFailOpen: currentSettings.nativeEngineFailOpen !== false,
|
|
gateAllowed:
|
|
persistDeltaBuildDiagnostics?.gateAllowed ??
|
|
(directPersistDelta ? true : nativePersistGate.allowed),
|
|
gateReasons: cloneRuntimeDebugValue(
|
|
persistDeltaBuildDiagnostics?.gateReasons,
|
|
directPersistDelta ? ["direct-delta"] : nativePersistGate.reasons,
|
|
),
|
|
preloadGateAllowed: directPersistDelta ? true : nativePersistGate.allowed,
|
|
preloadGateReasons: cloneRuntimeDebugValue(
|
|
directPersistDelta ? ["direct-delta"] : nativePersistGate.reasons,
|
|
[],
|
|
),
|
|
minSnapshotRecords: nativePersistGate.minSnapshotRecords,
|
|
minStructuralDelta: nativePersistGate.minStructuralDelta,
|
|
minCombinedSerializedChars:
|
|
persistDeltaBuildDiagnostics?.minCombinedSerializedChars ??
|
|
nativePersistGate.minCombinedSerializedChars,
|
|
beforeRecordCount:
|
|
persistDeltaBuildDiagnostics?.beforeRecordCount ??
|
|
nativePersistGate.beforeRecordCount,
|
|
afterRecordCount:
|
|
persistDeltaBuildDiagnostics?.afterRecordCount ??
|
|
nativePersistGate.afterRecordCount,
|
|
maxSnapshotRecords:
|
|
persistDeltaBuildDiagnostics?.maxSnapshotRecords ??
|
|
nativePersistGate.maxSnapshotRecords,
|
|
structuralDelta:
|
|
persistDeltaBuildDiagnostics?.structuralDelta ??
|
|
nativePersistGate.structuralDelta,
|
|
preloadStatus: nativePersistPreloadStatus,
|
|
preloadMs: nativePersistPreloadMs,
|
|
preloadError: nativePersistPreloadError,
|
|
moduleLoaded: Boolean(nativePersistModuleStatus?.loaded),
|
|
moduleSource: String(nativePersistModuleStatus?.source || ""),
|
|
moduleError: String(
|
|
nativePersistModuleStatus?.error || nativePersistPreloadError || "",
|
|
),
|
|
baseSnapshotReadMs: normalizePersistDeltaDiagnosticsMs(baseSnapshotReadMs),
|
|
snapshotBuildMs: normalizePersistDeltaDiagnosticsMs(graphSnapshotBuildMs),
|
|
snapshotNodesMs: normalizePersistDeltaDiagnosticsMs(
|
|
snapshotBuildDiagnostics?.nodesMs,
|
|
),
|
|
snapshotEdgesMs: normalizePersistDeltaDiagnosticsMs(
|
|
snapshotBuildDiagnostics?.edgesMs,
|
|
),
|
|
snapshotTombstonesMs: normalizePersistDeltaDiagnosticsMs(
|
|
snapshotBuildDiagnostics?.tombstonesMs,
|
|
),
|
|
snapshotStateMs: normalizePersistDeltaDiagnosticsMs(
|
|
snapshotBuildDiagnostics?.stateMs,
|
|
),
|
|
snapshotMetaMs: normalizePersistDeltaDiagnosticsMs(
|
|
snapshotBuildDiagnostics?.metaMs,
|
|
),
|
|
snapshotNodeCount: Math.max(
|
|
0,
|
|
Math.floor(Number(snapshotBuildDiagnostics?.nodeCount || 0)),
|
|
),
|
|
snapshotEdgeCount: Math.max(
|
|
0,
|
|
Math.floor(Number(snapshotBuildDiagnostics?.edgeCount || 0)),
|
|
),
|
|
snapshotTombstoneCount: Math.max(
|
|
0,
|
|
Math.floor(Number(snapshotBuildDiagnostics?.tombstoneCount || 0)),
|
|
),
|
|
commitStorageKind: String(
|
|
commitDiagnostics?.storageKind || localStore.storagePrimary || "",
|
|
),
|
|
commitStoreMode: String(
|
|
commitDiagnostics?.storeMode || localStore.storageMode || "",
|
|
),
|
|
commitQueueWaitMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.queueWaitMs,
|
|
),
|
|
commitMs: normalizePersistDeltaDiagnosticsMs(commitDiagnostics?.commitMs),
|
|
commitTxMs: normalizePersistDeltaDiagnosticsMs(commitDiagnostics?.txMs),
|
|
commitSnapshotReadMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.snapshotReadMs,
|
|
),
|
|
commitSnapshotWriteMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.snapshotWriteMs,
|
|
),
|
|
commitManifestReadMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.manifestReadMs,
|
|
),
|
|
commitWalSerializeMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.walSerializeMs,
|
|
),
|
|
commitWalFileWriteMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.walFileWriteMs,
|
|
),
|
|
commitWalWriteMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.walWriteMs,
|
|
),
|
|
commitManifestSerializeMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.manifestSerializeMs,
|
|
),
|
|
commitManifestFileWriteMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.manifestFileWriteMs,
|
|
),
|
|
commitManifestWriteMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.manifestWriteMs,
|
|
),
|
|
commitCacheApplyMs: normalizePersistDeltaDiagnosticsMs(
|
|
commitDiagnostics?.cacheApplyMs,
|
|
),
|
|
commitPayloadBytes: Math.max(
|
|
0,
|
|
Math.floor(Number(commitDiagnostics?.payloadBytes || 0)),
|
|
),
|
|
commitWalBytes: Math.max(
|
|
0,
|
|
Math.floor(Number(commitDiagnostics?.walBytes || 0)),
|
|
),
|
|
commitRuntimeMetaKeyCount: Math.max(
|
|
0,
|
|
Math.floor(Number(commitDiagnostics?.runtimeMetaKeyCount || 0)),
|
|
),
|
|
status: "committed",
|
|
commitRevision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
commitDelta: cloneRuntimeDebugValue(commitResult?.delta, null),
|
|
totalMs: normalizePersistDeltaDiagnosticsMs(persistTotalMs),
|
|
untrackedMs: normalizePersistDeltaDiagnosticsMs(
|
|
Math.max(0, persistTotalMs - persistAccountedMs),
|
|
),
|
|
};
|
|
persistDeltaDiagnostics.fallbackReason =
|
|
persistDeltaDiagnostics.requestedNative && !persistDeltaDiagnostics.usedNative
|
|
? String(
|
|
(persistDeltaDiagnostics.preloadStatus !== "loaded" &&
|
|
persistDeltaDiagnostics.preloadStatus !== "pending"
|
|
? persistDeltaDiagnostics.preloadStatus
|
|
: persistDeltaDiagnostics.nativeAttemptStatus) ||
|
|
"js",
|
|
)
|
|
: "";
|
|
const persistObservability = buildPersistObservabilitySummary(
|
|
persistDeltaDiagnostics,
|
|
);
|
|
persistDeltaDiagnostics.pathKey = String(
|
|
persistObservability?.lastPathKey || "unknown",
|
|
);
|
|
persistDeltaDiagnostics.reasonKey = String(
|
|
persistObservability?.lastReasonKey || "graph-save",
|
|
);
|
|
persistDeltaDiagnostics.pathReasonKey = String(
|
|
persistObservability?.lastPathReasonKey || "unknown::graph-save",
|
|
);
|
|
persistDeltaDiagnostics.pathSampleCount = Math.max(
|
|
0,
|
|
Math.floor(
|
|
Number(
|
|
persistObservability?.byPath?.[persistDeltaDiagnostics.pathKey]?.count || 0,
|
|
),
|
|
),
|
|
);
|
|
persistDeltaDiagnostics.reasonSampleCount = Math.max(
|
|
0,
|
|
Math.floor(
|
|
Number(
|
|
persistObservability?.byReason?.[persistDeltaDiagnostics.reasonKey]?.count || 0,
|
|
),
|
|
),
|
|
);
|
|
|
|
const opfsWriteLockState =
|
|
typeof db?.getWriteLockSnapshot === "function"
|
|
? cloneRuntimeDebugValue(db.getWriteLockSnapshot(), null)
|
|
: null;
|
|
const localStoreDiagnostics =
|
|
typeof readLocalStoreDiagnosticsSync === "function"
|
|
? readLocalStoreDiagnosticsSync(db, localStore)
|
|
: {
|
|
resolvedLocalStore: `${localStore?.storagePrimary || "indexeddb"}:${localStore?.storageMode || "indexeddb"}`,
|
|
localStoreFormatVersion:
|
|
localStore.storagePrimary === "opfs" ? 2 : 1,
|
|
localStoreMigrationState: "idle",
|
|
opfsWalDepth: 0,
|
|
opfsPendingBytes: 0,
|
|
opfsCompactionState: null,
|
|
};
|
|
|
|
if (persistRole === "cache-mirror") {
|
|
updateGraphPersistenceState({
|
|
hostProfile: persistenceEnvironment.hostProfile,
|
|
primaryStorageTier: persistenceEnvironment.primaryStorageTier,
|
|
cacheStorageTier: persistenceEnvironment.cacheStorageTier,
|
|
cacheMirrorState: "saved",
|
|
cacheLag: Math.max(
|
|
0,
|
|
Number(graphPersistenceState.lukerManifestRevision || 0) -
|
|
normalizeIndexedDbRevision(commitResult?.revision, requestedRevision),
|
|
),
|
|
storagePrimary: localStore.storagePrimary,
|
|
storageMode: localStore.storageMode,
|
|
resolvedLocalStore: localStoreDiagnostics.resolvedLocalStore,
|
|
localStoreFormatVersion: localStoreDiagnostics.localStoreFormatVersion,
|
|
localStoreMigrationState: localStoreDiagnostics.localStoreMigrationState,
|
|
indexedDbRevision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
indexedDbLastError: "",
|
|
lastSyncError: scheduleUploadWarning,
|
|
opfsWriteLockState,
|
|
opfsWalDepth: localStoreDiagnostics.opfsWalDepth,
|
|
opfsPendingBytes: localStoreDiagnostics.opfsPendingBytes,
|
|
opfsCompactionState: localStoreDiagnostics.opfsCompactionState,
|
|
persistObservability,
|
|
dualWriteLastResult: {
|
|
action: "cache-mirror",
|
|
target: localStore.storagePrimary,
|
|
success: true,
|
|
chatId: normalizedChatId,
|
|
revision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
reason: String(reason || "graph-save"),
|
|
warning: scheduleUploadWarning || "",
|
|
delta: cloneRuntimeDebugValue(commitResult?.delta, null),
|
|
at: Date.now(),
|
|
},
|
|
persistDelta: persistDeltaDiagnostics,
|
|
});
|
|
return {
|
|
saved: true,
|
|
accepted: false,
|
|
mirrored: true,
|
|
chatId: normalizedChatId,
|
|
revision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
reason: String(reason || "graph-save"),
|
|
saveMode: `${localStore.reasonPrefix}-cache-mirror`,
|
|
storageTier: localStoreTier,
|
|
warning: scheduleUploadWarning || "",
|
|
delta: cloneRuntimeDebugValue(commitResult?.delta, null),
|
|
snapshot,
|
|
};
|
|
}
|
|
|
|
updateGraphPersistenceState({
|
|
hostProfile: persistenceEnvironment.hostProfile,
|
|
primaryStorageTier: persistenceEnvironment.primaryStorageTier,
|
|
cacheStorageTier: persistenceEnvironment.cacheStorageTier,
|
|
cacheMirrorState:
|
|
persistenceEnvironment.hostProfile === "luker" ? "idle" : "none",
|
|
cacheLag:
|
|
persistenceEnvironment.hostProfile === "luker"
|
|
? Math.max(
|
|
0,
|
|
Number(graphPersistenceState.lukerManifestRevision || 0) -
|
|
normalizeIndexedDbRevision(commitResult?.revision, requestedRevision),
|
|
)
|
|
: Number(graphPersistenceState.cacheLag || 0),
|
|
revision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
storagePrimary: localStore.storagePrimary,
|
|
storageMode: localStore.storageMode,
|
|
resolvedLocalStore: localStoreDiagnostics.resolvedLocalStore,
|
|
localStoreFormatVersion: localStoreDiagnostics.localStoreFormatVersion,
|
|
localStoreMigrationState: localStoreDiagnostics.localStoreMigrationState,
|
|
dbReady: true,
|
|
lastPersistedRevision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
pendingPersist: false,
|
|
queuedPersistRevision: 0,
|
|
queuedPersistChatId: "",
|
|
queuedPersistMode: "",
|
|
queuedPersistRotateIntegrity: false,
|
|
queuedPersistReason: "",
|
|
indexedDbRevision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
metadataIntegrity:
|
|
getChatMetadataIntegrity(context) ||
|
|
currentIdentity.integrity ||
|
|
graphPersistenceState.metadataIntegrity,
|
|
indexedDbLastError: "",
|
|
lastSyncError: scheduleUploadWarning,
|
|
syncDirty: true,
|
|
syncDirtyReason: String(reason || "graph-save"),
|
|
lastPersistReason: String(reason || "graph-save"),
|
|
lastPersistMode: directPersistDelta
|
|
? `${localStore.reasonPrefix}-direct-delta`
|
|
: `${localStore.reasonPrefix}-delta`,
|
|
lastAcceptedRevision: Math.max(
|
|
Number(graphPersistenceState.lastAcceptedRevision || 0),
|
|
normalizeIndexedDbRevision(commitResult?.revision, requestedRevision),
|
|
),
|
|
acceptedStorageTier: localStoreTier,
|
|
acceptedBy: localStoreTier,
|
|
lastRecoverableStorageTier: "none",
|
|
persistDiagnosticTier: "none",
|
|
opfsWriteLockState,
|
|
opfsWalDepth: localStoreDiagnostics.opfsWalDepth,
|
|
opfsPendingBytes: localStoreDiagnostics.opfsPendingBytes,
|
|
opfsCompactionState: localStoreDiagnostics.opfsCompactionState,
|
|
persistObservability,
|
|
dualWriteLastResult: {
|
|
action: "save",
|
|
target: localStore.storagePrimary,
|
|
success: true,
|
|
chatId: normalizedChatId,
|
|
revision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
reason: String(reason || "graph-save"),
|
|
warning: scheduleUploadWarning || "",
|
|
delta: cloneRuntimeDebugValue(commitResult?.delta, null),
|
|
at: Date.now(),
|
|
},
|
|
persistDelta: persistDeltaDiagnostics,
|
|
});
|
|
clearPendingGraphPersistRetry();
|
|
if (
|
|
(graphPersistenceState.loadState === GRAPH_LOAD_STATES.SHADOW_RESTORED ||
|
|
(graphPersistenceState.loadState === GRAPH_LOAD_STATES.LOADING &&
|
|
hasMeaningfulRuntimeGraphForChat(normalizedChatId, currentIdentity))) &&
|
|
(areChatIdsEquivalentForResolvedIdentity(
|
|
normalizedChatId,
|
|
graphPersistenceState.chatId || getCurrentChatId(),
|
|
currentIdentity,
|
|
) ||
|
|
areChatIdsEquivalentForResolvedIdentity(
|
|
graphPersistenceState.chatId || getCurrentChatId(),
|
|
normalizedChatId,
|
|
currentIdentity,
|
|
))
|
|
) {
|
|
applyGraphLoadState(GRAPH_LOAD_STATES.LOADED, {
|
|
chatId: normalizedChatId,
|
|
reason:
|
|
graphPersistenceState.loadState === GRAPH_LOAD_STATES.SHADOW_RESTORED
|
|
? `shadow-promoted:${String(reason || "graph-save")}`
|
|
: `local-store-confirmed:${String(reason || "graph-save")}`,
|
|
revision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
lastPersistedRevision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
queuedPersistRevision: 0,
|
|
queuedPersistChatId: "",
|
|
pendingPersist: false,
|
|
shadowSnapshotUsed: true,
|
|
shadowSnapshotRevision: Math.max(
|
|
Number(graphPersistenceState.shadowSnapshotRevision || 0),
|
|
normalizeIndexedDbRevision(commitResult?.revision, requestedRevision),
|
|
),
|
|
shadowSnapshotUpdatedAt: String(
|
|
graphPersistenceState.shadowSnapshotUpdatedAt || "",
|
|
),
|
|
shadowSnapshotReason: String(
|
|
graphPersistenceState.shadowSnapshotReason ||
|
|
"shadow-restore-promoted",
|
|
),
|
|
dbReady: true,
|
|
writesBlocked: false,
|
|
});
|
|
}
|
|
rememberResolvedGraphIdentityAlias(getContext(), normalizedChatId);
|
|
|
|
return {
|
|
saved: true,
|
|
accepted: true,
|
|
chatId: normalizedChatId,
|
|
revision: normalizeIndexedDbRevision(
|
|
commitResult?.revision,
|
|
requestedRevision,
|
|
),
|
|
reason: String(reason || "graph-save"),
|
|
saveMode: directPersistDelta
|
|
? `${localStore.reasonPrefix}-direct-delta`
|
|
: `${localStore.reasonPrefix}-delta`,
|
|
storageTier: localStoreTier,
|
|
warning: scheduleUploadWarning || "",
|
|
delta: cloneRuntimeDebugValue(commitResult?.delta, null),
|
|
snapshot,
|
|
};
|
|
} catch (error) {
|
|
console.warn(
|
|
`[ST-BME] ${localStore.statusLabel} 写入失败,保留 metadata 兜底:`,
|
|
error,
|
|
);
|
|
updatePersistDeltaDiagnostics({
|
|
status: "failed",
|
|
error: error?.message || String(error),
|
|
failedAt: Date.now(),
|
|
});
|
|
const persistenceEnvironment = buildPersistenceEnvironment(context, localStore);
|
|
const opfsWriteLockState =
|
|
typeof db?.getWriteLockSnapshot === "function"
|
|
? cloneRuntimeDebugValue(db.getWriteLockSnapshot(), null)
|
|
: null;
|
|
const localStoreDiagnostics =
|
|
typeof readLocalStoreDiagnosticsSync === "function"
|
|
? readLocalStoreDiagnosticsSync(db, localStore)
|
|
: {
|
|
resolvedLocalStore: `${localStore?.storagePrimary || "indexeddb"}:${localStore?.storageMode || "indexeddb"}`,
|
|
localStoreFormatVersion:
|
|
localStore?.storagePrimary === "opfs" ? 2 : 1,
|
|
localStoreMigrationState: "idle",
|
|
opfsWalDepth: 0,
|
|
opfsPendingBytes: 0,
|
|
opfsCompactionState: null,
|
|
};
|
|
updateGraphPersistenceState({
|
|
hostProfile: persistenceEnvironment.hostProfile,
|
|
primaryStorageTier: persistenceEnvironment.primaryStorageTier,
|
|
cacheStorageTier: persistenceEnvironment.cacheStorageTier,
|
|
cacheMirrorState:
|
|
persistRole === "cache-mirror"
|
|
? "error"
|
|
: graphPersistenceState.cacheMirrorState,
|
|
storagePrimary: localStore.storagePrimary,
|
|
storageMode: localStore.storageMode,
|
|
resolvedLocalStore: localStoreDiagnostics.resolvedLocalStore,
|
|
localStoreFormatVersion: localStoreDiagnostics.localStoreFormatVersion,
|
|
localStoreMigrationState: localStoreDiagnostics.localStoreMigrationState,
|
|
indexedDbLastError: error?.message || String(error),
|
|
opfsWriteLockState,
|
|
opfsWalDepth: localStoreDiagnostics.opfsWalDepth,
|
|
opfsPendingBytes: localStoreDiagnostics.opfsPendingBytes,
|
|
opfsCompactionState: localStoreDiagnostics.opfsCompactionState,
|
|
dualWriteLastResult: {
|
|
action: persistRole === "cache-mirror" ? "cache-mirror" : "save",
|
|
target: localStore.storagePrimary,
|
|
success: false,
|
|
chatId: normalizedChatId,
|
|
revision: normalizeIndexedDbRevision(revision),
|
|
reason: String(reason || "graph-save"),
|
|
error: error?.message || String(error),
|
|
at: Date.now(),
|
|
},
|
|
});
|
|
return {
|
|
saved: false,
|
|
chatId: normalizedChatId,
|
|
revision: normalizeIndexedDbRevision(revision),
|
|
reason:
|
|
persistRole === "cache-mirror"
|
|
? "cache-mirror-write-failed"
|
|
: `${String(localStore?.reasonPrefix || "indexeddb")}-write-failed`,
|
|
error,
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
export function queueGraphPersistToIndexedDbImpl(runtime,
|
|
chatId,
|
|
graph,
|
|
{
|
|
revision = 0,
|
|
reason = "graph-save",
|
|
persistRole = "primary",
|
|
scheduleCloudUpload = undefined,
|
|
persistDelta = null,
|
|
graphSnapshot = null,
|
|
persistSnapshot = null,
|
|
graphDetached = false,
|
|
} = {},
|
|
) {
|
|
const graphPersistenceState = createGraphPersistenceStateProxy(runtime);
|
|
const currentGraph = runtime.getCurrentGraph?.() || null;
|
|
const nativeHydrateInstallPromiseRef = createNativeHydrateInstallPromiseRef(runtime);
|
|
const nativePersistDeltaInstallPromiseRef = createNativePersistDeltaInstallPromiseRef(runtime);
|
|
const bmeIndexedDbLatestQueuedRevisionByChatId = runtime.bmeIndexedDbLatestQueuedRevisionByChatId;
|
|
const bmeIndexedDbWriteInFlightByChatId = runtime.bmeIndexedDbWriteInFlightByChatId;
|
|
const updateGraphPersistenceState = runtime.updateGraphPersistenceState || ((patch = {}) => runtime.setGraphPersistenceState?.({ ...(runtime.getGraphPersistenceState?.() || {}), ...(patch || {}) }));
|
|
const AUTHORITY_GRAPH_STORE_KIND = runtime.AUTHORITY_GRAPH_STORE_KIND;
|
|
const BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET = runtime.BME_INDEXEDDB_FALLBACK_LOAD_STATE_SET;
|
|
const GRAPH_LOAD_STATES = runtime.GRAPH_LOAD_STATES;
|
|
const applyAcceptedPendingPersistState = runtime.applyAcceptedPendingPersistState;
|
|
const applyGraphLoadState = runtime.applyGraphLoadState;
|
|
const applyIndexedDbEmptyToRuntime = runtime.applyIndexedDbEmptyToRuntime;
|
|
const applyIndexedDbSnapshotToRuntime = runtime.applyIndexedDbSnapshotToRuntime;
|
|
const applyPersistDeltaToSnapshot = runtime.applyPersistDeltaToSnapshot;
|
|
const applyShadowSnapshotToRuntime = runtime.applyShadowSnapshotToRuntime;
|
|
const areChatIdsEquivalentForResolvedIdentity = runtime.areChatIdsEquivalentForResolvedIdentity;
|
|
const buildBmeSyncRuntimeOptions = runtime.buildBmeSyncRuntimeOptions;
|
|
const buildGraphLocalStoreSelectorKey = runtime.buildGraphLocalStoreSelectorKey;
|
|
const buildGraphPersistResult = runtime.buildGraphPersistResult;
|
|
const buildPersistDelta = runtime.buildPersistDelta;
|
|
const buildPersistDeltaFromGraphDirtyState = runtime.buildPersistDeltaFromGraphDirtyState;
|
|
const buildPersistObservabilitySummary = runtime.buildPersistObservabilitySummary;
|
|
const buildPersistenceEnvironment = runtime.buildPersistenceEnvironment;
|
|
const buildSnapshotFromGraph = runtime.buildSnapshotFromGraph;
|
|
const cacheIndexedDbSnapshot = runtime.cacheIndexedDbSnapshot;
|
|
const canPersistGraphToMetadataFallback = runtime.canPersistGraphToMetadataFallback;
|
|
const clearPendingGraphPersistRetry = runtime.clearPendingGraphPersistRetry;
|
|
const cloneGraphForPersistence = runtime.cloneGraphForPersistence;
|
|
const cloneRuntimeDebugValue = runtime.cloneRuntimeDebugValue;
|
|
const createShadowComparisonGraph = runtime.createShadowComparisonGraph;
|
|
const detectIndexedDbSnapshotCommitMarkerMismatch = runtime.detectIndexedDbSnapshotCommitMarkerMismatch;
|
|
const detectStaleIndexedDbSnapshotAgainstRuntime = runtime.detectStaleIndexedDbSnapshotAgainstRuntime;
|
|
const ensureBmeChatManager = runtime.ensureBmeChatManager;
|
|
const ensureCurrentGraphRuntimeState = runtime.ensureCurrentGraphRuntimeState;
|
|
const evaluateNativeHydrateGate = runtime.evaluateNativeHydrateGate;
|
|
const evaluatePersistNativeDeltaGate = runtime.evaluatePersistNativeDeltaGate;
|
|
const getChatMetadataIntegrity = runtime.getChatMetadataIntegrity;
|
|
const getContext = runtime.getContext;
|
|
const getCurrentChatId = runtime.getCurrentChatId;
|
|
const getGraphPersistedRevision = runtime.getGraphPersistedRevision;
|
|
const getPreferredGraphLocalStorePresentationSync = runtime.getPreferredGraphLocalStorePresentationSync;
|
|
const getRequestedGraphLocalStorageMode = runtime.getRequestedGraphLocalStorageMode;
|
|
const getSettings = runtime.getSettings;
|
|
const hasMeaningfulRuntimeGraphForChat = runtime.hasMeaningfulRuntimeGraphForChat;
|
|
const isAuthorityGraphStorePresentation = runtime.isAuthorityGraphStorePresentation;
|
|
const isGraphLocalStorageModeOpfs = runtime.isGraphLocalStorageModeOpfs;
|
|
const isIndexedDbSnapshotMeaningful = runtime.isIndexedDbSnapshotMeaningful;
|
|
const isRestoreLockActive = runtime.isRestoreLockActive;
|
|
const maybeCaptureGraphShadowSnapshot = runtime.maybeCaptureGraphShadowSnapshot;
|
|
const maybeClearAcceptedPendingPersistState = runtime.maybeClearAcceptedPendingPersistState;
|
|
const maybeImportLegacyIndexedDbSnapshotToLocalStore = runtime.maybeImportLegacyIndexedDbSnapshotToLocalStore;
|
|
const maybeImportLegacyOpfsSnapshotToLocalStore = runtime.maybeImportLegacyOpfsSnapshotToLocalStore;
|
|
const maybeMigrateLegacyGraphToIndexedDb = runtime.maybeMigrateLegacyGraphToIndexedDb;
|
|
const maybeRecoverIndexedDbGraphFromStableIdentity = runtime.maybeRecoverIndexedDbGraphFromStableIdentity;
|
|
const maybeResolveOrphanAcceptedCommitMarker = runtime.maybeResolveOrphanAcceptedCommitMarker;
|
|
const maybeResumePendingAutoExtraction = runtime.maybeResumePendingAutoExtraction;
|
|
const normalizeChatIdCandidate = runtime.normalizeChatIdCandidate;
|
|
const normalizeGraphRuntimeState = runtime.normalizeGraphRuntimeState;
|
|
const normalizeIndexedDbRevision = runtime.normalizeIndexedDbRevision;
|
|
const normalizeLoadDiagnosticsMs = runtime.normalizeLoadDiagnosticsMs;
|
|
const normalizePersistDeltaDiagnosticsMs = runtime.normalizePersistDeltaDiagnosticsMs;
|
|
const persistGraphToChatMetadata = runtime.persistGraphToChatMetadata;
|
|
const persistGraphToConfiguredDurableTier = runtime.persistGraphToConfiguredDurableTier;
|
|
const pruneGraphPersistDirtyState = runtime.pruneGraphPersistDirtyState;
|
|
const queueGraphPersist = runtime.queueGraphPersist;
|
|
const queueRuntimeGraphLocalStoreRepair = runtime.queueRuntimeGraphLocalStoreRepair;
|
|
const readCachedIndexedDbSnapshot = runtime.readCachedIndexedDbSnapshot;
|
|
const readLoadDiagnosticsNow = runtime.readLoadDiagnosticsNow;
|
|
const readLocalStoreDiagnosticsSync = runtime.readLocalStoreDiagnosticsSync;
|
|
const readPersistDeltaDiagnosticsNow = runtime.readPersistDeltaDiagnosticsNow;
|
|
const recordLocalPersistEarlyFailure = runtime.recordLocalPersistEarlyFailure;
|
|
const recordPersistMismatchDiagnostic = runtime.recordPersistMismatchDiagnostic;
|
|
const refreshCurrentChatLocalStoreBinding = runtime.refreshCurrentChatLocalStoreBinding;
|
|
const rememberResolvedGraphIdentityAlias = runtime.rememberResolvedGraphIdentityAlias;
|
|
const resolveCompatibleGraphShadowSnapshot = runtime.resolveCompatibleGraphShadowSnapshot;
|
|
const resolveCurrentChatIdentity = runtime.resolveCurrentChatIdentity;
|
|
const resolveDbGraphStorePresentation = runtime.resolveDbGraphStorePresentation;
|
|
const resolveLocalStoreTierFromPresentation = runtime.resolveLocalStoreTierFromPresentation;
|
|
const resolvePendingPersistGraphSource = runtime.resolvePendingPersistGraphSource;
|
|
const resolvePendingPersistLastProcessedAssistantFloor = runtime.resolvePendingPersistLastProcessedAssistantFloor;
|
|
const resolvePersistRevisionFloor = runtime.resolvePersistRevisionFloor;
|
|
const resolveSnapshotGraphStorePresentation = runtime.resolveSnapshotGraphStorePresentation;
|
|
const schedulePendingGraphPersistRetry = runtime.schedulePendingGraphPersistRetry;
|
|
const scheduleUpload = runtime.scheduleUpload;
|
|
const shouldPreferShadowSnapshotOverOfficial = runtime.shouldPreferShadowSnapshotOverOfficial;
|
|
const stampGraphPersistenceMeta = runtime.stampGraphPersistenceMeta;
|
|
const syncCommitMarkerToPersistenceState = runtime.syncCommitMarkerToPersistenceState;
|
|
const updateLoadDiagnostics = runtime.updateLoadDiagnostics;
|
|
const updatePersistDeltaDiagnostics = runtime.updatePersistDeltaDiagnostics;
|
|
const console = runtime.console || globalThis.console;
|
|
|
|
const normalizedChatId = normalizeChatIdCandidate(chatId);
|
|
if (!normalizedChatId || (!graph && !persistDelta)) return;
|
|
|
|
if (persistRole === "cache-mirror") {
|
|
const persistenceEnvironment = buildPersistenceEnvironment(
|
|
getContext(),
|
|
getPreferredGraphLocalStorePresentationSync(),
|
|
);
|
|
updateGraphPersistenceState({
|
|
hostProfile: persistenceEnvironment.hostProfile,
|
|
primaryStorageTier: persistenceEnvironment.primaryStorageTier,
|
|
cacheStorageTier: persistenceEnvironment.cacheStorageTier,
|
|
cacheMirrorState: "queued",
|
|
});
|
|
}
|
|
|
|
const normalizedRevision = normalizeIndexedDbRevision(revision);
|
|
const latestQueuedRevision = normalizeIndexedDbRevision(
|
|
bmeIndexedDbLatestQueuedRevisionByChatId.get(normalizedChatId),
|
|
);
|
|
bmeIndexedDbLatestQueuedRevisionByChatId.set(
|
|
normalizedChatId,
|
|
Math.max(latestQueuedRevision, normalizedRevision),
|
|
);
|
|
|
|
const previousWritePromise =
|
|
bmeIndexedDbWriteInFlightByChatId.get(normalizedChatId) ||
|
|
Promise.resolve();
|
|
const nextWritePromise = previousWritePromise
|
|
.catch(() => null)
|
|
.then(async () => {
|
|
const currentLatestRevision = normalizeIndexedDbRevision(
|
|
bmeIndexedDbLatestQueuedRevisionByChatId.get(normalizedChatId),
|
|
);
|
|
if (
|
|
normalizedRevision > 0 &&
|
|
normalizedRevision < currentLatestRevision
|
|
) {
|
|
return {
|
|
saved: false,
|
|
skipped: true,
|
|
reason: "indexeddb-write-superseded",
|
|
revision: normalizedRevision,
|
|
};
|
|
}
|
|
const persistGraphSnapshot = graphSnapshot
|
|
? graphSnapshot
|
|
: graph
|
|
? graphDetached === true
|
|
? normalizeGraphRuntimeState(graph, normalizedChatId)
|
|
: cloneGraphForPersistence(graph, normalizedChatId)
|
|
: null;
|
|
return await saveGraphToIndexedDbImpl(runtime, normalizedChatId, persistGraphSnapshot, {
|
|
revision: normalizedRevision,
|
|
reason,
|
|
persistRole,
|
|
scheduleCloudUpload,
|
|
persistDelta,
|
|
graphSnapshot: persistGraphSnapshot,
|
|
persistSnapshot,
|
|
sourceGraph: graphDetached === true ? null : graph,
|
|
});
|
|
})
|
|
.finally(() => {
|
|
if (
|
|
bmeIndexedDbWriteInFlightByChatId.get(normalizedChatId) ===
|
|
nextWritePromise
|
|
) {
|
|
bmeIndexedDbWriteInFlightByChatId.delete(normalizedChatId);
|
|
}
|
|
});
|
|
|
|
bmeIndexedDbWriteInFlightByChatId.set(normalizedChatId, nextWritePromise);
|
|
|
|
}
|