// Implements an in-memory fixed-size cache for document contents, import type { VaultUpdateId } from "../persistence/database"; // Doubly-linked list node for O(1) LRU operations class LRUNode { public constructor( public key: VaultUpdateId, public value: Uint8Array, public prev: LRUNode | null = null, public next: LRUNode | null = null ) {} } // evicting the least recently used documents when the size limit is exceeded. export class FixedSizeDocumentCache { private readonly maxSizeInBytes: number; private currentSizeInBytes: number; private readonly cache: Map; private head: LRUNode | null; // Least recently used private tail: LRUNode | null; // Most recently used public constructor(maxSizeInBytes: number) { this.maxSizeInBytes = maxSizeInBytes; this.currentSizeInBytes = 0; this.cache = new Map(); this.head = null; this.tail = null; } public get(updateId: VaultUpdateId): Uint8Array | undefined { const node = this.cache.get(updateId); if (node) { this.moveToTail(node); return node.value; } return undefined; } public put(updateId: VaultUpdateId, content: Uint8Array): void { if (content.byteLength > this.maxSizeInBytes) { // Document is too large to fit in the cache return; } // If the document is already in the cache, update it const existingNode = this.cache.get(updateId); if (existingNode != null) { this.currentSizeInBytes -= existingNode.value.byteLength; this.removeNode(existingNode); this.cache.delete(updateId); } const newNode = new LRUNode(updateId, content); this.cache.set(updateId, newNode); this.addToTail(newNode); this.currentSizeInBytes += content.byteLength; // Evict least recently used documents if over size limit while (this.currentSizeInBytes > this.maxSizeInBytes && this.head) { const lruNode = this.head; this.removeNode(lruNode); this.cache.delete(lruNode.key); this.currentSizeInBytes -= lruNode.value.byteLength; } } public clear(): void { this.cache.clear(); this.head = null; this.tail = null; this.currentSizeInBytes = 0; } private removeNode(node: LRUNode): void { if (node.prev) { node.prev.next = node.next; } else { this.head = node.next; } if (node.next) { node.next.prev = node.prev; } else { this.tail = node.prev; } node.prev = null; node.next = null; } private addToTail(node: LRUNode): void { node.prev = this.tail; node.next = null; if (this.tail) { this.tail.next = node; } this.tail = node; this.head ??= node; } private moveToTail(node: LRUNode): void { if (node === this.tail) { return; } this.removeNode(node); this.addToTail(node); } }