Hyperlinkv0.8.0-beta.28

Cache

Cache.getconsteffect/Cache.ts:398
<Key, A>(key: Key): <E, R>(
  self: Cache<Key, A, E, R>
) => Effect.Effect<A, E, R>
<Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<
  A,
  E,
  R
>

Retrieves the value for a key, invoking the lookup function on a cache miss or expired entry.

Details

Concurrent get calls for the same missing key share the same pending lookup. The cache stores the lookup Exit, so failed lookups are cached and will fail again until the entry expires, is invalidated, or is refreshed.

Example (Getting cached values)

import { Cache, Effect } from "effect"

const program = Effect.gen(function*() {
  const cache = yield* Cache.make({
    capacity: 10,
    lookup: (key: string) => Effect.succeed(key.length)
  })

  // Cache miss - triggers lookup function
  const result1 = yield* Cache.get(cache, "hello")
  console.log(result1) // 5

  // Cache hit - returns cached value without lookup
  const result2 = yield* Cache.get(cache, "hello")
  console.log(result2) // 5 (from cache)

  return { result1, result2 }
})

Example (Handling lookup failures)

import { Cache, Effect } from "effect"

// Error handling when lookup fails
const program = Effect.gen(function*() {
  const cache = yield* Cache.make<string, number, string>({
    capacity: 10,
    lookup: (key: string) =>
      key === "error"
        ? Effect.fail("Lookup failed")
        : Effect.succeed(key.length)
  })

  // Successful lookup
  const success = yield* Cache.get(cache, "hello")
  console.log(success) // 5

  // Failed lookup - returns error
  const failure = yield* Effect.exit(Cache.get(cache, "error"))
  console.log(failure) // Exit.fail("Lookup failed")
})

Example (Sharing concurrent lookups)

import { Cache, Effect } from "effect"

// Concurrent access - multiple gets of same key only invoke lookup once
const program = Effect.gen(function*() {
  let lookupCount = 0
  const cache = yield* Cache.make({
    capacity: 10,
    lookup: (key: string) =>
      Effect.sync(() => {
        lookupCount++
        return key.length
      })
  })

  // Multiple concurrent gets
  const results = yield* Effect.all([
    Cache.get(cache, "hello"),
    Cache.get(cache, "hello"),
    Cache.get(cache, "hello")
  ], { concurrency: "unbounded" })

  console.log(results) // [5, 5, 5]
  console.log(lookupCount) // 1 (lookup called only once)
})
combinators
Source effect/Cache.ts:39835 lines
export const get: {
  <Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect.Effect<A, E, R>
  <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<A, E, R>
} = dual(
  2,
  <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<A, E, R> =>
    core.withFiber((fiber) => {
      const oentry = MutableHashMap.get(self.map, key)
      if (Option.isSome(oentry) && !hasExpired(oentry.value, fiber)) {
        // Move the entry to the end of the map to keep it fresh
        MutableHashMap.remove(self.map, key)
        MutableHashMap.set(self.map, key, oentry.value)
        return Deferred.await(oentry.value.deferred)
      }
      const deferred = Deferred.makeUnsafe<A, E>()
      const entry: Entry<A, E> = {
        expiresAt: undefined,
        deferred
      }
      MutableHashMap.set(self.map, key, entry)
      if (Number.isFinite(self.capacity)) {
        checkCapacity(self)
      }
      return effect.onExit(self.lookup(key), (exit) => {
        Deferred.doneUnsafe(deferred, exit)
        const ttl = self.timeToLive(exit, key)
        if (Duration.isFinite(ttl)) {
          entry.expiresAt = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl)
        } else if (Duration.isZero(ttl)) {
          MutableHashMap.remove(self.map, key)
        }
        return effect.void
      })
    })
)
Referenced by 1 symbols