Files
deepseek-harness/packages/storage/storage-json/src/atomic.ts
T
imccyu 1529be6fd4 feat(storage): json backend — one human-readable file per unit
Atomic whole-file replacement (same-dir temp + fsync + rename + parent
fsync); the in-memory unit state is authoritative and the file is always
the current net state, pretty-printed. Missing files open as empty units
and materialize on first write; foreign or unparsable files reject with
malformed-medium, stored-version drift with version-mismatch.
2026-07-25 11:08:04 +08:00

54 lines
1.8 KiB
TypeScript

/**
* Atomic whole-file replacement for the JSON backend.
*
* Publish protocol: write a same-directory temp file, fsync it, then
* `rename()` over the target. Rename is an atomic replace on POSIX and on
* Windows (libuv maps it to `MoveFileExW(..., MOVEFILE_REPLACE_EXISTING)`),
* and replacement is the intended semantic here — unlike the session-log
* backend's link()+unlink() no-clobber protocol, a unit file has exactly one
* writer per process and last-write-wins is correct. After the rename the
* parent directory is fsynced on POSIX so the new entry is crash-durable.
* @module @deepseek-ai/dsh-storage-json/src/atomic
*/
import { open, rename, rm } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { randomUUID } from 'node:crypto'
/**
* Durably replace `path` with `data`.
* @param path - Absolute target file path.
* @param data - Full new file content.
* @returns resolution after the replacement is crash-durable.
*/
export async function writeAtomic(path: string, data: string): Promise<void> {
const tmp = join(dirname(path), `.${randomUUID()}.tmp`)
try {
const handle = await open(tmp, 'wx', 0o600)
try {
await handle.writeFile(data, 'utf8')
await handle.sync()
} finally {
await handle.close()
}
await rename(tmp, path)
await fsyncDirectory(dirname(path))
} catch (error) {
await rm(tmp, { force: true })
throw error
}
}
/** fsync a POSIX directory so a just-renamed entry is crash-durable. */
/* v8 ignore start -- Windows rejects O_RDONLY directory opens; POSIX coverage exercises this. */
async function fsyncDirectory(path: string): Promise<void> {
if (process.platform === 'win32') return
const handle = await open(path, 'r')
try {
await handle.sync()
} finally {
await handle.close()
}
}
/* v8 ignore stop */