Conversation
…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
|
Too many files changed? Review this PR in Change Stack to see how the pieces fit before you dive in. 📝 WalkthroughWalkthroughThe 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. ChangesResolve-only pnpr protocol and runtime
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
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
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
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
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
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
]
}
]
} |
|
| Branch | pr/12232 |
| Testbed | pacquet |
🚨 1 Alert
| Benchmark | Measure Units | View | Benchmark Result (Result Δ%) | Upper Boundary (Limit %) |
|---|---|---|---|---|
| isolated-linker.fresh-restore.cold-cache.cold-store | Latency 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
| Benchmark | Latency | Benchmark 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%) |
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.
Review Summary by QodoRestructure pnpr into two-phase resolver: server-side resolution, client-side parallel tarball fetching
WalkthroughsDescription• 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 Diagramflowchart 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"]
File Changes1. pnpr/crates/pnpr/src/install_accelerator.rs
|
Micro-Benchmark ResultsLinux |
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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 winDon't materialize the whole workspace for a filtered pnpr mutation.
preparePnprProjects()intentionally expands to every workspace importer so the server returns a complete lockfile, butinstallFromPnpmRegistry()reuses that same array forselectedProjectDirs. On filteredinstallSome/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 forallProjects.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 winPreserve
resolutionPolicyViolationsinstead of casting it away.
installFromPnpmRegistry()already returnsresolutionPolicyViolations, but this adapter drops the field and then casts the object toMutateModulesResult. That breaks the result contract for pnpr-backedmutateModules()calls and can bubbleundefinedout throughinstall()/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 winRestrict the pnpr fast path to
installSomecases it can actually emulate.
canUsePnprForMutations()currently routes every non-updateinstallSomethroughmergeInstallSelectors(), but that helper skips the normalinstallSome()behaviors forallowNew === false,saveCatalogName/defaultCatalog, andcatalogModemismatch 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 existinginstallSome()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 valueMinor: 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 usesapplication/json(set ininstall_accelerator.rsline 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-Encodingresponses 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
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.lockpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (28)
.changeset/pnpr-resolve-only.mdinstalling/deps-installer/src/install/index.tspacquet/crates/cli/src/cli_args/install.rspacquet/crates/cli/tests/pnpr_install.rspacquet/crates/pnpr-client/Cargo.tomlpacquet/crates/pnpr-client/src/lib.rspacquet/crates/pnpr-client/src/tests.rspacquet/crates/pnpr-client/tests/integration.rspnpr/client/README.mdpnpr/client/package.jsonpnpr/client/src/fetchFromPnpmRegistry.tspnpr/client/src/index.tspnpr/client/src/protocol.tspnpr/client/tsconfig.jsonpnpr/crates/pnpr/src/config.rspnpr/crates/pnpr/src/install_accelerator.rspnpr/crates/pnpr/src/install_accelerator/diff.rspnpr/crates/pnpr/src/install_accelerator/grant_table.rspnpr/crates/pnpr/src/install_accelerator/grant_table/tests.rspnpr/crates/pnpr/src/install_accelerator/protocol.rspnpr/crates/pnpr/src/install_accelerator/public_packages.rspnpr/crates/pnpr/src/install_accelerator/public_packages/tests.rspnpr/crates/pnpr/src/install_accelerator/resolve.rspnpr/crates/pnpr/src/install_accelerator/tests.rspnpr/crates/pnpr/src/server.rsworker/src/index.tsworker/src/start.tsworker/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
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.
Summary
Reworks pnpr from an install/file accelerator into a resolve-only accelerator:
POST /v1/resolveresolves against the client-supplied registries and returns a gzipped JSON lockfile responseThe follow-up resolution fast paths are included on the new measured path:
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.
pnpr@HEADpnpr@main1.677 s ± 0.0901.686 s ± 0.070492.5 ms ± 18.1521.9 ms ± 33.41.997 s ± 0.0252.003 s ± 0.0381.211 s ± 0.0241.236 s ± 0.038Trade-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-clientcargo test -p pnpr resolvercargo test -p pacquet-pnpr-clientcargo clippy --locked -p pnpr -p pacquet-pnpr-client --all-targets -- -D warningscargo fmt --all -- --checkcargo dylint --all -- --all-targets --workspacegit diff --checkorigin/fix/12230Note
This is a coordinated client/server protocol change. Clients using
/v1/resolveneed a pnpr server with this endpoint. Older/v1/installinline-file responses are not parsed by the new client path.Closes #12230
Written by an agent (Codex, GPT-5).