Skip to content

refactor(VideoInfo): return a WatchNextContinuation instead of mutating state - #1239

Open
MFA-G wants to merge 1 commit into
LuanRT:mainfrom
MFA-G:feat/watch-next-continuation-class
Open

refactor(VideoInfo): return a WatchNextContinuation instead of mutating state#1239
MFA-G wants to merge 1 commit into
LuanRT:mainfrom
MFA-G:feat/watch-next-continuation-class

Conversation

@MFA-G

@MFA-G MFA-G commented Aug 13, 2026

Copy link
Copy Markdown

Fixes #1237

What

VideoInfo#getWatchNextContinuation() mutated the instance in place: it overwrote watch_next_feed, reassigned the private #watch_next_continuation, and returned this. As noted in the issue, that pattern doesn't play well with reactive frameworks (Vue and friends) because the object identity stays the same while its contents change underneath.

This adds a WatchNextContinuation class modelled directly on the existing CommentsContinuation / CommentThread#getContinuation() pattern referenced in the issue:

export default class WatchNextContinuation {
  public contents: ObservedArray<YTNode>;

  get has_continuation(): boolean;
  public async getContinuation(): Promise<WatchNextContinuation>;
}

VideoInfo#getWatchNextContinuation() now just builds one from the response and returns it; VideoInfo itself is left untouched.

Two small behavioural notes:

  • The ContinuationItem is filtered out of contents rather than pop()ed off after the fact, so feed items are never mixed with the continuation marker (the old code also relied on it being the last element).
  • An InnertubeError is thrown when AppendContinuationItemsAction is missing, same as before.

I deliberately left VideoInfo's constructor, watch_next_feed, and wn_has_continuation alone — the initial feed still comes from the watch page as it always did, so nothing changes for the first page.

Breaking change

getWatchNextContinuation() used to resolve to the (mutated) VideoInfo. It now resolves to a WatchNextContinuation:

// before
const info = await yt.getInfo(id);
let feed = info.watch_next_feed;
while (info.wn_has_continuation) {
  await info.getWatchNextContinuation();
  feed = info.watch_next_feed; // same object, mutated
}

// after
const info = await yt.getInfo(id);
let page = await info.getWatchNextContinuation();
console.log(page.contents);
while (page.has_continuation) {
  page = await page.getContinuation();
  console.log(page.contents);
}

Note that YTShorts.ShortFormVideoInfo#getWatchNextContinuation() has the same mutation pattern but a different response shape (reel_watch_sequence / ContinuationCommand), so I left it out of this PR to keep the change focused. Happy to follow up on it if you'd like it aligned too.

Testing

  • npx tsc --noEmit -p tsconfig.json — clean
  • npx eslint src/ — clean
  • npm run build:esm and npm run build:parser-map — clean (the new class is exported from src/parser/misc.ts by the generator, as with CommentsContinuation)
  • Ran the built class against a hand-crafted appendContinuationItemsAction payload to verify behaviour end to end:
contents length: 2
types: CompactVideo, CompactVideo
has_continuation: true
no ContinuationItem leaked: true
last page has_continuation: false len: 2
throws on missing action: InnertubeError

The tests/main.test.ts suite hits the live API and the only getWatchNextContinuation case there is the Shorts one, which is unaffected.

…ng state

`VideoInfo#getWatchNextContinuation()` overwrote `watch_next_feed` and the
private continuation token on the existing instance and returned `this`.
That in-place mutation is awkward to work with in reactive frameworks
(e.g. Vue), since the same object identity is reused while its contents
change underneath.

Add a `WatchNextContinuation` class mirroring `CommentsContinuation`:
it holds the parsed `contents`, exposes `has_continuation`, and returns a
brand new instance from `getContinuation()`. The `ContinuationItem` is
filtered out of `contents` instead of being popped off the array, so the
feed items are never mixed with the continuation marker.

BREAKING CHANGE: `VideoInfo#getWatchNextContinuation()` now resolves to a
`WatchNextContinuation` instead of the mutated `VideoInfo`. Read the new
items from `.contents` and keep calling `.getContinuation()` on the
returned object to page further.

Closes LuanRT#1237
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(VideoInfo.ts) Stop mutating watch_next_feed when fetching continuations

1 participant