@vltpkg/package-info 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +39 -10
  2. package/package.json +14 -14
package/dist/index.js CHANGED
@@ -26,6 +26,15 @@ const manifestCacheMaxAge = 5 * 60 * 1000;
26
26
  const noRegistryError = (spec) => error('No registry configured to resolve this spec. Set "registry" in ' +
27
27
  'vlt.json, pass --registry, or run `vlt login --registry=<url>`. ' +
28
28
  'See https://docs.vlt.sh/cli', { code: 'ECONFIG', spec });
29
+ /**
30
+ * A selector that can point at a different version tomorrow: a dist tag,
31
+ * or a range matching anything (`*`, empty string). Manifest results for
32
+ * these are not cached to disk, and packument requests for them force a
33
+ * revalidation of the registry client's cache entry.
34
+ *
35
+ * Takes a *final* spec (`spec.final`), same as `pickManifest` sees.
36
+ */
37
+ const isMovingSelector = (f) => !!(f.distTag || f.range?.isAny);
29
38
  export class PackageInfoClient {
30
39
  #registryClient;
31
40
  #projectRoot;
@@ -39,7 +48,9 @@ export class PackageInfoClient {
39
48
  #cachePath;
40
49
  // In-flight coalescing key is `${registry}${name}` — no representation
41
50
  // component. Safe only because every caller requests the same full
42
- // packument (see #fetchPackument).
51
+ // packument (see #fetchPackument). The one thing that does vary per
52
+ // caller is forceRevalidate, so record it and let a moving selector
53
+ // reuse a forced promise but never a non-forced one (see packument()).
43
54
  #packumentPromises = new Map();
44
55
  // unique temp file names for atomic manifest cache writes
45
56
  #manifestWriteRandom = randomBytes(6).toString('hex');
@@ -333,11 +344,9 @@ export class PackageInfoClient {
333
344
  if (options.before) {
334
345
  return;
335
346
  }
336
- // if the final resolved spec is either a dist tag or something that
337
- // matches any range (such as a semver range of `*` or empty string)
338
- // then we skip caching
347
+ // a moving selector's result is variable, so don't cache it
339
348
  const f = spec.final;
340
- if (f.distTag || f.range?.isAny) {
349
+ if (isMovingSelector(f)) {
341
350
  return;
342
351
  }
343
352
  const key = this.#manifestCacheKey(f, options);
@@ -660,16 +669,30 @@ export class PackageInfoClient {
660
669
  throw noRegistryError(spec);
661
670
  // Coalescing key has no representation component (see #fetchPackument).
662
671
  const packumentKey = `${registry}${name}`;
672
+ const forced = isMovingSelector(f);
663
673
  const inflight = this.#packumentPromises.get(packumentKey);
664
- if (inflight)
665
- return inflight;
674
+ // a moving selector must not ride along on a non-forced request:
675
+ // that one can settle to a fresh-but-stale cache hit, which is
676
+ // exactly what forceRevalidate exists to avoid. the other
677
+ // direction is fine -- a forced result is never staler.
678
+ // costs at most one extra concurrent GET for the same packument
679
+ // when both shapes are asked for at once.
680
+ if (inflight && (!forced || inflight.forced))
681
+ return inflight.promise;
666
682
  const pakuURL = new URL(name, registry);
667
683
  const promise = this.#fetchPackument(spec, options, pakuURL);
668
- this.#packumentPromises.set(packumentKey, promise);
669
- // Clean up once settled so we don't leak memory.
684
+ const record = { promise, forced };
685
+ this.#packumentPromises.set(packumentKey, record);
686
+ // Clean up once settled so we don't leak memory, unless a forced
687
+ // request has since taken the slot over.
670
688
  // Use .then/.catch instead of .finally to avoid creating
671
689
  // an unhandled rejection from the derived promise.
672
- promise.then(() => this.#packumentPromises.delete(packumentKey), () => this.#packumentPromises.delete(packumentKey));
690
+ const clear = () => {
691
+ if (this.#packumentPromises.get(packumentKey) === record) {
692
+ this.#packumentPromises.delete(packumentKey);
693
+ }
694
+ };
695
+ promise.then(clear, clear);
673
696
  return promise;
674
697
  }
675
698
  }
@@ -703,6 +726,12 @@ export class PackageInfoClient {
703
726
  const response = await (await this.getRegistryClient()).request(pakuURL, {
704
727
  headers: { accept: 'application/json' },
705
728
  ...(useCache === false ? { useCache } : {}),
729
+ // costs a conditional GET per moving selector on an otherwise warm
730
+ // cache, install included. 304s are cheap but not free; the
731
+ // alternative is serving a dist tag that moved (#1656).
732
+ ...(isMovingSelector(spec.final) ?
733
+ { forceRevalidate: true }
734
+ : {}),
706
735
  });
707
736
  if (response.statusCode !== 200) {
708
737
  throw this.#resolveError(spec, options, 'failed to fetch packument', {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vltpkg/package-info",
3
3
  "description": "Resolve and fetch package metadata and tarballs",
4
- "version": "1.0.9",
4
+ "version": "1.0.10",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/vltpkg/vltpkg.git",
@@ -13,17 +13,17 @@
13
13
  "url": "http://vlt.sh"
14
14
  },
15
15
  "dependencies": {
16
- "@vltpkg/error-cause": "1.0.9",
17
- "@vltpkg/git": "1.0.9",
18
- "@vltpkg/output": "1.0.9",
19
- "@vltpkg/package-json": "1.0.9",
20
- "@vltpkg/pick-manifest": "1.0.9",
21
- "@vltpkg/registry-client": "1.0.9",
22
- "@vltpkg/spec": "1.0.9",
23
- "@vltpkg/tar": "1.0.9",
24
- "@vltpkg/types": "1.0.9",
25
- "@vltpkg/workspaces": "1.0.9",
26
- "@vltpkg/xdg": "1.0.9",
16
+ "@vltpkg/error-cause": "1.0.10",
17
+ "@vltpkg/git": "1.0.10",
18
+ "@vltpkg/output": "1.0.10",
19
+ "@vltpkg/package-json": "1.0.10",
20
+ "@vltpkg/pick-manifest": "1.0.10",
21
+ "@vltpkg/registry-client": "1.0.10",
22
+ "@vltpkg/spec": "1.0.10",
23
+ "@vltpkg/tar": "1.0.10",
24
+ "@vltpkg/types": "1.0.10",
25
+ "@vltpkg/workspaces": "1.0.10",
26
+ "@vltpkg/xdg": "1.0.10",
27
27
  "ssri": "^13.0.0",
28
28
  "tar": "^7.5.2"
29
29
  },
@@ -32,8 +32,8 @@
32
32
  "@types/node": "^22.19.2",
33
33
  "@types/pacote": "^11.1.8",
34
34
  "@vltpkg/benchmark": "0.0.0",
35
- "@vltpkg/cache-unzip": "1.0.9",
36
- "@vltpkg/vlt-json": "1.0.9",
35
+ "@vltpkg/cache-unzip": "1.0.10",
36
+ "@vltpkg/vlt-json": "1.0.10",
37
37
  "eslint": "^9.39.1",
38
38
  "pacote": "^21.0.4",
39
39
  "prettier": "^3.7.4",