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.
- package/README.md +17 -22
- package/dist/browser.d.ts +9 -6
- package/dist/browser.js +28 -178
- package/dist/browser.js.map +1 -1
- package/dist/discover.d.ts +15 -0
- package/dist/discover.js +131 -0
- package/dist/discover.js.map +1 -0
- package/dist/disk.d.ts +2 -26
- package/dist/disk.js +8 -107
- package/dist/disk.js.map +1 -1
- package/dist/entry.d.ts +29 -17
- package/dist/entry.js +11 -114
- package/dist/entry.js.map +1 -1
- package/dist/node.d.ts +9 -6
- package/dist/node.js +101 -6
- package/dist/node.js.map +1 -1
- package/dist/shared.d.ts +17 -0
- package/dist/shared.js +5 -0
- package/dist/shared.js.map +1 -0
- package/dist/vite.d.ts +12 -0
- package/dist/vite.js +253 -0
- package/dist/vite.js.map +1 -0
- package/package.json +17 -13
- package/skills/vitecache/SKILL.md +41 -46
- package/dist/dependencies.d.ts +0 -23
- package/dist/dependencies.js +0 -147
- package/dist/dependencies.js.map +0 -1
- package/dist/hash.d.ts +0 -4
- package/dist/hash.js +0 -41
- package/dist/hash.js.map +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/key.d.ts +0 -3
- package/dist/key.js +0 -10
- package/dist/key.js.map +0 -1
- package/dist/packages.d.ts +0 -6
- package/dist/packages.js +0 -52
- package/dist/packages.js.map +0 -1
- package/dist/prune.d.ts +0 -11
- package/dist/prune.js +0 -71
- package/dist/prune.js.map +0 -1
- package/dist/storage.d.ts +0 -12
- package/dist/storage.js +0 -95
- package/dist/storage.js.map +0 -1
- package/dist/types.d.ts +0 -89
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/vite/index.d.ts +0 -8
- package/dist/vite/index.js +0 -220
- package/dist/vite/index.js.map +0 -1
- package/dist/vite/manifest.d.ts +0 -3
- package/dist/vite/manifest.js +0 -34
- package/dist/vite/manifest.js.map +0 -1
- package/dist/vite/resolve.d.ts +0 -9
- package/dist/vite/resolve.js +0 -90
- package/dist/vite/resolve.js.map +0 -1
package/dist/disk.js
CHANGED
|
@@ -1,116 +1,17 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
await
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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,
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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"]}
|
package/dist/shared.d.ts
ADDED
|
@@ -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;
|