UNPKG

@solomonai/cache

Version:

<div align="center"> <h1 align="center">@solomonai/cache</h1> <h5>Cache all the things</h5> </div>

263 lines (260 loc) 6.83 kB
import { Err, Ok, BaseError } from '@unkey/error'; import superjson from 'superjson'; // src/stores/cloudflare.ts var CacheError = class extends BaseError { name = "CacheError"; retry = false; tier; key; constructor(opts) { super(opts); this.name = "CacheError"; this.tier = opts.tier; this.key = opts.key; } }; // src/stores/cloudflare.ts var CloudflareStore = class { config; name = "cloudflare"; constructor(config) { this.config = config; } createCacheKey(namespace, key) { return new URL( `https://${this.config.domain}/cache/${this.config.cacheBuster ?? "v1"}/${String( namespace )}/${key}` ); } async get(namespace, key) { let res; try { res = await caches.default.match(new Request(this.createCacheKey(namespace, key))); } catch (err) { return Err( new CacheError({ tier: this.name, key, message: err.message }) ); } if (!res) { return Ok(void 0); } const raw = await res.text(); try { const entry = superjson.parse(raw); return Ok(entry); } catch (err) { return Err( new CacheError({ tier: this.name, key, message: err.message }) ); } } async set(namespace, key, entry) { const req = new Request(this.createCacheKey(namespace, key)); const res = new Response(superjson.stringify(entry), { headers: { "Content-Type": "application/json", "Cache-Control": `public, max-age=${Math.floor(entry.staleUntil / 1e3)}` } }); try { await caches.default.put(req, res); return Ok(); } catch (err) { return Err( new CacheError({ tier: this.name, key, message: err.message }) ); } } async remove(namespace, keys) { const cacheKeys = (Array.isArray(keys) ? keys : [keys]).map( (key) => this.createCacheKey(namespace, key) ); await Promise.all(cacheKeys.map((key) => caches.default.delete(key))); return await fetch( `https://api.cloudflare.com/client/v4zones/${this.config.zoneId}/purge_cache`, { method: "POST", headers: { Authorization: `Bearer ${this.config.cloudflareApiKey}`, "Content-Type": "application/json" }, body: JSON.stringify({ files: cacheKeys.map((k) => k.toString()) }) } ).then((res) => res.body?.cancel()).then(() => Ok()).catch( (err) => Err( new CacheError({ tier: this.name, key: keys.toString(), message: err.message }) ) ); } }; var MemoryStore = class { state; unstableEvictOnSet; name = "memory"; constructor(config) { this.state = config.persistentMap; this.unstableEvictOnSet = config.unstableEvictOnSet; } buildCacheKey(namespace, key) { return [namespace, key].join("::"); } async get(namespace, key) { const value = this.state.get(this.buildCacheKey(namespace, key)); if (!value) { return Promise.resolve(Ok(void 0)); } if (value.expires <= Date.now()) { await this.remove(namespace, key); } return Promise.resolve(Ok(value.entry)); } async set(namespace, key, entry) { this.state.set(this.buildCacheKey(namespace, key), { expires: entry.staleUntil, entry }); if (this.unstableEvictOnSet && Math.random() < this.unstableEvictOnSet.frequency) { const now = Date.now(); this.state.forEach((value, key2, map) => { if (value.expires < now) { map.delete(key2); } }); for (const [k] of this.state) { if (this.state.size <= this.unstableEvictOnSet.maxItems) { break; } this.state.delete(k); } } return Promise.resolve(Ok()); } async remove(namespace, keys) { const cacheKeys = (Array.isArray(keys) ? keys : [keys]).map( (key) => this.buildCacheKey(namespace, key).toString() ); for (const key of cacheKeys) { this.state.delete(key); } return Promise.resolve(Ok()); } }; var UpstashRedisStore = class { redis; name = "upstash-redis"; constructor(config) { this.redis = config.redis; } buildCacheKey(namespace, key) { return [namespace, key].join("::"); } async get(namespace, key) { const value = await this.redis.get(this.buildCacheKey(namespace, key)); if (!value) { return Ok(void 0); } return Ok(value); } async set(namespace, key, entry) { await this.redis.set(this.buildCacheKey(namespace, key), entry, { pxat: entry.staleUntil }); return Ok(); } async remove(namespace, keys) { const cacheKeys = (Array.isArray(keys) ? keys : [keys]).map( (key) => this.buildCacheKey(namespace, key).toString() ); this.redis.del(...cacheKeys); return Ok(); } }; var LibSQLStore = class { tableName; client; name = "libsql"; constructor(config) { this.client = config.client; this.tableName = config.tableName || "cache"; } buildCacheKey(namespace, key) { return `${namespace}::${key}`; } async get(namespace, key) { try { const result = await this.client.execute({ sql: `SELECT value, freshUntil, staleUntil FROM ${this.tableName} WHERE key = ?`, args: [this.buildCacheKey(namespace, key)] }); if (result.rows.length === 0) { return Ok(void 0); } const row = result.rows[0]; const entry = { value: JSON.parse(row.value), freshUntil: Number(row.freshUntil), staleUntil: Number(row.staleUntil) }; return Ok(entry); } catch { return Ok(void 0); } } async set(namespace, key, entry) { try { await this.client.execute({ sql: `INSERT OR REPLACE INTO ${this.tableName} (key, value, freshUntil, staleUntil) VALUES (?, ?, ?, ?)`, args: [ this.buildCacheKey(namespace, key), JSON.stringify(entry.value), entry.freshUntil, entry.staleUntil ] }); return Ok(); } catch { return Ok(); } } async remove(namespace, keys) { const cacheKeys = (Array.isArray(keys) ? keys : [keys]).map( (key) => this.buildCacheKey(namespace, key).toString() ); try { await Promise.all( cacheKeys.map( (cacheKey) => this.client.execute({ sql: `DELETE FROM ${this.tableName} WHERE key = ?`, args: [cacheKey] }) ) ); return Ok(); } catch { return Ok(); } } }; export { CloudflareStore, LibSQLStore, MemoryStore, UpstashRedisStore }; //# sourceMappingURL=stores.mjs.map //# sourceMappingURL=stores.mjs.map