3.6 KiB
3.6 KiB
Spec-First Inventory — add-cache-storage-redis-backends
Per openspec/AGENTS.md §0c step 6 + AGENTS.md §2b. Implementation may not begin until this exists.
cache-storage — Requirements
R1. StorageCache extends AbstractCache
- R1.S1 instance is
instanceof AbstractCacheand has the fullAbstractCacheShape - R1.S2 inherited wrap caches the loader result on a miss
R2. 32-byte fixed-width metadata header
- R2.S1 header.encode → header.decode round-trips every field
- R2.S2 decodeHeader throws ENTRY_CORRUPT on bytes shorter than HEADER_BYTES
R3. TTL expiration is lazy on read
- R3.S1 get returns undefined and deletes when entry has expired
- R3.S2 entry with no ttl never expires
R4. ENTRY_CORRUPT on short blob from storage
- R4.S1 short blob surfaces as ENTRY_CORRUPT with
error.key === originalKey
R5. Codec decouples T from byte storage
- R5.S1 JsonCodec round-trips structured value
- R5.S2 JsonCodec.decode throws CODEC_ERROR on malformed bytes
- R5.S3 IdentityCodec encode/decode preserves bytes
R6. CACHE_STORAGE error registry
- R6.S1 every CACHE_STORAGE_* code reachable
R7. Module augmentation registers "storage" provider
- R7.S1 hasCacheProvider("storage") true after import
- R7.S2 createCache("storage", ...) returns a StorageCache
R8. Namespacing prefixes every stored key
- R8.S1 namespace prefixes keys before delegating to storage
R9. Lifecycle events emitted when EventBusLike provided
- R9.S1 emits hit on get after set
- R9.S2 emits miss on get of unknown key
R10. Stats counters via getStats
- R10.S1 hitRate computes hits / (hits + misses)
R11. Tier purity — universal
- R11.S1 src contains no node:/bun: imports
cache-storage — error-code reachability (R6)
- ENTRY_CORRUPT — short blob OR decodeHeader on undersized buffer
- CODEC_ERROR — JsonCodec.decode malformed bytes
- BACKEND_ERROR — underlying storage throws non-cache error → wrapped
- KEY_TOO_LONG — set with key longer than max allowed
- VALUE_TOO_LARGE — set with value larger than max allowed
cache-redis — Requirements
R1. RedisCache extends AbstractCache
- R1.S1 instance is
instanceof AbstractCachewith full shape
R2. RedisLike interface
- R2.S1 FakeRedis stub satisfies the interface
- R2.S2 cache round-trips set→get against FakeRedis
R3. Native TTL via SET ... EX
- R3.S1 positive ttl invokes SET with EX seconds
- R3.S2 zero default ttl invokes SET without EX
R4. INVALID_TTL on negative ttl
- R4.S1 negative ttl rejected with INVALID_TTL
R5. clear() refuses when no namespace
- R5.S1 clear without namespace throws COMMAND_FAILED
- R5.S2 clear with namespace deletes only namespaced keys
R6. CACHE_REDIS error registry
- R6.S1 every CACHE_REDIS_* code reachable
R7. Codec layer shared with cache-storage
- R7.S1 JsonCodec emits UTF-8 string into redis.set
- R7.S2 IdentityCodec emits base64 round-trip
R8. Module augmentation registers "redis" provider
- R8.S1 hasCacheProvider("redis") true after import
- R8.S2 createCache("redis", ...) returns a RedisCache
R9. getStats client-side counters
- R9.S1 reports hits/misses/hitRate, size 0
R10. Tier — server
- R10.S1 src contains no node:/bun: imports
cache-redis — error-code reachability (R6)
- CONNECTION_FAILED — wrap RedisLike error during set/get
- COMMAND_FAILED — clear() without namespace
- CODEC_ERROR — codec decode failure during get
- KEY_TOO_LONG — set with oversized key
- INVALID_TTL — negative ttl in set