Skip to content

feat(fs): compiled ReadStream/WriteStream event+option parity (#980, part 2 of 2)#1009

Merged
nickna merged 1 commit into
mainfrom
wrk/issue-980-fs-streams
Jun 29, 2026
Merged

feat(fs): compiled ReadStream/WriteStream event+option parity (#980, part 2 of 2)#1009
nickna merged 1 commit into
mainfrom
wrk/issue-980-fs-streams

Conversation

@nickna

@nickna nickna commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Completes #980 (epic #968's final child). Part 1 (the interpreter half) merged via #1008; this PR is part 2 — the compiled $FsReadStream/$FsWriteStream rewrite, bringing the compiled backend to parity with the interpreter.

What changed

  • $FsReadStream: emits open/ready/close via an OnListenerAdded replay (exactly how the base $Readable already replays end — no event loop); chunks by highWaterMark (multiple data events); honors fd/start/end/encoding/emitClose/autoClose; adds Pending/bytesRead.
  • $FsWriteStream: reparented from object onto $EventEmitter (was a non-emitter with a no-op On stub) — now emits open/ready/finish/close, honors flags/fd/start/autoClose/emitClose, Write handles byte[]/$Buffer/string so pipe() moves content; adds Pending/bytesWritten.

Why replay, not deferral

The compiled entry point doesn't run the event loop for a raw $EventLoop.Scheduled action in a top-level program (a #971-family quiescence issue — the action provably never fired), so deferral was abandoned in favor of replay, which needs no event loop.

Verification

8 dual-mode parity tests (read events+chunking+bytesRead, emitClose+start/end, write events+bytesWritten+content, pipe read→write). Byte-identical interp==compiled, standalone preserved. Full suite 14480/0; TS conformance unchanged.

Minor documented divergences: compiled reads eagerly (pending=false right after createReadStream vs interp true); AbortSignal abort is interpreter-only here (compiled AbortSignal limitation, #985). Merging this completes epic #968.

…part 2 of 2)

Completes #980 — brings the compiled $FsReadStream/$FsWriteStream to parity with
the interpreter (epic #968's final child). Achieved via a replay approach rather
than event-loop deferral: the compiled entry point does not run the loop for a
raw $EventLoop.Schedule'd action in a top-level program (a #971-family quiescence
issue), so deferral never fired. Instead, lifecycle events are emitted at creation
and re-emitted to late listeners via an OnListenerAdded override — exactly how the
base $Readable already replays 'end'. No event loop involved.

$FsReadStream: emits open/ready/close (replay) in Node order; reads honoring
fd/start/end/encoding into highWaterMark chunks (multiple 'data' events via the
base buffer); honors autoClose/emitClose; adds Pending; option parsing handles
$Undefined-for-absent (GetProperty returns $Undefined, not null).

$FsWriteStream: reparented from `object` onto $EventEmitter (was a non-emitter
with a no-op On stub) — now a real emitter with open/ready/finish/close; honors
flags/fd/start/autoClose/emitClose; Write handles byte[]/$Buffer/string so pipe()
moves content; adds Pending/bytesWritten.

8 dual-mode parity tests (read events+chunking+bytesRead, emitClose+start/end,
write events+bytesWritten+content, pipe). Byte-identical interp==compiled,
standalone preserved. Full suite 14480/0; TS conformance unchanged.

Known minor divergence: compiled reads eagerly (pending=false right after
createReadStream) vs interp's deferred read (pending=true until the tick);
AbortSignal abort is interpreter-only in this path (compiled AbortSignal, #985).
@nickna nickna merged commit 1eb128d into main Jun 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant