Appearance
Signed URL Cache
The CachedStorageProviderAdapter is a decorator over the S3StorageProviderAdapter that caches read signed URLs in Redis. It reduces AWS sign roundtrips when the same object is requested multiple times within a short window (e.g. an image rendered repeatedly in a chat history).
What is cached
Only read URLs — the methods getReadUrl and createReadUrl. Upload URLs, presigned POSTs, and binary operations (upload, download, delete, exists, deleteMany) pass through to the S3 adapter unchanged.
Upload URLs are not cached because each upload targets a unique S3 key, so the cache hit rate would be effectively zero.
Cache key strategy
storage:signed-url:read:{s3Key}:{expiresInSeconds}expiresInSeconds is part of the key on purpose: two callers asking for the same s3Key with different TTLs cannot share a URL, since the underlying presign embeds the expiry.
Cache TTL
The cache TTL is shorter than the URL TTL by a 60-second safety buffer:
cacheTtl = max(expiresInSeconds - 60, 30)-60s: ensures no consumer ever receives a URL that is about to expire.max(..., 30): floor for very short TTLs, so the value is never negative or trivially small.
Graceful degradation
If Redis is unavailable, the cache is treated as best-effort:
- A failing
cache.readis logged atwarnand the call falls through to the S3 adapter. - A failing
cache.writeis logged atwarn; the URL returned by S3 is still served to the caller.
The signed URL flow keeps working even when Redis is down. The cache is an optimization, not a hard dependency.
Wiring
The decorator is bound to both StorageProviderKey and StorageObjectSignerKey in apps/api/src/modules/storage/infrastructure/adapters/index.ts. Consumers continue to inject IStorageProvider or IStorageObjectSigner and receive the cached version transparently.
typescript
export const StorageInfrastructureAdapters = [
S3StorageProviderAdapter,
CachedStorageProviderAdapter,
{ provide: StorageProviderKey, useExisting: CachedStorageProviderAdapter },
{ provide: StorageObjectSignerKey, useExisting: CachedStorageProviderAdapter },
// …repos
];Failure cases that are not cached
Only successful results are written to Redis. If getReadUrl or createReadUrl returns a Result.fail(...), the failure is propagated as-is and the cache stays untouched, so transient sign errors do not poison the cache.