import { rename, rm } from 'node:fs/promises'; /** * Write a file so that its final name never holds a partial one. * * ── Why every write into a user's tree has to do this ── * * `Bun.write(path, data)` streams to the name the file will end up with, so for as long as the write * takes, that name exists holding a fragment — and anything reading the folder cannot tell. The failure * is silent rather than loud: a truncated m4a decodes fine, it is simply short. A commander transcribing * recordings as they land would produce a clean transcript of the first few seconds of a two-minute * recording and report success. * * A rename within one directory is atomic, so afterwards the final name either does not exist or is a * complete file. Every consumer gets that for free — a watcher, an agent, the phone's own upload * verification — instead of each inventing a heuristic like "the size stopped changing", which cannot * tell a finished write from a stalled one. * * ── Why it is a function rather than a rule ── * * It was a rule, applied at the two upload routes on 2026-08-24, and by the next morning the platform * had a dozen other writes that put files in the same folders — an OCR result beside its image, a * download landing, a save from the editor — every one of them straight to the final name. A guarantee * that holds for two of fourteen paths is not a guarantee, and the next person to add a fifteenth would * have had nothing to copy. * * ── `.part`, visibly ── * * A leftover should be VISIBLE, named for what it was going to be, next to where it belongs — not a * dotfile. Nothing sweeps them up: a job deleting files it did not create is a worse trade than a * fragment somebody can see. Only an interrupted PROCESS leaves one, since a failed write cleans up * after itself below. */ /** What `Bun.write` accepts, spelled out — `Parameters[1]` resolves to the first * overload only, which is `BunFile` and rejects the string and Buffer every caller actually passes. */ type WritableData = string | Blob | ArrayBufferView | ArrayBufferLike | Response; export async function writeAtomic(path: string, data: WritableData): Promise { // No mkdir: `Bun.write` creates the parent chain itself (`createPath` defaults on), which is why the // callers this replaced could drop theirs too. const partPath = `${path}.part`; try { // Bun.write's overloads do not union cleanly — every one of them accepts this at runtime, and the // signature is a set of alternatives rather than one parameter that takes them all. await Bun.write(partPath, data as Parameters[1]); await rename(partPath, path); } catch (err) { await rm(partPath, { force: true }).catch(() => {}); throw err; } }