Alexandro.Net Open Source maintained continuation

Node.js atomic file streams · ISC

@stackline/fs-write-stream-atomic

Stream a complete value into an exclusive adjacent file, keep the old target visible while writes are in flight, and replace it only after physical close—while preserving the established fs-write-stream-atomic@1.0.10 contract.

npm install @stackline/fs-write-stream-atomic

Keep an existing import unchanged with fs-write-stream-atomic: npm:@stackline/fs-write-stream-atomic@1.0.0 in your dependencies.

01 / Publication model

One rename reveals one complete value.

The destination is an ordinary Node.js Writable. Its bytes go to a randomized, exclusively opened sibling path. The previous target remains visible until the temporary stream closes, optional ownership is applied, and one adjacent rename succeeds.

This is atomic visibility on a filesystem that provides atomic same-filesystem rename. It is not file or directory fsync, crash durability, cross-filesystem movement, or a transaction across several files.

02 / Quick start

Forward errors into cleanup.

Use pipeline() when another stream produces the bytes. It destroys the destination on a source failure, allowing the atomic stream to close and remove its temporary file.

const { pipeline } = require('node:stream')
const atomic = require(
  '@stackline/fs-write-stream-atomic'
)

const output = atomic('database.bin', {
  mode: 0o600,
  highWaterMark: 1024 * 1024
})

pipeline(input, output, (error) => {
  if (error) console.error(error)
})

03 / Public API

A Writable with one job.

Open the complete API reference →

createWriteStreamAtomic(filename[, options])

Callable factory

The CommonJS root is callable with or without new. The documented filename is a string and the result extends core Writable.

new WriteStreamAtomic(filename[, options])

Named constructor

Available as a non-enumerable CommonJS property and a named ESM export. The ESM default is the same factory identity.

Writable + file options

Keep flow control

encoding, mode, flags, highWaterMark, callbacks, drain, corking, destruction, and chown: { uid, gid } remain available.

../index./index.js./package.jsonCommonJSESMTypeScript 3.9+

04 / Lifecycle

Finish is after rename.

open reports the temporary file descriptor. Write callbacks and drain preserve ordinary Writable flow control. During finalization, the inner file must close before ownership and rename. Only then can the outer stream emit finish; close follows.

Explicit destroy(), physical stream errors, chown failures, rename failures, and errors forwarded through pipeline() remove the adjacent temporary file. Abrupt process termination can still leave a file behind.

  1. 01
    openExclusive sibling file descriptor
  2. 02
    write callback / drainWritable acceptance and backpressure
  3. 03
    physical closeAll accepted bytes are closed
  4. 04
    chown → renameOwnership first, publication second
  5. 05
    finish → closeComplete replacement is observable

05 / Compatibility details

Preserved where callers can observe it.

Backpressure

When write() returns false, wait for drain. Callbacks report accepted writes and failures.

Concurrency

Contending writers each stage one complete candidate. A whole value wins; bytes are never interleaved.

Append flags

flags: 'a' appends to a new empty temporary file and then replaces the target. It does not append to old target bytes.

Windows EPERM

A rename EPERM counts as success only when SHA-512 comparison proves the target and temporary contents match.

Temporary identity

Process, worker-thread, invocation, and cryptographic random identity combine with exclusive creation.

Production graph

Exact graceful-fs@4.2.11 remains; iferr, imurmurhash, and readable-stream are removed.

06 / Migration

Choose the size of your diff.

Full migration checklist →
Lowest change

Alias the dependency

"fs-write-stream-atomic":
  "npm:@stackline/fs-write-stream-atomic@1.0.0"

Existing require('fs-write-stream-atomic') calls stay unchanged. Commit the manifest and lockfile together.

Explicit adoption

Use the scoped name

import atomic, {
  WriteStreamAtomic
} from
  '@stackline/fs-write-stream-atomic'

Real ESM and first-party TypeScript declarations are additive. The default export remains the callable factory.

Re-test in your application high-water-mark backpressurefinish/close timingmode and ownershipcancellationconcurrent writersWindows replacement

07 / Filesystem boundary

Atomic visibility is not durability.

The caller chooses the target and must enforce the allowed root, ownership policy, permissions, disk limits, cancellation, and trust boundary. Adjacent rename avoids a cross-filesystem move, but it cannot provide multi-file transactions or containment.

No file or directory fsync is performed. A successful event means the renamed bytes are visible, not guaranteed after sudden power loss. Bare .pipe() does not forward source errors; use pipeline() or destroy the destination explicitly.

The exclusive temporary-name correction is defense-in-depth. It is not an assigned vulnerability, security advisory, or CVE claim.

Read the complete safe-use boundary →

08 / Reference files

The contract is inspectable.