Files
mizan/packages/mizan-ts/src/cache/backend.ts
Ryth Azhur 4744ff052e Add TypeScript cache adapter with cross-language conformance tests
Port of Python's origin-side cache to TypeScript:
- cache/keys.ts: deriveCacheKey with stableStringify for JSON-canonical HMAC
- cache/backend.ts: MemoryCache (same API as Python)
- cache/index.ts: cacheGet, cachePut, cachePurge with AND semantics

Integrated into dispatch.ts:
- handleContextFetch: cache lookup before execution, store after
- handleMutationCall: purge on invalidation

Cross-language pin test proves Python and TypeScript produce identical
HMAC-SHA256 output for the same inputs:
  Public:      605a1ca5ad5994e9b765c8d1b330474c2a0d51a7b8fbbdc402f992da7ba902f6
  User-scoped: 30fc08eb46ee4ff2cf7d317e97dca90fd616511e0587304416f71dc863338dc2

34 TypeScript tests (9 new), 165 Python tests (1 new pin test).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 01:03:24 -04:00

71 lines
2.0 KiB
TypeScript

/**
* Cache backends — MemoryCache for testing.
*
* Same API as Python's MemoryCache. RedisCache is implemented
* per-adapter for production (not included here).
*/
export interface CacheBackend {
get(key: string): string | null
put(key: string, value: string, indexes: string[]): void
deleteMany(keys: string[]): number
getIndex(indexKey: string): Set<string>
removeFromIndex(indexKey: string, members: Set<string>): void
deleteIndex(indexKey: string): void
deleteIndexesByPrefix(prefix: string): void
clear(): void
}
export class MemoryCache implements CacheBackend {
private _store = new Map<string, string>()
private _indexes = new Map<string, Set<string>>()
get(key: string): string | null {
return this._store.get(key) ?? null
}
put(key: string, value: string, indexes: string[]): void {
this._store.set(key, value)
for (const idx of indexes) {
if (!this._indexes.has(idx)) {
this._indexes.set(idx, new Set())
}
this._indexes.get(idx)!.add(key)
}
}
deleteMany(keys: string[]): number {
let count = 0
for (const key of keys) {
if (this._store.delete(key)) count++
}
return count
}
getIndex(indexKey: string): Set<string> {
return new Set(this._indexes.get(indexKey) ?? [])
}
removeFromIndex(indexKey: string, members: Set<string>): void {
const idx = this._indexes.get(indexKey)
if (!idx) return
for (const m of members) idx.delete(m)
if (idx.size === 0) this._indexes.delete(indexKey)
}
deleteIndex(indexKey: string): void {
this._indexes.delete(indexKey)
}
deleteIndexesByPrefix(prefix: string): void {
for (const key of [...this._indexes.keys()]) {
if (key.startsWith(prefix)) this._indexes.delete(key)
}
}
clear(): void {
this._store.clear()
this._indexes.clear()
}
}