Add file changes

This commit is contained in:
Andras Schmelczer 2026-09-29 22:24:21 +01:00
commit c54128bd27

View file

@ -0,0 +1,79 @@
import type {
DocumentId,
RelativePath,
StoredDatabase
} from "../persistence/database";
import type { LocalChangeCheckpoint } from "../sync-operations/local-change-checkpoint";
import {
applyLocalChange,
getUnappliedChanges,
type LocalChange
} from "../sync-operations/local-changes";
import type { FileWrite } from "./file-operations";
export interface LocalChangeJournal {
readonly entries: readonly LocalChange[];
flush: () => Promise<void>;
}
interface FileChangeSessionOptions {
current: StoredDatabase;
planned: StoredDatabase;
writes: Partial<Record<DocumentId, FileWrite>>;
localChanges: LocalChangeJournal;
checkpoint: LocalChangeCheckpoint;
isProtectedPath: (path: RelativePath) => boolean;
}
/**
* Coordinates one FileOperations.applyChanges call with FileRenamer, tracking the
* working and planned identity maps, content writes, and affected paths. Local
* identity notifications invalidate the plan before the first file mutation;
* afterward, they are replayed into both maps so in-progress changes can finish
* consistently. The caller applies filesystem operations and commits the planned
* metadata; the session does not provide rollback.
*/
export class FileChangeSession {
public readonly current: StoredDatabase;
public readonly planned: StoredDatabase;
public readonly writes: Partial<Record<DocumentId, FileWrite>>;
public readonly affectedPaths = new Set<RelativePath>();
private mutationsStarted = false;
public constructor(private readonly options: FileChangeSessionOptions) {
this.current = options.current;
this.planned = options.planned;
this.writes = options.writes;
}
public abortIfStale(): void {
if (this.mutationsStarted) {
return;
}
this.options.checkpoint.assertUnchanged();
}
public beforeFileMutation(): void {
this.abortIfStale();
this.mutationsStarted = true;
}
public async replayLocalChanges(): Promise<void> {
const { localChanges, isProtectedPath } = this.options;
await localChanges.flush();
this.abortIfStale();
for (const change of getUnappliedChanges(
this.planned,
localChanges.entries
)) {
for (const state of [this.current, this.planned]) {
applyLocalChange(state, change, isProtectedPath);
state.lastAppliedLocalChangeId = change.changeId;
}
}
}
}