Skip to content

Instantly share code, notes, and snippets.

@SparK-Cruz
Last active January 2, 2026 16:00
Show Gist options
  • Select an option

  • Save SparK-Cruz/d8e6c3b0bf04019c982fcf99a65d7d6e to your computer and use it in GitHub Desktop.

Select an option

Save SparK-Cruz/d8e6c3b0bf04019c982fcf99a65d7d6e to your computer and use it in GitHub Desktop.
Generic cache service singleton with variable TTL
const DEFAULT_CACHE_TTL = 5000;
const now = () => {
return (new Date()).getTime();
};
interface CacheStoreDictionary {
[key: string]: { loaded: number, row: any }
}
export class CacheService {
private static _instance: CacheService;
private _cache: { [storeKey: string]: CacheStoreDictionary } = {};
public constructor() {
if (CacheService._instance) {
return CacheService._instance;
}
CacheService._instance = this;
}
public getStore(storeKey: string, timeToLive: number = DEFAULT_CACHE_TTL): CacheStore {
this._cache[storeKey] ??= {};
return new CacheStore(this._cache[storeKey], timeToLive);
}
}
export class CacheStore {
public static DUMMY: CacheStore = new CacheStore(Object.freeze({}), 0);
private _ttl: number;
private _store: CacheStoreDictionary;
public constructor(store: CacheStoreDictionary, timeToLive: number) {
this._store = store;
this._ttl = timeToLive;
}
public async readOrAdd(key: string, provider: () => any): Promise<any> {
const cached = this.read(key);
if (cached) return cached;
const model = await provider();
this.add(key, model);
return model;
}
public add(key: string, model: any): void {
this._store[key] ??= { loaded: 0, row: null };
const item = this._store[key];
item.loaded = now();
item.row = model;
}
public read(key: string): any | undefined {
const item = this._store[key];
if (!item) return;
if (item.loaded + this._ttl < now()) {
this.remove(key);
return;
}
return item.row;
}
public remove(key: string): void {
delete this._store[key];
}
}
import { CacheService } from "./CacheService";
const MAX_SIMULATED_LATENCY = 300;
const ONE_SECOND = 1000;
const ONE_MINUTE = ONE_SECOND * 60;
const STORE_KEY = 'yourStoreKeyByContext';
const CACHE_KEY = 'yourValue';
const cacheService = new CacheService();
const cache = cacheService.getStore(STORE_KEY, ONE_SECOND);
const sleep = (seconds: number) => {
return new Promise((resolve, _) => {
setTimeout(resolve, seconds * ONE_SECOND);
});
};
const someAsyncOperationWhereTheValueMayChange = () => {
return new Promise((resolve, _) => {
setTimeout(() => {
resolve(Math.round(Math.random() * 999999999));
}, Math.round(MAX_SIMULATED_LATENCY * Math.random()));
});
};
console.log("Read 1:", await cache.readOrAdd(CACHE_KEY, async () => await someAsyncOperationWhereTheValueMayChange()));
console.log("Read 2:", await cache.readOrAdd(CACHE_KEY, async () => await someAsyncOperationWhereTheValueMayChange()));
console.log("Read 3:", await cache.readOrAdd(CACHE_KEY, async () => await someAsyncOperationWhereTheValueMayChange()));
await sleep(2);
console.log("Read 4:", await cache.readOrAdd(CACHE_KEY, async () => await someAsyncOperationWhereTheValueMayChange()));
console.log("Read 5:", await cache.readOrAdd(CACHE_KEY, async () => await someAsyncOperationWhereTheValueMayChange()));
console.log("Read 6:", await cache.readOrAdd(CACHE_KEY, async () => await someAsyncOperationWhereTheValueMayChange()));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment