Skip to content

#1271 Extract the duplicated atomic-write sequence into one helper - #1306

Merged
williamthorsen merged 4 commits into
mainfrom
1271
Aug 13, 2026
Merged

#1271 Extract the duplicated atomic-write sequence into one helper#1306
williamthorsen merged 4 commits into
mainfrom
1271

Conversation

@williamthorsen

@williamthorsen williamthorsen commented Aug 13, 2026

Copy link
Copy Markdown
Owner

What

Extracts duplicated code into a shared writeAtomic function that atomically writes content to a temp file and then renames it. Also fixes an issue where a temporary file could be left on disk after a failed write.

Why

A change to kb's atomic-write contract had to be applied twice, once per copy, and could be applied once by mistake; the most recent such change was made identically in each. Neither call site's suite reached the failure paths, so the cleanup behavior went unguarded wherever it lived.

Details

🐛 Bug fixes

  • writeAtomic's writeFile call sits inside the try, so a write that fails partway takes the same unlink-and-rethrow path as a failed rename; writeNote and writeTaxonomy both inherit it.

♻️ Refactoring

  • filesystem/write-atomic.ts carries the sequence: a randomBytes(8) temp suffix beside the target, writeFile, rename, best-effort unlink on failure, rethrow. Its docblock records why the temp file is a sibling, which is that the rename is atomic only within one filesystem.
  • filesystem/index.ts is untouched, so the module reaches no published subpath.
  • note-io/write-note.ts keeps renderNote and delegates the write; taxonomy/write-taxonomy.ts loses its private writeAtomic copy along with the "matching note-io" framing in its docblock.

🧪 Tests

  • filesystem/__tests__/write-atomic.unit.test.ts covers five cases: content lands at the target, the temp file is a sibling of the target, and three failure paths, namely a failed write, a failed rename, and a failed rename whose cleanup also fails, where the rename error is the one that reaches the caller.
  • Failures are forced through a passthrough vi.mock('node:fs/promises') that overrides rename and unlink per test, leaving every other operation live.
  • Both call sites' existing suites are unchanged.

Closes #1271

Adds `writeAtomic` at `packages/kb/src/filesystem/write-atomic.ts`, holding the temp-file-plus-rename sequence that `note-io/write-note.ts` and `taxonomy/write-taxonomy.ts` each spell out for themselves. The helper is internal: `filesystem/index.ts` does not re-export it, keeping it off the `@williamthorsen/kb/filesystem` entry point.

Its `__tests__` sibling covers the failure paths neither call site's suite reaches: a `rename` failure removes the staged temp file and rethrows, and a cleanup failure on top of that still surfaces the rename error. The suite also pins the temp file as a sibling of the target, since a cross-directory rename can cross filesystems, where it is not atomic.
Reads the temp path `writeAtomic` staged by narrowing the recorded `writeFile` argument to a string, so a `FileHandle` or `URL` argument fails the check rather than reaching an assertion as `[object Object]`.
Points `writeNote` and `writeTaxonomy` at `filesystem/write-atomic.ts` and deletes the two copies of the temp-file-plus-rename sequence they each carried. kb spells the atomic-write contract once, so a change to it can no longer be made in one copy and missed in the other.

`writeNote` keeps rendering the note and hands the rendered content to the helper.
Fixes an orphan `.tmp` file left beside the target when a `writeAtomic` write fails partway, as on a full disk. Cleanup guarded the `rename` alone, so a failed `writeFile` had nothing to reap what it had already created. The `writeFile` call now sits inside the same `try`, and both `writeNote` and `writeTaxonomy` inherit the fix.
@williamthorsen williamthorsen added refactoring Improvement to code without change in functionality scope:kb labels Aug 13, 2026
@github-actions

Copy link
Copy Markdown

Dependency audit

Production dependency audit passed.

@williamthorsen williamthorsen self-assigned this Aug 13, 2026
@williamthorsen
williamthorsen marked this pull request as ready for review August 13, 2026 14:17
@williamthorsen
williamthorsen merged commit 5fc4309 into main Aug 13, 2026
3 checks passed
@williamthorsen
williamthorsen deleted the 1271 branch August 13, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoring Improvement to code without change in functionality scope:kb

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extract kb's duplicated atomic-write sequence into a single helper

1 participant