Skip to content

Bring Reader into conformance with other resumable decoders - #6

Merged
namingbe merged 12 commits into
masterfrom
claude/friendly-thompson-f0ving
Jun 10, 2026
Merged

Bring Reader into conformance with other resumable decoders#6
namingbe merged 12 commits into
masterfrom
claude/friendly-thompson-f0ving

Conversation

@namingbe

@namingbe namingbe commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

No description provided.

claude added 12 commits June 9, 2026 17:36
On input not ending in the row-terminating empty line, Reader._finalize
used to emit the incomplete trailing row — the non-resumable behavior,
diverging from the Java/Rust/Scala readers which buffer it. Align with
them: _finalize no longer flushes the partial cell/row buffers, so
readRow() returns null without emitting the tail.

Non-resumable parse() keeps emitting the incomplete tail; existing
Reader tests now use properly terminated input.

Empty-row handling in _processChunk is a separate known issue and is
left untouched here.
Three scaling fixes in the streaming Reader, all the same shape:
batch work instead of paying per smallest unit.

- _processChunk: scan chunks with indexOf and slice whole lines instead
  of appending to a string char by char; a line split across chunks
  accumulates pieces in a list joined once on completion. 16MB cell
  arriving in 8KB chunks: 9.6s -> 0.014s.
- unescape: return early when the cell has no backslash.
- readRow: consume the row queue via head index instead of shift(),
  which is O(n) per call once the queue is large; queue and head reset
  when drained. 1M-row drain went from minutes to 0.37s.

Differential-tested against parse() over the nsv-tests enum+valid
corpora at every chunk-split pair, and the 92MB champernowne fixed
point streamed row-identical.
The row stream emits only terminated rows, and end of stream is final
in Node, so without an accessor the withheld tail would be destroyed.
partialRow() returns a copy of the row in progress — completed cells
plus the unfinished cell run through unescape — or null, queryable at
any point. For any input, readRows() plus partialRow() equals parse().
The streaming layer no longer parses incrementally: it scans each chunk
for the last row boundary (a newline preceded by a newline, with one
carried bit for chunk-spanning pairs and stream start), hands everything
up to it to parse(), and keeps the rest as the raw pending tail. The
Reader can no longer disagree with parse() on row content by
construction.

partialRow() becomes partial(): the raw encoded tail, empty when none.
Consumers who want the truncated row as cells run it through parse()
themselves. For any input and chunking, readRows() concatenated with
parse(partial()) equals parse(input).
_pieces -> _tailPieces, _rowHead -> _rowQueueHead, _done -> _ended.
The carried previous-byte-was-newline bit was redundant with the tail
itself (empty or ending in newline), so it's a derivation now, not a
member. Drop the _processChunk comment essay.
Rope concatenation makes string append O(1), so the fragment list was
only earning its keep by making the line-start check readable without
flattening; carrying that one bit explicitly is cheaper than the list.
_partial mirrors the partial() accessor it backs. Also dedupe the
_finalize doc block left by the merge and rename tests to row terms.
Since the unterminated final row stopped being flushed at end of input,
_finalize was a one-line flag set with a name promising more.
Header described newlines as separators — the spec's named pitfall;
they terminate. Reader class doc claimed per-chunk parsing it no longer
does. partial() moved out of the middle of the read-method family.
readRows/iterator no longer re-start what readRow starts.
@namingbe
namingbe merged commit 3639025 into master Jun 10, 2026
5 checks passed
@namingbe
namingbe deleted the claude/friendly-thompson-f0ving branch June 10, 2026 18:43
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.

2 participants