vitecache 0.3.0 → 0.4.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.
Files changed (57) hide show
  1. package/README.md +17 -22
  2. package/dist/browser.d.ts +9 -6
  3. package/dist/browser.js +28 -178
  4. package/dist/browser.js.map +1 -1
  5. package/dist/discover.d.ts +15 -0
  6. package/dist/discover.js +131 -0
  7. package/dist/discover.js.map +1 -0
  8. package/dist/disk.d.ts +2 -26
  9. package/dist/disk.js +8 -107
  10. package/dist/disk.js.map +1 -1
  11. package/dist/entry.d.ts +29 -17
  12. package/dist/entry.js +11 -114
  13. package/dist/entry.js.map +1 -1
  14. package/dist/node.d.ts +9 -6
  15. package/dist/node.js +101 -6
  16. package/dist/node.js.map +1 -1
  17. package/dist/shared.d.ts +17 -0
  18. package/dist/shared.js +5 -0
  19. package/dist/shared.js.map +1 -0
  20. package/dist/vite.d.ts +12 -0
  21. package/dist/vite.js +253 -0
  22. package/dist/vite.js.map +1 -0
  23. package/package.json +17 -13
  24. package/skills/vitecache/SKILL.md +41 -46
  25. package/dist/dependencies.d.ts +0 -23
  26. package/dist/dependencies.js +0 -147
  27. package/dist/dependencies.js.map +0 -1
  28. package/dist/hash.d.ts +0 -4
  29. package/dist/hash.js +0 -41
  30. package/dist/hash.js.map +0 -1
  31. package/dist/index.d.ts +0 -1
  32. package/dist/index.js +0 -2
  33. package/dist/index.js.map +0 -1
  34. package/dist/key.d.ts +0 -3
  35. package/dist/key.js +0 -10
  36. package/dist/key.js.map +0 -1
  37. package/dist/packages.d.ts +0 -6
  38. package/dist/packages.js +0 -52
  39. package/dist/packages.js.map +0 -1
  40. package/dist/prune.d.ts +0 -11
  41. package/dist/prune.js +0 -71
  42. package/dist/prune.js.map +0 -1
  43. package/dist/storage.d.ts +0 -12
  44. package/dist/storage.js +0 -95
  45. package/dist/storage.js.map +0 -1
  46. package/dist/types.d.ts +0 -89
  47. package/dist/types.js +0 -2
  48. package/dist/types.js.map +0 -1
  49. package/dist/vite/index.d.ts +0 -8
  50. package/dist/vite/index.js +0 -220
  51. package/dist/vite/index.js.map +0 -1
  52. package/dist/vite/manifest.d.ts +0 -3
  53. package/dist/vite/manifest.js +0 -34
  54. package/dist/vite/manifest.js.map +0 -1
  55. package/dist/vite/resolve.d.ts +0 -9
  56. package/dist/vite/resolve.js +0 -90
  57. package/dist/vite/resolve.js.map +0 -1
package/dist/disk.js CHANGED
@@ -1,116 +1,17 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { appendFile, mkdir, readFile, realpath, rename, unlink, utimes, writeFile } from 'node:fs/promises';
3
- import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
- function code(error) {
5
- return error instanceof Error && 'code' in error ? error.code : undefined;
6
- }
1
+ import { readFile } from 'node:fs/promises';
7
2
  export function missing(error) {
8
- return code(error) === 'ENOENT';
9
- }
10
- /** A filesystem error for a path that does not exist, also below a path component that is a file. */
11
- export function notFound(error) {
12
- return missing(error) || code(error) === 'ENOTDIR';
13
- }
14
- /** Whether `path` is `root` or below it, compared lexically. */
15
- export function inside(root, path) {
16
- const name = relative(root, path);
17
- return name !== '..' && !name.startsWith(`..${sep}`) && !isAbsolute(name);
18
- }
19
- /**
20
- * `file` relative to `root`, with `/` separators. A file that lies outside `root` only lexically, as through a symlinked
21
- * root, is related by real paths instead.
22
- */
23
- export async function rootRelative(root, file) {
24
- const path = inside(root, file) ? relative(root, file) : relative(await realpath(root), await realpath(file));
25
- return path.split(sep).join('/');
26
- }
27
- export function location(options) {
28
- const root = resolve(options.root ?? process.cwd());
29
- return { root, directory: resolve(root, options.basePath ?? 'public/_vitecache') };
30
- }
31
- const pending = new Map();
32
- export function ignore(root, directory) {
33
- const key = `${root}\0${directory}`;
34
- const existing = pending.get(key);
35
- if (existing)
36
- return existing;
37
- const result = updateIgnore(root, directory).finally(() => pending.delete(key));
38
- pending.set(key, result);
39
- return result;
40
- }
41
- async function updateIgnore(root, directory) {
42
- const path = relative(root, directory);
43
- if (!path || !inside(root, directory))
44
- return;
45
- const entry = `${path.split(sep).join('/').replace(/[!*?[\]# ]/g, '\\$&')}/*`;
46
- const file = join(root, '.gitignore');
47
- await mkdir(root, { recursive: true });
48
- let contents = '';
49
- try {
50
- contents = await readFile(file, 'utf8');
51
- }
52
- catch (error) {
53
- if (!missing(error))
54
- throw error;
55
- }
56
- if (contents.split(/\r?\n/).includes(entry))
57
- return;
58
- await appendFile(file, `${contents && !contents.endsWith('\n') ? '\n' : ''}${entry}\n`);
59
- }
60
- /**
61
- * A file's bytes as a plain Uint8Array that owns its whole ArrayBuffer, as a browser's response gives them. A Buffer's
62
- * slice() shares its memory, so a slice whose buffer is transferred to a worker would detach the bytes it came from.
63
- */
64
- export async function load(path) {
65
- try {
66
- const bytes = await readFile(path);
67
- const owned = bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
68
- return owned ? new Uint8Array(bytes.buffer) : new Uint8Array(bytes);
69
- }
70
- catch (error) {
71
- if (!missing(error))
72
- throw error;
73
- return undefined;
74
- }
75
- }
76
- export async function save(path, value, beforeCommit) {
77
- await mkdir(dirname(path), { recursive: true });
78
- const temporary = `${path}.${randomUUID()}.tmp`;
79
- try {
80
- await writeFile(temporary, value, { flag: 'wx' });
81
- await beforeCommit?.();
82
- await rename(temporary, path);
83
- }
84
- finally {
85
- try {
86
- await unlink(temporary);
87
- }
88
- catch (error) {
89
- if (!missing(error))
90
- throw error;
91
- }
92
- }
3
+ return error instanceof Error && 'code' in error && (error.code === 'ENOENT' || error.code === 'ENOTDIR');
93
4
  }
