Safely overwrite a JSON config
Every project ends up with a config file that something else is watching — a dev server, a linter, your own hot-reload. You need to flip a field without ever letting a truncated, half-written JSON file be observed.
The recipe
Section titled “The recipe”The facade’s mergeJson reads, patches, and writes in one call. Pass
{ atomic: true } so the write goes through a temp file and an atomic rename.
import { fs } from "@warlock.js/fs";
await fs.files.mergeJson( "config.json", { features: { "dark-mode": true }, updatedAt: new Date().toISOString() }, { atomic: true },);Any watcher sees the old config or the new one, never a half-written one. A crash between read and write leaves the file untouched.
When the change isn’t a simple patch
Section titled “When the change isn’t a simple patch”mergeJson spreads a partial over the top level. When you need to compute the
new value from the old one — bump a counter, push into an array — reach for
editJson:
import { fs } from "@warlock.js/fs";
await fs.files.editJson("config.json", (cfg) => ({ ...cfg, builds: cfg.builds + 1,}), { atomic: true });What atomic does not do
Section titled “What atomic does not do”atomic makes the write safe to observe. It does not lock the
read-modify-write pair — two callers can both read the same starting state and
one write silently wins. @warlock.js/fs has no file locking by design.
If lost updates matter, serialize the whole operation:
- In-process — an
async-aware mutex around themergeJsoncall. - Cross-process —
@warlock.js/cache’scache.lock(key, ttl, fn), backed by Redis or Postgres.
Related
Section titled “Related”- Patch a JSON config — deep merges and field bumps.
- Write atomically — the mechanism underneath
{ atomic: true }. - The fs facade — the full
fs.files.*surface.