* Set a value in cache with automatic JSON serialization and optional TTL * @param key - Cache key to store under * @param value - Value to store * @param ttlMs - Time to live in milliseconds (optional - if omitted, key persists indefinitely) * @returns Result containing void or an error
(key: CacheKey, value: unknown, ttlMs?: number)
| 132 | * @returns Result containing void or an error |
| 133 | */ |
| 134 | async set(key: CacheKey, value: unknown, ttlMs?: number): Promise<Result<void, CacheError>> { |
| 135 | // Check Redis availability first |
| 136 | if (!this.isRedisClientReady()) { |
| 137 | return err({ |
| 138 | code: ErrorCode.RedisConnectionError, |
| 139 | }); |
| 140 | } |
| 141 | |
| 142 | // Validate key and optional TTL |
| 143 | const validation = validateInputs([key, ZCacheKey], [ttlMs, ZTtlMsOptional]); |
| 144 | if (!validation.ok) { |
| 145 | return validation; |
| 146 | } |
| 147 | |
| 148 | // Undefined is a caller bug, not a cacheable null. |
| 149 | if (value === undefined) { |
| 150 | logger.warn({ key, ttlMs }, "cache.set called with undefined; skipping write"); |
| 151 | return ok(undefined); |
| 152 | } |
| 153 | |
| 154 | try { |
| 155 | const serialized = JSON.stringify(value); |
| 156 | |
| 157 | if (ttlMs === undefined) { |
| 158 | // Set without expiration (persists indefinitely) |
| 159 | await this.withTimeout(this.redis.set(key, serialized)); |
| 160 | } else { |
| 161 | // Set with expiration |
| 162 | await this.withTimeout(this.redis.setEx(key, Math.floor(ttlMs / 1000), serialized)); |
| 163 | } |
| 164 | return ok(undefined); |
| 165 | } catch (error) { |
| 166 | logger.error({ error, key, ttlMs }, "Cache set operation failed"); |
| 167 | return err({ |
| 168 | code: ErrorCode.RedisOperationError, |
| 169 | }); |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * Delete one or more keys from cache (idempotent) |