94
- const unrecorded = new Set();
95
- /**
96
- * Sets a file's modification time to now; a file that no longer exists is left alone. Where this process may not change
97
- * it, as on a read-only deployment, it warns once per directory instead of failing the read that called it.
98
- */
99
- export async function touch(path) {
5
+ /** The file's bytes, or undefined when it does not exist. */
6
+ export async function read(path) {
100
7
  try {
101
- const now = new Date();
102
- await utimes(path, now, now);
8
+ // A plain Uint8Array owning its buffer, as a browser's response gives, rather than a Buffer.
9
+ return new Uint8Array(await readFile(path));
103
10
  }
104
11
  catch (error) {
105
12
  if (missing(error))
106
- return;
107
- const reason = code(error);
108
- if (reason !== 'EROFS' && reason !== 'EACCES' && reason !== 'EPERM')
109
- throw error;
110
- const directory = dirname(path);
111
- if (!unrecorded.has(directory))
112
- console.warn(`vitecache: cannot record use of entries in ${directory} (${reason}), so prune() treats them as unused.`);
113
- unrecorded.add(directory);
13
+ return undefined;
14
+ throw error;
114
15
  }
115
16
  }
116
17
  //# sourceMappingURL=disk.js.map
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, 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"]}
1
+ {"version":3,"file":"disk.js","sourceRoot":"","sources":["../src/disk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;AAC5G,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAY;IACrC,IAAI,CAAC;QACH,6FAA6F;QAC7F,OAAO,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACrC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import { readFile } from 'node:fs/promises';\n\nexport function missing(error: unknown): boolean {\n return error instanceof Error && 'code' in error && (error.code === 'ENOENT' || error.code === 'ENOTDIR');\n}\n\n/** The file's bytes, or undefined when it does not exist. */\nexport async function read(path: string): Promise<Uint8Array | undefined> {\n try {\n // A plain Uint8Array owning its buffer, as a browser's response gives, rather than a Buffer.\n return new Uint8Array(await readFile(path));\n } catch (error) {\n if (missing(error)) return undefined;\n throw error;\n }\n}\n"]}
package/dist/entry.d.ts CHANGED
@@ -1,20 +1,32 @@
1
- import type { Backend, CacheEntryOptions, SaveOptions } from './types.js';
2
- export declare class Entry {
3
- #private;
4
- constructor(options: CacheEntryOptions, backend: Backend);
5
- /** Checks validity like load(). Warns on browser misses without the Vite plugin. */
6
- has(): Promise<boolean>;
1
+ export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
2
+ readonly [key: string]: JsonValue;
3
+ };
4
+ /** A file, relative to the project root, or an installed package, whose current state an entry is checked against. */
5
+ export type Input = {
6
+ readonly path: string;
7
+ } | {
8
+ readonly package: string;
9
+ };
10
+ /** A JSON value selects which result an entry holds; an input invalidates it when it changes. */
11
+ export type Dependency = {
12
+ readonly value: JsonValue;
13
+ } | Input;
14
+ export interface CacheEntryOptions {
15
+ /** Identifies the computation and its implementation, for example "simplify@2". */
16
+ version: string;
17
+ /** Named dependencies. Names and declarations make up the entry's key; file contents and package versions do not. */
18
+ dependencies?: Readonly<Record<string, Dependency>>;
7
19
  /**
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.
20
+ * Node: the project root, whose public/_vitecache is the cache directory. Defaults to the Vite project, and its
21
+ * publicDir, while the vitecache plugin runs, else to process.cwd().
11
22
  */
12
- load(): Promise<Uint8Array | undefined>;
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
- */
19
- save(value: Uint8Array, options?: SaveOptions): Promise<void>;
23
+ root?: string;
24
+ /** Browsers: the URL the cache directory is served at, including any Vite base. Defaults to /_vitecache/. */
25
+ basePath?: string;
26
+ }
27
+ export interface SaveOptions {
28
+ /** Inputs the computation turned out to read. Checked on every load like declared ones, without changing the key. */
29
+ dependencies?: readonly Input[];
20
30
  }
31
+ /** The entry's name in the cache directory. */
32
+ export declare function key(options: CacheEntryOptions): string;
package/dist/entry.js CHANGED
@@ -1,116 +1,13 @@
1
- import { dependencies, equalHashes, hashableDependencies, isChecked, MissingDependencyError, savedDependencies } from './dependencies.js';
2
- import { key } from './key.js';
3
- export class Entry {
4
- #version;
5
- #resolution;
6
- #dependencies;
7
- #hasChecked;
8
- #backend;
9
- #baseline;
10
- #pending = false;
11
- constructor(options, backend) {
12
- if (typeof options.version !== 'string' || !options.version)
13
- throw new TypeError('CacheEntry requires a nonempty version.');
14
- this.#dependencies = dependencies(options.dependencies ?? {});
15
- this.#hasChecked = Object.values(this.#dependencies).some(isChecked);
16
- this.#version = options.version;
17
- this.#backend = backend;
18
- }
19
- #resolve() {
20
- this.#resolution ??= this.#backend.resolve(this.#dependencies).then((dependencies) => ({
21
- key: key(this.#version, dependencies),
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
- })).catch((error) => {
25
- this.#resolution = undefined;
26
- throw error;
27
- });
28
- return this.#resolution;
29
- }
30
- async #snapshot() {
31
- try {
32
- return await this.#backend.snapshot((await this.#resolve()).checked);
33
- }
34
- catch (error) {
35
- if (!(error instanceof MissingDependencyError))
36
- throw error;
37
- return { status: 'missing', message: error.message };
38
- }
39
- }
40
- /** Checks validity like load(). Warns on browser misses without the Vite plugin. */
41
- async has() {
42
- const found = await this.load() !== undefined;
43
- if (!found && !await this.#backend.canSave()) {
44
- console.warn(`vitecache: cache miss for ${this.#version}. Install the vitecache Vite plugin to generate cache files.`);
45
- }
46
- return found;
47
- }
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
- */
53
- async load() {
54
- // Value dependencies are fully captured by the key, so there is nothing to validate or baseline.
55
- if (!this.#hasChecked)
56
- return (await this.#backend.load((await this.#resolve()).key))?.bytes;
57
- const snapshot = await this.#snapshot();
58
- if (!this.#pending) {
59
- this.#baseline = snapshot;
60
- }
61
- if (snapshot.status === 'missing') {
62
- this.#pending = true;
63
- return undefined;
64
- }
65
- const { key } = await this.#resolve();
66
- const stored = await this.#backend.load(key);
67
- if (!stored) {
68
- this.#pending = true;
69
- return undefined;
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
- }
80
- return stored.bytes;
81
- }
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
- */
88
- async save(value, options = {}) {
89
- const bytes = new Uint8Array(value);
90
- const saved = savedDependencies(options.dependencies ?? {});
91
- const expected = this.#baseline;
92
- try {
93
- if (!await this.#backend.canSave()) {
94
- console.warn('vitecache: save skipped. Install the vitecache Vite plugin to write cache files from the browser.');
95
- return;
96
- }
97
- if (this.#hasChecked && expected === undefined) {
98
- throw new Error('Call CacheEntry.load() or has() before computing a result with file or package dependencies.');
99
- }
100
- if (expected?.status === 'missing')
101
- throw new MissingDependencyError(`Cannot save a cache entry with a missing dependency. ${expected.message}`);
102
- const resolved = await this.#resolve();
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
- });
109
- }
110
- finally {
111
- this.#pending = false;
112
- this.#baseline = undefined;
113
- }
114
- }
1
+ import { sha256 } from '@noble/hashes/sha2.js';
2
+ import { bytesToHex } from '@noble/hashes/utils.js';
3
+ import { configure } from 'safe-stable-stringify';
4
+ /** JSON with sorted object keys, so that declaration order does not change the key; anything else throws. */
5
+ const stringify = configure({ strict: true });
6
+ /** The entry's name in the cache directory. */
7
+ export function key(options) {
8
+ if (!options.version)
9
+ throw new TypeError('CacheEntry requires a version.');
10
+ const declaration = stringify({ version: options.version, dependencies: options.dependencies ?? {} });
11
+ return bytesToHex(sha256(new TextEncoder().encode(declaration)));
115
12
  }
