1/**
2 * Caches values loaded by an Effect lookup function.
3 *
4 * A cache stores successful and failed lookup results, shares an in-progress
5 * lookup when multiple callers request the same missing key, and limits entries
6 * by capacity and optional time-to-live rules. This module includes helpers for
7 * reading, setting, refreshing, invalidating, and inspecting cache contents.
8 *
9 * @since 4.0.0
10 */
11import * as Context from "./Context.ts"
12import * as Duration from "./Duration.ts"
13import type * as Effect from "./Effect.ts"
14import type * as Exit from "./Exit.ts"
15import type * as Fiber from "./Fiber.ts"
16import { dual } from "./Function.ts"
17import * as core from "./internal/core.ts"
18import { PipeInspectableProto } from "./internal/core.ts"
19import * as effect from "./internal/effect.ts"
20import * as Iterable from "./Iterable.ts"
21import * as MutableHashMap from "./MutableHashMap.ts"
22import * as Option from "./Option.ts"
23import type { Pipeable } from "./Pipeable.ts"
24import type { Predicate } from "./Predicate.ts"
25import * as Result from "./Result.ts"
26
27const TypeId = "~effect/Cache"
28
29/**
30 * A cache interface that provides a mutable key-value store with automatic TTL management,
31 * capacity limits, and lookup functions for cache misses.
32 *
33 * **Example** (Creating a basic cache)
34 *
35 * ```ts import.meta.vitest
36 * import { Cache, Effect } from "effect"
37 *
38 * // Basic cache with string keys and number values
39 * const program = Effect.gen(function*() {
40 * const cache = yield* Cache.make<string, number>({
41 * capacity: 100,
42 * lookup: (key: string) => Effect.succeed(key.length)
43 * })
44 *
45 * // Cache operations
46 * const value1 = yield* Cache.get(cache, "hello") // 5
47 * const value2 = yield* Cache.get(cache, "world") // 5
48 * const value3 = yield* Cache.get(cache, "hello") // 5 (cached)
49 *
50 * return [value1, value2, value3]
51 * })
52 *
53 * const actual = await Effect.runPromise(program)
54 * actual // => [5, 5, 5]
55 * ```
56 *
57 * **Example** (Handling lookup failures)
58 *
59 * ```ts import.meta.vitest
60 * import { Cache, Effect, Exit } from "effect"
61 *
62 * // Cache with error handling
63 * const program = Effect.gen(function*() {
64 * const cache = yield* Cache.make<string, number, string>({
65 * capacity: 10,
66 * lookup: (key: string) =>
67 * key === "error"
68 * ? Effect.fail("Lookup failed")
69 * : Effect.succeed(key.length)
70 * })
71 *
72 * // Handle successful and failed lookups
73 * const success = yield* Cache.get(cache, "test")
74 * const failure = yield* Effect.exit(Cache.get(cache, "error"))
75 *
76 * return [success, failure] as const
77 * })
78 *
79 * const actual = await Effect.runPromise(program)
80 * actual // => [4, Exit.fail("Lookup failed")]
81 * ```
82 *
83 * **Example** (Using complex keys with TTL)
84 *
85 * ```ts import.meta.vitest
86 * import { Cache, Data, Duration, Effect } from "effect"
87 *
88 * // Cache with complex key types and TTL
89 * class UserId extends Data.Class<{ id: number }> {}
90 *
91 * const program = Effect.gen(function*() {
92 * const userCache = yield* Cache.make<UserId, string>({
93 * capacity: 1000,
94 * lookup: (userId: UserId) => Effect.succeed(`User-${userId.id}`),
95 * timeToLive: Duration.minutes(5)
96 * })
97 *
98 * const userId = new UserId({ id: 123 })
99 * const userName = yield* Cache.get(userCache, userId)
100 *
101 * return userName
102 * })
103 *
104 * const actual = await Effect.runPromise(program)
105 * actual // => "User-123"
106 * ```
107 *
108 * @category models
109 * @since 2.0.0
110 */
111export interface Cache<in out Key, in out A, in out E = never, out R = never> extends Pipeable {
112 readonly [TypeId]: typeof TypeId
113 readonly map: MutableHashMap.MutableHashMap<Key, Entry<A, E>>
114 readonly capacity: number
115 readonly lookup: (key: Key) => Effect.Effect<A, E, R>
116 readonly timeToLive: (exit: Exit.Exit<A, E>, key: Key) => Duration.Duration
117}
118
119/**
120 * Represents a low-level cache entry containing a deferred lookup result and
121 * an optional expiration timestamp.
122 *
123 * **When to use**
124 *
125 * Use when inspecting a `Cache`'s low-level map and you need the stored
126 * deferred lookup result or expiration timestamp for a key.
127 *
128 * **Details**
129 *
130 * An `expiresAt` value of `undefined` means the entry does not expire.
131 *
132 * @see {@link Cache} for the public cache API that manages entries through
133 * combinators
134 *
135 * @category models
136 * @since 4.0.0
137 */
138export interface Entry<A, E> {
139 expiresAt: number | undefined
140 awaiters: number
141 readonly fiber: Fiber.Fiber<A, E>
142 await(this: Entry<A, E>): Effect.Effect<A, E>
143}
144
145/**
146 * Creates a cache with dynamic time-to-live based on the result and key.
147 *
148 * **When to use**
149 *
150 * Use when you need different cache entry lifetimes based on the lookup result
151 * or key characteristics.
152 *
153 * **Details**
154 *
155 * The timeToLive function receives both the exit result and the key, allowing
156 * for flexible TTL policies based on success/failure state and key characteristics.
157 *
158 * **Example** (Configuring dynamic time to live)
159 *
160 * ```ts import.meta.vitest
161 * import { Cache, Effect, Exit } from "effect"
162 *
163 * // Cache with TTL based on computed value
164 * const program = Effect.gen(function*() {
165 * const cache = yield* Cache.makeWith(
166 * (id: number) => Effect.succeed({ id, active: id % 2 === 0 }),
167 * {
168 * capacity: 1000,
169 * timeToLive(exit) {
170 * if (Exit.isSuccess(exit)) {
171 * const user = exit.value
172 * return user.active ? "1 hour" : "5 minutes"
173 * }
174 * return "30 seconds"
175 * }
176 * }
177 * )
178 *
179 * return cache.capacity
180 * })
181 *
182 * const actual = await Effect.runPromise(program)
183 * actual // => 1000
184 * ```
185 *
186 * @see {@link make} for a simpler cache constructor with a fixed time-to-live for all entries
187 * @category constructors
188 * @since 2.0.0
189 */
190export const makeWith = <
191 Key,
192 A,
193 E = never,
194 R = never,
195 ServiceMode extends "lookup" | "construction" = never
196>(lookup: (key: Key) => Effect.Effect<A, E, R>, options: {
197 readonly capacity: number
198 readonly timeToLive?: ((exit: Exit.Exit<A, E>, key: Key) => Duration.Input) | undefined
199 readonly requireServicesAt?: ServiceMode | undefined
200}): Effect.Effect<
201 Cache<Key, A, E, "lookup" extends ServiceMode ? R : never>,
202 never,
203 "lookup" extends ServiceMode ? never : R
204> =>
205 effect.contextWith((context: Context.Context<any>) => {
206 const self = Object.create(Proto)
207 self.lookup = (key: Key): Effect.Effect<A, E> =>
208 effect.updateContext(
209 lookup(key),
210 (input) => Context.merge(context, input)
211 )
212 self.map = MutableHashMap.make()
213 self.capacity = options.capacity
214 self.timeToLive = options.timeToLive
215 ? (exit: Exit.Exit<A, E>, key: Key) => Duration.fromInputUnsafe(options.timeToLive!(exit, key))
216 : defaultTimeToLive
217 return effect.succeed(self as Cache<Key, A, E>)
218 })
219
220/**
221 * Creates a cache with a fixed time-to-live for all entries.
222 *
223 * **Details**
224 *
225 * This is the basic cache constructor where all entries share the same TTL.
226 * The lookup function will be called when a key is not found or has expired.
227 *
228 * **Example** (Creating a basic cache)
229 *
230 * ```ts import.meta.vitest
231 * import { Cache, Effect } from "effect"
232 *
233 * // Basic cache with string keys
234 * const program = Effect.gen(function*() {
235 * const cache = yield* Cache.make<string, number>({
236 * capacity: 100,
237 * lookup: (key) => Effect.succeed(key.length)
238 * })
239 *
240 * const result1 = yield* Cache.get(cache, "hello")
241 * const result2 = yield* Cache.get(cache, "world")
242 * return { result1, result2 }
243 * })
244 *
245 * const actual = await Effect.runPromise(program)
246 * actual // => { result1: 5, result2: 5 }
247 * ```
248 *
249 * **Example** (Creating a cache with TTL)
250 *
251 * ```ts import.meta.vitest
252 * import { Cache, Effect } from "effect"
253 *
254 * const program = Effect.gen(function*() {
255 * const users = new Map([
256 * [123, { name: "Ada", email: "ada@example.com" }],
257 * [456, { name: "Grace", email: "grace@example.com" }]
258 * ])
259 *
260 * const cache = yield* Cache.make<
261 * number,
262 * { name: string; email: string },
263 * string
264 * >({
265 * capacity: 500,
266 * lookup: (userId) =>
267 * Effect.suspend(() => {
268 * const user = users.get(userId)
269 * return user === undefined
270 * ? Effect.fail(`User ${userId} not found`)
271 * : Effect.succeed(user)
272 * }),
273 * timeToLive: "15 minutes"
274 * })
275 *
276 * const user1 = yield* Cache.get(cache, 123)
277 * const user2 = yield* Cache.get(cache, 123)
278 * return [user1, user2, user1 === user2] as const
279 * })
280 *
281 * const actual = await Effect.runPromise(program)
282 * actual // => [{ name: "Ada", email: "ada@example.com" }, { name: "Ada", email: "ada@example.com" }, true]
283 * ```
284 *
285 * @category constructors
286 * @since 2.0.0
287 */
288export const make = <
289 Key,
290 A,
291 E = never,
292 R = never,
293 ServiceMode extends "lookup" | "construction" = never
294>(
295 options: {
296 readonly lookup: (key: Key) => Effect.Effect<A, E, R>
297 readonly capacity: number
298 readonly timeToLive?: Duration.Input | undefined
299 readonly requireServicesAt?: ServiceMode | undefined
300 }
301): Effect.Effect<
302 Cache<Key, A, E, "lookup" extends ServiceMode ? R : never>,
303 never,
304 "lookup" extends ServiceMode ? never : R
305> =>
306 makeWith<Key, A, E, R, ServiceMode>(options.lookup, {
307 ...options,
308 timeToLive: options.timeToLive !== undefined ? () => options.timeToLive! : defaultTimeToLive
309 })
310
311const Proto = {
312 ...PipeInspectableProto,
313 [TypeId]: TypeId,
314 toJSON(this: Cache<any, any, any>) {
315 return {
316 _id: "Cache",
317 capacity: this.capacity,
318 map: this.map
319 }
320 }
321}
322
323const defaultTimeToLive = <A, E>(_: Exit.Exit<A, E>, _key: unknown): Duration.Duration => Duration.infinity
324
325/**
326 * Retrieves the value for a key, invoking the lookup function on a cache miss
327 * or expired entry.
328 *
329 * **Details**
330 *
331 * Concurrent `get` calls for the same missing key share the same pending
332 * lookup. The cache stores the lookup `Exit`, so failed lookups are cached and
333 * will fail again until the entry expires, is invalidated, or is refreshed.
334 *
335 * **Example** (Getting cached values)
336 *
337 * ```ts import.meta.vitest
338 * import { Cache, Effect } from "effect"
339 *
340 * const program = Effect.gen(function*() {
341 * const cache = yield* Cache.make({
342 * capacity: 10,
343 * lookup: (key: string) => Effect.succeed(key.length)
344 * })
345 *
346 * // Cache miss - triggers lookup function
347 * const result1 = yield* Cache.get(cache, "hello")
348 *
349 * // Cache hit - returns cached value without lookup
350 * const result2 = yield* Cache.get(cache, "hello")
351 *
352 * return { result1, result2 }
353 * })
354 *
355 * const actual = await Effect.runPromise(program)
356 * actual // => { result1: 5, result2: 5 }
357 * ```
358 *
359 * **Example** (Handling lookup failures)
360 *
361 * ```ts import.meta.vitest
362 * import { Cache, Effect, Exit } from "effect"
363 *
364 * // Error handling when lookup fails
365 * const program = Effect.gen(function*() {
366 * const cache = yield* Cache.make<string, number, string>({
367 * capacity: 10,
368 * lookup: (key: string) =>
369 * key === "error"
370 * ? Effect.fail("Lookup failed")
371 * : Effect.succeed(key.length)
372 * })
373 *
374 * // Successful lookup
375 * const success = yield* Cache.get(cache, "hello")
376 *
377 * // Failed lookup - returns error
378 * const failure = yield* Effect.exit(Cache.get(cache, "error"))
379 * return [success, failure] as const
380 * })
381 *
382 * const actual = await Effect.runPromise(program)
383 * actual // => [5, Exit.fail("Lookup failed")]
384 * ```
385 *
386 * **Example** (Sharing concurrent lookups)
387 *
388 * ```ts import.meta.vitest
389 * import { Cache, Effect } from "effect"
390 *
391 * // Concurrent access - multiple gets of same key only invoke lookup once
392 * const program = Effect.gen(function*() {
393 * let lookupCount = 0
394 * const cache = yield* Cache.make({
395 * capacity: 10,
396 * lookup: (key: string) =>
397 * Effect.sync(() => {
398 * lookupCount++
399 * return key.length
400 * })
401 * })
402 *
403 * // Multiple concurrent gets
404 * const results = yield* Effect.all([
405 * Cache.get(cache, "hello"),
406 * Cache.get(cache, "hello"),
407 * Cache.get(cache, "hello")
408 * ], { concurrency: "unbounded" })
409 *
410 * return { results, lookupCount }
411 * })
412 *
413 * const actual = await Effect.runPromise(program)
414 * actual // => { results: [5, 5, 5], lookupCount: 1 }
415 * ```
416 *
417 * @category combinators
418 * @since 4.0.0
419 */
420export const get: {
421 <Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect.Effect<A, E, R>
422 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<A, E, R>
423} = dual(
424 2,
425 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<A, E, R> =>
426 core.withFiber((fiber) => {
427 const oentry = MutableHashMap.get(self.map, key)
428 if (Option.isSome(oentry)) {
429 MutableHashMap.remove(self.map, key)
430 if (!hasExpired(oentry.value, fiber)) {
431 // Move the entry to the end of the map to keep it fresh
432 MutableHashMap.set(self.map, key, oentry.value)
433 return oentry.value.await()
434 }
435 }
436 const entry = new EntryImpl(fiber, self.lookup(key))
437 entry.onInterrupt = () => removeEntry(self, key, entry)
438 let skipCache = false
439 entry.fiber.addObserver((exit) => {
440 // Release the key once the lookup can no longer be interrupted.
441 entry.onInterrupt = undefined
442 if (effect.exitHasInterrupts(exit)) {
443 removeEntry(self, key, entry)
444 return
445 }
446 const ttl = self.timeToLive(exit, key)
447 if (Duration.isZero(ttl)) {
448 skipCache = true
449 removeEntry(self, key, entry)
450 } else if (Duration.isFinite(ttl)) {
451 entry.expiresAt = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl)
452 }
453 })
454 const exit = entry.fiber.pollUnsafe()
455 if (!skipCache && (exit === undefined || !effect.exitHasInterrupts(exit))) {
456 MutableHashMap.set(self.map, key, entry)
457 }
458 if (Number.isFinite(self.capacity)) {
459 checkCapacity(self)
460 }
461 return entry.await()
462 })
463)
464
465class EntryImpl<A, E> implements Entry<A, E> {
466 expiresAt: number | undefined
467 awaiters: number
468 fiber: Fiber.Fiber<A, E>
469 onInterrupt: (() => void) | undefined
470
471 constructor(
472 parent: Fiber.Fiber<unknown, unknown>,
473 valueEffect: Effect.Effect<A, E, any>
474 ) {
475 this.fiber = effect.forkUnsafe(parent, valueEffect, true, true)
476 this.awaiters = 0
477 this.expiresAt = undefined
478 }
479
480 await(): Effect.Effect<A, E> {
481 const exit = this.fiber.pollUnsafe()
482 if (exit) return exit
483 this.awaiters++
484 return effect.onExit(effect.fiberJoin(this.fiber), () => {
485 this.awaiters--
486 if (this.awaiters > 0 || this.fiber.pollUnsafe()) return effect.void
487 // Detach before interrupting so new lookups do not join the abandoned fiber
488 // while its finalizers run.
489 this.onInterrupt?.()
490 return effect.fiberInterrupt(this.fiber)
491 })
492 }
493}
494
495const removeEntry = <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key, entry: Entry<A, E>): void => {
496 const current = MutableHashMap.get(self.map, key)
497 if (Option.isSome(current) && current.value === entry) {
498 MutableHashMap.remove(self.map, key)
499 }
500}
501
502const hasExpired = <A, E>(entry: Entry<A, E>, fiber: Fiber.Fiber<unknown, unknown>): boolean => {
503 if (entry.expiresAt === undefined) {
504 return false
505 }
506 return fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() >= entry.expiresAt
507}
508
509const checkCapacity = <K, A, E, R>(self: Cache<K, A, E, R>) => {
510 let diff = MutableHashMap.size(self.map) - self.capacity
511 if (diff <= 0) return
512 // MutableHashMap has insertion order, so we can remove the oldest entries
513 for (const [key] of self.map) {
514 MutableHashMap.remove(self.map, key)
515 diff--
516 if (diff === 0) return
517 }
518}
519
520/**
521 * Reads an existing cache entry without invoking the lookup function.
522 *
523 * **Details**
524 *
525 * Returns `Option.none()` when the key is missing or expired, and `Option.some`
526 * when a cached lookup has succeeded. If the entry is still pending, waits for
527 * it to complete. If the cached or pending lookup fails, this effect fails with
528 * the same error.
529 *
530 * **Example** (Reading cached values without lookup)
531 *
532 * ```ts import.meta.vitest
533 * import { Cache, Effect, Option } from "effect"
534 *
535 * const program = Effect.gen(function*() {
536 * const cache = yield* Cache.make({
537 * capacity: 10,
538 * lookup: (key: string) => Effect.succeed(key.length)
539 * })
540 *
541 * // No value in cache yet - returns None without lookup
542 * const empty = yield* Cache.getOption(cache, "hello")
543 *
544 * // Populate cache using get
545 * yield* Cache.get(cache, "hello")
546 *
547 * // Now getOption returns the cached value
548 * const cached = yield* Cache.getOption(cache, "hello")
549 * return [empty, cached] as const
550 * })
551 *
552 * const actual = await Effect.runPromise(program)
553 * actual // => [Option.none(), Option.some(5)]
554 * ```
555 *
556 * **Example** (Skipping expired entries)
557 *
558 * ```ts import.meta.vitest
559 * import { Cache, Effect, Option } from "effect"
560 * import { TestClock } from "effect/testing"
561 *
562 * // Expired entries return None
563 * const program = Effect.gen(function*() {
564 * const cache = yield* Cache.make({
565 * capacity: 10,
566 * lookup: (key: string) => Effect.succeed(key.length),
567 * timeToLive: "1 hour"
568 * })
569 *
570 * // Add value to cache
571 * yield* Cache.get(cache, "hello")
572 *
573 * // Value exists before expiration
574 * const beforeExpiry = yield* Cache.getOption(cache, "hello")
575 *
576 * // Simulate time passing
577 * yield* TestClock.adjust("2 hours")
578 *
579 * // Value expired - returns None
580 * const afterExpiry = yield* Cache.getOption(cache, "hello")
581 * return [beforeExpiry, afterExpiry] as const
582 * })
583 *
584 * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer()))
585 * actual // => [Option.some(5), Option.none()]
586 * ```
587 *
588 * **Example** (Waiting for pending lookups)
589 *
590 * ```ts import.meta.vitest
591 * import { Cache, Deferred, Effect, Fiber, Option } from "effect"
592 *
593 * // Waits for ongoing computation to complete
594 * const program = Effect.gen(function*() {
595 * const deferred = yield* Deferred.make<void>()
596 * const cache = yield* Cache.make({
597 * capacity: 10,
598 * lookup: (_key: string) => Deferred.await(deferred).pipe(Effect.as(42))
599 * })
600 *
601 * // Start lookup in background
602 * const getFiber = yield* Effect.forkChild(Cache.get(cache, "key"))
603 *
604 * // getOption waits for ongoing computation
605 * const optionFiber = yield* Effect.forkChild(Cache.getOption(cache, "key"))
606 *
607 * // Complete the computation
608 * yield* Deferred.succeed(deferred, void 0)
609 *
610 * const result = yield* Fiber.join(optionFiber)
611 * const value = yield* Fiber.join(getFiber)
612 * return [result, value] as const
613 * })
614 *
615 * const actual = await Effect.runPromise(program)
616 * actual // => [Option.some(42), 42]
617 * ```
618 *
619 * @category combinators
620 * @since 4.0.0
621 */
622export const getOption: {
623 <Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect.Effect<Option.Option<A>, E>
624 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<Option.Option<A>, E>
625} = dual(
626 2,
627 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<Option.Option<A>, E> =>
628 core.withFiber((fiber) => {
629 const entry = getImpl(self, key, fiber)
630 return entry ? effect.asSome(entry.await()) : effect.succeedNone
631 })
632)
633
634const getImpl = <Key, A, E, R>(
635 self: Cache<Key, A, E, R>,
636 key: Key,
637 fiber: Fiber.Fiber<any, any>,
638 isRead = true
639): Entry<A, E> | undefined => {
640 const oentry = MutableHashMap.get(self.map, key)
641 if (Option.isNone(oentry)) {
642 return undefined
643 } else if (hasExpired(oentry.value, fiber)) {
644 MutableHashMap.remove(self.map, key)
645 return undefined
646 } else if (isRead) {
647 MutableHashMap.remove(self.map, key)
648 MutableHashMap.set(self.map, key, oentry.value)
649 }
650 return oentry.value
651}
652
653/**
654 * Retrieves the value associated with the specified key from the cache, only if
655 * it contains a resolved successful value.
656 *
657 * **Details**
658 *
659 * This checks only an existing non-expired entry. It returns `Option.some` when
660 * the entry has already resolved successfully, and `Option.none` for missing,
661 * expired, failed, or still-pending entries.
662 *
663 * @see {@link get} for triggering or awaiting the cache lookup
664 * @see {@link getOption} for reading an existing entry as an optional effect
665 *
666 * @category combinators
667 * @since 4.0.0
668 */
669export const getSuccess: {
670 <Key, A, R>(key: Key): <E>(self: Cache<Key, A, E, R>) => Effect.Effect<Option.Option<A>>
671 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<Option.Option<A>>
672} = dual(
673 2,
674 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<Option.Option<A>> =>
675 core.withFiber((fiber) => {
676 const exit = getImpl(self, key, fiber)?.fiber.pollUnsafe()
677 if (exit && effect.exitIsSuccess(exit)) {
678 return effect.succeedSome(exit.value)
679 }
680 return effect.succeedNone
681 })
682)
683
684/**
685 * Sets the value associated with the specified key in the cache. This will
686 * overwrite any existing value for that key, skipping the lookup function.
687 *
688 * **Example** (Setting values directly)
689 *
690 * ```ts import.meta.vitest
691 * import { Cache, Effect } from "effect"
692 *
693 * const program = Effect.gen(function*() {
694 * const cache = yield* Cache.make({
695 * capacity: 100,
696 * lookup: (key: string) => Effect.succeed(key.length)
697 * })
698 *
699 * // Set a value directly without invoking lookup
700 * yield* Cache.set(cache, "hello", 42)
701 * return yield* Cache.get(cache, "hello")
702 * })
703 *
704 * const actual = await Effect.runPromise(program)
705 * actual // => 42
706 * ```
707 *
708 * **Example** (Overwriting cached values)
709 *
710 * ```ts import.meta.vitest
711 * import { Cache, Effect } from "effect"
712 *
713 * // Overwriting existing cached values
714 * const program = Effect.gen(function*() {
715 * const cache = yield* Cache.make({
716 * capacity: 100,
717 * lookup: (key: string) => Effect.succeed(key.length)
718 * })
719 *
720 * // First get populates via lookup
721 * const original = yield* Cache.get(cache, "test") // 4
722 *
723 * // Set overwrites the cached value
724 * yield* Cache.set(cache, "test", 999)
725 * const updated = yield* Cache.get(cache, "test") // 999
726 *
727 * return { original, updated }
728 * })
729 *
730 * const actual = await Effect.runPromise(program)
731 * actual // => { original: 4, updated: 999 }
732 * ```
733 *
734 * **Example** (Applying TTL to set values)
735 *
736 * ```ts import.meta.vitest
737 * import { Cache, Effect } from "effect"
738 * import { TestClock } from "effect/testing"
739 *
740 * // TTL behavior with set operations
741 * const program = Effect.gen(function*() {
742 * const cache = yield* Cache.make({
743 * capacity: 100,
744 * lookup: (key: string) => Effect.succeed(key.length),
745 * timeToLive: "1 hour"
746 * })
747 *
748 * // Set value with TTL applied
749 * yield* Cache.set(cache, "temporary", 123)
750 * const beforeExpiry = yield* Cache.has(cache, "temporary")
751 *
752 * // Advance time past TTL
753 * yield* TestClock.adjust("2 hours")
754 * const afterExpiry = yield* Cache.has(cache, "temporary")
755 * return [beforeExpiry, afterExpiry]
756 * })
757 *
758 * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer()))
759 * actual // => [true, false]
760 * ```
761 *
762 * **Example** (Enforcing capacity when setting values)
763 *
764 * ```ts import.meta.vitest
765 * import { Cache, Effect } from "effect"
766 *
767 * // Capacity enforcement with set operations
768 * const program = Effect.gen(function*() {
769 * const cache = yield* Cache.make({
770 * capacity: 2,
771 * lookup: (key: string) => Effect.succeed(key.length)
772 * })
773 *
774 * // Fill cache to capacity
775 * yield* Cache.set(cache, "a", 1)
776 * yield* Cache.set(cache, "b", 2)
777 * const sizeBeforeEviction = yield* Cache.size(cache)
778 *
779 * // Adding another entry evicts oldest
780 * yield* Cache.set(cache, "c", 3)
781 * const sizeAfterEviction = yield* Cache.size(cache)
782 * const hasOldest = yield* Cache.has(cache, "a")
783 * const hasNewest = yield* Cache.has(cache, "c")
784 * return [sizeBeforeEviction, sizeAfterEviction, hasOldest, hasNewest]
785 * })
786 *
787 * const actual = await Effect.runPromise(program)
788 * actual // => [2, 2, false, true]
789 * ```
790 *
791 * @category combinators
792 * @since 4.0.0
793 */
794export const set: {
795 <Key, A>(key: Key, value: A): <E, R>(self: Cache<Key, A, E, R>) => Effect.Effect<void>
796 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key, value: A): Effect.Effect<void>
797} = dual(
798 3,
799 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key, value: A): Effect.Effect<void> =>
800 core.withFiber((fiber) => {
801 const exit = core.exitSucceed(value)
802 const entry = new EntryImpl(fiber, exit)
803 const ttl = self.timeToLive(exit, key)
804 if (Duration.isZero(ttl)) {
805 MutableHashMap.remove(self.map, key)
806 return effect.void
807 }
808 entry.expiresAt = Duration.isFinite(ttl)
809 ? fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl)
810 : undefined
811 MutableHashMap.set(self.map, key, entry)
812 checkCapacity(self)
813 return effect.void
814 })
815)
816
817/**
818 * Checks whether the cache contains an entry for the specified key.
819 *
820 * **Details**
821 *
822 * This checks for an existing non-expired entry without invoking the cache
823 * lookup function. Expired entries are treated as absent.
824 *
825 * **Example** (Checking for cached keys)
826 *
827 * ```ts import.meta.vitest
828 * import { Cache, Effect } from "effect"
829 *
830 * const program = Effect.gen(function*() {
831 * const cache = yield* Cache.make({
832 * capacity: 100,
833 * lookup: (key: string) => Effect.succeed(key.length)
834 * })
835 *
836 * // Check non-existent key
837 * const missing = yield* Cache.has(cache, "missing")
838 *
839 * // Add entry and check existence
840 * yield* Cache.get(cache, "hello")
841 * const present = yield* Cache.has(cache, "hello")
842 * return [missing, present]
843 * })
844 *
845 * const actual = await Effect.runPromise(program)
846 * actual // => [false, true]
847 * ```
848 *
849 * **Example** (Checking TTL expiration)
850 *
851 * ```ts import.meta.vitest
852 * import { Cache, Effect } from "effect"
853 * import { TestClock } from "effect/testing"
854 *
855 * // TTL expiration behavior
856 * const program = Effect.gen(function*() {
857 * const cache = yield* Cache.make({
858 * capacity: 100,
859 * lookup: (key: string) => Effect.succeed(key.length),
860 * timeToLive: "1 hour"
861 * })
862 *
863 * // Add entry with TTL
864 * yield* Cache.get(cache, "expires")
865 * const initial = yield* Cache.has(cache, "expires")
866 *
867 * // Still valid before expiration
868 * yield* TestClock.adjust("30 minutes")
869 * const beforeExpiry = yield* Cache.has(cache, "expires")
870 *
871 * // Expired after TTL
872 * yield* TestClock.adjust("31 minutes")
873 * const afterExpiry = yield* Cache.has(cache, "expires")
874 * return [initial, beforeExpiry, afterExpiry]
875 * })
876 *
877 * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer()))
878 * actual // => [true, true, false]
879 * ```
880 *
881 * **Example** (Checking multiple keys)
882 *
883 * ```ts import.meta.vitest
884 * import { Cache, Effect } from "effect"
885 *
886 * // Checking multiple keys efficiently
887 * const program = Effect.gen(function*() {
888 * const cache = yield* Cache.make({
889 * capacity: 100,
890 * lookup: (key: string) => Effect.succeed(key.length)
891 * })
892 *
893 * // Populate some entries
894 * yield* Cache.set(cache, "apple", 5)
895 * yield* Cache.set(cache, "banana", 6)
896 *
897 * // Check multiple keys
898 * const keys = ["apple", "banana", "cherry", "date"]
899 * const results: Array<string> = []
900 * for (const key of keys) {
901 * const exists = yield* Cache.has(cache, key)
902 * results.push(`${key}: ${exists}`)
903 * }
904 * return results
905 * })
906 *
907 * const actual = await Effect.runPromise(program)
908 * actual // => ["apple: true", "banana: true", "cherry: false", "date: false"]
909 * ```
910 *
911 * @category combinators
912 * @since 4.0.0
913 */
914export const has: {
915 <Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect.Effect<boolean>
916 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<boolean>
917} = dual(
918 2,
919 <Key, A, E>(self: Cache<Key, A, E>, key: Key): Effect.Effect<boolean> =>
920 core.withFiber((fiber) => {
921 const oentry = getImpl(self, key, fiber, false)
922 return effect.succeed(oentry !== undefined)
923 })
924)
925
926/**
927 * Invalidates the entry associated with the specified key in the cache.
928 *
929 * **Example** (Invalidating cached entries)
930 *
931 * ```ts import.meta.vitest
932 * import { Cache, Effect } from "effect"
933 *
934 * const program = Effect.gen(function*() {
935 * const cache = yield* Cache.make({
936 * capacity: 10,
937 * lookup: (key: string) => Effect.succeed(key.length)
938 * })
939 *
940 * // Add a value to the cache
941 * yield* Cache.get(cache, "hello")
942 * const beforeInvalidation = yield* Cache.has(cache, "hello")
943 *
944 * // Invalidate the entry
945 * yield* Cache.invalidate(cache, "hello")
946 * const afterInvalidation = yield* Cache.has(cache, "hello")
947 *
948 * // Invalidating non-existent keys doesn't error
949 * yield* Cache.invalidate(cache, "nonexistent")
950 *
951 * // Get after invalidation will invoke lookup again
952 * let lookupCount = 0
953 * const cache2 = yield* Cache.make({
954 * capacity: 10,
955 * lookup: (key: string) =>
956 * Effect.sync(() => {
957 * lookupCount++
958 * return key.length
959 * })
960 * })
961 *
962 * yield* Cache.get(cache2, "test") // lookupCount = 1
963 * yield* Cache.invalidate(cache2, "test")
964 * yield* Cache.get(cache2, "test") // lookupCount = 2 (lookup called again)
965 * return { beforeInvalidation, afterInvalidation, lookupCount }
966 * })
967 *
968 * const actual = await Effect.runPromise(program)
969 * actual // => { beforeInvalidation: true, afterInvalidation: false, lookupCount: 2 }
970 * ```
971 *
972 * @category combinators
973 * @since 4.0.0
974 */
975export const invalidate: {
976 <Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect.Effect<void>
977 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<void>
978} = dual(2, <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<void> =>
979 effect.sync(() => {
980 MutableHashMap.remove(self.map, key)
981 }))
982
983/**
984 * Invalidates the entry associated with the specified key in the cache when the
985 * predicate returns true for the cached value.
986 *
987 * **Example** (Invalidating entries conditionally)
988 *
989 * ```ts import.meta.vitest
990 * import { Cache, Effect } from "effect"
991 *
992 * const program = Effect.gen(function*() {
993 * const cache = yield* Cache.make({
994 * capacity: 10,
995 * lookup: (key: string) => Effect.succeed(key.length)
996 * })
997 *
998 * // Add values to the cache
999 * yield* Cache.get(cache, "hello") // value = 5
1000 * yield* Cache.get(cache, "hi") // value = 2
1001 *
1002 * // Invalidate when value equals 5
1003 * const invalidated1 = yield* Cache.invalidateWhen(
1004 * cache,
1005 * "hello",
1006 * (value) => value === 5
1007 * )
1008 * const hasHello = yield* Cache.has(cache, "hello")
1009 *
1010 * // Don't invalidate when predicate doesn't match
1011 * const invalidated2 = yield* Cache.invalidateWhen(
1012 * cache,
1013 * "hi",
1014 * (value) => value === 5
1015 * )
1016 * const hasHi = yield* Cache.has(cache, "hi")
1017 *
1018 * // Returns false for non-existent keys
1019 * const invalidated3 = yield* Cache.invalidateWhen(
1020 * cache,
1021 * "nonexistent",
1022 * () => true
1023 * )
1024 *
1025 * // Returns false for failed cached values
1026 * const cacheWithErrors = yield* Cache.make<string, number, string>({
1027 * capacity: 10,
1028 * lookup: (key: string) =>
1029 * key === "fail" ? Effect.fail("error") : Effect.succeed(key.length)
1030 * })
1031 *
1032 * yield* Effect.exit(Cache.get(cacheWithErrors, "fail"))
1033 * const invalidated4 = yield* Cache.invalidateWhen(
1034 * cacheWithErrors,
1035 * "fail",
1036 * () => true
1037 * )
1038 * return [invalidated1, hasHello, invalidated2, hasHi, invalidated3, invalidated4]
1039 * })
1040 *
1041 * const actual = await Effect.runPromise(program)
1042 * actual // => [true, false, false, true, false, false]
1043 * ```
1044 *
1045 * @category combinators
1046 * @since 4.0.0
1047 */
1048export const invalidateWhen: {
1049 <Key, A>(key: Key, f: Predicate<A>): <E, R>(self: Cache<Key, A, E, R>) => Effect.Effect<boolean>
1050 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key, f: Predicate<A>): Effect.Effect<boolean>
1051} = dual(
1052 3,
1053 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key, f: Predicate<A>): Effect.Effect<boolean> =>
1054 core.withFiber((fiber) => {
1055 const oentry = getImpl(self, key, fiber, false)
1056 if (oentry === undefined) {
1057 return effect.succeed(false)
1058 }
1059 return oentry.await().pipe(
1060 effect.map((value) => {
1061 if (f(value)) {
1062 const current = MutableHashMap.get(self.map, key)
1063 if (Option.isNone(current) || current.value !== oentry) {
1064 return false
1065 }
1066 MutableHashMap.remove(self.map, key)
1067 return true
1068 }
1069 return false
1070 }),
1071 effect.catchCause(() => effect.succeed(false))
1072 )
1073 })
1074)
1075
1076/**
1077 * Forces a refresh of the value associated with the specified key in the cache.
1078 *
1079 * **Details**
1080 *
1081 * It will always invoke the lookup function to construct a new value,
1082 * overwriting any existing value for that key.
1083 *
1084 * **Example** (Refreshing cached values)
1085 *
1086 * ```ts import.meta.vitest
1087 * import { Cache, Effect } from "effect"
1088 *
1089 * // Force refresh of existing cached values
1090 * const program = Effect.gen(function*() {
1091 * let counter = 0
1092 * const cache = yield* Cache.make({
1093 * capacity: 10,
1094 * lookup: (key: string) => Effect.sync(() => `${key}-${++counter}`)
1095 * })
1096 *
1097 * // Initial cache population
1098 * const value1 = yield* Cache.get(cache, "user")
1099 *
1100 * // Get from cache (no lookup)
1101 * const value2 = yield* Cache.get(cache, "user")
1102 *
1103 * // Force refresh - always calls lookup
1104 * const refreshed = yield* Cache.refresh(cache, "user")
1105 *
1106 * // Subsequent gets return refreshed value
1107 * const value3 = yield* Cache.get(cache, "user")
1108 * return [value1, value2, refreshed, value3, counter]
1109 * })
1110 *
1111 * const actual = await Effect.runPromise(program)
1112 * actual // => ["user-1", "user-1", "user-2", "user-2", 2]
1113 * ```
1114 *
1115 * **Example** (Resetting TTL on refresh)
1116 *
1117 * ```ts import.meta.vitest
1118 * import { Cache, Effect } from "effect"
1119 * import { TestClock } from "effect/testing"
1120 *
1121 * // Refresh resets TTL (Time To Live)
1122 * const program = Effect.gen(function*() {
1123 * const cache = yield* Cache.make({
1124 * capacity: 10,
1125 * lookup: (key: string) => Effect.succeed(key.length),
1126 * timeToLive: "1 hour"
1127 * })
1128 *
1129 * yield* Cache.get(cache, "test")
1130 * yield* TestClock.adjust("45 minutes")
1131 *
1132 * // Entry would normally expire in 15 minutes
1133 * const beforeRefresh = yield* Cache.has(cache, "test")
1134 *
1135 * // Refresh resets the TTL to full 1 hour
1136 * yield* Cache.refresh(cache, "test")
1137 * yield* TestClock.adjust("30 minutes")
1138 *
1139 * // Still valid because TTL was reset
1140 * const afterRefresh = yield* Cache.has(cache, "test")
1141 * return [beforeRefresh, afterRefresh]
1142 * })
1143 *
1144 * const actual = await Effect.runPromise(Effect.provide(program, TestClock.layer()))
1145 * actual // => [true, true]
1146 * ```
1147 *
1148 * **Example** (Refreshing missing keys)
1149 *
1150 * ```ts import.meta.vitest
1151 * import { Cache, Effect } from "effect"
1152 *
1153 * // Refresh non-existent keys
1154 * const program = Effect.gen(function*() {
1155 * const cache = yield* Cache.make({
1156 * capacity: 10,
1157 * lookup: (key: string) => Effect.succeed(`value-for-${key}`)
1158 * })
1159 *
1160 * // Refresh non-existent key creates new entry
1161 * const result = yield* Cache.refresh(cache, "newKey")
1162 *
1163 * // Verify it's now cached
1164 * const cached = yield* Cache.has(cache, "newKey")
1165 * return [result, cached]
1166 * })
1167 *
1168 * const actual = await Effect.runPromise(program)
1169 * actual // => ["value-for-newKey", true]
1170 * ```
1171 *
1172 * @category combinators
1173 * @since 4.0.0
1174 */
1175export const refresh: {
1176 <Key, A>(key: Key): <E, R>(self: Cache<Key, A, E, R>) => Effect.Effect<A, E, R>
1177 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<A, E, R>
1178} = dual(
1179 2,
1180 <Key, A, E, R>(self: Cache<Key, A, E, R>, key: Key): Effect.Effect<A, E, R> =>
1181 core.withFiber((fiber) => {
1182 const entry = new EntryImpl(fiber, self.lookup(key))
1183 entry.onInterrupt = () => removeEntry(self, key, entry)
1184 const existing = getImpl(self, key, fiber, false) !== undefined
1185 if (!existing) {
1186 MutableHashMap.set(self.map, key, entry)
1187 checkCapacity(self)
1188 }
1189 entry.fiber.addObserver((exit) => {
1190 // Release the key once the lookup can no longer be interrupted.
1191 entry.onInterrupt = undefined
1192 if (effect.exitHasInterrupts(exit)) {
1193 removeEntry(self, key, entry)
1194 return
1195 }
1196 const ttl = self.timeToLive(exit, key)
1197 if (Duration.isZero(ttl)) {
1198 if (existing) MutableHashMap.remove(self.map, key)
1199 else removeEntry(self, key, entry)
1200 return effect.void
1201 }
1202 entry.expiresAt = Duration.isFinite(ttl)
1203 ? fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe() + Duration.toMillis(ttl)
1204 : undefined
1205 if (existing) {
1206 MutableHashMap.set(self.map, key, entry)
1207 checkCapacity(self)
1208 }
1209 })
1210 return entry.await()
1211 })
1212)
1213
1214/**
1215 * Invalidates all entries in the cache.
1216 *
1217 * **Example** (Invalidating all entries)
1218 *
1219 * ```ts import.meta.vitest
1220 * import { Cache, Effect } from "effect"
1221 *
1222 * // Clear all cached entries at once
1223 * const program = Effect.gen(function*() {
1224 * const cache = yield* Cache.make({
1225 * capacity: 10,
1226 * lookup: (key: string) => Effect.succeed(key.length)
1227 * })
1228 *
1229 * // Populate cache with multiple entries
1230 * yield* Cache.get(cache, "apple")
1231 * yield* Cache.get(cache, "banana")
1232 * yield* Cache.get(cache, "cherry")
1233 *
1234 * const sizeBeforeInvalidation = yield* Cache.size(cache)
1235 * const hasAppleBeforeInvalidation = yield* Cache.has(cache, "apple")
1236 *
1237 * // Clear all entries
1238 * yield* Cache.invalidateAll(cache)
1239 *
1240 * // Verify cache is empty
1241 * const sizeAfterInvalidation = yield* Cache.size(cache)
1242 * const hasAppleAfterInvalidation = yield* Cache.has(cache, "apple")
1243 * const hasBananaAfterInvalidation = yield* Cache.has(cache, "banana")
1244 * const hasCherryAfterInvalidation = yield* Cache.has(cache, "cherry")
1245 * return [
1246 * sizeBeforeInvalidation,
1247 * hasAppleBeforeInvalidation,
1248 * sizeAfterInvalidation,
1249 * hasAppleAfterInvalidation,
1250 * hasBananaAfterInvalidation,
1251 * hasCherryAfterInvalidation
1252 * ]
1253 * })
1254 *
1255 * const actual = await Effect.runPromise(program)
1256 * actual // => [3, true, 0, false, false, false]
1257 * ```
1258 *
1259 * @category combinators
1260 * @since 4.0.0
1261 */
1262export const invalidateAll = <Key, A, E, R>(self: Cache<Key, A, E, R>): Effect.Effect<void> =>
1263 effect.sync(() => {
1264 MutableHashMap.clear(self.map)
1265 })
1266
1267/**
1268 * Retrieves the approximate number of entries in the cache.
1269 *
1270 * **Details**
1271 *
1272 * Note that expired entries are counted until they are accessed and removed.
1273 * The size reflects the current number of entries stored, not the number
1274 * of valid entries.
1275 *
1276 * **Example** (Reading cache size)
1277 *
1278 * ```ts import.meta.vitest
1279 * import { Cache, Effect } from "effect"
1280 *
1281 * const program = Effect.gen(function*() {
1282 * const cache = yield* Cache.make({
1283 * capacity: 10,
1284 * lookup: (key: string) => Effect.succeed(key.length)
1285 * })
1286 *
1287 * // Empty cache has size 0
1288 * const emptySize = yield* Cache.size(cache)
1289 *
1290 * // Add entries and check size
1291 * yield* Cache.get(cache, "hello")
1292 * yield* Cache.get(cache, "world")
1293 * const sizeAfterAdding = yield* Cache.size(cache)
1294 *
1295 * // Size decreases after invalidation
1296 * yield* Cache.invalidate(cache, "hello")
1297 * const sizeAfterInvalidation = yield* Cache.size(cache)
1298 * return [emptySize, sizeAfterAdding, sizeAfterInvalidation]
1299 * })
1300 *
1301 * const actual = await Effect.runPromise(program)
1302 * actual // => [0, 2, 1]
1303 * ```
1304 *
1305 * @category combinators
1306 * @since 4.0.0
1307 */
1308export const size = <Key, A, E, R>(self: Cache<Key, A, E, R>): Effect.Effect<number> =>
1309 effect.sync(() => MutableHashMap.size(self.map))
1310
1311/**
1312 * Retrieves all active keys from the cache, automatically filtering out expired entries.
1313 *
1314 * **Example** (Reading active keys)
1315 *
1316 * ```ts import.meta.vitest
1317 * import { Cache, Effect } from "effect"
1318 *
1319 * // Basic key enumeration
1320 * const program = Effect.gen(function*() {
1321 * const cache = yield* Cache.make({
1322 * capacity: 10,
1323 * lookup: (key: string) => Effect.succeed(key.length)
1324 * })
1325 *
1326 * // Add some entries to the cache
1327 * yield* Cache.get(cache, "hello")
1328 * yield* Cache.get(cache, "world")
1329 * yield* Cache.get(cache, "cache")
1330 *
1331 * // Retrieve all active keys
1332 * const keys = yield* Cache.keys(cache)
1333 * return Array.from(keys).sort()
1334 * })
1335 *
1336 * const actual = await Effect.runPromise(program)
1337 * actual // => ["cache", "hello", "world"]
1338 * ```
1339 *
1340 * @category combinators
1341 * @since 4.0.0
1342 */
1343export const keys = <Key, A, E, R>(self: Cache<Key, A, E, R>): Effect.Effect<Iterable<Key>> =>
1344 core.withFiber((fiber) => {
1345 const now = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe()
1346 return effect.succeed(Iterable.filterMap(self.map, ([key, entry]) => {
1347 if (entry.expiresAt === undefined || entry.expiresAt > now) {
1348 return Result.succeed(key)
1349 }
1350 MutableHashMap.remove(self.map, key)
1351 return Result.failVoid
1352 }))
1353 })
1354
1355/**
1356 * Retrieves all successfully cached values from the cache, excluding failed
1357 * lookups and expired entries.
1358 *
1359 * **Example** (Reading all cached values)
1360 *
1361 * ```ts import.meta.vitest
1362 * import { Cache, Effect } from "effect"
1363 *
1364 * const program = Effect.gen(function*() {
1365 * const cache = yield* Cache.make({
1366 * capacity: 10,
1367 * lookup: (key: string) => Effect.succeed(key.length)
1368 * })
1369 *
1370 * // Add some values to the cache
1371 * yield* Cache.get(cache, "a")
1372 * yield* Cache.get(cache, "ab")
1373 * yield* Cache.get(cache, "abc")
1374 *
1375 * // Retrieve all cached values
1376 * const values = yield* Cache.values(cache)
1377 * return Array.from(values).sort()
1378 * })
1379 *
1380 * const actual = await Effect.runPromise(program)
1381 * actual // => [1, 2, 3]
1382 * ```
1383 *
1384 * @category combinators
1385 * @since 4.0.0
1386 */
1387export const values = <Key, A, E, R>(self: Cache<Key, A, E, R>): Effect.Effect<Iterable<A>> =>
1388 effect.map(entries(self), Iterable.map(([, value]) => value))
1389
1390/**
1391 * Retrieves all key-value pairs from the cache as an iterable. This function
1392 * only returns entries with successfully resolved values, filtering out any
1393 * failed lookups or expired entries.
1394 *
1395 * **Gotchas**
1396 *
1397 * Expired entries are removed from the cache while `entries` filters them out.
1398 *
1399 * @see {@link keys} for retrieving only cached keys
1400 * @see {@link values} for retrieving only cached values
1401 *
1402 * @category combinators
1403 * @since 4.0.0
1404 */
1405export const entries = <Key, A, E, R>(self: Cache<Key, A, E, R>): Effect.Effect<Iterable<[Key, A]>> =>
1406 core.withFiber((fiber) => {
1407 const now = fiber.getRef(effect.ClockRef).currentTimeMillisUnsafe()
1408 return effect.succeed(Iterable.filterMap(self.map, ([key, entry]) => {
1409 if (entry.expiresAt === undefined || entry.expiresAt > now) {
1410 const exit = entry.fiber.pollUnsafe()
1411 return exit && exit._tag === "Success"
1412 ? Result.succeed([key, exit.value])
1413 : Result.failVoid
1414 }
1415 MutableHashMap.remove(self.map, key)
1416 return Result.failVoid
1417 }))
1418 })