Skip to content

refactor(npm-resolver): unify pickPackage's cache-hit paths into a tier'd resolver chain #11628

@zkochan

Description

@zkochan

Background

Came up while reviewing #11622. resolving/npm-resolver/src/pickPackage.ts currently has three near-identical cache-hit paths — in-memory metaCache, preferOffline/pickLowestVersion disk-cache, and the 304-Not-Modified branch — each of which calls maybeUpgradeAbbreviatedMetaForReleaseAge, then persistUpgradedMeta (with a dryRun guard), then ctx.metaCache.set, then pickMatchingVersionFinal. Plus a fourth abbreviated→full upgrade lives inline in the post-fetch (200) path with its own slightly different logic (saves abbreviated to disk first, doesn't forward etag/modified).

Two fast-path catch blocks (version-spec cache and mtime-gated cache) also do the same dance: try pickMatchingVersionFast, swallow ERR_PNPM_MISSING_TIME under strict, fall through.

Proposal

Restructure pickPackage as a tier'd resolver chain. Each tier is a function that returns { meta, pickedPackage } | null:

tryFromInMemoryCache
  → tryFromVersionSpecDiskCache
  → tryFromMtimeGatedDiskCache
  → fetchAndMaybeUpgrade   (handles 304 + post-200 upgrade in one place)

All four tiers share a single "ensure meta has `time` if needed (upgrade, persist, set cache)" helper, replacing the current maybeUpgradeAbbreviatedMetaForReleaseAge + persistUpgradedMeta + boilerplate trio at every site.

Wins

  • Removes 3× duplication of the upgrade-and-persist block.
  • The post-fetch upgrade joins the same code path, so future upgrade-related bugs only need to be fixed in one place (cf. the in-memory + preferOffline gap that fix(npm-resolver): minimumReleaseAge handling for cached abbreviated metadata  #11622 had to fix after the fact).
  • Fast-path catch invariant (shouldRethrowFromFastPathCache) lives once, naturally.
  • The function reads top-down as a sequence of fallback attempts.

Risks / non-goals

  • Touches pre-PR code paths — the version-spec and mtime-gated fast paths exist as micro-optimizations to skip the network when possible. The chain refactor must preserve their short-circuit behavior, including the MISSING_TIME-swallowing fall-through.
  • The post-fetch upgrade currently saves the abbreviated meta to disk before fetching full (so a failed upgrade still leaves something cached). That behavior should be preserved or explicitly traded off.
  • The dual-cache layout (ABBREVIATED_META_DIR vs FULL_META_DIR) is unchanged by this refactor; the abbreviated dir continues to hold upgraded-to-full content after an upgrade. Worth a comment in the unified helper.

References


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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions