Skip to content

perf(pnpr): resolve server-side and fetch tarballs directly#12232

Merged
zkochan merged 5 commits into
mainfrom
fix/12230
Jun 6, 2026
Merged

perf(pnpr): resolve server-side and fetch tarballs directly#12232
zkochan merged 5 commits into
mainfrom
fix/12230

Conversation

@zkochan

@zkochan zkochan commented Jun 5, 2026

Copy link
Copy Markdown
Member

Summary

Reworks pnpr from an install/file accelerator into a resolve-only accelerator:

  • POST /v1/resolve resolves against the client-supplied registries and returns a gzipped JSON lockfile response
  • pacquet/pnpm clients then fetch tarballs normally from registries with their own credentials and existing parallel fetch/integrity paths
  • pnpr no longer serves package file bytes or store-index rows, so the server-side file diff, file-frame response, grant table, and public-package byte-gating code are removed

The follow-up resolution fast paths are included on the new measured path:

  • repeated public no-lockfile resolves use a bounded in-memory TTL cache
  • fresh frozen input lockfiles skip the server-side lockfile-only pacquet resolve after verification proves the lockfile is usable
  • input lockfile verification and the verdict cache are preserved

Benchmark

Integrated benchmark on Linux shows small improvements in all pnpr rows, with the clearest movement in hot restore. This should be treated as an incremental win rather than a large install-speed change.

Scenario pnpr@HEAD pnpr@main Change
fresh restore, cold cache + cold store 1.677 s ± 0.090 1.686 s ± 0.070 ~0.6% faster
fresh restore, hot cache + hot store 492.5 ms ± 18.1 521.9 ms ± 33.4 ~5.6% faster
fresh install, cold cache + cold store 1.997 s ± 0.025 2.003 s ± 0.038 ~0.3% faster
fresh install, hot cache + hot store 1.211 s ± 0.024 1.236 s ± 0.038 ~2.0% faster

Trade-off

Going registry-direct means pnpr no longer gates tarball bytes itself. Private package access is enforced by the upstream registry when the client fetches tarballs. Resolution policy still runs server-side: lockfile verification, release-age policy, trust policy, and resolved package selection continue to happen before the client fetches bytes.

Verification

  • cargo check -p pnpr -p pacquet-pnpr-client
  • cargo test -p pnpr resolver
  • cargo test -p pacquet-pnpr-client
  • cargo clippy --locked -p pnpr -p pacquet-pnpr-client --all-targets -- -D warnings
  • cargo fmt --all -- --check
  • cargo dylint --all -- --all-targets --workspace
  • git diff --check
  • pre-push hook passed before push to origin/fix/12230

Note

This is a coordinated client/server protocol change. Clients using /v1/resolve need a pnpr server with this endpoint. Older /v1/install inline-file responses are not parsed by the new client path.

Closes #12230


Written by an agent (Codex, GPT-5).

zkochan added 2 commits June 5, 2026 23:57
…h tarballs in parallel

The pnpr install accelerator now only creates the lockfile; tarballs are
fetched afterward like a normal install. `POST /v1/install` previously
returned the resolved lockfile and all missing file contents inline over
a single connection, which was bandwidth-bound on cold/WAN installs. pnpr
is now a stateless resolver: it resolves and verifies the lockfile
server-side, then the client fetches every tarball directly from the
registries in parallel.

Removes the inline file-serving path: file-level diff, the per-content
access gate (grant table, public-packages cache, upstream probes), and
the client-side CAFS-write plumbing.

Closes #12230
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Too many files changed? Review this PR in Change Stack to see how the pieces fit before you dive in.

Review Change Stack

📝 Walkthrough

Walkthrough

The PR transitions pnpr from a single-stream install accelerator (returning lockfile and all missing files inline) to a resolver-only service. The server now resolves and verifies lockfiles, then the client fetches tarballs in parallel from registries. This enables better WAN performance by avoiding the bandwidth bottleneck of one TCP connection.

Changes

Resolve-only pnpr protocol and runtime

