vitecache 0.2.0 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { relative, resolve, sep } from 'node:path';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport * as disk from '../disk.js';\nimport { ChangedDependenciesError, createStorage } from '../storage.js';\nimport type { Backend } from '../types.js';\nimport { dependencies, hashes, resolvedDependencies } from '../dependencies.js';\nimport { confine, inside, OutsideRootError, resolveDependencies } from './resolve.js';\nimport { assetUrl, publicPaths } from './manifest.js';\n\nexport interface VitecacheOptions {\n /** Cache directory relative to the Vite project root; must be inside publicDir. */\n basePath?: string;\n}\n\nclass RequestError extends Error {\n constructor(readonly status: number, message: string) {\n super(message);\n }\n}\n\nfunction status(error: unknown): number {\n if (error instanceof RequestError) return error.status;\n if (error instanceof ChangedDependenciesError) return 409;\n if (error instanceof OutsideRootError) return 403;\n if (error instanceof TypeError || error instanceof URIError) return 400;\n return 500;\n}\n\nfunction allowed(request: IncomingMessage): boolean {\n if (request.headers['sec-fetch-site'] === 'cross-site') return false;\n const origin = request.headers.origin;\n if (!origin) return true;\n try {\n const url = new URL(origin);\n return (url.protocol === 'http:' || url.protocol === 'https:') && url.host === request.headers.host;\n } catch {\n return false;\n }\n}\n\nasync function body(request: IncomingMessage): Promise<Uint8Array> {\n const chunks: Uint8Array[] = [];\n for await (const value of request) {\n const chunk: unknown = value;\n if (!Buffer.isBuffer(chunk)) throw new RequestError(400, 'Expected a binary request body');\n chunks.push(chunk);\n }\n return Buffer.concat(chunks);\n}\n\nfunction parse(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n throw new RequestError(400, 'Expected valid JSON');\n }\n}\n\nfunction validate<T>(value: unknown, parser: (value: unknown) => T): T {\n try {\n return parser(value);\n } catch (error) {\n if (!(error instanceof TypeError)) throw error;\n throw new RequestError(400, error.message);\n }\n}\n\nfunction header(request: IncomingMessage, name: string): unknown {\n const value = request.headers[name];\n if (typeof value !== 'string') throw new RequestError(400, `Missing ${name} header`);\n try {\n return parse(decodeURIComponent(value));\n } catch (error) {\n if (!(error instanceof URIError)) throw error;\n throw new RequestError(400, 'Invalid encoded request header');\n }\n}\n\nfunction json(response: ServerResponse, value: unknown): void {\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(value));\n}\n\nasync function respond(\n request: IncomingMessage,\n response: ServerResponse,\n name: string,\n storage: Backend,\n server: ViteDevServer,\n): Promise<void> {\n const root = server.config.root;\n response.setHeader('Cache-Control', 'no-store');\n if (name === '__vitecache' && request.method === 'GET') {\n json(response, { vitecache: 1 });\n return;\n }\n if ((name === '__vitecache/snapshot' || name === '__vitecache/resolve') && request.method === 'POST') {\n if (!allowed(request)) throw new RequestError(403, 'Cross-origin requests are forbidden');\n const value = parse(new TextDecoder().decode(await body(request)));\n if (typeof value !== 'object' || value === null || !('dependencies' in value)) {\n throw new RequestError(400, 'Expected dependencies');\n }\n if (name === '__vitecache/resolve') {\n const deps = validate(value.dependencies, dependencies);\n const origin = `${server.config.server.https ? 'https' : 'http'}://${request.headers.host}`;\n json(response, { dependencies: await resolveDependencies(server, deps, origin) });\n return;\n }\n const deps = validate(value.dependencies, resolvedDependencies);\n await confine(root, deps);\n json(response, { hashes: await storage.snapshot(deps) });\n return;\n }\n if (!/^[a-f0-9]{64}\\.bin$/.test(name)) {\n response.statusCode = 404;\n response.end();\n return;\n }\n const key = name.slice(0, -4);\n if (request.method === 'PUT') {\n if (!allowed(request)) throw new RequestError(403, 'Cross-origin requests are forbidden');\n const deps = validate(header(request, 'x-vitecache-dependencies'), resolvedDependencies);\n const expected = validate(header(request, 'x-vitecache-hashes'), hashes);\n await confine(root, deps);\n await storage.save(key, await body(request), deps, expected);\n response.statusCode = 204;\n response.end();\n return;\n }\n if (request.method !== 'GET' && request.method !== 'HEAD') {\n response.statusCode = 405;\n response.setHeader('Allow', 'GET, HEAD, PUT');\n response.end();\n return;\n }\n const stored = await storage.load(key, true);\n if (stored === undefined) {\n response.statusCode = 404;\n response.end();\n return;\n }\n response.setHeader('Content-Type', 'application/octet-stream');\n response.setHeader('Content-Length', stored.bytes.byteLength);\n response.setHeader('X-Vitecache-Dependencies', encodeURIComponent(JSON.stringify(stored.hashes)));\n response.end(request.method === 'HEAD' ? undefined : stored.bytes);\n}\n\n/** Persist and validate entries in development; preserve asset URL mappings in production builds. */\nexport function vitecache(options: VitecacheOptions = {}): Plugin {\n let resolved: { backend: Backend; root: string; prefix: string; directory: string; config: ResolvedConfig; cachePath: string } | undefined;\n return {\n name: 'vitecache',\n enforce: 'post',\n async configResolved(config) {\n if (!config.publicDir) throw new Error('vitecache requires Vite publicDir');\n const { root, directory } = disk.location({ root: config.root, ...options });\n const path = relative(config.publicDir, directory);\n if (!path || !inside(config.publicDir, directory)) {\n throw new Error('vitecache basePath must be a directory inside Vite publicDir');\n }\n const base = new URL(config.base, 'http://vitecache').pathname.replace(/\\/$/, '');\n resolved = {\n backend: createStorage({ root, ...options }),\n root,\n directory,\n config,\n cachePath: path.split(sep).join('/'),\n prefix: `${base}/${path.split(sep).map(encodeURIComponent).join('/')}/`,\n };\n await disk.ignore(root, directory);\n },\n async generateBundle(_options, bundle) {\n const storage = resolved;\n if (!storage) throw new Error('vitecache configuration was not resolved');\n const paths = new Map(Object.entries(await publicPaths(storage.config, storage.directory)));\n for (const asset of Object.values(bundle)) {\n if (asset.type !== 'asset') continue;\n const originals = [...new Set(asset.originalFileNames)];\n if (originals.length !== 1) continue;\n const original = originals[0];\n if (!original) continue;\n const file = relative(storage.root, resolve(storage.root, original));\n if (!inside(storage.root, resolve(storage.root, original))) continue;\n paths.set(assetUrl(asset.fileName), file.split(sep).join('/'));\n }\n const manifest = { paths: Object.fromEntries(paths), root: '../'.repeat(storage.cachePath.split('/').length + 1) };\n this.emitFile({ type: 'asset', fileName: `${storage.cachePath}/__vitecache/paths.json`, source: JSON.stringify(manifest) });\n },\n configureServer(server) {\n const storage = resolved;\n if (!storage) throw new Error('vitecache configuration was not resolved');\n server.middlewares.use((request, response, next) => {\n const path = new URL(request.url ?? '/', 'http://vitecache').pathname;\n if (!path.startsWith(storage.prefix)) {\n next();\n return;\n }\n void respond(request, response, path.slice(storage.prefix.length), storage.backend, server).catch((error: unknown) => {\n response.statusCode = status(error);\n response.setHeader('Content-Type', 'text/plain');\n response.end(error instanceof Error ? error.message : 'vitecache request failed');\n });\n });\n },\n };\n}\n\nexport default vitecache;\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/vite/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { relative, resolve, sep } from 'node:path';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport * as disk from '../disk.js';\nimport { createStorage } from '../storage.js';\nimport type { Storage } from '../storage.js';\nimport { ChangedDependenciesError, dependencies, hashableDependencies, hashes, MissingDependencyError } from '../dependencies.js';\nimport { confine, RequestError, resolveDependencies } from './resolve.js';\nimport { assetUrl, publicPaths } from './manifest.js';\n\nexport interface VitecacheOptions {\n /** Cache directory relative to the Vite project root; must be inside publicDir. */\n basePath?: string;\n}\n\nfunction status(error: unknown): number {\n if (error instanceof RequestError) return error.status;\n if (error instanceof ChangedDependenciesError) return 409;\n return 500;\n}\n\nfunction allowed(request: IncomingMessage): boolean {\n if (request.headers['sec-fetch-site'] === 'cross-site') return false;\n const origin = request.headers.origin;\n if (!origin) return true;\n try {\n const url = new URL(origin);\n return (url.protocol === 'http:' || url.protocol === 'https:') && url.host === request.headers.host;\n } catch {\n return false;\n }\n}\n\nasync function body(request: IncomingMessage): Promise<Uint8Array> {\n const chunks: Uint8Array[] = [];\n for await (const value of request) {\n const chunk: unknown = value;\n if (!Buffer.isBuffer(chunk)) throw new RequestError(400, 'Expected a binary request body');\n chunks.push(chunk);\n }\n return Buffer.concat(chunks);\n}\n\nfunction parse(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n throw new RequestError(400, 'Expected valid JSON');\n }\n}\n\nfunction parseRequest<T>(value: unknown, parser: (value: unknown) => T): T {\n try {\n return parser(value);\n } catch (error) {\n if (!(error instanceof TypeError)) throw error;\n throw new RequestError(400, error.message);\n }\n}\n\n/** A save body: a 4-byte big-endian metadata length, that much JSON metadata, then the cached bytes. */\nfunction upload(bytes: Uint8Array): { metadata: Record<string, unknown>; value: Uint8Array } {\n if (bytes.byteLength < 4) throw new RequestError(400, 'Expected a metadata length');\n const end = 4 + new DataView(bytes.buffer, bytes.byteOffset, 4).getUint32(0);\n if (end > bytes.byteLength) throw new RequestError(400, 'Truncated save metadata');\n const metadata = parse(new TextDecoder().decode(bytes.subarray(4, end)));\n if (typeof metadata !== 'object' || metadata === null || !('checked' in metadata) || !('expected' in metadata) || !('saved' in metadata)) {\n throw new RequestError(400, 'Expected checked, expected, and saved');\n }\n return { metadata, value: bytes.subarray(end) };\n}\n\n/** The `dependencies` of a same-origin POST's JSON body. */\nasync function posted(request: IncomingMessage): Promise<unknown> {\n if (!allowed(request)) throw new RequestError(403, 'Cross-origin requests are forbidden');\n const value = parse(new TextDecoder().decode(await body(request)));\n if (typeof value !== 'object' || value === null || !('dependencies' in value)) throw new RequestError(400, 'Expected dependencies');\n return value.dependencies;\n}\n\nfunction sendJson(response: ServerResponse, value: unknown): void {\n response.setHeader('Content-Type', 'application/json');\n response.end(JSON.stringify(value));\n}\n\nasync function respond(\n request: IncomingMessage,\n response: ServerResponse,\n name: string,\n storage: Storage,\n server: ViteDevServer,\n): Promise<void> {\n const root = server.config.root;\n response.setHeader('Cache-Control', 'no-store');\n if (name === '__vitecache' && request.method === 'GET') {\n sendJson(response, { vitecache: 1 });\n return;\n }\n if (name === '__vitecache/resolve' && request.method === 'POST') {\n const deps = parseRequest(await posted(request), dependencies);\n const origin = `${server.config.server.https ? 'https' : 'http'}://${request.headers.host}`;\n sendJson(response, { dependencies: await resolveDependencies(server, deps, origin) });\n return;\n }\n if (name === '__vitecache/snapshot' && request.method === 'POST') {\n const deps = parseRequest(await posted(request), hashableDependencies);\n await confine(root, deps);\n sendJson(response, await storage.snapshot(deps));\n return;\n }\n if (!/^[a-f0-9]{64}\\.bin$/.test(name)) {\n response.statusCode = 404;\n response.end();\n return;\n }\n const key = name.slice(0, -4);\n if (request.method === 'PUT') {\n if (!allowed(request)) throw new RequestError(403, 'Cross-origin requests are forbidden');\n const { metadata, value } = upload(await body(request));\n const checked = parseRequest(metadata.checked, hashableDependencies);\n const expected = parseRequest(metadata.expected, hashes);\n const saved = parseRequest(metadata.saved, hashableDependencies);\n await confine(root, checked);\n await confine(root, saved);\n await storage.save(key, value, { checked, expected, saved });\n response.statusCode = 204;\n response.end();\n return;\n }\n if (request.method !== 'GET') {\n response.statusCode = 405;\n response.setHeader('Allow', 'GET, PUT');\n response.end();\n return;\n }\n const stored = await storage.load(key);\n if (stored === undefined) {\n // Browsers log every 404 fetch as an error, and a miss is an expected answer.\n response.statusCode = 204;\n response.end();\n return;\n }\n // Browsers keep the bytes and revalidate them on every load, so an unchanged entry costs a 304 rather than a download.\n response.setHeader('Cache-Control', 'no-cache');\n const tag = `\"${stored.tag}\"`;\n response.setHeader('ETag', tag);\n response.setHeader('X-Vitecache-Dependencies', encodeURIComponent(JSON.stringify(stored.hashes)));\n if (request.headers['if-none-match'] === tag) {\n response.statusCode = 304;\n response.end();\n return;\n }\n response.setHeader('Content-Type', 'application/octet-stream');\n response.setHeader('Content-Length', stored.bytes.byteLength);\n response.end(stored.bytes);\n}\n\n/** Persist and validate entries in development; preserve asset URL mappings in production builds. */\nexport function vitecache(options: VitecacheOptions = {}): Plugin {\n let state: { storage: Storage; root: string; prefix: string; directory: string; config: ResolvedConfig; cachePath: string } | undefined;\n return {\n name: 'vitecache',\n enforce: 'post',\n async configResolved(config) {\n if (!config.publicDir) throw new Error('vitecache requires Vite publicDir');\n const { root, directory } = disk.location({ root: config.root, ...options });\n const path = relative(config.publicDir, directory);\n if (!path || !disk.inside(config.publicDir, directory)) {\n throw new Error('vitecache basePath must be a directory inside Vite publicDir');\n }\n const base = new URL(config.base, 'http://vitecache').pathname.replace(/\\/$/, '');\n state = {\n storage: createStorage({ root, ...options }),\n root,\n directory,\n config,\n cachePath: path.split(sep).join('/'),\n prefix: `${base}/${path.split(sep).map(encodeURIComponent).join('/')}/`,\n };\n await disk.ignore(root, directory);\n },\n async generateBundle(_options, bundle) {\n const resolved = state;\n if (!resolved) throw new Error('vitecache configuration was not resolved');\n const paths = new Map(Object.entries(await publicPaths(resolved.config, resolved.directory)));\n for (const asset of Object.values(bundle)) {\n if (asset.type !== 'asset') continue;\n const originals = [...new Set(asset.originalFileNames)];\n if (originals.length !== 1) continue;\n const original = originals[0];\n if (!original) continue;\n const file = resolve(resolved.root, original);\n if (!disk.inside(resolved.root, file)) continue;\n paths.set(assetUrl(asset.fileName), relative(resolved.root, file).split(sep).join('/'));\n }\n const manifest = { paths: Object.fromEntries(paths), root: '../'.repeat(resolved.cachePath.split('/').length + 1) };\n this.emitFile({ type: 'asset', fileName: `${resolved.cachePath}/__vitecache/paths.json`, source: JSON.stringify(manifest) });\n },\n configureServer(server) {\n const resolved = state;\n if (!resolved) throw new Error('vitecache configuration was not resolved');\n server.middlewares.use((request, response, next) => {\n const path = new URL(request.url ?? '/', 'http://vitecache').pathname;\n if (!path.startsWith(resolved.prefix)) {\n next();\n return;\n }\n void respond(request, response, path.slice(resolved.prefix.length), resolved.storage, server).catch((error: unknown) => {\n // A missing dependency is an expected answer, as in a snapshot: a body, and no error status for browsers to log.\n if (error instanceof MissingDependencyError) {\n sendJson(response, { status: 'missing', message: error.message });\n return;\n }\n response.statusCode = status(error);\n response.setHeader('Content-Type', 'text/plain');\n response.end(error instanceof Error ? error.message : 'vitecache request failed');\n });\n });\n },\n };\n}\n\nexport default vitecache;\n"]}
@@ -1,6 +1,6 @@
1
1
  import { readdir } from 'node:fs/promises';
2
2
  import { relative, resolve, sep } from 'node:path';
3
- import { inside } from './resolve.js';
3
+ import * as disk from '../disk.js';
4
4
  export function assetUrl(file) {
5
5
  return file.split('/').map(encodeURIComponent).join('/');
6
6
  }
@@ -12,13 +12,13 @@ export async function publicPaths(config, cacheDirectory) {
12
12
  entries = await readdir(directory, { withFileTypes: true });
13
13
  }
14
14
  catch (error) {
15
- if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
15
+ if (disk.notFound(error))
16
16
  return;
17
17
  throw error;
18
18
  }
19
19
  for (const entry of entries) {
20
20
  const file = resolve(directory, entry.name);
21
- if (inside(cacheDirectory, file))
21
+ if (disk.inside(cacheDirectory, file))
22
22
  continue;
23
23
  if (entry.isDirectory())
24
24
  await visit(file);
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/vite/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAsB,EAAE,cAAsB;IAC9E,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,KAAK,UAAU,KAAK,CAAC,SAAiB;QACpC,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO;YACjF,MAAM,KAAK,CAAC;QACd,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC;gBAAE,SAAS;YAC3C,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;iBACtC,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBACxB,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACzE,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACvF,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC9B,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC","sourcesContent":["import { readdir } from 'node:fs/promises';\nimport { relative, resolve, sep } from 'node:path';\nimport type { ResolvedConfig } from 'vite';\nimport { inside } from './resolve.js';\n\nexport function assetUrl(file: string): string {\n return file.split('/').map(encodeURIComponent).join('/');\n}\n\nexport async function publicPaths(config: ResolvedConfig, cacheDirectory: string): Promise<Record<string, string>> {\n const paths: [string, string][] = [];\n async function visit(directory: string): Promise<void> {\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return;\n throw error;\n }\n for (const entry of entries) {\n const file = resolve(directory, entry.name);\n if (inside(cacheDirectory, file)) continue;\n if (entry.isDirectory()) await visit(file);\n else if (entry.isFile()) {\n const publicPath = relative(config.publicDir, file).split(sep).join('/');\n paths.push([assetUrl(publicPath), relative(config.root, file).split(sep).join('/')]);\n }\n }\n }\n await visit(config.publicDir);\n return Object.fromEntries(paths);\n}\n"]}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/vite/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AAEnC,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAsB,EAAE,cAAsB;IAC9E,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,KAAK,UAAU,KAAK,CAAC,SAAiB;QACpC,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO;YACjC,MAAM,KAAK,CAAC;QACd,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,CAAC;gBAAE,SAAS;YAChD,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;iBACtC,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBACxB,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACzE,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACvF,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC9B,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC","sourcesContent":["import { readdir } from 'node:fs/promises';\nimport { relative, resolve, sep } from 'node:path';\nimport type { ResolvedConfig } from 'vite';\nimport * as disk from '../disk.js';\n\nexport function assetUrl(file: string): string {\n return file.split('/').map(encodeURIComponent).join('/');\n}\n\nexport async function publicPaths(config: ResolvedConfig, cacheDirectory: string): Promise<Record<string, string>> {\n const paths: [string, string][] = [];\n async function visit(directory: string): Promise<void> {\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch (error) {\n if (disk.notFound(error)) return;\n throw error;\n }\n for (const entry of entries) {\n const file = resolve(directory, entry.name);\n if (disk.inside(cacheDirectory, file)) continue;\n if (entry.isDirectory()) await visit(file);\n else if (entry.isFile()) {\n const publicPath = relative(config.publicDir, file).split(sep).join('/');\n paths.push([assetUrl(publicPath), relative(config.root, file).split(sep).join('/')]);\n }\n }\n }\n await visit(config.publicDir);\n return Object.fromEntries(paths);\n}\n"]}
@@ -1,8 +1,9 @@
1
1
  import type { ViteDevServer } from 'vite';
2
- import type { Dependencies, ResolvedDependencies } from '../types.js';
3
- export declare class OutsideRootError extends Error {
4
- constructor();
2
+ import type { Dependencies, Resolved } from '../types.js';
3
+ /** A request the plugin refuses, answered with `status` and the message. */
4
+ export declare class RequestError extends Error {
5
+ readonly status: number;
6
+ constructor(status: number, message: string);
5
7
  }
6
- export declare function inside(root: string, path: string): boolean;
7
- export declare function confine(root: string, dependencies: ResolvedDependencies): Promise<void>;
8
- export declare function resolveDependencies(server: ViteDevServer, dependencies: Dependencies, origin: string): Promise<ResolvedDependencies>;
8
+ export declare function confine(root: string, dependencies: Dependencies<Resolved>): Promise<void>;
9
+ export declare function resolveDependencies(server: ViteDevServer, dependencies: Dependencies, origin: string): Promise<Dependencies<Resolved>>;
@@ -1,14 +1,17 @@
1
1
  import { realpath, stat } from 'node:fs/promises';
2
- import { isAbsolute, relative, resolve, sep } from 'node:path';
2
+ import { isAbsolute, resolve } from 'node:path';
3
3
  import * as disk from '../disk.js';
4
- export class OutsideRootError extends Error {
5
- constructor() {
6
- super('File dependencies must be inside the project root');
4
+ import { MissingDependencyError } from '../dependencies.js';
5
+ /** A request the plugin refuses, answered with `status` and the message. */
6
+ export class RequestError extends Error {
7
+ status;
8
+ constructor(status, message) {
9
+ super(message);
10
+ this.status = status;
7
11
  }
8
12
  }
9
- export function inside(root, path) {
10
- const name = relative(root, path);
11
- return name !== '..' && !name.startsWith(`..${sep}`) && !isAbsolute(name);
13
+ function outsideRoot() {
14
+ return new RequestError(403, 'File dependencies must be inside the project root');
12
15
  }
13
16
  export async function confine(root, dependencies) {
14
17
  const canonicalRoot = await realpath(root);
@@ -16,28 +19,36 @@ export async function confine(root, dependencies) {
16
19
  if (!('path' in dependency))
17
20
  continue;
18
21
  const path = resolve(root, dependency.path);
19
- if (!inside(root, path))
20
- throw new OutsideRootError();
22
+ if (!disk.inside(root, path))
23
+ throw outsideRoot();
21
24
  try {
22
- if (!inside(canonicalRoot, await realpath(path)))
23
- throw new OutsideRootError();
25
+ if (!disk.inside(canonicalRoot, await realpath(path)))
26
+ throw outsideRoot();
24
27
  }
25
28
  catch (error) {
26
- if (!disk.missingDependency(error))
29
+ if (!disk.notFound(error))
27
30
  throw error;
28
31
  }
29
32
  }
30
33
  }
31
- async function exists(path) {
34
+ async function isFile(path) {
32
35
  try {
33
36
  return (await stat(path)).isFile();
34
37
  }
35
38
  catch (error) {
36
- if (!disk.missingDependency(error))
39
+ if (!disk.notFound(error))
37
40
  throw error;
38
41
  return false;
39
42
  }
40
43
  }
44
+ function decoded(pathname) {
45
+ try {
46
+ return decodeURIComponent(pathname);
47
+ }
48
+ catch {
49
+ throw new RequestError(400, `Malformed file dependency URL: ${pathname}`);
50
+ }
51
+ }
41
52
  export async function resolveDependencies(server, dependencies, origin) {
42
53
  const entries = [];
43
54
  const base = new URL(server.config.base, origin).pathname;
@@ -48,31 +59,29 @@ export async function resolveDependencies(server, dependencies, origin) {
48
59
  }
49
60
  const url = new URL(dependency.url, origin);
50
61
  if (url.origin !== origin)
51
- throw new TypeError('File dependency URLs must have the Vite server origin');
62
+ throw new RequestError(400, 'File dependency URLs must have the Vite server origin');
52
63
  if (!url.pathname.startsWith(base))
53
- throw new TypeError('File dependency URL is outside the Vite base');
54
- const pathname = decodeURIComponent(`/${url.pathname.slice(base.length)}`);
64
+ throw new RequestError(400, 'File dependency URL is outside the Vite base');
65
+ const pathname = decoded(`/${url.pathname.slice(base.length)}`);
55
66
  const publicFile = resolve(server.config.publicDir, `.${pathname}`);
56
67
  const rootFile = resolve(server.config.root, `.${pathname}`);
57
68
  let file;
58
- if (server.config.publicDir && await exists(publicFile))
69
+ if (await isFile(publicFile))
59
70
  file = publicFile;
60
71
  else if (pathname.startsWith('/@fs/'))
61
72
  file = pathname.slice('/@fs'.length).replace(/^\/([A-Za-z]:\/)/, '$1');
62
- else if (await exists(rootFile))
73
+ else if (await isFile(rootFile))
63
74
  file = rootFile;
64
75
  else {
65
76
  const module = await server.pluginContainer.resolveId(pathname);
77
+ // A virtual module is served without a file whose contents could be hashed.
66
78
  if (!module || !isAbsolute(module.id))
67
- throw new TypeError(`Cannot resolve file dependency URL: ${dependency.url}`);
79
+ throw new MissingDependencyError(`File dependency URL has no backing file: ${dependency.url}`);
68
80
  file = module.id.split('?')[0] ?? module.id;
69
81
  }
70
- if (!await exists(file))
71
- throw new TypeError(`File dependency URL has no backing file: ${dependency.url}`);
72
- const path = inside(server.config.root, file)
73
- ? relative(server.config.root, file)
74
- : relative(await realpath(server.config.root), await realpath(file));
75
- entries.push([name, { path: path.split(sep).join('/') }]);
82
+ if (!await isFile(file))
83
+ throw new MissingDependencyError(`File dependency URL has no backing file: ${dependency.url}`);
84
+ entries.push([name, { path: await disk.rootRelative(server.config.root, file) }]);
76
85
  }
77
86
  const resolved = Object.fromEntries(entries);
78
87
  await confine(server.config.root, resolved);
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/vite/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["import { realpath, stat } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\nimport type { ViteDevServer } from 'vite';\nimport type { Dependencies, ResolvedDependencies, ResolvedDependency } from '../types.js';\nimport * as disk from '../disk.js';\n\nexport class OutsideRootError extends Error {\n constructor() {\n super('File dependencies must be inside the project root');\n }\n}\n\nexport function inside(root: string, path: string): boolean {\n const name = relative(root, path);\n return name !== '..' && !name.startsWith(`..${sep}`) && !isAbsolute(name);\n}\n\nexport async function confine(root: string, dependencies: ResolvedDependencies): Promise<void> {\n const canonicalRoot = await realpath(root);\n for (const dependency of Object.values(dependencies)) {\n if (!('path' in dependency)) continue;\n const path = resolve(root, dependency.path);\n if (!inside(root, path)) throw new OutsideRootError();\n try {\n if (!inside(canonicalRoot, await realpath(path))) throw new OutsideRootError();\n } catch (error) {\n if (!disk.missingDependency(error)) throw error;\n }\n }\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isFile();\n } catch (error) {\n if (!disk.missingDependency(error)) throw error;\n return false;\n }\n}\n\nexport async function resolveDependencies(server: ViteDevServer, dependencies: Dependencies, origin: string): Promise<ResolvedDependencies> {\n const entries: [string, ResolvedDependency][] = [];\n const base = new URL(server.config.base, origin).pathname;\n for (const [name, dependency] of Object.entries(dependencies)) {\n if (!('url' in dependency)) {\n entries.push([name, dependency]);\n continue;\n }\n const url = new URL(dependency.url, origin);\n if (url.origin !== origin) throw new TypeError('File dependency URLs must have the Vite server origin');\n if (!url.pathname.startsWith(base)) throw new TypeError('File dependency URL is outside the Vite base');\n const pathname = decodeURIComponent(`/${url.pathname.slice(base.length)}`);\n const publicFile = resolve(server.config.publicDir, `.${pathname}`);\n const rootFile = resolve(server.config.root, `.${pathname}`);\n let file: string;\n if (server.config.publicDir && await exists(publicFile)) file = publicFile;\n else if (pathname.startsWith('/@fs/')) file = pathname.slice('/@fs'.length).replace(/^\\/([A-Za-z]:\\/)/, '$1');\n else if (await exists(rootFile)) file = rootFile;\n else {\n const module = await server.pluginContainer.resolveId(pathname);\n if (!module || !isAbsolute(module.id)) throw new TypeError(`Cannot resolve file dependency URL: ${dependency.url}`);\n file = module.id.split('?')[0] ?? module.id;\n }\n if (!await exists(file)) throw new TypeError(`File dependency URL has no backing file: ${dependency.url}`);\n const path = inside(server.config.root, file)\n ? relative(server.config.root, file)\n : relative(await realpath(server.config.root), await realpath(file));\n entries.push([name, { path: path.split(sep).join('/') }]);\n }\n const resolved = Object.fromEntries(entries);\n await confine(server.config.root, resolved);\n return resolved;\n}\n"]}
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/vite/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["import { realpath, stat } from 'node:fs/promises';\nimport { isAbsolute, resolve } from 'node:path';\nimport type { ViteDevServer } from 'vite';\nimport type { Dependencies, Resolved } from '../types.js';\nimport * as disk from '../disk.js';\nimport { MissingDependencyError } from '../dependencies.js';\n\n/** A request the plugin refuses, answered with `status` and the message. */\nexport class RequestError extends Error {\n constructor(readonly status: number, message: string) {\n super(message);\n }\n}\n\nfunction outsideRoot(): RequestError {\n return new RequestError(403, 'File dependencies must be inside the project root');\n}\n\nexport async function confine(root: string, dependencies: Dependencies<Resolved>): Promise<void> {\n const canonicalRoot = await realpath(root);\n for (const dependency of Object.values(dependencies)) {\n if (!('path' in dependency)) continue;\n const path = resolve(root, dependency.path);\n if (!disk.inside(root, path)) throw outsideRoot();\n try {\n if (!disk.inside(canonicalRoot, await realpath(path))) throw outsideRoot();\n } catch (error) {\n if (!disk.notFound(error)) throw error;\n }\n }\n}\n\nasync function isFile(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isFile();\n } catch (error) {\n if (!disk.notFound(error)) throw error;\n return false;\n }\n}\n\nfunction decoded(pathname: string): string {\n try {\n return decodeURIComponent(pathname);\n } catch {\n throw new RequestError(400, `Malformed file dependency URL: ${pathname}`);\n }\n}\n\nexport async function resolveDependencies(server: ViteDevServer, dependencies: Dependencies, origin: string): Promise<Dependencies<Resolved>> {\n const entries: [string, Resolved][] = [];\n const base = new URL(server.config.base, origin).pathname;\n for (const [name, dependency] of Object.entries(dependencies)) {\n if (!('url' in dependency)) {\n entries.push([name, dependency]);\n continue;\n }\n const url = new URL(dependency.url, origin);\n if (url.origin !== origin) throw new RequestError(400, 'File dependency URLs must have the Vite server origin');\n if (!url.pathname.startsWith(base)) throw new RequestError(400, 'File dependency URL is outside the Vite base');\n const pathname = decoded(`/${url.pathname.slice(base.length)}`);\n const publicFile = resolve(server.config.publicDir, `.${pathname}`);\n const rootFile = resolve(server.config.root, `.${pathname}`);\n let file: string;\n if (await isFile(publicFile)) file = publicFile;\n else if (pathname.startsWith('/@fs/')) file = pathname.slice('/@fs'.length).replace(/^\\/([A-Za-z]:\\/)/, '$1');\n else if (await isFile(rootFile)) file = rootFile;\n else {\n const module = await server.pluginContainer.resolveId(pathname);\n // A virtual module is served without a file whose contents could be hashed.\n if (!module || !isAbsolute(module.id)) throw new MissingDependencyError(`File dependency URL has no backing file: ${dependency.url}`);\n file = module.id.split('?')[0] ?? module.id;\n }\n if (!await isFile(file)) throw new MissingDependencyError(`File dependency URL has no backing file: ${dependency.url}`);\n entries.push([name, { path: await disk.rootRelative(server.config.root, file) }]);\n }\n const resolved = Object.fromEntries(entries);\n await confine(server.config.root, resolved);\n return resolved;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitecache",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Persistent binary caching for expensive computation in browsers and Node.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,6 +31,10 @@
31
31
  "node": "./dist/node.js",
32
32
  "default": "./dist/browser.js"
33
33
  },
34
+ "./prune": {
35
+ "types": "./dist/prune.d.ts",
36
+ "default": "./dist/prune.js"
37
+ },
34
38
  "./vite": {
35
39
  "types": "./dist/vite/index.d.ts",
36
40
  "default": "./dist/vite/index.js"
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  name: vitecache
3
- description: Cache expensive computation with vitecache in browsers or Node, connect browser saves to a Vite development server, and reuse cached bytes in deployed apps.
3
+ description: Cache expensive computation with vitecache in browsers or Node, connect browser saves to a Vite development server, reuse cached bytes in deployed apps, and prune entries no longer used.
4
4
  ---
5
5
 
6
6
  # vitecache
7
7
 
8
- Use vitecache when recomputing a result costs more than reading its encoded bytes. The application owns encoding and decoding. Declare every input that affects the computation as a named dependency. Use `{ value }` for JSON data and `{ package: "elkjs" }` for an installed library. Package dependencies track the named package’s installed version, so declare transitive packages separately when their versions matter. Bump the computation version for changes beyond what the declared dependencies capture.
8
+ Use vitecache when recomputing a result costs more than reading its encoded bytes. The application owns encoding and decoding. Declare every input that affects the computation as a named dependency. Use `{ value }` for JSON data and `{ package: "layout-engine" }` for an installed library. Package dependencies track the named package’s installed version, so declare transitive packages separately when their versions matter. Bump the computation version for changes beyond what the declared dependencies capture, such as a linked workspace package changed without a new version.
9
9
 
10
10
  Give dependencies stable names. Use `{ path: "src/simplify.ts" }` for a project-root-relative path or `{ url: modelUrl }` when you have an asset URL served by the same project origin. Paths resolve from the Node `root` or the Vite project root, never from the importing module, page URL, or cache directory. The Vite plugin resolves served URLs to source paths; Node accepts local file URLs. Keep the plugin in the production build configuration when using served URLs so deployed entries can recover the same source paths. Production mappings cover public files and emitted assets with one backing source. Use `{ path }` when an imported asset is inlined or deduplicated from several source files.
11
11
 
12
- Reordering JSON object properties or dependency declarations does not change identity; array order matters. Changing a dependency name, JSON value, resolved path, or package name does. File contents and installed package versions invalidate the existing result when filesystem validation is available; installation paths do not affect identity.
12
+ Reordering JSON object properties or dependency declarations does not change identity; array order matters. Changing a dependency name, JSON value, resolved path, or package name does. File contents and installed package versions invalidate the existing result in Node and through the Vite plugin; installation paths do not affect identity.
13
13
 
14
- Load once and compute on a miss. Reuse that entry when saving: loading captures file contents and package versions before computation, allowing saving to reject a result if those dependencies changed in the meantime. A new entry or a save without a preceding load cannot establish that relationship. Entries containing only value dependencies can save directly. If saving fails because dependencies changed, load again and recompute before retrying. Use a separate entry for each concurrent computation.
14
+ Load once and compute on a miss. Reuse that entry when saving: loading captures file contents and package versions before computation, allowing saving to reject a result if those dependencies changed in the meantime. A new entry or a save without a preceding load cannot establish that relationship. Entries containing only value dependencies can save directly. If saving rejects with `ChangedDependenciesError`, load again and recompute before retrying. Use a separate entry for each concurrent computation.
15
15
 
16
16
  ```ts
17
17
  import { CacheEntry } from "vitecache"
@@ -36,6 +36,18 @@ if (bytes === undefined) {
36
36
  const mesh = decodeMesh(bytes)
37
37
  ```
38
38
 
39
+ Constructor dependencies select which result an entry holds. When some inputs are only discovered while computing, such as the files a script imported or the textures a model referenced, pass them to `save(bytes, { dependencies })` instead, like a compiler's depfile. Their names stay out of the key, so generated names such as `file:${i}` are fine.
40
+
41
+ ```ts
42
+ const entry = new CacheEntry({ version: "bake@1", dependencies: { scene: { url: sceneUrl } } })
43
+ let bytes = await entry.load()
44
+ if (bytes === undefined) {
45
+ const { result, files } = await bake(sceneUrl) // files: URLs the bake read
46
+ bytes = encode(result)
47
+ await entry.save(bytes, { dependencies: Object.fromEntries(files.map((url, i) => [`file:${i}`, { url }])) })
48
+ }
49
+ ```
50
+
39
51
  Use the Vite plugin when browser computation should produce reusable project files:
40
52
 
41
53
  ```ts
@@ -45,6 +57,20 @@ import { vitecache } from "vitecache/vite"
45
57
  export default defineConfig({ plugins: [vitecache()] })
46
58
  ```
47
59
 
48
- Generate caches during development or from Node before building when deployed users should download precomputed results. Include the generated public files in the build or deployment even when Git ignores them. The plugin writes files during development; it does not supply a production write server. Browsers without the plugin assume files and package versions unchanged, so regenerate affected caches before deployment. Calling `save()` there warns and skips persistence.
60
+ Generate caches during development or from Node before building when deployed users should download precomputed results. Include the generated public files in the build or deployment even when Git ignores them. The plugin writes files during development; it does not supply a production write server. Browsers without the plugin assume files and package versions unchanged, saved dependencies included, so regenerate affected caches before deployment. The plugin answers a missing entry without an HTTP error, but a static host answers it with 404, which the browser console shows; each one marks a result the deployment lacks, so precompute what deployed users will request.
61
+
62
+ Browsers keep loaded entries in their HTTP cache and revalidate them before each use, because an entry's file name stays the same when its contents change. In deployments, serve the cache directory with `ETag` or `Last-Modified`, as most static hosts do, so unchanged entries cost a `304` instead of a download. Do not mark cache files immutable.
63
+
64
+ The cache directory keeps every entry ever saved, and the build deploys all of them. Loads and saves in Node or through the development server mark an entry as used; `prune` deletes entries not used since a time. Run it after a pass that loads everything the deployment needs, such as a precompute script:
65
+
66
+ ```ts
67
+ import { prune } from "vitecache/prune"
68
+
69
+ const start = new Date()
70
+ await precompute() // loads or saves every entry the app uses
71
+ await prune({ before: start })
72
+ ```
73
+
74
+ Entries that only browsers compute in development count as used only when a page loads them through the dev server, so prune with an older cutoff, such as a week ago, when no single pass loads everything.
49
75
 
50
76
  For custom locations, coordinate the filesystem destination with the URL used by browser callers. A filesystem path and a browser URL are different values. When deploying below a URL prefix, include that prefix in the browser cache path; the client does not infer Vite's base URL. Node jobs should select the same project root as the Vite app when launched from a different working directory.