Zoltan Kochan 315e59b24a feat(reporter): emit pnpm:progress and pnpm:fetching-progress (#372)
Backfills two channels from #347 in one PR — both are "progress"
shaped so they share the tarball-side reporter threading.

### `pnpm:progress` — per-package status transitions

Mirrors pnpm's emit at
[`installing/package-requester/src/packageRequester.ts:435`](https://github.com/pnpm/pnpm/blob/086c5e91e8/installing/package-requester/src/packageRequester.ts#L435).
Four `status` values fire per package:

- **`resolved`** — emitted before the tarball download in both
  lockfile paths. In the no-lockfile path,
  `install_package_from_registry::run` emits once the registry has
  picked a version. In the frozen-lockfile path,
  `create_virtual_store::run`'s warm batch and per-snapshot
  `install_package_by_snapshot::run`'s entry emit on each snapshot
  (the lockfile *is* the resolution).
- **`fetched`** — emitted from `fetch_and_extract_with_retry` on
  the successful `Ok` branch. Failed attempts that retry don't fire
  it; cache-hit early returns don't fire it.
- **`found_in_store`** — emitted from `run_without_mem_cache`'s
  cache-hit early returns (prefetched-cas + `load_cached_cas_paths`),
  from `create_virtual_store::run`'s warm batch where prefetch
  already settled the bytes, and from `run_with_mem_cache`'s
  in-process dedup hits (so the second requester of a shared URL
  also ticks the per-package counter).
- **`imported`** — emitted after `create_cas_files` returns `Ok` in
  both `create_virtual_dir_by_snapshot::run` and
  `install_package_from_registry::install_package_version`. `method`
  is the optimistic wire-mapped configured method
  (`Auto`/`CloneOrCopy` → `clone`); pacquet doesn't surface the
  per-package resolved method past `link_file`'s install-scoped
  atomic. Tracked under #347 for refinement once that becomes
  per-package observable.

### `pnpm:fetching-progress` — per-tarball download progress

Mirrors pnpm's emit at
[`installing/package-requester/src/packageRequester.ts:560`](https://github.com/pnpm/pnpm/blob/086c5e91e8/installing/package-requester/src/packageRequester.ts#L560)
and
[`fetching/tarball-fetcher/src/remoteTarballFetcher.ts:143`](https://github.com/pnpm/pnpm/blob/086c5e91e8/fetching/tarball-fetcher/src/remoteTarballFetcher.ts#L143).
Two `status` values:

- **`started`** — fires exactly once per HTTP attempt, including
  attempts that fail before the response head arrives (DNS /
  connect / timeout) so retried attempts stay visible in the
  reporter. `size` is the response's `Content-Length` when a
  response head arrives, and JSON `null` (i.e. `None`) when there
  is no response, or when the response is chunked / unknown-length.
  pnpm's reporter checks `size != null` before rendering a percent
  gauge.
- **`in_progress`** — gated and throttled to match pnpm exactly:
  - Skip entirely when `Content-Length` is unknown (chunked /
    streaming) or the tarball is smaller than `BIG_TARBALL_SIZE = 5 MB`.
    Most npm packages are well under this; per-byte progress for
    tiny tarballs floods the JS reporter with values that would
    render as 100% before any UI tick can show them.
  - Throttle to 500ms (matches `lodash.throttle(opts.onProgress, 500)`).
  - Trailing edge: a final emit after the body finishes if the
    last in-flight `downloaded` value differs from the last
    reported one. Matches `lodash.throttle`'s default
    `{leading: true, trailing: true}` so the consumer sees the
    actual end-of-download byte count.

### `run_with_mem_cache` deadlock fix

Pre-existing bug exposed by review: when the *owning* requester's
`run_without_mem_cache` errored, the function returned without
flipping the cache slot to `Available` or notifying waiters.
Concurrent siblings parked on `notify.notified().await` forever
and the install hung — a single 404 could deadlock every later
consumer of the same URL.

Fixed by adding `CacheValue::Failed`. The owner uses `match` on
the result; on `Err` it sets `Failed`, removes the entry from
`mem_cache` (so a fresh request can retry without inheriting the
failure), and notifies waiters. Waiters check for `Failed` on
wake and surface a new `TarballError::SiblingFetchFailed { url }`
instead of the `unreachable!` the previous code would have hit.

### Threading

`pacquet-tarball` gains a `pacquet-reporter` dependency.
`DownloadTarballToStore` carries a new `requester: &'a str` field
(the install root, same value as the `prefix` in
`pacquet_reporter::StageLog`). `<R: Reporter>` is threaded through
`run_with_mem_cache`, `run_without_mem_cache`,
`fetch_and_extract_with_retry`, and `fetch_and_extract_once`. The
generic monomorphises away — zero runtime cost. `requester` and
`<R>` cascade up through `InstallPackageBySnapshot`,
`InstallPackageFromRegistry`, `CreateVirtualDirBySnapshot`,
`CreateVirtualStore`, `InstallFrozenLockfile`,
`InstallWithoutLockfile`, and `Install::run`.
2026-05-02 22:49:05 +02:00
Description
No description provided
MIT 368 MiB
Languages
Rust 63.2%
TypeScript 36.3%
JavaScript 0.4%