Layer / File(s) Summary
Protocol contract and release metadata updates
.changeset/pnpr-resolve-only.md, pnpr/client/src/protocol.ts
Changeset documents behavioral shift to resolver-only; response metadata now contains only totalPackages instead of store/file counters.
Server configuration and install-accelerator cleanup
pnpr/crates/pnpr/src/config.rs, pnpr/crates/pnpr/src/lib.rs
Config removes install_accelerator_grant_ttl and related grant/public-package database fields; old accelerator grant_table, public_packages, and diff modules are deleted from the codebase.
Server resolver implementation and response handling
pnpr/crates/pnpr/src/resolver.rs, pnpr/crates/pnpr/src/resolver/protocol.rs, pnpr/crates/pnpr/src/resolver/resolve.rs, pnpr/crates/pnpr/src/resolver/tests.rs, pnpr/crates/pnpr/src/resolver/verdict_cache.rs
New resolver module implements handle_resolve for lockfile-only request handling, input lockfile verification under caller policy, resolution caching with TTL/eviction, and verdict cache integration; response building shifts from inline file frames to gzipped JSON.
Install accelerator refactored to lockfile-only response
pnpr/crates/pnpr/src/install_accelerator.rs
install_accelerator.rs simplifies to lockfile verification and JSON response rendering; removes grant/public database initialization, diff computation, and binary file-frame framing logic.
Server routing wires resolver protocol
pnpr/crates/pnpr/src/server.rs, pnpr/crates/pnpr/src/main.rs
Server adds lazily-initialized resolver engine, routes POST /v1/resolve, updates /v1/pnpr handshake for resolver versions, and adjusts compression predicate to support JSON responses.
Pnpr client libraries refactored to resolver-only
pacquet/crates/pnpr-client/Cargo.toml, pacquet/crates/pnpr-client/src/lib.rs, pnpr/client/src/resolveViaPnprServer.ts, pnpr/client/src/index.ts, pnpr/client/package.json, pnpr/client/tsconfig.json
Rust and TypeScript pnpr clients remove store/materialization APIs, switch to JSON response parsing, reduce response stats to totalPackages only, and update module exports/build references; both libraries now implement resolver-only request/response contracts.
Installer and CLI integrate resolve-only pnpr
installing/deps-installer/src/install/index.ts, pacquet/crates/cli/src/cli_args/install.rs
Installer calls resolveViaPnprServer with normalized project lists and forwarded auth, constructs optionalDependencies, removes server-side lockfile_only forwarding, and proceeds with local headless tarball fetch; CLI updates request options and documentation.
Worker message and concurrency API cleanup
worker/src/index.ts, worker/src/start.ts, worker/src/types.ts
Worker removes WriteCafsFilesMessage type, handler, and writeCafsFiles exported function; import concurrency becomes a fixed pLimit(4) without dynamic adjustment via setImportConcurrency.
Client tests and integration validation
pacquet/crates/pnpr-client/src/tests.rs, pacquet/crates/pnpr-client/tests/integration.rs
Client unit and integration tests refactored to parse JSON responses directly, remove StoreDir wiring from test helpers, keep credential forwarding and lockfile verification assertions, and remove file-materialization/caching assertions.
Test harness and documentation updates
pnpm/test/install/pnpmRegistry.ts, pacquet/crates/cli/tests/pnpr_install.rs, pnpr/client/README.md, pnpr/npm/pnpr/README.md, pacquet/tasks/integrated-benchmark/src/cli_args.rs, pacquet/tasks/integrated-benchmark/src/work_env.rs, pacquet/crates/network/src/auth.rs, pacquet/crates/package-manager/src/install.rs
Test proxy counts /v1/resolve instead of /v1/install; client README and package.json simplify usage examples and update descriptions; pnpr CLI and S3 docs update terminology from install-accelerator to resolver; doc comments across benchmark and auth code updated to reflect resolver protocol.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • #12230: The PR directly implements the two-phase resolver design proposed in this issue, decoupling lockfile resolution from parallel tarball fetching to avoid bandwidth bottlenecks on WAN/cold-store scenarios.

Possibly related PRs

  • pnpm/pnpm#12077: Main PR's resolver-only refactor replaces this PR's install-accelerator protocol endpoints and client APIs with a lockfile-only /v1/resolve flow.
  • pnpm/pnpm#12178: Both PRs modify pnpr-client protocol handling; main PR shifts from /v1/install inline-file responses to JSON-only /v1/resolve lockfile responses.
  • pnpm/pnpm#12198: Main PR's configuration refactor introduces hosted_store in pnpr config alongside the retrieved PR's S3/object-store support.

Poem

