mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-14 02:40:45 +08:00
refactor(rebirth): centralize persistence reducer
This commit is contained in:
144
sync/persistence-reducer.js
Normal file
144
sync/persistence-reducer.js
Normal file
@@ -0,0 +1,144 @@
|
||||
// ST-BME persistence reducer core.
|
||||
//
|
||||
// Pure helpers only: no IO, no graph mutation, no UI side effects. Phase 2
|
||||
// centralizes accepted/queued/pending invariants while leaving durable routing
|
||||
// (IndexedDB/OPFS/Authority/Luker) in the existing orchestration layer.
|
||||
|
||||
import {
|
||||
isAcceptedLegacyPersistenceTier,
|
||||
isRecoveryOnlyLegacyPersistenceTier,
|
||||
planAcceptedPendingPersistenceRepair,
|
||||
} from "./legacy-persistence-repair.js";
|
||||
|
||||
const SAVED_BATCH_ACCEPTED_TIERS = new Set([
|
||||
"indexeddb",
|
||||
"opfs",
|
||||
"authority-sql",
|
||||
"luker-chat-state",
|
||||
]);
|
||||
|
||||
function normalizeRevision(value = 0) {
|
||||
const numeric = Number(value || 0);
|
||||
if (!Number.isFinite(numeric) || numeric <= 0) return 0;
|
||||
return Math.floor(numeric);
|
||||
}
|
||||
|
||||
function normalizeTier(value = "none") {
|
||||
return String(value || "none").trim().toLowerCase() || "none";
|
||||
}
|
||||
|
||||
export function buildBatchPersistenceRecordFromPersistResult(persistResult = null) {
|
||||
const accepted = persistResult?.accepted === true;
|
||||
const queued = persistResult?.queued === true;
|
||||
const blocked = persistResult?.blocked === true;
|
||||
const recoverable = persistResult?.recoverable === true;
|
||||
const storageTier = normalizeTier(persistResult?.storageTier);
|
||||
let outcome = "failed";
|
||||
|
||||
if (accepted && SAVED_BATCH_ACCEPTED_TIERS.has(storageTier)) {
|
||||
outcome = "saved";
|
||||
} else if (accepted) {
|
||||
outcome = "fallback";
|
||||
} else if (queued) {
|
||||
outcome = "queued";
|
||||
} else if (recoverable) {
|
||||
outcome = "recoverable";
|
||||
} else if (blocked) {
|
||||
outcome = "blocked";
|
||||
}
|
||||
|
||||
return {
|
||||
outcome,
|
||||
accepted,
|
||||
recoverable,
|
||||
storageTier,
|
||||
reason: String(persistResult?.reason || ""),
|
||||
revision: normalizeRevision(persistResult?.revision),
|
||||
saveMode: String(persistResult?.saveMode || ""),
|
||||
saved: persistResult?.saved === true,
|
||||
queued,
|
||||
blocked,
|
||||
};
|
||||
}
|
||||
|
||||
export function applyPersistenceRecordToBatchStatus(batchStatus = null, persistenceRecord = null) {
|
||||
if (!batchStatus || typeof batchStatus !== "object" || !persistenceRecord) {
|
||||
return batchStatus;
|
||||
}
|
||||
const accepted = persistenceRecord.accepted === true;
|
||||
return {
|
||||
...batchStatus,
|
||||
persistence: persistenceRecord,
|
||||
historyAdvanceAllowed: accepted,
|
||||
historyAdvanced: accepted,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildAcceptedPersistenceStatePatch({
|
||||
currentState = null,
|
||||
persistenceRecord = null,
|
||||
acceptedRevision = persistenceRecord?.revision,
|
||||
acceptedStorageTier = persistenceRecord?.storageTier,
|
||||
acceptedBy = acceptedStorageTier,
|
||||
clearQueued = true,
|
||||
} = {}) {
|
||||
const revision = Math.max(
|
||||
normalizeRevision(currentState?.lastAcceptedRevision),
|
||||
normalizeRevision(acceptedRevision),
|
||||
);
|
||||
const tier = normalizeTier(acceptedStorageTier);
|
||||
const acceptedTier = isAcceptedLegacyPersistenceTier(tier) ? tier : "none";
|
||||
if (persistenceRecord?.accepted !== true || acceptedTier === "none") {
|
||||
return {};
|
||||
}
|
||||
const patch = {
|
||||
acceptedStorageTier: acceptedTier,
|
||||
acceptedBy: acceptedTier,
|
||||
lastAcceptedRevision: revision,
|
||||
lastRecoverableStorageTier: "none",
|
||||
pendingPersist: false,
|
||||
writesBlocked: false,
|
||||
};
|
||||
if (clearQueued) {
|
||||
patch.queuedPersistRevision = 0;
|
||||
patch.queuedPersistChatId = "";
|
||||
patch.queuedPersistMode = "";
|
||||
patch.queuedPersistRotateIntegrity = false;
|
||||
patch.queuedPersistReason = "";
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
export function buildQueuedPersistenceStatePatch({
|
||||
currentState = null,
|
||||
reason = "",
|
||||
revision = 0,
|
||||
chatId = "",
|
||||
immediate = false,
|
||||
recoverableTier = "none",
|
||||
} = {}) {
|
||||
const normalizedRevision = Math.max(
|
||||
normalizeRevision(currentState?.queuedPersistRevision),
|
||||
normalizeRevision(revision),
|
||||
);
|
||||
const tier = normalizeTier(recoverableTier);
|
||||
const recoverable = isRecoveryOnlyLegacyPersistenceTier(tier);
|
||||
return {
|
||||
queuedPersistRevision: normalizedRevision,
|
||||
queuedPersistChatId: String(chatId || ""),
|
||||
queuedPersistMode: immediate ? "immediate" : "debounced",
|
||||
queuedPersistRotateIntegrity: false,
|
||||
queuedPersistReason: String(reason || ""),
|
||||
pendingPersist: true,
|
||||
writesBlocked: !recoverable,
|
||||
lastPersistReason: String(reason || ""),
|
||||
lastPersistMode: immediate ? "pending-immediate" : "pending-debounced",
|
||||
lastRecoverableStorageTier: recoverable
|
||||
? tier
|
||||
: currentState?.lastRecoverableStorageTier,
|
||||
};
|
||||
}
|
||||
|
||||
export function planAcceptedPendingClear(options = {}) {
|
||||
return planAcceptedPendingPersistenceRepair(options);
|
||||
}
|
||||
Reference in New Issue
Block a user