250 lines
8.3 KiB
TypeScript
250 lines
8.3 KiB
TypeScript
import { VAULT_SYNC_CODES } from "../errors/codes.js";
|
||
import { VaultSyncError } from "../errors/vault-sync-error.js";
|
||
import { getVaultSyncInstruments } from "../otel/instruments.js";
|
||
import type {
|
||
MergeStrategy,
|
||
VaultHistoryEntry,
|
||
VaultItem,
|
||
VectorClockState,
|
||
} from "../types/types.js";
|
||
import { compare, lwwTotalOrder, merge } from "../vector-clock/vector-clock.js";
|
||
|
||
/**
|
||
* Permitted merge strategies. Exposed for runtime checks and exhaustive
|
||
* `switch` statements in consumers.
|
||
*/
|
||
export const MERGE_STRATEGIES: readonly MergeStrategy[] = [
|
||
"lww",
|
||
"append",
|
||
"atomic",
|
||
] as const;
|
||
|
||
function clockUnion(
|
||
a: VectorClockState,
|
||
b: VectorClockState
|
||
): VectorClockState {
|
||
return merge(a, b);
|
||
}
|
||
|
||
function chooseWinner(local: VaultItem, remote: VaultItem): VaultItem {
|
||
const order = lwwTotalOrder(local, remote);
|
||
if (order === -1) return local;
|
||
if (order === 1) return remote;
|
||
// Identical clock+updatedAt+nodeId but possibly different content (a
|
||
// pathological case from anomalous inputs). Pick deterministically by
|
||
// canonical-string content so commutativity is preserved.
|
||
const localCanonical = canonicalString(local);
|
||
const remoteCanonical = canonicalString(remote);
|
||
return localCanonical <= remoteCanonical ? local : remote;
|
||
}
|
||
|
||
function canonicalString(item: VaultItem): string {
|
||
return [
|
||
item.title,
|
||
item.password,
|
||
item.folderId ?? "",
|
||
String(item.deleted ?? false),
|
||
].join(" |