diff options
| author | Alex Pooley (@zuedev) <zuedev@gmail.com> | 2026-07-22 15:48:31 +0100 |
|---|---|---|
| committer | Alex Pooley (@zuedev) <zuedev@gmail.com> | 2026-07-22 15:48:31 +0100 |
| commit | c6c9ad5d948a38c05e6a7d75097ca8a54c5a33ae (patch) | |
| tree | c3aba3cd5c4f4708ca0bac39a2504a9309f67156 /174bg/discord-bot/source/utilities | |
| parent | 72487019b3f255cece40a8073bbad8bc9968a3b7 (diff) | |
| download | unnamed-group-c6c9ad5d948a38c05e6a7d75097ca8a54c5a33ae.tar unnamed-group-c6c9ad5d948a38c05e6a7d75097ca8a54c5a33ae.tar.gz unnamed-group-c6c9ad5d948a38c05e6a7d75097ca8a54c5a33ae.tar.bz2 unnamed-group-c6c9ad5d948a38c05e6a7d75097ca8a54c5a33ae.tar.xz unnamed-group-c6c9ad5d948a38c05e6a7d75097ca8a54c5a33ae.zip | |
implement caching for uex
Diffstat (limited to '174bg/discord-bot/source/utilities')
| -rw-r--r-- | 174bg/discord-bot/source/utilities/cache.js | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/174bg/discord-bot/source/utilities/cache.js b/174bg/discord-bot/source/utilities/cache.js new file mode 100644 index 0000000..229a3d0 --- /dev/null +++ b/174bg/discord-bot/source/utilities/cache.js @@ -0,0 +1,66 @@ +/** + * Utility module for caching arbitrary JSON-serializable values to disk with an expiry, + * to avoid re-fetching data (e.g. from external APIs) more often than necessary. + * + * Cache entries are stored under `.cache/<namespace>/<hashed-key>.json` at the project root. + */ + +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { createHash } from "node:crypto"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const CACHE_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", ".cache"); + +function keyToFileName(key) { + return `${createHash("sha256").update(key).digest("hex")}.json`; +} + +async function readEntry(namespace, key) { + const filePath = path.join(CACHE_ROOT, namespace, keyToFileName(key)); + + try { + const { expiresAt, data } = JSON.parse(await readFile(filePath, "utf-8")); + + if (Date.now() >= expiresAt) return undefined; + + return data; + } catch { + // missing, unreadable, or corrupt cache entry - treat as a cache miss + return undefined; + } +} + +async function writeEntry(namespace, key, data, ttlMs) { + const dir = path.join(CACHE_ROOT, namespace); + + await mkdir(dir, { recursive: true }); + await writeFile( + path.join(dir, keyToFileName(key)), + JSON.stringify({ expiresAt: Date.now() + ttlMs, data }), + ); +} + +/** + * Returns the cached value for `key` within `namespace` if it exists and hasn't expired. + * Otherwise, calls `fetcher`, caches its result for `ttlMs` milliseconds, and returns it. + * @param {string} namespace - Subdirectory under `.cache` to group related entries (e.g. "uex"). + * @param {string} key - Unique identifier for this cached value within the namespace. + * @param {number} ttlMs - How long the cached value should remain valid, in milliseconds. + * @param {() => Promise<any>} fetcher - Called to produce a fresh value on a cache miss. + */ +export async function cached(namespace, key, ttlMs, fetcher) { + const existing = await readEntry(namespace, key); + if (existing !== undefined) return existing; + + const data = await fetcher(); + + // don't cache failed/empty lookups so they get retried on the next call + if (data !== null && data !== undefined) { + await writeEntry(namespace, key, data, ttlMs); + } + + return data; +} + +export default { cached }; |
