first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
# permission-cache
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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 `ttl` ms 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 prefix
- `invalidateAll()` — clears the entire cache (e.g. on role assignment change)
- `size` getter — returns the current entry count (including not-yet-evicted expired entries)
- Injectable `Clock` for deterministic test control
## Usage: Basic Example
```ts
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
```ts
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).