Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

@vltpkg/cache

Package Overview
Dependencies
Maintainers
6
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vltpkg/cache - npm Package Compare versions

Comparing version
1.0.0-rc.10
to
1.0.0-rc.11
+103
dist/index.d.ts
import type { Integrity } from '@vltpkg/types';
import { LRUCache } from 'lru-cache';
export type CacheFetchContext = {
integrity?: Integrity;
} | undefined;
export type CacheOptions = {
[k in keyof LRUCache.Options<string, Buffer, CacheFetchContext>]?: LRUCache.Options<string, Buffer, CacheFetchContext>[k];
} & {
/**
* fetchMethod may not be provided, because this cache forces its own
* read-from-disk as the fetchMethod
*/
fetchMethod?: undefined;
/**
* folder where items should be stored to disk
*/
path: string;
/**
* called whenever an item is written to disk.
*/
onDiskWrite?: (path: string, key: string, data: Buffer) => any;
/**
* called whenever an item is deleted with `cache.delete(key, true)`
* Deletes of the in-memory data do not trigger this method.
*/
onDiskDelete?: (path: string, key: string, deleted: boolean) => any;
};
export type BooleanOrVoid = boolean | void;
export declare class Cache extends LRUCache<string, Buffer, CacheFetchContext> {
#private;
[Symbol.toStringTag]: string;
onDiskWrite?: CacheOptions['onDiskWrite'];
onDiskDelete?: CacheOptions['onDiskDelete'];
/**
* A list of the actions currently happening in the background
*/
get pending(): Promise<BooleanOrVoid>[];
/**
* By default, cache up to 1000 items in memory.
* Disk cache is unbounded.
*/
static get defaultMax(): number;
constructor(options: CacheOptions);
/**
* Walk over all the items cached to disk (not just in memory).
* Useful for cleanup, pruning, etc.
*
* Implementation for `for await` to walk over entries.
*/
walk(): AsyncGenerator<[string, Buffer<ArrayBufferLike>], void, unknown>;
[Symbol.asyncIterator](): AsyncGenerator<[
string,
Buffer
], void, void>;
/**
* Synchronous form of Cache.walk()
*/
walkSync(): Generator<[string, Buffer<ArrayBufferLike>], void, unknown>;
[Symbol.iterator](): Generator<[string, Buffer], void>;
/**
* Pass `true` as second argument to delete not just from the in-memory
* cache, but the disk backing as well.
*/
delete(key: string, fromDisk?: boolean, integrity?: Integrity): boolean;
/**
* Sets an item in the memory cache (like `LRUCache.set`), and schedules a
* background operation to write it to disk.
*
* Use the {@link CacheOptions#onDiskWrite} method to know exactly when this
* happens, or `await cache.promise()` to defer until all pending actions are
* completed.
*
* The `noDiskWrite` option can be set to prevent it from writing back to the
* disk cache. This is almost never relevant for consumers, and is used
* internally to prevent the write at the end of `fetch()` from excessively
* writing over a file we just read from.
*/
set(key: string, val: Buffer, options?: LRUCache.SetOptions<string, Buffer, CacheFetchContext> & {
/** set to `true` to prevent writes to disk cache */
noDiskWrite?: boolean;
/** sha512 integrity string */
integrity?: Integrity;
}): this;
/**
* Resolves when there are no pending writes to the disk cache
*/
promise(): Promise<void>;
/**
* given a key, figure out the path on disk where it lives
*/
path(key?: string): string;
/**
* given an SRI sha512 integrity string, get the path on disk that
* is hard-linked to the value.
*/
integrityPath(integrity?: Integrity): string | undefined;
/**
* Read synchronously from the fs cache storage if not already
* in memory.
*/
fetchSync(key: string, opts?: LRUCache.FetchOptions<string, Buffer, CacheFetchContext>): Buffer<ArrayBufferLike> | undefined;
}
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAc9C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAIpC,MAAM,MAAM,iBAAiB,GACzB;IACE,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,GACD,SAAS,CAAA;AAEb,MAAM,MAAM,YAAY,GAAG;KACxB,CAAC,IAAI,MAAM,QAAQ,CAAC,OAAO,CAC1B,MAAM,EACN,MAAM,EACN,iBAAiB,CAClB,CAAC,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;CAC5D,GAAG;IACF;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,GAAG,CAAA;IAC9D;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,GAAG,CAAA;CACpE,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,IAAI,CAAA;AAa1C,qBAAa,KAAM,SAAQ,QAAQ,CACjC,MAAM,EACN,MAAM,EACN,iBAAiB,CAClB;;IAEC,CAAC,MAAM,CAAC,WAAW,CAAC,SAAwB;IAI5C,WAAW,CAAC,EAAE,YAAY,CAAC,aAAa,CAAC,CAAA;IACzC,YAAY,CAAC,EAAE,YAAY,CAAC,cAAc,CAAC,CAAA;IAE3C;;OAEG;IACH,IAAI,OAAO,6BAEV;IAED;;;OAGG;IACH,MAAM,KAAK,UAAU,WAEpB;gBAEW,OAAO,EAAE,YAAY;IAiCjC;;;;;OAKG;IACI,IAAI;IAmBX,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,cAAc,CACtC;QAAC,MAAM;QAAE,MAAM;KAAC,EAChB,IAAI,EACJ,IAAI,CACL;IAID;;OAEG;IACF,QAAQ;IAqBT,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC;IAiBtD;;;OAGG;IACH,MAAM,CACJ,GAAG,EAAE,MAAM,EACX,QAAQ,UAAQ,EAChB,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO;IAaV;;;;;;;;;;;;OAYG;IACH,GAAG,CACD,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,QAAQ,CAAC,UAAU,CAC3B,MAAM,EACN,MAAM,EACN,iBAAiB,CAClB,GAAG;QACF,oDAAoD;QACpD,WAAW,CAAC,EAAE,OAAO,CAAA;QACrB,8BAA8B;QAC9B,SAAS,CAAC,EAAE,SAAS,CAAA;KACtB;IAuBH;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B;;OAEG;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM;IAGjB;;;OAGG;IACH,aAAa,CAAC,SAAS,CAAC,EAAE,SAAS;IAcnC;;;OAGG;IACH,SAAS,CACP,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC;CA2JlE"}
import { error } from '@vltpkg/error-cause';
import { XDG } from '@vltpkg/xdg';
import { createHash, randomBytes } from 'node:crypto';
import { opendirSync, readFileSync } from 'node:fs';
import { link, mkdir, opendir, readFile, rename, stat, writeFile, } from 'node:fs/promises';
import { LRUCache } from 'lru-cache';
import { resolve, dirname } from 'node:path';
import { rimraf } from 'rimraf';
const hash = (s) => createHash('sha512').update(s).digest('hex');
const FAILURE = null;
const success = (p) => p.then(result => result, () => FAILURE);
export class Cache extends LRUCache {
#path;
[Symbol.toStringTag] = '@vltpkg/cache.Cache';
#random = randomBytes(6).toString('hex');
#i = 0;
#pending = new Set();
onDiskWrite;
onDiskDelete;
/**
* A list of the actions currently happening in the background
*/
get pending() {
return [...this.#pending];
}
/**
* By default, cache up to 1000 items in memory.
* Disk cache is unbounded.
*/
static get defaultMax() {
return 10_000;
}
constructor(options) {
const { onDiskWrite, onDiskDelete, path = new XDG('vlt').cache(), fetchMethod: _, sizeCalculation = options.maxSize || options.maxEntrySize ?
(v, k) => v.length + k.length
: undefined, ...lruOpts } = options;
super({
max: Cache.defaultMax,
...lruOpts,
sizeCalculation,
fetchMethod: async (k, v, opts) => {
// do not write back to disk, since we just got it from there.
Object.assign(opts.options, {
noDiskWrite: true,
});
return this.#diskRead(k, v, opts.context?.integrity);
},
allowStaleOnFetchRejection: true,
allowStaleOnFetchAbort: true,
allowStale: true,
noDeleteOnStaleGet: true,
});
this.onDiskWrite = onDiskWrite;
this.onDiskDelete = onDiskDelete;
this.#path = path;
}
/**
* Walk over all the items cached to disk (not just in memory).
* Useful for cleanup, pruning, etc.
*
* Implementation for `for await` to walk over entries.
*/
async *walk() {
const dir = await opendir(this.#path, { bufferSize: 1024 });
for await (const entry of dir) {
const f = resolve(this.#path, entry.name);
if (f.endsWith('.key')) {
const entry = await Promise.all([
readFile(resolve(this.#path, f), 'utf8'),
readFile(resolve(this.#path, f.substring(0, f.length - '.key'.length))),
]);
yield entry;
}
}
}
[Symbol.asyncIterator]() {
return this.walk();
}
/**
* Synchronous form of Cache.walk()
*/
*walkSync() {
const dir = opendirSync(this.#path, { bufferSize: 1024 });
let entry = null;
while (null !== (entry = dir.readSync())) {
const f = resolve(this.#path, entry.name);
if (f.endsWith('.key')) {
const entry = [
readFileSync(resolve(this.#path, f), 'utf8'),
readFileSync(resolve(this.#path, f.substring(0, f.length - '.key'.length))),
];
yield entry;
}
}
dir.closeSync();
}
[Symbol.iterator]() {
return this.walkSync();
}
#unpend(p, fn, ...args) {
this.#pending.delete(p);
if (fn)
fn(...args);
}
#pend(p) {
this.#pending.add(p);
}
/**
* Pass `true` as second argument to delete not just from the in-memory
* cache, but the disk backing as well.
*/
delete(key, fromDisk = false, integrity) {
const ret = super.delete(key);
if (fromDisk) {
const path = this.path(key);
const p = this.#diskDelete(path, integrity).then(deleted => this.#unpend(p, this.onDiskDelete, path, key, deleted));
this.#pend(p);
}
return ret;
}
/**
* Sets an item in the memory cache (like `LRUCache.set`), and schedules a
* background operation to write it to disk.
*
* Use the {@link CacheOptions#onDiskWrite} method to know exactly when this
* happens, or `await cache.promise()` to defer until all pending actions are
* completed.
*
* The `noDiskWrite` option can be set to prevent it from writing back to the
* disk cache. This is almost never relevant for consumers, and is used
* internally to prevent the write at the end of `fetch()` from excessively
* writing over a file we just read from.
*/
set(key, val, options) {
super.set(key, val, options);
const { noDiskWrite, integrity } = options ?? {};
// set/delete also used internally by LRUCache to manage async fetches
// only write when we're putting an actual value into the cache
if (Buffer.isBuffer(val) && !noDiskWrite) {
// best effort, already have it in memory
const path = this.path(key);
const p = this.#diskWrite(path, key, val, integrity)
/* c8 ignore next */
.catch(() => { })
.then(() => this.#unpend(p, this.onDiskWrite, path, key, val));
this.#pend(p);
}
return this;
}
/**
* Resolves when there are no pending writes to the disk cache
*/
async promise() {
if (this.pending.length)
await Promise.all(this.pending);
/* c8 ignore next - race condition */
if (this.#pending.size)
await this.promise();
}
/**
* given a key, figure out the path on disk where it lives
*/
path(key) {
return key ? resolve(this.#path, hash(key)) : this.#path;
}
/**
* given an SRI sha512 integrity string, get the path on disk that
* is hard-linked to the value.
*/
integrityPath(integrity) {
if (!integrity)
return undefined;
const m = /^sha512-([a-zA-Z0-9/+]{86}==)$/.exec(integrity);
const hash = m?.[1];
if (!hash) {
throw error('invalid integrity value', {
found: integrity,
wanted: /^sha512-([a-zA-Z0-9/+]{86}==)$/,
});
}
const base = Buffer.from(hash, 'base64').toString('hex');
return resolve(this.#path, base);
}
/**
* Read synchronously from the fs cache storage if not already
* in memory.
*/
fetchSync(key, opts) {
const v = this.get(key);
if (v)
return v;
const intFile = this.#maybeIntegrityPath(opts?.context?.integrity);
if (intFile) {
try {
const v = readFileSync(intFile);
this.set(key, v, { ...opts, noDiskWrite: true });
return v;
/* c8 ignore start */
}
catch { }
}
/* c8 ignore stop */
try {
const v = readFileSync(this.path(key));
// suppress the disk write, because we just read it from disk
this.set(key, v, { ...opts, noDiskWrite: true });
return v;
/* c8 ignore start */
}
catch { }
}
/* c8 ignore stop */
/**
* Delete path and path + '.key'
*/
async #diskDelete(path, integrity) {
const intPath = this.#maybeIntegrityPath(integrity);
const paths = [path, path + '.key'];
if (intPath)
paths.push(intPath);
return await rimraf(paths);
}
#maybeIntegrityPath(i) {
try {
return this.integrityPath(i);
}
catch { }
}
async #writeFileAtomic(file, data) {
// ensure we get a different random key for every write,
// just in case the same file tries to write multiple times,
// it'll still be atomic.
const tmp = `${file}.${this.#random}.${this.#i++}`;
await writeFile(tmp, data);
await rename(tmp, file);
}
async #linkAtomic(src, dest) {
const tmp = `${dest}.${this.#random}.${this.#i++}`;
await link(src, tmp);
await rename(tmp, dest);
}
async #diskWrite(valFile, key, val, integrity) {
const dir = dirname(valFile);
const intFile = this.#maybeIntegrityPath(integrity);
// Create the directory if it doesn't exist and save the promise
// to ensure any write file operations happen after the dir is created.
const mkdirP = mkdir(dir, { recursive: true });
// Helper to atomically write a file to the cache directory, waiting for the dir
// to be created first.
const writeCacheFile = (file, data) => mkdirP.then(() => this.#writeFileAtomic(file, data));
// Always write the key file as early as possible
const writeKeyP = writeCacheFile(`${valFile}.key`, key);
// Helper to wait for the operations we always want to run (write the key file)
// combined with any other operations passed in.
const finish = async (ops) => {
await Promise.all([writeKeyP, ...ops]);
};
if (!intFile) {
// No integrity provided, just write the value and key files
return finish([writeCacheFile(valFile, val)]);
}
// Now we know that we have been passed an integrity value
// that we should attempt to use...somehow.
const intStats = await success(stat(intFile));
if (!intStats) {
// Integrity file doesn't exist yet
// Write the value file and then link that to a new integrity file
return finish([
writeCacheFile(valFile, val).then(() => link(valFile, intFile)),
]);
}
const valStats = await success(stat(valFile));
if (!valStats) {
// Value file doesn't exist but integrity does, we know they are the same
// because we trust the integrity file, so attempt to link and be done.
return finish([link(intFile, valFile)]);
}
// We now know that both the value and integrity files exist.
if (intStats.ino === valStats.ino &&
intStats.dev === valStats.dev) {
// Integrity and val file are already linked. If the are the same value
// then we can be done, otherwise we are probably unzipping and we should
// write the new integrity value and then link the value file to it.
return finish(val.length === valStats.size ?
[]
: [
writeCacheFile(intFile, val).then(() => this.#linkAtomic(intFile, valFile)),
]);
}
// By this point we know that the files are not linked, so
// we atomic link them because they should be the same entry.
return finish([this.#linkAtomic(intFile, valFile)]);
}
async #diskRead(k, v, integrity) {
const intFile = this.#maybeIntegrityPath(integrity);
const file = this.path(k);
const p = intFile ?
readFile(intFile).catch(async () => {
// if we get the value, but not integrity, link to the
// integrity file so we get it next time.
const value = await readFile(file);
await link(file, intFile);
return value;
})
: readFile(file);
return p.catch(() => v);
}
}
//# sourceMappingURL=index.js.map
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { error } from '@vltpkg/error-cause'\nimport type { Integrity } from '@vltpkg/types'\nimport { XDG } from '@vltpkg/xdg'\nimport { createHash, randomBytes } from 'node:crypto'\nimport { opendirSync, readFileSync } from 'node:fs'\nimport type { Dirent } from 'node:fs'\nimport {\n link,\n mkdir,\n opendir,\n readFile,\n rename,\n stat,\n writeFile,\n} from 'node:fs/promises'\nimport { LRUCache } from 'lru-cache'\nimport { resolve, dirname } from 'node:path'\nimport { rimraf } from 'rimraf'\n\nexport type CacheFetchContext =\n | {\n integrity?: Integrity\n }\n | undefined\n\nexport type CacheOptions = {\n [k in keyof LRUCache.Options<\n string,\n Buffer,\n CacheFetchContext\n >]?: LRUCache.Options<string, Buffer, CacheFetchContext>[k]\n} & {\n /**\n * fetchMethod may not be provided, because this cache forces its own\n * read-from-disk as the fetchMethod\n */\n fetchMethod?: undefined\n /**\n * folder where items should be stored to disk\n */\n path: string\n /**\n * called whenever an item is written to disk.\n */\n onDiskWrite?: (path: string, key: string, data: Buffer) => any\n /**\n * called whenever an item is deleted with `cache.delete(key, true)`\n * Deletes of the in-memory data do not trigger this method.\n */\n onDiskDelete?: (path: string, key: string, deleted: boolean) => any\n}\n\nexport type BooleanOrVoid = boolean | void\n\nconst hash = (s: string) =>\n createHash('sha512').update(s).digest('hex')\n\nconst FAILURE = null\n\nconst success = <T>(p: Promise<T>): Promise<T | typeof FAILURE> =>\n p.then(\n result => result,\n () => FAILURE,\n )\n\nexport class Cache extends LRUCache<\n string,\n Buffer,\n CacheFetchContext\n> {\n #path: string;\n [Symbol.toStringTag] = '@vltpkg/cache.Cache'\n #random = randomBytes(6).toString('hex')\n #i = 0\n #pending = new Set<Promise<BooleanOrVoid>>()\n onDiskWrite?: CacheOptions['onDiskWrite']\n onDiskDelete?: CacheOptions['onDiskDelete']\n\n /**\n * A list of the actions currently happening in the background\n */\n get pending() {\n return [...this.#pending]\n }\n\n /**\n * By default, cache up to 1000 items in memory.\n * Disk cache is unbounded.\n */\n static get defaultMax() {\n return 10_000\n }\n\n constructor(options: CacheOptions) {\n const {\n onDiskWrite,\n onDiskDelete,\n path = new XDG('vlt').cache(),\n fetchMethod: _,\n sizeCalculation = options.maxSize || options.maxEntrySize ?\n (v: Buffer, k: string) => v.length + k.length\n : undefined,\n ...lruOpts\n } = options\n\n super({\n max: Cache.defaultMax,\n ...lruOpts,\n sizeCalculation,\n fetchMethod: async (k, v, opts) => {\n // do not write back to disk, since we just got it from there.\n Object.assign(opts.options, {\n noDiskWrite: true,\n })\n return this.#diskRead(k, v, opts.context?.integrity)\n },\n allowStaleOnFetchRejection: true,\n allowStaleOnFetchAbort: true,\n allowStale: true,\n noDeleteOnStaleGet: true,\n })\n this.onDiskWrite = onDiskWrite\n this.onDiskDelete = onDiskDelete\n this.#path = path\n }\n\n /**\n * Walk over all the items cached to disk (not just in memory).\n * Useful for cleanup, pruning, etc.\n *\n * Implementation for `for await` to walk over entries.\n */\n async *walk() {\n const dir = await opendir(this.#path, { bufferSize: 1024 })\n for await (const entry of dir) {\n const f = resolve(this.#path, entry.name)\n if (f.endsWith('.key')) {\n const entry: [string, Buffer] = await Promise.all([\n readFile(resolve(this.#path, f), 'utf8'),\n readFile(\n resolve(\n this.#path,\n f.substring(0, f.length - '.key'.length),\n ),\n ),\n ])\n yield entry\n }\n }\n }\n\n [Symbol.asyncIterator](): AsyncGenerator<\n [string, Buffer],\n void,\n void\n > {\n return this.walk()\n }\n\n /**\n * Synchronous form of Cache.walk()\n */\n *walkSync() {\n const dir = opendirSync(this.#path, { bufferSize: 1024 })\n let entry: Dirent | null = null\n while (null !== (entry = dir.readSync())) {\n const f = resolve(this.#path, entry.name)\n if (f.endsWith('.key')) {\n const entry: [string, Buffer] = [\n readFileSync(resolve(this.#path, f), 'utf8'),\n readFileSync(\n resolve(\n this.#path,\n f.substring(0, f.length - '.key'.length),\n ),\n ),\n ]\n yield entry\n }\n }\n dir.closeSync()\n }\n\n [Symbol.iterator](): Generator<[string, Buffer], void> {\n return this.walkSync()\n }\n\n #unpend<F extends (...a: any[]) => any>(\n p: Promise<BooleanOrVoid>,\n fn: F | undefined,\n ...args: Parameters<F>\n ) {\n this.#pending.delete(p)\n if (fn) fn(...args)\n }\n\n #pend(p: Promise<BooleanOrVoid>) {\n this.#pending.add(p)\n }\n\n /**\n * Pass `true` as second argument to delete not just from the in-memory\n * cache, but the disk backing as well.\n */\n delete(\n key: string,\n fromDisk = false,\n integrity?: Integrity,\n ): boolean {\n const ret = super.delete(key)\n if (fromDisk) {\n const path = this.path(key)\n const p: Promise<void> = this.#diskDelete(path, integrity).then(\n deleted =>\n this.#unpend(p, this.onDiskDelete, path, key, deleted),\n )\n this.#pend(p)\n }\n return ret\n }\n\n /**\n * Sets an item in the memory cache (like `LRUCache.set`), and schedules a\n * background operation to write it to disk.\n *\n * Use the {@link CacheOptions#onDiskWrite} method to know exactly when this\n * happens, or `await cache.promise()` to defer until all pending actions are\n * completed.\n *\n * The `noDiskWrite` option can be set to prevent it from writing back to the\n * disk cache. This is almost never relevant for consumers, and is used\n * internally to prevent the write at the end of `fetch()` from excessively\n * writing over a file we just read from.\n */\n set(\n key: string,\n val: Buffer,\n options?: LRUCache.SetOptions<\n string,\n Buffer,\n CacheFetchContext\n > & {\n /** set to `true` to prevent writes to disk cache */\n noDiskWrite?: boolean\n /** sha512 integrity string */\n integrity?: Integrity\n },\n ) {\n super.set(key, val, options)\n const { noDiskWrite, integrity } = options ?? {}\n // set/delete also used internally by LRUCache to manage async fetches\n // only write when we're putting an actual value into the cache\n if (Buffer.isBuffer(val) && !noDiskWrite) {\n // best effort, already have it in memory\n const path = this.path(key)\n const p: Promise<void> = this.#diskWrite(\n path,\n key,\n val,\n integrity,\n )\n /* c8 ignore next */\n .catch(() => {})\n .then(() => this.#unpend(p, this.onDiskWrite, path, key, val))\n this.#pend(p)\n }\n return this\n }\n\n /**\n * Resolves when there are no pending writes to the disk cache\n */\n async promise(): Promise<void> {\n if (this.pending.length) await Promise.all(this.pending)\n /* c8 ignore next - race condition */\n if (this.#pending.size) await this.promise()\n }\n\n /**\n * given a key, figure out the path on disk where it lives\n */\n path(key?: string) {\n return key ? resolve(this.#path, hash(key)) : this.#path\n }\n /**\n * given an SRI sha512 integrity string, get the path on disk that\n * is hard-linked to the value.\n */\n integrityPath(integrity?: Integrity) {\n if (!integrity) return undefined\n const m = /^sha512-([a-zA-Z0-9/+]{86}==)$/.exec(integrity)\n const hash = m?.[1]\n if (!hash) {\n throw error('invalid integrity value', {\n found: integrity,\n wanted: /^sha512-([a-zA-Z0-9/+]{86}==)$/,\n })\n }\n const base = Buffer.from(hash, 'base64').toString('hex')\n return resolve(this.#path, base)\n }\n\n /**\n * Read synchronously from the fs cache storage if not already\n * in memory.\n */\n fetchSync(\n key: string,\n opts?: LRUCache.FetchOptions<string, Buffer, CacheFetchContext>,\n ) {\n const v = this.get(key)\n if (v) return v\n const intFile = this.#maybeIntegrityPath(opts?.context?.integrity)\n if (intFile) {\n try {\n const v = readFileSync(intFile)\n this.set(key, v, { ...opts, noDiskWrite: true })\n return v\n /* c8 ignore start */\n } catch {}\n }\n /* c8 ignore stop */\n try {\n const v = readFileSync(this.path(key))\n // suppress the disk write, because we just read it from disk\n this.set(key, v, { ...opts, noDiskWrite: true })\n return v\n /* c8 ignore start */\n } catch {}\n }\n /* c8 ignore stop */\n\n /**\n * Delete path and path + '.key'\n */\n async #diskDelete(\n path: string,\n integrity?: Integrity,\n ): Promise<boolean> {\n const intPath = this.#maybeIntegrityPath(integrity)\n const paths = [path, path + '.key']\n if (intPath) paths.push(intPath)\n return await rimraf(paths)\n }\n\n #maybeIntegrityPath(i?: Integrity) {\n try {\n return this.integrityPath(i)\n } catch {}\n }\n\n async #writeFileAtomic(file: string, data: Buffer | string) {\n // ensure we get a different random key for every write,\n // just in case the same file tries to write multiple times,\n // it'll still be atomic.\n const tmp = `${file}.${this.#random}.${this.#i++}`\n await writeFile(tmp, data)\n await rename(tmp, file)\n }\n\n async #linkAtomic(src: string, dest: string) {\n const tmp = `${dest}.${this.#random}.${this.#i++}`\n await link(src, tmp)\n await rename(tmp, dest)\n }\n\n async #diskWrite(\n valFile: string,\n key: string,\n val: Buffer,\n integrity?: Integrity,\n ): Promise<void> {\n const dir = dirname(valFile)\n const intFile = this.#maybeIntegrityPath(integrity)\n\n // Create the directory if it doesn't exist and save the promise\n // to ensure any write file operations happen after the dir is created.\n const mkdirP = mkdir(dir, { recursive: true })\n\n // Helper to atomically write a file to the cache directory, waiting for the dir\n // to be created first.\n const writeCacheFile = (file: string, data: Buffer | string) =>\n mkdirP.then(() => this.#writeFileAtomic(file, data))\n\n // Always write the key file as early as possible\n const writeKeyP = writeCacheFile(`${valFile}.key`, key)\n\n // Helper to wait for the operations we always want to run (write the key file)\n // combined with any other operations passed in.\n const finish = async (ops: Promise<void>[]) => {\n await Promise.all([writeKeyP, ...ops])\n }\n\n if (!intFile) {\n // No integrity provided, just write the value and key files\n return finish([writeCacheFile(valFile, val)])\n }\n\n // Now we know that we have been passed an integrity value\n // that we should attempt to use...somehow.\n\n const intStats = await success(stat(intFile))\n if (!intStats) {\n // Integrity file doesn't exist yet\n // Write the value file and then link that to a new integrity file\n return finish([\n writeCacheFile(valFile, val).then(() =>\n link(valFile, intFile),\n ),\n ])\n }\n\n const valStats = await success(stat(valFile))\n if (!valStats) {\n // Value file doesn't exist but integrity does, we know they are the same\n // because we trust the integrity file, so attempt to link and be done.\n return finish([link(intFile, valFile)])\n }\n\n // We now know that both the value and integrity files exist.\n\n if (\n intStats.ino === valStats.ino &&\n intStats.dev === valStats.dev\n ) {\n // Integrity and val file are already linked. If the are the same value\n // then we can be done, otherwise we are probably unzipping and we should\n // write the new integrity value and then link the value file to it.\n return finish(\n val.length === valStats.size ?\n []\n : [\n writeCacheFile(intFile, val).then(() =>\n this.#linkAtomic(intFile, valFile),\n ),\n ],\n )\n }\n\n // By this point we know that the files are not linked, so\n // we atomic link them because they should be the same entry.\n return finish([this.#linkAtomic(intFile, valFile)])\n }\n\n async #diskRead(\n k: string,\n v: Buffer | undefined,\n integrity?: Integrity,\n ) {\n const intFile = this.#maybeIntegrityPath(integrity)\n const file = this.path(k)\n const p =\n intFile ?\n readFile(intFile).catch(async () => {\n // if we get the value, but not integrity, link to the\n // integrity file so we get it next time.\n const value = await readFile(file)\n await link(file, intFile)\n return value\n })\n : readFile(file)\n return p.catch(() => v)\n }\n}\n"]}
+6
-20
{
"name": "@vltpkg/cache",
"description": "The filesystem cache for `@vlt/registry-client`",
"version": "1.0.0-rc.10",
"version": "1.0.0-rc.11",
"repository": {

@@ -11,19 +11,8 @@ "type": "git",

"author": "vlt technology inc. <support@vlt.sh> (http://vlt.sh)",
"tshy": {
"selfLink": false,
"liveDev": true,
"dialects": [
"esm"
],
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
}
},
"dependencies": {
"lru-cache": "^11.2.4",
"rimraf": "^6.1.2",
"@vltpkg/types": "1.0.0-rc.10",
"@vltpkg/error-cause": "1.0.0-rc.10",
"@vltpkg/xdg": "1.0.0-rc.10"
"@vltpkg/error-cause": "1.0.0-rc.11",
"@vltpkg/types": "1.0.0-rc.11",
"@vltpkg/xdg": "1.0.0-rc.11"
},

@@ -36,3 +25,2 @@ "devDependencies": {

"tap": "^21.5.0",
"tshy": "^3.1.0",
"typedoc": "~0.27.9",

@@ -50,3 +38,3 @@ "typescript": "5.7.3",

"prettier": "../../.prettierrc.js",
"module": "./dist/esm/index.js",
"module": "./dist/index.js",
"type": "module",

@@ -57,4 +45,3 @@ "exports": {

"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
"default": "./dist/index.js"
}

@@ -74,5 +61,4 @@ }

"posttest": "tsc --noEmit",
"tshy": "tshy",
"typecheck": "tsc --noEmit"
}
}
import type { Integrity } from '@vltpkg/types';
import { LRUCache } from 'lru-cache';
export type CacheFetchContext = {
integrity?: Integrity;
} | undefined;
export type CacheOptions = {
[k in keyof LRUCache.Options<string, Buffer, CacheFetchContext>]?: LRUCache.Options<string, Buffer, CacheFetchContext>[k];
} & {
/**
* fetchMethod may not be provided, because this cache forces its own
* read-from-disk as the fetchMethod
*/
fetchMethod?: undefined;
/**
* folder where items should be stored to disk
*/
path: string;
/**
* called whenever an item is written to disk.
*/
onDiskWrite?: (path: string, key: string, data: Buffer) => any;
/**
* called whenever an item is deleted with `cache.delete(key, true)`
* Deletes of the in-memory data do not trigger this method.
*/
onDiskDelete?: (path: string, key: string, deleted: boolean) => any;
};
export type BooleanOrVoid = boolean | void;
export declare class Cache extends LRUCache<string, Buffer, CacheFetchContext> {
#private;
[Symbol.toStringTag]: string;
onDiskWrite?: CacheOptions['onDiskWrite'];
onDiskDelete?: CacheOptions['onDiskDelete'];
/**
* A list of the actions currently happening in the background
*/
get pending(): Promise<BooleanOrVoid>[];
/**
* By default, cache up to 1000 items in memory.
* Disk cache is unbounded.
*/
static get defaultMax(): number;
constructor(options: CacheOptions);
/**
* Walk over all the items cached to disk (not just in memory).
* Useful for cleanup, pruning, etc.
*
* Implementation for `for await` to walk over entries.
*/
walk(): AsyncGenerator<[string, Buffer<ArrayBufferLike>], void, unknown>;
[Symbol.asyncIterator](): AsyncGenerator<[
string,
Buffer
], void, void>;
/**
* Synchronous form of Cache.walk()
*/
walkSync(): Generator<[string, Buffer<ArrayBufferLike>], void, unknown>;
[Symbol.iterator](): Generator<[string, Buffer], void>;
/**
* Pass `true` as second argument to delete not just from the in-memory
* cache, but the disk backing as well.
*/
delete(key: string, fromDisk?: boolean, integrity?: Integrity): boolean;
/**
* Sets an item in the memory cache (like `LRUCache.set`), and schedules a
* background operation to write it to disk.
*
* Use the {@link CacheOptions#onDiskWrite} method to know exactly when this
* happens, or `await cache.promise()` to defer until all pending actions are
* completed.
*
* The `noDiskWrite` option can be set to prevent it from writing back to the
* disk cache. This is almost never relevant for consumers, and is used
* internally to prevent the write at the end of `fetch()` from excessively
* writing over a file we just read from.
*/
set(key: string, val: Buffer, options?: LRUCache.SetOptions<string, Buffer, CacheFetchContext> & {
/** set to `true` to prevent writes to disk cache */
noDiskWrite?: boolean;
/** sha512 integrity string */
integrity?: Integrity;
}): this;
/**
* Resolves when there are no pending writes to the disk cache
*/
promise(): Promise<void>;
/**
* given a key, figure out the path on disk where it lives
*/
path(key?: string): string;
/**
* given an SRI sha512 integrity string, get the path on disk that
* is hard-linked to the value.
*/
integrityPath(integrity?: Integrity): string | undefined;
/**
* Read synchronously from the fs cache storage if not already
* in memory.
*/
fetchSync(key: string, opts?: LRUCache.FetchOptions<string, Buffer, CacheFetchContext>): Buffer<ArrayBufferLike> | undefined;
}
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAc9C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAIpC,MAAM,MAAM,iBAAiB,GACzB;IACE,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,GACD,SAAS,CAAA;AAEb,MAAM,MAAM,YAAY,GAAG;KACxB,CAAC,IAAI,MAAM,QAAQ,CAAC,OAAO,CAC1B,MAAM,EACN,MAAM,EACN,iBAAiB,CAClB,CAAC,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC;CAC5D,GAAG;IACF;;;OAGG;IACH,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,GAAG,CAAA;IAC9D;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,GAAG,CAAA;CACpE,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,IAAI,CAAA;AAa1C,qBAAa,KAAM,SAAQ,QAAQ,CACjC,MAAM,EACN,MAAM,EACN,iBAAiB,CAClB;;IAEC,CAAC,MAAM,CAAC,WAAW,CAAC,SAAwB;IAI5C,WAAW,CAAC,EAAE,YAAY,CAAC,aAAa,CAAC,CAAA;IACzC,YAAY,CAAC,EAAE,YAAY,CAAC,cAAc,CAAC,CAAA;IAE3C;;OAEG;IACH,IAAI,OAAO,6BAEV;IAED;;;OAGG;IACH,MAAM,KAAK,UAAU,WAEpB;gBAEW,OAAO,EAAE,YAAY;IAiCjC;;;;;OAKG;IACI,IAAI;IAmBX,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,cAAc,CACtC;QAAC,MAAM;QAAE,MAAM;KAAC,EAChB,IAAI,EACJ,IAAI,CACL;IAID;;OAEG;IACF,QAAQ;IAqBT,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC;IAiBtD;;;OAGG;IACH,MAAM,CACJ,GAAG,EAAE,MAAM,EACX,QAAQ,UAAQ,EAChB,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO;IAaV;;;;;;;;;;;;OAYG;IACH,GAAG,CACD,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,QAAQ,CAAC,UAAU,CAC3B,MAAM,EACN,MAAM,EACN,iBAAiB,CAClB,GAAG;QACF,oDAAoD;QACpD,WAAW,CAAC,EAAE,OAAO,CAAA;QACrB,8BAA8B;QAC9B,SAAS,CAAC,EAAE,SAAS,CAAA;KACtB;IAuBH;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B;;OAEG;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM;IAGjB;;;OAGG;IACH,aAAa,CAAC,SAAS,CAAC,EAAE,SAAS;IAcnC;;;OAGG;IACH,SAAS,CACP,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC;CA2JlE"}
import { error } from '@vltpkg/error-cause';
import { XDG } from '@vltpkg/xdg';
import { createHash, randomBytes } from 'node:crypto';
import { opendirSync, readFileSync } from 'node:fs';
import { link, mkdir, opendir, readFile, rename, stat, writeFile, } from 'node:fs/promises';
import { LRUCache } from 'lru-cache';
import { resolve, dirname } from 'node:path';
import { rimraf } from 'rimraf';
const hash = (s) => createHash('sha512').update(s).digest('hex');
const FAILURE = null;
const success = (p) => p.then(result => result, () => FAILURE);
export class Cache extends LRUCache {
#path;
[Symbol.toStringTag] = '@vltpkg/cache.Cache';
#random = randomBytes(6).toString('hex');
#i = 0;
#pending = new Set();
onDiskWrite;
onDiskDelete;
/**
* A list of the actions currently happening in the background
*/
get pending() {
return [...this.#pending];
}
/**
* By default, cache up to 1000 items in memory.
* Disk cache is unbounded.
*/
static get defaultMax() {
return 10_000;
}
constructor(options) {
const { onDiskWrite, onDiskDelete, path = new XDG('vlt').cache(), fetchMethod: _, sizeCalculation = options.maxSize || options.maxEntrySize ?
(v, k) => v.length + k.length
: undefined, ...lruOpts } = options;
super({
max: Cache.defaultMax,
...lruOpts,
sizeCalculation,
fetchMethod: async (k, v, opts) => {
// do not write back to disk, since we just got it from there.
Object.assign(opts.options, {
noDiskWrite: true,
});
return this.#diskRead(k, v, opts.context?.integrity);
},
allowStaleOnFetchRejection: true,
allowStaleOnFetchAbort: true,
allowStale: true,
noDeleteOnStaleGet: true,
});
this.onDiskWrite = onDiskWrite;
this.onDiskDelete = onDiskDelete;
this.#path = path;
}
/**
* Walk over all the items cached to disk (not just in memory).
* Useful for cleanup, pruning, etc.
*
* Implementation for `for await` to walk over entries.
*/
async *walk() {
const dir = await opendir(this.#path, { bufferSize: 1024 });
for await (const entry of dir) {
const f = resolve(this.#path, entry.name);
if (f.endsWith('.key')) {
const entry = await Promise.all([
readFile(resolve(this.#path, f), 'utf8'),
readFile(resolve(this.#path, f.substring(0, f.length - '.key'.length))),
]);
yield entry;
}
}
}
[Symbol.asyncIterator]() {
return this.walk();
}
/**
* Synchronous form of Cache.walk()
*/
*walkSync() {
const dir = opendirSync(this.#path, { bufferSize: 1024 });
let entry = null;
while (null !== (entry = dir.readSync())) {
const f = resolve(this.#path, entry.name);
if (f.endsWith('.key')) {
const entry = [
readFileSync(resolve(this.#path, f), 'utf8'),
readFileSync(resolve(this.#path, f.substring(0, f.length - '.key'.length))),
];
yield entry;
}
}
dir.closeSync();
}
[Symbol.iterator]() {
return this.walkSync();
}
#unpend(p, fn, ...args) {
this.#pending.delete(p);
if (fn)
fn(...args);
}
#pend(p) {
this.#pending.add(p);
}
/**
* Pass `true` as second argument to delete not just from the in-memory
* cache, but the disk backing as well.
*/
delete(key, fromDisk = false, integrity) {
const ret = super.delete(key);
if (fromDisk) {
const path = this.path(key);
const p = this.#diskDelete(path, integrity).then(deleted => this.#unpend(p, this.onDiskDelete, path, key, deleted));
this.#pend(p);
}
return ret;
}
/**
* Sets an item in the memory cache (like `LRUCache.set`), and schedules a
* background operation to write it to disk.
*
* Use the {@link CacheOptions#onDiskWrite} method to know exactly when this
* happens, or `await cache.promise()` to defer until all pending actions are
* completed.
*
* The `noDiskWrite` option can be set to prevent it from writing back to the
* disk cache. This is almost never relevant for consumers, and is used
* internally to prevent the write at the end of `fetch()` from excessively
* writing over a file we just read from.
*/
set(key, val, options) {
super.set(key, val, options);
const { noDiskWrite, integrity } = options ?? {};
// set/delete also used internally by LRUCache to manage async fetches
// only write when we're putting an actual value into the cache
if (Buffer.isBuffer(val) && !noDiskWrite) {
// best effort, already have it in memory
const path = this.path(key);
const p = this.#diskWrite(path, key, val, integrity)
/* c8 ignore next */
.catch(() => { })
.then(() => this.#unpend(p, this.onDiskWrite, path, key, val));
this.#pend(p);
}
return this;
}
/**
* Resolves when there are no pending writes to the disk cache
*/
async promise() {
if (this.pending.length)
await Promise.all(this.pending);
/* c8 ignore next - race condition */
if (this.#pending.size)
await this.promise();
}
/**
* given a key, figure out the path on disk where it lives
*/
path(key) {
return key ? resolve(this.#path, hash(key)) : this.#path;
}
/**
* given an SRI sha512 integrity string, get the path on disk that
* is hard-linked to the value.
*/
integrityPath(integrity) {
if (!integrity)
return undefined;
const m = /^sha512-([a-zA-Z0-9/+]{86}==)$/.exec(integrity);
const hash = m?.[1];
if (!hash) {
throw error('invalid integrity value', {
found: integrity,
wanted: /^sha512-([a-zA-Z0-9/+]{86}==)$/,
});
}
const base = Buffer.from(hash, 'base64').toString('hex');
return resolve(this.#path, base);
}
/**
* Read synchronously from the fs cache storage if not already
* in memory.
*/
fetchSync(key, opts) {
const v = this.get(key);
if (v)
return v;
const intFile = this.#maybeIntegrityPath(opts?.context?.integrity);
if (intFile) {
try {
const v = readFileSync(intFile);
this.set(key, v, { ...opts, noDiskWrite: true });
return v;
/* c8 ignore start */
}
catch { }
}
/* c8 ignore stop */
try {
const v = readFileSync(this.path(key));
// suppress the disk write, because we just read it from disk
this.set(key, v, { ...opts, noDiskWrite: true });
return v;
/* c8 ignore start */
}
catch { }
}
/* c8 ignore stop */
/**
* Delete path and path + '.key'
*/
async #diskDelete(path, integrity) {
const intPath = this.#maybeIntegrityPath(integrity);
const paths = [path, path + '.key'];
if (intPath)
paths.push(intPath);
return await rimraf(paths);
}
#maybeIntegrityPath(i) {
try {
return this.integrityPath(i);
}
catch { }
}
async #writeFileAtomic(file, data) {
// ensure we get a different random key for every write,
// just in case the same file tries to write multiple times,
// it'll still be atomic.
const tmp = `${file}.${this.#random}.${this.#i++}`;
await writeFile(tmp, data);
await rename(tmp, file);
}
async #linkAtomic(src, dest) {
const tmp = `${dest}.${this.#random}.${this.#i++}`;
await link(src, tmp);
await rename(tmp, dest);
}
async #diskWrite(valFile, key, val, integrity) {
const dir = dirname(valFile);
const intFile = this.#maybeIntegrityPath(integrity);
// Create the directory if it doesn't exist and save the promise
// to ensure any write file operations happen after the dir is created.
const mkdirP = mkdir(dir, { recursive: true });
// Helper to atomically write a file to the cache directory, waiting for the dir
// to be created first.
const writeCacheFile = (file, data) => mkdirP.then(() => this.#writeFileAtomic(file, data));
// Always write the key file as early as possible
const writeKeyP = writeCacheFile(`${valFile}.key`, key);
// Helper to wait for the operations we always want to run (write the key file)
// combined with any other operations passed in.
const finish = async (ops) => {
await Promise.all([writeKeyP, ...ops]);
};
if (!intFile) {
// No integrity provided, just write the value and key files
return finish([writeCacheFile(valFile, val)]);
}
// Now we know that we have been passed an integrity value
// that we should attempt to use...somehow.
const intStats = await success(stat(intFile));
if (!intStats) {
// Integrity file doesn't exist yet
// Write the value file and then link that to a new integrity file
return finish([
writeCacheFile(valFile, val).then(() => link(valFile, intFile)),
]);
}
const valStats = await success(stat(valFile));
if (!valStats) {
// Value file doesn't exist but integrity does, we know they are the same
// because we trust the integrity file, so attempt to link and be done.
return finish([link(intFile, valFile)]);
}
// We now know that both the value and integrity files exist.
if (intStats.ino === valStats.ino &&
intStats.dev === valStats.dev) {
// Integrity and val file are already linked. If the are the same value
// then we can be done, otherwise we are probably unzipping and we should
// write the new integrity value and then link the value file to it.
return finish(val.length === valStats.size ?
[]
: [
writeCacheFile(intFile, val).then(() => this.#linkAtomic(intFile, valFile)),
]);
}
// By this point we know that the files are not linked, so
// we atomic link them because they should be the same entry.
return finish([this.#linkAtomic(intFile, valFile)]);
}
async #diskRead(k, v, integrity) {
const intFile = this.#maybeIntegrityPath(integrity);
const file = this.path(k);
const p = intFile ?
readFile(intFile).catch(async () => {
// if we get the value, but not integrity, link to the
// integrity file so we get it next time.
const value = await readFile(file);
await link(file, intFile);
return value;
})
: readFile(file);
return p.catch(() => v);
}
}
//# sourceMappingURL=index.js.map
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { error } from '@vltpkg/error-cause'\nimport type { Integrity } from '@vltpkg/types'\nimport { XDG } from '@vltpkg/xdg'\nimport { createHash, randomBytes } from 'node:crypto'\nimport { opendirSync, readFileSync } from 'node:fs'\nimport type { Dirent } from 'node:fs'\nimport {\n link,\n mkdir,\n opendir,\n readFile,\n rename,\n stat,\n writeFile,\n} from 'node:fs/promises'\nimport { LRUCache } from 'lru-cache'\nimport { resolve, dirname } from 'node:path'\nimport { rimraf } from 'rimraf'\n\nexport type CacheFetchContext =\n | {\n integrity?: Integrity\n }\n | undefined\n\nexport type CacheOptions = {\n [k in keyof LRUCache.Options<\n string,\n Buffer,\n CacheFetchContext\n >]?: LRUCache.Options<string, Buffer, CacheFetchContext>[k]\n} & {\n /**\n * fetchMethod may not be provided, because this cache forces its own\n * read-from-disk as the fetchMethod\n */\n fetchMethod?: undefined\n /**\n * folder where items should be stored to disk\n */\n path: string\n /**\n * called whenever an item is written to disk.\n */\n onDiskWrite?: (path: string, key: string, data: Buffer) => any\n /**\n * called whenever an item is deleted with `cache.delete(key, true)`\n * Deletes of the in-memory data do not trigger this method.\n */\n onDiskDelete?: (path: string, key: string, deleted: boolean) => any\n}\n\nexport type BooleanOrVoid = boolean | void\n\nconst hash = (s: string) =>\n createHash('sha512').update(s).digest('hex')\n\nconst FAILURE = null\n\nconst success = <T>(p: Promise<T>): Promise<T | typeof FAILURE> =>\n p.then(\n result => result,\n () => FAILURE,\n )\n\nexport class Cache extends LRUCache<\n string,\n Buffer,\n CacheFetchContext\n> {\n #path: string;\n [Symbol.toStringTag] = '@vltpkg/cache.Cache'\n #random = randomBytes(6).toString('hex')\n #i = 0\n #pending = new Set<Promise<BooleanOrVoid>>()\n onDiskWrite?: CacheOptions['onDiskWrite']\n onDiskDelete?: CacheOptions['onDiskDelete']\n\n /**\n * A list of the actions currently happening in the background\n */\n get pending() {\n return [...this.#pending]\n }\n\n /**\n * By default, cache up to 1000 items in memory.\n * Disk cache is unbounded.\n */\n static get defaultMax() {\n return 10_000\n }\n\n constructor(options: CacheOptions) {\n const {\n onDiskWrite,\n onDiskDelete,\n path = new XDG('vlt').cache(),\n fetchMethod: _,\n sizeCalculation = options.maxSize || options.maxEntrySize ?\n (v: Buffer, k: string) => v.length + k.length\n : undefined,\n ...lruOpts\n } = options\n\n super({\n max: Cache.defaultMax,\n ...lruOpts,\n sizeCalculation,\n fetchMethod: async (k, v, opts) => {\n // do not write back to disk, since we just got it from there.\n Object.assign(opts.options, {\n noDiskWrite: true,\n })\n return this.#diskRead(k, v, opts.context?.integrity)\n },\n allowStaleOnFetchRejection: true,\n allowStaleOnFetchAbort: true,\n allowStale: true,\n noDeleteOnStaleGet: true,\n })\n this.onDiskWrite = onDiskWrite\n this.onDiskDelete = onDiskDelete\n this.#path = path\n }\n\n /**\n * Walk over all the items cached to disk (not just in memory).\n * Useful for cleanup, pruning, etc.\n *\n * Implementation for `for await` to walk over entries.\n */\n async *walk() {\n const dir = await opendir(this.#path, { bufferSize: 1024 })\n for await (const entry of dir) {\n const f = resolve(this.#path, entry.name)\n if (f.endsWith('.key')) {\n const entry: [string, Buffer] = await Promise.all([\n readFile(resolve(this.#path, f), 'utf8'),\n readFile(\n resolve(\n this.#path,\n f.substring(0, f.length - '.key'.length),\n ),\n ),\n ])\n yield entry\n }\n }\n }\n\n [Symbol.asyncIterator](): AsyncGenerator<\n [string, Buffer],\n void,\n void\n > {\n return this.walk()\n }\n\n /**\n * Synchronous form of Cache.walk()\n */\n *walkSync() {\n const dir = opendirSync(this.#path, { bufferSize: 1024 })\n let entry: Dirent | null = null\n while (null !== (entry = dir.readSync())) {\n const f = resolve(this.#path, entry.name)\n if (f.endsWith('.key')) {\n const entry: [string, Buffer] = [\n readFileSync(resolve(this.#path, f), 'utf8'),\n readFileSync(\n resolve(\n this.#path,\n f.substring(0, f.length - '.key'.length),\n ),\n ),\n ]\n yield entry\n }\n }\n dir.closeSync()\n }\n\n [Symbol.iterator](): Generator<[string, Buffer], void> {\n return this.walkSync()\n }\n\n #unpend<F extends (...a: any[]) => any>(\n p: Promise<BooleanOrVoid>,\n fn: F | undefined,\n ...args: Parameters<F>\n ) {\n this.#pending.delete(p)\n if (fn) fn(...args)\n }\n\n #pend(p: Promise<BooleanOrVoid>) {\n this.#pending.add(p)\n }\n\n /**\n * Pass `true` as second argument to delete not just from the in-memory\n * cache, but the disk backing as well.\n */\n delete(\n key: string,\n fromDisk = false,\n integrity?: Integrity,\n ): boolean {\n const ret = super.delete(key)\n if (fromDisk) {\n const path = this.path(key)\n const p: Promise<void> = this.#diskDelete(path, integrity).then(\n deleted =>\n this.#unpend(p, this.onDiskDelete, path, key, deleted),\n )\n this.#pend(p)\n }\n return ret\n }\n\n /**\n * Sets an item in the memory cache (like `LRUCache.set`), and schedules a\n * background operation to write it to disk.\n *\n * Use the {@link CacheOptions#onDiskWrite} method to know exactly when this\n * happens, or `await cache.promise()` to defer until all pending actions are\n * completed.\n *\n * The `noDiskWrite` option can be set to prevent it from writing back to the\n * disk cache. This is almost never relevant for consumers, and is used\n * internally to prevent the write at the end of `fetch()` from excessively\n * writing over a file we just read from.\n */\n set(\n key: string,\n val: Buffer,\n options?: LRUCache.SetOptions<\n string,\n Buffer,\n CacheFetchContext\n > & {\n /** set to `true` to prevent writes to disk cache */\n noDiskWrite?: boolean\n /** sha512 integrity string */\n integrity?: Integrity\n },\n ) {\n super.set(key, val, options)\n const { noDiskWrite, integrity } = options ?? {}\n // set/delete also used internally by LRUCache to manage async fetches\n // only write when we're putting an actual value into the cache\n if (Buffer.isBuffer(val) && !noDiskWrite) {\n // best effort, already have it in memory\n const path = this.path(key)\n const p: Promise<void> = this.#diskWrite(\n path,\n key,\n val,\n integrity,\n )\n /* c8 ignore next */\n .catch(() => {})\n .then(() => this.#unpend(p, this.onDiskWrite, path, key, val))\n this.#pend(p)\n }\n return this\n }\n\n /**\n * Resolves when there are no pending writes to the disk cache\n */\n async promise(): Promise<void> {\n if (this.pending.length) await Promise.all(this.pending)\n /* c8 ignore next - race condition */\n if (this.#pending.size) await this.promise()\n }\n\n /**\n * given a key, figure out the path on disk where it lives\n */\n path(key?: string) {\n return key ? resolve(this.#path, hash(key)) : this.#path\n }\n /**\n * given an SRI sha512 integrity string, get the path on disk that\n * is hard-linked to the value.\n */\n integrityPath(integrity?: Integrity) {\n if (!integrity) return undefined\n const m = /^sha512-([a-zA-Z0-9/+]{86}==)$/.exec(integrity)\n const hash = m?.[1]\n if (!hash) {\n throw error('invalid integrity value', {\n found: integrity,\n wanted: /^sha512-([a-zA-Z0-9/+]{86}==)$/,\n })\n }\n const base = Buffer.from(hash, 'base64').toString('hex')\n return resolve(this.#path, base)\n }\n\n /**\n * Read synchronously from the fs cache storage if not already\n * in memory.\n */\n fetchSync(\n key: string,\n opts?: LRUCache.FetchOptions<string, Buffer, CacheFetchContext>,\n ) {\n const v = this.get(key)\n if (v) return v\n const intFile = this.#maybeIntegrityPath(opts?.context?.integrity)\n if (intFile) {\n try {\n const v = readFileSync(intFile)\n this.set(key, v, { ...opts, noDiskWrite: true })\n return v\n /* c8 ignore start */\n } catch {}\n }\n /* c8 ignore stop */\n try {\n const v = readFileSync(this.path(key))\n // suppress the disk write, because we just read it from disk\n this.set(key, v, { ...opts, noDiskWrite: true })\n return v\n /* c8 ignore start */\n } catch {}\n }\n /* c8 ignore stop */\n\n /**\n * Delete path and path + '.key'\n */\n async #diskDelete(\n path: string,\n integrity?: Integrity,\n ): Promise<boolean> {\n const intPath = this.#maybeIntegrityPath(integrity)\n const paths = [path, path + '.key']\n if (intPath) paths.push(intPath)\n return await rimraf(paths)\n }\n\n #maybeIntegrityPath(i?: Integrity) {\n try {\n return this.integrityPath(i)\n } catch {}\n }\n\n async #writeFileAtomic(file: string, data: Buffer | string) {\n // ensure we get a different random key for every write,\n // just in case the same file tries to write multiple times,\n // it'll still be atomic.\n const tmp = `${file}.${this.#random}.${this.#i++}`\n await writeFile(tmp, data)\n await rename(tmp, file)\n }\n\n async #linkAtomic(src: string, dest: string) {\n const tmp = `${dest}.${this.#random}.${this.#i++}`\n await link(src, tmp)\n await rename(tmp, dest)\n }\n\n async #diskWrite(\n valFile: string,\n key: string,\n val: Buffer,\n integrity?: Integrity,\n ): Promise<void> {\n const dir = dirname(valFile)\n const intFile = this.#maybeIntegrityPath(integrity)\n\n // Create the directory if it doesn't exist and save the promise\n // to ensure any write file operations happen after the dir is created.\n const mkdirP = mkdir(dir, { recursive: true })\n\n // Helper to atomically write a file to the cache directory, waiting for the dir\n // to be created first.\n const writeCacheFile = (file: string, data: Buffer | string) =>\n mkdirP.then(() => this.#writeFileAtomic(file, data))\n\n // Always write the key file as early as possible\n const writeKeyP = writeCacheFile(`${valFile}.key`, key)\n\n // Helper to wait for the operations we always want to run (write the key file)\n // combined with any other operations passed in.\n const finish = async (ops: Promise<void>[]) => {\n await Promise.all([writeKeyP, ...ops])\n }\n\n if (!intFile) {\n // No integrity provided, just write the value and key files\n return finish([writeCacheFile(valFile, val)])\n }\n\n // Now we know that we have been passed an integrity value\n // that we should attempt to use...somehow.\n\n const intStats = await success(stat(intFile))\n if (!intStats) {\n // Integrity file doesn't exist yet\n // Write the value file and then link that to a new integrity file\n return finish([\n writeCacheFile(valFile, val).then(() =>\n link(valFile, intFile),\n ),\n ])\n }\n\n const valStats = await success(stat(valFile))\n if (!valStats) {\n // Value file doesn't exist but integrity does, we know they are the same\n // because we trust the integrity file, so attempt to link and be done.\n return finish([link(intFile, valFile)])\n }\n\n // We now know that both the value and integrity files exist.\n\n if (\n intStats.ino === valStats.ino &&\n intStats.dev === valStats.dev\n ) {\n // Integrity and val file are already linked. If the are the same value\n // then we can be done, otherwise we are probably unzipping and we should\n // write the new integrity value and then link the value file to it.\n return finish(\n val.length === valStats.size ?\n []\n : [\n writeCacheFile(intFile, val).then(() =>\n this.#linkAtomic(intFile, valFile),\n ),\n ],\n )\n }\n\n // By this point we know that the files are not linked, so\n // we atomic link them because they should be the same entry.\n return finish([this.#linkAtomic(intFile, valFile)])\n }\n\n async #diskRead(\n k: string,\n v: Buffer | undefined,\n integrity?: Integrity,\n ) {\n const intFile = this.#maybeIntegrityPath(integrity)\n const file = this.path(k)\n const p =\n intFile ?\n readFile(intFile).catch(async () => {\n // if we get the value, but not integrity, link to the\n // integrity file so we get it next time.\n const value = await readFile(file)\n await link(file, intFile)\n return value\n })\n : readFile(file)\n return p.catch(() => v)\n }\n}\n"]}
{
"type": "module"
}