717ae1031e
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.
116 lines
3.6 KiB
JavaScript
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);
|
|
}
|