Resolver for npm-hosted packages with caching, offline support, and registry authentication
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
The metadata management system handles caching and retrieval of package information from npm registries, providing efficient storage and access to package metadata with support for offline operations.
The cache interface provides storage and retrieval of package metadata to minimize registry requests.
/**
* Interface for caching package metadata
*/
interface PackageMetaCache {
/**
* Retrieve cached metadata for a package
* @param key - Cache key (typically package name)
* @returns Cached metadata or undefined if not found
*/
get(key: string): PackageMeta | undefined;
/**
* Store metadata in cache
* @param key - Cache key (typically package name)
* @param meta - Package metadata to cache
*/
set(key: string, meta: PackageMeta): void;
/**
* Check if metadata exists in cache
* @param key - Cache key to check
* @returns True if metadata is cached
*/
has(key: string): boolean;
}Complete package metadata retrieved from npm registries.
/**
* Complete package metadata from npm registry
*/
interface PackageMeta {
/** Distribution tags mapping tag names to version numbers */
'dist-tags': { [name: string]: string };
/** All available versions with their manifests */
versions: { [name: string]: PackageInRegistry };
/** Timestamp when metadata was cached (optional) */
cachedAt?: number;
}
/**
* Package manifest with distribution information
*/
type PackageInRegistry = PackageManifest & {
/** Distribution metadata for downloading */
dist: {
/** Subresource integrity hash (optional) */
integrity?: string;
/** SHA-1 hash of the tarball */
shasum: string;
/** URL to download the package tarball */
tarball: string;
};
};The resolver automatically selects appropriate package versions from cached metadata based on the specification requirements and any preferred version configuration. This selection process is handled internally when resolving packages.
import createResolveFromNpm, { PackageMetaCache, PackageMeta } from '@pnpm/npm-resolver';
// Simple Map-based cache
const cache: PackageMetaCache = new Map();
const resolve = createResolveFromNpm({
metaCache: cache,
store: './pnpm-store',
rawNpmConfig: {
registry: 'https://registry.npmjs.org/',
},
});
// First resolution will fetch from registry and cache
const result1 = await resolve(
{ alias: 'lodash', pref: '^4.0.0' },
{
registry: 'https://registry.npmjs.org/',
prefix: process.cwd()
}
);
// Second resolution will use cached metadata
const result2 = await resolve(
{ alias: 'lodash', pref: '4.17.20' },
{
registry: 'https://registry.npmjs.org/',
prefix: process.cwd()
}
);
console.log(`Cache has lodash: ${cache.has('lodash')}`);class PersistentPackageCache implements PackageMetaCache {
private cache = new Map<string, PackageMeta>();
private cacheFile: string;
constructor(cacheFile: string) {
this.cacheFile = cacheFile;
this.loadFromDisk();
}
get(key: string): PackageMeta | undefined {
return this.cache.get(key);
}
set(key: string, meta: PackageMeta): void {
meta.cachedAt = Date.now();
this.cache.set(key, meta);
this.saveToDisk();
}
has(key: string): boolean {
return this.cache.has(key);
}
private loadFromDisk(): void {
try {
const data = fs.readFileSync(this.cacheFile, 'utf8');
const entries = JSON.parse(data);
this.cache = new Map(entries);
} catch {
// Cache file doesn't exist or is invalid
}
}
private saveToDisk(): void {
const entries = Array.from(this.cache.entries());
fs.writeFileSync(this.cacheFile, JSON.stringify(entries));
}
}
const persistentCache = new PersistentPackageCache('./package-cache.json');// Inspect cached metadata
const cachedMeta = cache.get('express');
if (cachedMeta) {
console.log('Available versions:', Object.keys(cachedMeta.versions));
console.log('Dist tags:', cachedMeta['dist-tags']);
console.log('Latest version:', cachedMeta['dist-tags'].latest);
// Check if cached recently (within 1 hour)
const oneHourAgo = Date.now() - (60 * 60 * 1000);
const isFresh = cachedMeta.cachedAt && cachedMeta.cachedAt > oneHourAgo;
console.log(`Cache is fresh: ${isFresh}`);
}// Inspect cached metadata
const expressMeta = cache.get('express');
if (expressMeta) {
console.log('Available versions:', Object.keys(expressMeta.versions));
console.log('Dist tags:', expressMeta['dist-tags']);
console.log('Latest version:', expressMeta['dist-tags'].latest);
// Get specific version information
const latestVersion = expressMeta['dist-tags'].latest;
const packageInfo = expressMeta.versions[latestVersion];
if (packageInfo) {
console.log(`Latest: ${packageInfo.name}@${packageInfo.version}`);
console.log(`Tarball: ${packageInfo.dist.tarball}`);
console.log(`Integrity: ${packageInfo.dist.integrity || packageInfo.dist.shasum}`);
}
}// Create resolver that prefers cached data
const offlineFirstResolve = createResolveFromNpm({
metaCache: cache,
store: './pnpm-store',
preferOffline: true, // Use cache when available
rawNpmConfig: {
registry: 'https://registry.npmjs.org/',
},
});
// This will use cached metadata if available
const result = await offlineFirstResolve(
{ alias: 'react', pref: '^17.0.0' },
{
registry: 'https://registry.npmjs.org/',
prefix: process.cwd()
}
);// Pre-populate cache with commonly used packages
const commonPackages = ['lodash', 'express', 'react', 'axios'];
for (const packageName of commonPackages) {
try {
await resolve(
{ alias: packageName, pref: 'latest' },
{
registry: 'https://registry.npmjs.org/',
prefix: process.cwd()
}
);
console.log(`Cached metadata for ${packageName}`);
} catch (error) {
console.error(`Failed to cache ${packageName}:`, error.message);
}
}class StatisticalPackageCache implements PackageMetaCache {
private cache = new Map<string, PackageMeta>();
private hits = 0;
private misses = 0;
get(key: string): PackageMeta | undefined {
const result = this.cache.get(key);
if (result) {
this.hits++;
} else {
this.misses++;
}
return result;
}
set(key: string, meta: PackageMeta): void {
this.cache.set(key, meta);
}
has(key: string): boolean {
return this.cache.has(key);
}
getStats() {
const total = this.hits + this.misses;
return {
hits: this.hits,
misses: this.misses,
hitRate: total > 0 ? this.hits / total : 0,
cacheSize: this.cache.size,
};
}
}Install with Tessl CLI
npx tessl i tessl/npm-pnpm--npm-resolver