🐰 The pnpr path once streamed all at once,
But single TCP proved a mighty dunce.
Now lockfiles resolve in parallel streams—
Fast WAN installs, at last, fulfill our dreams!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #12230: server-side resolution with verification, client-side parallel tarball fetching, stateless resolver architecture, and policy enforcement at resolution time.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the two-phase design. Documentation updates, worker plumbing removal, and related refactors (grant table, public packages, diff computation) are necessary to complete the transition from monolithic install accelerator to resolver.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main architectural change: shifting from an accelerator that handles install/fetch to a server-side resolver while clients fetch tarballs in parallel.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/12230

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 and usage tips.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Each scenario has pacquet rows (direct install) and pnpr rows (the same client through the pnpr install accelerator), so pnpr@HEAD vs pacquet@HEAD is the pnpr-vs-direct ratio. Cold-store scenarios wipe the client store between runs (warm server); hot-store scenarios keep it warm. The pacquet@HEAD rows feed the pacquet Bencher testbed; the pnpr@HEAD rows feed the pnpr testbed.

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.692 ± 0.091 4.603 4.897 2.80 ± 0.16
pacquet@main 4.667 ± 0.033 4.617 4.717 2.78 ± 0.15
pnpr@HEAD 1.677 ± 0.090 1.603 1.895 1.00
pnpr@main 1.686 ± 0.070 1.614 1.867 1.01 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.691600039060001,
      "stddev": 0.09101750977485554,
      "median": 4.65084399076,
      "user": 2.43101366,
      "system": 2.26096136,
      "min": 4.60287223976,
      "max": 4.8974701537600005,
      "times": [
        4.762248495760001,
        4.63648200876,
        4.6419892667600005,
        4.8974701537600005,
        4.60287223976,
        4.6813341387600005,
        4.62913021676,
        4.63698947776,
        4.65969871476,
        4.76778567776
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.66677599546,
      "stddev": 0.03310984285075305,
      "median": 4.65754818226,
      "user": 2.46854956,
      "system": 2.2620134599999995,
      "min": 4.61650678076,
      "max": 4.71663494976,
      "times": [
        4.71663494976,
        4.650760157760001,
        4.63190236076,
        4.65351515576,
        4.68679933576,
        4.7111223657600005,
        4.61650678076,
        4.68941744776,
        4.66158120876,
        4.649520191760001
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.67679741766,
      "stddev": 0.08961492935229064,
      "median": 1.64843186326,
      "user": 2.79805486,
      "system": 1.9898762599999997,
      "min": 1.6034298497600001,
      "max": 1.89459131876,
      "times": [
        1.60832453076,
        1.61295535876,
        1.62580082976,
        1.7100359817600002,
        1.67106289676,
        1.6769005507599999,
        1.89459131876,
        1.74114863276,
        1.62372422676,
        1.6034298497600001
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 1.68615389596,
      "stddev": 0.07042048207000841,
      "median": 1.6721098332600002,
      "user": 2.82118956,
      "system": 2.00487316,
      "min": 1.6139973437600001,
      "max": 1.8665063717600001,
      "times": [
        1.64605933076,
        1.6885430837600002,
        1.67443650076,
        1.66221882276,
        1.8665063717600001,
        1.6371950187600002,
        1.6139973437600001,
        1.6729195437600002,
        1.6713001227600002,
        1.72836282076
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 504.7 ± 49.7 479.3 644.1 1.03 ± 0.10
pacquet@main 491.8 ± 9.3 470.5 505.4 1.00
pnpr@HEAD 492.5 ± 18.1 479.8 540.0 1.00 ± 0.04
pnpr@main 521.9 ± 33.4 484.6 600.4 1.06 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.5047119582799999,
      "stddev": 0.049713760967809655,
      "median": 0.48928358288000007,
      "user": 0.36805557999999994,
      "system": 0.7934237799999999,
      "min": 0.47930699738000004,
      "max": 0.6441239323800001,
      "times": [
        0.6441239323800001,
        0.5062903093800001,
        0.49251685038000004,
        0.49561856338000004,
        0.48204217338000005,
        0.47930699738000004,
        0.47999537838000006,
        0.49588025138,
        0.48529481138,
        0.48605031538000004
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.4918113915800001,
      "stddev": 0.009304294969427657,
      "median": 0.49199933338,
      "user": 0.37492417999999994,
      "system": 0.7893954799999998,
      "min": 0.47053038038000006,
      "max": 0.5053830693800001,
      "times": [
        0.5053830693800001,
        0.49940711938000004,
        0.49151132738000003,
        0.49577369538000005,
        0.49693735938000005,
        0.47053038038000006,
        0.48660291638000003,
        0.48798917738,
        0.49149153138000007,
        0.49248733938000006
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.49245201658000004,
      "stddev": 0.018098259688561696,
      "median": 0.48494742938,
      "user": 0.37356098,
      "system": 0.7930151799999999,
      "min": 0.47981349838000004,
      "max": 0.54001501338,
      "times": [
        0.50366624938,
        0.49281199738000003,
        0.48432692538000005,
        0.47981349838000004,
        0.48201691838000005,
        0.54001501338,
        0.48989584238000006,
        0.48556793338000004,
        0.48294014638000005,
        0.48346564138000003
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.52190080008,
      "stddev": 0.033395669018562786,
      "median": 0.5217787623800001,
      "user": 0.36167488,
      "system": 0.7924627799999999,
      "min": 0.48460111338000006,
      "max": 0.6003817963800001,
      "times": [
        0.52196972338,
        0.49150727338000005,
        0.5314798523800001,
        0.4884823923800001,
        0.48460111338000006,
        0.53470785838,
        0.5319007463800001,
        0.6003817963800001,
        0.51238944338,
        0.52158780138
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.004 ± 0.010 1.989 2.026 1.00 ± 0.01
pacquet@main 1.994 ± 0.024 1.949 2.027 1.00
pnpr@HEAD 1.997 ± 0.025 1.964 2.050 1.00 ± 0.02
pnpr@main 2.003 ± 0.038 1.951 2.078 1.00 ± 0.02
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.00377047426,
      "stddev": 0.009870947861722077,
      "median": 2.00234693526,
      "user": 3.9193897799999995,
      "system": 1.9384239200000004,
      "min": 1.98882720326,
      "max": 2.02642844826,
      "times": [
        2.02642844826,
        1.99917292226,
        1.98882720326,
        2.0077044912599997,
        2.00447462026,
        2.00090616426,
        2.00115173926,
        1.99619402526,
        2.0035421312599997,
        2.00930299726
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.9944090842599997,
      "stddev": 0.024388587285502732,
      "median": 1.99708532076,
      "user": 3.91437308,
      "system": 1.91238282,
      "min": 1.94931749226,
      "max": 2.0272490472599998,
      "times": [
        2.0272490472599998,
        2.01388852926,
        1.94931749226,
        1.9992851592599998,
        1.99488548226,
        1.99402334426,
        2.0121611542599998,
        1.9933685642599999,
        1.95646227726,
        2.00344979226
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.99667072996,
      "stddev": 0.02504506474314031,
      "median": 1.99961102376,
      "user": 3.8978721800000002,
      "system": 1.90530202,
      "min": 1.96361118026,
      "max": 2.05014449326,
      "times": [
        2.0060161062599997,
        1.96361118026,
        1.99933670826,
        1.96755815326,
        2.05014449326,
        1.97282910926,
        2.00181697926,
        1.99988533926,
        2.0087407162599997,
        1.9967685142599998
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.0034884894600005,
      "stddev": 0.037679716000572044,
      "median": 1.99441717326,
      "user": 3.90812028,
      "system": 1.9269911199999998,
      "min": 1.95064240626,
      "max": 2.07783635826,
      "times": [
        2.07783635826,
        2.05334471226,
        1.98425215026,
        2.01437154626,
        1.99332077026,
        1.99551357626,
        2.0071607282599997,
        1.97131068326,
        1.98713196326,
        1.95064240626
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.215 ± 0.010 1.198 1.228 1.00 ± 0.02
pacquet@main 1.242 ± 0.065 1.204 1.425 1.03 ± 0.06
pnpr@HEAD 1.211 ± 0.024 1.196 1.275 1.00
pnpr@main 1.236 ± 0.038 1.205 1.309 1.02 ± 0.04
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.21537533984,
      "stddev": 0.010197552165699054,
      "median": 1.21583108904,
      "user": 1.5683310799999997,
      "system": 1.0691787599999998,
      "min": 1.19820906304,
      "max": 1.2280847100399999,
      "times": [
        1.2280847100399999,
        1.22368992004,
        1.20749057104,
        1.20937635304,
        1.20889297204,
        1.20735063304,
        1.19820906304,
        1.22228582504,
        1.22296997404,
        1.22540337704
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.24200094954,
      "stddev": 0.06540893957463492,
      "median": 1.2224097295399998,
      "user": 1.56137178,
      "system": 1.09418626,
      "min": 1.2040937540399999,
      "max": 1.42549131804,
      "times": [
        1.21958772104,
        1.2040937540399999,
        1.2073175920399999,
        1.2386718640399998,
        1.22632872004,
        1.2216064950399999,
        1.42549131804,
        1.23707819204,
        1.2232129640399998,
        1.2166208750399998
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 1.2113760618399998,
      "stddev": 0.023532737108346653,
      "median": 1.2031071200399999,
      "user": 1.56339498,
      "system": 1.06027186,
      "min": 1.19568206004,
      "max": 1.2746177590399999,
      "times": [
        1.21558663204,
        1.1978472630399999,
        1.19993428104,
        1.19568206004,
        1.2111748360399999,
        1.19730740004,
        1.21647268404,
        1.19885774404,
        1.20627995904,
        1.2746177590399999
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 1.2361242042399998,
      "stddev": 0.03796821944486373,
      "median": 1.2231566750399998,
      "user": 1.5674385800000001,
      "system": 1.0837592600000001,
      "min": 1.2052334440399999,
      "max": 1.30912597604,
      "times": [
        1.30912597604,
        1.2184454730399998,
        1.2232753700399999,
        1.21228366804,
        1.22303798004,
        1.22948593604,
        1.2095643920399999,
        1.2052334440399999,
        1.3041923580399999,
        1.22659744504
      ]
    }
  ]
}

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12232
Testbedpacquet

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
isolated-linker.fresh-restore.cold-cache.cold-storeLatency
seconds (s)
📈 plot
🚷 threshold
🚨 alert (🔔)
4.69 s
(+28.72%)Baseline: 3.64 s
4.37 s
(107.27%)

Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
2,003.77 ms
(-9.47%)Baseline: 2,213.31 ms
2,655.97 ms
(75.44%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,215.38 ms
(-10.24%)Baseline: 1,354.00 ms
1,624.80 ms
(74.80%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
🚨 view alert (🔔)
4,691.60 ms
(+28.72%)Baseline: 3,644.68 ms
4,373.61 ms
(107.27%)

isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
504.71 ms
(-25.16%)Baseline: 674.41 ms
809.30 ms
(62.36%)
🐰 View full continuous benchmarking report in Bencher

Port the two-phase install-accelerator change to the pacquet Rust stack
(keeping pnpm and pacquet in sync). `PnprClient::install` now sends a
resolve-only request and parses the gzipped JSON `{ lockfile, stats,
violations }` response, returning just the lockfile. It no longer asks
for inline files, sends store integrities, or writes file content / index
entries into the client store.

The CLI's `install_via_pnpr` already runs a frozen `Install` after
resolving; that materialization now fetches every tarball directly from
the registries, like a normal install.

Removes the now-unused `base64` and `pacquet-store-dir` deps from the
pnpr-client crate.
@zkochan zkochan marked this pull request as ready for review June 5, 2026 22:47
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Restructure pnpr into two-phase resolver: server-side resolution, client-side parallel tarball fetching

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Restructure pnpr into a stateless resolver: server resolves and verifies lockfile only, client
  fetches tarballs in parallel from registries
• Remove inline file-serving machinery: eliminate file-level diff, per-content access gates, grant
  tables, and CAFS-write plumbing
• Simplify protocol to single gzipped JSON response carrying resolved lockfile and stats instead of
  binary frames with file contents
• Update client to parse JSON response and run normal headless install for tarball fetching,
  removing store-integrity reading and file-frame parsing
Diagram
flowchart LR
  Client["Client<br/>sends project"]
  Server["pnpr Server<br/>resolves + verifies"]
  Response["Lockfile + stats<br/>gzipped JSON"]
  Fetch["Client fetches<br/>tarballs in parallel"]
  
  Client -- "POST /v1/install" --> Server
  Server -- "200 OK" --> Response
  Response --> Fetch
  Fetch -- "normal install" --> NodeModules["node_modules"]

Loading

Grey Divider

File Changes

1. pnpr/crates/pnpr/src/install_accelerator.rs ✨ Enhancement +56/-489

Remove file-serving and access-gating machinery

pnpr/crates/pnpr/src/install_accelerator.rs


2. pnpr/crates/pnpr/src/install_accelerator/resolve.rs ✨ Enhancement +8/-153

Simplify resolve to lockfile-only, no tarball fetch

pnpr/crates/pnpr/src/install_accelerator/resolve.rs


3. pacquet/crates/pnpr-client/src/lib.rs ✨ Enhancement +30/-289

Parse JSON response, remove file materialization logic

pacquet/crates/pnpr-client/src/lib.rs


View more (26)
4. pacquet/crates/pnpr-client/tests/integration.rs 🧪 Tests +22/-138

Update tests to verify lockfile-only resolution

pacquet/crates/pnpr-client/tests/integration.rs


5. pacquet/crates/cli/src/cli_args/install.rs 📝 Documentation +7/-7

Update pnpr install flow documentation

pacquet/crates/cli/src/cli_args/install.rs


6. pnpr/crates/pnpr/src/server.rs ✨ Enhancement +6/-16

Remove identity resolution and access-gate parameters

pnpr/crates/pnpr/src/server.rs


7. pacquet/crates/cli/tests/pnpr_install.rs 📝 Documentation +2/-1

Update test comment about tarball fetching source

pacquet/crates/cli/tests/pnpr_install.rs


8. pnpr/client/src/fetchFromPnpmRegistry.ts ✨ Enhancement +17/-94

Parse JSON response, remove file writing and store index logic

pnpr/client/src/fetchFromPnpmRegistry.ts


9. installing/deps-installer/src/install/index.ts ✨ Enhancement +30/-95

Remove wrapped store controller and file-download waiting

installing/deps-installer/src/install/index.ts


10. worker/src/index.ts ✨ Enhancement +1/-53

Remove writeCafsFiles and setImportConcurrency exports

worker/src/index.ts


11. pnpr/client/src/index.ts ✨ Enhancement +1/-1

Remove writeRawIndexEntries export

pnpr/client/src/index.ts


12. pnpr/client/README.md 📝 Documentation +6/-13

Update documentation to reflect resolve-only architecture

pnpr/client/README.md


13. pnpr/client/package.json 📝 Documentation +2/-8

Update description and remove store-related dependencies

pnpr/client/package.json


14. .changeset/pnpr-resolve-only.md 📝 Documentation +8/-0

Document two-phase resolver architecture change

.changeset/pnpr-resolve-only.md


15. pacquet/crates/pnpr-client/Cargo.toml Additional files +0/-2

...

pacquet/crates/pnpr-client/Cargo.toml


16. pacquet/crates/pnpr-client/src/tests.rs Additional files +12/-31

...

pacquet/crates/pnpr-client/src/tests.rs


17. pnpm-lock.yaml Additional files +0/-12

...

pnpm-lock.yaml


18. pnpr/client/src/protocol.ts Additional files +0/-6

...

pnpr/client/src/protocol.ts


19. pnpr/client/tsconfig.json Additional files +0/-9

...

pnpr/client/tsconfig.json


20. pnpr/crates/pnpr/src/config.rs Additional files +0/-24

...

pnpr/crates/pnpr/src/config.rs


21. pnpr/crates/pnpr/src/install_accelerator/diff.rs Additional files +0/-157

...

pnpr/crates/pnpr/src/install_accelerator/diff.rs


22. pnpr/crates/pnpr/src/install_accelerator/grant_table.rs Additional files +0/-108

...

pnpr/crates/pnpr/src/install_accelerator/grant_table.rs


23. pnpr/crates/pnpr/src/install_accelerator/grant_table/tests.rs Additional files +0/-74

...

pnpr/crates/pnpr/src/install_accelerator/grant_table/tests.rs


24. pnpr/crates/pnpr/src/install_accelerator/protocol.rs Additional files +0/-10

...

pnpr/crates/pnpr/src/install_accelerator/protocol.rs


25. pnpr/crates/pnpr/src/install_accelerator/public_packages.rs Additional files +0/-95

...

pnpr/crates/pnpr/src/install_accelerator/public_packages.rs


26. pnpr/crates/pnpr/src/install_accelerator/public_packages/tests.rs Additional files +0/-42

...

pnpr/crates/pnpr/src/install_accelerator/public_packages/tests.rs


27. pnpr/crates/pnpr/src/install_accelerator/tests.rs Additional files +0/-354

...

pnpr/crates/pnpr/src/install_accelerator/tests.rs


28. worker/src/start.ts Additional files +0/-76

...

worker/src/start.ts


29. worker/src/types.ts Additional files +0/-12

...

worker/src/types.ts


Grey Divider

Qodo Logo

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.01      7.6±0.23ms   568.7 KB/sec    1.00      7.5±0.19ms   575.3 KB/sec

@codecov-commenter

codecov-commenter commented Jun 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.93082% with 67 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.35%. Comparing base (ae212c8) to head (02c26a9).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
pnpr/crates/pnpr/src/resolver/resolve.rs 8.33% 44 Missing ⚠️
pnpr/crates/pnpr/src/resolver.rs 90.61% 23 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12232      +/-   ##
==========================================
- Coverage   87.85%   87.35%   -0.51%     
==========================================
  Files         278      276       -2     
  Lines       32153    32196      +43     
==========================================
- Hits        28249    28125     -124     
- Misses       3904     4071     +167     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@zkochan zkochan changed the title feat(pnpr): two-phase install accelerator — resolve server-side, fetch tarballs in parallel perf(pnpr): two-phase install accelerator — resolve server-side, fetch tarballs in parallel Jun 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
installing/deps-installer/src/install/index.ts (3)

2293-2298: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't materialize the whole workspace for a filtered pnpr mutation.

preparePnprProjects() intentionally expands to every workspace importer so the server returns a complete lockfile, but installFromPnpmRegistry() reuses that same array for selectedProjectDirs. On filtered installSome / uninstallSome, this turns the local headless phase into a full-workspace link/build instead of only materializing the mutated projects.

Suggested direction
 async function installFromPnpmRegistry (
   manifest: ProjectManifest,
   rootDir: ProjectRootDir,
   opts: Opts,
-  allInstallProjects?: Array<{ rootDir: ProjectRootDir, manifest: ProjectManifest }>
+  allInstallProjects?: Array<{ rootDir: ProjectRootDir, manifest: ProjectManifest }>,
+  selectedProjectDirs: ProjectRootDir[] = [rootDir]
 ): Promise<InstallResult & { stats: InstallationResultStats, lockfile: LockfileObject }> {
@@
-      selectedProjectDirs: (allInstallProjects ?? [{ rootDir }]).map(p => p.rootDir),
+      selectedProjectDirs,

Then have mutateModulesViaPnpr() pass the original mutated roots, while still passing the expanded workspace set for allProjects.

Also applies to: 2445-2458

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@installing/deps-installer/src/install/index.ts` around lines 2293 - 2298,
preparePnprProjects() currently expands to every workspace importer but
installFromPnpmRegistry() is being called with that full array as
selectedProjectDirs (pnprProjects), which causes full workspace materialization
for filtered installSome/uninstallSome operations; change the call sites (e.g.,
in mutateModulesViaPnpr()) so that installFromPnpmRegistry() receives two
separate arrays: one for allProjects (the fully expanded workspace from
preparePnprProjects()) and one for selectedProjectDirs containing only the
original mutated roots (the filtered project roots passed into
mutateModulesViaPnpr()), ensuring installFromPnpmRegistry() and any downstream
logic use selectedProjectDirs only to drive link/build materialization while
still using allProjects to produce the complete lockfile.

2318-2322: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve resolutionPolicyViolations instead of casting it away.

installFromPnpmRegistry() already returns resolutionPolicyViolations, but this adapter drops the field and then casts the object to MutateModulesResult. That breaks the result contract for pnpr-backed mutateModules() calls and can bubble undefined out through install() / addDependenciesToPackage().

Minimal fix
   return {
     updatedProjects,
     stats: result.stats,
     ignoredBuilds: result.ignoredBuilds,
+    resolutionPolicyViolations: result.resolutionPolicyViolations,
   } as MutateModulesResult
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@installing/deps-installer/src/install/index.ts` around lines 2318 - 2322, The
return value is dropping result.resolutionPolicyViolations and then casting to
MutateModulesResult; update the returned object in the function that wraps
installFromPnpmRegistry (the block returning updatedProjects, stats,
ignoredBuilds) to include resolutionPolicyViolations:
result.resolutionPolicyViolations so the mutateModules() contract is preserved,
and remove or adjust the unsafe cast if necessary so the object shape matches
MutateModulesResult; this fixes downstream callers such as mutateModules(),
install(), and addDependenciesToPackage() receiving undefined for that field.

2087-2094: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restrict the pnpr fast path to installSome cases it can actually emulate.

canUsePnprForMutations() currently routes every non-update installSome through mergeInstallSelectors(), but that helper skips the normal installSome() behaviors for allowNew === false, saveCatalogName / defaultCatalog, and catalogMode mismatch handling. In those cases the pnpr path can rewrite the manifest differently or succeed where the standard resolver would throw. Please fall back to the normal path until the pnpr helper matches the existing installSome() semantics.

Also applies to: 2203-2226

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@installing/deps-installer/src/install/index.ts` around lines 2087 - 2094, The
pnpr fast-path in canUsePnprForMutations should be limited to only those
installSome mutations that mergeInstallSelectors can faithfully emulate: change
the predicate so it does NOT short-circuit true for 'uninstallSome', and only
returns true for p.mutation === 'installSome' whose InstallSomeDepsMutation
fields show no updates (update and updateToLatest are false and updateMatching
== null) AND whose install options allow pnpr: allowNew !== false,
saveCatalogName and defaultCatalog are not set, and catalogMode is compatible
with mergeInstallSelectors (i.e. not a special catalogMode that would change
manifest handling). Otherwise return false to force the normal
installSome()/resolver path; update canUsePnprForMutations and any similar
checks in the nearby block that mirror the same logic.
🧹 Nitpick comments (1)
pnpr/crates/pnpr/src/server.rs (1)

186-188: 💤 Low value

Minor: Update comment to reflect new content type.

The compression layer comment still references application/x-pnpr-install-inline, which was the old binary protocol's content type. The new lockfile-only response uses application/json (set in install_accelerator.rs line 260). Consider updating the comment to mention the install accelerator response is already gzipped (without naming the obsolete content type), or just rely on the general "Already-Content-Encoding responses are skipped" rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pnpr/crates/pnpr/src/server.rs` around lines 186 - 188, Update the inline
comment in the compression layer in server.rs to stop referencing the obsolete
content type `application/x-pnpr-install-inline`; instead note that the install
accelerator response is already gzipped (now served as `application/json` from
the install accelerator) or simply state that responses with an existing
Content-Encoding are skipped by the layer. Locate the comment near the
compression/middleware handling for accelerator responses (the block describing
"accelerator response" in server.rs) and adjust the wording to either mention
"install accelerator responses are already gzipped" or remove the specific
MIME-type reference.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pacquet/crates/pnpr-client/src/lib.rs`:
- Around line 34-37: InstallOptions is missing an optionalDependencies field so
the client never forwards optional deps to the server; add a new pub
optional_dependencies: DepMap (or Option<DepMap> if appropriate) to the
InstallOptions struct and update the code that constructs the install payload
(the place that builds projects[0]) to populate optionalDependencies from this
field and ensure the serializer uses the JSON key "optionalDependencies" so the
/v1/install payload includes optional edges.

---

Outside diff comments:
In `@installing/deps-installer/src/install/index.ts`:
- Around line 2293-2298: preparePnprProjects() currently expands to every
workspace importer but installFromPnpmRegistry() is being called with that full
array as selectedProjectDirs (pnprProjects), which causes full workspace
materialization for filtered installSome/uninstallSome operations; change the
call sites (e.g., in mutateModulesViaPnpr()) so that installFromPnpmRegistry()
receives two separate arrays: one for allProjects (the fully expanded workspace
from preparePnprProjects()) and one for selectedProjectDirs containing only the
original mutated roots (the filtered project roots passed into
mutateModulesViaPnpr()), ensuring installFromPnpmRegistry() and any downstream
logic use selectedProjectDirs only to drive link/build materialization while
still using allProjects to produce the complete lockfile.
- Around line 2318-2322: The return value is dropping
result.resolutionPolicyViolations and then casting to MutateModulesResult;
update the returned object in the function that wraps installFromPnpmRegistry
(the block returning updatedProjects, stats, ignoredBuilds) to include
resolutionPolicyViolations: result.resolutionPolicyViolations so the
mutateModules() contract is preserved, and remove or adjust the unsafe cast if
necessary so the object shape matches MutateModulesResult; this fixes downstream
callers such as mutateModules(), install(), and addDependenciesToPackage()
receiving undefined for that field.
- Around line 2087-2094: The pnpr fast-path in canUsePnprForMutations should be
limited to only those installSome mutations that mergeInstallSelectors can
faithfully emulate: change the predicate so it does NOT short-circuit true for
'uninstallSome', and only returns true for p.mutation === 'installSome' whose
InstallSomeDepsMutation fields show no updates (update and updateToLatest are
false and updateMatching == null) AND whose install options allow pnpr: allowNew
!== false, saveCatalogName and defaultCatalog are not set, and catalogMode is
compatible with mergeInstallSelectors (i.e. not a special catalogMode that would
change manifest handling). Otherwise return false to force the normal
installSome()/resolver path; update canUsePnprForMutations and any similar
checks in the nearby block that mirror the same logic.

---

Nitpick comments:
In `@pnpr/crates/pnpr/src/server.rs`:
- Around line 186-188: Update the inline comment in the compression layer in
server.rs to stop referencing the obsolete content type
`application/x-pnpr-install-inline`; instead note that the install accelerator
response is already gzipped (now served as `application/json` from the install
accelerator) or simply state that responses with an existing Content-Encoding
are skipped by the layer. Locate the comment near the compression/middleware
handling for accelerator responses (the block describing "accelerator response"
in server.rs) and adjust the wording to either mention "install accelerator
responses are already gzipped" or remove the specific MIME-type reference.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 91280b4e-31fb-4833-b6ee-ed96b494b087

📥 Commits

Reviewing files that changed from the base of the PR and between 50cb7af and 4079114.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .changeset/pnpr-resolve-only.md
  • installing/deps-installer/src/install/index.ts
  • pacquet/crates/cli/src/cli_args/install.rs
  • pacquet/crates/cli/tests/pnpr_install.rs
  • pacquet/crates/pnpr-client/Cargo.toml
  • pacquet/crates/pnpr-client/src/lib.rs
  • pacquet/crates/pnpr-client/src/tests.rs
  • pacquet/crates/pnpr-client/tests/integration.rs
  • pnpr/client/README.md
  • pnpr/client/package.json
  • pnpr/client/src/fetchFromPnpmRegistry.ts
  • pnpr/client/src/index.ts
  • pnpr/client/src/protocol.ts
  • pnpr/client/tsconfig.json
  • pnpr/crates/pnpr/src/config.rs
  • pnpr/crates/pnpr/src/install_accelerator.rs
  • pnpr/crates/pnpr/src/install_accelerator/diff.rs
  • pnpr/crates/pnpr/src/install_accelerator/grant_table.rs
  • pnpr/crates/pnpr/src/install_accelerator/grant_table/tests.rs
  • pnpr/crates/pnpr/src/install_accelerator/protocol.rs
  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs
  • pnpr/crates/pnpr/src/install_accelerator/public_packages/tests.rs
  • pnpr/crates/pnpr/src/install_accelerator/resolve.rs
  • pnpr/crates/pnpr/src/install_accelerator/tests.rs
  • pnpr/crates/pnpr/src/server.rs
  • worker/src/index.ts
  • worker/src/start.ts
  • worker/src/types.ts
💤 Files with no reviewable changes (13)
  • pnpr/crates/pnpr/src/install_accelerator/public_packages/tests.rs
  • pnpr/crates/pnpr/src/install_accelerator/public_packages.rs
  • pnpr/client/src/protocol.ts
  • worker/src/types.ts
  • pacquet/crates/pnpr-client/Cargo.toml
  • pnpr/crates/pnpr/src/install_accelerator/diff.rs
  • pnpr/crates/pnpr/src/install_accelerator/tests.rs
  • pnpr/crates/pnpr/src/install_accelerator/grant_table/tests.rs
  • pnpr/crates/pnpr/src/install_accelerator/protocol.rs
  • pnpr/crates/pnpr/src/install_accelerator/grant_table.rs
  • pnpr/client/tsconfig.json
  • worker/src/start.ts
  • pnpr/crates/pnpr/src/config.rs

Comment thread pacquet/crates/pnpr-client/src/lib.rs Outdated
The pacquet pnpr client never forwarded optional dependencies to the
server, so resolved lockfiles could miss optional edges. Match the
TypeScript client (which forwards them) by adding optional_dependencies
to InstallOptions, including it in the /v1/install payload, and
collecting the Optional dependency group in install_via_pnpr.
@zkochan zkochan changed the title perf(pnpr): two-phase install accelerator — resolve server-side, fetch tarballs in parallel perf(pnpr): resolve server-side and fetch tarballs directly Jun 6, 2026
@zkochan zkochan merged commit 089484a into main Jun 6, 2026
25 checks passed
@zkochan zkochan deleted the fix/12230 branch June 6, 2026 00:16
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.

pnpr install accelerator: single-stream /v1/install is bandwidth-bound on cold/WAN installs — decouple resolution from parallel file transfer

2 participants