Reorder params and add error

This commit is contained in:
Andras Schmelczer 2026-09-27 22:16:38 +01:00
commit 85bb96a44e
6 changed files with 23 additions and 14 deletions

View file

@ -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 PermanentSyncError extends Error {}
export class LocalChangesDuringReconciliation extends Error {} export class LocalChangesDuringReconciliation extends Error {}

View file

@ -14,10 +14,10 @@ test("current state keeps absent optional fields absent across save and reload",
const restart = (): Database => const restart = (): Database =>
new Database( new Database(
saved, saved,
"vault",
async (next) => { async (next) => {
saved = structuredClone(next); saved = structuredClone(next);
}, },
"vault",
async () => structuredClone(saved) async () => structuredClone(saved)
); );
const db = restart(); const db = restart();
@ -36,10 +36,10 @@ test("current reset state is restored after an uncertain save", async () => {
}; };
const db = new Database( const db = new Database(
undefined, undefined,
"vault",
async () => { async () => {
throw new Error("interrupted"); throw new Error("interrupted");
}, },
"vault",
async () => structuredClone(saved) async () => structuredClone(saved)
); );
await assert.rejects(db.save(), /interrupted/); await assert.rejects(db.save(), /interrupted/);

View file

@ -73,8 +73,8 @@ export class Database {
public constructor( public constructor(
initial: StoredDatabase | undefined, initial: StoredDatabase | undefined,
private readonly saveData: (data: StoredDatabase) => Promise<void>,
vaultKey: string, vaultKey: string,
private readonly saveData: (data: StoredDatabase) => Promise<void>,
private readonly loadData: () => Promise<StoredDatabase | undefined> private readonly loadData: () => Promise<StoredDatabase | undefined>
) { ) {
const state = const state =

View file

@ -19,12 +19,12 @@ for (const change of ["version", "identity", "path", "delete"] as const) {
const content = new TextEncoder().encode("same bytes"); const content = new TextEncoder().encode("same bytes");
const digest = await hash(content); const digest = await hash(content);
const state = createEmptyDatabase("test"); const state = createEmptyDatabase("test");
state.local.a = "a.md"; state.actualFileManifest.a = "a.md";
state.documents.a = { base: { vaultUpdateId: 1, hash: digest } }; state.documents.a = { base: { vaultUpdateId: 1, hash: digest } };
const database = new Database( const database = new Database(
state, state,
async () => undefined,
"test", "test",
async () => undefined,
async () => undefined async () => undefined
); );
const sent: { documentsWithCursors: DocumentWithCursors[] }[] = []; const sent: { documentsWithCursors: DocumentWithCursors[] }[] = [];
@ -39,17 +39,17 @@ for (const change of ["version", "identity", "path", "delete"] as const) {
read: async (): Promise<{ content: Uint8Array }> => { read: async (): Promise<{ content: Uint8Array }> => {
const current = database.state; const current = database.state;
assert.ok(current.documents.a?.base); assert.ok(current.documents.a?.base);
assert.ok(current.local.a !== undefined); assert.ok(current.actualFileManifest.a !== undefined);
if (change === "delete") { if (change === "delete") {
delete current.local.a; delete current.actualFileManifest.a;
} else if (change === "version") { } else if (change === "version") {
current.documents.a.base.vaultUpdateId = 2; current.documents.a.base.vaultUpdateId = 2;
} else if (change === "identity") { } else if (change === "identity") {
current.local.replacement = current.local.a; current.actualFileManifest.replacement = current.actualFileManifest.a;
current.documents.replacement = current.documents.a; current.documents.replacement = current.documents.a;
delete current.local.a; delete current.actualFileManifest.a;
} else { } else {
current.local.a = "moved.md"; current.actualFileManifest.a = "moved.md";
} }
return { content }; return { content };

View file

@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process";
import { resolve } from "node:path"; import { resolve } from "node:path";
import { pathToFileURL } from "node:url"; import { pathToFileURL } from "node:url";
import { test } from "node:test"; import { test } from "node:test";
import { ConflictingPathError } from "../errors/errors";
import { import {
allocatePortablePath, allocatePortablePath,
validatePortablePaths, validatePortablePaths,
@ -87,7 +88,7 @@ test("shared directories remain distinct from aliases and file ancestors", () =>
]) { ]) {
assert.throws(() => { assert.throws(() => {
validatePortablePaths(paths); 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( assert.throws(
() => { () => {
validatePortablePaths(["a.md", "A.md"]); validatePortablePaths(["a.md", "A.md"]);
}, },
{ {
name: "TypeError", name: "ConflictingPathError",
message: "Conflicting path: A.md" message: "Conflicting path: A.md"
} }
); );

View file

@ -1,3 +1,4 @@
import { ConflictingPathError } from "../errors/errors";
import type { DocumentId, RelativePath } from "../persistence/database"; import type { DocumentId, RelativePath } from "../persistence/database";
export const INTERNAL_DIRECTORY = ".vault-link-sync"; export const INTERNAL_DIRECTORY = ".vault-link-sync";
@ -78,7 +79,7 @@ export function validatePortablePaths(paths: Iterable<RelativePath>): void {
previous && previous &&
(previous.spelling !== spelling || previous.file || file) (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 }; const node = previous ?? { id: nodes.size + 1, spelling, file };