import type * as Cache from "../Cache.js" import type { Deferred } from "../Deferred.js" import { seconds } from "../Duration.js" import type * as Effect from "../Effect.js" import { dual } from "../Function.js" import { globalValue } from "../GlobalValue.js" import type * as Request from "../Request.js" import type * as RequestResolver from "../RequestResolver.js" import * as BlockedRequests from "./blockedRequests.js" import { unsafeMakeWith } from "./cache.js" import * as core from "./core.js" import { ensuring } from "./fiberRuntime.js" import { Listeners } from "./request.js" type RequestCache = Cache.Cache, { listeners: Request.Listeners handle: Deferred }> /** @internal */ export const currentCache = globalValue( Symbol.for("effect/FiberRef/currentCache"), () => core.fiberRefUnsafeMake(unsafeMakeWith, { listeners: Request.Listeners handle: Deferred }>( 65536, () => core.map(core.deferredMake(), (handle) => ({ listeners: new Listeners(), handle })), () => seconds(60) )) ) /** @internal */ export const currentCacheEnabled = globalValue( Symbol.for("effect/FiberRef/currentCacheEnabled"), () => core.fiberRefUnsafeMake(false) ) /** @internal */ export const fromRequest = < A extends Request.Request, Ds extends | RequestResolver.RequestResolver | Effect.Effect, any, any> >( request: A, dataSource: Ds ): Effect.Effect< Request.Request.Success, Request.Request.Error, [Ds] extends [Effect.Effect] ? Effect.Effect.Context : never > => core.flatMap( (core.isEffect(dataSource) ? dataSource : core.succeed(dataSource)) as Effect.Effect< RequestResolver.RequestResolver >, (ds) => core.fiberIdWith((id) => { const proxy = new Proxy(request, {}) return core.fiberRefGetWith(currentCacheEnabled, (cacheEnabled) => { if (cacheEnabled) { const cached: Effect.Effect = core.fiberRefGetWith(currentCache, (cache) => core.flatMap(cache.getEither(proxy), (orNew) => { switch (orNew._tag) { case "Left": { if (orNew.left.listeners.interrupted) { return core.flatMap( cache.invalidateWhen(proxy, (entry) => entry.handle === orNew.left.handle), () => cached ) } orNew.left.listeners.increment() return core.uninterruptibleMask((restore) => core.flatMap( core.exit(core.blocked( BlockedRequests.empty, restore(core.deferredAwait(orNew.left.handle)) )), (exit) => { orNew.left.listeners.decrement() return exit } ) ) } case "Right": { orNew.right.listeners.increment() return core.uninterruptibleMask((restore) => core.flatMap( core.exit( core.blocked( BlockedRequests.single( ds as RequestResolver.RequestResolver, BlockedRequests.makeEntry({ request: proxy, result: orNew.right.handle, listeners: orNew.right.listeners, ownerId: id, state: { completed: false } }) ), restore(core.deferredAwait(orNew.right.handle)) ) ), () => { orNew.right.listeners.decrement() return core.deferredAwait(orNew.right.handle) } ) ) } } })) return cached } const listeners = new Listeners() listeners.increment() return core.flatMap( core.deferredMake, Request.Request.Error>(), (ref) => ensuring( core.blocked( BlockedRequests.single( ds as RequestResolver.RequestResolver, BlockedRequests.makeEntry({ request: proxy, result: ref, listeners, ownerId: id, state: { completed: false } }) ), core.deferredAwait(ref) ), core.sync(() => listeners.decrement() ) ) ) }) }) ) /** @internal */ export const cacheRequest = >( request: A, result: Request.Request.Result ): Effect.Effect => { return core.fiberRefGetWith(currentCacheEnabled, (cacheEnabled) => { if (cacheEnabled) { return core.fiberRefGetWith(currentCache, (cache) => core.flatMap(cache.getEither(request), (orNew) => { switch (orNew._tag) { case "Left": { return core.void } case "Right": { return core.deferredComplete(orNew.right.handle, result) } } })) } return core.void }) } /** @internal */ export const withRequestCaching: { (strategy: boolean): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, strategy: boolean ): Effect.Effect } = dual< ( strategy: boolean ) => (self: Effect.Effect) => Effect.Effect, ( self: Effect.Effect, strategy: boolean ) => Effect.Effect >(2, (self, strategy) => core.fiberRefLocally(self, currentCacheEnabled, strategy)) /** @internal */ export const withRequestCache: { (cache: Request.Cache): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, cache: Request.Cache ): Effect.Effect } = dual< ( cache: Request.Cache ) => (self: Effect.Effect) => Effect.Effect, ( self: Effect.Effect, cache: Request.Cache ) => Effect.Effect >( 2, // @ts-expect-error (self, cache) => core.fiberRefLocally(self, currentCache, cache) )