From 85bb96a44e2875cbeeccc8d6be79478b25602a91 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 27 Sep 2026 22:16:38 +0100 Subject: [PATCH] Reorder params and add error --- frontend/sync-client/src/errors/errors.ts | 7 +++++++ .../sync-client/src/persistence/database.test.ts | 4 ++-- frontend/sync-client/src/persistence/database.ts | 2 +- .../src/sync-operations/outgoing-cursor.test.ts | 14 +++++++------- .../sync-client/src/utils/portable-path.test.ts | 7 ++++--- frontend/sync-client/src/utils/portable-path.ts | 3 ++- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/frontend/sync-client/src/errors/errors.ts b/frontend/sync-client/src/errors/errors.ts index b00271f2..523a6aa7 100644 --- a/frontend/sync-client/src/errors/errors.ts +++ b/frontend/sync-client/src/errors/errors.ts @@ -19,6 +19,13 @@ export class AuthenticationError extends Error { } } +export class ConflictingPathError extends Error { + public constructor(path: string) { + super(`Conflicting path: ${path}`); + this.name = "ConflictingPathError"; + } +} + export class PermanentSyncError extends Error {} export class LocalChangesDuringReconciliation extends Error {} diff --git a/frontend/sync-client/src/persistence/database.test.ts b/frontend/sync-client/src/persistence/database.test.ts index ea871f55..cedabee2 100644 --- a/frontend/sync-client/src/persistence/database.test.ts +++ b/frontend/sync-client/src/persistence/database.test.ts @@ -14,10 +14,10 @@ test("current state keeps absent optional fields absent across save and reload", const restart = (): Database => new Database( saved, + "vault", async (next) => { saved = structuredClone(next); }, - "vault", async () => structuredClone(saved) ); const db = restart(); @@ -36,10 +36,10 @@ test("current reset state is restored after an uncertain save", async () => { }; const db = new Database( undefined, + "vault", async () => { throw new Error("interrupted"); }, - "vault", async () => structuredClone(saved) ); await assert.rejects(db.save(), /interrupted/); diff --git a/frontend/sync-client/src/persistence/database.ts b/frontend/sync-client/src/persistence/database.ts index fa543409..b08b34f5 100644 --- a/frontend/sync-client/src/persistence/database.ts +++ b/frontend/sync-client/src/persistence/database.ts @@ -73,8 +73,8 @@ export class Database { public constructor( initial: StoredDatabase | undefined, - private readonly saveData: (data: StoredDatabase) => Promise, vaultKey: string, + private readonly saveData: (data: StoredDatabase) => Promise, private readonly loadData: () => Promise ) { const state = diff --git a/frontend/sync-client/src/sync-operations/outgoing-cursor.test.ts b/frontend/sync-client/src/sync-operations/outgoing-cursor.test.ts index 90119c8f..8fcac4da 100644 --- a/frontend/sync-client/src/sync-operations/outgoing-cursor.test.ts +++ b/frontend/sync-client/src/sync-operations/outgoing-cursor.test.ts @@ -19,12 +19,12 @@ for (const change of ["version", "identity", "path", "delete"] as const) { const content = new TextEncoder().encode("same bytes"); const digest = await hash(content); const state = createEmptyDatabase("test"); - state.local.a = "a.md"; + state.actualFileManifest.a = "a.md"; state.documents.a = { base: { vaultUpdateId: 1, hash: digest } }; const database = new Database( state, - async () => undefined, "test", + async () => undefined, async () => undefined ); const sent: { documentsWithCursors: DocumentWithCursors[] }[] = []; @@ -39,17 +39,17 @@ for (const change of ["version", "identity", "path", "delete"] as const) { read: async (): Promise<{ content: Uint8Array }> => { const current = database.state; assert.ok(current.documents.a?.base); - assert.ok(current.local.a !== undefined); + assert.ok(current.actualFileManifest.a !== undefined); if (change === "delete") { - delete current.local.a; + delete current.actualFileManifest.a; } else if (change === "version") { current.documents.a.base.vaultUpdateId = 2; } else if (change === "identity") { - current.local.replacement = current.local.a; + current.actualFileManifest.replacement = current.actualFileManifest.a; current.documents.replacement = current.documents.a; - delete current.local.a; + delete current.actualFileManifest.a; } else { - current.local.a = "moved.md"; + current.actualFileManifest.a = "moved.md"; } return { content }; diff --git a/frontend/sync-client/src/utils/portable-path.test.ts b/frontend/sync-client/src/utils/portable-path.test.ts index cd13bb8c..20e6591a 100644 --- a/frontend/sync-client/src/utils/portable-path.test.ts +++ b/frontend/sync-client/src/utils/portable-path.test.ts @@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process"; import { resolve } from "node:path"; import { pathToFileURL } from "node:url"; import { test } from "node:test"; +import { ConflictingPathError } from "../errors/errors"; import { allocatePortablePath, validatePortablePaths, @@ -87,7 +88,7 @@ test("shared directories remain distinct from aliases and file ancestors", () => ]) { assert.throws(() => { validatePortablePaths(paths); - }, /Conflicting path/); + }, ConflictingPathError); } }); @@ -236,13 +237,13 @@ test("allocation resolves both ancestor and leaf collisions without changing sib ); }); -test("manifest conflicts throw TypeError", () => { +test("manifest conflicts throw ConflictingPathError", () => { assert.throws( () => { validatePortablePaths(["a.md", "A.md"]); }, { - name: "TypeError", + name: "ConflictingPathError", message: "Conflicting path: A.md" } ); diff --git a/frontend/sync-client/src/utils/portable-path.ts b/frontend/sync-client/src/utils/portable-path.ts index ca54057a..4aea15fe 100644 --- a/frontend/sync-client/src/utils/portable-path.ts +++ b/frontend/sync-client/src/utils/portable-path.ts @@ -1,3 +1,4 @@ +import { ConflictingPathError } from "../errors/errors"; import type { DocumentId, RelativePath } from "../persistence/database"; export const INTERNAL_DIRECTORY = ".vault-link-sync"; @@ -78,7 +79,7 @@ export function validatePortablePaths(paths: Iterable): void { previous && (previous.spelling !== spelling || previous.file || file) ) { - throw new TypeError(`Conflicting path: ${path}`); + throw new ConflictingPathError(path); } const node = previous ?? { id: nodes.size + 1, spelling, file };