vitecache 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -6
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +77 -58
- package/dist/browser.js.map +1 -1
- package/dist/dependencies.d.ts +11 -3
- package/dist/dependencies.js +33 -17
- package/dist/dependencies.js.map +1 -1
- package/dist/disk.d.ts +18 -1
- package/dist/disk.js +51 -6
- package/dist/disk.js.map +1 -1
- package/dist/entry.d.ts +11 -2
- package/dist/entry.js +39 -19
- package/dist/entry.js.map +1 -1
- package/dist/hash.d.ts +4 -0
- package/dist/hash.js +41 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/key.d.ts +2 -2
- package/dist/key.js.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +1 -1
- package/dist/node.js.map +1 -1
- package/dist/packages.d.ts +5 -0
- package/dist/packages.js +40 -15
- package/dist/packages.js.map +1 -1
- package/dist/prune.d.ts +11 -0
- package/dist/prune.js +71 -0
- package/dist/prune.js.map +1 -0
- package/dist/storage.d.ts +11 -6
- package/dist/storage.js +54 -121
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +25 -15
- package/dist/types.js.map +1 -1
- package/dist/vite/index.js +64 -64
- package/dist/vite/index.js.map +1 -1
- package/dist/vite/manifest.js +3 -3
- package/dist/vite/manifest.js.map +1 -1
- package/dist/vite/resolve.d.ts +7 -6
- package/dist/vite/resolve.js +31 -24
- package/dist/vite/resolve.js.map +1 -1
- package/package.json +5 -1
- package/skills/vitecache/SKILL.md +20 -6
package/dist/storage.js
CHANGED
|
@@ -1,160 +1,93 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { realpath, stat } from 'node:fs/promises';
|
|
3
|
-
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
4
2
|
import { fileURLToPath } from 'node:url';
|
|
5
3
|
import * as disk from './disk.js';
|
|
6
|
-
import { equalHashes, hashes, MissingDependencyError, resolvedDependencies
|
|
4
|
+
import { ChangedDependenciesError, equalHashes, hashableDependencies, hashes, MissingDependencyError, resolvedDependencies } from './dependencies.js';
|
|
5
|
+
import { contentHash, fileHash } from './hash.js';
|
|
7
6
|
import { packageVersion } from './packages.js';
|
|
8
|
-
function digest(bytes) {
|
|
9
|
-
return createHash('sha256').update(bytes).digest('hex');
|
|
10
|
-
}
|
|
11
|
-
export class ChangedDependenciesError extends Error {
|
|
12
|
-
constructor() {
|
|
13
|
-
super('vitecache dependencies changed during computation.');
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
7
|
/** A metadata file vitecache wrote; any failure to read it names the file, since no request caused it. */
|
|
17
8
|
function metadata(bytes, file) {
|
|
18
9
|
try {
|
|
19
|
-
|
|
10
|
+
const value = JSON.parse(new TextDecoder().decode(bytes));
|
|
11
|
+
if (typeof value !== 'object' || value === null || !('contentHash' in value) || !('dependencies' in value)) {
|
|
12
|
+
throw new Error('Expected contentHash and dependencies.');
|
|
13
|
+
}
|
|
14
|
+
const hash = value.contentHash;
|
|
15
|
+
if (typeof hash !== 'string' || !/^[a-f0-9]{64}$/.test(hash))
|
|
16
|
+
throw new Error('Expected a content hash.');
|
|
17
|
+
const validated = hashes(value.dependencies);
|
|
18
|
+
// Metadata without saved dependencies: all that vitecache 0.1 wrote, and what 0.2 wrote for entries that had none.
|
|
19
|
+
if (!('saved' in value))
|
|
20
|
+
return { contentHash: hash, dependencies: validated, saved: {}, savedHashes: {} };
|
|
21
|
+
// vitecache 0.2 kept each saved dependency with its hash in one record. Its entry misses until a save replaces it.
|
|
22
|
+
if (!('savedHashes' in value))
|
|
23
|
+
return undefined;
|
|
24
|
+
return { contentHash: hash, dependencies: validated, saved: hashableDependencies(value.saved), savedHashes: hashes(value.savedHashes) };
|
|
20
25
|
}
|
|
21
26
|
catch (cause) {
|
|
22
27
|
throw new Error(`Invalid vitecache metadata: ${file}`, { cause });
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
|
-
function parse(bytes) {
|
|
26
|
-
const value = JSON.parse(new TextDecoder().decode(bytes));
|
|
27
|
-
if (typeof value !== 'object' || value === null || !('contentHash' in value) || !('dependencies' in value)) {
|
|
28
|
-
throw new Error('Expected contentHash and dependencies.');
|
|
29
|
-
}
|
|
30
|
-
const { contentHash, dependencies } = value;
|
|
31
|
-
if (typeof contentHash !== 'string' || !/^[a-f0-9]{64}$/.test(contentHash))
|
|
32
|
-
throw new Error('Expected a content hash.');
|
|
33
|
-
if (!('saved' in value))
|
|
34
|
-
return { contentHash, dependencies: hashes(dependencies) };
|
|
35
|
-
if (typeof value.saved !== 'object' || value.saved === null)
|
|
36
|
-
throw new Error('Expected saved dependencies.');
|
|
37
|
-
const saved = Object.entries(value.saved).map(([name, item]) => {
|
|
38
|
-
if (typeof item !== 'object' || item === null || !('dependency' in item) || !('hash' in item))
|
|
39
|
-
throw new Error('Expected a saved dependency and its hash.');
|
|
40
|
-
const dependency = resolvedDependencies(savedDependencies({ [name]: item.dependency }))[name];
|
|
41
|
-
const hash = hashes({ [name]: item.hash })[name];
|
|
42
|
-
if (dependency === undefined || hash === undefined)
|
|
43
|
-
throw new Error('Expected a saved dependency and its hash.');
|
|
44
|
-
return [name, { dependency, hash }];
|
|
45
|
-
});
|
|
46
|
-
return { contentHash, dependencies: hashes(dependencies), saved: Object.fromEntries(saved) };
|
|
47
|
-
}
|
|
48
30
|
function matches(current, expected) {
|
|
49
|
-
return current.status === '
|
|
31
|
+
return current.status === 'current' && equalHashes(current.hashes, expected);
|
|
50
32
|
}
|
|
51
33
|
export function createStorage(options) {
|
|
52
34
|
const { root, directory } = disk.location(options);
|
|
53
|
-
const fileHashes = new Map();
|
|
54
|
-
async function fileHash(path) {
|
|
55
|
-
if (!options.memoizeHashes) {
|
|
56
|
-
const bytes = await disk.load(path);
|
|
57
|
-
return bytes === undefined ? undefined : digest(bytes);
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
const { size, mtimeNs: mtime, ctimeNs: ctime } = await stat(path, { bigint: true });
|
|
61
|
-
const cached = fileHashes.get(path);
|
|
62
|
-
if (cached?.size === size && cached.mtime === mtime && cached.ctime === ctime)
|
|
63
|
-
return await cached.hash;
|
|
64
|
-
const hash = disk.load(path).then((bytes) => bytes === undefined ? undefined : digest(bytes));
|
|
65
|
-
fileHashes.set(path, { size, mtime, ctime, hash });
|
|
66
|
-
const value = await hash;
|
|
67
|
-
if (value === undefined && fileHashes.get(path)?.hash === hash)
|
|
68
|
-
fileHashes.delete(path);
|
|
69
|
-
return value;
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
fileHashes.delete(path);
|
|
73
|
-
throw error;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
35
|
async function snapshot(dependencies) {
|
|
77
|
-
const entries = await Promise.all(Object.entries(dependencies).
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
if (dependency.path === undefined)
|
|
84
|
-
throw new TypeError('Expected a resolved dependency.');
|
|
85
|
-
const hash = await fileHash(resolve(root, dependency.path));
|
|
86
|
-
return hash === undefined ? null : [name, hash];
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
if (!disk.missingDependency(error))
|
|
90
|
-
throw error;
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
36
|
+
const entries = await Promise.all(Object.entries(dependencies).map(async ([name, dependency]) => {
|
|
37
|
+
if (dependency.package === undefined)
|
|
38
|
+
return [name, await fileHash(resolve(root, dependency.path))];
|
|
39
|
+
const version = await packageVersion(root, dependency.package);
|
|
40
|
+
return [name, version === undefined ? undefined : contentHash(new TextEncoder().encode(version))];
|
|
93
41
|
}));
|
|
94
42
|
const present = [];
|
|
95
|
-
for (const
|
|
96
|
-
if (
|
|
97
|
-
return { status: 'missing' };
|
|
98
|
-
present.push(
|
|
43
|
+
for (const [name, hash] of entries) {
|
|
44
|
+
if (hash === undefined)
|
|
45
|
+
return { status: 'missing', message: `Missing file or package dependency: ${name}` };
|
|
46
|
+
present.push([name, hash]);
|
|
99
47
|
}
|
|
100
|
-
return { status: '
|
|
48
|
+
return { status: 'current', hashes: Object.fromEntries(present) };
|
|
49
|
+
}
|
|
50
|
+
async function load(key) {
|
|
51
|
+
const file = join(directory, `${key}.json`);
|
|
52
|
+
const raw = await disk.load(file);
|
|
53
|
+
if (raw === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
const parsed = metadata(raw, file);
|
|
56
|
+
if (!parsed || !matches(await snapshot(parsed.saved), parsed.savedHashes))
|
|
57
|
+
return undefined;
|
|
58
|
+
const bytes = await disk.load(join(directory, `${key}.bin`));
|
|
59
|
+
if (bytes === undefined || contentHash(bytes) !== parsed.contentHash)
|
|
60
|
+
return undefined;
|
|
61
|
+
// The metadata file's modification time is the entry's last use. The bytes keep theirs, so static hosts keep their
|
|
62
|
+
// validators.
|
|
63
|
+
await disk.touch(file);
|
|
64
|
+
return { tag: contentHash(raw), hashes: parsed.dependencies, bytes };
|
|
101
65
|
}
|
|
102
66
|
return {
|
|
103
67
|
async canSave() { return true; },
|
|
104
|
-
async resolve(
|
|
105
|
-
return resolvedDependencies(Object.fromEntries(await Promise.all(Object.entries(
|
|
68
|
+
async resolve(declared) {
|
|
69
|
+
return resolvedDependencies(Object.fromEntries(await Promise.all(Object.entries(declared).map(async ([name, dependency]) => {
|
|
106
70
|
if (dependency.url === undefined)
|
|
107
71
|
return [name, dependency];
|
|
108
72
|
const url = new URL(dependency.url);
|
|
109
73
|
if (url.protocol !== 'file:') {
|
|
110
74
|
throw new Error('Node file dependency URLs must use file:. Use a project-root-relative path for Vite-served files.');
|
|
111
75
|
}
|
|
112
|
-
|
|
113
|
-
let path = relative(root, file);
|
|
114
|
-
if (path === '..' || path.startsWith(`..${sep}`) || isAbsolute(path)) {
|
|
115
|
-
path = relative(await realpath(root), await realpath(file));
|
|
116
|
-
}
|
|
117
|
-
return [name, { path: path.split(sep).join('/') }];
|
|
76
|
+
return [name, { path: await disk.rootRelative(root, fileURLToPath(url)) }];
|
|
118
77
|
}))));
|
|
119
78
|
},
|
|
120
79
|
snapshot,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const record = await disk.load(file);
|
|
124
|
-
if (record === undefined)
|
|
125
|
-
return undefined;
|
|
126
|
-
const { contentHash, dependencies, saved } = metadata(record, file);
|
|
127
|
-
if (saved) {
|
|
128
|
-
const entries = Object.entries(saved);
|
|
129
|
-
const current = await snapshot(Object.fromEntries(entries.map(([name, { dependency }]) => [name, dependency])));
|
|
130
|
-
if (!matches(current, Object.fromEntries(entries.map(([name, { hash }]) => [name, hash]))))
|
|
131
|
-
return undefined;
|
|
132
|
-
}
|
|
133
|
-
const bytes = await disk.load(join(directory, `${key}.bin`));
|
|
134
|
-
if (bytes === undefined || digest(bytes) !== contentHash)
|
|
135
|
-
return undefined;
|
|
136
|
-
return { bytes, hashes: dependencies };
|
|
137
|
-
},
|
|
138
|
-
async save(key, value, validation, expected, saved) {
|
|
139
|
-
if (!matches(await snapshot(validation), expected))
|
|
140
|
-
throw new ChangedDependenciesError();
|
|
80
|
+
load,
|
|
81
|
+
async save(key, value, { checked, expected, saved }) {
|
|
141
82
|
const current = await snapshot(saved);
|
|
142
|
-
if (current.status !== '
|
|
143
|
-
throw new MissingDependencyError(
|
|
83
|
+
if (current.status !== 'current')
|
|
84
|
+
throw new MissingDependencyError(`Cannot save a cache entry with a missing saved dependency. ${current.message}`);
|
|
144
85
|
await disk.ignore(root, directory);
|
|
145
86
|
await disk.save(join(directory, `${key}.bin`), value, async () => {
|
|
146
|
-
if (!matches(await snapshot(
|
|
87
|
+
if (!matches(await snapshot(checked), expected))
|
|
147
88
|
throw new ChangedDependenciesError();
|
|
148
89
|
});
|
|
149
|
-
const record = { contentHash:
|
|
150
|
-
const entries = Object.entries(saved).map(([name, dependency]) => {
|
|
151
|
-
const hash = current.hashes[name];
|
|
152
|
-
if (hash === undefined)
|
|
153
|
-
throw new Error(`vitecache did not hash saved dependency ${name}.`);
|
|
154
|
-
return [name, { dependency, hash }];
|
|
155
|
-
});
|
|
156
|
-
if (entries.length)
|
|
157
|
-
record.saved = Object.fromEntries(entries);
|
|
90
|
+
const record = { contentHash: contentHash(value), dependencies: expected, saved, savedHashes: current.hashes };
|
|
158
91
|
await disk.save(join(directory, `${key}.json`), new TextEncoder().encode(JSON.stringify(record)));
|
|
159
92
|
},
|
|
160
93
|
};
|
package/dist/storage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport { realpath, stat } from 'node:fs/promises';\nimport { isAbsolute, join, relative, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport * as disk from './disk.js';\nimport { equalHashes, hashes, MissingDependencyError, resolvedDependencies, savedDependencies } from './dependencies.js';\nimport { packageVersion } from './packages.js';\nimport type { Backend, CacheEntryOptions, Dependencies, Hashes, ResolvedDependencies, ResolvedDependency, Snapshot } from './types.js';\n\nfunction digest(bytes: Uint8Array): string {\n return createHash('sha256').update(bytes).digest('hex');\n}\n\ninterface Metadata {\n contentHash: string;\n dependencies: Hashes;\n /** Saved dependencies with their hashes at save time; absent when there were none. */\n saved?: Readonly<Record<string, { dependency: ResolvedDependency; hash: string }>>;\n}\n\nexport class ChangedDependenciesError extends Error {\n constructor() {\n super('vitecache dependencies changed during computation.');\n }\n}\n\n/** A metadata file vitecache wrote; any failure to read it names the file, since no request caused it. */\nfunction metadata(bytes: Uint8Array, file: string): Metadata {\n try {\n return parse(bytes);\n } catch (cause) {\n throw new Error(`Invalid vitecache metadata: ${file}`, { cause });\n }\n}\n\nfunction parse(bytes: Uint8Array): Metadata {\n const value: unknown = JSON.parse(new TextDecoder().decode(bytes));\n if (typeof value !== 'object' || value === null || !('contentHash' in value) || !('dependencies' in value)) {\n throw new Error('Expected contentHash and dependencies.');\n }\n const { contentHash, dependencies } = value;\n if (typeof contentHash !== 'string' || !/^[a-f0-9]{64}$/.test(contentHash)) throw new Error('Expected a content hash.');\n if (!('saved' in value)) return { contentHash, dependencies: hashes(dependencies) };\n if (typeof value.saved !== 'object' || value.saved === null) throw new Error('Expected saved dependencies.');\n const saved = Object.entries(value.saved).map(([name, item]: [string, unknown]) => {\n if (typeof item !== 'object' || item === null || !('dependency' in item) || !('hash' in item)) throw new Error('Expected a saved dependency and its hash.');\n const dependency = resolvedDependencies(savedDependencies({ [name]: item.dependency }))[name];\n const hash = hashes({ [name]: item.hash })[name];\n if (dependency === undefined || hash === undefined) throw new Error('Expected a saved dependency and its hash.');\n return [name, { dependency, hash }] as const;\n });\n return { contentHash, dependencies: hashes(dependencies), saved: Object.fromEntries(saved) };\n}\n\nfunction matches(current: Snapshot, expected: Hashes): boolean {\n return current.status === 'available' && equalHashes(current.hashes, expected);\n}\n\nexport function createStorage(options: Pick<CacheEntryOptions, 'root' | 'basePath'> & { memoizeHashes?: boolean }): Backend {\n const { root, directory } = disk.location(options);\n const fileHashes = new Map<string, { size: bigint; mtime: bigint; ctime: bigint; hash: Promise<string | undefined> }>();\n\n async function fileHash(path: string): Promise<string | undefined> {\n if (!options.memoizeHashes) {\n const bytes = await disk.load(path);\n return bytes === undefined ? undefined : digest(bytes);\n }\n try {\n const { size, mtimeNs: mtime, ctimeNs: ctime } = await stat(path, { bigint: true });\n const cached = fileHashes.get(path);\n if (cached?.size === size && cached.mtime === mtime && cached.ctime === ctime) return await cached.hash;\n const hash = disk.load(path).then((bytes) => bytes === undefined ? undefined : digest(bytes));\n fileHashes.set(path, { size, mtime, ctime, hash });\n const value = await hash;\n if (value === undefined && fileHashes.get(path)?.hash === hash) fileHashes.delete(path);\n return value;\n } catch (error) {\n fileHashes.delete(path);\n throw error;\n }\n }\n\n async function snapshot(dependencies: ResolvedDependencies): Promise<Snapshot> {\n const entries = await Promise.all(Object.entries(dependencies).filter(([, dependency]) => !('value' in dependency)).map(async ([name, dependency]) => {\n try {\n if (dependency.package !== undefined) {\n const version = await packageVersion(root, dependency.package);\n return version === undefined ? null : [name, digest(new TextEncoder().encode(version))] satisfies [string, string];\n }\n if (dependency.path === undefined) throw new TypeError('Expected a resolved dependency.');\n const hash = await fileHash(resolve(root, dependency.path));\n return hash === undefined ? null : [name, hash] satisfies [string, string];\n } catch (error) {\n if (!disk.missingDependency(error)) throw error;\n return null;\n }\n }));\n const present: [string, string][] = [];\n for (const entry of entries) {\n if (entry === null) return { status: 'missing' };\n present.push(entry);\n }\n return { status: 'available', hashes: Object.fromEntries(present) };\n }\n\n return {\n async canSave() { return true; },\n async resolve(dependencies: Dependencies) {\n return resolvedDependencies(Object.fromEntries(await Promise.all(Object.entries(dependencies).map(async ([name, dependency]) => {\n if (dependency.url === undefined) return [name, dependency];\n const url = new URL(dependency.url);\n if (url.protocol !== 'file:') {\n throw new Error('Node file dependency URLs must use file:. Use a project-root-relative path for Vite-served files.');\n }\n const file = fileURLToPath(url);\n let path = relative(root, file);\n if (path === '..' || path.startsWith(`..${sep}`) || isAbsolute(path)) {\n path = relative(await realpath(root), await realpath(file));\n }\n return [name, { path: path.split(sep).join('/') }];\n }))));\n },\n snapshot,\n async load(key) {\n const file = join(directory, `${key}.json`);\n const record = await disk.load(file);\n if (record === undefined) return undefined;\n const { contentHash, dependencies, saved } = metadata(record, file);\n if (saved) {\n const entries = Object.entries(saved);\n const current = await snapshot(Object.fromEntries(entries.map(([name, { dependency }]) => [name, dependency])));\n if (!matches(current, Object.fromEntries(entries.map(([name, { hash }]) => [name, hash])))) return undefined;\n }\n const bytes = await disk.load(join(directory, `${key}.bin`));\n if (bytes === undefined || digest(bytes) !== contentHash) return undefined;\n return { bytes, hashes: dependencies };\n },\n async save(key, value, validation, expected, saved) {\n if (!matches(await snapshot(validation), expected)) throw new ChangedDependenciesError();\n const current = await snapshot(saved);\n if (current.status !== 'available') throw new MissingDependencyError('Cannot save a cache entry with missing saved dependencies.');\n await disk.ignore(root, directory);\n await disk.save(join(directory, `${key}.bin`), value, async () => {\n if (!matches(await snapshot(validation), expected)) throw new ChangedDependenciesError();\n });\n const record: Metadata = { contentHash: digest(value), dependencies: expected };\n const entries = Object.entries(saved).map(([name, dependency]) => {\n const hash = current.hashes[name];\n if (hash === undefined) throw new Error(`vitecache did not hash saved dependency ${name}.`);\n return [name, { dependency, hash }] as const;\n });\n if (entries.length) record.saved = Object.fromEntries(entries);\n await disk.save(join(directory, `${key}.json`), new TextEncoder().encode(JSON.stringify(record)));\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,wBAAwB,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACtJ,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAW/C,0GAA0G;AAC1G,SAAS,QAAQ,CAAC,KAAiB,EAAE,IAAY;IAC/C,IAAI,CAAC;QACH,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACnE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,aAAa,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,cAAc,IAAI,KAAK,CAAC,EAAE,CAAC;YAC3G,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,CAAC;QAC/B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1G,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAC7C,mHAAmH;QACnH,IAAI,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;QAC3G,mHAAmH;QACnH,IAAI,CAAC,CAAC,aAAa,IAAI,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAChD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,oBAAoB,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;IAC1I,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAcD,SAAS,OAAO,CAAC,OAAiB,EAAE,QAAgB;IAClD,OAAO,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAqD;IACjF,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEnD,KAAK,UAAU,QAAQ,CAAC,YAAoC;QAC1D,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE;YAC9F,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,EAAE,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAU,CAAC;YAC7G,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;YAC/D,OAAO,CAAC,IAAI,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAU,CAAC;QAC7G,CAAC,CAAC,CAAC,CAAC;QACJ,MAAM,OAAO,GAAuB,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;YACnC,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,uCAAuC,IAAI,EAAE,EAAE,CAAC;YAC7G,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;IACpE,CAAC;IAED,KAAK,UAAU,IAAI,CAAC,GAAW;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC;YAAE,OAAO,SAAS,CAAC;QAC5F,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC;QAC7D,IAAI,KAAK,KAAK,SAAS,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,WAAW;YAAE,OAAO,SAAS,CAAC;QACvF,mHAAmH;QACnH,cAAc;QACd,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvB,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,KAAK,EAAE,CAAC;IACvE,CAAC;IAED,OAAO;QACL,KAAK,CAAC,OAAO,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC;QAChC,KAAK,CAAC,OAAO,CAAC,QAAsB;YAClC,OAAO,oBAAoB,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE;gBACzH,IAAI,UAAU,CAAC,GAAG,KAAK,SAAS;oBAAE,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;gBAC5D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;gBACpC,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;oBAC7B,MAAM,IAAI,KAAK,CAAC,mGAAmG,CAAC,CAAC;gBACvH,CAAC;gBACD,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;YAC7E,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACR,CAAC;QACD,QAAQ;QACR,IAAI;QACJ,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;YACjD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC;YACtC,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;gBAAE,MAAM,IAAI,sBAAsB,CAAC,8DAA8D,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;YACpJ,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACnC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE;gBAC/D,IAAI,CAAC,OAAO,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC;oBAAE,MAAM,IAAI,wBAAwB,EAAE,CAAC;YACxF,CAAC,CAAC,CAAC;YACH,MAAM,MAAM,GAAa,EAAE,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;YACzH,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACpG,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport * as disk from './disk.js';\nimport { ChangedDependenciesError, equalHashes, hashableDependencies, hashes, MissingDependencyError, resolvedDependencies } from './dependencies.js';\nimport { contentHash, fileHash } from './hash.js';\nimport { packageVersion } from './packages.js';\nimport type { Backend, CacheEntryOptions, Dependencies, Hashable, Hashes, Snapshot, Stored } from './types.js';\n\ninterface Metadata {\n contentHash: string;\n dependencies: Hashes;\n saved: Dependencies<Hashable>;\n /** The saved dependencies' hashes when the entry was saved. */\n savedHashes: Hashes;\n}\n\n/** A metadata file vitecache wrote; any failure to read it names the file, since no request caused it. */\nfunction metadata(bytes: Uint8Array, file: string): Metadata | undefined {\n try {\n const value: unknown = JSON.parse(new TextDecoder().decode(bytes));\n if (typeof value !== 'object' || value === null || !('contentHash' in value) || !('dependencies' in value)) {\n throw new Error('Expected contentHash and dependencies.');\n }\n const hash = value.contentHash;\n if (typeof hash !== 'string' || !/^[a-f0-9]{64}$/.test(hash)) throw new Error('Expected a content hash.');\n const validated = hashes(value.dependencies);\n // Metadata without saved dependencies: all that vitecache 0.1 wrote, and what 0.2 wrote for entries that had none.\n if (!('saved' in value)) return { contentHash: hash, dependencies: validated, saved: {}, savedHashes: {} };\n // vitecache 0.2 kept each saved dependency with its hash in one record. Its entry misses until a save replaces it.\n if (!('savedHashes' in value)) return undefined;\n return { contentHash: hash, dependencies: validated, saved: hashableDependencies(value.saved), savedHashes: hashes(value.savedHashes) };\n } catch (cause) {\n throw new Error(`Invalid vitecache metadata: ${file}`, { cause });\n }\n}\n\n/** A stored entry as storage reads it, its hashes always known. */\nexport interface Found extends Stored {\n /** The entity tag's opaque value: it changes whenever the entry's bytes or dependency hashes do. */\n tag: string;\n hashes: Hashes;\n}\n\nexport interface Storage extends Backend {\n /** Reads a valid entry and records its use for prune(). */\n load(key: string): Promise<Found | undefined>;\n}\n\nfunction matches(current: Snapshot, expected: Hashes): boolean {\n return current.status === 'current' && equalHashes(current.hashes, expected);\n}\n\nexport function createStorage(options: Pick<CacheEntryOptions, 'root' | 'basePath'>): Storage {\n const { root, directory } = disk.location(options);\n\n async function snapshot(dependencies: Dependencies<Hashable>): Promise<Exclude<Snapshot, { status: 'unchecked' }>> {\n const entries = await Promise.all(Object.entries(dependencies).map(async ([name, dependency]) => {\n if (dependency.package === undefined) return [name, await fileHash(resolve(root, dependency.path))] as const;\n const version = await packageVersion(root, dependency.package);\n return [name, version === undefined ? undefined : contentHash(new TextEncoder().encode(version))] as const;\n }));\n const present: [string, string][] = [];\n for (const [name, hash] of entries) {\n if (hash === undefined) return { status: 'missing', message: `Missing file or package dependency: ${name}` };\n present.push([name, hash]);\n }\n return { status: 'current', hashes: Object.fromEntries(present) };\n }\n\n async function load(key: string): Promise<Found | undefined> {\n const file = join(directory, `${key}.json`);\n const raw = await disk.load(file);\n if (raw === undefined) return undefined;\n const parsed = metadata(raw, file);\n if (!parsed || !matches(await snapshot(parsed.saved), parsed.savedHashes)) return undefined;\n const bytes = await disk.load(join(directory, `${key}.bin`));\n if (bytes === undefined || contentHash(bytes) !== parsed.contentHash) return undefined;\n // The metadata file's modification time is the entry's last use. The bytes keep theirs, so static hosts keep their\n // validators.\n await disk.touch(file);\n return { tag: contentHash(raw), hashes: parsed.dependencies, bytes };\n }\n\n return {\n async canSave() { return true; },\n async resolve(declared: Dependencies) {\n return resolvedDependencies(Object.fromEntries(await Promise.all(Object.entries(declared).map(async ([name, dependency]) => {\n if (dependency.url === undefined) return [name, dependency];\n const url = new URL(dependency.url);\n if (url.protocol !== 'file:') {\n throw new Error('Node file dependency URLs must use file:. Use a project-root-relative path for Vite-served files.');\n }\n return [name, { path: await disk.rootRelative(root, fileURLToPath(url)) }];\n }))));\n },\n snapshot,\n load,\n async save(key, value, { checked, expected, saved }) {\n const current = await snapshot(saved);\n if (current.status !== 'current') throw new MissingDependencyError(`Cannot save a cache entry with a missing saved dependency. ${current.message}`);\n await disk.ignore(root, directory);\n await disk.save(join(directory, `${key}.bin`), value, async () => {\n if (!matches(await snapshot(checked), expected)) throw new ChangedDependenciesError();\n });\n const record: Metadata = { contentHash: contentHash(value), dependencies: expected, saved, savedHashes: current.hashes };\n await disk.save(join(directory, `${key}.json`), new TextEncoder().encode(JSON.stringify(record)));\n },\n };\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -23,32 +23,37 @@ export type Dependency = {
|
|
|
23
23
|
readonly path?: never;
|
|
24
24
|
readonly url?: never;
|
|
25
25
|
};
|
|
26
|
-
export type Dependencies = Readonly<Record<string,
|
|
27
|
-
|
|
26
|
+
export type Dependencies<D extends Dependency = Dependency> = Readonly<Record<string, D>>;
|
|
27
|
+
/** A dependency whose URL, if it had one, was resolved to a path. */
|
|
28
|
+
export type Resolved<D extends Dependency = Dependency> = Exclude<D, {
|
|
28
29
|
readonly url: string;
|
|
29
30
|
}>;
|
|
30
|
-
/** A file, URL, or package dependency: one whose current state can be checked again later. */
|
|
31
|
-
export type
|
|
31
|
+
/** A file, URL, or package dependency: unlike a value, one whose current state can be checked again later. */
|
|
32
|
+
export type Checked<D extends Dependency = Dependency> = Exclude<D, {
|
|
32
33
|
readonly value: JsonValue;
|
|
33
34
|
}>;
|
|
34
|
-
|
|
35
|
-
export type
|
|
35
|
+
/** A dependency passed to save(): only checked kinds, since values select the result. */
|
|
36
|
+
export type SavedDependency = Checked;
|
|
37
|
+
export type SavedDependencies = Dependencies<SavedDependency>;
|
|
38
|
+
/** A resolved checked dependency, a file path or a package: one whose current state can be hashed. */
|
|
39
|
+
export type Hashable = Resolved<Checked>;
|
|
36
40
|
export type Paths = Readonly<Record<string, string>>;
|
|
37
41
|
/** Dependency names mapped to file-content or installed-package-version hashes. */
|
|
38
42
|
export type Hashes = Readonly<Record<string, string>>;
|
|
39
|
-
/** Current dependency hashes, a missing dependency, or
|
|
43
|
+
/** Current dependency hashes, a missing dependency, or dependencies a browser without the plugin cannot check. */
|
|
40
44
|
export type Snapshot = {
|
|
41
|
-
readonly status: '
|
|
45
|
+
readonly status: 'current';
|
|
42
46
|
readonly hashes: Hashes;
|
|
43
47
|
} | {
|
|
44
48
|
readonly status: 'missing';
|
|
49
|
+
readonly message: string;
|
|
45
50
|
} | {
|
|
46
|
-
readonly status: '
|
|
51
|
+
readonly status: 'unchecked';
|
|
47
52
|
};
|
|
48
53
|
export interface CacheEntryOptions {
|
|
49
54
|
/** Identifies the computation and its implementation version, for example "simplify@2". */
|
|
50
55
|
version: string;
|
|
51
|
-
/** Named dependencies
|
|
56
|
+
/** Named dependencies. JSON values affect the key; file contents and installed package versions are validated in Node or through the Vite plugin. Paths are always project-root-relative. URLs resolve to those paths through Vite, or from local file: URLs in Node. Packages track only the declared package's installed version, without importing it. */
|
|
52
57
|
dependencies?: Dependencies;
|
|
53
58
|
/** Disk directory in Node (default public/_vitecache); URL prefix in browsers (default /_vitecache/). Excluded from the key. */
|
|
54
59
|
basePath?: string;
|
|
@@ -71,9 +76,14 @@ export interface Stored {
|
|
|
71
76
|
}
|
|
72
77
|
export interface Backend {
|
|
73
78
|
canSave(): Promise<boolean>;
|
|
74
|
-
resolve(dependencies: Dependencies): Promise<
|
|
75
|
-
snapshot(dependencies:
|
|
76
|
-
load(key: string
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
resolve(dependencies: Dependencies): Promise<Dependencies<Resolved>>;
|
|
80
|
+
snapshot(dependencies: Dependencies<Hashable>): Promise<Snapshot>;
|
|
81
|
+
load(key: string): Promise<Stored | undefined>;
|
|
82
|
+
save(key: string, value: Uint8Array, check: {
|
|
83
|
+
/** Must still match `expected`. */
|
|
84
|
+
checked: Dependencies<Hashable>;
|
|
85
|
+
expected: Hashes;
|
|
86
|
+
/** Hashed now and rechecked on load. */
|
|
87
|
+
saved: Dependencies<Hashable>;
|
|
88
|
+
}): Promise<void>;
|
|
79
89
|
}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue };\n\n/** Exactly one source per dependency. Names and descriptors participate in cache identity. */\nexport type Dependency =\n | { readonly value: JsonValue; readonly path?: never; readonly url?: never; readonly package?: never }\n | { readonly path: string; readonly value?: never; readonly url?: never; readonly package?: never }\n | { readonly url: string; readonly value?: never; readonly path?: never; readonly package?: never }\n | { readonly package: string; readonly value?: never; readonly path?: never; readonly url?: never };\nexport type Dependencies = Readonly<Record<string,
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue };\n\n/** Exactly one source per dependency. Names and descriptors participate in cache identity. */\nexport type Dependency =\n | { readonly value: JsonValue; readonly path?: never; readonly url?: never; readonly package?: never }\n | { readonly path: string; readonly value?: never; readonly url?: never; readonly package?: never }\n | { readonly url: string; readonly value?: never; readonly path?: never; readonly package?: never }\n | { readonly package: string; readonly value?: never; readonly path?: never; readonly url?: never };\nexport type Dependencies<D extends Dependency = Dependency> = Readonly<Record<string, D>>;\n/** A dependency whose URL, if it had one, was resolved to a path. */\nexport type Resolved<D extends Dependency = Dependency> = Exclude<D, { readonly url: string }>;\n/** A file, URL, or package dependency: unlike a value, one whose current state can be checked again later. */\nexport type Checked<D extends Dependency = Dependency> = Exclude<D, { readonly value: JsonValue }>;\n/** A dependency passed to save(): only checked kinds, since values select the result. */\nexport type SavedDependency = Checked;\nexport type SavedDependencies = Dependencies<SavedDependency>;\n/** A resolved checked dependency, a file path or a package: one whose current state can be hashed. */\nexport type Hashable = Resolved<Checked>;\nexport type Paths = Readonly<Record<string, string>>;\n/** Dependency names mapped to file-content or installed-package-version hashes. */\nexport type Hashes = Readonly<Record<string, string>>;\n/** Current dependency hashes, a missing dependency, or dependencies a browser without the plugin cannot check. */\nexport type Snapshot =\n | { readonly status: 'current'; readonly hashes: Hashes }\n | { readonly status: 'missing'; readonly message: string }\n | { readonly status: 'unchecked' };\n\nexport interface CacheEntryOptions {\n /** Identifies the computation and its implementation version, for example \"simplify@2\". */\n version: string;\n /** Named dependencies. JSON values affect the key; file contents and installed package versions are validated in Node or through the Vite plugin. Paths are always project-root-relative. URLs resolve to those paths through Vite, or from local file: URLs in Node. Packages track only the declared package's installed version, without importing it. */\n dependencies?: Dependencies;\n /** Disk directory in Node (default public/_vitecache); URL prefix in browsers (default /_vitecache/). Excluded from the key. */\n basePath?: string;\n /** Node project root, default process.cwd(). Browser dependencies resolve against the Vite root. Excluded from the key. */\n root?: string;\n}\n\nexport interface SaveOptions {\n /**\n * What the result was built from, when known only after computing it. They do not affect the key, so a save with\n * different saved dependencies replaces the same entry. Rechecked by every load() where file contents are validated;\n * a changed or missing one is a miss. Hashed when save() runs, so one that changed after the computation read it goes\n * unnoticed until it changes again. One without a backing file makes save() throw MissingDependencyError. Values select\n * the result, so they belong in the constructor.\n */\n dependencies?: SavedDependencies;\n}\n\nexport interface Stored {\n bytes: Uint8Array;\n hashes?: Hashes;\n}\n\nexport interface Backend {\n canSave(): Promise<boolean>;\n resolve(dependencies: Dependencies): Promise<Dependencies<Resolved>>;\n snapshot(dependencies: Dependencies<Hashable>): Promise<Snapshot>;\n load(key: string): Promise<Stored | undefined>;\n save(key: string, value: Uint8Array, check: {\n /** Must still match `expected`. */\n checked: Dependencies<Hashable>;\n expected: Hashes;\n /** Hashed now and rechecked on load. */\n saved: Dependencies<Hashable>;\n }): Promise<void>;\n}\n"]}
|
package/dist/vite/index.js
CHANGED
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
import { relative, resolve, sep } from 'node:path';
|
|
2
2
|
import * as disk from '../disk.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { confine,
|
|
3
|
+
import { createStorage } from '../storage.js';
|
|
4
|
+
import { ChangedDependenciesError, dependencies, hashableDependencies, hashes, MissingDependencyError } from '../dependencies.js';
|
|
5
|
+
import { confine, RequestError, resolveDependencies } from './resolve.js';
|
|
6
6
|
import { assetUrl, publicPaths } from './manifest.js';
|
|
7
|
-
class RequestError extends Error {
|
|
8
|
-
status;
|
|
9
|
-
constructor(status, message) {
|
|
10
|
-
super(message);
|
|
11
|
-
this.status = status;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
7
|
function status(error) {
|
|
15
8
|
if (error instanceof RequestError)
|
|
16
9
|
return error.status;
|
|
17
10
|
if (error instanceof ChangedDependenciesError)
|
|
18
11
|
return 409;
|
|
19
|
-
if (error instanceof MissingDependencyError)
|
|
20
|
-
return 404;
|
|
21
|
-
if (error instanceof OutsideRootError)
|
|
22
|
-
return 403;
|
|
23
|
-
if (error instanceof TypeError || error instanceof URIError)
|
|
24
|
-
return 400;
|
|
25
12
|
return 500;
|
|
26
13
|
}
|
|
27
14
|
function allowed(request) {
|
|
@@ -56,7 +43,7 @@ function parse(value) {
|
|
|
56
43
|
throw new RequestError(400, 'Expected valid JSON');
|
|
57
44
|
}
|
|
58
45
|
}
|
|
59
|
-
function
|
|
46
|
+
function parseRequest(value, parser) {
|
|
60
47
|
try {
|
|
61
48
|
return parser(value);
|
|
62
49
|
}
|
|
@@ -74,12 +61,21 @@ function upload(bytes) {
|
|
|
74
61
|
if (end > bytes.byteLength)
|
|
75
62
|
throw new RequestError(400, 'Truncated save metadata');
|
|
76
63
|
const metadata = parse(new TextDecoder().decode(bytes.subarray(4, end)));
|
|
77
|
-
if (typeof metadata !== 'object' || metadata === null || !('
|
|
78
|
-
throw new RequestError(400, 'Expected
|
|
64
|
+
if (typeof metadata !== 'object' || metadata === null || !('checked' in metadata) || !('expected' in metadata) || !('saved' in metadata)) {
|
|
65
|
+
throw new RequestError(400, 'Expected checked, expected, and saved');
|
|
79
66
|
}
|
|
80
67
|
return { metadata, value: bytes.subarray(end) };
|
|
81
68
|
}
|
|
82
|
-
|
|
69
|
+
/** The `dependencies` of a same-origin POST's JSON body. */
|
|
70
|
+
async function posted(request) {
|
|
71
|
+
if (!allowed(request))
|
|
72
|
+
throw new RequestError(403, 'Cross-origin requests are forbidden');
|
|
73
|
+
const value = parse(new TextDecoder().decode(await body(request)));
|
|
74
|
+
if (typeof value !== 'object' || value === null || !('dependencies' in value))
|
|
75
|
+
throw new RequestError(400, 'Expected dependencies');
|
|
76
|
+
return value.dependencies;
|
|
77
|
+
}
|
|
78
|
+
function sendJson(response, value) {
|
|
83
79
|
response.setHeader('Content-Type', 'application/json');
|
|
84
80
|
response.end(JSON.stringify(value));
|
|
85
81
|
}
|
|
@@ -87,25 +83,19 @@ async function respond(request, response, name, storage, server) {
|
|
|
87
83
|
const root = server.config.root;
|
|
88
84
|
response.setHeader('Cache-Control', 'no-store');
|
|
89
85
|
if (name === '__vitecache' && request.method === 'GET') {
|
|
90
|
-
|
|
86
|
+
sendJson(response, { vitecache: 1 });
|
|
91
87
|
return;
|
|
92
88
|
}
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const deps = validate(value.dependencies, dependencies);
|
|
102
|
-
const origin = `${server.config.server.https ? 'https' : 'http'}://${request.headers.host}`;
|
|
103
|
-
json(response, { dependencies: await resolveDependencies(server, deps, origin) });
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const deps = validate(value.dependencies, resolvedDependencies);
|
|
89
|
+
if (name === '__vitecache/resolve' && request.method === 'POST') {
|
|
90
|
+
const deps = parseRequest(await posted(request), dependencies);
|
|
91
|
+
const origin = `${server.config.server.https ? 'https' : 'http'}://${request.headers.host}`;
|
|
92
|
+
sendJson(response, { dependencies: await resolveDependencies(server, deps, origin) });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (name === '__vitecache/snapshot' && request.method === 'POST') {
|
|
96
|
+
const deps = parseRequest(await posted(request), hashableDependencies);
|
|
107
97
|
await confine(root, deps);
|
|
108
|
-
|
|
98
|
+
sendJson(response, await storage.snapshot(deps));
|
|
109
99
|
return;
|
|
110
100
|
}
|
|
111
101
|
if (!/^[a-f0-9]{64}\.bin$/.test(name)) {
|
|
@@ -118,37 +108,46 @@ async function respond(request, response, name, storage, server) {
|
|
|
118
108
|
if (!allowed(request))
|
|
119
109
|
throw new RequestError(403, 'Cross-origin requests are forbidden');
|
|
120
110
|
const { metadata, value } = upload(await body(request));
|
|
121
|
-
const
|
|
122
|
-
const expected =
|
|
123
|
-
const saved =
|
|
124
|
-
await confine(root,
|
|
111
|
+
const checked = parseRequest(metadata.checked, hashableDependencies);
|
|
112
|
+
const expected = parseRequest(metadata.expected, hashes);
|
|
113
|
+
const saved = parseRequest(metadata.saved, hashableDependencies);
|
|
114
|
+
await confine(root, checked);
|
|
125
115
|
await confine(root, saved);
|
|
126
|
-
await storage.save(key, value,
|
|
116
|
+
await storage.save(key, value, { checked, expected, saved });
|
|
127
117
|
response.statusCode = 204;
|
|
128
118
|
response.end();
|
|
129
119
|
return;
|
|
130
120
|
}
|
|
131
|
-
if (request.method !== 'GET'
|
|
121
|
+
if (request.method !== 'GET') {
|
|
132
122
|
response.statusCode = 405;
|
|
133
|
-
response.setHeader('Allow', 'GET,
|
|
123
|
+
response.setHeader('Allow', 'GET, PUT');
|
|
134
124
|
response.end();
|
|
135
125
|
return;
|
|
136
126
|
}
|
|
137
|
-
const stored = await storage.load(key
|
|
127
|
+
const stored = await storage.load(key);
|
|
138
128
|
if (stored === undefined) {
|
|
139
129
|
// Browsers log every 404 fetch as an error, and a miss is an expected answer.
|
|
140
130
|
response.statusCode = 204;
|
|
141
131
|
response.end();
|
|
142
132
|
return;
|
|
143
133
|
}
|
|
134
|
+
// Browsers keep the bytes and revalidate them on every load, so an unchanged entry costs a 304 rather than a download.
|
|
135
|
+
response.setHeader('Cache-Control', 'no-cache');
|
|
136
|
+
const tag = `"${stored.tag}"`;
|
|
137
|
+
response.setHeader('ETag', tag);
|
|
138
|
+
response.setHeader('X-Vitecache-Dependencies', encodeURIComponent(JSON.stringify(stored.hashes)));
|
|
139
|
+
if (request.headers['if-none-match'] === tag) {
|
|
140
|
+
response.statusCode = 304;
|
|
141
|
+
response.end();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
144
|
response.setHeader('Content-Type', 'application/octet-stream');
|
|
145
145
|
response.setHeader('Content-Length', stored.bytes.byteLength);
|
|
146
|
-
response.
|
|
147
|
-
response.end(request.method === 'HEAD' ? undefined : stored.bytes);
|
|
146
|
+
response.end(stored.bytes);
|
|
148
147
|
}
|
|
149
148
|
/** Persist and validate entries in development; preserve asset URL mappings in production builds. */
|
|
150
149
|
export function vitecache(options = {}) {
|
|
151
|
-
let
|
|
150
|
+
let state;
|
|
152
151
|
return {
|
|
153
152
|
name: 'vitecache',
|
|
154
153
|
enforce: 'post',
|
|
@@ -157,12 +156,12 @@ export function vitecache(options = {}) {
|
|
|
157
156
|
throw new Error('vitecache requires Vite publicDir');
|
|
158
157
|
const { root, directory } = disk.location({ root: config.root, ...options });
|
|
159
158
|
const path = relative(config.publicDir, directory);
|
|
160
|
-
if (!path || !inside(config.publicDir, directory)) {
|
|
159
|
+
if (!path || !disk.inside(config.publicDir, directory)) {
|
|
161
160
|
throw new Error('vitecache basePath must be a directory inside Vite publicDir');
|
|
162
161
|
}
|
|
163
162
|
const base = new URL(config.base, 'http://vitecache').pathname.replace(/\/$/, '');
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
state = {
|
|
164
|
+
storage: createStorage({ root, ...options }),
|
|
166
165
|
root,
|
|
167
166
|
directory,
|
|
168
167
|
config,
|
|
@@ -172,10 +171,10 @@ export function vitecache(options = {}) {
|
|
|
172
171
|
await disk.ignore(root, directory);
|
|
173
172
|
},
|
|
174
173
|
async generateBundle(_options, bundle) {
|
|
175
|
-
const
|
|
176
|
-
if (!
|
|
174
|
+
const resolved = state;
|
|
175
|
+
if (!resolved)
|
|
177
176
|
throw new Error('vitecache configuration was not resolved');
|
|
178
|
-
const paths = new Map(Object.entries(await publicPaths(
|
|
177
|
+
const paths = new Map(Object.entries(await publicPaths(resolved.config, resolved.directory)));
|
|
179
178
|
for (const asset of Object.values(bundle)) {
|
|
180
179
|
if (asset.type !== 'asset')
|
|
181
180
|
continue;
|
|
@@ -185,30 +184,31 @@ export function vitecache(options = {}) {
|
|
|
185
184
|
const original = originals[0];
|
|
186
185
|
if (!original)
|
|
187
186
|
continue;
|
|
188
|
-
const file =
|
|
189
|
-
if (!inside(
|
|
187
|
+
const file = resolve(resolved.root, original);
|
|
188
|
+
if (!disk.inside(resolved.root, file))
|
|
190
189
|
continue;
|
|
191
|
-
paths.set(assetUrl(asset.fileName), file.split(sep).join('/'));
|
|
190
|
+
paths.set(assetUrl(asset.fileName), relative(resolved.root, file).split(sep).join('/'));
|
|
192
191
|
}
|
|
193
|
-
const manifest = { paths: Object.fromEntries(paths), root: '../'.repeat(
|
|
194
|
-
this.emitFile({ type: 'asset', fileName: `${
|
|
192
|
+
const manifest = { paths: Object.fromEntries(paths), root: '../'.repeat(resolved.cachePath.split('/').length + 1) };
|
|
193
|
+
this.emitFile({ type: 'asset', fileName: `${resolved.cachePath}/__vitecache/paths.json`, source: JSON.stringify(manifest) });
|
|
195
194
|
},
|
|
196
195
|
configureServer(server) {
|
|
197
|
-
const
|
|
198
|
-
if (!
|
|
196
|
+
const resolved = state;
|
|
197
|
+
if (!resolved)
|
|
199
198
|
throw new Error('vitecache configuration was not resolved');
|
|
200
199
|
server.middlewares.use((request, response, next) => {
|
|
201
200
|
const path = new URL(request.url ?? '/', 'http://vitecache').pathname;
|
|
202
|
-
if (!path.startsWith(
|
|
201
|
+
if (!path.startsWith(resolved.prefix)) {
|
|
203
202
|
next();
|
|
204
203
|
return;
|
|
205
204
|
}
|
|
206
|
-
void respond(request, response, path.slice(
|
|
207
|
-
|
|
205
|
+
void respond(request, response, path.slice(resolved.prefix.length), resolved.storage, server).catch((error) => {
|
|
206
|
+
// A missing dependency is an expected answer, as in a snapshot: a body, and no error status for browsers to log.
|
|
208
207
|
if (error instanceof MissingDependencyError) {
|
|
209
|
-
|
|
208
|
+
sendJson(response, { status: 'missing', message: error.message });
|
|
210
209
|
return;
|
|
211
210
|
}
|
|
211
|
+
response.statusCode = status(error);
|
|
212
212
|
response.setHeader('Content-Type', 'text/plain');
|
|
213
213
|
response.end(error instanceof Error ? error.message : 'vitecache request failed');
|
|
214
214
|
});
|