Shared specification satisfied by the C#, TypeScript, and Python implementations.
| Term | Meaning |
|---|---|
| Cache | The aggregate root (Cache<V>). Owns entries keyed by string, enforces capacity and TTL |
| Entry | A value stored under a key with the clock time of insertion |
| Capacity | The maximum number of entries the cache holds; a put that would exceed it evicts the least-recently-used key |
| TTL | Time-to-live: entries are considered expired once now - insertedAt >= ttl |
| Recency | Order of last access; get and put on an existing key both refresh recency. Expired lookups do not |
| Clock | Collaborator returning "now" — injected so TTL tests control time without sleeping |
| Explicit Sweep | evictExpired() — separate method that drops all expired entries in one pass |
- A new cache is empty;
sizeis0,contains(key)isfalsefor every key. - Capacity must be strictly positive; zero and negative capacities are rejected.
- TTL must be strictly positive; zero and negative durations are rejected.
put(key, value)stores an entry stamped withclock.now().puton an existing key replaces the value and refreshes both its insertion time and recency.putwhensize == capacityandkeyis new evicts the least-recently-used entry before inserting.get(key)returns the stored value, ornull/Noneif the key is absent or its entry has expired. An expiredgetalso removes the stale entry and does not refresh recency.geton a live entry refreshes recency (but not insertion time — TTL is measured fromput, not last access).contains(key)returnstrueonly for a key whose entry is present and not expired; acontainscall does not refresh recency.evictExpired()removes every entry wherenow - insertedAt >= ttlin a single pass.- Rejected operations (invalid capacity, invalid TTL) throw domain exceptions with byte-identical messages across languages.
DefaultCapacity = 100DefaultTtl = 60 seconds(TimeSpan.FromSeconds(60)/60_000 ms/timedelta(seconds=60))
These defaults match the kata brief; builders override them per scenario.
- A new cache has size zero
- A new cache contains no keys
- Cache rejects non-positive capacity with CacheCapacityInvalidException
- Cache rejects non-positive TTL with CacheTtlInvalidException
- Putting a key then getting it returns the stored value
- Getting a missing key returns null
- Put increases the size by one
- Putting the same key twice replaces the value without growing the size
- Contains returns true for a stored key
- Contains returns false for a missing key
- Filling the cache to capacity does not evict
- Putting a new key when at capacity evicts the least-recently-used key
- Getting a key refreshes recency so it is not the next evicted
- Replacing an existing key refreshes recency so it is not the next evicted
- A get after TTL has elapsed returns null
- Contains returns false once TTL has elapsed
- An expired entry is not counted in size after eviction sweep
- Explicit evictExpired removes all expired entries
- Explicit evictExpired leaves live entries intact
- TTL is measured from insertion time, not from last access