RcMap: Support dynamic idleTimeToLive values per key
Problem
Currently, RcMap.make accepts a static idleTimeToLive option that applies uniformly to all entries in the map. This value is stored once in the RcMapImpl constructor and used for all entries throughout the lifetime of the map.
However, there are use cases where different keys require different idle TTL values. For example:
- Resource pools with varying cost: Expensive resources (e.g., database connections to a primary) should have longer TTLs, while cheap resources (e.g., read replicas) can have shorter TTLs
- Priority-based caching: High-priority or frequently accessed resources should be kept longer than low-priority ones
- Dynamic workload adaptation: During high load, reduce TTLs to free resources faster; during idle periods, extend TTLs to avoid unnecessary re-acquisition
Proposed Solution
Allow idleTimeToLive to be a function that receives the key and returns a duration:
const map = yield* RcMap.make({
lookup: (key: string) => acquireResource(key),
idleTimeToLive: (key: string) => {
if (key.startsWith("premium:")) return Duration.minutes(10)
if (key.startsWith("cache:")) return Duration.seconds(30)
return Duration.minutes(1) // default
}
})
API Design Options
Option A: Function signature
idleTimeToLive?: Duration.DurationInput | ((key: K) => Duration.DurationInput) | undefined
Option B: Effectful function (more flexible)
idleTimeToLive?: Duration.DurationInput | ((key: K) => Effect.Effect<Duration.DurationInput>) | undefined
Option B would allow the TTL to depend on async context (e.g., checking a config service), though Option A is simpler and covers most use cases.
Implementation Considerations
-
Entry-level TTL storage: Currently
RcMapImplstoresidleTimeToLiveat the class level. With this change, each entry would need to store its computed TTL (or we compute it on each release) -
touchfunction behavior: Thetouchfunction currently usesself.idleTimeToLiveto extend the expiration. With dynamic TTLs, it should use the entry's configured TTL. This could be stored on theEntrytype:interface Entry<A, E> { // ... existing fields readonly idleTimeToLive: Duration.Duration | undefined // computed at acquisition time } -
Backwards compatibility: The change is backwards compatible since static durations would continue to work as before
Alternatives Considered
- Multiple RcMaps: Create separate RcMaps for different TTL requirements. This works but adds complexity when the key space is unified and resources need to interact.
- Custom wrapper: Implement TTL logic externally by calling
invalidateon a schedule. This is error-prone and doesn't integrate well with the reference counting semantics.
🤖 Generated with Claude Code