Files
pnpm/cache/api/src/cacheView.ts
Zoltan Kochan 2554264fdd perf: use NDJSON format for metadata cache (#11188)
The metadata cache files now use a two-line NDJSON format:
- Line 1: cache headers (etag, modified, cachedAt) ~100 bytes
- Line 2: raw registry metadata JSON (unchanged)

This allows loadMetaHeaders to read only the first 1 KB of the file
to extract conditional-request headers (etag, modified), avoiding
the cost of reading and parsing multi-MB metadata files when the
registry returns 200 and the old metadata would be discarded.

Also moves cache directories to v11/ namespace (v11/metadata,
v11/metadata-full, v11/metadata-full-filtered) since the format
is not backwards compatible.
2026-04-04 01:24:05 +02:00

70 lines
2.4 KiB
TypeScript

import fs from 'node:fs'
import path from 'node:path'
import type { PackageMeta } from '@pnpm/resolving.npm-resolver'
import { StoreIndex, storeIndexKey } from '@pnpm/store.index'
import getRegistryName from 'encode-registry'
import { glob } from 'tinyglobby'
interface CachedVersions {
cachedVersions: string[]
nonCachedVersions: string[]
cachedAt?: string
distTags: Record<string, string>
}
export async function cacheView (opts: { cacheDir: string, storeDir: string, registry?: string }, packageName: string): Promise<string> {
const prefix = opts.registry ? `${getRegistryName(opts.registry)}` : '*'
const metaFilePaths = (await glob(`${prefix}/${packageName}.jsonl`, {
cwd: opts.cacheDir,
expandDirectories: false,
})).sort()
const metaFilesByPath: Record<string, CachedVersions> = {}
const storeIndex = new StoreIndex(opts.storeDir)
try {
for (const filePath of metaFilePaths) {
let metaObject: PackageMeta | null
const fullPath = path.join(opts.cacheDir, filePath)
let mtime: Date | undefined
try {
const raw = fs.readFileSync(fullPath, 'utf8')
mtime = fs.statSync(fullPath).mtime
const newlineIdx = raw.indexOf('\n')
if (newlineIdx !== -1) {
// NDJSON format: line 1 = headers, line 2 = metadata
metaObject = JSON.parse(raw.slice(newlineIdx + 1)) as PackageMeta
} else {
metaObject = JSON.parse(raw) as PackageMeta
}
} catch {
continue
}
if (!metaObject) continue
const cachedVersions: string[] = []
const nonCachedVersions: string[] = []
for (const [version, manifest] of Object.entries(metaObject.versions)) {
if (!manifest.dist.integrity) continue
const key = storeIndexKey(manifest.dist.integrity, `${manifest.name}@${manifest.version}`)
if (storeIndex.has(key)) {
cachedVersions.push(version)
} else {
nonCachedVersions.push(version)
}
}
let registryName = filePath
while (path.dirname(registryName) !== '.') {
registryName = path.dirname(registryName)
}
metaFilesByPath[registryName.replaceAll('+', ':')] = {
cachedVersions,
nonCachedVersions,
cachedAt: mtime?.toString(),
distTags: metaObject['dist-tags'],
}
}
} finally {
storeIndex.close()
}
return JSON.stringify(metaFilesByPath, null, 2)
}