3.5 KiB
permission-cache
(no submodules)
Purpose
PermissionCache provides a TTL-bounded, size-capped in-process cache for permission check results in @suaveplan/permissions. Permission checks can be expensive — they may traverse multi-level role inheritance chains and evaluate ABAC conditions — so caching their boolean outcome for frequently-checked user/resource/action triples significantly reduces latency on hot paths.
The cache accepts an injectable Clock for deterministic testing: advance the fake clock to simulate TTL expiry without wall-clock waits.
Features
- TTL-based expiry: entries older than
ttlms are evicted on read - Size-capped: when full, the oldest entry is evicted before inserting a new one
invalidate(userId)— invalidates all entries for a specific user by key prefixinvalidateAll()— clears the entire cache (e.g. on role assignment change)sizegetter — returns the current entry count (including not-yet-evicted expired entries)- Injectable
Clockfor deterministic test control
Usage: Basic Example
import { PermissionCache } from "@suaveplan/permissions";
const cache = new PermissionCache({ ttl: 60_000 });
cache.set("u1:posts:read", true);
console.log(cache.get("u1:posts:read")); // true
cache.invalidate("u1");
console.log(cache.get("u1:posts:read")); // undefined — invalidated
Usage: Advanced Example
import { PermissionCache } from "@suaveplan/permissions";
let tick = 0;
const cache = new PermissionCache({ ttl: 1000, clock: () => tick });
cache.set("u1:posts:read", true);
tick = 999;
console.log(cache.get("u1:posts:read")); // true — not yet expired
tick = 1001;
console.log(cache.get("u1:posts:read")); // undefined — expired
// With PolicyEngine
import { PolicyEngine, RoleManager } from "@suaveplan/permissions";
import { MemoryAdapter } from "@suaveplan/permissions";
const adapter = new MemoryAdapter();
const roleManager = new RoleManager(adapter);
const policyEngine = new PolicyEngine(
new PermissionManager(adapter),
{ cache: new PermissionCache({ ttl: 30_000 }) },
);
API Reference
constructor(options: PermissionCacheOptions)
options.ttl— entry TTL in milliseconds (required)options.maxSize— maximum entry count before eviction (default: 1 000)options.clock— injectable time source (default:Date.now)
get(key: string): boolean | undefined
Returns the cached value if present and not expired, or undefined. Auto-evicts expired entries on read.
set(key: string, value: boolean): void
Stores an entry. If the cache is at capacity, evicts the oldest entry first.
invalidate(userId: string): void
Removes all entries whose key starts with userId:. Use after role assignment changes.
invalidateAll(): void
Clears all entries.
size: number
Current entry count. May include entries that are expired but not yet read (lazy eviction).
Implementation Notes
The eviction strategy is "evict the oldest-inserted entry" (Map insertion order) rather than true LRU. This keeps the implementation O(1) for all operations without the overhead of a doubly-linked list. For most permission-check workloads — where the hot users are also the most recently active — insertion-order eviction provides good cache efficiency. The invalidate(userId) method iterates all keys but the cache is bounded by maxSize, so the worst case is O(maxSize) — acceptable for typical values (≤10 000).