Skip to content

Let a caller take entries as they parse, instead of holding the catalogue - #8

Merged
ralyodio merged 1 commit into
mainfrom
playlist-size-cap
Sep 7, 2026
Merged

Let a caller take entries as they parse, instead of holding the catalogue#8
ralyodio merged 1 commit into
mainfrom
playlist-size-cap

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Published as @profullstack/player@0.7.0. genrewatch and tipoffwatch both pin it.

Why

parseM3uStream never held the file, but it did hold every entry it kept. On the list that prompted this — 583MB, roughly 2.6 million entries — that array alone is more heap than the container has. The byte ceiling the two sites enforce upstream was really guarding this array, so it was a heap ceiling wearing a disguise.

What changed

  • onEntries on parseM3uStream: takes what each chunk produced, then the parser forgets it. Awaited rather than called per entry, because push is synchronous and a consumer writing to a database has to be able to make the parse wait. That is what bounds an import to one batch of memory whatever the size of the list.
  • drain() on createM3uParser: the same thing for the line-at-a-time form.
  • kept counts across drains, so a drained parser does not forget it was full and start the ceiling again from zero.
  • max of 0 or NaN now means unlimited, not keep-nothing. An unset PLAYLIST_MAX_CHANNELS parses to 0, and reading that literally would turn a missing config line into an import that succeeds and stores no channels — the one failure mode nothing reports.

MAX_CHANNELS still defaults to 300,000 for callers that pass no max; both sites now pass their own.

Tests

125 pass, tsc --noEmit clean, build clean. New coverage: entries handed over and forgotten; the peak held is a chunk rather than the list; onEntries is not raced by the parse; a rejection from it aborts; the last entry (which lands on the flushed tail after the final chunk is drained) still arrives.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Tn2NNUBNaAXJxZvq9bZRnH

…ogue

parseM3uStream never held the file, but it did hold every entry it kept,
and on the list that prompted this -- 583MB, ~2.6 million entries -- that
array alone is more heap than the container has. The byte ceiling the
sites enforce upstream existed largely to keep this array survivable, so
it was really a heap ceiling wearing a disguise.

`onEntries` takes what each chunk produced and the parser forgets it. It
is awaited rather than called per entry because `push` is synchronous and
a consumer writing to a database has to be able to make the parse wait;
that is what bounds an import to one batch of memory whatever the size of
the list. `drain()` is the same thing on the line-at-a-time parser, and
`kept` counts across drains so a drained parser does not forget it was
full and start the ceiling again from zero.

`max` of 0 or NaN now means unlimited rather than keep-nothing. An unset
PLAYLIST_MAX_CHANNELS parses to 0, and reading that literally would turn
a missing config line into an import that succeeds and stores no
channels, which is the one failure mode nothing reports.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tn2NNUBNaAXJxZvq9bZRnH
@ralyodio
ralyodio merged commit 6798506 into main Sep 7, 2026
3 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