@vltpkg/cache
Advanced tools
+103
| 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"} |
+309
| 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" | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
8
-11.11%47097
-0.66%7
-12.5%3
50%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated