vitecache 0.2.1 → 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 (51) hide show
  1. package/README.md +17 -26
  2. package/dist/browser.d.ts +9 -6
  3. package/dist/browser.js +28 -159
  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 -9
  9. package/dist/disk.js +9 -63
  10. package/dist/disk.js.map +1 -1
  11. package/dist/entry.d.ts +31 -10
  12. package/dist/entry.js +11 -94
  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 -9
  24. package/skills/vitecache/SKILL.md +46 -37
  25. package/dist/dependencies.d.ts +0 -15
  26. package/dist/dependencies.js +0 -131
  27. package/dist/dependencies.js.map +0 -1
  28. package/dist/index.d.ts +0 -2
  29. package/dist/index.js +0 -2
  30. package/dist/index.js.map +0 -1
  31. package/dist/key.d.ts +0 -3
  32. package/dist/key.js +0 -10
  33. package/dist/key.js.map +0 -1
  34. package/dist/packages.d.ts +0 -1
  35. package/dist/packages.js +0 -27
  36. package/dist/packages.js.map +0 -1
  37. package/dist/storage.d.ts +0 -7
  38. package/dist/storage.js +0 -162
  39. package/dist/storage.js.map +0 -1
  40. package/dist/types.d.ts +0 -79
  41. package/dist/types.js +0 -2
  42. package/dist/types.js.map +0 -1
  43. package/dist/vite/index.d.ts +0 -8
  44. package/dist/vite/index.js +0 -220
  45. package/dist/vite/index.js.map +0 -1
  46. package/dist/vite/manifest.d.ts +0 -3
  47. package/dist/vite/manifest.js +0 -34
  48. package/dist/vite/manifest.js.map +0 -1
  49. package/dist/vite/resolve.d.ts +0 -8
  50. package/dist/vite/resolve.js +0 -83
  51. package/dist/vite/resolve.js.map +0 -1
package/dist/entry.d.ts CHANGED
@@ -1,11 +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>;
7
- /** Returns undefined for missing dependencies or missing/stale results, saved dependencies included. A miss captures dependency hashes until the next save attempt finishes. */
8
- 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. */
10
- save(value: Uint8Array, options?: SaveOptions): Promise<void>;
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>>;
19
+ /**
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().
22
+ */
23
+ root?: string;
24
+ /** Browsers: the URL the cache directory is served at, including any Vite base. Defaults to /_vitecache/. */
25
+ basePath?: string;
11
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[];
30
+ }
31
+ /** The entry's name in the cache directory. */
32
+ export declare function key(options: CacheEntryOptions): string;
package/dist/entry.js CHANGED
@@ -1,96 +1,13 @@
1
- import { dependencies, equalHashes, MissingDependencyError, savedDependencies } from './dependencies.js';
2
- import { key } from './key.js';
3
- export class Entry {
4
- #version;
5
- #resolution;
6
- #dependencies;
7
- #validates;
8
- #backend;
9
- #baseline;
10
- #prepared = false;
11
- #pending = false;
12
- constructor(options, backend) {
13
- if (typeof options.version !== 'string' || !options.version)
14
- throw new TypeError('CacheEntry requires a nonempty version.');
15
- this.#dependencies = dependencies(options.dependencies ?? {});
16
- this.#validates = Object.values(this.#dependencies).some((dependency) => !Object.hasOwn(dependency, 'value'));
17
- this.#version = options.version;
18
- this.#backend = backend;
19
- }
20
- #resolve() {
21
- this.#resolution ??= this.#backend.resolve(this.#dependencies).then((dependencies) => ({
22
- key: key(this.#version, dependencies),
23
- validation: Object.fromEntries(Object.entries(dependencies).filter(([, dependency]) => !Object.hasOwn(dependency, 'value'))),
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()).validation);
33
- }
34
- catch (error) {
35
- if (!(error instanceof MissingDependencyError))
36
- throw error;
37
- return { status: 'missing' };
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
- /** Returns undefined for missing dependencies or missing/stale results, saved dependencies included. A miss captures dependency hashes until the next save attempt finishes. */
49
- async load() {
50
- // 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;
53
- const snapshot = await this.#snapshot();
54
- if (!this.#pending) {
55
- this.#baseline = snapshot;
56
- this.#prepared = true;
57
- }
58
- if (snapshot.status === 'missing') {
59
- this.#pending = true;
60
- return undefined;
61
- }
62
- 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)))) {
65
- this.#pending = true;
66
- return undefined;
67
- }
68
- return stored.bytes;
69
- }
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. */
71
- async save(value, options = {}) {
72
- const bytes = new Uint8Array(value);
73
- const saved = savedDependencies(options.dependencies ?? {});
74
- const expected = this.#baseline;
75
- try {
76
- if (!await this.#backend.canSave()) {
77
- console.warn('vitecache: save skipped. Install the vitecache Vite plugin to write cache files from the browser.');
78
- return;
79
- }
80
- if (this.#validates && !this.#prepared) {
81
- throw new Error('Call CacheEntry.load() or has() before computing a result with file or package dependencies.');
82
- }
83
- if (expected?.status === 'missing')
84
- throw new MissingDependencyError('Cannot save a cache entry with missing file or package dependencies.');
85
- 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));
88
- }
89
- finally {
90
- this.#prepared = false;
91
- this.#pending = false;
92
- this.#baseline = undefined;
93
- }
94
- }
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)));
95
12
  }
