vitecache 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Drawcall
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,49 @@
1
- # Temporary Holding Version
1
+ # vitecache
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Cache expensive computation as bytes, then reuse it across browser sessions and Node runs.
4
+
5
+ ```sh
6
+ pnpm add vitecache
7
+ ```
8
+
9
+ ```ts
10
+ import { CacheEntry } from "vitecache"
11
+ import modelUrl from "./models/chair.glb?url"
12
+
13
+ const entry = new CacheEntry({
14
+ version: "simplify@2",
15
+ dependencies: {
16
+ settings: { value: { ratio: 0.5, preserveUVs: true } },
17
+ algorithm: { path: "src/simplify.ts" },
18
+ model: { url: modelUrl },
19
+ },
20
+ })
21
+
22
+ let bytes = await entry.load()
23
+
24
+ if (bytes === undefined) {
25
+ bytes = await simplify()
26
+ await entry.save(bytes)
27
+ }
28
+
29
+ useMeshBytes(bytes)
30
+ ```
31
+
32
+ Entries identify computations by version and named dependencies: JSON `{ value }`, file `{ path }` or `{ url }`, and installed library `{ package: "elkjs" }`. Node and the Vite development plugin validate file contents and package versions without changing entry keys.
33
+
34
+ Paths are always relative to the project root: `root` in Node (the working directory by default), or the Vite project root in browsers. The Vite plugin resolves served URLs to their source paths and preserves mappings for public files and emitted assets in production builds; Node accepts local file URLs.
35
+
36
+ Package installation paths are excluded from identity; transitive packages need their own declarations. Load before computing so saving can detect dependencies that changed during computation.
37
+
38
+ Node reads and writes `<project_root>/public/_vitecache/`. Add the plugin to save browser results into the same directory during development:
39
+
40
+ ```ts
41
+ import { defineConfig } from "vite"
42
+ import { vitecache } from "vitecache/vite"
43
+
44
+ export default defineConfig({ plugins: [vitecache()] })
45
+ ```
46
+
47
+ Node saves and the plugin add the cache directory to `.gitignore`. Generate caches before building and include them in your deployment. Browsers without the plugin read public cache files, assume dependencies unchanged, and warn and skip writes when `save()` is called.
48
+
49
+ For usage patterns, see the [vitecache skill](skills/vitecache/SKILL.md), also shipped in this package.
@@ -0,0 +1,7 @@
1
+ import { Entry } from './entry.js';
2
+ import type { CacheEntryOptions } from './types.js';
3
+ export type { CacheEntryOptions, Dependency, Dependencies, JsonValue } from './types.js';
4
+ /** A persistent binary computation result. Pass a browser basePath matching the public cache URL, including any Vite base. */
5
+ export declare class CacheEntry extends Entry {
6
+ constructor(options: CacheEntryOptions);
7
+ }
@@ -0,0 +1,136 @@
1
+ import { hashes, paths, record, resolvedDependencies } from './dependencies.js';
2
+ import { Entry } from './entry.js';
3
+ function createBackend(options) {
4
+ const base = new URL(`${(options.basePath ?? '/_vitecache/').replace(/\/+$/, '')}/`, globalThis.location.href);
5
+ let discovery;
6
+ async function discover() {
7
+ const response = await fetch(new URL('__vitecache', base), { cache: 'no-store' });
8
+ if (response.status === 404)
9
+ return false;
10
+ if (!response.ok)
11
+ throw new Error(`vitecache discovery failed: HTTP ${response.status}`);
12
+ // Static SPA servers may return index.html for unknown paths.
13
+ if (!response.headers.get('content-type')?.includes('application/json'))
14
+ return false;
15
+ const data = await response.json();
16
+ return typeof data === 'object' && data !== null && 'vitecache' in data && data.vitecache === 1;
17
+ }
18
+ function plugin() {
19
+ discovery ??= discover().catch((error) => {
20
+ discovery = undefined;
21
+ throw error;
22
+ });
23
+ return discovery;
24
+ }
25
+ return {
26
+ canSave: plugin,
27
+ async resolve(dependencies) {
28
+ const locators = Object.entries(dependencies).map(([name, locator]) => {
29
+ if (locator.url === undefined)
30
+ return [name, locator];
31
+ const url = new URL(locator.url, globalThis.location.href);
32
+ if (url.origin !== new URL(globalThis.location.href).origin)
33
+ throw new Error('URL dependencies must be served by the same Vite project origin.');
34
+ return [name, { url: url.href }];
35
+ });
36
+ if (locators.every(([, locator]) => locator.url === undefined)) {
37
+ return resolvedDependencies(Object.fromEntries(locators));
38
+ }
39
+ if (await plugin()) {
40
+ const response = await fetch(new URL('__vitecache/resolve', base), {
41
+ method: 'POST',
42
+ headers: { 'Content-Type': 'application/json' },
43
+ body: JSON.stringify({ dependencies: Object.fromEntries(locators) }),
44
+ cache: 'no-store',
45
+ });
46
+ if (!response.ok)
47
+ throw new Error(`vitecache URL resolution failed: HTTP ${response.status}`);
48
+ const data = await response.json();
49
+ const resolved = resolvedDependencies(record(data).dependencies);
50
+ if (Object.keys(resolved).length !== locators.length || !locators.every(([name]) => Object.hasOwn(resolved, name))) {
51
+ throw new Error('Invalid vitecache resolved dependencies.');
52
+ }
53
+ return resolved;
54
+ }
55
+ const manifestUrl = new URL('__vitecache/paths.json', base);
56
+ const response = await fetch(manifestUrl, { cache: 'no-store' });
57
+ if (!response.ok || !response.headers.get('content-type')?.includes('application/json')) {
58
+ throw new Error('URL dependencies require the vitecache Vite plugin during development and build. Missing production URL mapping.');
59
+ }
60
+ const data = await response.json();
61
+ const manifest = record(data);
62
+ const mapping = paths(manifest.paths);
63
+ if (typeof manifest.root !== 'string' || !/^(?:\.\.\/)+$/.test(manifest.root))
64
+ throw new Error('Invalid vitecache production mapping root.');
65
+ const projectRoot = new URL(manifest.root, manifestUrl);
66
+ const mapped = Object.fromEntries(Object.entries(mapping).map(([url, path]) => [new URL(url, projectRoot).pathname, path]));
67
+ const resolved = locators.map(([name, locator]) => {
68
+ if (locator.url === undefined)
69
+ return [name, locator];
70
+ const pathname = new URL(locator.url).pathname;
71
+ const path = mapped[pathname];
72
+ if (path === undefined)
73
+ throw new Error(`No vitecache source file mapping for URL: ${pathname}`);
74
+ return [name, { path }];
75
+ });
76
+ return resolvedDependencies(Object.fromEntries(resolved));
77
+ },
78
+ async snapshot(dependencies) {
79
+ if (!await plugin())
80
+ return undefined;
81
+ const response = await fetch(new URL('__vitecache/snapshot', base), {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body: JSON.stringify({ dependencies }),
85
+ cache: 'no-store',
86
+ });
87
+ if (!response.ok)
88
+ throw new Error(`vitecache dependency validation failed: HTTP ${response.status}`);
89
+ const data = await response.json();
90
+ if (typeof data !== 'object' || data === null || !('hashes' in data)) {
91
+ throw new TypeError('Invalid vitecache dependency response.');
92
+ }
93
+ return data.hashes === null ? null : hashes(data.hashes);
94
+ },
95
+ async load(key, validate) {
96
+ const response = await fetch(new URL(`${key}.bin`, base), { cache: 'no-store' });
97
+ if (response.status === 404)
98
+ return undefined;
99
+ if (!response.ok)
100
+ throw new Error(`vitecache load failed: HTTP ${response.status}`);
101
+ const contentType = response.headers.get('content-type')?.split(';')[0]?.trim();
102
+ if (contentType === 'text/html')
103
+ return undefined;
104
+ if (contentType !== 'application/octet-stream')
105
+ throw new Error(`Unexpected vitecache content type: ${contentType ?? 'missing'}`);
106
+ const bytes = new Uint8Array(await response.arrayBuffer());
107
+ if (!validate)
108
+ return { bytes };
109
+ const header = response.headers.get('X-Vitecache-Dependencies');
110
+ if (header === null)
111
+ throw new Error('Missing vitecache dependency metadata.');
112
+ const value = JSON.parse(decodeURIComponent(header));
113
+ return { bytes, hashes: hashes(value) };
114
+ },
115
+ async save(key, value, dependencies, expected) {
116
+ const response = await fetch(new URL(`${key}.bin`, base), {
117
+ method: 'PUT',
118
+ headers: {
119
+ 'Content-Type': 'application/octet-stream',
120
+ 'X-Vitecache-Dependencies': encodeURIComponent(JSON.stringify(dependencies)),
121
+ 'X-Vitecache-Hashes': encodeURIComponent(JSON.stringify(expected)),
122
+ },
123
+ body: new Uint8Array(value),
124
+ });
125
+ if (!response.ok)
126
+ throw new Error(`vitecache save failed: HTTP ${response.status}`);
127
+ },
128
+ };
129
+ }
130
+ /** A persistent binary computation result. Pass a browser basePath matching the public cache URL, including any Vite base. */
131
+ export class CacheEntry extends Entry {
132
+ constructor(options) {
133
+ super(options, createBackend(options));
134
+ }
135
+ }
136
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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","sourcesContent":["import { hashes, paths, record, resolvedDependencies } from './dependencies.js';\nimport { Entry } from './entry.js';\nimport type { Backend, CacheEntryOptions, Dependency, ResolvedDependency } from './types.js';\nexport type { CacheEntryOptions, Dependency, Dependencies, JsonValue } from './types.js';\n\nfunction createBackend(options: CacheEntryOptions): Backend {\n const base = new URL(`${(options.basePath ?? '/_vitecache/').replace(/\\/+$/, '')}/`, globalThis.location.href);\n let discovery: Promise<boolean> | undefined;\n\n async function discover(): Promise<boolean> {\n const response = await fetch(new URL('__vitecache', base), { cache: 'no-store' });\n if (response.status === 404) return false;\n if (!response.ok) throw new Error(`vitecache discovery failed: HTTP ${response.status}`);\n // Static SPA servers may return index.html for unknown paths.\n if (!response.headers.get('content-type')?.includes('application/json')) return false;\n const data: unknown = await response.json();\n return typeof data === 'object' && data !== null && 'vitecache' in data && data.vitecache === 1;\n }\n\n function plugin(): Promise<boolean> {\n discovery ??= discover().catch((error: unknown) => {\n discovery = undefined;\n throw error;\n });\n return discovery;\n }\n\n return {\n canSave: plugin,\n async resolve(dependencies) {\n const locators = Object.entries(dependencies).map(([name, locator]): [string, Dependency] => {\n if (locator.url === undefined) return [name, locator];\n const url = new URL(locator.url, globalThis.location.href);\n if (url.origin !== new URL(globalThis.location.href).origin) throw new Error('URL dependencies must be served by the same Vite project origin.');\n return [name, { url: url.href }];\n });\n if (locators.every(([, locator]) => locator.url === undefined)) {\n return resolvedDependencies(Object.fromEntries(locators));\n }\n if (await plugin()) {\n const response = await fetch(new URL('__vitecache/resolve', base), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ dependencies: Object.fromEntries(locators) }),\n cache: 'no-store',\n });\n if (!response.ok) throw new Error(`vitecache URL resolution failed: HTTP ${response.status}`);\n const data: unknown = await response.json();\n const resolved = resolvedDependencies(record(data).dependencies);\n if (Object.keys(resolved).length !== locators.length || !locators.every(([name]) => Object.hasOwn(resolved, name))) {\n throw new Error('Invalid vitecache resolved dependencies.');\n }\n return resolved;\n }\n const manifestUrl = new URL('__vitecache/paths.json', base);\n const response = await fetch(manifestUrl, { cache: 'no-store' });\n if (!response.ok || !response.headers.get('content-type')?.includes('application/json')) {\n throw new Error('URL dependencies require the vitecache Vite plugin during development and build. Missing production URL mapping.');\n }\n const data: unknown = await response.json();\n const manifest = record(data);\n const mapping = paths(manifest.paths);\n if (typeof manifest.root !== 'string' || !/^(?:\\.\\.\\/)+$/.test(manifest.root)) throw new Error('Invalid vitecache production mapping root.');\n const projectRoot = new URL(manifest.root, manifestUrl);\n const mapped = Object.fromEntries(Object.entries(mapping).map(([url, path]) => [new URL(url, projectRoot).pathname, path]));\n const resolved = locators.map(([name, locator]): [string, ResolvedDependency] => {\n if (locator.url === undefined) return [name, locator];\n const pathname = new URL(locator.url).pathname;\n const path = mapped[pathname];\n if (path === undefined) throw new Error(`No vitecache source file mapping for URL: ${pathname}`);\n return [name, { path }];\n });\n return resolvedDependencies(Object.fromEntries(resolved));\n },\n async snapshot(dependencies) {\n if (!await plugin()) return undefined;\n const response = await fetch(new URL('__vitecache/snapshot', base), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ dependencies }),\n cache: 'no-store',\n });\n if (!response.ok) throw new Error(`vitecache dependency validation failed: HTTP ${response.status}`);\n const data: unknown = await response.json();\n if (typeof data !== 'object' || data === null || !('hashes' in data)) {\n throw new TypeError('Invalid vitecache dependency response.');\n }\n return data.hashes === null ? null : hashes(data.hashes);\n },\n async load(key, validate) {\n const response = await fetch(new URL(`${key}.bin`, base), { cache: 'no-store' });\n if (response.status === 404) return undefined;\n if (!response.ok) throw new Error(`vitecache load failed: HTTP ${response.status}`);\n const contentType = response.headers.get('content-type')?.split(';')[0]?.trim();\n if (contentType === 'text/html') return undefined;\n if (contentType !== 'application/octet-stream') throw new Error(`Unexpected vitecache content type: ${contentType ?? 'missing'}`);\n const bytes = new Uint8Array(await response.arrayBuffer());\n if (!validate) return { bytes };\n const header = response.headers.get('X-Vitecache-Dependencies');\n if (header === null) throw new Error('Missing vitecache dependency metadata.');\n const value: unknown = JSON.parse(decodeURIComponent(header));\n return { bytes, hashes: hashes(value) };\n },\n async save(key, value, dependencies, expected) {\n const response = await fetch(new URL(`${key}.bin`, base), {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/octet-stream',\n 'X-Vitecache-Dependencies': encodeURIComponent(JSON.stringify(dependencies)),\n 'X-Vitecache-Hashes': encodeURIComponent(JSON.stringify(expected)),\n },\n body: new Uint8Array(value),\n });\n if (!response.ok) throw new Error(`vitecache save failed: HTTP ${response.status}`);\n },\n };\n}\n\n/** A persistent binary computation result. Pass a browser basePath matching the public cache URL, including any Vite base. */\nexport class CacheEntry extends Entry {\n constructor(options: CacheEntryOptions) {\n super(options, createBackend(options));\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { Dependencies, Hashes, JsonValue, Paths, ResolvedDependencies } from './types.js';
2
+ export declare function record(value: unknown): Record<string, unknown>;
3
+ export declare function paths(value: unknown): Paths;
4
+ /** Validate and snapshot JSON before asynchronous dependency resolution starts. */
5
+ export declare function json(value: unknown, ancestors?: Set<object>): JsonValue;
6
+ export declare function dependencies(value: unknown): Dependencies;
7
+ export declare function resolvedDependencies(value: unknown): ResolvedDependencies;
8
+ export declare function hashes(value: unknown): Hashes;
9
+ export declare function equalHashes(first: Hashes, second: Hashes): boolean;
@@ -0,0 +1,118 @@
1
+ export function record(value) {
2
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
3
+ throw new TypeError('Expected a named dependency record.');
4
+ }
5
+ const prototype = Object.getPrototypeOf(value);
6
+ if (prototype !== Object.prototype && prototype !== null)
7
+ throw new TypeError('Expected a plain dependency record.');
8
+ if (Object.getOwnPropertySymbols(value).length)
9
+ throw new TypeError('Dependency names must be strings.');
10
+ return Object.fromEntries(Object.keys(value).sort().map((name) => {
11
+ const property = Object.getOwnPropertyDescriptor(value, name);
12
+ if (!property || !('value' in property))
13
+ throw new TypeError('Dependency getters are not supported.');
14
+ const item = property.value;
15
+ return [name, item];
16
+ }));
17
+ }
18
+ function path(value) {
19
+ if (typeof value !== 'string' || !value || value.includes('\0'))
20
+ throw new TypeError('File dependencies require nonempty paths.');
21
+ if (/^(?:[\\/]|[a-z]:|[a-z][a-z\d+.-]*:\/\/)/i.test(value)) {
22
+ throw new TypeError('File dependency paths must be relative to the project root.');
23
+ }
24
+ const parts = [];
25
+ for (const part of value.replace(/\\/g, '/').split('/')) {
26
+ if (!part || part === '.')
27
+ continue;
28
+ if (part !== '..') {
29
+ parts.push(part);
30
+ continue;
31
+ }
32
+ if (!parts.length)
33
+ throw new TypeError('File dependency paths must stay inside the project root.');
34
+ parts.pop();
35
+ }
36
+ if (!parts.length)
37
+ throw new TypeError('File dependencies must identify files, not the project root.');
38
+ return parts.join('/');
39
+ }
40
+ export function paths(value) {
41
+ return Object.freeze(Object.fromEntries(Object.entries(record(value)).map(([name, value]) => {
42
+ if (!name)
43
+ throw new TypeError('File dependencies require nonempty names.');
44
+ return [name, path(value)];
45
+ })));
46
+ }
47
+ /** Validate and snapshot JSON before asynchronous dependency resolution starts. */
48
+ export function json(value, ancestors = new Set()) {
49
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
50
+ return value;
51
+ if (typeof value === 'number' && Number.isFinite(value))
52
+ return value;
53
+ if (typeof value !== 'object')
54
+ throw new TypeError('Dependency values must contain only JSON values.');
55
+ if (ancestors.has(value))
56
+ throw new TypeError('Dependency values must not contain cycles.');
57
+ ancestors.add(value);
58
+ try {
59
+ if (!Array.isArray(value))
60
+ return Object.freeze(Object.fromEntries(Object.entries(record(value)).map(([name, item]) => [name, json(item, ancestors)])));
61
+ if (Object.getOwnPropertySymbols(value).length)
62
+ throw new TypeError('Dependency values must not contain symbol properties.');
63
+ const items = [];
64
+ for (let index = 0; index < value.length; index++) {
65
+ const property = Object.getOwnPropertyDescriptor(value, index);
66
+ if (!property || !('value' in property))
67
+ throw new TypeError('Dependency values must not contain sparse arrays or getters.');
68
+ const item = property.value;
69
+ items.push(json(item, ancestors));
70
+ }
71
+ return Object.freeze(items);
72
+ }
73
+ finally {
74
+ ancestors.delete(value);
75
+ }
76
+ }
77
+ export function dependencies(value) {
78
+ const entries = Object.entries(record(value)).map(([name, value]) => {
79
+ if (!name)
80
+ throw new TypeError('Dependencies require nonempty names.');
81
+ const source = record(value);
82
+ if (Object.keys(source).length !== 1)
83
+ throw new TypeError('Each dependency must have exactly one value, path, url, or package.');
84
+ if (Object.hasOwn(source, 'value'))
85
+ return [name, Object.freeze({ value: json(source.value) })];
86
+ if (Object.hasOwn(source, 'path'))
87
+ return [name, Object.freeze({ path: path(source.path) })];
88
+ if (Object.hasOwn(source, 'url') && typeof source.url === 'string' && source.url && !source.url.includes('\0')) {
89
+ return [name, Object.freeze({ url: source.url })];
90
+ }
91
+ if (Object.hasOwn(source, 'package') && typeof source.package === 'string' && /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(source.package)) {
92
+ return [name, Object.freeze({ package: source.package })];
93
+ }
94
+ throw new TypeError('Each dependency must have a JSON value, root-relative path, nonempty URL, or package name without a subpath.');
95
+ });
96
+ return Object.freeze(Object.fromEntries(entries));
97
+ }
98
+ export function resolvedDependencies(value) {
99
+ const entries = Object.entries(dependencies(value)).map(([name, dependency]) => {
100
+ if (dependency.url !== undefined)
101
+ throw new TypeError('Expected resolved dependencies, not URLs.');
102
+ return [name, dependency];
103
+ });
104
+ return Object.freeze(Object.fromEntries(entries));
105
+ }
106
+ export function hashes(value) {
107
+ return Object.freeze(Object.fromEntries(Object.entries(record(value)).map(([name, hash]) => {
108
+ if (!name || typeof hash !== 'string' || !/^[a-f0-9]{64}$/.test(hash)) {
109
+ throw new TypeError('Invalid file dependency hash.');
110
+ }
111
+ return [name, hash];
112
+ })));
113
+ }
114
+ export function equalHashes(first, second) {
115
+ const names = Object.keys(first);
116
+ return names.length === Object.keys(second).length && names.every((name) => first[name] === second[name]);
117
+ }
118
+ //# sourceMappingURL=dependencies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../src/dependencies.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Dependencies, Dependency, Hashes, JsonValue, Paths, ResolvedDependencies, ResolvedDependency } from './types.js';\n\nexport function record(value: unknown): Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new TypeError('Expected a named dependency record.');\n }\n const prototype: unknown = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) throw new TypeError('Expected a plain dependency record.');\n if (Object.getOwnPropertySymbols(value).length) throw new TypeError('Dependency names must be strings.');\n return Object.fromEntries(Object.keys(value).sort().map((name) => {\n const property = Object.getOwnPropertyDescriptor(value, name);\n if (!property || !('value' in property)) throw new TypeError('Dependency getters are not supported.');\n const item: unknown = property.value;\n return [name, item];\n }));\n}\n\nfunction path(value: unknown): string {\n if (typeof value !== 'string' || !value || value.includes('\\0')) throw new TypeError('File dependencies require nonempty paths.');\n if (/^(?:[\\\\/]|[a-z]:|[a-z][a-z\\d+.-]*:\\/\\/)/i.test(value)) {\n throw new TypeError('File dependency paths must be relative to the project root.');\n }\n const parts: string[] = [];\n for (const part of value.replace(/\\\\/g, '/').split('/')) {\n if (!part || part === '.') continue;\n if (part !== '..') {\n parts.push(part);\n continue;\n }\n if (!parts.length) throw new TypeError('File dependency paths must stay inside the project root.');\n parts.pop();\n }\n if (!parts.length) throw new TypeError('File dependencies must identify files, not the project root.');\n return parts.join('/');\n}\n\nexport function paths(value: unknown): Paths {\n return Object.freeze(Object.fromEntries(Object.entries(record(value)).map(([name, value]) => {\n if (!name) throw new TypeError('File dependencies require nonempty names.');\n return [name, path(value)];\n })));\n}\n\n/** Validate and snapshot JSON before asynchronous dependency resolution starts. */\nexport function json(value: unknown, ancestors = new Set<object>()): JsonValue {\n if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value !== 'object') throw new TypeError('Dependency values must contain only JSON values.');\n if (ancestors.has(value)) throw new TypeError('Dependency values must not contain cycles.');\n ancestors.add(value);\n try {\n if (!Array.isArray(value)) return Object.freeze(Object.fromEntries(Object.entries(record(value)).map(([name, item]) => [name, json(item, ancestors)])));\n if (Object.getOwnPropertySymbols(value).length) throw new TypeError('Dependency values must not contain symbol properties.');\n const items: JsonValue[] = [];\n for (let index = 0; index < value.length; index++) {\n const property = Object.getOwnPropertyDescriptor(value, index);\n if (!property || !('value' in property)) throw new TypeError('Dependency values must not contain sparse arrays or getters.');\n const item: unknown = property.value;\n items.push(json(item, ancestors));\n }\n return Object.freeze(items);\n } finally {\n ancestors.delete(value);\n }\n}\n\nexport function dependencies(value: unknown): Dependencies {\n const entries = Object.entries(record(value)).map(([name, value]): [string, Dependency] => {\n if (!name) throw new TypeError('Dependencies require nonempty names.');\n const source = record(value);\n if (Object.keys(source).length !== 1) throw new TypeError('Each dependency must have exactly one value, path, url, or package.');\n if (Object.hasOwn(source, 'value')) return [name, Object.freeze({ value: json(source.value) })];\n if (Object.hasOwn(source, 'path')) return [name, Object.freeze({ path: path(source.path) })];\n if (Object.hasOwn(source, 'url') && typeof source.url === 'string' && source.url && !source.url.includes('\\0')) {\n return [name, Object.freeze({ url: source.url })];\n }\n if (Object.hasOwn(source, 'package') && typeof source.package === 'string' && /^(?:@[a-z0-9][a-z0-9._-]*\\/)?[a-z0-9][a-z0-9._-]*$/i.test(source.package)) {\n return [name, Object.freeze({ package: source.package })];\n }\n throw new TypeError('Each dependency must have a JSON value, root-relative path, nonempty URL, or package name without a subpath.');\n });\n return Object.freeze(Object.fromEntries(entries));\n}\n\nexport function resolvedDependencies(value: unknown): ResolvedDependencies {\n const entries = Object.entries(dependencies(value)).map(([name, dependency]): [string, ResolvedDependency] => {\n if (dependency.url !== undefined) throw new TypeError('Expected resolved dependencies, not URLs.');\n return [name, dependency];\n });\n return Object.freeze(Object.fromEntries(entries));\n}\n\nexport function hashes(value: unknown): Hashes {\n return Object.freeze(Object.fromEntries(Object.entries(record(value)).map(([name, hash]) => {\n if (!name || typeof hash !== 'string' || !/^[a-f0-9]{64}$/.test(hash)) {\n throw new TypeError('Invalid file dependency hash.');\n }\n return [name, hash];\n })));\n}\n\nexport function equalHashes(first: Hashes, second: Hashes): boolean {\n const names = Object.keys(first);\n return names.length === Object.keys(second).length && names.every((name) => first[name] === second[name]);\n}\n"]}
package/dist/disk.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { CacheEntryOptions } from './types.js';
2
+ export declare function missing(error: unknown): boolean;
3
+ export declare function missingDependency(error: unknown): boolean;
4
+ export declare function location(options: Pick<CacheEntryOptions, 'root' | 'basePath'>): {
5
+ root: string;
6
+ directory: string;
7
+ };
8
+ export declare function ignore(root: string, directory: string): Promise<void>;
9
+ export declare function load(path: string): Promise<Uint8Array | undefined>;
10
+ export declare function save(path: string, value: Uint8Array, beforeCommit?: () => Promise<void>): Promise<void>;
package/dist/disk.js ADDED
@@ -0,0 +1,71 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { appendFile, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
3
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
+ export function missing(error) {
5
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
6
+ }
7
+ export function missingDependency(error) {
8
+ return missing(error) || (error instanceof Error && 'code' in error && error.code === 'ENOTDIR');
9
+ }
10
+ export function location(options) {
11
+ const root = resolve(options.root ?? process.cwd());
12
+ return { root, directory: resolve(root, options.basePath ?? 'public/_vitecache') };
13
+ }
14
+ const pending = new Map();
15
+ export function ignore(root, directory) {
16
+ const key = `${root}\0${directory}`;
17
+ const existing = pending.get(key);
18
+ if (existing)
19
+ return existing;
20
+ const result = updateIgnore(root, directory).finally(() => pending.delete(key));
21
+ pending.set(key, result);
22
+ return result;
23
+ }
24
+ async function updateIgnore(root, directory) {
25
+ const path = relative(root, directory);
26
+ if (!path || path === '..' || path.startsWith(`..${sep}`) || isAbsolute(path))
27
+ return;
28
+ const entry = `${path.split(sep).join('/').replace(/[!*?[\]# ]/g, '\\$&')}/*`;
29
+ const file = join(root, '.gitignore');
30
+ await mkdir(root, { recursive: true });
31
+ let contents = '';
32
+ try {
33
+ contents = await readFile(file, 'utf8');
34
+ }
35
+ catch (error) {
36
+ if (!missing(error))
37
+ throw error;
38
+ }
39
+ if (contents.split(/\r?\n/).includes(entry))
40
+ return;
41
+ await appendFile(file, `${contents && !contents.endsWith('\n') ? '\n' : ''}${entry}\n`);
42
+ }
43
+ export async function load(path) {
44
+ try {
45
+ return await readFile(path);
46
+ }
47
+ catch (error) {
48
+ if (!missing(error))
49
+ throw error;
50
+ return undefined;
51
+ }
52
+ }
53
+ export async function save(path, value, beforeCommit) {
54
+ await mkdir(dirname(path), { recursive: true });
55
+ const temporary = `${path}.${randomUUID()}.tmp`;
56
+ try {
57
+ await writeFile(temporary, value, { flag: 'wx' });
58
+ await beforeCommit?.();
59
+ await rename(temporary, path);
60
+ }
61
+ finally {
62
+ try {
63
+ await unlink(temporary);
64
+ }
65
+ catch (error) {
66
+ if (!missing(error))
67
+ throw error;
68
+ }
69
+ }
70
+ }
71
+ //# sourceMappingURL=disk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disk.js","sourceRoot":"","sources":["../src/disk.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { appendFile, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\nimport type { CacheEntryOptions } from './types.js';\n\nexport function missing(error: unknown): boolean {\n return error instanceof Error && 'code' in error && error.code === 'ENOENT';\n}\n\nexport function missingDependency(error: unknown): boolean {\n return missing(error) || (error instanceof Error && 'code' in error && error.code === 'ENOTDIR');\n}\n\nexport function location(options: Pick<CacheEntryOptions, 'root' | 'basePath'>): { root: string; directory: string } {\n const root = resolve(options.root ?? process.cwd());\n return { root, directory: resolve(root, options.basePath ?? 'public/_vitecache') };\n}\n\nconst pending = new Map<string, Promise<void>>();\n\nexport function ignore(root: string, directory: string): Promise<void> {\n const key = `${root}\\0${directory}`;\n const existing = pending.get(key);\n if (existing) return existing;\n const result = updateIgnore(root, directory).finally(() => pending.delete(key));\n pending.set(key, result);\n return result;\n}\n\nasync function updateIgnore(root: string, directory: string): Promise<void> {\n const path = relative(root, directory);\n if (!path || path === '..' || path.startsWith(`..${sep}`) || isAbsolute(path)) return;\n const entry = `${path.split(sep).join('/').replace(/[!*?[\\]# ]/g, '\\\\$&')}/*`;\n const file = join(root, '.gitignore');\n await mkdir(root, { recursive: true });\n let contents = '';\n try {\n contents = await readFile(file, 'utf8');\n } catch (error) {\n if (!missing(error)) throw error;\n }\n if (contents.split(/\\r?\\n/).includes(entry)) return;\n await appendFile(file, `${contents && !contents.endsWith('\\n') ? '\\n' : ''}${entry}\\n`);\n}\n\nexport async function load(path: string): Promise<Uint8Array | undefined> {\n try {\n return await readFile(path);\n } catch (error) {\n if (!missing(error)) throw error;\n return undefined;\n }\n}\n\nexport async function save(path: string, value: Uint8Array, beforeCommit?: () => Promise<void>): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const temporary = `${path}.${randomUUID()}.tmp`;\n try {\n await writeFile(temporary, value, { flag: 'wx' });\n await beforeCommit?.();\n await rename(temporary, path);\n } finally {\n try {\n await unlink(temporary);\n } catch (error) {\n if (!missing(error)) throw error;\n }\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import type { Backend, CacheEntryOptions } 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 or stale results. 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): Promise<void>;
11
+ }
package/dist/entry.js ADDED
@@ -0,0 +1,81 @@
1
+ import { dependencies, equalHashes } 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
+ /** Checks validity like load(). Warns on browser misses without the Vite plugin. */
31
+ async has() {
32
+ const found = await this.load() !== undefined;
33
+ if (!found && this.#baseline === undefined) {
34
+ console.warn(`vitecache: cache miss for ${(await this.#resolve()).key}. Install the vitecache Vite plugin to generate cache files.`);
35
+ }
36
+ return found;
37
+ }
38
+ /** Returns undefined for missing or stale results. A miss captures dependency hashes until the next save attempt finishes. */
39
+ async load() {
40
+ const resolved = await this.#resolve();
41
+ const snapshot = await this.#backend.snapshot(resolved.validation);
42
+ if (!this.#pending) {
43
+ this.#baseline = snapshot;
44
+ this.#prepared = true;
45
+ }
46
+ if (snapshot === null) {
47
+ this.#pending = true;
48
+ return undefined;
49
+ }
50
+ const stored = await this.#backend.load(resolved.key, snapshot !== undefined);
51
+ if (!stored || (snapshot !== undefined && (!stored.hashes || !equalHashes(snapshot, stored.hashes)))) {
52
+ this.#pending = true;
53
+ return undefined;
54
+ }
55
+ return stored.bytes;
56
+ }
57
+ /** 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. */
58
+ async save(value) {
59
+ const bytes = new Uint8Array(value);
60
+ const expected = this.#baseline;
61
+ try {
62
+ if (!await this.#backend.canSave()) {
63
+ console.warn('vitecache: save skipped. Install the vitecache Vite plugin to write cache files from the browser.');
64
+ return;
65
+ }
66
+ if (this.#validates && !this.#prepared) {
67
+ throw new Error('Call CacheEntry.load() or has() before computing a result with file or package dependencies.');
68
+ }
69
+ if (expected === null)
70
+ throw new Error('Cannot save a cache entry with missing file or package dependencies.');
71
+ const resolved = await this.#resolve();
72
+ await this.#backend.save(resolved.key, bytes, resolved.validation, expected ?? {});
73
+ }
74
+ finally {
75
+ this.#prepared = false;
76
+ this.#pending = false;
77
+ this.#baseline = undefined;
78
+ }
79
+ }
80
+ }
81
+ //# sourceMappingURL=entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAG/B,MAAM,OAAO,KAAK;IACP,QAAQ,CAAS;IAC1B,WAAW,CAAyE;IAC3E,aAAa,CAAe;IAC5B,UAAU,CAAU;IACpB,QAAQ,CAAU;IAC3B,SAAS,CAA4B;IACrC,SAAS,GAAG,KAAK,CAAC;IAClB,QAAQ,GAAG,KAAK,CAAC;IAEjB,YAAY,OAA0B,EAAE,OAAgB;QACtD,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE,MAAM,IAAI,SAAS,CAAC,yCAAyC,CAAC,CAAC;QAC5H,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAC9D,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QAC9G,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,QAAQ;QACN,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;YACrF,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC;YACrC,UAAU,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;SAC7H,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC3B,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;YAC7B,MAAM,KAAK,CAAC;QACd,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,oFAAoF;IACpF,KAAK,CAAC,GAAG;QACP,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC;QAC9C,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,6BAA6B,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,8DAA8D,CAAC,CAAC;QACvI,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,8HAA8H;IAC9H,KAAK,CAAC,IAAI;QACR,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACnE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;YAC1B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;QACD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC;QAC9E,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACrG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,wMAAwM;IACxM,KAAK,CAAC,IAAI,CAAC,KAAiB;QAC1B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnC,OAAO,CAAC,IAAI,CAAC,mGAAmG,CAAC,CAAC;gBAClH,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACvC,MAAM,IAAI,KAAK,CAAC,8FAA8F,CAAC,CAAC;YAClH,CAAC;YACD,IAAI,QAAQ,KAAK,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;YAC/G,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;YACvC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,UAAU,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;QACrF,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC7B,CAAC;IACH,CAAC;CACF","sourcesContent":["import { dependencies, equalHashes } from './dependencies.js';\nimport { key } from './key.js';\nimport type { Backend, CacheEntryOptions, Dependencies, Hashes, ResolvedDependencies } 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: Hashes | null | 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 /** 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 && this.#baseline === undefined) {\n console.warn(`vitecache: cache miss for ${(await this.#resolve()).key}. Install the vitecache Vite plugin to generate cache files.`);\n }\n return found;\n }\n\n /** Returns undefined for missing or stale results. A miss captures dependency hashes until the next save attempt finishes. */\n async load(): Promise<Uint8Array | undefined> {\n const resolved = await this.#resolve();\n const snapshot = await this.#backend.snapshot(resolved.validation);\n if (!this.#pending) {\n this.#baseline = snapshot;\n this.#prepared = true;\n }\n if (snapshot === null) {\n this.#pending = true;\n return undefined;\n }\n const stored = await this.#backend.load(resolved.key, snapshot !== undefined);\n if (!stored || (snapshot !== undefined && (!stored.hashes || !equalHashes(snapshot, 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): Promise<void> {\n const bytes = new Uint8Array(value);\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 === null) throw new Error('Cannot save a cache entry with missing file or package dependencies.');\n const resolved = await this.#resolve();\n await this.#backend.save(resolved.key, bytes, resolved.validation, expected ?? {});\n } finally {\n this.#prepared = false;\n this.#pending = false;\n this.#baseline = undefined;\n }\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export { CacheEntry } from './browser.js';
2
+ export type { CacheEntryOptions, Dependency, Dependencies, JsonValue } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { CacheEntry } from './browser.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC","sourcesContent":["export { CacheEntry } from './browser.js';\nexport type { CacheEntryOptions, Dependency, Dependencies, JsonValue } from './types.js';\n"]}
package/dist/key.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { ResolvedDependencies } from './types.js';
2
+ export declare function digest(bytes: Uint8Array): string;
3
+ export declare function key(version: string, dependencies: ResolvedDependencies): string;