You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf(js): drop sorted-btree and the Manual clock from every bundle - #8671
Nightly's 30-day size check (10% limit) fails from 11-21: against the 10-22 report, js/net Brotli is 1.59x (98,909 to 157,282 bytes) and every other bundle is 1.40 to 1.48x. The maintainer chose to audit and shrink, then accept the rest as feature growth, without loosening the check.
Approach
I attributed every first-load bundle against the 10-22 Nightly metafiles (nightly-size-history of run 39779729461), counting only the static import closure, as sh/size/bundles.ts does. Two dependencies don't earn their bytes:
sorted-btree (19.4 KB raw, about 5 KB Brotli, in every bundle). It backs the ledger's two ordered indexes, which need only floor/ceil/next/prev lookups, insert, delete, and in-order iteration. The ledger now uses an in-tree Sorted list (js/net/src/util/sorted.ts, about 150 lines). It stores values in chunks of 128 to 512, the way Python's sortedcontainers does, so an insert or delete moves at most one chunk plus the chunk index. A peer that fragments arrivals still costs logarithmic lookups, the property moq-dev/moq#6869 added the tree for. This also removes the CommonJS interop shim.
@moq/clock's Manual test clock (2.3 KB raw in net and hang). It moves from @moq/clock to the existing @moq/clock/test entrypoint. @moq/net's Clock namespace re-export no longer carries it.
Everything else is feature code (below).
Bundle (Brotli)
10-22
main
this PR
change
vs 10-22
js/net
98,909
156,956
151,568
-5,388 (-3.4%)
1.53x
js/hang
119,937
178,006
172,522
-5,484 (-3.1%)
1.44x
js/watch
133,560
196,056
191,129
-4,927 (-2.5%)
1.43x
js/watch-ui
144,409
206,890
201,883
-5,007 (-2.4%)
1.40x
js/publish
131,060
189,445
184,571
-4,874 (-2.6%)
1.41x
The rest is feature growth
About 190 KB of the roughly 200 KB of raw first-load growth is js/net, spread over about 320 commits since 10-22 (74 of them feat). The largest per-file changes:
routes, warm routes, and route-key proofs (#8443, #8457, #8615)
Outside js/net, the watch player gained 12 KB raw, the publish encoder 9 KB, and @moq/ui 5.5 KB, all features. Zod shrank, and async-mutex, uuid, and @svta/cml-iso-bmff left the first load.
What this means for Nightly
The 30-day check still fails until a post-growth report becomes its anchor. Nightly recorded no report from 11-08 to 11-12, so the anchor stays at 11-07 from 12-08 to 12-12. Against 11-07, this PR brings hang, watch-ui, and publish within 10%, but js/net is 1.107x (151,568 vs 136,880; the limit is 150,568). From 12-13 the 11-13 report becomes the anchor. This is the outcome the quest's Plan accepted.
Choices made without the maintainer
I replaced sorted-btree with in-tree code. This reverses moq-dev/moq#6869's choice and removes it from the "Kept" list in Audit cost. That audit compared it only with another library (@js-sdsl/ordered-map), never with in-tree code. The ledger bounds what a peer can make us do, which is core functionality, and the 150 lines are covered by a differential property test. The alternative was to keep it at about 5 KB Brotli per bundle.
Manual moves to @moq/clock/test with no migration note.@moq/clock is unreleased (0.1.0; its MIGRATE.md says "First release"), so no published consumer breaks.
qmux stays in the first load. It is the next largest removable piece, 44 KB raw in js/net. The WebSocket fallback races every connect after a short head start, and it is the only transport where WebTransport is missing. A lazy chunk would either be fetched on nearly every connect or add a round trip to the connect itself.
pako stays. It was already in the first load on 10-22, so it isn't part of the growth. Its chunked inflate (chunkSize: 4096) stops a decompression bomb at the declared size. Smaller libraries such as fflate inflate a whole push before the callback runs, which would lose that bound.
Impact
@moq/clock (unreleased): Manual is exported from @moq/clock/test, not @moq/clock. @moq/net's Clock namespace no longer includes Manual. Migration: import { Manual } from "@moq/clock/test".
@moq/net drops its sorted-btree dependency. Its public API is unchanged.
No wire change.
Validation
bun sh/size/bundles.ts on builds of main and this branch gave the table above (same host and toolchain).
js/net/src/util/sorted.test.ts checks Sorted against a plain sorted array: 100 fast-check runs of up to 4,000 mixed inserts and deletes (size: "max", so chunks split and merge), comparing every lookup and every iteration. It also passes with the chunk size temporarily forced to 2, where nearly every operation splits or merges.
Ledger A/B, with the old sorted-btree ledger and the new one interleaved in one process (5 rounds, median ns per arrival, including expiry and coverage). Headless Chrome, which is the target:
Arrivals
Order
sorted-btree
Sorted
2,048
ascending
439
293
2,048
descending
586
781
2,048
shuffled
830
928
2,048
rolling
684
610
32,768
ascending
592
504
32,768
descending
870
1,010
32,768
shuffled
1,212
1,324
32,768
rolling
902
865
In-order and rolling arrivals (the normal cases) are faster. Reordered arrivals are 8 to 33% slower, about 1 µs per arrival even with 32k open gaps. Bun showed parity within noise. The host was loaded, so treat these as indicative.
The built @moq/netLedger constructs and accounts under plain Node ESM. The old CommonJS-only dependency needed a shim for that.
The built @moq/clock exports Manual only from ./test.
just check passes, including every JS package's tests (@moq/net 3,266, @moq/watch 628, @moq/hang 551, ...).
Caused by
moq-dev/moq#6869 - took a 19 KB dependency for the ledger's ordered indexes.
moq-dev/moq#5538 - exported the test-only Manual clock from the package index, so it ships in every bundle that re-exports @moq/clock.
Score: 9/10, No actionable regressions were found: the ordered-index replacement preserves ledger behavior and the Manual export move is consistent, though builds and tests were not run as instructed.
The ledger's two ordered indexes use an in-tree chunked Sorted list instead
of sorted-btree, and @moq/clock's test-only Manual clock moves to
@moq/clock/test, so no shipped bundle carries either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
changed the title
The JS bundles shed what isn't worth its weight
perf(js): drop sorted-btree and the Manual clock from every bundle
Nov 13, 2026
The clock package now exposes Manual through @moq/clock/test, and documentation, benchmarks, tests, and other consumers use that entry point. The network ledger replaces sorted-btree with a new in-tree chunked Sorted collection. New tests cover the collection’s ordered operations and deletion behavior. The dependency list and audit documentation also record the replacement.
Merge Risk: Minimal · up to ae861
This PR moves the test-only Manual clock to @moq/clock/test and replaces sorted-btree with an in-tree sorted list in the ledger to shrink bundles. No in-repo consumer is left on the old import path, and the repository allows breaking API changes on main. Correct the PR note that says @moq/net has no public API change, because Clock.Manual is no longer exported there. Otherwise the change looks safe to merge.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title names two changes: dropping the sorted-btree dependency and moving the Manual test clock out of every bundle. The changeset does both. The ledger now uses an in-tree Sorted class in place of…
Description check
The description matches the changeset. It covers replacing sorted-btree with the in-tree Sorted list in js/net/src/util/sorted.ts, moving Manual to @moq/clock/test, the bundle-size tables, the Nightly…
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Full details: Title check
Explanation
The title names two changes: dropping the sorted-btree dependency and moving the Manual test clock out of every bundle. The changeset does both. The ledger now uses an in-tree Sorted class in place of sorted-btree, and Manual moves from @moq/clock to @moq/clock/test. The title is specific and matches the main changes, and it follows the conventional commit style.
Full details: Description check
Explanation
The description matches the changeset. It covers replacing sorted-btree with the in-tree Sorted list in js/net/src/util/sorted.ts, moving Manual to @moq/clock/test, the bundle-size tables, the Nightly size-check impact, and the validation steps. It is detailed and on topic.
Finishing Touches
Simplify code
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
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.
The reason will be displayed to describe this comment to others. Learn more.
Additional comments: 1
js/clock/src/index.ts-12-12: 1
12-12: Major · Data Integrity & Integration · Heavy lift
Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.
Preserve or declare the public API break.
Removing this export breaks existing import { Manual } from "@moq/clock" imports. It also removes Clock.Manual from @moq/net, contrary to the stated unchanged public API. Preserve compatibility or release this as a breaking change with migration guidance.
Review info
Run configuration
Source mode: Auto
Repository: Unavailable
Branch: Unavailable
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: c1c5c030-e3e7-4cd6-8eb2-8132c335fce9
Commits
Reviewing files that changed from the base of the PR and between d6195e8 and ae8616b.
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.
The reason will be displayed to describe this comment to others. Learn more.
✅ No new issues found.
Reviewed changes
I reviewed the whole PR: the in-tree Sorted list that replaces sorted-btree in the ledger, and the move of the Manual clock to @moq/clock/test across every importer.
Sorted chunked list:js/net/src/util/sorted.ts keeps values in sorted chunks of 128 to 512 entries. Lookups use two binary searches, and #seek keeps its result in fields, so a lookup allocates nothing. I traced #seek, #before, the split in insert, and the merge and re-halve in delete. In each, a position before the last chunk never lands past that chunk's end, and after the first split every chunk keeps at least 128 entries.
Ledger port:js/net/src/ledger.ts maps each sorted-btree call to its Sorted equivalent:
getPairOrNextLower → floor
getPairOrNextHigher → ceil
nextHigherPair → higher
nextLowerPair → lower
entries → from
#nextExpiry now matches runs by identity instead of by start. The two are equivalent because both indexes hold the same Run objects. demand changes only #ages while it iterates #runs, which follows Sorted's rule against changing a list during iteration. The ledger never inserts a key that is already stored.
sorted-btree removed: it is gone from js/net/package.json and bun.lock, and so is the CommonJS default-import shim.
Manual moved to @moq/clock/test: the move covers the index, API reports, docs, README, benches, mock.ts, replay.ts, and the harnesses under test/. No import of Manual from the @moq/clock root remains, and nothing uses Clock.Manual through @moq/net.
Quest bookkeeping:quest/m0/nightly/bundle-size.md is completed, and quest/m3/audit-cost.md now lists sorted-btree as replaced.
I ran bun test src/util/sorted.test.ts src/ledger* in js/net locally: 15 pass, 0 fail.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Completes quest/m0/nightly/bundle-size.md.
Problem
Nightly's 30-day size check (10% limit) fails from 11-21: against the 10-22 report,
js/netBrotli is 1.59x (98,909 to 157,282 bytes) and every other bundle is 1.40 to 1.48x. The maintainer chose to audit and shrink, then accept the rest as feature growth, without loosening the check.Approach
I attributed every first-load bundle against the 10-22 Nightly metafiles (
nightly-size-historyof run 39779729461), counting only the static import closure, assh/size/bundles.tsdoes. Two dependencies don't earn their bytes:sorted-btree(19.4 KB raw, about 5 KB Brotli, in every bundle). It backs the ledger's two ordered indexes, which need only floor/ceil/next/prev lookups, insert, delete, and in-order iteration. The ledger now uses an in-treeSortedlist (js/net/src/util/sorted.ts, about 150 lines). It stores values in chunks of 128 to 512, the way Python'ssortedcontainersdoes, so an insert or delete moves at most one chunk plus the chunk index. A peer that fragments arrivals still costs logarithmic lookups, the property moq-dev/moq#6869 added the tree for. This also removes the CommonJS interop shim.@moq/clock'sManualtest clock (2.3 KB raw innetandhang). It moves from@moq/clockto the existing@moq/clock/testentrypoint.@moq/net'sClocknamespace re-export no longer carries it.Everything else is feature code (below).
js/netjs/hangjs/watchjs/watch-uijs/publishThe rest is feature growth
About 190 KB of the roughly 200 KB of raw first-load growth is
js/net, spread over about 320 commits since 10-22 (74 of themfeat). The largest per-file changes:track.jslite/subscriber.jsietf/subscriber.js(lazy chunk)lite/publisher.jsresume.jswindow.js,socket.js,cache.js,fill.js,listing.js,datagram.jsorigin.js,lite/announce.js,lite/floors.js, ...)Outside
js/net, the watch player gained 12 KB raw, the publish encoder 9 KB, and@moq/ui5.5 KB, all features. Zod shrank, andasync-mutex,uuid, and@svta/cml-iso-bmffleft the first load.What this means for Nightly
The 30-day check still fails until a post-growth report becomes its anchor. Nightly recorded no report from 11-08 to 11-12, so the anchor stays at 11-07 from 12-08 to 12-12. Against 11-07, this PR brings
hang,watch-ui, andpublishwithin 10%, butjs/netis 1.107x (151,568 vs 136,880; the limit is 150,568). From 12-13 the 11-13 report becomes the anchor. This is the outcome the quest's Plan accepted.Choices made without the maintainer
sorted-btreewith in-tree code. This reverses moq-dev/moq#6869's choice and removes it from the "Kept" list in Audit cost. That audit compared it only with another library (@js-sdsl/ordered-map), never with in-tree code. The ledger bounds what a peer can make us do, which is core functionality, and the 150 lines are covered by a differential property test. The alternative was to keep it at about 5 KB Brotli per bundle.Manualmoves to@moq/clock/testwith no migration note.@moq/clockis unreleased (0.1.0; itsMIGRATE.mdsays "First release"), so no published consumer breaks.js/net. The WebSocket fallback races every connect after a short head start, and it is the only transport where WebTransport is missing. A lazy chunk would either be fetched on nearly every connect or add a round trip to the connect itself.chunkSize: 4096) stops a decompression bomb at the declared size. Smaller libraries such asfflateinflate a whole push before the callback runs, which would lose that bound.Impact
@moq/clock(unreleased):Manualis exported from@moq/clock/test, not@moq/clock.@moq/net'sClocknamespace no longer includesManual. Migration:import { Manual } from "@moq/clock/test".@moq/netdrops itssorted-btreedependency. Its public API is unchanged.Validation
bun sh/size/bundles.tson builds of main and this branch gave the table above (same host and toolchain).js/net/src/util/sorted.test.tschecksSortedagainst a plain sorted array: 100 fast-check runs of up to 4,000 mixed inserts and deletes (size: "max", so chunks split and merge), comparing every lookup and every iteration. It also passes with the chunk size temporarily forced to 2, where nearly every operation splits or merges.Ledger A/B, with the old
sorted-btreeledger and the new one interleaved in one process (5 rounds, median ns per arrival, including expiry and coverage). Headless Chrome, which is the target:SortedIn-order and rolling arrivals (the normal cases) are faster. Reordered arrivals are 8 to 33% slower, about 1 µs per arrival even with 32k open gaps. Bun showed parity within noise. The host was loaded, so treat these as indicative.
The built
@moq/netLedgerconstructs and accounts under plain Node ESM. The old CommonJS-only dependency needed a shim for that.The built
@moq/clockexportsManualonly from./test.just checkpasses, including every JS package's tests (@moq/net3,266,@moq/watch628,@moq/hang551, ...).Caused by
Manualclock from the package index, so it ships in every bundle that re-exports@moq/clock.Follow-ups
None.
Models
Size: M
Implementer: Claude Code, claude-opus-5-5
🤖 Generated with Claude Code
(Written by Claude Opus 5.5)
Review
Rewritten by
quest reviewafter each round.Head: ae8616b
Base: origin/main at d9f3ceb
Reviewer: Codex, gpt-6.1-sol, high effort
Session:
01a1d6f2-b98e-7383-bca9-a6fc6080c2db, round 1Score: 9/10,
No actionable regressions were found: the ordered-index replacement preserves ledger behavior and the Manual export move is consistent, though builds and tests were not run as instructed.Rounds: 1; findings 0: fixed 0, refuted 0, open 0
Findings: none