import type { SettingsMigrationReviewReason, SettingsMigrationState, } from "@vrtmrz/livesync-commonlib/compat/common/types"; export const DATABASE_COMPATIBILITY_VERSION_KEY = "database-compatibility-version"; export const DATABASE_COMPATIBILITY_LEGACY_VERSION_KEY_PREFIX = "obsidian-live-sync-ver"; export const COMPATIBILITY_PAUSE_SETTING_MESSAGE = "Remote synchronisation is paused until this device's compatibility review has been completed."; export type DatabaseCompatibilityVersionState = "missing" | "invalid" | "upgrade" | "downgrade"; export interface DatabaseCompatibilityReason { source: "database-version"; state: DatabaseCompatibilityVersionState; acknowledgedVersion?: number; currentVersion: number; resumable: boolean; } export interface SettingsCompatibilityReason { source: "settings-schema"; sourceVersion: number; currentVersion: number; isFromFutureSchema: boolean; resumable: boolean; reviewReasons: readonly SettingsMigrationReviewReason[]; } export interface LegacyCompatibilityReason { source: "legacy-review"; message: string; resumable: true; } export type CompatibilityPauseReason = | DatabaseCompatibilityReason | SettingsCompatibilityReason | LegacyCompatibilityReason; export interface CompatibilityPause { reasons: readonly CompatibilityPauseReason[]; resumable: boolean; } export interface CompatibilityEvaluation { pause?: CompatibilityPause; initialiseAcknowledgedVersion: boolean; } export interface CompatibilityEvaluationInput { acknowledgedVersion: string | null; currentVersion: number; migrationState?: SettingsMigrationState; legacyReviewMessage: string; } function databaseVersionReason( acknowledgedVersion: string | null, currentVersion: number, isNewVault: boolean ): DatabaseCompatibilityReason | undefined { if (acknowledgedVersion === null || acknowledgedVersion === "") { if (isNewVault) return undefined; return { source: "database-version", state: "missing", currentVersion, resumable: true, }; } const parsed = Number(acknowledgedVersion); if (!Number.isSafeInteger(parsed)) { return { source: "database-version", state: "invalid", currentVersion, resumable: true, }; } if (parsed === currentVersion) return undefined; if (parsed < currentVersion) { return { source: "database-version", state: "upgrade", acknowledgedVersion: parsed, currentVersion, resumable: true, }; } return { source: "database-version", state: "downgrade", acknowledgedVersion: parsed, currentVersion, resumable: false, }; } /** * Derives a host-neutral compatibility pause from device-local and settings-schema state. * * The caller owns persistence, user interaction, and the actual replication gate. */ export function evaluateCompatibilityPause(input: CompatibilityEvaluationInput): CompatibilityEvaluation { const isNewVault = input.migrationState?.isNewVault === true; const reasons: CompatibilityPauseReason[] = []; const databaseReason = databaseVersionReason(input.acknowledgedVersion, input.currentVersion, isNewVault); if (databaseReason) reasons.push(databaseReason); if (input.migrationState?.requiresSyncReview === true) { reasons.push({ source: "settings-schema", sourceVersion: input.migrationState.sourceVersion, currentVersion: input.migrationState.targetVersion, isFromFutureSchema: input.migrationState.isFromFutureSchema, resumable: !input.migrationState.isFromFutureSchema, reviewReasons: input.migrationState.reviewReasons, }); } if (input.legacyReviewMessage !== "" && reasons.length === 0) { reasons.push({ source: "legacy-review", message: input.legacyReviewMessage, resumable: true, }); } if (reasons.length === 0) { return { initialiseAcknowledgedVersion: isNewVault && (input.acknowledgedVersion === null || input.acknowledgedVersion === ""), }; } return { pause: { reasons, resumable: reasons.every((reason) => reason.resumable), }, initialiseAcknowledgedVersion: false, }; } export function legacyDatabaseCompatibilityVersionKey(vaultName: string): string { return `${DATABASE_COMPATIBILITY_LEGACY_VERSION_KEY_PREFIX}${vaultName}`; }