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>
71 lines
2.0 KiB
TypeScript
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()
|
|
}
|
|
}
|