UNPKG

typescript

Version:

TypeScript is a language for application scale JavaScript development

169 lines 6.62 kB
/** * Builds a composite ref key from a snapshot ID and project ID. */ function refKey(snapshotId, projectId) { return `${snapshotId}:${projectId}`; } /** * Client-side cache for source files keyed by (path, parseOptionsKey, contentHash). * * Supports multiple versions of the same file at the same path (e.g., from * different snapshots with different file contents). Each version is identified * by its content hash and parse options key. * * Entries are ref-counted by (snapshot, project) pairs. When a snapshot is * disposed, all refs for that snapshot across all projects are released, * and entries with no remaining references are evicted. * * When a new snapshot is created, unchanged cache entries from the previous * snapshot are retained per-project. Only files within changed or removed * projects are invalidated. */ export class SourceFileCache { /** Map from path to all cached versions of that file */ cache = new Map(); /** Map from snapshotId to (projectId → Set of paths fetched through that project) */ snapshotProjectPaths = new Map(); /** * Get a cached source file already retained for the given (snapshot, project) pair. * This does not require a content hash or parse options key — it returns the entry * if one exists with a matching ref. Used to skip the server request entirely when * retainForSnapshot has already carried over the ref. * * A given (snapshot, project) pair always parses a file the same way, so there is * at most one matching entry per ref. */ getRetained(path, snapshotId, projectId) { const entries = this.cache.get(path); if (!entries) return undefined; const key = refKey(snapshotId, projectId); const entry = entries.find(e => e.refs.has(key)); return entry?.file; } /** * Store a source file in the cache and retain it for the given (snapshot, project) pair. * Returns the cached file — which may be an existing entry if the hash matches. */ set(path, file, parseOptionsKey, contentHash, snapshotId, projectId) { let entries = this.cache.get(path); if (!entries) { entries = []; this.cache.set(path, entries); } const ref = refKey(snapshotId, projectId); // Check if we already have this exact version const existing = entries.find(e => e.parseOptionsKey === parseOptionsKey && e.contentHash === contentHash); if (existing) { existing.refs.add(ref); this.trackPath(snapshotId, projectId, path); return existing.file; } entries.push({ file, contentHash, parseOptionsKey, refs: new Set([ref]) }); this.trackPath(snapshotId, projectId, path); return file; } /** * Retain cache entries from a previous snapshot for a new snapshot. * For each project in the previous snapshot: * - Removed projects: skip (don't retain any refs). * - Changed projects: retain refs for files not listed in changedFiles/deletedFiles. * - Unchanged projects: retain all refs. */ retainForSnapshot(newSnapshotId, previousSnapshotId, changes) { const prevProjectMap = this.snapshotProjectPaths.get(previousSnapshotId); if (!prevProjectMap) return; const removedProjects = new Set(changes?.removedProjects ?? []); const changedProjects = changes?.changedProjects ?? {}; for (const [projectId, paths] of prevProjectMap) { if (removedProjects.has(projectId)) continue; const projectChanges = changedProjects[projectId]; let invalidPaths; if (projectChanges) { invalidPaths = new Set(); for (const p of projectChanges.changedFiles ?? []) invalidPaths.add(p); for (const p of projectChanges.deletedFiles ?? []) invalidPaths.add(p); } const prevRef = refKey(previousSnapshotId, projectId); const newRef = refKey(newSnapshotId, projectId); for (const path of paths) { if (invalidPaths?.has(path)) continue; const entries = this.cache.get(path); if (!entries) continue; for (const entry of entries) { if (entry.refs.has(prevRef)) { entry.refs.add(newRef); this.trackPath(newSnapshotId, projectId, path); } } } } } /** * Release all entries retained by the given snapshot across all projects. * Only visits paths that the snapshot actually referenced. * Entries with no remaining refs are evicted. */ releaseSnapshot(snapshotId) { const projectMap = this.snapshotProjectPaths.get(snapshotId); if (!projectMap) return; for (const [projectId, paths] of projectMap) { const key = refKey(snapshotId, projectId); for (const path of paths) { const entries = this.cache.get(path); if (!entries) continue; for (let i = entries.length - 1; i >= 0; i--) { entries[i].refs.delete(key); if (entries[i].refs.size === 0) { entries.splice(i, 1); } } if (entries.length === 0) { this.cache.delete(path); } } } this.snapshotProjectPaths.delete(snapshotId); } trackPath(snapshotId, projectId, path) { let projectMap = this.snapshotProjectPaths.get(snapshotId); if (!projectMap) { projectMap = new Map(); this.snapshotProjectPaths.set(snapshotId, projectMap); } let paths = projectMap.get(projectId); if (!paths) { paths = new Set(); projectMap.set(projectId, paths); } paths.add(path); } /** * Clear all entries from the cache. */ clear() { this.cache.clear(); this.snapshotProjectPaths.clear(); } /** * Get the number of unique paths in the cache. */ get size() { return this.cache.size; } /** * Check if a path is in the cache. */ has(path) { return this.cache.has(path); } } //# sourceMappingURL=sourceFileCache.js.map