vitecache 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/disk.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"disk.js","sourceRoot":"","sources":["../src/disk.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { appendFile, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\nimport type { CacheEntryOptions } from './types.js';\n\nexport function missing(error: unknown): boolean {\n return error instanceof Error && 'code' in error && error.code === 'ENOENT';\n}\n\nexport function missingDependency(error: unknown): boolean {\n return missing(error) || (error instanceof Error && 'code' in error && error.code === 'ENOTDIR');\n}\n\nexport function location(options: Pick<CacheEntryOptions, 'root' | 'basePath'>): { root: string; directory: string } {\n const root = resolve(options.root ?? process.cwd());\n return { root, directory: resolve(root, options.basePath ?? 'public/_vitecache') };\n}\n\nconst pending = new Map<string, Promise<void>>();\n\nexport function ignore(root: string, directory: string): Promise<void> {\n const key = `${root}\\0${directory}`;\n const existing = pending.get(key);\n if (existing) return existing;\n const result = updateIgnore(root, directory).finally(() => pending.delete(key));\n pending.set(key, result);\n return result;\n}\n\nasync function updateIgnore(root: string, directory: string): Promise<void> {\n const path = relative(root, directory);\n if (!path || path === '..' || path.startsWith(`..${sep}`) || isAbsolute(path)) return;\n const entry = `${path.split(sep).join('/').replace(/[!*?[\\]# ]/g, '\\\\$&')}/*`;\n const file = join(root, '.gitignore');\n await mkdir(root, { recursive: true });\n let contents = '';\n try {\n contents = await readFile(file, 'utf8');\n } catch (error) {\n if (!missing(error)) throw error;\n }\n if (contents.split(/\\r?\\n/).includes(entry)) return;\n await appendFile(file, `${contents && !contents.endsWith('\\n') ? '\\n' : ''}${entry}\\n`);\n}\n\nexport async function load(path: string): Promise<Uint8Array | undefined> {\n try {\n return await readFile(path);\n } catch (error) {\n if (!missing(error)) throw error;\n return undefined;\n }\n}\n\nexport async function save(path: string, value: Uint8Array, beforeCommit?: () => Promise<void>): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const temporary = `${path}.${randomUUID()}.tmp`;\n try {\n await writeFile(temporary, value, { flag: 'wx' });\n await beforeCommit?.();\n await rename(temporary, path);\n } finally {\n try {\n await unlink(temporary);\n } catch (error) {\n if (!missing(error)) throw error;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"disk.js","sourceRoot":"","sources":["../src/disk.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { appendFile, mkdir, readFile, realpath, rename, unlink, utimes, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\nimport type { CacheEntryOptions } from './types.js';\n\nfunction code(error: unknown): unknown {\n return error instanceof Error && 'code' in error ? error.code : undefined;\n}\n\nexport function missing(error: unknown): boolean {\n return code(error) === 'ENOENT';\n}\n\n/** A filesystem error for a path that does not exist, also below a path component that is a file. */\nexport function notFound(error: unknown): boolean {\n return missing(error) || code(error) === 'ENOTDIR';\n}\n\n/** Whether `path` is `root` or below it, compared lexically. */\nexport function inside(root: string, path: string): boolean {\n const name = relative(root, path);\n return name !== '..' && !name.startsWith(`..${sep}`) && !isAbsolute(name);\n}\n\n/**\n * `file` relative to `root`, with `/` separators. A file that lies outside `root` only lexically, as through a symlinked\n * root, is related by real paths instead.\n */\nexport async function rootRelative(root: string, file: string): Promise<string> {\n const path = inside(root, file) ? relative(root, file) : relative(await realpath(root), await realpath(file));\n return path.split(sep).join('/');\n}\n\nexport function location(options: Pick<CacheEntryOptions, 'root' | 'basePath'>): { root: string; directory: string } {\n const root = resolve(options.root ?? process.cwd());\n return { root, directory: resolve(root, options.basePath ?? 'public/_vitecache') };\n}\n\nconst pending = new Map<string, Promise<void>>();\n\nexport function ignore(root: string, directory: string): Promise<void> {\n const key = `${root}\\0${directory}`;\n const existing = pending.get(key);\n if (existing) return existing;\n const result = updateIgnore(root, directory).finally(() => pending.delete(key));\n pending.set(key, result);\n return result;\n}\n\nasync function updateIgnore(root: string, directory: string): Promise<void> {\n const path = relative(root, directory);\n if (!path || !inside(root, directory)) return;\n const entry = `${path.split(sep).join('/').replace(/[!*?[\\]# ]/g, '\\\\$&')}/*`;\n const file = join(root, '.gitignore');\n await mkdir(root, { recursive: true });\n let contents = '';\n try {\n contents = await readFile(file, 'utf8');\n } catch (error) {\n if (!missing(error)) throw error;\n }\n if (contents.split(/\\r?\\n/).includes(entry)) return;\n await appendFile(file, `${contents && !contents.endsWith('\\n') ? '\\n' : ''}${entry}\\n`);\n}\n\n/**\n * A file's bytes as a plain Uint8Array that owns its whole ArrayBuffer, as a browser's response gives them. A Buffer's\n * slice() shares its memory, so a slice whose buffer is transferred to a worker would detach the bytes it came from.\n */\nexport async function load(path: string): Promise<Uint8Array | undefined> {\n try {\n const bytes = await readFile(path);\n const owned = bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;\n return owned ? new Uint8Array(bytes.buffer) : new Uint8Array(bytes);\n } catch (error) {\n if (!missing(error)) throw error;\n return undefined;\n }\n}\n\nexport async function save(path: string, value: Uint8Array, beforeCommit?: () => Promise<void>): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const temporary = `${path}.${randomUUID()}.tmp`;\n try {\n await writeFile(temporary, value, { flag: 'wx' });\n await beforeCommit?.();\n await rename(temporary, path);\n } finally {\n try {\n await unlink(temporary);\n } catch (error) {\n if (!missing(error)) throw error;\n }\n }\n}\n\nconst unrecorded = new Set<string>();\n\n/**\n * Sets a file's modification time to now; a file that no longer exists is left alone. Where this process may not change\n * it, as on a read-only deployment, it warns once per directory instead of failing the read that called it.\n */\nexport async function touch(path: string): Promise<void> {\n try {\n const now = new Date();\n await utimes(path, now, now);\n } catch (error) {\n if (missing(error)) return;\n const reason = code(error);\n if (reason !== 'EROFS' && reason !== 'EACCES' && reason !== 'EPERM') throw error;\n const directory = dirname(path);\n if (!unrecorded.has(directory)) console.warn(`vitecache: cannot record use of entries in ${directory} (${reason}), so prune() treats them as unused.`);\n unrecorded.add(directory);\n }\n}\n"]}
package/dist/entry.d.ts CHANGED
@@ -4,8 +4,17 @@ export declare class Entry {
4
4
  constructor(options: CacheEntryOptions, backend: Backend);
5
5
  /** Checks validity like load(). Warns on browser misses without the Vite plugin. */
6
6
  has(): Promise<boolean>;
7
- /** Returns undefined for missing dependencies or missing/stale results, saved dependencies included. A miss captures dependency hashes until the next save attempt finishes. */
7
+ /**
8
+ * Returns undefined for missing dependencies or missing/stale results, saved dependencies included. A miss captures
9
+ * dependency hashes until the next save attempt finishes. A hit is a plain Uint8Array, not a Node Buffer, that owns
10
+ * its whole ArrayBuffer in Node and browsers alike: its slice() copies, and its buffer can be transferred.
11
+ */
8
12
  load(): Promise<Uint8Array | undefined>;
9
- /** Saves bytes on disk. With file or package dependencies, call load()/has() before computing; changed dependencies reject the save. Without the Vite plugin, browser saves warn and skip the write. */
13
+ /**
14
+ * Saves bytes on disk. With file or package dependencies, call load()/has() before computing: a dependency that changed
15
+ * since then rejects with ChangedDependenciesError, and one without a backing file, such as a server route, a virtual
16
+ * module or a package the app does not install, with MissingDependencyError. Without the Vite plugin, browser saves
17
+ * warn and skip the write.
18
+ */
10
19
  save(value: Uint8Array, options?: SaveOptions): Promise<void>;
11
20
  }
package/dist/entry.js CHANGED
@@ -1,26 +1,26 @@
1
- import { dependencies, equalHashes, MissingDependencyError, savedDependencies } from './dependencies.js';
1
+ import { dependencies, equalHashes, hashableDependencies, isChecked, MissingDependencyError, savedDependencies } from './dependencies.js';
2
2
  import { key } from './key.js';
3
3
  export class Entry {
4
4
  #version;
5
5
  #resolution;
6
6
  #dependencies;
7
- #validates;
7
+ #hasChecked;
8
8
  #backend;
9
9
  #baseline;
10
- #prepared = false;
11
10
  #pending = false;
12
11
  constructor(options, backend) {
13
12
  if (typeof options.version !== 'string' || !options.version)
14
13
  throw new TypeError('CacheEntry requires a nonempty version.');
15
14
  this.#dependencies = dependencies(options.dependencies ?? {});
16
- this.#validates = Object.values(this.#dependencies).some((dependency) => !Object.hasOwn(dependency, 'value'));
15
+ this.#hasChecked = Object.values(this.#dependencies).some(isChecked);
17
16
  this.#version = options.version;
18
17
  this.#backend = backend;
19
18
  }
20
19
  #resolve() {
21
20
  this.#resolution ??= this.#backend.resolve(this.#dependencies).then((dependencies) => ({
22
21
  key: key(this.#version, dependencies),
23
- validation: Object.fromEntries(Object.entries(dependencies).filter(([, dependency]) => !Object.hasOwn(dependency, 'value'))),
22
+ // Values are fully captured by the key, so only the rest is checked.
23
+ checked: Object.fromEntries(Object.entries(dependencies).flatMap(([name, dependency]) => isChecked(dependency) ? [[name, dependency]] : [])),
24
24
  })).catch((error) => {
25
25
  this.#resolution = undefined;
26
26
  throw error;
@@ -29,12 +29,12 @@ export class Entry {
29
29
  }
30
30
  async #snapshot() {
31
31
  try {
32
- return await this.#backend.snapshot((await this.#resolve()).validation);
32
+ return await this.#backend.snapshot((await this.#resolve()).checked);
33
33
  }
34
34
  catch (error) {
35
35
  if (!(error instanceof MissingDependencyError))
36
36
  throw error;
37
- return { status: 'missing' };
37
+ return { status: 'missing', message: error.message };
38
38
  }
39
39
  }
40
40
  /** Checks validity like load(). Warns on browser misses without the Vite plugin. */
@@ -45,29 +45,46 @@ export class Entry {
45
45
  }
46
46
  return found;
47
47
  }
