writePositionMap: tmp + rename — the same crash-safety rewriteLedgers uses
Flag routed from the UI stream's rename-shuffle fix (a9339b3): the
positions ledger was the one remaining plain writeFileSync among the
rewritable ledgers — a torn write (crash mid-write) could replace it
with a half-written file, and readPositionMap parses a failure to {}
(honest empty, but the resting layout would be silently lost). The
tmp+rename pattern from rewriteLedgers, ~4 lines up the file: write a
uniquely-named temp, rename over the ledger — a crash leaves either the
old ledger intact or the new one complete, plus an orphan .tmp-*.
879/879 tests, typecheck/lint/build green; live-verified via the node
deletion's positions prune (no temp residue, valid JSON).
This commit is contained in:
+15
-5
@@ -304,11 +304,21 @@ function readPositionMap(): Record<string, Position> {
|
||||
|
||||
function writePositionMap(positions: Record<string, Position>): void {
|
||||
fs.mkdirSync(path.dirname(POSITIONS_FILE), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
POSITIONS_FILE,
|
||||
JSON.stringify({ positions, updatedAt: new Date().toISOString() }, null, "\t"),
|
||||
"utf-8",
|
||||
);
|
||||
// Tmp + rename — the same crash-safety rewriteLedgers uses two blocks
|
||||
// up (a9339b3 UI flag): a torn write must never replace the ledger
|
||||
// with a half-written file the reader parses as empty.
|
||||
const tmp = `${POSITIONS_FILE}.tmp-${crypto.randomUUID().slice(0, 8)}`;
|
||||
try {
|
||||
fs.writeFileSync(tmp, JSON.stringify({ positions, updatedAt: new Date().toISOString() }, null, "\t"), "utf-8");
|
||||
fs.renameSync(tmp, POSITIONS_FILE);
|
||||
} catch (err) {
|
||||
try {
|
||||
fs.unlinkSync(tmp);
|
||||
} catch {
|
||||
/* the rename either already happened or the tmp never existed */
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const isPosition = (v: unknown): v is Position =>
|
||||
|
||||
Reference in New Issue
Block a user