Fix lints & format

This commit is contained in:
Andras Schmelczer 2026-05-09 15:28:43 +01:00
parent 6d40097bcd
commit 792f57dc7e
36 changed files with 342 additions and 1687 deletions

View file

@ -2,7 +2,10 @@ import { describe, it } from "node:test";
import assert from "node:assert";
import { Logger } from "../tracing/logger";
import { Settings } from "../persistence/settings";
import { STORED_STATE_SCHEMA_VERSION, SyncEventQueue } from "./sync-event-queue";
import {
STORED_STATE_SCHEMA_VERSION,
SyncEventQueue
} from "./sync-event-queue";
import { scheduleOfflineChanges } from "./offline-change-detector";
import type { FileOperations } from "../file-operations/file-operations";
import type { RelativePath } from "./types";
@ -22,19 +25,20 @@ const makeQueue = async (): Promise<SyncEventQueue> => {
);
};
const makeOperations = (
files: Record<string, Uint8Array>
): FileOperations => {
return {
listFilesRecursively: async () => Object.keys(files),
const makeOperations = (files: Record<string, Uint8Array>): FileOperations => {
const map = new Map<RelativePath, Uint8Array>(Object.entries(files));
const partial: Partial<FileOperations> = {
listFilesRecursively: async () => [...map.keys()],
read: async (path: RelativePath) => {
const data = files[path];
const data = map.get(path);
if (data === undefined) {
throw new Error(`File not found: ${path}`);
}
return data;
}
} as unknown as FileOperations;
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return partial as FileOperations;
};
describe("scheduleOfflineChanges", () => {
@ -70,7 +74,8 @@ describe("scheduleOfflineChanges", () => {
operations,
queue,
(path) => enqueued.push({ kind: "create", path }),
(args) => enqueued.push({ kind: "update", path: args.relativePath }),
(args) =>
enqueued.push({ kind: "update", path: args.relativePath }),
(path) => enqueued.push({ kind: "delete", path })
);
@ -109,13 +114,12 @@ describe("scheduleOfflineChanges", () => {
operations,
queue,
(path) => enqueued.push({ kind: "create", path }),
(args) => enqueued.push({ kind: "update", path: args.relativePath }),
(args) =>
enqueued.push({ kind: "update", path: args.relativePath }),
(path) => enqueued.push({ kind: "delete", path })
);
assert.deepStrictEqual(enqueued, [
{ kind: "update", path: "doc.md" }
]);
assert.deepStrictEqual(enqueued, [{ kind: "update", path: "doc.md" }]);
});
it("schedules a delete for a settled record whose local file is missing", async () => {
@ -136,13 +140,12 @@ describe("scheduleOfflineChanges", () => {
operations,
queue,
(path) => enqueued.push({ kind: "create", path }),
(args) => enqueued.push({ kind: "update", path: args.relativePath }),
(args) =>
enqueued.push({ kind: "update", path: args.relativePath }),
(path) => enqueued.push({ kind: "delete", path })
);
assert.deepStrictEqual(enqueued, [
{ kind: "delete", path: "gone.md" }
]);
assert.deepStrictEqual(enqueued, [{ kind: "delete", path: "gone.md" }]);
});
it("detects an offline rename when an untracked file matches a deleted record's content hash", async () => {

View file

@ -7,6 +7,24 @@ import type { SyncEventQueue } from "./sync-event-queue";
import { removeFromArray } from "../utils/remove-from-array";
import { FileNotFoundError } from "../errors/file-not-found-error";
async function readOrUndefined(
operations: FileOperations,
path: RelativePath,
logger: Logger
): Promise<Uint8Array | undefined> {
try {
return await operations.read(path);
} catch (e) {
if (e instanceof FileNotFoundError) {
logger.debug(
`File ${path} disappeared before offline-scan could read it; skipping`
);
return undefined;
}
throw e;
}
}
/**
* Scans the local filesystem and the document database to determine
* which files were created, updated, moved, or deleted while the
@ -85,18 +103,10 @@ export async function scheduleOfflineChanges(
// the whole scan; nothing to sync for a file that's already gone.
const disappearedPaths = new Set<RelativePath>();
for (const path of locallyPossibleCreatedFiles) {
let content: Uint8Array;
try {
content = await operations.read(path);
} catch (e) {
if (e instanceof FileNotFoundError) {
logger.debug(
`File ${path} disappeared before offline-scan could read it; skipping`
);
disappearedPaths.add(path);
continue;
}
throw e;
const content = await readOrUndefined(operations, path, logger);
if (content === undefined) {
disappearedPaths.add(path);
continue;
}
const contentHash = await hash(content);
@ -148,8 +158,7 @@ export async function scheduleOfflineChanges(
for (const path of syncedLocalFiles) {
const record = allDocuments.get(path);
if (
record !== undefined &&
record.localPath !== undefined &&
record?.localPath !== undefined &&
record.localPath !== record.remoteRelativePath &&
!allLocalFiles.has(record.remoteRelativePath) &&
queue.byLocalPath.get(record.remoteRelativePath) === undefined

View file

@ -2,7 +2,10 @@ import { describe, it } from "node:test";
import assert from "node:assert";
import { Logger, LogLevel } from "../tracing/logger";
import { Settings } from "../persistence/settings";
import { STORED_STATE_SCHEMA_VERSION, SyncEventQueue } from "./sync-event-queue";
import {
STORED_STATE_SCHEMA_VERSION,
SyncEventQueue
} from "./sync-event-queue";
import { Reconciler } from "./reconciler";
import { SyncResetError } from "../errors/sync-reset-error";
import type { FileOperations } from "../file-operations/file-operations";
@ -32,18 +35,22 @@ describe("Reconciler", () => {
localPath: undefined
});
const operations = {
const operationsPartial: Partial<FileOperations> = {
exists: async () => false,
create: async () => {
assert.fail("reset-interrupted placement should not write");
}
} as unknown as FileOperations;
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const operations = operationsPartial as FileOperations;
const syncService = {
const syncServicePartial: Partial<SyncService> = {
getDocumentVersionContent: async () => {
throw new SyncResetError();
}
} as unknown as SyncService;
};
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const syncService = syncServicePartial as SyncService;
const reconciler = new Reconciler(
logger,

View file

@ -307,7 +307,10 @@ describe("SyncEventQueue", () => {
queue.byLocalPath.get("renamed.md" as RelativePath),
undefined
);
assert.strictEqual(queue.getDocumentByDocumentId("A")?.localPath, "a.md");
assert.strictEqual(
queue.getDocumentByDocumentId("A")?.localPath,
"a.md"
);
// setLocalPath does re-key — it's the explicit path-mutation API.
await queue.setLocalPath("A", "later.md" as RelativePath);

View file

@ -220,9 +220,7 @@ export class SyncEventQueue {
* path) still fires when neither side holds a record for the
* collision target.
*/
public lastSeenUpdateIdForCreate(
requestPath: RelativePath
): VaultUpdateId {
public lastSeenUpdateIdForCreate(requestPath: RelativePath): VaultUpdateId {
let watermark = this._lastSeenUpdateId.min;
for (const record of this.byDocId.values()) {
if (
@ -324,7 +322,7 @@ export class SyncEventQueue {
!pendingCreate.isProcessing
) {
this.cancelPendingCreate(pendingCreate);
if (recordIsDeleting && record !== undefined) {
if (recordIsDeleting) {
// A stale deleting record was still claiming this path.
// The not-yet-started create/delete pair collapsed to
// nothing, and the disk file is gone, so clear the stale
@ -343,11 +341,11 @@ export class SyncEventQueue {
path: lookupPath
});
this.notifyPendingUpdateCountChanged();
if (recordOwnsLookupPath && record !== undefined) {
if (recordOwnsLookupPath) {
// The file is gone from disk; clear the doc's localPath so the
// Reconciler doesn't try to operate on a vacated slot.
await this.setLocalPath(record.documentId, undefined);
} else if (recordIsDeleting && record !== undefined) {
} else if (recordIsDeleting) {
// A stale deleting record was still claiming this path while a
// newer pending create owned the actual disk file. Drop the
// stale claim now that the file is gone.
@ -648,14 +646,6 @@ export class SyncEventQueue {
return this.byDocId.get(target);
}
public getDocumentByDocumentIdOrFail(target: DocumentId): DocumentRecord {
const result = this.getDocumentByDocumentId(target);
if (!result) {
throw new Error(`No document found with id ${target}`);
}
return result;
}
public getRecordByLocalPath(
path: RelativePath
): DocumentRecord | undefined {
@ -814,6 +804,7 @@ export class SyncEventQueue {
event.path === path &&
event.documentId !== promise
) {
// eslint-disable-next-line no-restricted-syntax -- splice-by-index here is a reorder, not an item removal
this.events.splice(i, 1);
this.events.splice(createIndex, 0, event);
createIndex++;
@ -866,6 +857,7 @@ export class SyncEventQueue {
typeof event.documentId === "string" &&
blockingDocIds.has(event.documentId)
) {
// eslint-disable-next-line no-restricted-syntax -- splice-by-index here is a reorder, not an item removal
this.events.splice(i, 1);
this.events.splice(createIndex, 0, event);
createIndex++;
@ -907,8 +899,8 @@ export class SyncEventQueue {
this._byLocalPath.delete(previousLocalPath);
}
record.localPath = newLocalPath;
let displacedRecord: DocumentRecord | undefined;
let displacedOldPath: RelativePath | undefined;
let displacedRecord: DocumentRecord | undefined = undefined;
let displacedOldPath: RelativePath | undefined = undefined;
if (newLocalPath !== undefined) {
const displaced = this._byLocalPath.get(newLocalPath);
if (displaced !== undefined && displaced !== record) {