fix: enable guarded recovery of unapplied deletions

This commit is contained in:
vorotamoroz committed 2026-11-10 12:10:38 +00:00
1 parent 517f5c248f
commit 18ff38e04c
14 files changed
+1253 -13

No files matched your search

+1 -1
View File
@@ -115,7 +115,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
path: services.path,
replication: services.replication,
storageAccess: storageAccess,
fileReflectionProvenance: createFileReflectionProvenance(services.keyValueDB),
fileReflectionProvenance: createFileReflectionProvenance(services.keyValueDB, true),
});
const rebuilder = new ServiceRebuilder({
path: services.path,
@@ -20,8 +20,12 @@ export type FileReflectionProvenanceStoreFactory = {
* watching, or replication can invoke it. Operations are never held waiting for
* readiness; they fail on a lifecycle violation and may fail during reset.
*/
export function createFileReflectionProvenance(keyValueDB: FileReflectionProvenanceStoreFactory) {
export function createFileReflectionProvenance(
keyValueDB: FileReflectionProvenanceStoreFactory,
enableDeletionRecovery = false
) {
return new StoredFileReflectionProvenance(
keyValueDB.openSimpleStore<FileReflectionProvenanceRecord>(FILE_REFLECTION_PROVENANCE_STORE)
keyValueDB.openSimpleStore<FileReflectionProvenanceRecord>(FILE_REFLECTION_PROVENANCE_STORE),
enableDeletionRecovery
);
}
@@ -2,10 +2,7 @@ import { describe, expect, it, vi } from "vitest";
import type { SimpleStore } from "@vrtmrz/livesync-commonlib/compat/common/utils";
import type { FileReflectionProvenanceRecord } from "@vrtmrz/livesync-commonlib/compat/interfaces/FileReflectionProvenance";
import type { FilePathWithPrefix } from "@vrtmrz/livesync-commonlib/compat/common/types";
import {
createFileReflectionProvenance,
FILE_REFLECTION_PROVENANCE_STORE,
} from "./FileReflectionProvenance";
import { createFileReflectionProvenance, FILE_REFLECTION_PROVENANCE_STORE } from "./FileReflectionProvenance";
describe("createFileReflectionProvenance", () => {
it("uses one reset-scoped host store for exact reflected revisions", async () => {
@@ -24,10 +21,14 @@ describe("createFileReflectionProvenance", () => {
const openSimpleStore = vi.fn().mockReturnValue(store);
const path = "note.md" as FilePathWithPrefix;
const provenance = createFileReflectionProvenance({ openSimpleStore });
const provenance = createFileReflectionProvenance({ openSimpleStore }, true);
expect(openSimpleStore).toHaveBeenCalledWith(FILE_REFLECTION_PROVENANCE_STORE);
await provenance.set(path, { revision: "3-displayed", observedStorageMtime: 123.456 });
expect(provenance.listPaths).toBeTypeOf("function");
await expect(provenance.listPaths!()).resolves.toEqual([path]);
expect(store.keys).toHaveBeenCalledTimes(1);
expect(store.get).not.toHaveBeenCalled();
expect(openSimpleStore).toHaveBeenCalledTimes(1);
await expect(provenance.get(path)).resolves.toEqual({
revision: "3-displayed",
@@ -35,3 +36,9 @@ describe("createFileReflectionProvenance", () => {
});
});
});
it("keeps shared application composition opted out unless the host requests recovery", () => {
const openSimpleStore = vi.fn().mockReturnValue({ keys: vi.fn(), get: vi.fn() });
const provenance = createFileReflectionProvenance({ openSimpleStore });
expect(provenance.listPaths).toBeUndefined();
});