Files
import { writeAtomic } from '@adecore/utils/fs';File helpers for Node and Bun. A page cannot import this entry point.
Atomic writes
writeAtomic(target, content, mode, options) writes a temporary file next to the target and renames it over the target. A process that dies halfway leaves a stray temporary file, never half a file. mode defaults to 0o600, readable by the owner only.
await writeAtomic(settingsPath, JSON.stringify(settings, null, 4));A power cut can still leave an empty target on APFS and ext4. With durable set in WriteAtomicOptions, the write flushes the file before the rename and the folder after it. Use it for a file whose loss costs a person their work:
await writeAtomic(draftPath, draft, 0o600, { durable: true });writeAtomicSync does the same without awaiting. Use it on an exit path, or under a watch reload that restarts the module before an awaited write comes back.
Writes to one file still need an order of their own. Run them through a Serializer.
The parts of a write
The steps are exported for code that needs one of them alone:
tempNameFor(target)gives the temporary name. The write opens that file exclusively, so it never writes through a link someone put on that name.replaceSync(temp, target, rename)is the rename on its own.renamedefaults torenameSyncand lets a test refuse it.syncDirectory(path)flushes a folder. It does nothing on Windows, which cannot open a folder for it and journals the rename itself.
Both renames retry a few times when isBusyRename(e) says Windows refused because a scanner or an indexer held the file. That covers EPERM, EBUSY and EACCES, never EMFILE or ENFILE, which mean the process ran out of descriptors.
Small checks
removeFile(path)removes a file and leaves a missing one alone.fileExists(path)follows a link, so a link that leads nowhere does not count.entryExists(path)counts the link itself, also when it leads nowhere.