Files
obsidian-livesync/src/common/databaseCompatibility.ts
T
2026-07-22 06:21:54 +00:00

150 lines
4.6 KiB
TypeScript

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}`;
}