Skip to content

Instantly share code, notes, and snippets.

@mattiamanzati
Created August 22, 2022 15:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattiamanzati/ef35dd6799094760a9e37ee5ffa77eb3 to your computer and use it in GitHub Desktop.
Save mattiamanzati/ef35dd6799094760a9e37ee5ffa77eb3 to your computer and use it in GitHub Desktop.
import * as T from "@effect/core/io/Effect"
import * as SE from "@effect/core/stm/TSemaphore"
import * as HM from "@tsplus/stdlib/collections/HashMap"
import { pipe } from "@tsplus/stdlib/data/Function"
import * as O from "@tsplus/stdlib/data/Maybe"
/**
* Given a function (key: K) => Effect<R, E, A> this DataLoader
* batches and caches requests such as concurrent lookups
* for the same key cannot happen.
* Subsequent lookups will use the cache value, if any.
*/
export class DataLoader<K, R, E, A> {
cacheMap: HM.HashMap<K, A> = HM.empty()
semaphoreMap: HM.HashMap<K, SE.TSemaphore> = HM.empty()
constructor(
readonly resolve: (key: K) => T.Effect<R, E, A>,
readonly cachingEnabled: boolean
) {}
invalidateAll() {
return T.sync(() => {
this.cacheMap = HM.empty()
})
}
lookup(key: K): T.Effect<R, E, A> {
return pipe(
T.sync(() =>
pipe(
this.semaphoreMap,
HM.get(key),
O.getOrElse(() => {
const semaphore = SE.unsafeMake(1)
this.semaphoreMap = pipe(this.semaphoreMap, HM.set(key, semaphore))
return semaphore
})
)
),
T.flatMap((semaphoreForKey) =>
pipe(
T.sync(() =>
pipe(this.cacheMap, this.cachingEnabled ? HM.get(key) : () => O.none)
),
T.flatMap((valueInCache) =>
pipe(
valueInCache,
O.map(T.succeed),
O.getOrElse(() =>
pipe(
this.resolve(key),
T.tap((result) =>
T.sync(() => {
this.cacheMap = pipe(this.cacheMap, HM.set(key, result))
})
)
)
)
)
),
SE.withPermit(semaphoreForKey)
)
)
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment