/
githubmirror
/
angular-cli
Обзор
Документация
Войти
/
githubmirror
/
angular-cli
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/angular/build/src/tools/esbuild/sqlite-cache-store.ts
169 строк
5 KB
Alan Agius
perf(@angular/build): batch last_accessed updates in sqlite cache store
07 авг 2026, 17:53
07 авг 2026, 17:53
a55a6b7
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import { DatabaseSync, StatementSync } from 'node:sqlite'; import { Cache, PersistentCacheStore } from './cache'; export class SqliteCacheStore implements PersistentCacheStore<unknown> { #db: DatabaseSync | undefined; #getStmt: StatementSync | undefined; #hasStmt: StatementSync | undefined; #setStmt: StatementSync | undefined; #updateAccessedStmt: StatementSync | undefined; readonly #pendingAccessedKeys = new Set<string>(); #flushTimeout: NodeJS.Timeout | undefined; constructor( readonly cachePath: string, private readonly maxPayloadSize = 1024 * 1024 * 1024, private readonly ttlDays = 14, ) {} #ensureDb(): DatabaseSync { if (!this.#db) { this.#db = new DatabaseSync(this.cachePath); // Optimize SQLite for cache usage this.#db.exec('PRAGMA auto_vacuum = FULL;'); this.#db.exec('PRAGMA journal_mode = WAL;'); this.#db.exec('PRAGMA synchronous = NORMAL;'); this.#db.exec('PRAGMA busy_timeout = 5000;'); this.#db.exec('PRAGMA temp_store = MEMORY;'); this.#db.exec('PRAGMA mmap_size = 268435456;'); this.#db.exec( 'CREATE TABLE IF NOT EXISTS cache (key TEXT PRIMARY KEY, value TEXT, last_accessed INTEGER NOT NULL) WITHOUT ROWID;', ); this.#getStmt = this.#db.prepare('SELECT value FROM cache WHERE key = ?'); this.#hasStmt = this.#db.prepare('SELECT 1 FROM cache WHERE key = ?'); this.#setStmt = this.#db.prepare( 'INSERT OR REPLACE INTO cache (key, value, last_accessed) VALUES (?, ?, unixepoch())', ); this.#updateAccessedStmt = this.#db.prepare( 'UPDATE cache SET last_accessed = unixepoch() WHERE key = ?', ); } return this.#db; } #queueAccessUpdate(key: string): void { this.#pendingAccessedKeys.add(key); if (this.#pendingAccessedKeys.size >= 100) { this.#flushAccessUpdates(); } else if (!this.#flushTimeout) { this.#flushTimeout = setTimeout(() => this.#flushAccessUpdates(), 500); this.#flushTimeout.unref?.(); } } #flushAccessUpdates(): void { if (this.#flushTimeout) { clearTimeout(this.#flushTimeout); this.#flushTimeout = undefined; } if (!this.#db || this.#pendingAccessedKeys.size === 0 || !this.#updateAccessedStmt) { return; } try { this.#db.exec('BEGIN IMMEDIATE TRANSACTION;'); for (const key of this.#pendingAccessedKeys) { this.#updateAccessedStmt.run(key); } this.#db.exec('COMMIT;'); } catch { try { this.#db.exec('ROLLBACK;'); } catch { // Ignore rollback errors if transaction was not active } } finally { this.#pendingAccessedKeys.clear(); } } // eslint-disable-next-line @typescript-eslint/no-explicit-any async get(key: string): Promise<any> { this.#ensureDb(); const row = this.#getStmt?.get(key) as { value: string } | undefined; if (row) { this.#queueAccessUpdate(key); try { return JSON.parse(row.value); } catch { return undefined; } } return undefined; } has(key: string): boolean { this.#ensureDb(); return !!this.#hasStmt?.get(key); } async set(key: string, value: unknown): Promise<this> { this.#ensureDb(); this.#pendingAccessedKeys.delete(key); this.#setStmt?.run(key, JSON.stringify(value)); return this; } createCache<V = unknown>(namespace: string): Cache<V> { return new Cache(this, namespace); } close(): void { if (this.#db) { try { // Flush any pending access updates in one transaction before pruning this.#flushAccessUpdates(); // 1. Delete items older than N days this.#db .prepare("DELETE FROM cache WHERE last_accessed < unixepoch('now', ?);") .run(`-${this.ttlDays} days`); // 2. Prune oldest items if payload exceeds maxPayloadSize const pruneStmt = this.#db.prepare(` DELETE FROM cache WHERE key IN ( SELECT key FROM ( SELECT key, sum(length(key) + length(value)) OVER (ORDER BY last_accessed DESC, key DESC) as running_size FROM cache ) WHERE running_size > ? ); `); pruneStmt.run(this.maxPayloadSize); } catch { // Pruning errors should not block build success } finally { if (this.#flushTimeout) { clearTimeout(this.#flushTimeout); this.#flushTimeout = undefined; } this.#pendingAccessedKeys.clear(); this.#getStmt = undefined; this.#hasStmt = undefined; this.#setStmt = undefined; this.#updateAccessedStmt = undefined; this.#db.close(); this.#db = undefined; } } } }