48
- /** Returns undefined for missing dependencies or missing/stale results, saved dependencies included. A miss captures dependency hashes until the next save attempt finishes. */
48
+ /**
49
+ * Returns undefined for missing dependencies or missing/stale results, saved dependencies included. A miss captures
50
+ * dependency hashes until the next save attempt finishes. A hit is a plain Uint8Array, not a Node Buffer, that owns
51
+ * its whole ArrayBuffer in Node and browsers alike: its slice() copies, and its buffer can be transferred.
52
+ */
49
53
  async load() {
50
54
  // Value dependencies are fully captured by the key, so there is nothing to validate or baseline.
51
- if (!this.#validates)
52
- return (await this.#backend.load((await this.#resolve()).key, false))?.bytes;
55
+ if (!this.#hasChecked)
56
+ return (await this.#backend.load((await this.#resolve()).key))?.bytes;
53
57
  const snapshot = await this.#snapshot();
54
58
  if (!this.#pending) {
55
59
  this.#baseline = snapshot;
56
- this.#prepared = true;
57
60
  }
58
61
  if (snapshot.status === 'missing') {
59
62
  this.#pending = true;
60
63
  return undefined;
61
64
  }
62
65
  const { key } = await this.#resolve();
63
- const stored = await this.#backend.load(key, snapshot.status === 'available');
64
- if (!stored || (snapshot.status === 'available' && (!stored.hashes || !equalHashes(snapshot.hashes, stored.hashes)))) {
66
+ const stored = await this.#backend.load(key);
67
+ if (!stored) {
65
68
  this.#pending = true;
66
69
  return undefined;
67
70
  }
71
+ if (snapshot.status === 'current') {
72
+ // A backend that checked dependencies serves their hashes with every entry.
73
+ if (!stored.hashes)
74
+ throw new Error('vitecache served an entry without its dependency hashes.');
75
+ if (!equalHashes(snapshot.hashes, stored.hashes)) {
76
+ this.#pending = true;
77
+ return undefined;
78
+ }
79
+ }
68
80
  return stored.bytes;
69
81
  }
70
- /** Saves bytes on disk. With file or package dependencies, call load()/has() before computing; changed dependencies reject the save. Without the Vite plugin, browser saves warn and skip the write. */
82
+ /**
83
+ * Saves bytes on disk. With file or package dependencies, call load()/has() before computing: a dependency that changed
84
+ * since then rejects with ChangedDependenciesError, and one without a backing file, such as a server route, a virtual
85
+ * module or a package the app does not install, with MissingDependencyError. Without the Vite plugin, browser saves
86
+ * warn and skip the write.
87
+ */
71
88
  async save(value, options = {}) {
72
89
  const bytes = new Uint8Array(value);
73
90
  const saved = savedDependencies(options.dependencies ?? {});
@@ -77,17 +94,20 @@ export class Entry {
77
94
  console.warn('vitecache: save skipped. Install the vitecache Vite plugin to write cache files from the browser.');
78
95
  return;
79
96
  }
80
- if (this.#validates && !this.#prepared) {
97
+ if (this.#hasChecked && expected === undefined) {
81
98
  throw new Error('Call CacheEntry.load() or has() before computing a result with file or package dependencies.');
82
99
  }
83
100
  if (expected?.status === 'missing')
84
- throw new MissingDependencyError('Cannot save a cache entry with missing file or package dependencies.');
101
+ throw new MissingDependencyError(`Cannot save a cache entry with a missing dependency. ${expected.message}`);
85
102
  const resolved = await this.#resolve();
86
- const hashes = expected?.status === 'available' ? expected.hashes : {};
87
- await this.#backend.save(resolved.key, bytes, resolved.validation, hashes, await this.#backend.resolve(saved));
103
+ const hashes = expected?.status === 'current' ? expected.hashes : {};
104
+ await this.#backend.save(resolved.key, bytes, {
105
+ checked: resolved.checked,
106
+ expected: hashes,
107
+ saved: hashableDependencies(await this.#backend.resolve(saved)),
108
+ });
88
109
  }
89
110
  finally {
90
- this.#prepared = false;
91
111
  this.#pending = false;
92
112
  this.#baseline = undefined;
93
113
  }
package/dist/entry.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"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","sourcesContent":["import { dependencies, equalHashes, MissingDependencyError, savedDependencies } from './dependencies.js';\nimport { key } from './key.js';\nimport type { Backend, CacheEntryOptions, Dependencies, ResolvedDependencies, SaveOptions, Snapshot } from './types.js';\n\nexport class Entry {\n readonly #version: string;\n #resolution: Promise<{ key: string; validation: ResolvedDependencies }> | undefined;\n readonly #dependencies: Dependencies;\n readonly #validates: boolean;\n readonly #backend: Backend;\n #baseline: Snapshot | undefined;\n #prepared = false;\n #pending = false;\n\n constructor(options: CacheEntryOptions, backend: Backend) {\n if (typeof options.version !== 'string' || !options.version) throw new TypeError('CacheEntry requires a nonempty version.');\n this.#dependencies = dependencies(options.dependencies ?? {});\n this.#validates = Object.values(this.#dependencies).some((dependency) => !Object.hasOwn(dependency, 'value'));\n this.#version = options.version;\n this.#backend = backend;\n }\n\n #resolve(): Promise<{ key: string; validation: ResolvedDependencies }> {\n this.#resolution ??= this.#backend.resolve(this.#dependencies).then((dependencies) => ({\n key: key(this.#version, dependencies),\n validation: Object.fromEntries(Object.entries(dependencies).filter(([, dependency]) => !Object.hasOwn(dependency, 'value'))),\n })).catch((error: unknown) => {\n this.#resolution = undefined;\n throw error;\n });\n return this.#resolution;\n }\n\n async #snapshot(): Promise<Snapshot> {\n try {\n return await this.#backend.snapshot((await this.#resolve()).validation);\n } catch (error) {\n if (!(error instanceof MissingDependencyError)) throw error;\n return { status: 'missing' };\n }\n }\n\n /** Checks validity like load(). Warns on browser misses without the Vite plugin. */\n async has(): Promise<boolean> {\n const found = await this.load() !== undefined;\n if (!found && !await this.#backend.canSave()) {\n console.warn(`vitecache: cache miss for ${this.#version}. Install the vitecache Vite plugin to generate cache files.`);\n }\n return found;\n }\n\n /** Returns undefined for missing dependencies or missing/stale results, saved dependencies included. A miss captures dependency hashes until the next save attempt finishes. */\n async load(): Promise<Uint8Array | undefined> {\n // Value dependencies are fully captured by the key, so there is nothing to validate or baseline.\n if (!this.#validates) return (await this.#backend.load((await this.#resolve()).key, false))?.bytes;\n const snapshot = await this.#snapshot();\n if (!this.#pending) {\n this.#baseline = snapshot;\n this.#prepared = true;\n }\n if (snapshot.status === 'missing') {\n this.#pending = true;\n return undefined;\n }\n const { key } = await this.#resolve();\n const stored = await this.#backend.load(key, snapshot.status === 'available');\n if (!stored || (snapshot.status === 'available' && (!stored.hashes || !equalHashes(snapshot.hashes, stored.hashes)))) {\n this.#pending = true;\n return undefined;\n }\n return stored.bytes;\n }\n\n /** Saves bytes on disk. With file or package dependencies, call load()/has() before computing; changed dependencies reject the save. Without the Vite plugin, browser saves warn and skip the write. */\n async save(value: Uint8Array, options: SaveOptions = {}): Promise<void> {\n const bytes = new Uint8Array(value);\n const saved = savedDependencies(options.dependencies ?? {});\n const expected = this.#baseline;\n try {\n if (!await this.#backend.canSave()) {\n console.warn('vitecache: save skipped. Install the vitecache Vite plugin to write cache files from the browser.');\n return;\n }\n if (this.#validates && !this.#prepared) {\n throw new Error('Call CacheEntry.load() or has() before computing a result with file or package dependencies.');\n }\n if (expected?.status === 'missing') throw new MissingDependencyError('Cannot save a cache entry with missing file or package dependencies.');\n const resolved = await this.#resolve();\n const hashes = expected?.status === 'available' ? expected.hashes : {};\n await this.#backend.save(resolved.key, bytes, resolved.validation, hashes, await this.#backend.resolve(saved));\n } finally {\n this.#prepared = false;\n this.#pending = false;\n this.#baseline = undefined;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,oBAAoB,EAAE,SAAS,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC1I,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAG/B,MAAM,OAAO,KAAK;IACP,QAAQ,CAAS;IAC1B,WAAW,CAAwE;IAC1E,aAAa,CAAe;IAC5B,WAAW,CAAU;IACrB,QAAQ,CAAU;IAC3B,SAAS,CAAuB;IAChC,QAAQ,GAAG,KAAK,CAAC;IAEjB,YAAY,OAA0B,EAAE,OAAgB;QACtD,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE,MAAM,IAAI,SAAS,CAAC,yCAAyC,CAAC,CAAC;QAC5H,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAC9D,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,QAAQ;QACN,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;YACrF,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC;YACrC,qEAAqE;YACrE,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,UAAU,CAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SACtJ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC3B,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;YAC7B,MAAM,KAAK,CAAC;QACd,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,SAAS;QACb,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;QACvE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,CAAC,KAAK,YAAY,sBAAsB,CAAC;gBAAE,MAAM,KAAK,CAAC;YAC5D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QACvD,CAAC;IACH,CAAC;IAED,oFAAoF;IACpF,KAAK,CAAC,GAAG;QACP,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC;QAC9C,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,6BAA6B,IAAI,CAAC,QAAQ,8DAA8D,CAAC,CAAC;QACzH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAI;QACR,iGAAiG;QACjG,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC;QAC7F,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC5B,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAClC,4EAA4E;YAC5E,IAAI,CAAC,MAAM,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;YAChG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBACjD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACrB,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI,CAAC,KAAiB,EAAE,UAAuB,EAAE;QACrD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnC,OAAO,CAAC,IAAI,CAAC,mGAAmG,CAAC,CAAC;gBAClH,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,WAAW,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC/C,MAAM,IAAI,KAAK,CAAC,8FAA8F,CAAC,CAAC;YAClH,CAAC;YACD,IAAI,QAAQ,EAAE,MAAM,KAAK,SAAS;gBAAE,MAAM,IAAI,sBAAsB,CAAC,wDAAwD,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;YACjJ,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,QAAQ,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE;gBAC5C,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,oBAAoB,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;aAChE,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC7B,CAAC;IACH,CAAC;CACF","sourcesContent":["import { dependencies, equalHashes, hashableDependencies, isChecked, MissingDependencyError, savedDependencies } from './dependencies.js';\nimport { key } from './key.js';\nimport type { Backend, CacheEntryOptions, Dependencies, Hashable, SaveOptions, Snapshot } from './types.js';\n\nexport class Entry {\n readonly #version: string;\n #resolution: Promise<{ key: string; checked: Dependencies<Hashable> }> | undefined;\n readonly #dependencies: Dependencies;\n readonly #hasChecked: boolean;\n readonly #backend: Backend;\n #baseline: Snapshot | undefined;\n #pending = false;\n\n constructor(options: CacheEntryOptions, backend: Backend) {\n if (typeof options.version !== 'string' || !options.version) throw new TypeError('CacheEntry requires a nonempty version.');\n this.#dependencies = dependencies(options.dependencies ?? {});\n this.#hasChecked = Object.values(this.#dependencies).some(isChecked);\n this.#version = options.version;\n this.#backend = backend;\n }\n\n #resolve(): Promise<{ key: string; checked: Dependencies<Hashable> }> {\n this.#resolution ??= this.#backend.resolve(this.#dependencies).then((dependencies) => ({\n key: key(this.#version, dependencies),\n // Values are fully captured by the key, so only the rest is checked.\n checked: Object.fromEntries(Object.entries(dependencies).flatMap(([name, dependency]) => isChecked(dependency) ? [[name, dependency] as const] : [])),\n })).catch((error: unknown) => {\n this.#resolution = undefined;\n throw error;\n });\n return this.#resolution;\n }\n\n async #snapshot(): Promise<Snapshot> {\n try {\n return await this.#backend.snapshot((await this.#resolve()).checked);\n } catch (error) {\n if (!(error instanceof MissingDependencyError)) throw error;\n return { status: 'missing', message: error.message };\n }\n }\n\n /** Checks validity like load(). Warns on browser misses without the Vite plugin. */\n async has(): Promise<boolean> {\n const found = await this.load() !== undefined;\n if (!found && !await this.#backend.canSave()) {\n console.warn(`vitecache: cache miss for ${this.#version}. Install the vitecache Vite plugin to generate cache files.`);\n }\n return found;\n }\n\n /**\n * Returns undefined for missing dependencies or missing/stale results, saved dependencies included. A miss captures\n * dependency hashes until the next save attempt finishes. A hit is a plain Uint8Array, not a Node Buffer, that owns\n * its whole ArrayBuffer in Node and browsers alike: its slice() copies, and its buffer can be transferred.\n */\n async load(): Promise<Uint8Array | undefined> {\n // Value dependencies are fully captured by the key, so there is nothing to validate or baseline.\n if (!this.#hasChecked) return (await this.#backend.load((await this.#resolve()).key))?.bytes;\n const snapshot = await this.#snapshot();\n if (!this.#pending) {\n this.#baseline = snapshot;\n }\n if (snapshot.status === 'missing') {\n this.#pending = true;\n return undefined;\n }\n const { key } = await this.#resolve();\n const stored = await this.#backend.load(key);\n if (!stored) {\n this.#pending = true;\n return undefined;\n }\n if (snapshot.status === 'current') {\n // A backend that checked dependencies serves their hashes with every entry.\n if (!stored.hashes) throw new Error('vitecache served an entry without its dependency hashes.');\n if (!equalHashes(snapshot.hashes, stored.hashes)) {\n this.#pending = true;\n return undefined;\n }\n }\n return stored.bytes;\n }\n\n /**\n * Saves bytes on disk. With file or package dependencies, call load()/has() before computing: a dependency that changed\n * since then rejects with ChangedDependenciesError, and one without a backing file, such as a server route, a virtual\n * module or a package the app does not install, with MissingDependencyError. Without the Vite plugin, browser saves\n * warn and skip the write.\n */\n async save(value: Uint8Array, options: SaveOptions = {}): Promise<void> {\n const bytes = new Uint8Array(value);\n const saved = savedDependencies(options.dependencies ?? {});\n const expected = this.#baseline;\n try {\n if (!await this.#backend.canSave()) {\n console.warn('vitecache: save skipped. Install the vitecache Vite plugin to write cache files from the browser.');\n return;\n }\n if (this.#hasChecked && expected === undefined) {\n throw new Error('Call CacheEntry.load() or has() before computing a result with file or package dependencies.');\n }\n if (expected?.status === 'missing') throw new MissingDependencyError(`Cannot save a cache entry with a missing dependency. ${expected.message}`);\n const resolved = await this.#resolve();\n const hashes = expected?.status === 'current' ? expected.hashes : {};\n await this.#backend.save(resolved.key, bytes, {\n checked: resolved.checked,\n expected: hashes,\n saved: hashableDependencies(await this.#backend.resolve(saved)),\n });\n } finally {\n this.#pending = false;\n this.#baseline = undefined;\n }\n }\n}\n"]}
package/dist/hash.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /** SHA-256 hex, computed natively since files can be large. */
2
+ export declare function contentHash(bytes: Uint8Array): string;
3
+ /** The content hash of the file at absolute `path`, or undefined when it does not exist. */
4
+ export declare function fileHash(path: string): Promise<string | undefined>;
package/dist/hash.js ADDED
@@ -0,0 +1,41 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { stat } from 'node:fs/promises';
3
+ import * as disk from './disk.js';
4
+ /** SHA-256 hex, computed natively since files can be large. */
5
+ export function contentHash(bytes) {
6
+ return createHash('sha256').update(bytes).digest('hex');
7
+ }
8
+ /**
9
+ * Content hashes of dependency files by absolute path, shared by every storage in the process, so that each
10
+ * `CacheEntry` does not read and hash a large file again. Reused while the file's identity, size, mtime and ctime are
11
+ * unchanged. As git does for its index, a hash taken within `RACY_NS` of the file's mtime is not kept: a write in the
12
+ * same timestamp tick could follow it unseen. That compares the process's clock with the filesystem's: one whose clock
13
+ * runs more than `RACY_NS` behind, as a network filesystem's can, may have a hash reused across such a write.
14
+ *
15
+ * Nothing evicts a hash except a failed read, so a long-running server holds one per dependency file it ever hashed.
16
+ */
17
+ const fileHashes = new Map();
18
+ const RACY_NS = 2000000000n;
19
+ /** The content hash of the file at absolute `path`, or undefined when it does not exist. */
20
+ export async function fileHash(path) {
21
+ try {
22
+ const { dev, ino, size, mtimeNs, ctimeNs } = await stat(path, { bigint: true });
23
+ const stats = [dev, ino, size, mtimeNs, ctimeNs].join();
24
+ const known = fileHashes.get(path);
25
+ if (known?.stats === stats)
26
+ return await known.hash;
27
+ const hash = disk.load(path).then((bytes) => bytes === undefined ? undefined : contentHash(bytes));
28
+ if (BigInt(Date.now()) * 1000000n - mtimeNs < RACY_NS)
29
+ fileHashes.delete(path);
30
+ else
31
+ fileHashes.set(path, { stats, hash });
32
+ return await hash;
33
+ }
34
+ catch (error) {
35
+ fileHashes.delete(path);
36
+ if (disk.notFound(error))
37
+ return undefined;
38
+ throw error;
39
+ }
40
+ }
41
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,+DAA+D;AAC/D,MAAM,UAAU,WAAW,CAAC,KAAiB;IAC3C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,GAAG,IAAI,GAAG,EAAgE,CAAC;AAC3F,MAAM,OAAO,GAAG,WAAc,CAAC;AAE/B,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY;IACzC,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAChF,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACxD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,KAAK,EAAE,KAAK,KAAK,KAAK;YAAE,OAAO,MAAM,KAAK,CAAC,IAAI,CAAC;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QACnG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,QAAU,GAAG,OAAO,GAAG,OAAO;YAAE,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;;YAC5E,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,OAAO,MAAM,IAAI,CAAC;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAC3C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import { createHash } from 'node:crypto';\nimport { stat } from 'node:fs/promises';\nimport * as disk from './disk.js';\n\n/** SHA-256 hex, computed natively since files can be large. */\nexport function contentHash(bytes: Uint8Array): string {\n return createHash('sha256').update(bytes).digest('hex');\n}\n\n/**\n * Content hashes of dependency files by absolute path, shared by every storage in the process, so that each\n * `CacheEntry` does not read and hash a large file again. Reused while the file's identity, size, mtime and ctime are\n * unchanged. As git does for its index, a hash taken within `RACY_NS` of the file's mtime is not kept: a write in the\n * same timestamp tick could follow it unseen. That compares the process's clock with the filesystem's: one whose clock\n * runs more than `RACY_NS` behind, as a network filesystem's can, may have a hash reused across such a write.\n *\n * Nothing evicts a hash except a failed read, so a long-running server holds one per dependency file it ever hashed.\n */\nconst fileHashes = new Map<string, { stats: string; hash: Promise<string | undefined> }>();\nconst RACY_NS = 2_000_000_000n;\n\n/** The content hash of the file at absolute `path`, or undefined when it does not exist. */\nexport async function fileHash(path: string): Promise<string | undefined> {\n try {\n const { dev, ino, size, mtimeNs, ctimeNs } = await stat(path, { bigint: true });\n const stats = [dev, ino, size, mtimeNs, ctimeNs].join();\n const known = fileHashes.get(path);\n if (known?.stats === stats) return await known.hash;\n const hash = disk.load(path).then((bytes) => bytes === undefined ? undefined : contentHash(bytes));\n if (BigInt(Date.now()) * 1_000_000n - mtimeNs < RACY_NS) fileHashes.delete(path);\n else fileHashes.set(path, { stats, hash });\n return await hash;\n } catch (error) {\n fileHashes.delete(path);\n if (disk.notFound(error)) return undefined;\n throw error;\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,2 +1 @@
1
- export { CacheEntry, MissingDependencyError } from './browser.js';
2
- export type { CacheEntryOptions, Dependency, Dependencies, JsonValue, SavedDependencies, SavedDependency, SaveOptions } from './types.js';
1
+ export * from './browser.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { CacheEntry, MissingDependencyError } from './browser.js';
1
+ export * from './browser.js';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC","sourcesContent":["export { CacheEntry, MissingDependencyError } from './browser.js';\nexport type { CacheEntryOptions, Dependency, Dependencies, JsonValue, SavedDependencies, SavedDependency, SaveOptions } from './types.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC","sourcesContent":["export * from './browser.js';\n"]}
package/dist/key.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import type { ResolvedDependencies } from './types.js';
1
+ import type { Dependencies, Resolved } from './types.js';
2
2
  export declare function digest(bytes: Uint8Array): string;
3
- export declare function key(version: string, dependencies: ResolvedDependencies): string;
3
+ export declare function key(version: string, dependencies: Dependencies<Resolved>): string;
package/dist/key.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"key.js","sourceRoot":"","sources":["../src/key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAGzC,MAAM,UAAU,MAAM,CAAC,KAAiB;IACtC,OAAO,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,YAAkC;IACrE,OAAO,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3F,CAAC","sourcesContent":["import { sha256 } from '@noble/hashes/sha2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\nimport { json } from './dependencies.js';\nimport type { ResolvedDependencies } from './types.js';\n\nexport function digest(bytes: Uint8Array): string {\n return bytesToHex(sha256(bytes));\n}\n\nexport function key(version: string, dependencies: ResolvedDependencies): string {\n return digest(new TextEncoder().encode(JSON.stringify(json({ version, dependencies }))));\n}\n"]}
1
+ {"version":3,"file":"key.js","sourceRoot":"","sources":["../src/key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAGzC,MAAM,UAAU,MAAM,CAAC,KAAiB;IACtC,OAAO,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,YAAoC;IACvE,OAAO,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3F,CAAC","sourcesContent":["import { sha256 } from '@noble/hashes/sha2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\nimport { json } from './dependencies.js';\nimport type { Dependencies, Resolved } from './types.js';\n\nexport function digest(bytes: Uint8Array): string {\n return bytesToHex(sha256(bytes));\n}\n\nexport function key(version: string, dependencies: Dependencies<Resolved>): string {\n return digest(new TextEncoder().encode(JSON.stringify(json({ version, dependencies }))));\n}\n"]}
package/dist/node.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Entry } from './entry.js';
2
2
  import type { CacheEntryOptions } from './types.js';
3
- export { MissingDependencyError } from './dependencies.js';
3
+ export { ChangedDependenciesError, MissingDependencyError } from './dependencies.js';
4
4
  export type { CacheEntryOptions, Dependencies, Dependency, JsonValue, SavedDependencies, SavedDependency, SaveOptions } from './types.js';
5
5
  /** A persistent binary computation result with content-based dependency validation. */
6
6
  export declare class CacheEntry extends Entry {
package/dist/node.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Entry } from './entry.js';
2
2
  import { createStorage } from './storage.js';
3
- export { MissingDependencyError } from './dependencies.js';
3
+ export { ChangedDependenciesError, MissingDependencyError } from './dependencies.js';
4
4
  /** A persistent binary computation result with content-based dependency validation. */
5
5
  export class CacheEntry extends Entry {
6
6
  constructor(options) {
package/dist/node.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,uFAAuF;AACvF,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAA0B;QACpC,KAAK,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACzC,CAAC;CACF","sourcesContent":["import { Entry } from './entry.js';\nimport { createStorage } from './storage.js';\nimport type { CacheEntryOptions } from './types.js';\nexport { MissingDependencyError } from './dependencies.js';\nexport type { CacheEntryOptions, Dependencies, Dependency, JsonValue, SavedDependencies, SavedDependency, SaveOptions } from './types.js';\n\n/** A persistent binary computation result with content-based dependency validation. */\nexport class CacheEntry extends Entry {\n constructor(options: CacheEntryOptions) {\n super(options, createStorage(options));\n }\n}\n"]}
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAGrF,uFAAuF;AACvF,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAA0B;QACpC,KAAK,CAAC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACzC,CAAC;CACF","sourcesContent":["import { Entry } from './entry.js';\nimport { createStorage } from './storage.js';\nimport type { CacheEntryOptions } from './types.js';\nexport { ChangedDependenciesError, MissingDependencyError } from './dependencies.js';\nexport type { CacheEntryOptions, Dependencies, Dependency, JsonValue, SavedDependencies, SavedDependency, SaveOptions } from './types.js';\n\n/** A persistent binary computation result with content-based dependency validation. */\nexport class CacheEntry extends Entry {\n constructor(options: CacheEntryOptions) {\n super(options, createStorage(options));\n }\n}\n"]}
@@ -1 +1,6 @@
1
+ /**
2
+ * The installed version of package `name` as seen from `root`. The project at `root` resolves its own name to itself
3
+ * before node_modules. Node does that only when its package.json declares `exports`; this does without, as a version
4
+ * needs no entry point.
5
+ */
1
6
  export declare function packageVersion(root: string, name: string): Promise<string | undefined>;
package/dist/packages.js CHANGED
@@ -1,26 +1,51 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { createRequire } from 'node:module';
3
3
  import { join } from 'node:path';
4
- import { missingDependency } from './disk.js';
4
+ import { notFound } from './disk.js';
5
+ async function readManifest(directory) {
6
+ const file = join(directory, 'package.json');
7
+ let contents;
8
+ try {
9
+ contents = await readFile(file, 'utf8');
10
+ }
11
+ catch (error) {
12
+ if (!notFound(error))
13
+ throw error;
14
+ return undefined;
15
+ }
16
+ let manifest;
17
+ try {
18
+ manifest = JSON.parse(contents);
19
+ }
20
+ catch (cause) {
21
+ throw new Error(`Invalid package manifest: ${file}`, { cause });
22
+ }
23
+ if (typeof manifest !== 'object' || manifest === null)
24
+ throw new TypeError(`Expected an object in ${file}.`);
25
+ return manifest;
26
+ }
27
+ function version(name, manifest) {
28
+ if (typeof manifest.version !== 'string' || !manifest.version.trim()) {
29
+ throw new TypeError(`Package ${name} must declare a nonempty version in package.json.`);
30
+ }
31
+ return manifest.version;
32
+ }
33
+ /**
34
+ * The installed version of package `name` as seen from `root`. The project at `root` resolves its own name to itself
35
+ * before node_modules. Node does that only when its package.json declares `exports`; this does without, as a version
36
+ * needs no entry point.
37
+ */
5
38
  export async function packageVersion(root, name) {
39
+ const own = await readManifest(root);
40
+ if (own?.name === name)
41
+ return version(name, own);
6
42
  const directories = createRequire(join(root, 'package.json')).resolve.paths(name);
7
43
  if (directories === null)
8
44
  throw new TypeError(`Expected an installed package: ${name}`);
9
45
  for (const directory of directories) {
10
- let contents;
11
- try {
12
- contents = await readFile(join(directory, name, 'package.json'), 'utf8');
13
- }
14
- catch (error) {
15
- if (!missingDependency(error))
16
- throw error;
17
- continue;
18
- }
19
- const manifest = JSON.parse(contents);
20
- if (typeof manifest !== 'object' || manifest === null || !('version' in manifest) || typeof manifest.version !== 'string' || !manifest.version.trim()) {
21
- throw new TypeError(`Package ${name} must declare a nonempty version in package.json.`);
22
- }
23
- return manifest.version;
46
+ const manifest = await readManifest(join(directory, name));
47
+ if (manifest !== undefined)
48
+ return version(name, manifest);
24
49
  }
25
50
  return undefined;
26
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"packages.js","sourceRoot":"","sources":["../src/packages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,IAAY;IAC7D,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClF,IAAI,WAAW,KAAK,IAAI;QAAE,MAAM,IAAI,SAAS,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;IACxF,KAAK,MAAM,SAAS,IAAI,WAAW,EAAE,CAAC;QACpC,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;gBAAE,MAAM,KAAK,CAAC;YAC3C,SAAS;QACX,CAAC;QACD,MAAM,QAAQ,GAAY,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,QAAQ,CAAC,IAAI,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YACtJ,MAAM,IAAI,SAAS,CAAC,WAAW,IAAI,mDAAmD,CAAC,CAAC;QAC1F,CAAC;QACD,OAAO,QAAQ,CAAC,OAAO,CAAC;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { missingDependency } from './disk.js';\n\nexport async function packageVersion(root: string, name: string): Promise<string | undefined> {\n const directories = createRequire(join(root, 'package.json')).resolve.paths(name);\n if (directories === null) throw new TypeError(`Expected an installed package: ${name}`);\n for (const directory of directories) {\n let contents: string;\n try {\n contents = await readFile(join(directory, name, 'package.json'), 'utf8');\n } catch (error) {\n if (!missingDependency(error)) throw error;\n continue;\n }\n const manifest: unknown = JSON.parse(contents);\n if (typeof manifest !== 'object' || manifest === null || !('version' in manifest) || typeof manifest.version !== 'string' || !manifest.version.trim()) {\n throw new TypeError(`Package ${name} must declare a nonempty version in package.json.`);\n }\n return manifest.version;\n }\n return undefined;\n}\n"]}
1
+ {"version":3,"file":"packages.js","sourceRoot":"","sources":["../src/packages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAIrC,KAAK,UAAU,YAAY,CAAC,SAAiB;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAC7C,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,QAAiB,CAAC;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI;QAAE,MAAM,IAAI,SAAS,CAAC,yBAAyB,IAAI,GAAG,CAAC,CAAC;IAC7G,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,QAAkB;IAC/C,IAAI,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACrE,MAAM,IAAI,SAAS,CAAC,WAAW,IAAI,mDAAmD,CAAC,CAAC;IAC1F,CAAC;IACD,OAAO,QAAQ,CAAC,OAAO,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,IAAY;IAC7D,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,GAAG,EAAE,IAAI,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAClD,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClF,IAAI,WAAW,KAAK,IAAI;QAAE,MAAM,IAAI,SAAS,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;IACxF,KAAK,MAAM,SAAS,IAAI,WAAW,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;QAC3D,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join } from 'node:path';\nimport { notFound } from './disk.js';\n\ntype Manifest = { name?: unknown; version?: unknown };\n\nasync function readManifest(directory: string): Promise<Manifest | undefined> {\n const file = join(directory, 'package.json');\n let contents: string;\n try {\n contents = await readFile(file, 'utf8');\n } catch (error) {\n if (!notFound(error)) throw error;\n return undefined;\n }\n let manifest: unknown;\n try {\n manifest = JSON.parse(contents);\n } catch (cause) {\n throw new Error(`Invalid package manifest: ${file}`, { cause });\n }\n if (typeof manifest !== 'object' || manifest === null) throw new TypeError(`Expected an object in ${file}.`);\n return manifest;\n}\n\nfunction version(name: string, manifest: Manifest): string {\n if (typeof manifest.version !== 'string' || !manifest.version.trim()) {\n throw new TypeError(`Package ${name} must declare a nonempty version in package.json.`);\n }\n return manifest.version;\n}\n\n/**\n * The installed version of package `name` as seen from `root`. The project at `root` resolves its own name to itself\n * before node_modules. Node does that only when its package.json declares `exports`; this does without, as a version\n * needs no entry point.\n */\nexport async function packageVersion(root: string, name: string): Promise<string | undefined> {\n const own = await readManifest(root);\n if (own?.name === name) return version(name, own);\n const directories = createRequire(join(root, 'package.json')).resolve.paths(name);\n if (directories === null) throw new TypeError(`Expected an installed package: ${name}`);\n for (const directory of directories) {\n const manifest = await readManifest(join(directory, name));\n if (manifest !== undefined) return version(name, manifest);\n }\n return undefined;\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import type { CacheEntryOptions } from './types.js';
2
+ export interface PruneOptions extends Pick<CacheEntryOptions, 'root' | 'basePath'> {
3
+ /** Entries not loaded or saved since this time are deleted. */
4
+ before: Date;
5
+ }
6
+ /**
7
+ * Deletes cache entries that no load or save used since `before` and returns their keys. Also deletes files that saves
8
+ * staged before it, which no save will finish. Loads in Node and through the Vite development server count as use;
9
+ * deployed browsers do not. Other files in the cache directory are kept.
10
+ */
11
+ export declare function prune(options: PruneOptions): Promise<string[]>;
package/dist/prune.js ADDED
@@ -0,0 +1,71 @@
1
+ import { readdir, stat, unlink } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import * as disk from './disk.js';
4
+ /** An entry's bytes or metadata. */
5
+ const entryFile = /^([a-f0-9]{64})\.(?:bin|json)$/;
6
+ /** Either one staged by a save, unfinished while recent and abandoned once old. */
7
+ const stagedFile = /^[a-f0-9]{64}\.(?:bin|json)\.[^.]+\.tmp$/;
8
+ /**
9
+ * Deletes cache entries that no load or save used since `before` and returns their keys. Also deletes files that saves
10
+ * staged before it, which no save will finish. Loads in Node and through the Vite development server count as use;
11
+ * deployed browsers do not. Other files in the cache directory are kept.
12
+ */
13
+ export async function prune(options) {
14
+ const time = options.before.getTime();
15
+ if (Number.isNaN(time))
16
+ throw new TypeError('prune requires a valid before date.');
17
+ // Some filesystems store whole seconds, so a use just after `before` can be recorded just before it.
18
+ const cutoff = Math.floor(time / 1000) * 1000;
19
+ const { directory } = disk.location(options);
20
+ let names;
21
+ try {
22
+ names = await readdir(directory);
23
+ }
24
+ catch (error) {
25
+ if (!disk.missing(error))
26
+ throw error;
27
+ return [];
28
+ }
29
+ const files = await Promise.all(names.filter((name) => entryFile.test(name) || stagedFile.test(name)).map(async (name) => ({
30
+ name,
31
+ modified: await modified(join(directory, name)),
32
+ })));
33
+ const abandoned = [];
34
+ const entries = new Map();
35
+ for (const { name, modified } of files) {
36
+ const key = entryFile.exec(name)?.[1];
37
+ if (key === undefined) {
38
+ if (modified < cutoff)
39
+ abandoned.push(name);
40
+ continue;
41
+ }
42
+ const entry = entries.get(key) ?? { files: [], used: -Infinity };
43
+ entries.set(key, entry);
44
+ entry.files.push(name);
45
+ entry.used = Math.max(entry.used, modified);
46
+ }
47
+ const unused = [...entries].filter(([, { used }]) => used < cutoff);
48
+ await Promise.all([...abandoned, ...unused.flatMap(([, entry]) => entry.files)].map((name) => remove(join(directory, name))));
49
+ return unused.map(([key]) => key);
50
+ }
51
+ async function modified(path) {
52
+ try {
53
+ return (await stat(path)).mtimeMs;
54
+ }
55
+ catch (error) {
56
+ if (!disk.missing(error))
57
+ throw error;
58
+ // A save renamed or removed it since the listing, so it is in use.
59
+ return Infinity;
60
+ }
61
+ }
62
+ async function remove(path) {
63
+ try {
64
+ await unlink(path);
65
+ }
66
+ catch (error) {
67
+ if (!disk.missing(error))
68
+ throw error;
69
+ }
70
+ }
71
+ //# sourceMappingURL=prune.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prune.js","sourceRoot":"","sources":["../src/prune.ts"],"names":[],"mappings":"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","sourcesContent":["import { readdir, stat, unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport * as disk from './disk.js';\nimport type { CacheEntryOptions } from './types.js';\n\nexport interface PruneOptions extends Pick<CacheEntryOptions, 'root' | 'basePath'> {\n /** Entries not loaded or saved since this time are deleted. */\n before: Date;\n}\n\n/** An entry's bytes or metadata. */\nconst entryFile = /^([a-f0-9]{64})\\.(?:bin|json)$/;\n/** Either one staged by a save, unfinished while recent and abandoned once old. */\nconst stagedFile = /^[a-f0-9]{64}\\.(?:bin|json)\\.[^.]+\\.tmp$/;\n\n/**\n * Deletes cache entries that no load or save used since `before` and returns their keys. Also deletes files that saves\n * staged before it, which no save will finish. Loads in Node and through the Vite development server count as use;\n * deployed browsers do not. Other files in the cache directory are kept.\n */\nexport async function prune(options: PruneOptions): Promise<string[]> {\n const time = options.before.getTime();\n if (Number.isNaN(time)) throw new TypeError('prune requires a valid before date.');\n // Some filesystems store whole seconds, so a use just after `before` can be recorded just before it.\n const cutoff = Math.floor(time / 1000) * 1000;\n const { directory } = disk.location(options);\n let names: string[];\n try {\n names = await readdir(directory);\n } catch (error) {\n if (!disk.missing(error)) throw error;\n return [];\n }\n const files = await Promise.all(names.filter((name) => entryFile.test(name) || stagedFile.test(name)).map(async (name) => ({\n name,\n modified: await modified(join(directory, name)),\n })));\n const abandoned: string[] = [];\n const entries = new Map<string, { files: string[]; used: number }>();\n for (const { name, modified } of files) {\n const key = entryFile.exec(name)?.[1];\n if (key === undefined) {\n if (modified < cutoff) abandoned.push(name);\n continue;\n }\n const entry = entries.get(key) ?? { files: [], used: -Infinity };\n entries.set(key, entry);\n entry.files.push(name);\n entry.used = Math.max(entry.used, modified);\n }\n const unused = [...entries].filter(([, { used }]) => used < cutoff);\n await Promise.all([...abandoned, ...unused.flatMap(([, entry]) => entry.files)].map((name) => remove(join(directory, name))));\n return unused.map(([key]) => key);\n}\n\nasync function modified(path: string): Promise<number> {\n try {\n return (await stat(path)).mtimeMs;\n } catch (error) {\n if (!disk.missing(error)) throw error;\n // A save renamed or removed it since the listing, so it is in use.\n return Infinity;\n }\n}\n\nasync function remove(path: string): Promise<void> {\n try {\n await unlink(path);\n } catch (error) {\n if (!disk.missing(error)) throw error;\n }\n}\n"]}
package/dist/storage.d.ts CHANGED
@@ -1,7 +1,12 @@
1
- import type { Backend, CacheEntryOptions } from './types.js';
2
- export declare class ChangedDependenciesError extends Error {
3
- constructor();
1
+ import type { Backend, CacheEntryOptions, Hashes, Stored } from './types.js';
2
+ /** A stored entry as storage reads it, its hashes always known. */
3
+ export interface Found extends Stored {
4
+ /** The entity tag's opaque value: it changes whenever the entry's bytes or dependency hashes do. */
5
+ tag: string;
6
+ hashes: Hashes;
4
7
  }
5
- export declare function createStorage(options: Pick<CacheEntryOptions, 'root' | 'basePath'> & {
6
- memoizeHashes?: boolean;
7
- }): Backend;
8
+ export interface Storage extends Backend {
9
+ /** Reads a valid entry and records its use for prune(). */
10
+ load(key: string): Promise<Found | undefined>;
11
+ }
12
+ export declare function createStorage(options: Pick<CacheEntryOptions, 'root' | 'basePath'>): Storage;