mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
fc48446ec4 | |||
3644b83ac6 | |||
cd73791b6f | |||
03de71df2e | |||
83d5d3faf4 | |||
0c8bec4c89 | |||
7fc93817c1 | |||
d0a00031a1 | |||
6ebc5aa0c2 | |||
8a6b4bb49e | |||
20799b9a3e | |||
ec3afc615c | |||
da692ccfc3 | |||
53b89f3a18 | |||
58cded28d3 | |||
592c5f3732 | |||
f44a78a5f5 | |||
e0bdd5dfdc | |||
99c0f06b72 | |||
66fc6df3d7 | |||
a36425a905 | |||
d920fee6d4 | |||
23590be5de | |||
94acc3c93b | |||
5616326a3b | |||
7601860c5c |
39
CHANGELOG.md
39
CHANGELOG.md
@@ -1,5 +1,44 @@
|
||||
# Changelog
|
||||
|
||||
## 0.16.7
|
||||
|
||||
* `Common`:
|
||||
* New extensions `ifTrue`/`ifFalse`/`alsoIfTrue`/`alsoIfFalse`/`letIfTrue`/`letIfFalse`
|
||||
* `Diff` now is serializable
|
||||
* Add `IndexedValue` serializer
|
||||
* `repeatOnFailure` extending: now you may pass any lambda to check if continue to try/do something
|
||||
* `Compose`:
|
||||
* New extension `MutableState.asState`
|
||||
* `Coroutines`:
|
||||
* `Compose`:
|
||||
* All the `Flow` conversations to compose `State`/`MutableState`/`SnapshotStateList`/`List` got several new
|
||||
parameters
|
||||
* `Flow.toMutableState` now is deprecated in favor to `asMutableComposeState`
|
||||
* `Repos`:
|
||||
* `Cache`:
|
||||
* New type `FullCacheRepo`
|
||||
* New type `CommonCacheRepo`
|
||||
* `CacheRepo` got `invalidate` method. It will fully reload `FullCacheRepo` and just clear `CommonCacheRepo`
|
||||
* New extensions `KVCache.actualizeAll`
|
||||
|
||||
## 0.16.6
|
||||
|
||||
* `Startup`:
|
||||
* `Launcher`:
|
||||
* Improvements in `StartLauncherPlugin#start` methods
|
||||
* Add opportunity to pass second argument on `JVM` platform as log level
|
||||
* `Repos`:
|
||||
* `Ktor`:
|
||||
* `Client`:
|
||||
* All clients repos got opportunity to customize their flows
|
||||
* `Exposed`:
|
||||
* Extensions `eqOrIsNull` and `neqOrIsNotNull` for `Column`
|
||||
|
||||
## 0.16.5
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `2.2.1` -> `2.2.2`
|
||||
|
||||
## 0.16.4
|
||||
|
||||
* `Coroutines`:
|
||||
|
@@ -0,0 +1,10 @@
|
||||
package dev.inmo.micro_utils.common.compose
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
|
||||
/**
|
||||
* Converts current [MutableState] to immutable [State] using [derivedStateOf]
|
||||
*/
|
||||
fun <T> MutableState<T>.asState(): State<T> = derivedStateOf { this.value }
|
@@ -2,6 +2,8 @@
|
||||
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private inline fun <T> getObject(
|
||||
additional: MutableList<T>,
|
||||
iterator: Iterator<T>
|
||||
@@ -24,13 +26,14 @@ private inline fun <T> getObject(
|
||||
*
|
||||
* @see calculateDiff
|
||||
*/
|
||||
@Serializable
|
||||
data class Diff<T> internal constructor(
|
||||
val removed: List<IndexedValue<T>>,
|
||||
val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
|
||||
/**
|
||||
* Old-New values pairs
|
||||
*/
|
||||
val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||
val added: List<IndexedValue<T>>
|
||||
val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
|
||||
val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
|
||||
)
|
||||
|
||||
private inline fun <T> performChanges(
|
||||
|
@@ -0,0 +1,43 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
|
||||
return if (this) {
|
||||
block()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
|
||||
return if (this) {
|
||||
null
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean {
|
||||
letIfTrue(block)
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean {
|
||||
letIfFalse(block)
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun <T> Boolean.ifTrue(block: () -> T): T? {
|
||||
return if (this) {
|
||||
block()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> Boolean.ifFalse(block: () -> T): T? {
|
||||
return if (this) {
|
||||
null
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializer
|
||||
import kotlinx.serialization.builtins.PairSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
class IndexedValueSerializer<T>(private val subSerializer: KSerializer<T>) : KSerializer<IndexedValue<T>> {
|
||||
private val originalSerializer = PairSerializer(Int.serializer(), subSerializer)
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = originalSerializer.descriptor
|
||||
|
||||
override fun deserialize(decoder: Decoder): IndexedValue<T> {
|
||||
val pair = originalSerializer.deserialize(decoder)
|
||||
return IndexedValue(
|
||||
pair.first,
|
||||
pair.second
|
||||
)
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: IndexedValue<T>) {
|
||||
originalSerializer.serialize(
|
||||
encoder,
|
||||
Pair(value.index, value.value)
|
||||
)
|
||||
}
|
||||
}
|
@@ -1,5 +1,27 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
/**
|
||||
* Executes the given [action] until getting of successful result specified number of [times].
|
||||
*
|
||||
* A zero-based index of current iteration is passed as a parameter to [action].
|
||||
*/
|
||||
inline fun <R> repeatOnFailure(
|
||||
onFailure: (Throwable) -> Boolean,
|
||||
action: () -> R
|
||||
): Result<R> {
|
||||
do {
|
||||
runCatching {
|
||||
action()
|
||||
}.onFailure {
|
||||
if (!onFailure(it)) {
|
||||
return Result.failure(it)
|
||||
}
|
||||
}.onSuccess {
|
||||
return Result.success(it)
|
||||
}
|
||||
} while (true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given [action] until getting of successful result specified number of [times].
|
||||
*
|
||||
@@ -10,12 +32,23 @@ inline fun <R> repeatOnFailure(
|
||||
onEachFailure: (Throwable) -> Unit = {},
|
||||
action: (Int) -> R
|
||||
): Optional<R> {
|
||||
repeat(times) {
|
||||
runCatching {
|
||||
action(it)
|
||||
}.onFailure(onEachFailure).onSuccess {
|
||||
return Optional.presented(it)
|
||||
var i = 0
|
||||
val result = repeatOnFailure(
|
||||
{
|
||||
onEachFailure(it)
|
||||
if (i < times) {
|
||||
i++
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
return Optional.absent()
|
||||
) {
|
||||
action(i)
|
||||
}
|
||||
return if (result.isSuccess) {
|
||||
Optional.presented(result.getOrThrow())
|
||||
} else {
|
||||
Optional.absent()
|
||||
}
|
||||
}
|
||||
|
@@ -3,24 +3,58 @@ package dev.inmo.micro_utils.coroutines.compose
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import dev.inmo.micro_utils.common.applyDiff
|
||||
import dev.inmo.micro_utils.coroutines.ExceptionHandler
|
||||
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
* Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList]
|
||||
*
|
||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList]
|
||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
|
||||
scope: CoroutineScope
|
||||
scope: CoroutineScope,
|
||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
): SnapshotStateList<T> {
|
||||
val state = mutableStateListOf<T>()
|
||||
subscribeSafelyWithoutExceptions(scope) {
|
||||
val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let {
|
||||
{
|
||||
withContext(useContextOnChange) {
|
||||
state.applyDiff(it)
|
||||
}
|
||||
}
|
||||
} ?: {
|
||||
state.applyDiff(it)
|
||||
}
|
||||
subscribeSafelyWithoutExceptions(scope, onException, changeBlock)
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* In fact, it is just classcast of [asMutableComposeListState] to [List]
|
||||
*
|
||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [List]
|
||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||
*
|
||||
* @return Changing in time [List] which follow [Flow] values
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified T> Flow<List<T>>.asComposeList(
|
||||
scope: CoroutineScope
|
||||
): List<T> = asMutableComposeListState(scope)
|
||||
scope: CoroutineScope,
|
||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
): List<T> = asMutableComposeListState(scope, useContextOnChange, onException)
|
||||
|
||||
|
@@ -1,35 +1,94 @@
|
||||
package dev.inmo.micro_utils.coroutines.compose
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import dev.inmo.micro_utils.common.compose.asState
|
||||
import dev.inmo.micro_utils.coroutines.ExceptionHandler
|
||||
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
|
||||
import dev.inmo.micro_utils.coroutines.doInUI
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
* Will map [this] [Flow] as [MutableState]. Returned [MutableState] WILL NOT change source [Flow]
|
||||
*
|
||||
* @param initial First value which will be passed to the result [MutableState]
|
||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
|
||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||
*/
|
||||
fun <T> Flow<T>.asMutableComposeState(
|
||||
initial: T,
|
||||
scope: CoroutineScope
|
||||
scope: CoroutineScope,
|
||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
): MutableState<T> {
|
||||
val state = mutableStateOf(initial)
|
||||
subscribeSafelyWithoutExceptions(scope) { state.value = it }
|
||||
val changeBlock: suspend (T) -> Unit = useContextOnChange ?.let {
|
||||
{
|
||||
withContext(useContextOnChange) {
|
||||
state.value = it
|
||||
}
|
||||
}
|
||||
} ?: {
|
||||
state.value = it
|
||||
}
|
||||
subscribeSafelyWithoutExceptions(scope, onException, block = changeBlock)
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Will map [this] [StateFlow] as [MutableState]. Returned [MutableState] WILL NOT change source [StateFlow].
|
||||
* This conversation will pass its [StateFlow.value] as the first value
|
||||
*
|
||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
|
||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T> StateFlow<T>.asMutableComposeState(
|
||||
scope: CoroutineScope
|
||||
): MutableState<T> = asMutableComposeState(value, scope)
|
||||
scope: CoroutineScope,
|
||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
): MutableState<T> = asMutableComposeState(value, scope, useContextOnChange, onException)
|
||||
|
||||
/**
|
||||
* Will create [MutableState] using [asMutableComposeState] and use [asState] to convert it as immutable state
|
||||
*
|
||||
* @param initial First value which will be passed to the result [State]
|
||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
|
||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||
*/
|
||||
fun <T> Flow<T>.asComposeState(
|
||||
initial: T,
|
||||
scope: CoroutineScope
|
||||
scope: CoroutineScope,
|
||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
): State<T> {
|
||||
val state = asMutableComposeState(initial, scope)
|
||||
return derivedStateOf { state.value }
|
||||
val state = asMutableComposeState(initial, scope, useContextOnChange, onException)
|
||||
return state.asState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Will map [this] [StateFlow] as [State]. This conversation will pass its [StateFlow.value] as the first value
|
||||
*
|
||||
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
|
||||
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
|
||||
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
|
||||
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T> StateFlow<T>.asComposeState(
|
||||
scope: CoroutineScope
|
||||
): State<T> = asComposeState(value, scope)
|
||||
scope: CoroutineScope,
|
||||
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
): State<T> = asComposeState(value, scope, useContextOnChange, onException)
|
||||
|
||||
|
@@ -7,17 +7,15 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(initial, scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
|
||||
fun <T> Flow<T>.toMutableState(
|
||||
initial: T,
|
||||
scope: CoroutineScope
|
||||
): MutableState<T> {
|
||||
val state = mutableStateOf(initial)
|
||||
subscribeSafelyWithoutExceptions(scope) { state.value = it }
|
||||
return state
|
||||
}
|
||||
): MutableState<T> = asMutableComposeState(initial, scope)
|
||||
|
||||
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T> StateFlow<T>.toMutableState(
|
||||
scope: CoroutineScope
|
||||
): MutableState<T> = toMutableState(value, scope)
|
||||
): MutableState<T> = asMutableComposeState(scope)
|
||||
|
||||
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.16.4
|
||||
android_code_version=172
|
||||
version=0.16.7
|
||||
android_code_version=175
|
||||
|
@@ -13,7 +13,7 @@ jb-dokka = "1.7.20"
|
||||
klock = "3.4.0"
|
||||
uuid = "0.6.0"
|
||||
|
||||
ktor = "2.2.1"
|
||||
ktor = "2.2.2"
|
||||
|
||||
gh-release = "2.4.1"
|
||||
|
||||
|
@@ -19,7 +19,7 @@ import kotlinx.coroutines.isActive
|
||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||
*/
|
||||
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
|
||||
inline fun <reified T : Any> openBaseWebSocketFlow(
|
||||
inline fun <T : Any> openBaseWebSocketFlow(
|
||||
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
|
||||
): Flow<T> {
|
||||
@@ -57,7 +57,7 @@ inline fun <reified T : Any> HttpClient.openWebSocketFlow(
|
||||
): Flow<T> {
|
||||
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
||||
|
||||
return openBaseWebSocketFlow<T>(checkReconnection) {
|
||||
return openBaseWebSocketFlow(checkReconnection) {
|
||||
val block: suspend DefaultClientWebSocketSession.() -> Unit = {
|
||||
while (isActive) {
|
||||
send(receiveDeserialized<T>())
|
||||
|
@@ -10,12 +10,14 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||
protected open val kvCache: KVCache<IdType, ObjectType>,
|
||||
protected open val idGetter: (ObjectType) -> IdType
|
||||
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CacheRepo {
|
||||
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CommonCacheRepo {
|
||||
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
|
||||
kvCache.set(id, it)
|
||||
})
|
||||
|
||||
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
|
||||
|
||||
override suspend fun invalidate() = kvCache.clear()
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
||||
@@ -28,7 +30,7 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
protected open val kvCache: KVCache<IdType, ObjectType>,
|
||||
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
protected open val idGetter: (ObjectType) -> IdType
|
||||
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CacheRepo {
|
||||
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CommonCacheRepo {
|
||||
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
|
||||
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
|
||||
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
|
||||
@@ -72,6 +74,8 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
|
||||
return created
|
||||
}
|
||||
|
||||
override suspend fun invalidate() = kvCache.clear()
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
|
||||
|
@@ -1,3 +1,5 @@
|
||||
package dev.inmo.micro_utils.repos.cache
|
||||
|
||||
interface CacheRepo
|
||||
interface CacheRepo {
|
||||
suspend fun invalidate()
|
||||
}
|
||||
|
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CommonCacheRepo.kt
vendored
Normal file
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CommonCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.cache
|
||||
|
||||
/**
|
||||
* Any inheritor of this should work with next logic: try to take data from some [dev.inmo.micro_utils.repos.cache.cache.KVCache] and,
|
||||
* if not exists, take from origin and save to the cache for future reuse
|
||||
*/
|
||||
interface CommonCacheRepo : CacheRepo
|
@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.*
|
||||
open class ReadKeyValueCacheRepo<Key,Value>(
|
||||
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||
protected open val kvCache: KVCache<Key, Value>,
|
||||
) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo {
|
||||
) : ReadKeyValueRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
||||
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
|
||||
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
|
||||
|
||||
@@ -23,6 +23,8 @@ open class ReadKeyValueCacheRepo<Key,Value>(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun invalidate() = kvCache.clear()
|
||||
}
|
||||
|
||||
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
||||
@@ -33,9 +35,11 @@ open class KeyValueCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValueRepo<Key, Value>,
|
||||
kvCache: KVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CacheRepo {
|
||||
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CommonCacheRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
|
||||
override suspend fun invalidate() = kvCache.clear()
|
||||
}
|
||||
|
||||
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
||||
|
@@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.*
|
||||
open class ReadKeyValuesCacheRepo<Key,Value>(
|
||||
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||
protected open val kvCache: KVCache<Key, List<Value>>
|
||||
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
|
||||
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||
return getAll(k, reversed).paginate(
|
||||
pagination
|
||||
@@ -30,6 +30,8 @@ open class ReadKeyValuesCacheRepo<Key,Value>(
|
||||
}
|
||||
})
|
||||
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
|
||||
|
||||
override suspend fun invalidate() = kvCache.clear()
|
||||
}
|
||||
|
||||
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||
@@ -40,7 +42,7 @@ open class KeyValuesCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValuesRepo<Key, Value>,
|
||||
kvCache: KVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
|
||||
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) ->
|
||||
kvCache.set(
|
||||
k,
|
||||
@@ -56,6 +58,8 @@ open class KeyValuesCacheRepo<Key,Value>(
|
||||
protected val onDataClearedJob = parentRepo.onDataCleared.onEach {
|
||||
kvCache.unset(it)
|
||||
}.launchIn(scope)
|
||||
|
||||
override suspend fun invalidate() = kvCache.clear()
|
||||
}
|
||||
|
||||
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(
|
||||
|
@@ -8,6 +8,7 @@ import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.cache.*
|
||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -32,12 +33,7 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
}
|
||||
|
||||
protected open suspend fun actualizeAll() {
|
||||
kvCache.clear()
|
||||
doForAllWithNextPaging {
|
||||
parentRepo.getByPagination(it).also {
|
||||
kvCache.set(it.results.associateBy { idGetter(it) })
|
||||
}
|
||||
}
|
||||
kvCache.actualizeAll(parentRepo)
|
||||
}
|
||||
|
||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
|
||||
@@ -69,6 +65,10 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
{ getById(id) },
|
||||
{ it ?.let { set(idGetter(it), it) } }
|
||||
)
|
||||
|
||||
override suspend fun invalidate() {
|
||||
actualizeAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
||||
@@ -92,7 +92,11 @@ open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
scope,
|
||||
idGetter
|
||||
),
|
||||
CRUDRepo<ObjectType, IdType, InputValueType>
|
||||
CRUDRepo<ObjectType, IdType, InputValueType> {
|
||||
override suspend fun invalidate() {
|
||||
actualizeAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
||||
kvCache: FullKVCache<IdType, ObjectType>,
|
||||
|
@@ -5,6 +5,7 @@ import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -68,6 +69,10 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
|
||||
{ parentRepo.keys(v, pagination, reversed) },
|
||||
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||
)
|
||||
|
||||
override suspend fun invalidate() {
|
||||
actualizeAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
||||
@@ -81,6 +86,10 @@ open class FullWriteKeyValueCacheRepo<Key,Value>(
|
||||
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
|
||||
override suspend fun invalidate() {
|
||||
kvCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
|
||||
@@ -89,13 +98,17 @@ fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
|
||||
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
|
||||
|
||||
open class FullKeyValueCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValueRepo<Key, Value>,
|
||||
override val parentRepo: KeyValueRepo<Key, Value>,
|
||||
kvCache: FullKVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
|
||||
KeyValueRepo<Key,Value>,
|
||||
ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(parentRepo, kvCache) {
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
|
||||
|
||||
override suspend fun invalidate() {
|
||||
kvCache.actualizeAll(parentRepo)
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
||||
|
@@ -5,6 +5,7 @@ import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.*
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
@@ -33,8 +34,7 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
|
||||
}
|
||||
|
||||
protected open suspend fun actualizeAll() {
|
||||
doAllWithCurrentPaging { kvCache.keys(it).also { kvCache.unset(it.results) } }
|
||||
kvCache.set(parentRepo.getAll())
|
||||
kvCache.actualizeAll(parentRepo)
|
||||
}
|
||||
|
||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||
@@ -102,6 +102,9 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
|
||||
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||
)
|
||||
|
||||
override suspend fun invalidate() {
|
||||
actualizeAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||
@@ -125,6 +128,10 @@ open class FullWriteKeyValuesCacheRepo<Key,Value>(
|
||||
kvCache.get(it.first) ?.minus(it.second) ?: return@onEach
|
||||
)
|
||||
}.launchIn(scope)
|
||||
|
||||
override suspend fun invalidate() {
|
||||
kvCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
|
||||
@@ -133,7 +140,7 @@ fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
|
||||
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
|
||||
|
||||
open class FullKeyValuesCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValuesRepo<Key, Value>,
|
||||
override val parentRepo: KeyValuesRepo<Key, Value>,
|
||||
kvCache: FullKVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),
|
||||
@@ -146,6 +153,10 @@ open class FullKeyValuesCacheRepo<Key,Value>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun invalidate() {
|
||||
kvCache.actualizeAll(parentRepo)
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> KeyValuesRepo<Key, Value>.caching(
|
||||
|
61
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/util/ActualizeAll.kt
vendored
Normal file
61
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/util/ActualizeAll.kt
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package dev.inmo.micro_utils.repos.cache.util
|
||||
|
||||
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||
import dev.inmo.micro_utils.repos.set
|
||||
|
||||
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||
getAll: () -> Map<K, V>
|
||||
) {
|
||||
clear()
|
||||
set(getAll())
|
||||
}
|
||||
|
||||
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||
repo: ReadKeyValueRepo<K, V>
|
||||
) {
|
||||
clear()
|
||||
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
|
||||
val initPagination = FirstPagePagination(count)
|
||||
doForAllWithNextPaging(initPagination) {
|
||||
keys(it).also {
|
||||
set(
|
||||
it.results.mapNotNull { k -> repo.get(k) ?.let { k to it } }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
|
||||
repo: ReadKeyValuesRepo<K, V>
|
||||
) {
|
||||
clear()
|
||||
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
|
||||
val initPagination = FirstPagePagination(count)
|
||||
doForAllWithNextPaging(initPagination) {
|
||||
keys(it).also {
|
||||
set(
|
||||
it.results.associateWith { k -> repo.getAll(k) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||
repo: ReadCRUDRepo<V, K>
|
||||
) {
|
||||
clear()
|
||||
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
|
||||
val initPagination = FirstPagePagination(count)
|
||||
doForAllWithNextPaging(initPagination) {
|
||||
keys(it).also {
|
||||
set(
|
||||
it.results.mapNotNull { k -> repo.getById(k) ?.let { k to it } }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@@ -93,6 +93,10 @@ suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
vararg toSet: Pair<Key, Value>
|
||||
) = set(toSet.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
toSet: List<Pair<Key, Value>>
|
||||
) = set(toSet.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
k: Key, v: Value
|
||||
) = set(k to v)
|
||||
@@ -125,7 +129,11 @@ interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValue
|
||||
* By default, will remove all the data of current repo using [doAllWithCurrentPaging], [keys] and [unset]
|
||||
*/
|
||||
suspend fun clear() {
|
||||
doAllWithCurrentPaging { keys(it).also { unset(it.results) } }
|
||||
var count: Int
|
||||
do {
|
||||
count = count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
|
||||
keys(FirstPagePagination(count)).also { unset(it.results) }
|
||||
} while(count > 0)
|
||||
}
|
||||
}
|
||||
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>
|
||||
|
@@ -0,0 +1,23 @@
|
||||
package dev.inmo.micro_utils.repos.exposed
|
||||
|
||||
import org.jetbrains.exposed.sql.Column
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNotNull
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNull
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.neq
|
||||
|
||||
fun <T> Column<T?>.eqOrIsNull(
|
||||
value: T?
|
||||
) = if (value == null) {
|
||||
isNull()
|
||||
} else {
|
||||
eq(value)
|
||||
}
|
||||
|
||||
fun <T> Column<T?>.neqOrIsNotNull(
|
||||
value: T?
|
||||
) = if (value == null) {
|
||||
isNotNull()
|
||||
} else {
|
||||
neq(value)
|
||||
}
|
@@ -1,12 +1,17 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.createStandardWebsocketFlow
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.deletedObjectsIdsFlowRouting
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.newObjectsFlowRouting
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.updatedObjectsFlowRouting
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.util.reflect.TypeInfo
|
||||
import io.ktor.util.reflect.typeInfo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.*
|
||||
|
||||
class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
||||
@@ -21,6 +26,15 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
||||
baseUrl: String,
|
||||
httpClient: HttpClient,
|
||||
contentType: ContentType,
|
||||
newObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, newObjectsFlowRouting),
|
||||
),
|
||||
updatedObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
|
||||
),
|
||||
deletedObjectsIdsFlow: Flow<IdType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
|
||||
),
|
||||
noinline idSerializer: suspend (IdType) -> String
|
||||
) = KtorCRUDRepoClient(
|
||||
KtorReadCRUDRepoClient(
|
||||
@@ -35,7 +49,10 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
||||
KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
|
||||
baseUrl,
|
||||
httpClient,
|
||||
contentType
|
||||
contentType,
|
||||
newObjectsFlow,
|
||||
updatedObjectsFlow,
|
||||
deletedObjectsIdsFlow
|
||||
)
|
||||
)
|
||||
|
||||
@@ -44,11 +61,23 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
||||
subpart: String,
|
||||
httpClient: HttpClient,
|
||||
contentType: ContentType,
|
||||
newObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, newObjectsFlowRouting),
|
||||
),
|
||||
updatedObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
|
||||
),
|
||||
deletedObjectsIdsFlow: Flow<IdType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
|
||||
),
|
||||
noinline idSerializer: suspend (IdType) -> String
|
||||
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
|
||||
buildStandardUrl(baseUrl, subpart),
|
||||
httpClient,
|
||||
contentType,
|
||||
newObjectsFlow,
|
||||
updatedObjectsFlow,
|
||||
deletedObjectsIdsFlow,
|
||||
idSerializer
|
||||
)
|
||||
}
|
||||
@@ -80,11 +109,23 @@ inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepo
|
||||
subpart: String,
|
||||
httpClient: HttpClient,
|
||||
contentType: ContentType,
|
||||
newObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, newObjectsFlowRouting),
|
||||
),
|
||||
updatedObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
|
||||
),
|
||||
deletedObjectsIdsFlow: Flow<IdType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
|
||||
),
|
||||
noinline idSerializer: suspend (IdType) -> String
|
||||
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
|
||||
buildStandardUrl(baseUrl, subpart),
|
||||
httpClient,
|
||||
contentType,
|
||||
newObjectsFlow,
|
||||
updatedObjectsFlow,
|
||||
deletedObjectsIdsFlow,
|
||||
idSerializer
|
||||
)
|
||||
|
||||
|
@@ -53,19 +53,22 @@ class KtorWriteCrudRepoClient<ObjectType, IdType, InputValue> (
|
||||
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
|
||||
baseUrl: String,
|
||||
httpClient: HttpClient,
|
||||
contentType: ContentType
|
||||
contentType: ContentType,
|
||||
newObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, newObjectsFlowRouting),
|
||||
),
|
||||
updatedObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
|
||||
),
|
||||
deletedObjectsIdsFlow: Flow<IdType> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
|
||||
),
|
||||
) = KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
|
||||
baseUrl,
|
||||
httpClient,
|
||||
httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, newObjectsFlowRouting),
|
||||
),
|
||||
httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
|
||||
),
|
||||
httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
|
||||
),
|
||||
newObjectsFlow,
|
||||
updatedObjectsFlow,
|
||||
deletedObjectsIdsFlow,
|
||||
{
|
||||
contentType(contentType)
|
||||
setBody(it)
|
||||
|
@@ -1,10 +1,14 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.key.value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.createStandardWebsocketFlow
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.onNewValueRoute
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.onValueRemovedRoute
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.encodeURLQueryComponent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.*
|
||||
|
||||
class KtorKeyValueRepoClient<Key, Value> (
|
||||
@@ -20,6 +24,12 @@ class KtorKeyValueRepoClient<Key, Value> (
|
||||
httpClient: HttpClient,
|
||||
contentType: ContentType,
|
||||
noinline idSerializer: suspend (Key) -> String,
|
||||
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
),
|
||||
onValueRemoved: Flow<Key> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
),
|
||||
noinline valueSerializer: suspend (Value) -> String
|
||||
) = KtorKeyValueRepoClient(
|
||||
KtorReadKeyValueRepoClient(
|
||||
@@ -28,7 +38,9 @@ class KtorKeyValueRepoClient<Key, Value> (
|
||||
KtorWriteKeyValueRepoClient(
|
||||
baseUrl,
|
||||
httpClient,
|
||||
contentType
|
||||
contentType,
|
||||
onNewValue,
|
||||
onValueRemoved
|
||||
)
|
||||
)
|
||||
inline operator fun <reified Key, reified Value> invoke(
|
||||
@@ -37,12 +49,20 @@ class KtorKeyValueRepoClient<Key, Value> (
|
||||
httpClient: HttpClient,
|
||||
contentType: ContentType,
|
||||
noinline idSerializer: suspend (Key) -> String,
|
||||
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
),
|
||||
onValueRemoved: Flow<Key> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
),
|
||||
noinline valueSerializer: suspend (Value) -> String
|
||||
) = KtorKeyValueRepoClient(
|
||||
buildStandardUrl(baseUrl, subpart),
|
||||
httpClient,
|
||||
contentType,
|
||||
idSerializer,
|
||||
onNewValue,
|
||||
onValueRemoved,
|
||||
valueSerializer
|
||||
)
|
||||
}
|
||||
|
@@ -60,17 +60,19 @@ class KtorWriteKeyValueRepoClient<Key, Value>(
|
||||
inline operator fun <reified Key, reified Value> invoke(
|
||||
baseUrl: String,
|
||||
httpClient: HttpClient,
|
||||
contentType: ContentType
|
||||
contentType: ContentType,
|
||||
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
),
|
||||
onValueRemoved: Flow<Key> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
),
|
||||
) = KtorWriteKeyValueRepoClient<Key, Value>(
|
||||
baseUrl,
|
||||
httpClient,
|
||||
contentType,
|
||||
httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
),
|
||||
httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
),
|
||||
onNewValue,
|
||||
onValueRemoved,
|
||||
typeInfo<List<Key>>(),
|
||||
typeInfo<List<Value>>(),
|
||||
typeInfo<Map<Key, Value>>()
|
||||
|
@@ -1,10 +1,15 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.key.values
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.createStandardWebsocketFlow
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.onDataClearedRoute
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.onNewValueRoute
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.onValueRemovedRoute
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.encodeURLQueryComponent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.*
|
||||
|
||||
class KtorKeyValuesRepoClient<Key, Value> (
|
||||
@@ -20,6 +25,15 @@ class KtorKeyValuesRepoClient<Key, Value> (
|
||||
httpClient: HttpClient,
|
||||
contentType: ContentType,
|
||||
noinline keySerializer: suspend (Key) -> String,
|
||||
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
),
|
||||
onValueRemoved: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
),
|
||||
onDataCleared: Flow<Key> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onDataClearedRoute),
|
||||
),
|
||||
noinline valueSerializer: suspend (Value) -> String
|
||||
) = KtorKeyValuesRepoClient(
|
||||
KtorReadKeyValuesRepoClient(
|
||||
@@ -32,7 +46,10 @@ class KtorKeyValuesRepoClient<Key, Value> (
|
||||
KtorWriteKeyValuesRepoClient(
|
||||
baseUrl,
|
||||
httpClient,
|
||||
contentType
|
||||
contentType,
|
||||
onNewValue,
|
||||
onValueRemoved,
|
||||
onDataCleared
|
||||
)
|
||||
)
|
||||
inline operator fun <reified Key : Any, reified Value : Any> invoke(
|
||||
@@ -41,12 +58,24 @@ class KtorKeyValuesRepoClient<Key, Value> (
|
||||
httpClient: HttpClient,
|
||||
contentType: ContentType,
|
||||
noinline keySerializer: suspend (Key) -> String,
|
||||
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
),
|
||||
onValueRemoved: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
),
|
||||
onDataCleared: Flow<Key> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onDataClearedRoute),
|
||||
),
|
||||
noinline valueSerializer: suspend (Value) -> String
|
||||
) = KtorKeyValuesRepoClient(
|
||||
buildStandardUrl(baseUrl, subpart),
|
||||
httpClient,
|
||||
contentType,
|
||||
keySerializer,
|
||||
onNewValue,
|
||||
onValueRemoved,
|
||||
onDataCleared,
|
||||
valueSerializer
|
||||
)
|
||||
}
|
||||
@@ -59,13 +88,25 @@ inline fun <reified Key : Any, reified Value : Any> KtorKeyValuesRepoClient(
|
||||
keySerializer: SerializationStrategy<Key>,
|
||||
valueSerializer: SerializationStrategy<Value>,
|
||||
serialFormat: StringFormat,
|
||||
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
),
|
||||
onValueRemoved: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
),
|
||||
onDataCleared: Flow<Key> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onDataClearedRoute),
|
||||
),
|
||||
) = KtorKeyValuesRepoClient<Key, Value>(
|
||||
baseUrl,
|
||||
httpClient,
|
||||
contentType,
|
||||
{
|
||||
serialFormat.encodeToString(keySerializer, it).encodeURLQueryComponent()
|
||||
}
|
||||
},
|
||||
onNewValue,
|
||||
onValueRemoved,
|
||||
onDataCleared
|
||||
) {
|
||||
serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent()
|
||||
}
|
||||
@@ -77,13 +118,25 @@ inline fun <reified Key : Any, reified Value : Any> KtorKeyValuesRepoClient(
|
||||
keySerializer: SerializationStrategy<Key>,
|
||||
valueSerializer: SerializationStrategy<Value>,
|
||||
serialFormat: BinaryFormat,
|
||||
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
),
|
||||
onValueRemoved: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
),
|
||||
onDataCleared: Flow<Key> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onDataClearedRoute),
|
||||
),
|
||||
) = KtorKeyValuesRepoClient<Key, Value>(
|
||||
baseUrl,
|
||||
httpClient,
|
||||
contentType,
|
||||
{
|
||||
serialFormat.encodeHex(keySerializer, it)
|
||||
}
|
||||
},
|
||||
onNewValue,
|
||||
onValueRemoved,
|
||||
onDataCleared
|
||||
) {
|
||||
serialFormat.encodeHex(valueSerializer, it)
|
||||
}
|
||||
|
@@ -84,20 +84,23 @@ class KtorWriteKeyValuesRepoClient<Key : Any, Value : Any>(
|
||||
inline operator fun <reified Key : Any, reified Value : Any> invoke(
|
||||
baseUrl: String,
|
||||
httpClient: HttpClient,
|
||||
contentType: ContentType
|
||||
contentType: ContentType,
|
||||
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
),
|
||||
onValueRemoved: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
),
|
||||
onDataCleared: Flow<Key> = httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onDataClearedRoute),
|
||||
),
|
||||
) = KtorWriteKeyValuesRepoClient<Key, Value>(
|
||||
baseUrl,
|
||||
httpClient,
|
||||
contentType,
|
||||
httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
),
|
||||
httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
),
|
||||
httpClient.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onDataClearedRoute),
|
||||
),
|
||||
onNewValue,
|
||||
onValueRemoved,
|
||||
onDataCleared,
|
||||
typeInfo<Key>(),
|
||||
typeInfo<Value>(),
|
||||
typeInfo<Map<Key, List<Value>>>()
|
||||
|
@@ -14,6 +14,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.SerialFormat
|
||||
import kotlinx.serialization.StringFormat
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.decodeFromJsonElement
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.KoinApplication
|
||||
@@ -93,18 +94,19 @@ object StartLauncherPlugin : StartPlugin {
|
||||
|
||||
/**
|
||||
* Will create [KoinApplication], init, load modules using [StartLauncherPlugin] and start plugins using the same base
|
||||
* plugin
|
||||
* plugin. It is basic [start] method which accepts both [config] and [rawConfig] which suppose to be the same or
|
||||
* at least [rawConfig] must contain serialized variant of [config]
|
||||
*
|
||||
* @param rawConfig It is expected that this [JsonObject] will contain serialized [Config] ([StartLauncherPlugin] will
|
||||
* deserialize it in its [StartLauncherPlugin.setupDI]
|
||||
*/
|
||||
suspend fun start(rawConfig: JsonObject) {
|
||||
suspend fun start(config: Config, rawConfig: JsonObject) {
|
||||
|
||||
logger.i("Start initialization")
|
||||
val koinApp = KoinApplication.init()
|
||||
koinApp.modules(
|
||||
module {
|
||||
setupDI(rawConfig)
|
||||
setupDI(config, rawConfig)
|
||||
}
|
||||
)
|
||||
logger.i("Modules loaded")
|
||||
@@ -116,26 +118,26 @@ object StartLauncherPlugin : StartPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create [KoinApplication], init, load modules using [StartLauncherPlugin] and start plugins using the same base
|
||||
* plugin
|
||||
* Call [start] with deserialized [Config] as config and [rawConfig] as is
|
||||
*
|
||||
* @param config In difference with other [start] method here config is already deserialized and this config will
|
||||
* be converted to [JsonObject] as raw config. That means that all plugins from [config] will receive
|
||||
* serialized version of [config] in [StartPlugin.setupDI] method
|
||||
* @param rawConfig It is expected that this [JsonObject] will contain serialized [Config]
|
||||
*/
|
||||
suspend fun start(rawConfig: JsonObject) {
|
||||
|
||||
start(defaultJson.decodeFromJsonElement(Config.serializer(), rawConfig), rawConfig)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Call [start] with deserialized [Config] as is and serialize it to [JsonObject] to pass as the first parameter
|
||||
* to the basic [start] method
|
||||
*
|
||||
* @param config Will be converted to [JsonObject] as raw config. That means that all plugins from [config] will
|
||||
* receive serialized version of [config] in [StartPlugin.setupDI] method
|
||||
*/
|
||||
suspend fun start(config: Config) {
|
||||
|
||||
logger.i("Start initialization")
|
||||
val koinApp = KoinApplication.init()
|
||||
logger.i("Koin app created")
|
||||
koinApp.modules(
|
||||
module {
|
||||
setupDI(config)
|
||||
}
|
||||
)
|
||||
startKoin(koinApp)
|
||||
logger.i("Koin started")
|
||||
startPlugin(koinApp.koin)
|
||||
start(config, defaultJson.encodeToJsonElement(Config.serializer(), config).jsonObject)
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package dev.inmo.micro_utils.startup.launcher
|
||||
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.LogLevel
|
||||
import dev.inmo.kslog.common.i
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import java.io.File
|
||||
@@ -23,10 +24,25 @@ import java.io.File
|
||||
* In that case in `build/distributions` folder you will be able to find zip and tar files with all required
|
||||
* tools for application running (via their `bin/app_name` binary). In that case yoy will not need to pass
|
||||
* `--args=...` and launch will look like `./bin/app_name sample.config.json`
|
||||
*
|
||||
* ## Debug mode
|
||||
*
|
||||
* You may pass the second parameter, one of [LogLevel] enum variants to setup [KSLog] minimal logging level. Sample:
|
||||
*
|
||||
* ```bash
|
||||
* ./gradlew run --args="sample.config.json DEBUG" // enable debugging output
|
||||
* ```
|
||||
*
|
||||
* OR
|
||||
* ```bash
|
||||
* ./gradlew run --args="sample.config.json WARNING" // enable logging since WARNING
|
||||
* ```
|
||||
*
|
||||
* **Default level is [LogLevel.INFO]**
|
||||
*/
|
||||
suspend fun main(args: Array<String>) {
|
||||
|
||||
KSLog.default = KSLog("Launcher")
|
||||
KSLog.default = KSLog("Launcher", args.getOrNull(1) ?.let { LogLevel.valueOf(it) } ?: LogLevel.INFO)
|
||||
val (configPath) = args
|
||||
val file = File(configPath)
|
||||
KSLog.i("Start read config from ${file.absolutePath}")
|
||||
|
Reference in New Issue
Block a user