Files
core/src/core/cache.js
T
John Dvorak 717ae1031e initial commit: @arbiter/core authorization engine with js-rigor hardening
Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/
binary modes, condensed snapshots, value relations) with 39 rigor test
campaigns. Includes fixes for snapshot binary writer/reader format
mismatch (snapshot-of-snapshot corruption), possibility write-boundary
validation, empty-graph snapshot serialization, relation lookup cache
direction collision, config-redefinition cache invalidation, binary
threshold semantics, defeasible compiled routing, and comparator
reason whitelisting.
2026-07-31 13:44:06 -07:00

116 lines
3.6 KiB
JavaScript

// cache.js — bounded LRU cache with an injectable factory.
//
// Default implementation is a Map-based LRU (insertion-order eviction) that
// matches the surface the engine previously consumed from HyperbolicLRUCache:
// constructor(capacity, { onEvict }) + set / get / has / delete / clear / size()
// onEvict(key, value) fires both on capacity eviction and on explicit delete(),
// preserving the prior contract. Hyperbolic-specific options (sampleSize,
// sketchEpsilon, sketchDelta) are accepted and ignored.
//
// Dependency injection: Arbiter exposes `cacheFactory` (from options.cacheFactory,
// defaulting to `defaultCacheFactory` below). Every cache construction site calls
// its arbiter's cacheFactory, so a consumer can substitute any cache impl — e.g.
// `new Arbiter({ cacheFactory: (c, o) => new HyperbolicLRUCache(c, o) })` — with no
// optional imports and no call-site edits.
export class SimpleLRUCache {
constructor(capacity = 1024, options = {}) {
this.capacity = capacity;
this.onEvict = options.onEvict;
this._map = new Map();
}
// Current entry count. Exposed as a method to match HyperbolicLRUCache.size().
size() {
return this._map.size;
}
has(key) {
return this._map.has(key);
}
get(key) {
if (!this._map.has(key)) return undefined;
const value = this._map.get(key);
// Refresh recency: re-insert at the MRU end (Map preserves insertion order).
this._map.delete(key);
this._map.set(key, value);
return value;
}
set(key, value) {
if (this._map.has(key)) {
// Key exists: refresh recency by re-inserting.
this._map.delete(key);
} else if (this.capacity > 0 && this._map.size >= this.capacity) {
// At capacity: evict the least-recently-used (oldest) entry.
const oldestKey = this._map.keys().next().value;
const oldestValue = this._map.get(oldestKey);
this._map.delete(oldestKey);
this._fireOnEvict(oldestKey, oldestValue);
}
this._map.set(key, value);
}
delete(key) {
if (!this._map.has(key)) return false;
const value = this._map.get(key);
this._map.delete(key);
// Match HyperbolicLRUCache: explicit delete also notifies onEvict.
this._fireOnEvict(key, value);
return true;
}
// Alias of delete() matching the invalidation surface the engine
// consumed from HyperbolicLRUCache (invalidate / invalidateMany /
// invalidateByPattern).
invalidate(key) {
return this.delete(key);
}
invalidateMany(keys) {
let removed = 0;
for (const key of keys) {
if (this.delete(key)) removed++;
}
return removed;
}
/**
* Delete every entry whose key matches the pattern. The pattern may be
* a RegExp or a plain string (substring match). Used by the DecisionCache
* port to invalidate composite keys containing a given node id.
*/
invalidateByPattern(pattern) {
const matcher = pattern instanceof RegExp
? (key) => pattern.test(key)
: (key) => typeof key === 'string' && key.includes(pattern);
const matched = [];
for (const key of this._map.keys()) {
if (matcher(key)) matched.push(key);
}
for (const key of matched) this.delete(key);
return matched.length;
}
clear() {
this._map.clear();
}
_fireOnEvict(key, value) {
if (this.onEvict) {
try {
this.onEvict(key, value);
} catch (e) {
console.error('cache onEvict callback failed:', e);
}
}
}
}
// Default factory: produces a SimpleLRUCache. Override per-Arbiter via
// options.cacheFactory to plug in an alternative cache implementation.
export function defaultCacheFactory(capacity, options) {
return new SimpleLRUCache(capacity, options);
}