Skip to content

feat(net): simplify JavaScript API - #3815

Merged
kixelated merged 4 commits into
mainfrom
quest/m1/api-js-net
Sep 20, 2026
Merged

kixelated merged 4 commits into
mainfrom
quest/m1/api-js-net

Conversation

@kixelated

@kixelated kixelated commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

Problem

@moq/net exposed overlapping names, positional connection arguments, ad-hoc duration and range types, and transport-only operations on public handles. Those shapes obscured ownership and forced consumers to assemble lower-level request loops themselves. Connection pooling also kept the first requested linger and dropped delay defaults when callers supplied only part of the backoff configuration.

Approach

  • Consolidate errors, connection properties, origin requests, durations, and subscription ranges around one reviewed API.
  • Move transport-only broadcast, announce, origin, and bandwidth operations behind package-private wire views.
  • Migrate every JavaScript consumer and the public documentation, including publish's static-track model.
  • Fix shared linger selection and partial delay merging with regression coverage, remove the obsolete discovery host list, and retire the completed quest.

Impact

  • Errors: SessionError, StreamError, StreamErrorOptions, NotFound, and Lagged move under Error as Error.Session, Error.Stream, Error.StreamOptions, Error.NotFound, and Error.TooFarBehind. Numeric error codes remain top-level.
  • Connections: connect(url, props?) becomes connect({ url, ... }); accept(transport, url, props?) becomes accept({ transport, url, ... }); WebSocketOptions becomes WebSocketProps; Connection.Delay becomes Connection.Backoff; and public configuration durations use Time.Milli.
  • Established connections: Established.consume() and Established.announcedBroadcast() are removed in favor of the origin request surface.
  • Origins: consumer Origin.Request becomes Origin.Requesting; incoming BroadcastRequest becomes Origin.Request; and Table.request(path) becomes request(path, { announced? }).
  • Subscriptions: startGroup and endGroup become the typed inclusive/exclusive Groups range, and maxAge uses Time.Milli.
  • Encapsulation: low-level origin, announcement, broadcast, track, and bandwidth transport operations become package-private rather than public API.
  • Discovery: every host is discovery-capable by default; the hard-coded exclusion list is removed.
  • Consumers: all in-repository packages, examples, and documentation are migrated. External consumers must update for these breaking TypeScript API changes.
  • Wire: no protocol framing, message, enum, catalog encoding, or version-negotiation changes.

Alternatives

Compatibility aliases or parallel methods would preserve the ambiguous surface through the release. Keeping transport operations public would also expose implementation details the consumer API does not need. This PR makes the breaking cleanup directly before release instead.

Follow-ups

Release notes should call out that importing the Error namespace may require an alias such as Error as NetError to avoid colliding with the JavaScript global. No code follow-up is required.

Validation

  • nix develop --command just check
  • nix develop --command just test
  • @moq/net: 825 tests passed
  • Affected publish, watch, room, hang, binary, JSON, and browser smoke suites passed

(written by GPT-5)

@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 0eb7d055-ef00-4ec2-86cc-799a5694f82b

📥 Commits

Reviewing files that changed from the base of the PR and between 8a6a05f and ccb8b02.

📒 Files selected for processing (80)
  • demo/web/src/publish.ts
  • demo/web/src/stats.ts
  • doc/lib/js/net.md
  • js/binary/src/snapshot/consumer.ts
  • js/binary/src/snapshot/producer.ts
  • js/binary/src/snapshot/snapshot.test.ts
  • js/binary/src/stream/stream.test.ts
  • js/clock/src/main.ts
  • js/hang/src/container/consumer.outoforder.test.ts
  • js/hang/src/container/consumer.test.ts
  • js/hang/src/container/consumer.ts
  • js/hang/src/container/track.ts
  • js/json/src/snapshot/compression.test.ts
  • js/json/src/snapshot/producer.ts
  • js/json/src/snapshot/snapshot.test.ts
  • js/json/src/stream/codec.test.ts
  • js/json/src/stream/stream.test.ts
  • js/json/src/window/consumer.ts
  • js/json/src/window/window.test.ts
  • js/moq-boy/src/game.ts
  • js/net/examples/connection.ts
  • js/net/examples/discovery.ts
  • js/net/examples/publish.ts
  • js/net/examples/subscribe.ts
  • js/net/examples/wait.ts
  • js/net/src/announce.ts
  • js/net/src/announced.test.ts
  • js/net/src/announced.ts
  • js/net/src/bandwidth_api.ts
  • js/net/src/broadcast.test.ts
  • js/net/src/broadcast.ts
  • js/net/src/connection/accept.ts
  • js/net/src/connection/bandwidth.test.ts
  • js/net/src/connection/connect.test.ts
  • js/net/src/connection/connect.ts
  • js/net/src/connection/established.ts
  • js/net/src/connection/forward.test.ts
  • js/net/src/connection/forward.ts
  • js/net/src/connection/index.ts
  • js/net/src/connection/pool.test.ts
  • js/net/src/connection/pool.ts
  • js/net/src/connection/reload.test.ts
  • js/net/src/connection/reload.ts
  • js/net/src/connection/stats.test.ts
  • js/net/src/consume.ts
  • js/net/src/error.ts
  • js/net/src/errors.ts
  • js/net/src/group.test.ts
  • js/net/src/group.ts
  • js/net/src/ietf/connection.ts
  • js/net/src/ietf/publisher.test.ts
  • js/net/src/ietf/publisher.ts
  • js/net/src/ietf/subscriber.test.ts
  • js/net/src/ietf/subscriber.ts
  • js/net/src/index.ts
  • js/net/src/integration.test.ts
  • js/net/src/internal.ts
  • js/net/src/lite/connection.ts
  • js/net/src/lite/publisher.test.ts
  • js/net/src/lite/publisher.ts
  • js/net/src/lite/subscriber.ts
  • js/net/src/origin.test.ts
  • js/net/src/origin.ts
  • js/net/src/track.test.ts
  • js/net/src/track.ts
  • js/net/src/wire.ts
  • js/publish/src/broadcast.test.ts
  • js/publish/src/broadcast.ts
  • js/publish/src/catalog.ts
  • js/room/src/metadata.ts
  • js/room/src/room.test.ts
  • js/watch/src/broadcast.test.ts
  • js/watch/src/broadcast.ts
  • js/watch/src/media.test.ts
  • js/watch/src/media.ts
  • js/watch/src/text/renderer.ts
  • quest/m1/README.md
  • quest/m1/api-js-net.md
  • quest/m1/api-review-gate.md
  • quest/m2/js-net-additive.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kixelated

Copy link
Copy Markdown
Collaborator Author

Recommendation: MERGE

Reviewed head ccb8b02ccfd9acab908a3229e1fa17041a7d6c7b (~80 files, net −193 lines). Wire/protocol unchanged; this is a TypeScript API-shape and encapsulation cleanup with in-repo migrations and a couple of real connection-pool fixes.

Positive improvement?

Yes. The public surface is clearer and smaller:

  • Errors consolidate under Error.* (Session / Stream / TooFarBehind, …) with codes still top-level — matches how apps already alias (Error as NetError).
  • Connect/accept take one props object ({ url, … }); durations use Time.Milli; subscription ranges use Groups instead of ad-hoc startGroup/endGroup.
  • Origin consumption unifies on Origin.request(path, { announced? })Requesting, replacing the separate Announce.Broadcast / announcedBroadcast / blind-request paths.
  • Transport-only ops (subscribe/resolveTrackInfo/fetchGroup/requested, origin receive/attach/answer, session consume) move behind package-private WeakMap wire views (wire.ts) — good encapsulation without splitting implementations.

Publish switching catalog/media to static tracks (demand-gated encoder handles) removes the request-loop boilerplate and matches how fan-out actually works.

Worth the complexity?

Yes. The WeakMap wire registry + internal hooks look heavier than “just make methods private,” but they preserve one shared implementation for lite/ietf while keeping transport ops off the public handle. Net code shrinks, docs/examples move with the API, and every in-repo consumer is updated in the same PR. Completing/removing the m1/m2 quest docs is consistent with shipping the reviewed API.

Different approach?

Not needed. Soft-deprecation / dual exports would prolong the messy surface this PR is closing. Breaking on main is intentional and acceptable here because consumers and docs land together and the wire stays stable. Continuing to expose transport ops on public handles would keep the wrong abstraction.

