1import { Cache, type CacheOptions } from '@wora/cache-persist';
2import { LRUCache } from 'lru-cache';
3
4export interface PersistedLRUOptions {
5 prefix?: string;
6 max: number;
7 ttl?: number;
8 persistOptions?: CacheOptions;
9}
10
11// eslint-disable-next-line @typescript-eslint/no-empty-object-type
12export class PersistedLRU<K extends string, V extends {}> {
13 private memory: LRUCache<K, V>;
14 private storage: Cache;
15 private signals: Map<K, ((data: V) => void)[]>;
16
17 private prefix = '';
18
19 constructor(opts: PersistedLRUOptions) {
20 this.memory = new LRUCache<K, V>({
21 max: opts.max,
22 ttl: opts.ttl
23 });
24 this.storage = new Cache(opts.persistOptions);
25 this.prefix = opts.prefix ? `${opts.prefix}%` : '';
26 this.signals = new Map();
27
28 this.init();
29 }
30
31 async init(): Promise<void> {
32 await this.storage.restore();
33
34 const state = this.storage.getState();
35 for (const [key, val] of Object.entries(state)) {
36 try {
37 // console.log('restoring', key);
38 const k = this.unprefix(key) as unknown as K;
39 const v = val as V;
40 this.memory.set(k, v);
41 } catch (err) {
42 console.warn('skipping invalid persisted entry', key, err);
43 }
44 }
45 }
46
47 get(key: K): V | undefined {
48 return this.memory.get(key);
49 }
50 getSignal(key: K): Promise<V> {
51 return new Promise<V>((resolve) => {
52 if (!this.signals.has(key)) {
53 this.signals.set(key, [resolve]);
54 return;
55 }
56 const signals = this.signals.get(key)!;
57 signals.push(resolve);
58 this.signals.set(key, signals);
59 });
60 }
61 set(key: K, value: V): void {
62 this.memory.set(key, value);
63 this.storage.set(this.prefixed(key), value);
64 for (const signal of this.signals.get(key) ?? []) {
65 signal(value);
66 }
67 this.storage.flush(); // TODO: uh evil and fucked up (this whole file is evil honestly)
68 }
69 has(key: K): boolean {
70 return this.memory.has(key);
71 }
72 delete(key: K): void {
73 this.memory.delete(key);
74 this.storage.delete(this.prefixed(key));
75 this.storage.flush();
76 }
77 clear(): void {
78 this.memory.clear();
79 this.storage.purge();
80 this.storage.flush();
81 }
82
83 private prefixed(key: K): string {
84 return this.prefix + key;
85 }
86 private unprefix(prefixed: string): string {
87 return prefixed.slice(this.prefix.length);
88 }
89}