(
filePath: string,
data: string | Uint8Array,
opts: AtomicWriteOptions = {},
)
| 35 | * the temp file is cleaned up and the original target is left untouched. |
| 36 | */ |
| 37 | export async function writeFileAtomic( |
| 38 | filePath: string, |
| 39 | data: string | Uint8Array, |
| 40 | opts: AtomicWriteOptions = {}, |
| 41 | ): Promise<void> { |
| 42 | const dir = path.dirname(filePath); |
| 43 | const tmp = path.join(dir, `.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${seq++}`); |
| 44 | const mode = opts.mode ?? 0o644; |
| 45 | |
| 46 | let fh: fs.FileHandle | undefined; |
| 47 | try { |
| 48 | // 'wx' → O_CREAT|O_EXCL: the temp name is unique, so this never clobbers. |
| 49 | fh = await fs.open(tmp, 'wx', mode); |
| 50 | await fh.writeFile(data, { encoding: opts.encoding ?? 'utf-8' }); |
| 51 | await fh.sync(); // flush contents to disk before the rename |
| 52 | await fh.close(); |
| 53 | fh = undefined; |
| 54 | |
| 55 | await fs.rename(tmp, filePath); // atomic replace |
| 56 | |
| 57 | if (opts.fsyncDir !== false) { |
| 58 | // Make the directory entry (the rename) durable. Not supported on every |
| 59 | // platform/fs — best-effort, never fatal. |
| 60 | let dh: fs.FileHandle | undefined; |
| 61 | try { |
| 62 | dh = await fs.open(dir, 'r'); |
| 63 | await dh.sync(); |
| 64 | } catch { |
| 65 | /* directory fsync unsupported here — acceptable */ |
| 66 | } finally { |
| 67 | await dh?.close().catch(() => {}); |
| 68 | } |
| 69 | } |
| 70 | } catch (err) { |
| 71 | if (fh) await fh.close().catch(() => {}); |
| 72 | await fs.unlink(tmp).catch(() => {}); // don't leave a turd behind |
| 73 | throw err; |
| 74 | } |
| 75 | } |
no outgoing calls
no test coverage detected