UNPKG

@trifrost/core

Version:

Blazingly fast, runtime-agnostic server framework for modern edge and node environments

142 lines (141 loc) 4.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MemoryRateLimit = exports.MemoryCache = exports.MemoryStore = exports.MemoryStoreAdapter = void 0; const array_1 = require("@valkyriestudios/utils/array"); const number_1 = require("@valkyriestudios/utils/number"); const _Cache_1 = require("../modules/Cache/_Cache"); const _RateLimit_1 = require("../modules/RateLimit/_RateLimit"); const _Storage_1 = require("./_Storage"); class MemoryStoreAdapter { #store = new Map(); /* Garbage collection interval */ #gc = null; /* Used for lru (least-recently-used) tracking */ #lru = new Set(); /* Set to the max amount of items allowed in our store if configured */ #lruMax = null; constructor(opts) { /* Configure garbage collection interval */ const filter = typeof opts?.gc_filter === 'function' ? opts.gc_filter : (_key, _v, _now, _exp) => _exp <= _now; if ((0, number_1.isIntGt)(opts?.gc_interval, 0)) { this.#gc = setInterval(() => { const now = Date.now(); for (const [key, entry] of this.#store.entries()) { if (filter(key, entry.value, now, entry.expires)) { this.#store.delete(key); if (this.isLRU) this.#lru.delete(key); } } }, opts.gc_interval); } /* Set max usage value if max entries is provided */ if ((0, number_1.isIntGt)(opts?.max_items, 0)) this.#lruMax = opts.max_items; } get isLRU() { return this.#lruMax !== null; } async get(key) { const val = this.#store.get(key); if (!val) return null; if (this.isLRU) this.#lru.delete(key); if (Date.now() > val.expires) { this.#store.delete(key); return null; } else { if (this.isLRU) this.#lru.add(key); return val.value; } } async set(key, value, ttl) { if (this.isLRU) { /* Mark as most recently used in LRU */ this.#lru.delete(key); this.#lru.add(key); /* Evict if above size */ if (this.#lru.size > this.#lruMax) { const to_evict = this.#lru.values().next().value; if (to_evict) { this.#store.delete(to_evict); this.#lru.delete(to_evict); } } } this.#store.set(key, { value, expires: Date.now() + ttl * 1000 }); } async del(key) { this.#store.delete(key); if (this.isLRU) this.#lru.delete(key); } async delPrefixed(prefix) { for (const k of this.#store.keys()) { if (k.startsWith(prefix)) { this.#store.delete(k); if (this.isLRU) this.#lru.delete(k); } } } async stop() { if (!this.#gc) return; clearInterval(this.#gc); this.#gc = null; } } exports.MemoryStoreAdapter = MemoryStoreAdapter; /** * MARK: Store */ class MemoryStore extends _Storage_1.Store { constructor(opts) { super('MemoryStore', new MemoryStoreAdapter(opts)); } } exports.MemoryStore = MemoryStore; /** * MARK: Cache */ class MemoryCache extends _Cache_1.TriFrostCache { constructor(cfg) { super({ store: new _Storage_1.Store('MemoryCache', new MemoryStoreAdapter({ gc_interval: (0, number_1.isIntGt)(cfg?.gc_interval, 0) ? cfg?.gc_interval : 60_000, ...(cfg?.max_items !== null && { max_items: (0, number_1.isIntGt)(cfg?.max_items, 0) ? cfg.max_items : 1_000 }), })), }); } } exports.MemoryCache = MemoryCache; /** * MARK: RateLimit */ class MemoryRateLimit extends _RateLimit_1.TriFrostRateLimit { constructor(cfg) { const window = (0, number_1.isIntGt)(cfg?.window, 0) ? cfg.window : 60_000; let adapter; if (cfg?.strategy === 'sliding') { adapter = new MemoryStoreAdapter({ gc_interval: (0, number_1.isIntGt)(cfg?.gc_interval, 0) ? cfg.gc_interval : 60_000, gc_filter: (_, timestamps, now) => (0, array_1.isNeArray)(timestamps) && timestamps[timestamps.length - 1] < now - window, }); } else { adapter = new MemoryStoreAdapter({ gc_interval: (0, number_1.isIntGt)(cfg?.gc_interval, 0) ? cfg.gc_interval : 60_000, gc_filter: (_, value, now) => value.reset <= now, }); } super({ ...(cfg || {}), store: new _Storage_1.Store('MemoryRateLimit', adapter), }); } } exports.MemoryRateLimit = MemoryRateLimit;