96
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":"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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,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 { 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 { 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,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,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,GAAG,EAAwD,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AASrC;;;GAGG;AACH,MAAM,UAAU,GAAG,IAAI,GAAG,EAAgE,CAAC;AAE3F,KAAK,UAAU,QAAQ,CAAC,IAAY;IAClC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACxE,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACrC,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;IACH,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/E,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,KAAK,EAAE,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IAClD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACnG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,QAAU,GAAG,KAAK,CAAC,OAAO,GAAG,WAAc;QAAE,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;;QACzF,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,uFAAuF;AACvF,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,IAAY;IACtD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACjE,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;QACnG,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,QAAQ,GAA0C,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACpG,IAAI,IAAI,KAAK,GAAG,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI;YAAE,SAAS;QACrD,IAAI,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ;YAAE,MAAM,IAAI,SAAS,CAAC,GAAG,IAAI,uBAAuB,CAAC,CAAC;QAC9F,OAAO,QAAQ,CAAC,OAAO,CAAC;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,2GAA2G;AAC3G,MAAM,OAAO,UAAU;IACZ,IAAI,CAAS;IACb,OAAO,CAAU;IACjB,KAAK,CAAS;IACd,UAAU,CAAS;IAC5B,iHAAiH;IACjH,OAAO,CAAyB;IAEhC,YAAY,OAA0B;QACpC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;QACzB,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,UAAU,EAAuB,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC;QAClG,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,IAAI,CAAC,UAAU,GAAG,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;IACpE,CAAC;IAED,OAAO,CAAC,MAAwB;QAC9B,OAAO,OAAO,CAAC,GAAG,CAChB,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;YAChC,UAAU;YACV,KAAK,EACH,MAAM,IAAI,UAAU;gBAClB,CAAC,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;gBACtD,CAAC,CAAC,MAAM,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC;SAC3D,CAAC,CAAC,CACJ,CAAC;IACJ,CAAC;IAED,gGAAgG;IAChG,KAAK,CAAC,IAAI;QACR,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC;QACxE,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAChC,iHAAiH;QACjH,MAAM,EAAE,MAAM,EAAE,GAA4B,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC3F,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;QAC5E,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;YAAE,OAAO,SAAS,CAAC;QACzE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,oHAAoH;IACpH,KAAK,CAAC,IAAI,CAAC,KAAiB,EAAE,UAAuB,EAAE;QACrD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QAClG,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACtF,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;QAClE,IAAI,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC9G,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,oEAAoE;QACpE,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,eAAe,CACnB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,IAAI,MAAM,CAAC,EACzC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAC9D,CAAC;QACF,MAAM,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,IAAI,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAChG,CAAC;CACF","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;