116
13
  //# sourceMappingURL=entry.js.map
package/dist/entry.js.map CHANGED
@@ -1 +1 @@
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"]}
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AA6BlD,6GAA6G;AAC7G,MAAM,SAAS,GAAG,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAE9C,+CAA+C;AAC/C,MAAM,UAAU,GAAG,CAAC,OAA0B;IAC5C,IAAI,CAAC,OAAO,CAAC,OAAO;QAAE,MAAM,IAAI,SAAS,CAAC,gCAAgC,CAAC,CAAC;IAC5E,MAAM,WAAW,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC,CAAC;IACtG,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC","sourcesContent":["import { sha256 } from '@noble/hashes/sha2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\nimport { configure } from 'safe-stable-stringify';\n\nexport type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue };\n\n/** A file, relative to the project root, or an installed package, whose current state an entry is checked against. */\nexport type Input = { readonly path: string } | { readonly package: string };\n\n/** A JSON value selects which result an entry holds; an input invalidates it when it changes. */\nexport type Dependency = { readonly value: JsonValue } | Input;\n\nexport interface CacheEntryOptions {\n /** Identifies the computation and its implementation, for example \"simplify@2\". */\n version: string;\n /** Named dependencies. Names and declarations make up the entry's key; file contents and package versions do not. */\n dependencies?: Readonly<Record<string, Dependency>>;\n /**\n * Node: the project root, whose public/_vitecache is the cache directory. Defaults to the Vite project, and its\n * publicDir, while the vitecache plugin runs, else to process.cwd().\n */\n root?: string;\n /** Browsers: the URL the cache directory is served at, including any Vite base. Defaults to /_vitecache/. */\n basePath?: string;\n}\n\nexport interface SaveOptions {\n /** Inputs the computation turned out to read. Checked on every load like declared ones, without changing the key. */\n dependencies?: readonly Input[];\n}\n\n/** JSON with sorted object keys, so that declaration order does not change the key; anything else throws. */\nconst stringify = configure({ strict: true });\n\n/** The entry's name in the cache directory. */\nexport function key(options: CacheEntryOptions): string {\n if (!options.version) throw new TypeError('CacheEntry requires a version.');\n const declaration = stringify({ version: options.version, dependencies: options.dependencies ?? {} });\n return bytesToHex(sha256(new TextEncoder().encode(declaration)));\n}\n"]}
package/dist/node.d.ts CHANGED
@@ -1,8 +1,11 @@
1
- import { Entry } from './entry.js';
2
- import type { CacheEntryOptions } from './types.js';
3
- export { ChangedDependenciesError, MissingDependencyError } from './dependencies.js';
4
- export type { CacheEntryOptions, Dependencies, Dependency, JsonValue, SavedDependencies, SavedDependency, SaveOptions } from './types.js';
5
- /** A persistent binary computation result with content-based dependency validation. */
6
- export declare class CacheEntry extends Entry {
1
+ import { type CacheEntryOptions, type SaveOptions } from './entry.js';
2
+ export type { CacheEntryOptions, Dependency, Input, JsonValue, SaveOptions } from './entry.js';
3
+ /** A cached result in the cache directory on disk, which Node computes and saves for every environment. */
4
+ export declare class CacheEntry {
5
+ #private;
7
6
  constructor(options: CacheEntryOptions);
7
+ /** The saved bytes while every input they were built from is unchanged, otherwise undefined. */
8
+ load(): Promise<Uint8Array | undefined>;
9
+ /** Saves the result computed after load(), built from the inputs as that load() found them, plus `dependencies`. */
10
+ save(value: Uint8Array, options?: SaveOptions): Promise<void>;
8
11
  }
package/dist/node.js CHANGED
@@ -1,10 +1,105 @@
1
- import { Entry } from './entry.js';
2
- import { createStorage } from './storage.js';
3
- export { ChangedDependenciesError, MissingDependencyError } from './dependencies.js';
4
- /** A persistent binary computation result with content-based dependency validation. */
5
- export class CacheEntry extends Entry {
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, stat } from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import { join, resolve } from 'node:path';
5
+ import writeFileAtomic from 'write-file-atomic';
6
+ import { missing, read } from './disk.js';
7
+ import { key } from './entry.js';
8
+ import { shared } from './shared.js';
9
+ /**
10
+ * File hashes by path, reused while size, mtime and ctime are unchanged. As git does, a hash taken within two seconds
11
+ * of the file's mtime is not kept, since a write in the same timestamp tick could follow it unseen.
12
+ */
13
+ const fileHashes = new Map();
14
+ async function fileHash(path) {
15
+ const stats = await stat(path, { bigint: true }).catch((error) => {
16
+ if (missing(error))
17
+ return undefined;
18
+ throw error;
19
+ });
20
+ if (!stats)
21
+ return undefined;
22
+ const signature = [stats.ino, stats.size, stats.mtimeNs, stats.ctimeNs].join();
23
+ const known = fileHashes.get(path);
24
+ if (known?.stats === signature)
25
+ return known.hash;
26
+ const hash = read(path).then((bytes) => bytes && createHash('sha256').update(bytes).digest('hex'));
27
+ if (BigInt(Date.now()) * 1000000n - stats.mtimeNs < 2000000000n)
28
+ fileHashes.delete(path);
29
+ else
30
+ fileHashes.set(path, { stats: signature, hash });
31
+ return hash;
32
+ }
33
+ /** The installed version of a package as `root` resolves it, its own name included. */
34
+ async function packageVersion(root, name) {
35
+ const own = join(root, 'package.json');
36
+ const directories = createRequire(own).resolve.paths(name) ?? [];
37
+ for (const file of [own, ...directories.map((directory) => join(directory, name, 'package.json'))]) {
38
+ const bytes = await read(file);
39
+ if (!bytes)
40
+ continue;
41
+ const manifest = JSON.parse(new TextDecoder().decode(bytes));
42
+ if (file === own && manifest.name !== name)
43
+ continue;
44
+ if (typeof manifest.version !== 'string')
45
+ throw new TypeError(`${file} declares no version.`);
46
+ return manifest.version;
47
+ }
48
+ return undefined;
49
+ }
50
+ /** A cached result in the cache directory on disk, which Node computes and saves for every environment. */
51
+ export class CacheEntry {
52
+ #key;
53
+ #inputs;
54
+ #root;
55
+ #directory;
56
+ /** The declared inputs as the last load() found them, which save() records as what the result was built from. */
57
+ #loaded;
6
58
  constructor(options) {
7
- super(options, createStorage(options));
59
+ this.#key = key(options);
60
+ const dependencies = Object.values(options.dependencies ?? {});
61
+ this.#inputs = dependencies.filter((dependency) => !('value' in dependency));
62
+ this.#root = resolve(options.root ?? shared.root ?? process.cwd());
63
+ const plugin = options.root === undefined ? shared.directory : undefined;
64
+ this.#directory = plugin ?? join(this.#root, 'public/_vitecache');
65
+ }
66
+ #record(inputs) {
67
+ return Promise.all(inputs.map(async (dependency) => ({
68
+ dependency,
69
+ state: 'path' in dependency
70
+ ? await fileHash(resolve(this.#root, dependency.path))
71
+ : await packageVersion(this.#root, dependency.package),
72
+ })));
73
+ }
74
+ /** The saved bytes while every input they were built from is unchanged, otherwise undefined. */
75
+ async load() {
76
+ shared.used.set(this.#key, Date.now());
77
+ this.#loaded = await this.#record(this.#inputs);
78
+ const metadata = await read(join(this.#directory, `${this.#key}.json`));
79
+ if (!metadata)
80
+ return undefined;
81
+ // Written by save() below; any other shape compares unequal. Metadata from before 0.4 has no inputs, and misses.
82
+ const { inputs } = JSON.parse(new TextDecoder().decode(metadata));
83
+ if (!inputs)
84
+ return undefined;
85
+ const current = await this.#record(inputs.map((input) => input.dependency));
86
+ if (JSON.stringify(current) !== JSON.stringify(inputs))
87
+ return undefined;
88
+ return read(join(this.#directory, `${this.#key}.bin`));
89
+ }
90
+ /** Saves the result computed after load(), built from the inputs as that load() found them, plus `dependencies`. */
91
+ async save(value, options = {}) {
92
+ if (!this.#loaded)
93
+ throw new Error('Call CacheEntry.load() before computing the result to save.');
94
+ const inputs = [...this.#loaded, ...(await this.#record(options.dependencies ?? []))];
95
+ const missing = inputs.find((input) => input.state === undefined);
96
+ if (missing)
97
+ throw new Error(`vitecache input has no file or package: ${JSON.stringify(missing.dependency)}`);
98
+ shared.saves++;
99
+ // Atomic writes, so that a reader sees the old file or the new one.
100
+ await mkdir(this.#directory, { recursive: true });
101
+ await writeFileAtomic(join(this.#directory, `${this.#key}.bin`), Buffer.from(value.buffer, value.byteOffset, value.byteLength));
102
+ await writeFileAtomic(join(this.#directory, `${this.#key}.json`), JSON.stringify({ inputs }));
8
103
  }
9
104
  }
10
105
  //# sourceMappingURL=node.js.map
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,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
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport { mkdir, stat } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { join, resolve } from 'node:path';\nimport writeFileAtomic from 'write-file-atomic';\nimport { missing, read } from './disk.js';\nimport { key, type CacheEntryOptions, type Input, type SaveOptions } from './entry.js';\nimport { shared } from './shared.js';\nexport type { CacheEntryOptions, Dependency, Input, JsonValue, SaveOptions } from './entry.js';\n\n/** An input and its state when it was read: a file's hash or a package's version, undefined when it has neither. */\ninterface Recorded {\n dependency: Input;\n state: string | undefined;\n}\n\n/**\n * File hashes by path, reused while size, mtime and ctime are unchanged. As git does, a hash taken within two seconds\n * of the file's mtime is not kept, since a write in the same timestamp tick could follow it unseen.\n */\nconst fileHashes = new Map<string, { stats: string; hash: Promise<string | undefined> }>();\n\nasync function fileHash(path: string): Promise<string | undefined> {\n const stats = await stat(path, { bigint: true }).catch((error: unknown) => {\n if (missing(error)) return undefined;\n throw error;\n });\n if (!stats) return undefined;\n const signature = [stats.ino, stats.size, stats.mtimeNs, stats.ctimeNs].join();\n const known = fileHashes.get(path);\n if (known?.stats === signature) return known.hash;\n const hash = read(path).then((bytes) => bytes && createHash('sha256').update(bytes).digest('hex'));\n if (BigInt(Date.now()) * 1_000_000n - stats.mtimeNs < 2_000_000_000n) fileHashes.delete(path);\n else fileHashes.set(path, { stats: signature, hash });\n return hash;\n}\n\n/** The installed version of a package as `root` resolves it, its own name included. */\nasync function packageVersion(root: string, name: string): Promise<string | undefined> {\n const own = join(root, 'package.json');\n const directories = createRequire(own).resolve.paths(name) ?? [];\n for (const file of [own, ...directories.map((directory) => join(directory, name, 'package.json'))]) {\n const bytes = await read(file);\n if (!bytes) continue;\n const manifest: { name?: unknown; version?: unknown } = JSON.parse(new TextDecoder().decode(bytes));\n if (file === own && manifest.name !== name) continue;\n if (typeof manifest.version !== 'string') throw new TypeError(`${file} declares no version.`);\n return manifest.version;\n }\n return undefined;\n}\n\n/** A cached result in the cache directory on disk, which Node computes and saves for every environment. */\nexport class CacheEntry {\n readonly #key: string;\n readonly #inputs: Input[];\n readonly #root: string;\n readonly #directory: string;\n /** The declared inputs as the last load() found them, which save() records as what the result was built from. */\n #loaded: Recorded[] | undefined;\n\n constructor(options: CacheEntryOptions) {\n this.#key = key(options);\n const dependencies = Object.values(options.dependencies ?? {});\n this.#inputs = dependencies.filter((dependency): dependency is Input => !('value' in dependency));\n this.#root = resolve(options.root ?? shared.root ?? process.cwd());\n const plugin = options.root === undefined ? shared.directory : undefined;\n this.#directory = plugin ?? join(this.#root, 'public/_vitecache');\n }\n\n #record(inputs: readonly Input[]): Promise<Recorded[]> {\n return Promise.all(\n inputs.map(async (dependency) => ({\n dependency,\n state:\n 'path' in dependency\n ? await fileHash(resolve(this.#root, dependency.path))\n : await packageVersion(this.#root, dependency.package),\n })),\n );\n }\n\n /** The saved bytes while every input they were built from is unchanged, otherwise undefined. */\n async load(): Promise<Uint8Array | undefined> {\n shared.used.set(this.#key, Date.now());\n this.#loaded = await this.#record(this.#inputs);\n const metadata = await read(join(this.#directory, `${this.#key}.json`));\n if (!metadata) return undefined;\n // Written by save() below; any other shape compares unequal. Metadata from before 0.4 has no inputs, and misses.\n const { inputs }: { inputs?: Recorded[] } = JSON.parse(new TextDecoder().decode(metadata));\n if (!inputs) return undefined;\n const current = await this.#record(inputs.map((input) => input.dependency));\n if (JSON.stringify(current) !== JSON.stringify(inputs)) return undefined;\n return read(join(this.#directory, `${this.#key}.bin`));\n }\n\n /** Saves the result computed after load(), built from the inputs as that load() found them, plus `dependencies`. */\n async save(value: Uint8Array, options: SaveOptions = {}): Promise<void> {\n if (!this.#loaded) throw new Error('Call CacheEntry.load() before computing the result to save.');\n const inputs = [...this.#loaded, ...(await this.#record(options.dependencies ?? []))];\n const missing = inputs.find((input) => input.state === undefined);\n if (missing) throw new Error(`vitecache input has no file or package: ${JSON.stringify(missing.dependency)}`);\n shared.saves++;\n // Atomic writes, so that a reader sees the old file or the new one.\n await mkdir(this.#directory, { recursive: true });\n await writeFileAtomic(\n join(this.#directory, `${this.#key}.bin`),\n Buffer.from(value.buffer, value.byteOffset, value.byteLength),\n );\n await writeFileAtomic(join(this.#directory, `${this.#key}.json`), JSON.stringify({ inputs }));\n }\n}\n"]}
@@ -0,0 +1,17 @@
1
+ declare global {
2
+ /** Set by the vitecache Vite plugin. On globalThis, since Vite's module runner can load a second copy of this. */
3
+ var vitecache: Shared | undefined;
4
+ }
5
+ interface Shared {
6
+ /** The plugin's project. */
7
+ root?: string;
8
+ directory?: string;
9
+ /** When each key was last loaded in this process, which the plugin prunes by. */
10
+ used: Map<string, number>;
11
+ /** How many entries this process saved, which a build's second pass checks. */
12
+ saves: number;
13
+ /** Whether a build is starting the server it runs loaders with. */
14
+ running?: boolean;
15
+ }
16
+ export declare const shared: Shared;
17
+ export {};
package/dist/shared.js ADDED
@@ -0,0 +1,5 @@
1
+ export const shared = (globalThis.vitecache ??= { used: new Map(), saves: 0 });
2
+ // Another vitecache version in this process may have made it, as when Vite restarts its server after a rebuild.
3
+ shared.used ??= new Map();
4
+ shared.saves ??= 0;
5
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,MAAM,GAAW,CAAC,UAAU,CAAC,SAAS,KAAK,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;AACvF,gHAAgH;AAChH,MAAM,CAAC,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;AAC1B,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC","sourcesContent":["declare global {\n /** Set by the vitecache Vite plugin. On globalThis, since Vite's module runner can load a second copy of this. */\n var vitecache: Shared | undefined;\n}\n\ninterface Shared {\n /** The plugin's project. */\n root?: string;\n directory?: string;\n /** When each key was last loaded in this process, which the plugin prunes by. */\n used: Map<string, number>;\n /** How many entries this process saved, which a build's second pass checks. */\n saves: number;\n /** Whether a build is starting the server it runs loaders with. */\n running?: boolean;\n}\n\nexport const shared: Shared = (globalThis.vitecache ??= { used: new Map(), saves: 0 });\n// Another vitecache version in this process may have made it, as when Vite restarts its server after a rebuild.\nshared.used ??= new Map();\nshared.saves ??= 0;\n"]}
package/dist/vite.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { type Plugin } from 'vite';
2
+ export interface VitecacheOptions {
3
+ /** Development: milliseconds an entry stays after the last load that used it, so that an undone edit reuses it. */
4
+ pruneAfter?: number;
5
+ }
6
+ /**
7
+ * Finds cached loader loads in the project's source files and warms them in Node, so that browsers read what Node
8
+ * saved, then deletes the entries no load uses. In development it scans and warms at startup and again after every
9
+ * file change, holds cache requests until warming finishes, and deletes entries unused for `pruneAfter`. A build scans
10
+ * and warms once, checks that a second pass saves nothing, and deletes every entry it did not use.
11
+ */
12
+ export declare function vitecache({ pruneAfter }?: VitecacheOptions): Plugin;