Bug fixes that support merge

  • Shared linger: pooled connections now take Math.max of requested lingers instead of “first dialer wins.”
  • Partial delay merge: delay: { timeout: 0, ...props.delay } so a partial backoff object no longer drops defaults.
  • Blind answers: RequestSlot.blind + forward loop skip/withdraw when no blind demand — avoids answering paths nobody is waiting on.
  • Dropping the hardcoded NO_DISCOVERY_HOSTS list is the right default now that discovery is opt-out via props.

Nits (non-blocking)

  • Importing the Error namespace forces Error as NetError (or similar) to avoid colliding with the global — already reflected in migrations; worth a one-liner in the changelog/release notes.
  • CI was still pending at review time; author reports @moq/net 823 pass and affected packages green locally, with an unrelated @moq/pattern timeout on full just test. Confirm CI green before merge if not already.

Verdict: MERGE — clear API win, encapsulation is justified, migrations/docs are complete, protocol risk is low.

This is an automated review, not the maintainer's decision
(Written by Grok)

@moq-bot

moq-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor
  1. Breaking @moq/net 0.3.5 API targets main (js/net/package.json:4, AGENTS.md#Development). Retarget PR to dev and rebase onto origin/dev.
  2. New public re-export file has no module docs (js/net/src/bandwidth_api.ts:1, js/CLAUDE.md#Conventions). Add @module block and one line doc per export.
  3. PR body lacks required sections (CONTRIBUTING.md#PRs). Rewrite to Problem, Approach, Impact, Alternatives, Follow-ups.

Verdict: request changes
(Written by Muse Spark)

New%20session%20-%202026-09-20T15%3A22%3A03.098Z
opencode session  |  github run

kixelated and others added 3 commits September 20, 2026 13:07
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
@moq-bot

moq-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor
  1. Breaking @moq/net API targets main with version 0.3.5 (js/net/package.json:4, AGENTS.md#Development). Retarget PR to dev and rebase onto origin/dev.
  2. Shared linger Math.max fix has no failing-first test (js/net/src/connection/pool.ts:438, AGENTS.md#Required). Add test with short then long linger proving long wins after last release.
  3. Private loop partial delay merge has no failing-first test (js/net/src/connection/pool.ts:249, AGENTS.md#Required). Add test with partial delay proving timeout stays 0 not 10000ms.
  4. Doc links old error alias (js/net/src/track.ts:1283, js/CLAUDE.md#Conventions). Change {@link Lagged} to {@link TooFarBehind}.

Verdict: request changes
(Written by Muse Spark)

New%20session%20-%202026-09-20T20%3A10%3A35.935Z
opencode session  |  github run

Co-Authored-By: OpenAI Codex <codex@openai.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge pass complete:

  • rebased onto current main;
  • added the missing @module documentation for the public bandwidth entry point;
  • added regressions proving the longest shared linger wins and private partial delay settings retain the unlimited retry timeout;
  • corrected the stale Lagged documentation link to TooFarBehind;
  • rewrote the PR description into the required Problem, Approach, Impact, Alternatives, and Follow-ups structure;
  • verified nix develop --command just check, nix develop --command just test, the focused pool tests, @moq/net type checks, and all 825 @moq/net tests.

The automated request to retarget breaking API work to dev is intentionally waived for this final pre-release pass per maintainer direction.

(written by GPT-5)

@kixelated
kixelated enabled auto-merge (squash) September 20, 2026 20:18
@kixelated
kixelated merged commit 990a96a into main Sep 20, 2026
4 checks passed
@kixelated
kixelated deleted the quest/m1/api-js-net branch September 20, 2026 20:24
@moq-bot

moq-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor
  1. Concept doc names removed JS error exports (doc/concept/moq-lite.md:194, AGENTS.md#Required). Update to Error.Session and Error.Stream carrying SessionCode/StreamCode.
  2. Binary stream module doc names removed Lagged error (js/binary/src/stream/index.ts:10, AGENTS.md#Required). Change to TooFarBehind via public Error.TooFarBehind.

Verdict: request changes
(Written by Muse Spark)

New%20session%20-%202026-09-20T20%3A19%3A05.449Z
opencode session  |  github run

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