Skip to content

feat(pacquet/resolving-npm-resolver): port abbreviated metadata fast path#11794

Merged
zkochan merged 2 commits into
mainfrom
npm-resolver-opt
May 21, 2026
Merged

feat(pacquet/resolving-npm-resolver): port abbreviated metadata fast path#11794
zkochan merged 2 commits into
mainfrom
npm-resolver-opt

Conversation

@zkochan

@zkochan zkochan commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

Default the resolver-time fetch to the abbreviated install-v1 packument (application/vnd.npm.install-v1+json) and only request full metadata when the call explicitly needs it — mirrors pnpm's fullMetadata = opts.optional || ctx.fullMetadata derivation in pickPackage.ts L201.

Pacquet previously always requested full metadata; the picker module documented this as a known simplification. After this PR pacquet matches pnpm's two-tier metadata flow.

What changed

  • Fetchers (fetch_full_metadata.rs, fetch_full_metadata_cached.rs): added a full_metadata: bool field. Selects between Accept: application/json; q=1.0, */* (full) and application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */* (abbreviated). The cached variant also routes the on-disk mirror to FULL_META_DIR vs ABBREVIATED_META_DIR.
  • PickPackageContext.full_metadata is the install-wide bias; PickPackageOptions.optional is the per-call override that forces full (needed for libc/cpu/os fields on optional deps — pnpm/pnpm#9950). In-memory cache key gains a :full suffix when full, so the two modes can coexist without contamination.
  • maybe_upgrade_abbreviated_meta_for_release_age ports pnpm's maybeUpgradeAbbreviatedMetaForReleaseAge. When published_by is active, the picker has abbreviated metadata that lacks per-version time, and the package's top-level modified falls past the cutoff, the orchestrator re-fetches full metadata so the minimumReleaseAge check runs on real timestamps instead of degrading to the warn-and-skip fallback. The upgraded full meta is persisted back to the abbreviated mirror so the next install skips the upgrade fetch — matches upstream's persistUpgradedMeta.
  • Resolver wiring: NpmResolver and NamedRegistryResolver gain full_metadata (defaulted to false) and plumb wanted_dependency.optional into the picker. The verifier sets full_metadata: true explicitly.
  • persist_meta_to_mirror takes a meta_dir parameter so tests can seed either cache.

Test plan

  • 5 new tests in pick_package/tests.rs: abbreviated default, optional override forces full, cache-key separation between modes, upgrade triggers when modified > cutoff, upgrade skipped when modified == cutoff (boundary).
  • All 1836 workspace tests pass (cargo nextest run --workspace).
  • cargo fmt --check clean.
  • cargo clippy --workspace --all-targets -- --deny warnings clean.
  • Existing fetcher / pick_package / resolver tests updated for the new struct fields and the q-value Accept headers.

Written by an agent (Claude Code, claude-opus-4-7).

Summary by CodeRabbit

Release Notes

  • New Features
    • Improved package metadata resolution with optimized abbreviated metadata format by default, reducing resolution time.
    • Enhanced support for optional dependencies with intelligent full metadata fetching when required.
    • Separate cache management for different metadata types to optimize storage and retrieval.
    • Trust verification now requests appropriate metadata depth based on security requirements.

Review Change Stack

…path

Default the resolver-time fetch to the abbreviated install-v1
packument (`application/vnd.npm.install-v1+json`) and only request
full metadata when the call explicitly needs it. Mirrors pnpm's
`fullMetadata = opts.optional || ctx.fullMetadata` derivation in
[pickPackage.ts L201](https://github.com/pnpm/pnpm/blob/2a9bd897bf/resolving/npm-resolver/src/pickPackage.ts#L201).

- Adds `full_metadata` to the cached/non-cached fetchers and routes
  the Accept header + mirror dir (`ABBREVIATED_META_DIR` vs
  `FULL_META_DIR`) off it.
- `PickPackageContext.full_metadata` is the install-wide bias;
  `PickPackageOptions.optional` forces full per-call (#9950).
  In-memory cache key gains a `:full` suffix when full so the two
  modes can coexist without contamination.
- Ports `maybeUpgradeAbbreviatedMetaForReleaseAge`: when
  `published_by` is active, the picker ends up with an abbreviated
  packument that lacks per-version `time`, and the package's
  top-level `modified` falls past the cutoff, the orchestrator
  re-fetches full metadata so the maturity check runs on real
  timestamps. The upgraded full meta is persisted back to the
  abbreviated mirror so the next install skips the upgrade fetch.
- Verifier and resolver pass the appropriate flag explicitly
  (`true` for the verifier, `false` for the resolver and named
  registry resolver).

Tests cover the abbreviated default, the optional override, the
cache-key separation, the boundary case `modified == cutoff`, and
the upgrade trigger when `modified > cutoff`.

---
Written by an agent (Claude Code, claude-opus-4-7).
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17d1bbe3-7f78-4b19-9fc8-f2c60df8cc43

📥 Commits

Reviewing files that changed from the base of the PR and between b2a95fa and a03f9b8.

📒 Files selected for processing (15)
  • pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
  • pacquet/crates/package-manager/src/install_without_lockfile.rs
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs
  • pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs
  • pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata.rs
  • pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata/tests.rs
  • pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata_cached.rs
  • pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata_cached/tests.rs
  • pacquet/crates/resolving-npm-resolver/src/named_registry_resolver.rs
  • pacquet/crates/resolving-npm-resolver/src/named_registry_resolver/tests.rs
  • pacquet/crates/resolving-npm-resolver/src/npm_resolver.rs
  • pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.rs
  • pacquet/crates/resolving-npm-resolver/src/pick_package.rs
  • pacquet/crates/resolving-npm-resolver/src/pick_package/tests.rs
  • pacquet/crates/resolving-npm-resolver/tests/chain.rs

📝 Walkthrough

Walkthrough

This PR implements full vs. abbreviated npm packument metadata selection throughout the resolution stack. Resolvers now default to abbreviated metadata but can upgrade to full metadata per-call when needed (e.g., for optional dependencies or maturity filtering), with automatic upgrade logic when abbreviated metadata lacks per-version timestamps.

Changes

Full vs. Abbreviated Packument Metadata Selection

Layer / File(s) Summary
Metadata contract and accept-header selection
pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata.rs
New ACCEPT_FULL_DOC and ACCEPT_ABBREVIATED_DOC constants and full_metadata: bool flag in FetchFullMetadataOptions enable dynamic selection between full and abbreviated packument forms; the Accept header is now chosen at request time instead of hardcoded.
Cached fetch mirror and header logic
pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata_cached.rs, tests/fetch_full_metadata_cached/*
Cache layer now selects mirror directory (metadata-full/ vs metadata/) based on full_metadata flag and applies the corresponding Accept header during conditional GET requests; module docs updated to reflect separate mirror layouts.
Picker context and per-call metadata options
pacquet/crates/resolving-npm-resolver/src/pick_package.rs
PickPackageContext gains full_metadata (install-wide bias) and PickPackageOptions gains optional (per-call forcing); mirror directory constants imported.
Picker metadata selection and upgrade orchestration
pacquet/crates/resolving-npm-resolver/src/pick_package.rs, tests/pick_package/*
Orchestrator computes full_metadata from opts.optional || ctx.full_metadata, selects mirror directory accordingly, and implements abbreviated-to-full upgrade logic when published_by is active but per-version time is missing; in-memory cache keys scoped by metadata mode; persist_meta_to_mirror refactored to accept explicit mirror directory; new maybe_upgrade_abbreviated_meta_for_release_age helper with upgrade outcome and mirror persistence.
Resolver configuration and picker wiring
pacquet/crates/resolving-npm-resolver/src/npm_resolver.rs, pacquet/crates/resolving-npm-resolver/src/named_registry_resolver.rs
NpmResolver and NamedRegistryResolver both gain full_metadata: bool field; resolvers extract optional from WantedDependency.optional and thread it through pick_from_registry; signature extended to accept optional parameter and wire both flags into picker context and options.
Integration: installer and verifier configuration
pacquet/crates/package-manager/src/install_without_lockfile.rs, pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier.rs, pacquet/crates/package-manager/src/install_package_from_registry/tests.rs
Installer initializes resolvers with full_metadata: false (abbreviated by default); verifier forces full_metadata: true to ensure per-version time and trust evidence are available for maturity and trust checks.
Test infrastructure and coverage
pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata/tests.rs, pacquet/crates/resolving-npm-resolver/src/fetch_full_metadata_cached/tests.rs, pacquet/crates/resolving-npm-resolver/src/pick_package/tests.rs, pacquet/crates/resolving-npm-resolver/src/npm_resolver/tests.rs, pacquet/crates/resolving-npm-resolver/src/named_registry_resolver/tests.rs, pacquet/crates/resolving-npm-resolver/src/create_npm_resolution_verifier/tests.rs, pacquet/crates/resolving-npm-resolver/tests/chain.rs
Mock server expectations updated for dynamic Accept headers; existing tests instrumented with full_metadata configuration; comprehensive new test suite validates endpoint/mirror selection (abbreviated "install-v1" vs full), optional-dependency forcing, separate in-memory cache keying, and abbreviated-to-full upgrade at maturity cutoff boundaries including mirror persistence of upgraded metadata.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • pnpm/pnpm#11628: Unifying pickPackage's cache-hit paths and consolidating upgrade-and-persist behavior is directly addressed by the new orchestration in pick_package and its upgrade logic.
  • pnpm/pnpm#11722: Lockfile verification work directly overlaps with changes to the verifier's packument-fetch code and threading of full_metadata through the resolution pipeline.
  • pnpm/pnpm#11756: NpmResolver/installer wiring of the new full_metadata flag directly relates to resolver configuration changes in this PR.

Possibly related PRs

  • pnpm/pnpm#11622: Both PRs implement abbreviated-to-full metadata upgrade when per-version time is missing under minimumReleaseAge filtering, e.g., maybe_upgrade_abbreviated_meta_for_release_age logic.
  • pnpm/pnpm#11772: Both PRs modify JSR resolution path in npm_resolver.rs with changes to resolve_jsr_impl and pick_from_registry wiring for optional-dependency handling.
  • pnpm/pnpm#11704: Both PRs update the verifier's publish-time lookup to fetch full metadata via fetch_full_metadata_cached.

Suggested labels

area: resolution

Poem

🐰 Abbreviated metadata hops along,
Then when timestamps are missing, it's wrong—
So the picker leaps up for the full packument true,
Caching both paths through the mirrors brand new!
Full or abbreviated, now both have their say.

✨ 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 npm-resolver-opt

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 May 21, 2026

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.07      8.4±0.40ms   516.3 KB/sec    1.00      7.8±0.27ms   554.4 KB/sec

…a intra-doc link

`fetch_full_metadata` is both a function and a module, so the bare
`[fetch_full_metadata]` link triggers
`rustdoc::broken_intra_doc_links` under `--deny warnings`. Add
parentheses so it resolves to the function.

---
Written by an agent (Claude Code, claude-opus-4-7).
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.78125% with 31 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.21%. Comparing base (df990fd) to head (a03f9b8).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
.../crates/resolving-npm-resolver/src/pick_package.rs 67.70% 31 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11794      +/-   ##
==========================================
+ Coverage   87.20%   87.21%   +0.01%     
==========================================
  Files         190      193       +3     
  Lines       22525    22745     +220     
==========================================
+ Hits        19642    19837     +195     
- Misses       2883     2908      +25     

☔ View full report in Codecov by Sentry.
📢 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.

@github-actions

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.511 ± 0.099 2.352 2.708 1.04 ± 0.06
pacquet@main 2.417 ± 0.088 2.325 2.540 1.00
pnpm 4.879 ± 0.083 4.767 5.011 2.02 ± 0.08
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.5114722401200007,
      "stddev": 0.09922436863299834,
      "median": 2.51435993732,
      "user": 2.7726585399999997,
      "system": 3.7823095,
      "min": 2.35164001832,
      "max": 2.70825047732,
      "times": [
        2.53539317032,
        2.48113460832,
        2.5804553873200002,
        2.70825047732,
        2.56307147432,
        2.52532752932,
        2.50339234532,
        2.35164001832,
        2.4708671193200002,
        2.39519027132
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.41694094312,
      "stddev": 0.087946634574729,
      "median": 2.36510078982,
      "user": 2.80432894,
      "system": 3.7189978000000004,
      "min": 2.3253760313200003,
      "max": 2.54025872332,
      "times": [
        2.35629283832,
        2.3253760313200003,
        2.37234943732,
        2.3420560203200003,
        2.54025872332,
        2.53494301932,
        2.35785214232,
        2.45783668232,
        2.52630498332,
        2.3561395533200002
      ]
    },
    {
      "command": "pnpm",
      "mean": 4.87898504812,
      "stddev": 0.08342204992821828,
      "median": 4.86118812032,
      "user": 8.302601039999999,
      "system": 4.2356356,
      "min": 4.76724645732,
      "max": 5.01118736732,
      "times": [
        4.92640410232,
        4.83139105332,
        4.77957710032,
        4.83877100432,
        4.88360523632,
        4.93452690732,
        4.831757677320001,
        4.76724645732,
        4.98538357532,
        5.01118736732
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 680.3 ± 19.1 666.9 733.3 1.00
pacquet@main 721.7 ± 57.5 687.0 870.0 1.06 ± 0.09
pnpm 2589.7 ± 82.6 2494.8 2706.1 3.81 ± 0.16
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.68030974682,
      "stddev": 0.0190958721363392,
      "median": 0.67510885582,
      "user": 0.38464622000000004,
      "system": 1.56064372,
      "min": 0.66688203682,
      "max": 0.73333308582,
      "times": [
        0.73333308582,
        0.67848867182,
        0.67512061982,
        0.6719791458200001,
        0.67411647982,
        0.67784498782,
        0.68081557982,
        0.66941976882,
        0.66688203682,
        0.67509709182
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.7216909539199999,
      "stddev": 0.05748444012501744,
      "median": 0.69359617782,
      "user": 0.40327751999999994,
      "system": 1.5740221200000002,
      "min": 0.68703620182,
      "max": 0.86997046782,
      "times": [
        0.86997046782,
        0.69555754682,
        0.69120849982,
        0.69023980982,
        0.7217799808200001,
        0.71387665682,
        0.6916348088200001,
        0.68964573782,
        0.76595982882,
        0.68703620182
      ]
    },
    {
      "command": "pnpm",
      "mean": 2.5897434523199996,
      "stddev": 0.08257935042297507,
      "median": 2.56276864582,
      "user": 3.2852317199999996,
      "system": 2.24980022,
      "min": 2.49479246082,
      "max": 2.70614046282,
      "times": [
        2.69370965982,
        2.56235973682,
        2.70614046282,
        2.64959589182,
        2.53290100582,
        2.49479246082,
        2.5631775548199998,
        2.51156404382,
        2.50909433282,
        2.67409937382
      ]
    }
  ]
}

@zkochan zkochan marked this pull request as ready for review May 21, 2026 05:15
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants