--- a/dsh-session-persistence-jsonl/lib/index.js +++ b/dsh-session-persistence-jsonl/lib/index.js @@ -0,0 +0,0 @@ import z from "@deepseek-ai/schemastery"; import { readdirSync } from "node:fs"; -import { link, mkdir, mkdtemp, open, readFile, readdir, realpath, rm, stat, truncate } from "node:fs/promises"; +import { link, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, truncate } from "node:fs/promises"; import { dirname, join, parse, resolve, toNamespacedPath } from "node:path"; import { performance } from "node:perf_hooks"; import { scheduler } from "node:timers/promises"; @@ -0,0 +0,0 @@ const api = await win32(); if (api.moveFileExW(toNamespacedPath(existing), toNamespacedPath(replacement), MOVEFILE_WRITE_THROUGH) === 0) throw win32Error("MoveFileExW", api.getLastError(), existing, replacement); } +async function replaceFileWin32(existing, replacement) { + const api = await win32(); + if (api.moveFileExW(toNamespacedPath(existing), toNamespacedPath(replacement), 1 | MOVEFILE_WRITE_THROUGH) === 0) throw win32Error("MoveFileExW", api.getLastError(), existing, replacement); + } + /** * Create `target` and its missing ancestors with durable Windows namespace * publication. Each missing directory is first created as a random staging @@ -0,0 +0,0 @@ append(id, events) { return this.coordinator.append(id, events); } + truncate(id, atSeq) { + return this.coordinator.truncate(id, atSeq); + } prepare(id, signal) { return this.coordinator.prepare(id, signal); } @@ -0,0 +0,0 @@ await handle.close(); } } + + /** + * Rewrite the stored log keeping exactly the provided events (durable truncate). + * Serialized by the coordinator (which flushes write-behind first), so no concurrent + * append can interleave; we overwrite the file in place with the kept prefix. + */ + async rewriteStored(meta, events) { + const path = logPath(this.root, meta.cwd, meta.id, this.compression); + const content = await this.encodeMaterialization(meta, events); + const handle = await open(path, "w"); + try { + await handle.writeFile(content); + await handle.sync(); + } finally { + await handle.close(); + } + } + /** Truncate the log file to `offset` bytes and fsync (discard the crash tail). */ async repair(meta, offset) { const path = logPath(this.root, meta.cwd, meta.id, this.compression);