diff --git a/cache-blocking/README.md b/cache-blocking/README.md new file mode 100644 index 0000000..e0de3fa --- /dev/null +++ b/cache-blocking/README.md @@ -0,0 +1,3 @@ +# Module cache-blocking + +Blocking wrappers for Pedestal Cache, for contexts in which coroutines are not available in. diff --git a/cache-blocking/build.gradle.kts b/cache-blocking/build.gradle.kts new file mode 100644 index 0000000..3612eba --- /dev/null +++ b/cache-blocking/build.gradle.kts @@ -0,0 +1,41 @@ +@file:Suppress("UNUSED_VARIABLE") + +import java.net.URL + +plugins { + id("opensavvy.gradle.library") +} + +kotlin { + jvm() + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.cache) + + implementation(projects.logger) + } + } + + val commonTest by getting { + dependencies { + implementation(projects.tester) + implementation(KotlinX.coroutines.test) + implementation(projects.stateArrow) + } + } + } +} + +tasks.withType().configureEach { + dokkaSourceSets.configureEach { + includes.from("${project.projectDir}/README.md") + + sourceLink { + localDirectory.set(file("src")) + remoteUrl.set(URL("https://gitlab.com/opensavvy/pedestal/-/blob/main/cache-blocking/src")) + remoteLineSuffix.set("#L") + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 840e22b..5253c68 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,6 +16,7 @@ include( "progress", "progress-coroutines", "cache", + "cache-blocking", "backbone", "spine", "spine-ktor", -- 2.51.2 From 88af7cfbfa632e97c3e1b51cc3640d7f50532672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Thu, 27 Jul 2023 16:42:09 +0200 Subject: [PATCH 2/3] feat(cache): Blocking wrapper for Cache --- cache-blocking/README.md | 4 + .../src/jvmMain/kotlin/BlockingCache.kt | 130 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 cache-blocking/src/jvmMain/kotlin/BlockingCache.kt diff --git a/cache-blocking/README.md b/cache-blocking/README.md index e0de3fa..08d3d5e 100644 --- a/cache-blocking/README.md +++ b/cache-blocking/README.md @@ -1,3 +1,7 @@ # Module cache-blocking Blocking wrappers for Pedestal Cache, for contexts in which coroutines are not available in. + +# Package opensavvy.cache.blocking + +Blocking wrappers instantiated with the [`blocking`][opensavvy.cache.blocking.blocking] helper. diff --git a/cache-blocking/src/jvmMain/kotlin/BlockingCache.kt b/cache-blocking/src/jvmMain/kotlin/BlockingCache.kt new file mode 100644 index 0000000..f04272e --- /dev/null +++ b/cache-blocking/src/jvmMain/kotlin/BlockingCache.kt @@ -0,0 +1,130 @@ +package opensavvy.cache.blocking + +import kotlinx.coroutines.runBlocking +import opensavvy.cache.Cache +import opensavvy.cache.InfallibleCache +import opensavvy.state.coroutines.now +import opensavvy.state.outcome.Outcome +import opensavvy.state.outcome.value + +/** + * A blocking cache implementation. + * + * Unlike [asynchronous caches][Cache], this implementation doesn't allow subscribing to a value to see it change over time. + * + * To instantiate this class, see the [blocking] helper. + * + * @param I An identifier representing a cached object. + * @param F A cache value that represents a failure. + * @param T A cache value that represents a success. + * @property upstream The underlying cache instance. + */ +class BlockingCache( + private val upstream: Cache, +) { + + /** + * Gets the value associated with [id] in the cache, at the current time. + * + * Unlike [Cache.get], this function does not allow subscribing to the value to see its changes over time. + */ + operator fun get(id: I): Outcome = runBlocking { + upstream[id].now() + } + + /** + * Forces the cache to accept [value] as a more recent value for the given [id] than whatever it was previously storing. + * + * For more information, see [Cache.update]. + */ + operator fun set(id: I, value: T) = runBlocking { + upstream.update(id, value) + } + + /** + * Forces the cache to accept the given [values] as more recent than their associated identifier than whatever was + * previously stored. + * + * For more information, see [Cache.update]. + */ + fun update(values: Collection>) = runBlocking { + upstream.update(values) + } + + /** + * Forces the cache to accept the given [values] as more recent than their associated identifier than whatever was + * previously stored. + * + * For more information, see [Cache.update]. + */ + fun update(vararg values: Pair) = update(values.asList()) + + /** + * Tells the cache that the value it stores for [id] is out-of-date, and should be queried again the next time it is requested. + * + * For more information, see [Cache.expire]. + */ + fun expire(id: I) { + expire(listOf(id)) + } + + /** + * Tells the cache that the value it stores for the given [ids] are out-of-date, and should be queried again next time they are requested. + * + * For more information, see [Cache.expire]. + */ + fun expire(ids: Collection) = runBlocking { + upstream.expire(ids) + } + + /** + * Tells the cache that all values are out-of-date, and should be queried again the next time they are requested. + * + * For more information, see [Cache.expireAll]. + */ + fun expireAll() = runBlocking { + upstream.expireAll() + } +} + +/** + * Convenience function to access a value from [infallible caches][InfallibleCache] that are blocking. + * + * For more information, see [BlockingCache.get] and [Cache.get]. + */ +fun BlockingCache.getValue(id: I): T = + get(id).value + +/** + * Converts an [asynchronous cache][Cache] into a [blocking cache][BlockingCache]. + * + * ### Example + * + * ```kotlin + * // Create the coroutine context + * val cachingJob = SupervisorJob() + * val cachingScope = CoroutineScope(cachingJob) + * + * // Instantiate the cache instance + * val cache = cache { it * 2 } + * .cachedInMemory(cachingJob) + * .expireAfter(2.minutes, cachingScope) + * .blocking() + * + * // Access the cache + * println(cache[1]) // 2 + * + * // Force the cache to accept another value + * cache[1] = 3 + * println(cache[1]) // 3 + * + * // Force the cache to forget the value, meaning a new request will be started on next access + * cache.expire(1) + * println(cache[1]) // 2 + * + * // Don't forget to stop the cache workers when you're done using the cache + * cachingJob.cancel() + * ``` + */ +fun Cache.blocking() = + BlockingCache(this) -- 2.51.2 From 9446e790f3bbecc5578faf102bed05cc320a21cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Thu, 27 Jul 2023 17:16:47 +0200 Subject: [PATCH 3/3] feat(cache): Blocking wrapper for ContextualCache --- .../jvmMain/kotlin/BlockingContextualCache.kt | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 cache-blocking/src/jvmMain/kotlin/BlockingContextualCache.kt diff --git a/cache-blocking/src/jvmMain/kotlin/BlockingContextualCache.kt b/cache-blocking/src/jvmMain/kotlin/BlockingContextualCache.kt new file mode 100644 index 0000000..aa0168f --- /dev/null +++ b/cache-blocking/src/jvmMain/kotlin/BlockingContextualCache.kt @@ -0,0 +1,165 @@ +package opensavvy.cache.blocking + +import kotlinx.coroutines.runBlocking +import opensavvy.cache.Cache +import opensavvy.cache.InfallibleCache +import opensavvy.cache.contextual.ContextualCache +import opensavvy.state.coroutines.now +import opensavvy.state.outcome.Outcome +import opensavvy.state.outcome.value + +/** + * A blocking cache wrapper, which offers different results depending on the context. + * + * Unlike [asynchronous caches][ContextualCache], this implementation doesn't allow subscribing to a value to see it change over time. + * + * To instantiate this class, see the [blocking] helper. + * + * @param I The identifier used to request from the cache + * @param C The context which differentiates between cache results. + * @param F The possible failures when requesting from the cache. + * @param T The possible successful values when requesting from the cache. + */ +class BlockingContextualCache( + private val upstream: ContextualCache, +) { + + /** + * Gets the value associated with [id] and [context] in the cache, at the current time. + * + * Unlike [ContextualCache.get], this function does not allow subscribing to the value to see its changes over time. + */ + operator fun get(id: I, context: C): Outcome = runBlocking { + upstream[id, context].now() + } + + /** + * Forces the cache to accept [value] as a more recent value for the given [id] and [context] than whatever it was previously storing. + * + * For more information, see [ContextualCache.update]. + */ + operator fun set(id: I, context: C, value: T) = runBlocking { + upstream.update(id, context, value) + } + + /** + * Forces the cache to accept the given [values] as more recent than their associated identifier than whatever was + * previously stored. + * + * For more information, see [ContextualCache.update]. + */ + fun update(values: Collection>) = runBlocking { + upstream.update(values) + } + + /** + * Forces the cache to accept the given [values] as more recent than their associated identifier than whatever was + * previously stored. + * + * For more information, see [ContextualCache.update]. + */ + fun update(vararg values: Triple) = update(values.asList()) + + /** + * Tells the cache that the value it stores for [id] is out-of-date for all contexts, and should be queried again the next time it is requested. + * + * For more information, see [ContextualCache.expire]. + */ + fun expire(id: I) = runBlocking { + upstream.expire(id) + } + + /** + * Tells the cache that the value it stores for [id] and [context] is out-of-date for all contexts, and should be queried again the next time it is requested. + * + * For more information, see [ContextualCache.expire]. + */ + fun expire(id: I, context: C) = runBlocking { + upstream.expire(id, context) + } + + /** + * Tells the cache that the value it stores for the given [ids] are out-of-date for all contexts, and should be queried again next time they are requested. + * + * For more information, see [ContextualCache.expire]. + */ + fun expire(ids: Collection) = runBlocking { + upstream.expire(ids) + } + + /** + * Tells the cache that the value it stores for the given [ids] and contexts are out-of-date, and should be queried again next time they are requested. + * + * For more information, see [ContextualCache.expire]. + */ + fun expireContextual(ids: Collection>) = runBlocking { + upstream.expireContextual(ids) + } + + /** + * Tells the cache that all values are out-of-date, and should be queried again the next time they are requested. + * + * For more information, see [ContextualCache.expire]. + */ + fun expireAll() = runBlocking { + upstream.expireAll() + } +} + +/** + * Convenience function to access a value from [infallible caches][InfallibleCache] that are blocking. + * + * For more information, see [BlockingCache.get] and [Cache.get]. + */ +fun BlockingContextualCache.getValue(id: I, context: C): T = + get(id, context).value + +/** + * Converts an [asynchronous contextual cache][ContextualCache] into a [blocking cache][BlockingContextualCache]. + * + * ### Example + * + * ```kotlin + * // Create the coroutine context + * val cachingJob = SupervisorJob() + * val cachingScope = CoroutineScope(cachingJob) + * + * class User(val isAdmin: Boolean) + * val admin = User(true) + * val user = User(false) + * + * // Instantiate the cache instance + * val cache = cache { it, user -> + * if (user.isAdmin) + * (it * 2).success() + * else + * null.success() + * } + * .cachedInMemory(cachingJob) + * .expireAfter(2.minutes, cachingScope) + * .blocking() + * + * // Access the cache as an admin + * println(cache[1, admin]) // 2 + * + * // Access the cache as a regular user + * println(cache[1, user]) // null + * + * // Force the cache to accept another value + * cache[1, user] = 3 + * println(cache[1, user]) // 3 + * + * // Force the cache to forget the value for a specific user, meaning a new request will be started on next access + * cache.expire(1, user) + * println(cache[1, user]) // null + * + * // Force the cache to forget the value for all users + * cache.expire(1) + * println(cache[1, user]) // null + * + * // Don't forget to stop the cache workers when you're done using the cache + * cachingJob.cancel() + * ``` + */ +fun ContextualCache.blocking() = + BlockingContextualCache(this)