mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
101 Commits
Author | SHA1 | Date | |
---|---|---|---|
9493e97a38 | |||
88bd770260 | |||
a7bd33b7bf | |||
73c724a2e5 | |||
d8cf3c6726 | |||
15dee238b5 | |||
c70626734e | |||
5a765ea1bc | |||
8215f9d2c6 | |||
d2e6d2ec80 | |||
3718181e8f | |||
0d825cf424 | |||
28a804d988 | |||
9e4bb9d678 | |||
9c40d7da3d | |||
2b76ad0aa9 | |||
e4b619e050 | |||
36c09feaf2 | |||
2d68321503 | |||
85455ab21c | |||
18d63eb980 | |||
2e429e9704 | |||
f4af28059b | |||
c1476bd075 | |||
16c720fddd | |||
8b4b4a5eca | |||
32e6e5b7e2 | |||
a9f7fd8e32 | |||
95be1a26f2 | |||
ef9b31aee0 | |||
df3c01ff0a | |||
4704c5a33d | |||
225c06550a | |||
f0987614c6 | |||
269c2876f3 | |||
168d6acf7c | |||
a5f718e257 | |||
4f68459582 | |||
442db122cf | |||
580d757be2 | |||
47b0f6d2d8 | |||
3f6f6ebc2b | |||
2645ea29d6 | |||
79f2041565 | |||
4a7567f288 | |||
8a890ed6ed | |||
3d90df6897 | |||
681c13144a | |||
b64f2e6d32 | |||
428eabb1bd | |||
2162e83bce | |||
6142022283 | |||
e6d9c8250f | |||
46178e723b | |||
605f55acd2 | |||
0f8b69aa60 | |||
551d8ec480 | |||
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 | |||
8b43d785cc | |||
b62d3a0b7d | |||
fad73c7213 | |||
2403c7c2b0 | |||
fa090bf920 | |||
a83ee86340 | |||
204955bcce | |||
ee56e9543a | |||
96fdff6ffd | |||
58b007cbb3 | |||
4f0c139889 | |||
c584c24fce | |||
85e5cee154 | |||
5af91981f1 | |||
2fe4f08059 | |||
83796f345a | |||
c1e21364a6 | |||
067d9d0d3b |
124
CHANGELOG.md
124
CHANGELOG.md
@@ -1,5 +1,129 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.17.0
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.7.20` -> `1.8.10`
|
||||||
|
* `Serialization`: `1.4.1` -> `1.5.0`
|
||||||
|
* `KSLog`: `0.5.4` -> `1.0.0`
|
||||||
|
* `AppCompat`: `1.6.0` -> `1.6.1`
|
||||||
|
|
||||||
|
## 0.16.13
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Generator`:
|
||||||
|
* Module has been created
|
||||||
|
|
||||||
|
## 0.16.12
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Exposed`:
|
||||||
|
* `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach
|
||||||
|
* `Koin`:
|
||||||
|
* `Generator`:
|
||||||
|
* Module has been created
|
||||||
|
|
||||||
|
## 0.16.11
|
||||||
|
|
||||||
|
* `LanguageCodes`:
|
||||||
|
* In android and JVM targets now available `toJavaLocale` and from Java `Locale` conversations from/to
|
||||||
|
`IetfLanguageCode`
|
||||||
|
|
||||||
|
## 0.16.10
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Cache`:
|
||||||
|
* New transformer type: `ReadCRUDFromKeyValueRepo`
|
||||||
|
* New transformer type: `ReadKeyValueFromCRUDRepo`
|
||||||
|
* `Pagination`:
|
||||||
|
* New `paginate` extensions with `reversed` support for `List`/`Set`
|
||||||
|
|
||||||
|
## 0.16.9
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Koin`: `3.2.2` -> `3.3.2`
|
||||||
|
* `AppCompat`: `1.5.1` -> `1.6.0`
|
||||||
|
* `Ktor`:
|
||||||
|
* `Client`
|
||||||
|
* `HttpResponse.bodyOrNull` now retrieve callback to check if body should be received or null
|
||||||
|
* New extension `HttpResponse.bodyOrNullOnNoContent`
|
||||||
|
|
||||||
|
## 0.16.8
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Ktor`: `2.2.2` -> `2.2.3`
|
||||||
|
* `Ktor`:
|
||||||
|
* `Client`
|
||||||
|
* Fixes in `HttpClient.uniUpload`
|
||||||
|
* `Server`
|
||||||
|
* Fixes in `PartData.FileItem.download`
|
||||||
|
* `Repos`:
|
||||||
|
* `Cache`:
|
||||||
|
* New type of caches: `FallbackCacheRepo`
|
||||||
|
* Fixes in `Write*` variants of cached repos
|
||||||
|
* New type `ActionWrapper`
|
||||||
|
* New `AutoRecache*` classes for all types of repos as `FallbackCacheRepo`s
|
||||||
|
* `Common`:
|
||||||
|
* New transformations for key-value and key-values vice-verse
|
||||||
|
* Fixes in `FileReadKeyValueRepo`
|
||||||
|
|
||||||
|
## 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`:
|
||||||
|
* Create `launchInCurrentThread`
|
||||||
|
|
||||||
|
## 0.16.3
|
||||||
|
|
||||||
|
* `Startup`:
|
||||||
|
* `Launcher`:
|
||||||
|
* All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now
|
||||||
|
|
||||||
|
## 0.16.2
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Compose`: `1.2.1` -> `1.2.2`
|
||||||
|
* `Startup`:
|
||||||
|
* Module become available on `JS` target
|
||||||
|
|
||||||
## 0.16.1
|
## 0.16.1
|
||||||
|
|
||||||
* `Coroutines`:
|
* `Coroutines`:
|
||||||
|
@@ -9,6 +9,7 @@ buildscript {
|
|||||||
dependencies {
|
dependencies {
|
||||||
classpath libs.buildscript.kt.gradle
|
classpath libs.buildscript.kt.gradle
|
||||||
classpath libs.buildscript.kt.serialization
|
classpath libs.buildscript.kt.serialization
|
||||||
|
classpath libs.buildscript.kt.ksp
|
||||||
classpath libs.buildscript.jb.dokka
|
classpath libs.buildscript.jb.dokka
|
||||||
classpath libs.buildscript.gh.release
|
classpath libs.buildscript.gh.release
|
||||||
classpath libs.buildscript.android.gradle
|
classpath libs.buildscript.android.gradle
|
||||||
|
@@ -18,6 +18,7 @@ kotlin {
|
|||||||
api project(":micro_utils.coroutines")
|
api project(":micro_utils.coroutines")
|
||||||
api libs.android.fragment
|
api libs.android.fragment
|
||||||
}
|
}
|
||||||
|
dependsOn jvmMain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
private inline fun <T> getObject(
|
private inline fun <T> getObject(
|
||||||
additional: MutableList<T>,
|
additional: MutableList<T>,
|
||||||
iterator: Iterator<T>
|
iterator: Iterator<T>
|
||||||
@@ -24,13 +26,14 @@ private inline fun <T> getObject(
|
|||||||
*
|
*
|
||||||
* @see calculateDiff
|
* @see calculateDiff
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
data class Diff<T> internal constructor(
|
data class Diff<T> internal constructor(
|
||||||
val removed: List<IndexedValue<T>>,
|
val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
|
||||||
/**
|
/**
|
||||||
* Old-New values pairs
|
* Old-New values pairs
|
||||||
*/
|
*/
|
||||||
val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
|
||||||
val added: List<IndexedValue<T>>
|
val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
|
||||||
)
|
)
|
||||||
|
|
||||||
private inline fun <T> performChanges(
|
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
|
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].
|
* Executes the given [action] until getting of successful result specified number of [times].
|
||||||
*
|
*
|
||||||
@@ -10,12 +32,23 @@ inline fun <R> repeatOnFailure(
|
|||||||
onEachFailure: (Throwable) -> Unit = {},
|
onEachFailure: (Throwable) -> Unit = {},
|
||||||
action: (Int) -> R
|
action: (Int) -> R
|
||||||
): Optional<R> {
|
): Optional<R> {
|
||||||
repeat(times) {
|
var i = 0
|
||||||
runCatching {
|
val result = repeatOnFailure(
|
||||||
action(it)
|
{
|
||||||
}.onFailure(onEachFailure).onSuccess {
|
onEachFailure(it)
|
||||||
return Optional.presented(it)
|
if (i < times) {
|
||||||
|
i++
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
action(i)
|
||||||
|
}
|
||||||
|
return if (result.isSuccess) {
|
||||||
|
Optional.presented(result.getOrThrow())
|
||||||
|
} else {
|
||||||
|
Optional.absent()
|
||||||
}
|
}
|
||||||
return Optional.absent()
|
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api libs.kt.coroutines.android
|
api libs.kt.coroutines.android
|
||||||
}
|
}
|
||||||
|
dependsOn(jvmMain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,24 +3,58 @@ package dev.inmo.micro_utils.coroutines.compose
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import dev.inmo.micro_utils.common.applyDiff
|
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 dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
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")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
|
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
|
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||||
|
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
): SnapshotStateList<T> {
|
): SnapshotStateList<T> {
|
||||||
val state = mutableStateListOf<T>()
|
val state = mutableStateListOf<T>()
|
||||||
subscribeSafelyWithoutExceptions(scope) {
|
val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let {
|
||||||
|
{
|
||||||
|
withContext(useContextOnChange) {
|
||||||
|
state.applyDiff(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: {
|
||||||
state.applyDiff(it)
|
state.applyDiff(it)
|
||||||
}
|
}
|
||||||
|
subscribeSafelyWithoutExceptions(scope, onException, changeBlock)
|
||||||
return state
|
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")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified T> Flow<List<T>>.asComposeList(
|
inline fun <reified T> Flow<List<T>>.asComposeList(
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
): List<T> = asMutableComposeListState(scope)
|
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
|
package dev.inmo.micro_utils.coroutines.compose
|
||||||
|
|
||||||
import androidx.compose.runtime.*
|
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 dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
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(
|
fun <T> Flow<T>.asMutableComposeState(
|
||||||
initial: T,
|
initial: T,
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
|
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||||
|
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
): MutableState<T> {
|
): MutableState<T> {
|
||||||
val state = mutableStateOf(initial)
|
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
|
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")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <T> StateFlow<T>.asMutableComposeState(
|
inline fun <T> StateFlow<T>.asMutableComposeState(
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
): MutableState<T> = asMutableComposeState(value, scope)
|
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(
|
fun <T> Flow<T>.asComposeState(
|
||||||
initial: T,
|
initial: T,
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
|
useContextOnChange: CoroutineContext? = Dispatchers.Main,
|
||||||
|
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||||
): State<T> {
|
): State<T> {
|
||||||
val state = asMutableComposeState(initial, scope)
|
val state = asMutableComposeState(initial, scope, useContextOnChange, onException)
|
||||||
return derivedStateOf { state.value }
|
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")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <T> StateFlow<T>.asComposeState(
|
inline fun <T> StateFlow<T>.asComposeState(
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope,
|
||||||
): State<T> = asComposeState(value, scope)
|
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.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(initial, scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
|
||||||
fun <T> Flow<T>.toMutableState(
|
fun <T> Flow<T>.toMutableState(
|
||||||
initial: T,
|
initial: T,
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope
|
||||||
): MutableState<T> {
|
): MutableState<T> = asMutableComposeState(initial, scope)
|
||||||
val state = mutableStateOf(initial)
|
|
||||||
subscribeSafelyWithoutExceptions(scope) { state.value = it }
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <T> StateFlow<T>.toMutableState(
|
inline fun <T> StateFlow<T>.toMutableState(
|
||||||
scope: CoroutineScope
|
scope: CoroutineScope
|
||||||
): MutableState<T> = toMutableState(value, scope)
|
): MutableState<T> = asMutableComposeState(scope)
|
||||||
|
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
|
fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T {
|
||||||
|
val scope = CoroutineScope(Dispatchers.Unconfined)
|
||||||
|
return scope.launchSynchronously(block)
|
||||||
|
}
|
@@ -6,7 +6,7 @@ fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T
|
|||||||
var result: Result<T>? = null
|
var result: Result<T>? = null
|
||||||
val objectToSynchronize = Object()
|
val objectToSynchronize = Object()
|
||||||
synchronized(objectToSynchronize) {
|
synchronized(objectToSynchronize) {
|
||||||
launch {
|
launch(start = CoroutineStart.UNDISPATCHED) {
|
||||||
result = safelyWithResult(block)
|
result = safelyWithResult(block)
|
||||||
}.invokeOnCompletion {
|
}.invokeOnCompletion {
|
||||||
synchronized(objectToSynchronize) {
|
synchronized(objectToSynchronize) {
|
||||||
|
@@ -0,0 +1,47 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class LaunchInCurrentThreadTests {
|
||||||
|
@Test
|
||||||
|
fun simpleTestThatLaunchInCurrentThreadWorks() {
|
||||||
|
val expectedResult = 10
|
||||||
|
val result = launchInCurrentThread {
|
||||||
|
expectedResult
|
||||||
|
}
|
||||||
|
assertEquals(expectedResult, result)
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun simpleTestThatSeveralLaunchInCurrentThreadWorks() {
|
||||||
|
val testData = 0 until 100
|
||||||
|
|
||||||
|
testData.forEach {
|
||||||
|
val result = launchInCurrentThread {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
assertEquals(it, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun simpleTestThatLaunchInCurrentThreadWillCorrectlyHandleSuspensionsWorks() {
|
||||||
|
val testData = 0 until 100
|
||||||
|
|
||||||
|
suspend fun test(data: Any): Any {
|
||||||
|
return withContext(Dispatchers.Default) {
|
||||||
|
delay(1)
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testData.forEach {
|
||||||
|
val result = launchInCurrentThread {
|
||||||
|
test(it)
|
||||||
|
}
|
||||||
|
assertEquals(it, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,30 +1,5 @@
|
|||||||
apply plugin: 'com.getkeepsafe.dexcount'
|
apply plugin: 'com.getkeepsafe.dexcount'
|
||||||
|
|
||||||
ext {
|
|
||||||
jvmKotlinFolderFile = {
|
|
||||||
String sep = File.separator
|
|
||||||
return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
|
|
||||||
}
|
|
||||||
|
|
||||||
enableIncludingJvmCodeInAndroidPart = {
|
|
||||||
File jvmKotlinFolder = jvmKotlinFolderFile()
|
|
||||||
if (jvmKotlinFolder.exists()) {
|
|
||||||
android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disableIncludingJvmCodeInAndroidPart = {
|
|
||||||
File jvmKotlinFolder = jvmKotlinFolderFile()
|
|
||||||
String[] oldDirs = android.sourceSets.main.java.srcDirs
|
|
||||||
android.sourceSets.main.java.srcDirs = []
|
|
||||||
for (oldDir in oldDirs) {
|
|
||||||
if (oldDir != jvmKotlinFolder.path) {
|
|
||||||
android.sourceSets.main.java.srcDirs += oldDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
||||||
buildToolsVersion libs.versions.android.props.buildTools.get()
|
buildToolsVersion libs.versions.android.props.buildTools.get()
|
||||||
@@ -58,10 +33,4 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
String sep = File.separator
|
|
||||||
main.java.srcDirs += "src${sep}main${sep}kotlin"
|
|
||||||
enableIncludingJvmCodeInAndroidPart()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -23,11 +23,12 @@ allprojects {
|
|||||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
||||||
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
|
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
|
||||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
||||||
|
mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle"
|
||||||
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
||||||
|
|
||||||
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
||||||
|
|
||||||
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
||||||
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
|
publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.16.1
|
version=0.17.0
|
||||||
android_code_version=169
|
android_code_version=182
|
||||||
|
@@ -1,37 +1,40 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kt = "1.7.20"
|
kt = "1.8.10"
|
||||||
kt-serialization = "1.4.1"
|
kt-serialization = "1.5.0"
|
||||||
kt-coroutines = "1.6.4"
|
kt-coroutines = "1.6.4"
|
||||||
|
|
||||||
kslog = "0.5.4"
|
kslog = "1.0.0"
|
||||||
|
|
||||||
jb-compose = "1.2.1"
|
jb-compose = "1.3.1-rc01"
|
||||||
jb-exposed = "0.41.1"
|
jb-exposed = "0.41.1"
|
||||||
jb-dokka = "1.7.20"
|
jb-dokka = "1.7.20"
|
||||||
|
|
||||||
klock = "3.4.0"
|
klock = "3.4.0"
|
||||||
uuid = "0.6.0"
|
uuid = "0.7.0"
|
||||||
|
|
||||||
ktor = "2.2.1"
|
ktor = "2.2.3"
|
||||||
|
|
||||||
gh-release = "2.4.1"
|
gh-release = "2.4.1"
|
||||||
|
|
||||||
koin = "3.2.2"
|
koin = "3.3.2"
|
||||||
|
|
||||||
android-gradle = "7.3.0"
|
ksp = "1.8.10-1.0.9"
|
||||||
dexcount = "3.1.0"
|
kotlin-poet = "1.12.0"
|
||||||
|
|
||||||
|
android-gradle = "7.3.1"
|
||||||
|
dexcount = "4.0.0"
|
||||||
|
|
||||||
android-coreKtx = "1.9.0"
|
android-coreKtx = "1.9.0"
|
||||||
android-recyclerView = "1.2.1"
|
android-recyclerView = "1.2.1"
|
||||||
android-appCompat = "1.5.1"
|
android-appCompat = "1.6.1"
|
||||||
android-fragment = "1.5.5"
|
android-fragment = "1.5.5"
|
||||||
android-espresso = "3.4.0"
|
android-espresso = "3.5.1"
|
||||||
android-test = "1.1.3"
|
android-test = "1.1.5"
|
||||||
|
|
||||||
android-props-minSdk = "21"
|
android-props-minSdk = "21"
|
||||||
android-props-compileSdk = "33"
|
android-props-compileSdk = "33"
|
||||||
android-props-buildTools = "33.0.0"
|
android-props-buildTools = "33.0.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
||||||
@@ -83,9 +86,16 @@ android-test-junit = { module = "androidx.test.ext:junit", version.ref = "androi
|
|||||||
kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
|
kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
|
||||||
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
|
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
|
||||||
|
|
||||||
|
# ksp dependencies
|
||||||
|
|
||||||
|
kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" }
|
||||||
|
ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
||||||
|
|
||||||
|
# Buildscript
|
||||||
|
|
||||||
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
|
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
|
||||||
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
|
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
|
||||||
|
buildscript-kt-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
|
||||||
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
|
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
|
||||||
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
|
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
|
||||||
buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
|
buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
118
jvm.publish.gradle
Normal file
118
jvm.publish.gradle
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
|
task javadocJar(type: Jar) {
|
||||||
|
from javadoc
|
||||||
|
classifier = 'javadoc'
|
||||||
|
}
|
||||||
|
task sourcesJar(type: Jar) {
|
||||||
|
from sourceSets.main.allSource
|
||||||
|
classifier = 'sources'
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
maven(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
|
||||||
|
artifact javadocJar
|
||||||
|
artifact sourcesJar
|
||||||
|
|
||||||
|
pom {
|
||||||
|
resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
|
||||||
|
description = "It is set of projects with micro tools for avoiding of routines coding"
|
||||||
|
name = "${project.name}"
|
||||||
|
url = "https://github.com/InsanusMokrassar/MicroUtils/"
|
||||||
|
|
||||||
|
scm {
|
||||||
|
developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/MicroUtils.git[push=]https://github.com/InsanusMokrassar/MicroUtils.git"
|
||||||
|
url = "https://github.com/InsanusMokrassar/MicroUtils.git"
|
||||||
|
}
|
||||||
|
|
||||||
|
developers {
|
||||||
|
|
||||||
|
developer {
|
||||||
|
id = "InsanusMokrassar"
|
||||||
|
name = "Aleksei Ovsiannikov"
|
||||||
|
email = "ovsyannikov.alexey95@gmail.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
developer {
|
||||||
|
id = "000Sanya"
|
||||||
|
name = "Syrov Aleksandr"
|
||||||
|
email = "000sanya.000sanya@gmail.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
|
||||||
|
license {
|
||||||
|
name = "Apache Software License 2.0"
|
||||||
|
url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
|
||||||
|
maven {
|
||||||
|
name = "GithubPackages"
|
||||||
|
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
|
||||||
|
|
||||||
|
credentials {
|
||||||
|
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
|
||||||
|
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
|
||||||
|
maven {
|
||||||
|
name = "Gitea"
|
||||||
|
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
|
||||||
|
|
||||||
|
credentials(HttpHeaderCredentials) {
|
||||||
|
name = "Authorization"
|
||||||
|
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
|
||||||
|
}
|
||||||
|
|
||||||
|
authentication {
|
||||||
|
header(HttpHeaderAuthentication)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
|
||||||
|
maven {
|
||||||
|
name = "sonatype"
|
||||||
|
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||||
|
|
||||||
|
credentials {
|
||||||
|
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
||||||
|
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.hasProperty("signing.gnupg.keyName")) {
|
||||||
|
apply plugin: 'signing'
|
||||||
|
|
||||||
|
signing {
|
||||||
|
useGpgCmd()
|
||||||
|
|
||||||
|
sign publishing.publications
|
||||||
|
}
|
||||||
|
|
||||||
|
task signAll {
|
||||||
|
tasks.withType(Sign).forEach {
|
||||||
|
dependsOn(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
jvm.publish.kpsb
Normal file
1
jvm.publish.kpsb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}
|
100
koin/generator/README.md
Normal file
100
koin/generator/README.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Koin generator
|
||||||
|
|
||||||
|
It is Kotlin Symbol Processing generator for `Koin` module in `MicroUtils`.
|
||||||
|
|
||||||
|
1. [What may do this generator](#what-may-do-this-generator)
|
||||||
|
2. [How to add generator](#how-to-add-generator)
|
||||||
|
|
||||||
|
## What may do this generator
|
||||||
|
|
||||||
|
Let's imagine you want to have shortcuts in koin, to get something easily:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val koin: Koin// some initialization
|
||||||
|
|
||||||
|
val someUrl = koin.serverUrl
|
||||||
|
```
|
||||||
|
|
||||||
|
So, in that case you may mark containing file with next annotation (in the beginning of file):
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@file:GenerateKoinDefinition("serverUrl", String::class, nullable = false)
|
||||||
|
```
|
||||||
|
|
||||||
|
If file is called like `Sample.kt`, will be generated file `GeneratedDefinitionsSample.kt` with next content:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
public val Scope.serverUrl: String
|
||||||
|
get() = get(named("serverUrl"))
|
||||||
|
|
||||||
|
public val Koin.serverUrl: String
|
||||||
|
get() = get(named("serverUrl"))
|
||||||
|
|
||||||
|
public fun Module.serverUrlSingle(createdAtStart: Boolean = false,
|
||||||
|
definition: Definition<String>): KoinDefinition<String> =
|
||||||
|
single(named("serverUrl"), createdAtStart = createdAtStart, definition = definition)
|
||||||
|
|
||||||
|
public fun Module.serverUrlFactory(definition: Definition<String>):
|
||||||
|
KoinDefinition<String> = factory(named("serverUrl"), definition = definition)
|
||||||
|
```
|
||||||
|
|
||||||
|
Besides, you may use the generics:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false)
|
||||||
|
```
|
||||||
|
|
||||||
|
Will generate:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
public val Scope.sampleInfo: Sample<G1, G2>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
public val Koin.sampleInfo: Sample<G1, G2>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
|
||||||
|
definition: Definition<Sample<G1, G2>>): KoinDefinition<Sample<G1, G2>> =
|
||||||
|
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
|
||||||
|
|
||||||
|
public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>):
|
||||||
|
KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition)
|
||||||
|
```
|
||||||
|
|
||||||
|
In case you wish not to generate single:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false, generateSingle = false)
|
||||||
|
```
|
||||||
|
|
||||||
|
And you will take next code:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
public val Scope.sampleInfo: Sample<G1, G2>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
public val Koin.sampleInfo: Sample<G1, G2>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>):
|
||||||
|
KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition)
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to add generator
|
||||||
|
|
||||||
|
**Note: $ksp_version in the samples above is equal to supported `ksp` version presented in `/gradle/libs.versions.toml` of project**
|
||||||
|
|
||||||
|
**Note: $microutils_version in the version of MicroUtils library in your project**
|
||||||
|
|
||||||
|
1. Add `classpath` in `build.gradle` (`classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"`)
|
||||||
|
2. Add plugin to the plugins list of your module: `id "com.google.devtools.ksp"`
|
||||||
|
3. In `dependencies` block add to the required target/compile the dependency `dev.inmo:micro_utils.koin.generator:$microutils_version`:
|
||||||
|
```groovy
|
||||||
|
dependencies {
|
||||||
|
add("kspCommonMainMetadata", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in commonMain of your multiplatform module
|
||||||
|
add("kspJvm", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in main of your JVM module
|
||||||
|
}
|
||||||
|
|
||||||
|
ksp { // this generator do not require any arguments and we should left `ksp` empty
|
||||||
|
}
|
||||||
|
```
|
15
koin/generator/build.gradle
Normal file
15
koin/generator/build.gradle
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.jvm"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$publishJvmOnlyPath"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api project(":micro_utils.koin")
|
||||||
|
api libs.kotlin.poet
|
||||||
|
api libs.ksp
|
||||||
|
}
|
178
koin/generator/src/main/kotlin/Processor.kt
Normal file
178
koin/generator/src/main/kotlin/Processor.kt
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package dev.inmo.micro_utils.koin.generator
|
||||||
|
|
||||||
|
import com.google.devtools.ksp.KSTypeNotPresentException
|
||||||
|
import com.google.devtools.ksp.KSTypesNotPresentException
|
||||||
|
import com.google.devtools.ksp.KspExperimental
|
||||||
|
import com.google.devtools.ksp.getAnnotationsByType
|
||||||
|
import com.google.devtools.ksp.processing.CodeGenerator
|
||||||
|
import com.google.devtools.ksp.processing.Resolver
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
|
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||||
|
import com.google.devtools.ksp.symbol.KSFile
|
||||||
|
import com.squareup.kotlinpoet.ClassName
|
||||||
|
import com.squareup.kotlinpoet.FileSpec
|
||||||
|
import com.squareup.kotlinpoet.FunSpec
|
||||||
|
import com.squareup.kotlinpoet.ParameterSpec
|
||||||
|
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||||
|
import com.squareup.kotlinpoet.PropertySpec
|
||||||
|
import com.squareup.kotlinpoet.asTypeName
|
||||||
|
import com.squareup.kotlinpoet.ksp.toClassName
|
||||||
|
import com.squareup.kotlinpoet.ksp.toTypeName
|
||||||
|
import com.squareup.kotlinpoet.ksp.writeTo
|
||||||
|
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
|
||||||
|
import org.koin.core.Koin
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.scope.Scope
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
class Processor(
|
||||||
|
private val codeGenerator: CodeGenerator
|
||||||
|
) : SymbolProcessor {
|
||||||
|
private val definitionClassName = ClassName("org.koin.core.definition", "Definition")
|
||||||
|
private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition")
|
||||||
|
|
||||||
|
@OptIn(KspExperimental::class)
|
||||||
|
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||||
|
resolver.getSymbolsWithAnnotation(
|
||||||
|
GenerateKoinDefinition::class.qualifiedName!!
|
||||||
|
).filterIsInstance<KSFile>().forEach { ksFile ->
|
||||||
|
FileSpec.builder(
|
||||||
|
ksFile.packageName.asString(),
|
||||||
|
"GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}"
|
||||||
|
).apply {
|
||||||
|
addFileComment(
|
||||||
|
"""
|
||||||
|
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||||
|
TO REGENERATE IT JUST DELETE FILE
|
||||||
|
ORIGINAL FILE: ${ksFile.fileName}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach {
|
||||||
|
val type = runCatching {
|
||||||
|
it.type.asTypeName()
|
||||||
|
}.getOrElse { e ->
|
||||||
|
if (e is KSTypeNotPresentException) {
|
||||||
|
e.ksType.toClassName()
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val targetType = runCatching {
|
||||||
|
type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type))
|
||||||
|
}.getOrElse { e ->
|
||||||
|
when (e) {
|
||||||
|
is KSTypeNotPresentException -> e.ksType.toClassName()
|
||||||
|
}
|
||||||
|
if (e is KSTypesNotPresentException) {
|
||||||
|
type.parameterizedBy(*e.ksTypes.map { it.toTypeName() }.toTypedArray())
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}.copy(
|
||||||
|
nullable = it.nullable
|
||||||
|
)
|
||||||
|
fun addGetterProperty(
|
||||||
|
receiver: KClass<*>
|
||||||
|
) {
|
||||||
|
addProperty(
|
||||||
|
PropertySpec.builder(
|
||||||
|
it.name,
|
||||||
|
targetType,
|
||||||
|
).apply {
|
||||||
|
addKdoc(
|
||||||
|
"""
|
||||||
|
@return Definition by key "${it.name}"
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
getter(
|
||||||
|
FunSpec.getterBuilder().apply {
|
||||||
|
addCode(
|
||||||
|
"return " + (if (it.nullable) {
|
||||||
|
"getOrNull"
|
||||||
|
} else {
|
||||||
|
"get"
|
||||||
|
}) + "(named(\"${it.name}\"))"
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
receiver(receiver)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
addGetterProperty(Scope::class)
|
||||||
|
addGetterProperty(Koin::class)
|
||||||
|
|
||||||
|
if (it.generateSingle) {
|
||||||
|
addFunction(
|
||||||
|
FunSpec.builder("${it.name}Single").apply {
|
||||||
|
addKdoc(
|
||||||
|
"""
|
||||||
|
Will register [definition] with [org.koin.core.module.Module.single] and key "${it.name}"
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
receiver(Module::class)
|
||||||
|
addParameter(
|
||||||
|
ParameterSpec.builder(
|
||||||
|
"createdAtStart",
|
||||||
|
Boolean::class
|
||||||
|
).apply {
|
||||||
|
defaultValue("false")
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
addParameter(
|
||||||
|
ParameterSpec.builder(
|
||||||
|
"definition",
|
||||||
|
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
|
||||||
|
).build()
|
||||||
|
)
|
||||||
|
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
|
||||||
|
addCode(
|
||||||
|
"return single(named(\"${it.name}\"), createdAtStart = createdAtStart, definition = definition)"
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.generateFactory) {
|
||||||
|
addFunction(
|
||||||
|
FunSpec.builder("${it.name}Factory").apply {
|
||||||
|
addKdoc(
|
||||||
|
"""
|
||||||
|
Will register [definition] with [org.koin.core.module.Module.factory] and key "${it.name}"
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
receiver(Module::class)
|
||||||
|
addParameter(
|
||||||
|
ParameterSpec.builder(
|
||||||
|
"definition",
|
||||||
|
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
|
||||||
|
).build()
|
||||||
|
)
|
||||||
|
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
|
||||||
|
addCode(
|
||||||
|
"return factory(named(\"${it.name}\"), definition = definition)"
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
addImport("org.koin.core.qualifier", "named")
|
||||||
|
}
|
||||||
|
}.build().let {
|
||||||
|
File(
|
||||||
|
File(ksFile.filePath).parent,
|
||||||
|
"GeneratedDefinitions${ksFile.fileName}"
|
||||||
|
).takeIf { !it.exists() } ?.apply {
|
||||||
|
parentFile.mkdirs()
|
||||||
|
|
||||||
|
writer().use { writer ->
|
||||||
|
it.writeTo(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
11
koin/generator/src/main/kotlin/Provider.kt
Normal file
11
koin/generator/src/main/kotlin/Provider.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package dev.inmo.micro_utils.koin.generator
|
||||||
|
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||||
|
|
||||||
|
class Provider : SymbolProcessorProvider {
|
||||||
|
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(
|
||||||
|
environment.codeGenerator
|
||||||
|
)
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
dev.inmo.micro_utils.koin.generator.Provider
|
27
koin/generator/test/build.gradle
Normal file
27
koin/generator/test/build.gradle
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "com.google.devtools.ksp"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api project(":micro_utils.koin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
add("kspCommonMainMetadata", project(":micro_utils.koin.generator"))
|
||||||
|
}
|
||||||
|
|
||||||
|
ksp {
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||||
|
// TO REGENERATE IT JUST DELETE FILE
|
||||||
|
// ORIGINAL FILE: Test.kt
|
||||||
|
package dev.inmo.micro_utils.koin.generator.test
|
||||||
|
|
||||||
|
import kotlin.Boolean
|
||||||
|
import kotlin.String
|
||||||
|
import org.koin.core.Koin
|
||||||
|
import org.koin.core.definition.Definition
|
||||||
|
import org.koin.core.definition.KoinDefinition
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import org.koin.core.scope.Scope
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Definition by key "sampleInfo"
|
||||||
|
*/
|
||||||
|
public val Scope.sampleInfo: Test<String>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Definition by key "sampleInfo"
|
||||||
|
*/
|
||||||
|
public val Koin.sampleInfo: Test<String>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
|
||||||
|
*/
|
||||||
|
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
|
||||||
|
definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
|
||||||
|
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
|
||||||
|
*/
|
||||||
|
public fun Module.sampleInfoFactory(definition: Definition<Test<String>>):
|
||||||
|
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
|
13
koin/generator/test/src/commonMain/kotlin/Test.kt
Normal file
13
koin/generator/test/src/commonMain/kotlin/Test.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false)
|
||||||
|
package dev.inmo.micro_utils.koin.generator.test
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
|
||||||
|
import org.koin.core.Koin
|
||||||
|
|
||||||
|
class Test<T>(
|
||||||
|
koin: Koin
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
koin.sampleInfo
|
||||||
|
}
|
||||||
|
}
|
1
koin/generator/test/src/main/AndroidManifest.xml
Normal file
1
koin/generator/test/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.koin.generator.test"/>
|
@@ -0,0 +1,28 @@
|
|||||||
|
package dev.inmo.micro_utils.koin.annotations
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this annotation to mark files near to which generator should place generated extensions for koin [org.koin.core.scope.Scope]
|
||||||
|
* and [org.koin.core.Koin]
|
||||||
|
*
|
||||||
|
* @param name Name for definitions. This name will be available as extension for [org.koin.core.scope.Scope] and [org.koin.core.Koin]
|
||||||
|
* @param type Type of extensions. It is base star-typed class
|
||||||
|
* @param typeArgs Generic types for [type]. For example, if [type] == `Something::class` and [typeArgs] == `G1::class,
|
||||||
|
* G2::class`, the result type will be `Something<G1, G2>`
|
||||||
|
* @param nullable In case when true, extension will not throw error when definition has not been registered in koin
|
||||||
|
* @param generateSingle Generate definition factory with [org.koin.core.module.Module.single]. You will be able to use
|
||||||
|
* the extension [org.koin.core.module.Module].[name]Single(createdAtStart/* default false */) { /* your definition */ }
|
||||||
|
* @param generateFactory Generate definition factory with [org.koin.core.module.Module.factory]. You will be able to use
|
||||||
|
* the extension [org.koin.core.module.Module].[name]Factory { /* your definition */ }
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.FILE)
|
||||||
|
@Repeatable
|
||||||
|
annotation class GenerateKoinDefinition(
|
||||||
|
val name: String,
|
||||||
|
val type: KClass<*>,
|
||||||
|
vararg val typeArgs: KClass<*>,
|
||||||
|
val nullable: Boolean = true,
|
||||||
|
val generateSingle: Boolean = true,
|
||||||
|
val generateFactory: Boolean = true
|
||||||
|
)
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.koin
|
package dev.inmo.micro_utils.koin
|
||||||
|
|
||||||
import org.koin.core.definition.Definition
|
import org.koin.core.definition.Definition
|
||||||
|
import org.koin.core.definition.KoinDefinition
|
||||||
import org.koin.core.instance.InstanceFactory
|
import org.koin.core.instance.InstanceFactory
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import org.koin.core.qualifier.Qualifier
|
import org.koin.core.qualifier.Qualifier
|
||||||
@@ -13,7 +14,7 @@ inline fun <reified T : Any> Module.factoryWithBinds(
|
|||||||
qualifier: Qualifier? = null,
|
qualifier: Qualifier? = null,
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ inline fun <reified T : Any> Module.factoryWithBinds(
|
|||||||
qualifier: String,
|
qualifier: String,
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.koin
|
package dev.inmo.micro_utils.koin
|
||||||
|
|
||||||
import org.koin.core.definition.Definition
|
import org.koin.core.definition.Definition
|
||||||
|
import org.koin.core.definition.KoinDefinition
|
||||||
import org.koin.core.instance.InstanceFactory
|
import org.koin.core.instance.InstanceFactory
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@@ -8,6 +9,6 @@ import kotlin.reflect.KClass
|
|||||||
inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds(
|
inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds(
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return factoryWithBinds(RandomQualifier(), bindFilter, definition)
|
return factoryWithBinds(RandomQualifier(), bindFilter, definition)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.koin
|
package dev.inmo.micro_utils.koin
|
||||||
|
|
||||||
import org.koin.core.definition.Definition
|
import org.koin.core.definition.Definition
|
||||||
|
import org.koin.core.definition.KoinDefinition
|
||||||
import org.koin.core.instance.InstanceFactory
|
import org.koin.core.instance.InstanceFactory
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import org.koin.core.qualifier.Qualifier
|
import org.koin.core.qualifier.Qualifier
|
||||||
@@ -14,7 +15,7 @@ inline fun <reified T : Any> Module.singleWithBinds(
|
|||||||
createdAtStart: Boolean = false,
|
createdAtStart: Boolean = false,
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ inline fun <reified T : Any> Module.singleWithBinds(
|
|||||||
createdAtStart: Boolean = false,
|
createdAtStart: Boolean = false,
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.koin
|
package dev.inmo.micro_utils.koin
|
||||||
|
|
||||||
import org.koin.core.definition.Definition
|
import org.koin.core.definition.Definition
|
||||||
|
import org.koin.core.definition.KoinDefinition
|
||||||
import org.koin.core.instance.InstanceFactory
|
import org.koin.core.instance.InstanceFactory
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@@ -9,6 +10,6 @@ inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds(
|
|||||||
createdAtStart: Boolean = false,
|
createdAtStart: Boolean = false,
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition)
|
return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition)
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,6 @@ kotlin {
|
|||||||
api libs.ktor.client
|
api libs.ktor.client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
androidMain {
|
androidMain {
|
||||||
dependsOn jvmMain
|
dependsOn jvmMain
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,10 @@ import io.ktor.client.call.body
|
|||||||
import io.ktor.client.statement.HttpResponse
|
import io.ktor.client.statement.HttpResponse
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
|
|
||||||
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf {
|
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull(
|
||||||
status == HttpStatusCode.OK
|
statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK }
|
||||||
} ?.body<T>()
|
) = takeIf(statusFilter) ?.body<T>()
|
||||||
|
|
||||||
|
suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> {
|
||||||
|
it.status != HttpStatusCode.NoContent
|
||||||
|
}
|
||||||
|
@@ -19,7 +19,7 @@ import kotlinx.coroutines.isActive
|
|||||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
* 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")
|
@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 checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||||
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
|
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
|
||||||
): Flow<T> {
|
): Flow<T> {
|
||||||
@@ -57,7 +57,7 @@ inline fun <reified T : Any> HttpClient.openWebSocketFlow(
|
|||||||
): Flow<T> {
|
): Flow<T> {
|
||||||
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
||||||
|
|
||||||
return openBaseWebSocketFlow<T>(checkReconnection) {
|
return openBaseWebSocketFlow(checkReconnection) {
|
||||||
val block: suspend DefaultClientWebSocketSession.() -> Unit = {
|
val block: suspend DefaultClientWebSocketSession.() -> Unit = {
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
send(receiveDeserialized<T>())
|
send(receiveDeserialized<T>())
|
||||||
|
@@ -17,8 +17,11 @@ import io.ktor.http.HttpStatusCode
|
|||||||
import io.ktor.http.Parameters
|
import io.ktor.http.Parameters
|
||||||
import io.ktor.http.content.PartData
|
import io.ktor.http.content.PartData
|
||||||
import kotlinx.serialization.DeserializationStrategy
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
|
import kotlinx.serialization.InternalSerializationApi
|
||||||
|
import kotlinx.serialization.SerializationStrategy
|
||||||
import kotlinx.serialization.StringFormat
|
import kotlinx.serialization.StringFormat
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.serializer
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,6 +32,7 @@ import java.io.File
|
|||||||
* in case you wish to pass other source of multipart binary data than regular file
|
* in case you wish to pass other source of multipart binary data than regular file
|
||||||
* @suppress
|
* @suppress
|
||||||
*/
|
*/
|
||||||
|
@OptIn(InternalSerializationApi::class)
|
||||||
actual suspend fun <T> HttpClient.uniUpload(
|
actual suspend fun <T> HttpClient.uniUpload(
|
||||||
url: String,
|
url: String,
|
||||||
data: Map<String, Any>,
|
data: Map<String, Any>,
|
||||||
@@ -40,7 +44,8 @@ actual suspend fun <T> HttpClient.uniUpload(
|
|||||||
val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
|
val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
|
||||||
|
|
||||||
val formData = formData {
|
val formData = formData {
|
||||||
data.forEach { (k, v) ->
|
for (k in data.keys) {
|
||||||
|
val v = data[k] ?: continue
|
||||||
when (v) {
|
when (v) {
|
||||||
is File -> append(
|
is File -> append(
|
||||||
k,
|
k,
|
||||||
@@ -60,7 +65,7 @@ actual suspend fun <T> HttpClient.uniUpload(
|
|||||||
)
|
)
|
||||||
else -> append(
|
else -> append(
|
||||||
k,
|
k,
|
||||||
stringFormat.encodeToString(v)
|
stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +90,7 @@ actual suspend fun <T> HttpClient.uniUpload(
|
|||||||
submitForm(
|
submitForm(
|
||||||
url,
|
url,
|
||||||
Parameters.build {
|
Parameters.build {
|
||||||
formData.forEach {
|
for (it in formData) {
|
||||||
val formItem = (it as PartData.FormItem)
|
val formItem = (it as PartData.FormItem)
|
||||||
append(it.name!!, it.value)
|
append(it.name!!, it.value)
|
||||||
}
|
}
|
||||||
|
@@ -17,5 +17,8 @@ kotlin {
|
|||||||
api libs.ktor.io
|
api libs.ktor.io
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
androidMain {
|
||||||
|
dependsOn jvmMain
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,8 +10,8 @@ import java.io.File
|
|||||||
|
|
||||||
fun PartData.FileItem.download(target: File) {
|
fun PartData.FileItem.download(target: File) {
|
||||||
provider().use { input ->
|
provider().use { input ->
|
||||||
target.outputStream().use {
|
target.outputStream().asOutput().use {
|
||||||
input.copyTo(it.asOutput())
|
input.copyTo(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,3 +5,11 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
androidMain {
|
||||||
|
dependsOn jvmMain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
8
language_codes/src/jvmMain/kotlin/Locale.kt
Normal file
8
language_codes/src/jvmMain/kotlin/Locale.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package dev.inmo.micro_utils.language_codes
|
||||||
|
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
fun IetfLanguageCode.toJavaLocale(): Locale = Locale.forLanguageTag(code)
|
||||||
|
fun IetfLanguageCode?.toJavaLocaleOrDefault(): Locale = this ?.toJavaLocale() ?: Locale.getDefault()
|
||||||
|
|
||||||
|
fun Locale.toIetfLanguageCode(): IetfLanguageCode = IetfLanguageCode(toLanguageTag())
|
49
mppJsAndJavaProject.gradle
Normal file
49
mppJsAndJavaProject.gradle
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
project.version = "$version"
|
||||||
|
project.group = "$group"
|
||||||
|
|
||||||
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm {
|
||||||
|
compilations.main {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
js (IR) {
|
||||||
|
browser()
|
||||||
|
nodejs()
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commonTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-common')
|
||||||
|
implementation kotlin('test-annotations-common')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jvmTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-js')
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
package dev.inmo.micro_utils.pagination.utils
|
||||||
|
|
||||||
|
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
|
||||||
|
is List<T> -> optionallyReverse(reverse)
|
||||||
|
is Set<T> -> optionallyReverse(reverse)
|
||||||
|
else -> if (reverse) {
|
||||||
|
reversed()
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
|
||||||
|
reversed()
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
|
||||||
|
reversed().toSet()
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
|
||||||
|
Array(size) {
|
||||||
|
get(lastIndex - it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
@@ -32,6 +32,24 @@ fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
|
||||||
|
val actualPagination = with.optionallyReverse(
|
||||||
|
size,
|
||||||
|
reversed
|
||||||
|
)
|
||||||
|
|
||||||
|
val firstIndex = maxOf(actualPagination.firstIndex, 0)
|
||||||
|
val lastIndex = minOf(actualPagination.lastIndexExclusive, size)
|
||||||
|
if (firstIndex > lastIndex) {
|
||||||
|
return emptyPaginationResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
return subList(firstIndex, lastIndex).optionallyReverse(reversed).createPaginationResult(
|
||||||
|
with,
|
||||||
|
size.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
|
fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
|
||||||
return this.drop(with.firstIndex).take(with.size).createPaginationResult(
|
return this.drop(with.firstIndex).take(with.size).createPaginationResult(
|
||||||
with,
|
with,
|
||||||
@@ -39,30 +57,20 @@ fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
|
fun <T> Set<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
|
||||||
is List<T> -> optionallyReverse(reverse)
|
val actualPagination = with.optionallyReverse(
|
||||||
is Set<T> -> optionallyReverse(reverse)
|
size,
|
||||||
else -> if (reverse) {
|
reversed
|
||||||
reversed()
|
)
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
|
|
||||||
reversed()
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
|
|
||||||
reversed().toSet()
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
|
val firstIndex = maxOf(actualPagination.firstIndex, 0)
|
||||||
Array(size) {
|
val lastIndex = minOf(actualPagination.lastIndexExclusive, size)
|
||||||
get(lastIndex - it)
|
if (firstIndex > lastIndex) {
|
||||||
|
return emptyPaginationResult()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this
|
return this.drop(firstIndex).take(lastIndex - firstIndex).optionallyReverse(reversed).createPaginationResult(
|
||||||
|
with,
|
||||||
|
size.toLong()
|
||||||
|
)
|
||||||
}
|
}
|
@@ -10,12 +10,14 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||||
protected open val kvCache: KVCache<IdType, ObjectType>,
|
protected open val kvCache: KVCache<IdType, ObjectType>,
|
||||||
protected open val idGetter: (ObjectType) -> IdType
|
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 {
|
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
|
||||||
kvCache.set(id, it)
|
kvCache.set(id, it)
|
||||||
})
|
})
|
||||||
|
|
||||||
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
|
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(
|
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 kvCache: KVCache<IdType, ObjectType>,
|
||||||
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
protected open val idGetter: (ObjectType) -> IdType
|
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 newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
|
||||||
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
|
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
|
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
|
||||||
@@ -72,6 +74,8 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
|||||||
|
|
||||||
return created
|
return created
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
|
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache
|
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
|
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/FallbackCacheRepo.kt
vendored
Normal file
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/FallbackCacheRepo.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 their original repo, if successful - save data to internal
|
||||||
|
* [dev.inmo.micro_utils.repos.cache.cache.FullKVCache] or try to take data from that internal cache
|
||||||
|
*/
|
||||||
|
interface FallbackCacheRepo : CacheRepo
|
@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.*
|
|||||||
open class ReadKeyValueCacheRepo<Key,Value>(
|
open class ReadKeyValueCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||||
protected open val kvCache: KVCache<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 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)
|
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(
|
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
||||||
@@ -33,9 +35,11 @@ open class KeyValueCacheRepo<Key,Value>(
|
|||||||
parentRepo: KeyValueRepo<Key, Value>,
|
parentRepo: KeyValueRepo<Key, Value>,
|
||||||
kvCache: KVCache<Key, Value>,
|
kvCache: KVCache<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
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 onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
||||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.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(
|
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
||||||
|
@@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.*
|
|||||||
open class ReadKeyValuesCacheRepo<Key,Value>(
|
open class ReadKeyValuesCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||||
protected open val kvCache: KVCache<Key, List<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> {
|
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||||
return getAll(k, reversed).paginate(
|
return getAll(k, reversed).paginate(
|
||||||
pagination
|
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 contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
|
||||||
|
|
||||||
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||||
@@ -40,7 +42,7 @@ open class KeyValuesCacheRepo<Key,Value>(
|
|||||||
parentRepo: KeyValuesRepo<Key, Value>,
|
parentRepo: KeyValuesRepo<Key, Value>,
|
||||||
kvCache: KVCache<Key, List<Value>>,
|
kvCache: KVCache<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
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) ->
|
protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) ->
|
||||||
kvCache.set(
|
kvCache.set(
|
||||||
k,
|
k,
|
||||||
@@ -56,6 +58,8 @@ open class KeyValuesCacheRepo<Key,Value>(
|
|||||||
protected val onDataClearedJob = parentRepo.onDataCleared.onEach {
|
protected val onDataClearedJob = parentRepo.onDataCleared.onEach {
|
||||||
kvCache.unset(it)
|
kvCache.unset(it)
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(
|
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(
|
||||||
|
37
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/ActionWrapper.kt
vendored
Normal file
37
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/ActionWrapper.kt
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realizations should [wrap] the work with some conditions like retries on exceptions, calling timeout, etc.
|
||||||
|
*
|
||||||
|
* @see Timeouted
|
||||||
|
* @see Direct
|
||||||
|
*/
|
||||||
|
interface ActionWrapper {
|
||||||
|
/**
|
||||||
|
* Should execute [block] to take the result [T], but may return failure in case when something went wrong.
|
||||||
|
* This method should never throw any [Exception]
|
||||||
|
*/
|
||||||
|
suspend fun <T> wrap(block: suspend () -> T): Result<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type of [ActionWrapper]s will use [withTimeout]([timeoutMillis]) and if original call
|
||||||
|
* will not return anything in that timeout just return [Result] with failure
|
||||||
|
*/
|
||||||
|
class Timeouted(private val timeoutMillis: Long) : ActionWrapper {
|
||||||
|
override suspend fun <T> wrap(block: suspend () -> T): Result<T> = runCatchingSafely {
|
||||||
|
withTimeout(timeoutMillis) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* It is passthrough variant of [ActionWrapper] which will just call incoming block with wrapping into [runCatchingSafely]
|
||||||
|
*/
|
||||||
|
object Direct : ActionWrapper {
|
||||||
|
|
||||||
|
override suspend fun <T> wrap(block: suspend () -> T): Result<T> = runCatchingSafely { block() }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.CRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheCRUDRepo<RegisteredObject, Id, InputObject>(
|
||||||
|
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
|
||||||
|
originalRepo,
|
||||||
|
scope,
|
||||||
|
kvCache,
|
||||||
|
recacheDelay,
|
||||||
|
actionWrapper,
|
||||||
|
idGetter
|
||||||
|
),
|
||||||
|
WriteCRUDRepo<RegisteredObject, Id, InputObject> by AutoRecacheWriteCRUDRepo(originalRepo, scope, kvCache, idGetter),
|
||||||
|
CRUDRepo<RegisteredObject, Id, InputObject> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
|
||||||
|
protected open val originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
|
) : ReadCRUDRepo<RegisteredObject, Id>, FallbackCacheRepo {
|
||||||
|
val autoUpdateJob = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
actualizeAll()
|
||||||
|
|
||||||
|
delay(recacheDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
|
||||||
|
protected open suspend fun actualizeAll(): Result<Unit> {
|
||||||
|
return runCatchingSafely {
|
||||||
|
kvCache.actualizeAll(originalRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(id: Id): Boolean = actionWrapper.wrap {
|
||||||
|
originalRepo.contains(id)
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.contains(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = actionWrapper.wrap {
|
||||||
|
originalRepo.count()
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getByPagination(
|
||||||
|
pagination: Pagination
|
||||||
|
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
|
||||||
|
originalRepo.getByPagination(pagination)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
it.results.forEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
} ?: kvCache.values(pagination)
|
||||||
|
|
||||||
|
override suspend fun getIdsByPagination(
|
||||||
|
pagination: Pagination
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.getIdsByPagination(pagination)
|
||||||
|
}.getOrElse { kvCache.keys(pagination) }
|
||||||
|
|
||||||
|
override suspend fun getById(id: Id): RegisteredObject? = actionWrapper.wrap {
|
||||||
|
originalRepo.getById(id)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
} ?: kvCache.get(id)
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,65 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.plus
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||||
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
|
|
||||||
|
open class AutoRecacheWriteCRUDRepo<RegisteredObject, Id, InputObject>(
|
||||||
|
protected val originalRepo: WriteCRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
|
) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo {
|
||||||
|
override val deletedObjectsIdsFlow: Flow<Id>
|
||||||
|
get() = (originalRepo.deletedObjectsIdsFlow + kvCache.onValueRemoved).distinctUntilChanged()
|
||||||
|
override val newObjectsFlow: Flow<RegisteredObject>
|
||||||
|
get() = (originalRepo.newObjectsFlow + kvCache.onNewValue.map { it.second }).distinctUntilChanged()
|
||||||
|
override val updatedObjectsFlow: Flow<RegisteredObject>
|
||||||
|
get() = originalRepo.updatedObjectsFlow
|
||||||
|
|
||||||
|
private val onRemovingUpdatesListeningJob = originalRepo.deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onNewAndUpdatedObjectsListeningJob = merge(
|
||||||
|
originalRepo.newObjectsFlow,
|
||||||
|
originalRepo.updatedObjectsFlow,
|
||||||
|
).subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
values: List<UpdatedValuePair<Id, InputObject>>
|
||||||
|
): List<RegisteredObject> = originalRepo.update(values).onEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
id: Id,
|
||||||
|
value: InputObject
|
||||||
|
): RegisteredObject? = originalRepo.update(id, value) ?.also {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteById(ids: List<Id>) = originalRepo.deleteById(ids).also {
|
||||||
|
kvCache.unset(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(values: List<InputObject>): List<RegisteredObject> = originalRepo.create(values).onEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheKeyValueRepo<Id, RegisteredObject>(
|
||||||
|
override val originalRepo: KeyValueRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : AutoRecacheReadKeyValueRepo<Id, RegisteredObject> (
|
||||||
|
originalRepo,
|
||||||
|
scope,
|
||||||
|
kvCache,
|
||||||
|
recacheDelay,
|
||||||
|
actionWrapper,
|
||||||
|
idGetter
|
||||||
|
),
|
||||||
|
WriteKeyValueRepo<Id, RegisteredObject> by AutoRecacheWriteKeyValueRepo(originalRepo, scope, kvCache),
|
||||||
|
KeyValueRepo<Id, RegisteredObject> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: KeyValueRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<RegisteredObject>) = originalRepo.unsetWithValues(
|
||||||
|
toUnset
|
||||||
|
).also {
|
||||||
|
kvCache.unsetWithValues(toUnset)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,96 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
|
||||||
|
protected open val originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
|
) : ReadKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
|
val autoUpdateJob = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
actualizeAll()
|
||||||
|
|
||||||
|
delay(recacheDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
|
||||||
|
protected open suspend fun actualizeAll(): Result<Unit> {
|
||||||
|
return runCatchingSafely {
|
||||||
|
kvCache.actualizeAll(originalRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(key: Id): Boolean = actionWrapper.wrap {
|
||||||
|
originalRepo.contains(key)
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.contains(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = actionWrapper.wrap {
|
||||||
|
originalRepo.count()
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(k: Id): RegisteredObject? = actionWrapper.wrap {
|
||||||
|
originalRepo.get(k)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
kvCache.set(k, it)
|
||||||
|
} ?: kvCache.get(k)
|
||||||
|
|
||||||
|
override suspend fun values(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
|
||||||
|
originalRepo.values(pagination, reversed)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
it.results.forEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
} ?: kvCache.values(pagination, reversed)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.keys(pagination, reversed)
|
||||||
|
}.getOrElse { kvCache.keys(pagination, reversed) }
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: RegisteredObject,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.keys(v, pagination, reversed)
|
||||||
|
}.getOrElse { kvCache.keys(v, pagination, reversed) }
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,54 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.plus
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
||||||
|
open class AutoRecacheWriteKeyValueRepo<Id, RegisteredObject>(
|
||||||
|
protected val originalRepo: WriteKeyValueRepo<Id, RegisteredObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache()
|
||||||
|
) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
|
override val onValueRemoved: Flow<Id>
|
||||||
|
get() = (originalRepo.onValueRemoved + kvCache.onValueRemoved).distinctUntilChanged()
|
||||||
|
|
||||||
|
override val onNewValue: Flow<Pair<Id, RegisteredObject>>
|
||||||
|
get() = (originalRepo.onNewValue + kvCache.onNewValue).distinctUntilChanged()
|
||||||
|
|
||||||
|
private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onNewAndUpdatedObjectsListeningJob = originalRepo.onNewValue.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.set(it.first, it.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<RegisteredObject>) = originalRepo.unsetWithValues(
|
||||||
|
toUnset
|
||||||
|
).also {
|
||||||
|
kvCache.unsetWithValues(toUnset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unset(toUnset: List<Id>) = originalRepo.unset(
|
||||||
|
toUnset
|
||||||
|
).also {
|
||||||
|
kvCache.unset(toUnset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun set(toSet: Map<Id, RegisteredObject>) = originalRepo.set(
|
||||||
|
toSet
|
||||||
|
).also {
|
||||||
|
kvCache.set(toSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheKeyValuesRepo<Id, RegisteredObject>(
|
||||||
|
override val originalRepo: KeyValuesRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
actionWrapper: ActionWrapper = ActionWrapper.Direct
|
||||||
|
) : AutoRecacheReadKeyValuesRepo<Id, RegisteredObject> (
|
||||||
|
originalRepo,
|
||||||
|
scope,
|
||||||
|
kvCache,
|
||||||
|
recacheDelay,
|
||||||
|
actionWrapper
|
||||||
|
),
|
||||||
|
WriteKeyValuesRepo<Id, RegisteredObject> by AutoRecacheWriteKeyValuesRepo(originalRepo, scope, kvCache),
|
||||||
|
KeyValuesRepo<Id, RegisteredObject> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: KeyValuesRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
|
||||||
|
|
||||||
|
override suspend fun clearWithValue(v: RegisteredObject) {
|
||||||
|
super.clearWithValue(v)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,145 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.createPaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.emptyPaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.firstIndex
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheReadKeyValuesRepo<Id, RegisteredObject>(
|
||||||
|
protected open val originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
||||||
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct
|
||||||
|
) : ReadKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
|
val autoUpdateJob = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
actualizeAll()
|
||||||
|
|
||||||
|
delay(recacheDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
|
||||||
|
|
||||||
|
protected open suspend fun actualizeAll(): Result<Unit> {
|
||||||
|
return runCatchingSafely {
|
||||||
|
kvCache.actualizeAll(originalRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(k: Id): Boolean = actionWrapper.wrap {
|
||||||
|
originalRepo.contains(k)
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.contains(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = actionWrapper.wrap {
|
||||||
|
originalRepo.count()
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: RegisteredObject,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.keys(v, pagination, reversed)
|
||||||
|
}.getOrElse {
|
||||||
|
val results = mutableListOf<Id>()
|
||||||
|
|
||||||
|
val toSkip = pagination.firstIndex
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
doForAllWithNextPaging {
|
||||||
|
kvCache.keys(pagination, reversed).also {
|
||||||
|
it.results.forEach {
|
||||||
|
if (kvCache.get(it) ?.contains(v) == true) {
|
||||||
|
count++
|
||||||
|
if (count < toSkip || results.size >= pagination.size) {
|
||||||
|
return@forEach
|
||||||
|
} else {
|
||||||
|
results.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return@getOrElse results.createPaginationResult(
|
||||||
|
pagination,
|
||||||
|
count.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.keys(pagination, reversed)
|
||||||
|
}.getOrElse { kvCache.keys(pagination, reversed) }
|
||||||
|
|
||||||
|
override suspend fun get(
|
||||||
|
k: Id,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
|
||||||
|
originalRepo.get(k, pagination, reversed)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
it.results.forEach {
|
||||||
|
kvCache.set(k, ((kvCache.get(k) ?: return@also) + it).distinct())
|
||||||
|
}
|
||||||
|
} ?: kvCache.get(k) ?.run {
|
||||||
|
paginate(pagination.optionallyReverse(size, reversed)).let {
|
||||||
|
if (reversed) {
|
||||||
|
it.changeResultsUnchecked(
|
||||||
|
it.results.reversed()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: emptyPaginationResult()
|
||||||
|
|
||||||
|
override suspend fun count(k: Id): Long = actionWrapper.wrap {
|
||||||
|
originalRepo.count(k)
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.get(k) ?.size ?.toLong() ?: 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(k: Id, v: RegisteredObject): Boolean {
|
||||||
|
return (actionWrapper.wrap {
|
||||||
|
originalRepo.contains(k, v)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
kvCache.set(k, ((kvCache.get(k) ?: return@also) + v).distinct())
|
||||||
|
}) ?: (kvCache.get(k) ?.contains(v) == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.plus
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
||||||
|
open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
|
||||||
|
protected val originalRepo: WriteKeyValuesRepo<Id, RegisteredObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache()
|
||||||
|
) : WriteKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
|
override val onValueRemoved: Flow<Pair<Id, RegisteredObject>>
|
||||||
|
get() = originalRepo.onValueRemoved
|
||||||
|
|
||||||
|
override val onNewValue: Flow<Pair<Id, RegisteredObject>>
|
||||||
|
get() = originalRepo.onNewValue
|
||||||
|
override val onDataCleared: Flow<Id>
|
||||||
|
get() = (originalRepo.onDataCleared + kvCache.onValueRemoved).distinctUntilChanged()
|
||||||
|
|
||||||
|
private val onDataClearedListeningJob = originalRepo.onDataCleared.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.set(
|
||||||
|
it.first,
|
||||||
|
(kvCache.get(
|
||||||
|
it.first
|
||||||
|
) ?: return@subscribeSafelyWithoutExceptions) - it.second
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onNewAndUpdatedObjectsListeningJob = originalRepo.onNewValue.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.set(
|
||||||
|
it.first,
|
||||||
|
(kvCache.get(
|
||||||
|
it.first
|
||||||
|
) ?: return@subscribeSafelyWithoutExceptions) + it.second
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearWithValue(v: RegisteredObject) {
|
||||||
|
originalRepo.clearWithValue(v)
|
||||||
|
doForAllWithNextPaging(FirstPagePagination(kvCache.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE)) {
|
||||||
|
kvCache.keys(it).also {
|
||||||
|
it.results.forEach { id ->
|
||||||
|
kvCache.get(id) ?.takeIf { it.contains(v) } ?.let {
|
||||||
|
kvCache.unset(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear(k: Id) {
|
||||||
|
originalRepo.clear(k)
|
||||||
|
kvCache.unset(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun remove(toRemove: Map<Id, List<RegisteredObject>>) {
|
||||||
|
originalRepo.remove(toRemove)
|
||||||
|
toRemove.forEach { (k, v) ->
|
||||||
|
kvCache.set(k, (kvCache.get(k) ?: return@forEach) - v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun add(toAdd: Map<Id, List<RegisteredObject>>) {
|
||||||
|
originalRepo.add(toAdd)
|
||||||
|
toAdd.forEach { (k, v) ->
|
||||||
|
kvCache.set(k, (kvCache.get(k) ?: return@forEach) + v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
|
}
|
@@ -3,11 +3,10 @@ package dev.inmo.micro_utils.repos.cache.full
|
|||||||
import dev.inmo.micro_utils.common.*
|
import dev.inmo.micro_utils.common.*
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.*
|
import dev.inmo.micro_utils.repos.cache.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
@@ -32,12 +31,7 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open suspend fun actualizeAll() {
|
protected open suspend fun actualizeAll() {
|
||||||
kvCache.clear()
|
kvCache.actualizeAll(parentRepo)
|
||||||
doForAllWithNextPaging {
|
|
||||||
parentRepo.getByPagination(it).also {
|
|
||||||
kvCache.set(it.results.associateBy { idGetter(it) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
|
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
|
||||||
@@ -69,6 +63,10 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
{ getById(id) },
|
{ getById(id) },
|
||||||
{ it ?.let { set(idGetter(it), it) } }
|
{ it ?.let { set(idGetter(it), it) } }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
||||||
@@ -92,7 +90,11 @@ open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
|||||||
scope,
|
scope,
|
||||||
idGetter
|
idGetter
|
||||||
),
|
),
|
||||||
CRUDRepo<ObjectType, IdType, InputValueType>
|
CRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
||||||
kvCache: FullKVCache<IdType, ObjectType>,
|
kvCache: FullKVCache<IdType, ObjectType>,
|
||||||
|
@@ -5,9 +5,11 @@ import dev.inmo.micro_utils.pagination.Pagination
|
|||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
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 dev.inmo.micro_utils.repos.pagination.getAll
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
open class FullReadKeyValueCacheRepo<Key,Value>(
|
open class FullReadKeyValueCacheRepo<Key,Value>(
|
||||||
@@ -68,6 +70,10 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
|
|||||||
{ parentRepo.keys(v, pagination, reversed) },
|
{ parentRepo.keys(v, pagination, reversed) },
|
||||||
{ if (it.results.isNotEmpty()) actualizeAll() }
|
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
||||||
@@ -75,12 +81,16 @@ fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
|||||||
) = FullReadKeyValueCacheRepo(this, kvCache)
|
) = FullReadKeyValueCacheRepo(this, kvCache)
|
||||||
|
|
||||||
open class FullWriteKeyValueCacheRepo<Key,Value>(
|
open class FullWriteKeyValueCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: WriteKeyValueRepo<Key, Value>,
|
parentRepo: WriteKeyValueRepo<Key, Value>,
|
||||||
protected open val kvCache: FullKVCache<Key, Value>,
|
protected open val kvCache: FullKVCache<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
|
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
|
||||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
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)
|
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
|
fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
|
||||||
@@ -89,13 +99,17 @@ fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
|
|||||||
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
|
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
|
||||||
|
|
||||||
open class FullKeyValueCacheRepo<Key,Value>(
|
open class FullKeyValueCacheRepo<Key,Value>(
|
||||||
parentRepo: KeyValueRepo<Key, Value>,
|
protected open val parentRepo: KeyValueRepo<Key, Value>,
|
||||||
kvCache: FullKVCache<Key, Value>,
|
kvCache: FullKVCache<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
|
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
|
||||||
KeyValueRepo<Key,Value>,
|
KeyValueRepo<Key,Value>,
|
||||||
ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(parentRepo, kvCache) {
|
ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(parentRepo, kvCache) {
|
||||||
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
|
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
kvCache.actualizeAll(parentRepo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
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.pagination.utils.*
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
@@ -33,8 +34,7 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open suspend fun actualizeAll() {
|
protected open suspend fun actualizeAll() {
|
||||||
doAllWithCurrentPaging { kvCache.keys(it).also { kvCache.unset(it.results) } }
|
kvCache.actualizeAll(parentRepo)
|
||||||
kvCache.set(parentRepo.getAll())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
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() }
|
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||||
@@ -109,7 +112,7 @@ fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
|||||||
) = FullReadKeyValuesCacheRepo(this, kvCache)
|
) = FullReadKeyValuesCacheRepo(this, kvCache)
|
||||||
|
|
||||||
open class FullWriteKeyValuesCacheRepo<Key,Value>(
|
open class FullWriteKeyValuesCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: WriteKeyValuesRepo<Key, Value>,
|
parentRepo: WriteKeyValuesRepo<Key, Value>,
|
||||||
protected open val kvCache: FullKVCache<Key, List<Value>>,
|
protected open val kvCache: FullKVCache<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo {
|
) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo {
|
||||||
@@ -125,6 +128,10 @@ open class FullWriteKeyValuesCacheRepo<Key,Value>(
|
|||||||
kvCache.get(it.first) ?.minus(it.second) ?: return@onEach
|
kvCache.get(it.first) ?.minus(it.second) ?: return@onEach
|
||||||
)
|
)
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
|
fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
|
||||||
@@ -133,7 +140,7 @@ fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
|
|||||||
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
|
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
|
||||||
|
|
||||||
open class FullKeyValuesCacheRepo<Key,Value>(
|
open class FullKeyValuesCacheRepo<Key,Value>(
|
||||||
parentRepo: KeyValuesRepo<Key, Value>,
|
protected open val parentRepo: KeyValuesRepo<Key, Value>,
|
||||||
kvCache: FullKVCache<Key, List<Value>>,
|
kvCache: FullKVCache<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),
|
) : 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(
|
fun <Key, Value> KeyValuesRepo<Key, Value>.caching(
|
||||||
|
53
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/util/ActualizeAll.kt
vendored
Normal file
53
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/util/ActualizeAll.kt
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
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.pagination.utils.getAllByWithNextPaging
|
||||||
|
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.pagination.getAll
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
|
||||||
|
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||||
|
clear: Boolean = true,
|
||||||
|
getAll: () -> Map<K, V>
|
||||||
|
) {
|
||||||
|
set(
|
||||||
|
getAll().also {
|
||||||
|
if (clear) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||||
|
repo: ReadKeyValueRepo<K, V>,
|
||||||
|
clear: Boolean = true,
|
||||||
|
) {
|
||||||
|
actualizeAll(clear) {
|
||||||
|
repo.getAll { keys(it) }.toMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
|
||||||
|
repo: ReadKeyValuesRepo<K, V>,
|
||||||
|
clear: Boolean = true,
|
||||||
|
) {
|
||||||
|
actualizeAll(clear) {
|
||||||
|
repo.getAll { keys(it) }.toMap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||||
|
repo: ReadCRUDRepo<V, K>,
|
||||||
|
clear: Boolean = true,
|
||||||
|
) {
|
||||||
|
actualizeAll(clear) {
|
||||||
|
repo.getAllByWithNextPaging {
|
||||||
|
getIdsByPagination(it)
|
||||||
|
}.mapNotNull { it to (repo.getById(it) ?: return@mapNotNull null) }.toMap()
|
||||||
|
}
|
||||||
|
}
|
@@ -32,5 +32,3 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableIncludingJvmCodeInAndroidPart()
|
|
||||||
|
@@ -2,6 +2,8 @@ package dev.inmo.micro_utils.repos
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,24 +19,32 @@ interface ReadKeyValueRepo<Key, Value> : Repo {
|
|||||||
suspend fun get(k: Key): Value?
|
suspend fun get(k: Key): Value?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
* This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use
|
||||||
* ascending sort for [Key]s
|
* ascending sort for [Key]s
|
||||||
*/
|
*/
|
||||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
* This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use
|
||||||
* ascending sort for [Key]s
|
* ascending sort for [Key]s
|
||||||
*/
|
*/
|
||||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
* This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use
|
||||||
* ascending sort for [Key]s
|
* ascending sort for [Key]s.
|
||||||
|
*
|
||||||
|
* **DEFAULT REALIZATION IS NOT OPTIMAL AND HAS BEEN ADDED TO COVER CASES OF DIFFERENT COMMON MAPPINGS AND TRANSFORMATIONS**
|
||||||
*
|
*
|
||||||
* @param v This value should be used to exclude from search the items with different [Value]s
|
* @param v This value should be used to exclude from search the items with different [Value]s
|
||||||
*/
|
*/
|
||||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> {
|
||||||
|
return getAllWithNextPaging {
|
||||||
|
keys(it)
|
||||||
|
}.filter {
|
||||||
|
get(it) == v
|
||||||
|
}.paginate(pagination, reversed)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if [key] is presented in current collection or false otherwise
|
* @return true if [key] is presented in current collection or false otherwise
|
||||||
@@ -93,6 +103,10 @@ suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
|||||||
vararg toSet: Pair<Key, Value>
|
vararg toSet: Pair<Key, Value>
|
||||||
) = set(toSet.toMap())
|
) = 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(
|
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||||
k: Key, v: Value
|
k: Key, v: Value
|
||||||
) = set(k to v)
|
) = set(k to v)
|
||||||
@@ -125,7 +139,11 @@ interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValue
|
|||||||
* By default, will remove all the data of current repo using [doAllWithCurrentPaging], [keys] and [unset]
|
* By default, will remove all the data of current repo using [doAllWithCurrentPaging], [keys] and [unset]
|
||||||
*/
|
*/
|
||||||
suspend fun clear() {
|
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>
|
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.annotations
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this annotation and ksp generator (module `micro_utils.repos.generator`) to create the next hierarchy of models:
|
||||||
|
*
|
||||||
|
* * New model. For example: data class NewTest
|
||||||
|
* * Registered model. For example: data class RegisteredTest
|
||||||
|
*
|
||||||
|
* @param registeredSupertypes These [KClass]es will be used as supertypes for registered model
|
||||||
|
* @param serializable If true (default) will generate @[kotlinx.serialization.Serializable] for models. Affects [generateSerialName]
|
||||||
|
* @param serializable If true (default) will generate @[kotlinx.serialization.SerialName] for models with their names as values
|
||||||
|
*
|
||||||
|
* @see GenerateCRUDModelExcludeOverride
|
||||||
|
*/
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class GenerateCRUDModel(
|
||||||
|
vararg val registeredSupertypes: KClass<*>,
|
||||||
|
val serializable: Boolean = true,
|
||||||
|
val generateSerialName: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this annotation on properties which should be excluded from overriding in models.
|
||||||
|
*
|
||||||
|
* @see GenerateCRUDModel
|
||||||
|
*/
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
@Target(AnnotationTarget.PROPERTY)
|
||||||
|
annotation class GenerateCRUDModelExcludeOverride
|
||||||
|
|
||||||
|
|
@@ -0,0 +1,10 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import kotlin.js.JsName
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
fun <K, V> ReadKeyValueRepo<K, V>.asReadCRUDRepo() = ReadCRUDFromKeyValueRepo(this)
|
@@ -0,0 +1,20 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
|
||||||
|
open class ReadCRUDFromKeyValueRepo<RegisteredType, IdType>(
|
||||||
|
protected open val original: ReadKeyValueRepo<IdType, RegisteredType>
|
||||||
|
) : ReadCRUDRepo<RegisteredType, IdType> {
|
||||||
|
override suspend fun contains(id: IdType): Boolean = original.contains(id)
|
||||||
|
|
||||||
|
override suspend fun count(): Long = original.count()
|
||||||
|
|
||||||
|
override suspend fun getByPagination(pagination: Pagination): PaginationResult<RegisteredType> = original.values(pagination)
|
||||||
|
|
||||||
|
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = original.keys(pagination)
|
||||||
|
|
||||||
|
override suspend fun getById(id: IdType): RegisteredType? = original.get(id)
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kv
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import kotlin.js.JsName
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
fun <K, V> ReadKeyValuesRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromKeyValuesRepo(this)
|
||||||
|
|
||||||
|
fun <K, V> KeyValuesRepo<K, V>.asKeyValueRepo() = KeyValueFromKeyValuesRepo(this)
|
||||||
|
|
||||||
|
fun <K, V> ReadCRUDRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromCRUDRepo(this)
|
@@ -0,0 +1,41 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kv
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResults
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
|
|
||||||
|
open class KeyValueFromKeyValuesRepo<Key, Value>(
|
||||||
|
private val original: KeyValuesRepo<Key, Value>
|
||||||
|
) : KeyValueRepo<Key, List<Value>>, ReadKeyValueFromKeyValuesRepo<Key, Value>(original) {
|
||||||
|
override val onNewValue: Flow<Pair<Key, List<Value>>> = merge(
|
||||||
|
original.onNewValue,
|
||||||
|
original.onValueRemoved
|
||||||
|
).mapNotNull {
|
||||||
|
it.first to (get(it.first) ?: return@mapNotNull null)
|
||||||
|
}
|
||||||
|
override val onValueRemoved: Flow<Key> = original.onDataCleared
|
||||||
|
|
||||||
|
override suspend fun unset(toUnset: List<Key>) {
|
||||||
|
toUnset.forEach {
|
||||||
|
original.clear(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||||
|
original.set(toSet)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,46 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kv
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResults
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
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.transforms.kvs.ReadKeyValuesFromKeyValueRepo
|
||||||
|
import kotlin.jvm.JvmInline
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class ReadKeyValueFromCRUDRepo<Key, Value>(
|
||||||
|
private val original: ReadCRUDRepo<Value, Key>
|
||||||
|
) : ReadKeyValueRepo<Key, Value> {
|
||||||
|
override suspend fun get(k: Key): Value? = original.getById(k)
|
||||||
|
|
||||||
|
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = original.getByPagination(
|
||||||
|
pagination.optionallyReverse(count(), reversed)
|
||||||
|
).let {
|
||||||
|
if (reversed) {
|
||||||
|
it.changeResultsUnchecked(it.results.reversed())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = original.getIdsByPagination(
|
||||||
|
pagination.optionallyReverse(count(), reversed)
|
||||||
|
).let {
|
||||||
|
if (reversed) {
|
||||||
|
it.changeResultsUnchecked(it.results.reversed())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = original.count()
|
||||||
|
|
||||||
|
override suspend fun contains(key: Key): Boolean = original.contains(key)
|
||||||
|
}
|
@@ -0,0 +1,67 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kv
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResults
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
|
||||||
|
|
||||||
|
open class ReadKeyValueFromKeyValuesRepo<Key, Value>(
|
||||||
|
private val original: ReadKeyValuesRepo<Key, Value>
|
||||||
|
) : ReadKeyValueRepo<Key, List<Value>> {
|
||||||
|
override suspend fun get(k: Key): List<Value>? = original.getAll(k)
|
||||||
|
|
||||||
|
override suspend fun values(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<List<Value>> {
|
||||||
|
val keys = keys(pagination, reversed)
|
||||||
|
return keys.changeResults(
|
||||||
|
keys.results.mapNotNull {
|
||||||
|
get(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||||
|
return original.keys(pagination, reversed)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long {
|
||||||
|
return original.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(key: Key): Boolean {
|
||||||
|
return original.contains(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(v: List<Value>, pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||||
|
val keys = mutableSetOf<Key>()
|
||||||
|
|
||||||
|
doForAllWithNextPaging(FirstPagePagination(count().toInt())) {
|
||||||
|
original.keys(it).also {
|
||||||
|
it.results.forEach {
|
||||||
|
val values = get(it) ?: return@forEach
|
||||||
|
if (values.containsAll(v) && v.containsAll(values)) {
|
||||||
|
keys.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val paginated = keys.paginate(
|
||||||
|
pagination.optionallyReverse(keys.count(), reversed)
|
||||||
|
)
|
||||||
|
return if (reversed) {
|
||||||
|
paginated.changeResultsUnchecked(paginated.results.reversed())
|
||||||
|
} else {
|
||||||
|
paginated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kvs
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import kotlin.js.JsName
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
|
||||||
|
fun <K, V, VI : Iterable<V>> ReadKeyValueRepo<K, VI>.asReadKeyValuesRepo() = ReadKeyValuesFromKeyValueRepo(this)
|
||||||
|
|
||||||
|
fun <K, V, VI : Iterable<V>> KeyValueRepo<K, VI>.asKeyValuesRepo(
|
||||||
|
listToValuesIterable: suspend (List<V>) -> VI
|
||||||
|
): KeyValuesFromKeyValueRepo<K, V, VI> = KeyValuesFromKeyValueRepo(this, listToValuesIterable)
|
||||||
|
|
||||||
|
@JvmName("asListKeyValuesRepo")
|
||||||
|
@JsName("asListKeyValuesRepo")
|
||||||
|
fun <K, V> KeyValueRepo<K, List<V>>.asKeyValuesRepo(): KeyValuesFromKeyValueRepo<K, V, List<V>> = asKeyValuesRepo { it }
|
||||||
|
|
||||||
|
@JvmName("asSetKeyValuesRepo")
|
||||||
|
@JsName("asSetKeyValuesRepo")
|
||||||
|
fun <K, V> KeyValueRepo<K, Set<V>>.asKeyValuesRepo(): KeyValuesFromKeyValueRepo<K, V, Set<V>> = asKeyValuesRepo { it.toSet() }
|
@@ -0,0 +1,77 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kvs
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlin.js.JsName
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
open class KeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value>>(
|
||||||
|
private val original: KeyValueRepo<Key, ValuesIterable>,
|
||||||
|
private val listToValuesIterable: suspend (List<Value>) -> ValuesIterable
|
||||||
|
) : KeyValuesRepo<Key, Value>, ReadKeyValuesFromKeyValueRepo<Key, Value, ValuesIterable>(original) {
|
||||||
|
private val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
|
||||||
|
private val _onValueRemoved = MutableSharedFlow<Pair<Key, Value>>()
|
||||||
|
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||||
|
override val onValueRemoved: Flow<Pair<Key, Value>> = _onValueRemoved.asSharedFlow()
|
||||||
|
override val onDataCleared: Flow<Key> = original.onValueRemoved
|
||||||
|
|
||||||
|
override suspend fun clearWithValue(v: Value) {
|
||||||
|
val keys = mutableSetOf<Key>()
|
||||||
|
|
||||||
|
doForAllWithNextPaging(FirstPagePagination(count().toInt())) {
|
||||||
|
original.keys(it).also {
|
||||||
|
it.results.forEach {
|
||||||
|
if (contains(it, v)) {
|
||||||
|
keys.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
original.unset(keys.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear(k: Key) {
|
||||||
|
original.unset(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
||||||
|
original.set(
|
||||||
|
toRemove.mapNotNull { (k, removing) ->
|
||||||
|
val exists = original.get(k) ?: return@mapNotNull null
|
||||||
|
k to listToValuesIterable(exists - removing).also {
|
||||||
|
if (it.firstOrNull() == null) {
|
||||||
|
original.unset(k)
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
)
|
||||||
|
toRemove.forEach { (k, v) ->
|
||||||
|
v.forEach {
|
||||||
|
_onValueRemoved.emit(k to it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||||
|
original.set(
|
||||||
|
toAdd.mapNotNull { (k, adding) ->
|
||||||
|
val exists = original.get(k) ?: emptyList()
|
||||||
|
k to listToValuesIterable(exists + adding)
|
||||||
|
}.toMap()
|
||||||
|
)
|
||||||
|
toAdd.forEach { (k, v) ->
|
||||||
|
v.forEach {
|
||||||
|
_onNewValue.emit(k to it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,72 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kvs
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.emptyPaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
|
||||||
|
open class ReadKeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value>>(
|
||||||
|
private val original: ReadKeyValueRepo<Key, ValuesIterable>
|
||||||
|
) : ReadKeyValuesRepo<Key, Value> {
|
||||||
|
override suspend fun get(
|
||||||
|
k: Key,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Value> {
|
||||||
|
val iterable = original.get(k) ?: return emptyPaginationResult(pagination)
|
||||||
|
val paginated = iterable.paginate(
|
||||||
|
pagination.optionallyReverse(iterable.count(), reversed)
|
||||||
|
)
|
||||||
|
return if (reversed) {
|
||||||
|
paginated.changeResultsUnchecked(paginated.results.reversed())
|
||||||
|
} else {
|
||||||
|
paginated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> = original.keys(pagination, reversed)
|
||||||
|
|
||||||
|
override suspend fun count(): Long = original.count()
|
||||||
|
|
||||||
|
override suspend fun count(k: Key): Long = original.get(k) ?.count() ?.toLong() ?: 0L
|
||||||
|
|
||||||
|
override suspend fun contains(k: Key, v: Value): Boolean = original.get(k) ?.contains(v) == true
|
||||||
|
|
||||||
|
override suspend fun contains(k: Key): Boolean = original.contains(k)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: Value,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> {
|
||||||
|
val keys = mutableSetOf<Key>()
|
||||||
|
|
||||||
|
doForAllWithNextPaging(FirstPagePagination(count().toInt())) {
|
||||||
|
original.keys(it).also {
|
||||||
|
it.results.forEach {
|
||||||
|
if (contains(it, v)) {
|
||||||
|
keys.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val paginated = keys.paginate(
|
||||||
|
pagination.optionallyReverse(keys.count(), reversed)
|
||||||
|
)
|
||||||
|
return if (reversed) {
|
||||||
|
paginated.changeResultsUnchecked(paginated.results.reversed())
|
||||||
|
} else {
|
||||||
|
paginated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -31,11 +31,19 @@ class FileReadKeyValueRepo(
|
|||||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
|
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
|
||||||
val count = count()
|
val count = count()
|
||||||
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
||||||
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult()
|
val filesList = folder.list()
|
||||||
if (reversed) {
|
val files: Array<String> = if (resultPagination.firstIndex < count) {
|
||||||
filesPaths.reverse()
|
val filesPaths = filesList.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive.coerceAtMost(filesList.size))
|
||||||
|
|
||||||
|
if (reversed) {
|
||||||
|
filesPaths.reversedArray()
|
||||||
|
} else {
|
||||||
|
filesPaths
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyArray<String>()
|
||||||
}
|
}
|
||||||
return filesPaths.map { File(folder, it) }.createPaginationResult(
|
return files.map { File(folder, it) }.createPaginationResult(
|
||||||
resultPagination,
|
resultPagination,
|
||||||
count
|
count
|
||||||
)
|
)
|
||||||
@@ -44,11 +52,21 @@ class FileReadKeyValueRepo(
|
|||||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
|
||||||
val count = count()
|
val count = count()
|
||||||
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
||||||
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult()
|
val filesList = folder.list()
|
||||||
if (reversed) {
|
|
||||||
filesPaths.reverse()
|
val files: Array<String> = if (resultPagination.firstIndex < count) {
|
||||||
|
val filesPaths = filesList.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive.coerceAtMost(filesList.size))
|
||||||
|
|
||||||
|
if (reversed) {
|
||||||
|
filesPaths.reversedArray()
|
||||||
|
} else {
|
||||||
|
filesPaths
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyArray<String>()
|
||||||
}
|
}
|
||||||
return filesPaths.toList().createPaginationResult(
|
|
||||||
|
return files.toList().createPaginationResult(
|
||||||
resultPagination,
|
resultPagination,
|
||||||
count
|
count
|
||||||
)
|
)
|
||||||
|
@@ -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)
|
||||||
|
}
|
@@ -7,15 +7,9 @@ interface CommonExposedRepo<IdType, ObjectType> : ExposedRepo {
|
|||||||
val ResultRow.asId: IdType
|
val ResultRow.asId: IdType
|
||||||
val selectById: ISqlExpressionBuilder.(IdType) -> Op<Boolean>
|
val selectById: ISqlExpressionBuilder.(IdType) -> Op<Boolean>
|
||||||
val selectByIds: ISqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
val selectByIds: ISqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
||||||
get() = { list ->
|
get() = {
|
||||||
if (list.isEmpty()) {
|
it.foldRight<IdType, Op<Boolean>?>(null) { id, acc ->
|
||||||
Op.FALSE
|
acc ?.or(selectById(id)) ?: selectById(id)
|
||||||
} else {
|
} ?: Op.FALSE
|
||||||
var op = selectById(list.first())
|
|
||||||
(1 until list.size).forEach {
|
|
||||||
op = op.or(selectById(list[it]))
|
|
||||||
}
|
|
||||||
op
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
154
repos/generator/README.md
Normal file
154
repos/generator/README.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# Koin generator
|
||||||
|
|
||||||
|
It is Kotlin Symbol Processing generator for simple creating of typical models: `New` and `Registered`.
|
||||||
|
|
||||||
|
1. [What may do this generator](#what-may-do-this-generator)
|
||||||
|
2. [How to add generator](#how-to-add-generator)
|
||||||
|
|
||||||
|
## What may do this generator
|
||||||
|
|
||||||
|
So, you have several known things related to models:
|
||||||
|
|
||||||
|
* Interface with all necessary properties
|
||||||
|
* Id class or some registered marker
|
||||||
|
|
||||||
|
Minimal sample will be next:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@GenerateCRUDModel
|
||||||
|
interface Sample {
|
||||||
|
val property1: String
|
||||||
|
val property2: Int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And generator will create:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Serializable
|
||||||
|
@SerialName("NewSample")
|
||||||
|
data class NewSample(
|
||||||
|
override val property1: String,
|
||||||
|
override val property2: Int,
|
||||||
|
) : Sample
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("RegisteredSample")
|
||||||
|
data class RegisteredSample(
|
||||||
|
override val property1: String,
|
||||||
|
override val property2: Int,
|
||||||
|
) : Sample
|
||||||
|
|
||||||
|
fun Sample.asNew(): NewSample = NewSample(property1, property2)
|
||||||
|
|
||||||
|
fun Sample.asRegistered(): RegisteredSample = RegisteredSample(property1, property2)
|
||||||
|
```
|
||||||
|
|
||||||
|
But in most cases you will need to create some id class and registered interface:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Serializable
|
||||||
|
@JvmInline
|
||||||
|
value class SampleId(
|
||||||
|
val long: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed interface IRegisteredSample : Sample {
|
||||||
|
val id: SampleId
|
||||||
|
|
||||||
|
@GenerateCRUDModelExcludeOverride
|
||||||
|
val excludedProperty2: Boolean
|
||||||
|
get() = false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you may see, we have added `GenerateCRUDModelExcludeOverride` annotation. Properties marked with this annotation
|
||||||
|
WILL NOT be inclued into overriding in registered class (or your base interface if used there). So, if you will wish to
|
||||||
|
create model with id, use next form:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@GenerateCRUDModel(IRegisteredSample::class)
|
||||||
|
interface Sample {
|
||||||
|
val property1: String
|
||||||
|
val property2: Int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And generated registered class will be changed:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Serializable
|
||||||
|
@SerialName(value = "NewSample")
|
||||||
|
data class NewSample(
|
||||||
|
override val property1: String,
|
||||||
|
override val property2: Int,
|
||||||
|
) : Sample
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName(value = "RegisteredSample")
|
||||||
|
data class RegisteredSample(
|
||||||
|
override val id: SampleId,
|
||||||
|
override val property1: String,
|
||||||
|
override val property2: Int,
|
||||||
|
) : Sample, IRegisteredSample
|
||||||
|
|
||||||
|
fun Sample.asNew(): NewSample = NewSample(property1, property2)
|
||||||
|
|
||||||
|
fun Sample.asRegistered(id: SampleId): RegisteredSample = RegisteredSample(id, property1, property2)
|
||||||
|
```
|
||||||
|
|
||||||
|
So, full sample will look like:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
/**
|
||||||
|
* Your id value class. In fact, but it is not necessary
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
@JvmInline
|
||||||
|
value class SampleId(
|
||||||
|
val long: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
@GenerateCRUDModel(IRegisteredSample::class)
|
||||||
|
sealed interface Sample {
|
||||||
|
val property1: String
|
||||||
|
val property2: Int
|
||||||
|
|
||||||
|
@GenerateCRUDModelExcludeOverride
|
||||||
|
val excludedProperty: String
|
||||||
|
get() = "excluded"
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface IRegisteredSample : Sample {
|
||||||
|
val id: SampleId
|
||||||
|
|
||||||
|
@GenerateCRUDModelExcludeOverride
|
||||||
|
val excludedProperty2: Boolean
|
||||||
|
get() = false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You always may:
|
||||||
|
|
||||||
|
* Use any number of registered classes
|
||||||
|
* Disable serialization for models
|
||||||
|
* Disable serial names generation
|
||||||
|
|
||||||
|
## How to add generator
|
||||||
|
|
||||||
|
**Note: $ksp_version in the samples above is equal to supported `ksp` version presented in `/gradle/libs.versions.toml` of project**
|
||||||
|
|
||||||
|
**Note: $microutils_version in the version of MicroUtils library in your project**
|
||||||
|
|
||||||
|
1. Add `classpath` in `build.gradle` (`classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"`)
|
||||||
|
2. Add plugin to the plugins list of your module: `id "com.google.devtools.ksp"`
|
||||||
|
3. In `dependencies` block add to the required target/compile the dependency `dev.inmo:micro_utils.repos.generator:$microutils_version`:
|
||||||
|
```groovy
|
||||||
|
dependencies {
|
||||||
|
add("kspCommonMainMetadata", "dev.inmo:micro_utils.repos.generator:$microutils_version") // will work in commonMain of your multiplatform module
|
||||||
|
add("kspJvm", "dev.inmo:micro_utils.repos.generator:$microutils_version") // will work in main of your JVM module
|
||||||
|
}
|
||||||
|
|
||||||
|
ksp { // this generator do not require any arguments and we should left `ksp` empty
|
||||||
|
}
|
||||||
|
```
|
16
repos/generator/build.gradle
Normal file
16
repos/generator/build.gradle
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.jvm"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$publishJvmOnlyPath"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api libs.kt.reflect
|
||||||
|
api project(":micro_utils.repos.common")
|
||||||
|
api libs.kotlin.poet
|
||||||
|
api libs.ksp
|
||||||
|
}
|
217
repos/generator/src/main/kotlin/Processor.kt
Normal file
217
repos/generator/src/main/kotlin/Processor.kt
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.generator
|
||||||
|
|
||||||
|
import com.google.devtools.ksp.KspExperimental
|
||||||
|
import com.google.devtools.ksp.getAnnotationsByType
|
||||||
|
import com.google.devtools.ksp.isAnnotationPresent
|
||||||
|
import com.google.devtools.ksp.processing.CodeGenerator
|
||||||
|
import com.google.devtools.ksp.processing.Resolver
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
|
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||||
|
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||||
|
import com.google.devtools.ksp.symbol.KSClassifierReference
|
||||||
|
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
|
||||||
|
import com.google.devtools.ksp.symbol.KSReferenceElement
|
||||||
|
import com.google.devtools.ksp.symbol.KSType
|
||||||
|
import com.google.devtools.ksp.symbol.KSTypeAlias
|
||||||
|
import com.google.devtools.ksp.symbol.KSValueArgument
|
||||||
|
import com.google.devtools.ksp.symbol.Nullability
|
||||||
|
import com.squareup.kotlinpoet.AnnotationSpec
|
||||||
|
import com.squareup.kotlinpoet.ClassName
|
||||||
|
import com.squareup.kotlinpoet.CodeBlock
|
||||||
|
import com.squareup.kotlinpoet.FileSpec
|
||||||
|
import com.squareup.kotlinpoet.FunSpec
|
||||||
|
import com.squareup.kotlinpoet.KModifier
|
||||||
|
import com.squareup.kotlinpoet.ParameterSpec
|
||||||
|
import com.squareup.kotlinpoet.PropertySpec
|
||||||
|
import com.squareup.kotlinpoet.TypeName
|
||||||
|
import com.squareup.kotlinpoet.TypeSpec
|
||||||
|
import com.squareup.kotlinpoet.asTypeName
|
||||||
|
import com.squareup.kotlinpoet.ksp.toAnnotationSpec
|
||||||
|
import com.squareup.kotlinpoet.ksp.toClassName
|
||||||
|
import com.squareup.kotlinpoet.ksp.toTypeName
|
||||||
|
import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModel
|
||||||
|
import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModelExcludeOverride
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
|
||||||
|
private fun KSClassifierReference.quilifiedName(): String = "${qualifier ?.let { "${it.quilifiedName()}." } ?: ""}${referencedName()}"
|
||||||
|
|
||||||
|
class Processor(
|
||||||
|
private val codeGenerator: CodeGenerator
|
||||||
|
) : SymbolProcessor {
|
||||||
|
private val KSPropertyDeclaration.typeName: TypeName
|
||||||
|
get() {
|
||||||
|
return runCatching {
|
||||||
|
type.toTypeName()
|
||||||
|
}.getOrElse {
|
||||||
|
val element = type.element as KSClassifierReference
|
||||||
|
(type.element as KSClassifierReference).let {
|
||||||
|
ClassName(
|
||||||
|
element.qualifier ?.quilifiedName() ?: "",
|
||||||
|
element.referencedName()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@OptIn(KspExperimental::class)
|
||||||
|
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||||
|
val toRetry = resolver.getSymbolsWithAnnotation(
|
||||||
|
GenerateCRUDModel::class.qualifiedName!!
|
||||||
|
).filterIsInstance<KSClassDeclaration>().filterNot { ksClassDeclaration ->
|
||||||
|
val ksFile = ksClassDeclaration.containingFile ?: return@filterNot false
|
||||||
|
runCatching {
|
||||||
|
FileSpec.builder(
|
||||||
|
ksClassDeclaration.packageName.asString(),
|
||||||
|
"GeneratedModels${ksFile.fileName.removeSuffix(".kt")}"
|
||||||
|
).apply {
|
||||||
|
val annotation = ksClassDeclaration.getAnnotationsByType(GenerateCRUDModel::class).first()
|
||||||
|
addFileComment(
|
||||||
|
"""
|
||||||
|
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||||
|
TO REGENERATE IT JUST DELETE FILE
|
||||||
|
ORIGINAL FILE: ${ksFile.fileName}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
val newName = "New${ksClassDeclaration.simpleName.getShortName()}"
|
||||||
|
val registeredName = "Registered${ksClassDeclaration.simpleName.getShortName()}"
|
||||||
|
|
||||||
|
val allKSClassProperties = ksClassDeclaration.getAllProperties()
|
||||||
|
val excludedKSClassProperties = allKSClassProperties.filter {
|
||||||
|
it.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class)
|
||||||
|
}
|
||||||
|
val excludedKSClassPropertiesNames = excludedKSClassProperties.map { it.simpleName.asString() }
|
||||||
|
val ksClassProperties = allKSClassProperties.filter {
|
||||||
|
it !in excludedKSClassProperties
|
||||||
|
}
|
||||||
|
val ksClassPropertiesNames = ksClassProperties.map { it.simpleName.asString() }
|
||||||
|
val newNewType = TypeSpec.classBuilder(newName).apply {
|
||||||
|
val typeBuilder = this
|
||||||
|
addSuperinterface(ksClassDeclaration.toClassName())
|
||||||
|
addModifiers(KModifier.DATA)
|
||||||
|
if (annotation.serializable) {
|
||||||
|
addAnnotation(Serializable::class)
|
||||||
|
if (annotation.generateSerialName) {
|
||||||
|
addAnnotation(AnnotationSpec.get(SerialName(newName)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
primaryConstructor(
|
||||||
|
FunSpec.constructorBuilder().apply {
|
||||||
|
ksClassProperties.forEach {
|
||||||
|
addParameter(it.simpleName.getShortName(), it.typeName)
|
||||||
|
typeBuilder.addProperty(
|
||||||
|
PropertySpec.builder(it.simpleName.getShortName(), it.typeName, KModifier.OVERRIDE).apply {
|
||||||
|
initializer(it.simpleName.getShortName())
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
addType(
|
||||||
|
newNewType
|
||||||
|
)
|
||||||
|
|
||||||
|
val registeredSupertypes = ksClassDeclaration.annotations.filter {
|
||||||
|
it.shortName.asString() == GenerateCRUDModel::class.simpleName &&
|
||||||
|
it.annotationType.resolve().declaration.qualifiedName ?.asString() == GenerateCRUDModel::class.qualifiedName
|
||||||
|
}.flatMap {
|
||||||
|
(it.arguments.first().value as List<KSType>).map { it.declaration as KSClassDeclaration }
|
||||||
|
}.toList()
|
||||||
|
|
||||||
|
|
||||||
|
val registeredTypesProperties: List<KSPropertyDeclaration> = registeredSupertypes.flatMap { registeredType ->
|
||||||
|
registeredType.getAllProperties()
|
||||||
|
}.filter {
|
||||||
|
it.simpleName.asString() !in excludedKSClassPropertiesNames && it.getAnnotationsByType(GenerateCRUDModelExcludeOverride::class).none()
|
||||||
|
}
|
||||||
|
val allProperties: List<KSPropertyDeclaration> = ksClassProperties.toList() + registeredTypesProperties
|
||||||
|
val propertiesToOverrideInRegistered = allProperties.distinctBy { it.simpleName.asString() }.sortedBy { property ->
|
||||||
|
val name = property.simpleName.asString()
|
||||||
|
|
||||||
|
ksClassPropertiesNames.indexOf(name).takeIf { it > -1 } ?.let {
|
||||||
|
it + allProperties.size
|
||||||
|
} ?: allProperties.indexOfFirst { it.simpleName.asString() == name }
|
||||||
|
}
|
||||||
|
|
||||||
|
val newRegisteredType = TypeSpec.classBuilder(registeredName).apply {
|
||||||
|
val typeBuilder = this
|
||||||
|
addSuperinterface(ksClassDeclaration.toClassName())
|
||||||
|
if (annotation.serializable) {
|
||||||
|
addAnnotation(Serializable::class)
|
||||||
|
|
||||||
|
if (annotation.generateSerialName) {
|
||||||
|
addAnnotation(
|
||||||
|
AnnotationSpec.get(SerialName(registeredName))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addSuperinterfaces(registeredSupertypes.map { it.toClassName() })
|
||||||
|
addModifiers(KModifier.DATA)
|
||||||
|
primaryConstructor(
|
||||||
|
FunSpec.constructorBuilder().apply {
|
||||||
|
propertiesToOverrideInRegistered.forEach {
|
||||||
|
addParameter(
|
||||||
|
ParameterSpec.builder(it.simpleName.getShortName(), it.typeName).apply {
|
||||||
|
annotations += it.annotations.map { it.toAnnotationSpec() }
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
typeBuilder.addProperty(
|
||||||
|
PropertySpec.builder(it.simpleName.getShortName(), it.typeName, KModifier.OVERRIDE).apply {
|
||||||
|
initializer(it.simpleName.getShortName())
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
addType(
|
||||||
|
newRegisteredType
|
||||||
|
)
|
||||||
|
|
||||||
|
addFunction(
|
||||||
|
FunSpec.builder("asNew").apply {
|
||||||
|
receiver(ksClassDeclaration.toClassName())
|
||||||
|
addCode(
|
||||||
|
CodeBlock.of(
|
||||||
|
"return ${newNewType.name}(${newNewType.propertySpecs.joinToString { it.name }})"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
returns(ClassName(packageName, newNewType.name!!))
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
addFunction(
|
||||||
|
FunSpec.builder("asRegistered").apply {
|
||||||
|
receiver(ksClassDeclaration.toClassName())
|
||||||
|
(registeredTypesProperties.filter { it.simpleName.asString() !in ksClassPropertiesNames }).forEach {
|
||||||
|
addParameter(it.simpleName.asString(), it.typeName)
|
||||||
|
}
|
||||||
|
addCode(
|
||||||
|
CodeBlock.of(
|
||||||
|
"return ${newRegisteredType.name}(${newRegisteredType.propertySpecs.joinToString { it.name }})"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
returns(ClassName(packageName, newRegisteredType.name!!))
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}.build().let {
|
||||||
|
File(
|
||||||
|
File(ksFile.filePath).parent,
|
||||||
|
"GeneratedModels${ksFile.fileName}"
|
||||||
|
).takeIf { !it.exists() } ?.apply {
|
||||||
|
parentFile.mkdirs()
|
||||||
|
|
||||||
|
writer().use { writer ->
|
||||||
|
it.writeTo(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.isSuccess
|
||||||
|
}.toList()
|
||||||
|
|
||||||
|
return toRetry
|
||||||
|
}
|
||||||
|
}
|
11
repos/generator/src/main/kotlin/Provider.kt
Normal file
11
repos/generator/src/main/kotlin/Provider.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.generator
|
||||||
|
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||||
|
|
||||||
|
class Provider : SymbolProcessorProvider {
|
||||||
|
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(
|
||||||
|
environment.codeGenerator
|
||||||
|
)
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
dev.inmo.micro_utils.repos.generator.Provider
|
27
repos/generator/test/build.gradle
Normal file
27
repos/generator/test/build.gradle
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "com.google.devtools.ksp"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api project(":micro_utils.repos.common")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
add("kspCommonMainMetadata", project(":micro_utils.repos.generator"))
|
||||||
|
}
|
||||||
|
|
||||||
|
ksp {
|
||||||
|
}
|
@@ -0,0 +1,31 @@
|
|||||||
|
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||||
|
// TO REGENERATE IT JUST DELETE FILE
|
||||||
|
// ORIGINAL FILE: Test.kt
|
||||||
|
package dev.inmo.micro_utils.repos.generator.test
|
||||||
|
|
||||||
|
import kotlin.Int
|
||||||
|
import kotlin.String
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName(value = "NewTest")
|
||||||
|
public data class NewTest(
|
||||||
|
public override val property1: String,
|
||||||
|
public override val property2: Int,
|
||||||
|
public override val parent: ParentTypeId?,
|
||||||
|
) : Test
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName(value = "RegisteredTest")
|
||||||
|
public data class RegisteredTest(
|
||||||
|
public override val id: TestId,
|
||||||
|
public override val property1: String,
|
||||||
|
public override val property2: Int,
|
||||||
|
public override val parent: ParentTypeId?,
|
||||||
|
) : Test, IRegisteredTest
|
||||||
|
|
||||||
|
public fun Test.asNew(): NewTest = NewTest(property1, property2, parent)
|
||||||
|
|
||||||
|
public fun Test.asRegistered(id: TestId): RegisteredTest = RegisteredTest(id, property1, property2,
|
||||||
|
parent)
|
33
repos/generator/test/src/commonMain/kotlin/Test.kt
Normal file
33
repos/generator/test/src/commonMain/kotlin/Test.kt
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.generator.test
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModel
|
||||||
|
import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModelExcludeOverride
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.jvm.JvmInline
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@JvmInline
|
||||||
|
value class TestId(
|
||||||
|
val long: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
typealias ParentTypeId = TestId
|
||||||
|
|
||||||
|
@GenerateCRUDModel(IRegisteredTest::class)
|
||||||
|
sealed interface Test {
|
||||||
|
val property1: String
|
||||||
|
val property2: Int
|
||||||
|
val parent: ParentTypeId?
|
||||||
|
|
||||||
|
@GenerateCRUDModelExcludeOverride
|
||||||
|
val excludedProperty: String
|
||||||
|
get() = "excluded"
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface IRegisteredTest : Test {
|
||||||
|
val id: TestId
|
||||||
|
|
||||||
|
@GenerateCRUDModelExcludeOverride
|
||||||
|
val excludedProperty2: Boolean
|
||||||
|
get() = false
|
||||||
|
}
|
1
repos/generator/test/src/main/AndroidManifest.xml
Normal file
1
repos/generator/test/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.repos.generator.test"/>
|
@@ -1,12 +1,17 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.client.crud
|
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.ktor.common.*
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
import dev.inmo.micro_utils.repos.*
|
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.client.HttpClient
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.util.reflect.TypeInfo
|
import io.ktor.util.reflect.TypeInfo
|
||||||
import io.ktor.util.reflect.typeInfo
|
import io.ktor.util.reflect.typeInfo
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
||||||
@@ -21,6 +26,15 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
|||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
httpClient: HttpClient,
|
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),
|
||||||
|
),
|
||||||
noinline idSerializer: suspend (IdType) -> String
|
noinline idSerializer: suspend (IdType) -> String
|
||||||
) = KtorCRUDRepoClient(
|
) = KtorCRUDRepoClient(
|
||||||
KtorReadCRUDRepoClient(
|
KtorReadCRUDRepoClient(
|
||||||
@@ -35,7 +49,10 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
|||||||
KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
|
KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType
|
contentType,
|
||||||
|
newObjectsFlow,
|
||||||
|
updatedObjectsFlow,
|
||||||
|
deletedObjectsIdsFlow
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,11 +61,23 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
|||||||
subpart: String,
|
subpart: String,
|
||||||
httpClient: HttpClient,
|
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),
|
||||||
|
),
|
||||||
noinline idSerializer: suspend (IdType) -> String
|
noinline idSerializer: suspend (IdType) -> String
|
||||||
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
|
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
|
||||||
buildStandardUrl(baseUrl, subpart),
|
buildStandardUrl(baseUrl, subpart),
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType,
|
contentType,
|
||||||
|
newObjectsFlow,
|
||||||
|
updatedObjectsFlow,
|
||||||
|
deletedObjectsIdsFlow,
|
||||||
idSerializer
|
idSerializer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -80,11 +109,23 @@ inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepo
|
|||||||
subpart: String,
|
subpart: String,
|
||||||
httpClient: HttpClient,
|
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),
|
||||||
|
),
|
||||||
noinline idSerializer: suspend (IdType) -> String
|
noinline idSerializer: suspend (IdType) -> String
|
||||||
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
|
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
|
||||||
buildStandardUrl(baseUrl, subpart),
|
buildStandardUrl(baseUrl, subpart),
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType,
|
contentType,
|
||||||
|
newObjectsFlow,
|
||||||
|
updatedObjectsFlow,
|
||||||
|
deletedObjectsIdsFlow,
|
||||||
idSerializer
|
idSerializer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -53,19 +53,22 @@ class KtorWriteCrudRepoClient<ObjectType, IdType, InputValue> (
|
|||||||
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
|
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
httpClient: HttpClient,
|
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>(
|
) = KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
httpClient,
|
httpClient,
|
||||||
httpClient.createStandardWebsocketFlow(
|
newObjectsFlow,
|
||||||
buildStandardUrl(baseUrl, newObjectsFlowRouting),
|
updatedObjectsFlow,
|
||||||
),
|
deletedObjectsIdsFlow,
|
||||||
httpClient.createStandardWebsocketFlow(
|
|
||||||
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
|
|
||||||
),
|
|
||||||
httpClient.createStandardWebsocketFlow(
|
|
||||||
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
contentType(contentType)
|
contentType(contentType)
|
||||||
setBody(it)
|
setBody(it)
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.client.key.value
|
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.ktor.common.*
|
||||||
import dev.inmo.micro_utils.repos.*
|
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.client.HttpClient
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.http.encodeURLQueryComponent
|
import io.ktor.http.encodeURLQueryComponent
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
class KtorKeyValueRepoClient<Key, Value> (
|
class KtorKeyValueRepoClient<Key, Value> (
|
||||||
@@ -20,6 +24,12 @@ class KtorKeyValueRepoClient<Key, Value> (
|
|||||||
httpClient: HttpClient,
|
httpClient: HttpClient,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
noinline idSerializer: suspend (Key) -> String,
|
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
|
noinline valueSerializer: suspend (Value) -> String
|
||||||
) = KtorKeyValueRepoClient(
|
) = KtorKeyValueRepoClient(
|
||||||
KtorReadKeyValueRepoClient(
|
KtorReadKeyValueRepoClient(
|
||||||
@@ -28,7 +38,9 @@ class KtorKeyValueRepoClient<Key, Value> (
|
|||||||
KtorWriteKeyValueRepoClient(
|
KtorWriteKeyValueRepoClient(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType
|
contentType,
|
||||||
|
onNewValue,
|
||||||
|
onValueRemoved
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
inline operator fun <reified Key, reified Value> invoke(
|
inline operator fun <reified Key, reified Value> invoke(
|
||||||
@@ -37,12 +49,20 @@ class KtorKeyValueRepoClient<Key, Value> (
|
|||||||
httpClient: HttpClient,
|
httpClient: HttpClient,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
noinline idSerializer: suspend (Key) -> String,
|
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
|
noinline valueSerializer: suspend (Value) -> String
|
||||||
) = KtorKeyValueRepoClient(
|
) = KtorKeyValueRepoClient(
|
||||||
buildStandardUrl(baseUrl, subpart),
|
buildStandardUrl(baseUrl, subpart),
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType,
|
contentType,
|
||||||
idSerializer,
|
idSerializer,
|
||||||
|
onNewValue,
|
||||||
|
onValueRemoved,
|
||||||
valueSerializer
|
valueSerializer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -60,17 +60,19 @@ class KtorWriteKeyValueRepoClient<Key, Value>(
|
|||||||
inline operator fun <reified Key, reified Value> invoke(
|
inline operator fun <reified Key, reified Value> invoke(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
httpClient: HttpClient,
|
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>(
|
) = KtorWriteKeyValueRepoClient<Key, Value>(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType,
|
contentType,
|
||||||
httpClient.createStandardWebsocketFlow(
|
onNewValue,
|
||||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
onValueRemoved,
|
||||||
),
|
|
||||||
httpClient.createStandardWebsocketFlow(
|
|
||||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
|
||||||
),
|
|
||||||
typeInfo<List<Key>>(),
|
typeInfo<List<Key>>(),
|
||||||
typeInfo<List<Value>>(),
|
typeInfo<List<Value>>(),
|
||||||
typeInfo<Map<Key, Value>>()
|
typeInfo<Map<Key, Value>>()
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.client.key.values
|
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.ktor.common.*
|
||||||
import dev.inmo.micro_utils.repos.*
|
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.client.HttpClient
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.http.encodeURLQueryComponent
|
import io.ktor.http.encodeURLQueryComponent
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
class KtorKeyValuesRepoClient<Key, Value> (
|
class KtorKeyValuesRepoClient<Key, Value> (
|
||||||
@@ -20,6 +25,15 @@ class KtorKeyValuesRepoClient<Key, Value> (
|
|||||||
httpClient: HttpClient,
|
httpClient: HttpClient,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
noinline keySerializer: suspend (Key) -> String,
|
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
|
noinline valueSerializer: suspend (Value) -> String
|
||||||
) = KtorKeyValuesRepoClient(
|
) = KtorKeyValuesRepoClient(
|
||||||
KtorReadKeyValuesRepoClient(
|
KtorReadKeyValuesRepoClient(
|
||||||
@@ -32,7 +46,10 @@ class KtorKeyValuesRepoClient<Key, Value> (
|
|||||||
KtorWriteKeyValuesRepoClient(
|
KtorWriteKeyValuesRepoClient(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType
|
contentType,
|
||||||
|
onNewValue,
|
||||||
|
onValueRemoved,
|
||||||
|
onDataCleared
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
inline operator fun <reified Key : Any, reified Value : Any> invoke(
|
inline operator fun <reified Key : Any, reified Value : Any> invoke(
|
||||||
@@ -41,12 +58,24 @@ class KtorKeyValuesRepoClient<Key, Value> (
|
|||||||
httpClient: HttpClient,
|
httpClient: HttpClient,
|
||||||
contentType: ContentType,
|
contentType: ContentType,
|
||||||
noinline keySerializer: suspend (Key) -> String,
|
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
|
noinline valueSerializer: suspend (Value) -> String
|
||||||
) = KtorKeyValuesRepoClient(
|
) = KtorKeyValuesRepoClient(
|
||||||
buildStandardUrl(baseUrl, subpart),
|
buildStandardUrl(baseUrl, subpart),
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType,
|
contentType,
|
||||||
keySerializer,
|
keySerializer,
|
||||||
|
onNewValue,
|
||||||
|
onValueRemoved,
|
||||||
|
onDataCleared,
|
||||||
valueSerializer
|
valueSerializer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -59,13 +88,25 @@ inline fun <reified Key : Any, reified Value : Any> KtorKeyValuesRepoClient(
|
|||||||
keySerializer: SerializationStrategy<Key>,
|
keySerializer: SerializationStrategy<Key>,
|
||||||
valueSerializer: SerializationStrategy<Value>,
|
valueSerializer: SerializationStrategy<Value>,
|
||||||
serialFormat: StringFormat,
|
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>(
|
) = KtorKeyValuesRepoClient<Key, Value>(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType,
|
contentType,
|
||||||
{
|
{
|
||||||
serialFormat.encodeToString(keySerializer, it).encodeURLQueryComponent()
|
serialFormat.encodeToString(keySerializer, it).encodeURLQueryComponent()
|
||||||
}
|
},
|
||||||
|
onNewValue,
|
||||||
|
onValueRemoved,
|
||||||
|
onDataCleared
|
||||||
) {
|
) {
|
||||||
serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent()
|
serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent()
|
||||||
}
|
}
|
||||||
@@ -77,13 +118,25 @@ inline fun <reified Key : Any, reified Value : Any> KtorKeyValuesRepoClient(
|
|||||||
keySerializer: SerializationStrategy<Key>,
|
keySerializer: SerializationStrategy<Key>,
|
||||||
valueSerializer: SerializationStrategy<Value>,
|
valueSerializer: SerializationStrategy<Value>,
|
||||||
serialFormat: BinaryFormat,
|
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>(
|
) = KtorKeyValuesRepoClient<Key, Value>(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType,
|
contentType,
|
||||||
{
|
{
|
||||||
serialFormat.encodeHex(keySerializer, it)
|
serialFormat.encodeHex(keySerializer, it)
|
||||||
}
|
},
|
||||||
|
onNewValue,
|
||||||
|
onValueRemoved,
|
||||||
|
onDataCleared
|
||||||
) {
|
) {
|
||||||
serialFormat.encodeHex(valueSerializer, it)
|
serialFormat.encodeHex(valueSerializer, it)
|
||||||
}
|
}
|
||||||
|
@@ -84,20 +84,23 @@ class KtorWriteKeyValuesRepoClient<Key : Any, Value : Any>(
|
|||||||
inline operator fun <reified Key : Any, reified Value : Any> invoke(
|
inline operator fun <reified Key : Any, reified Value : Any> invoke(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
httpClient: HttpClient,
|
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>(
|
) = KtorWriteKeyValuesRepoClient<Key, Value>(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
httpClient,
|
httpClient,
|
||||||
contentType,
|
contentType,
|
||||||
httpClient.createStandardWebsocketFlow(
|
onNewValue,
|
||||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
onValueRemoved,
|
||||||
),
|
onDataCleared,
|
||||||
httpClient.createStandardWebsocketFlow(
|
|
||||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
|
||||||
),
|
|
||||||
httpClient.createStandardWebsocketFlow(
|
|
||||||
buildStandardUrl(baseUrl, onDataClearedRoute),
|
|
||||||
),
|
|
||||||
typeInfo<Key>(),
|
typeInfo<Key>(),
|
||||||
typeInfo<Value>(),
|
typeInfo<Value>(),
|
||||||
typeInfo<Map<Key, List<Value>>>()
|
typeInfo<Map<Key, List<Value>>>()
|
||||||
|
@@ -7,6 +7,8 @@ String[] includes = [
|
|||||||
":safe_wrapper",
|
":safe_wrapper",
|
||||||
":crypto",
|
":crypto",
|
||||||
":koin",
|
":koin",
|
||||||
|
":koin:generator",
|
||||||
|
":koin:generator:test",
|
||||||
":selector:common",
|
":selector:common",
|
||||||
":pagination:common",
|
":pagination:common",
|
||||||
":pagination:exposed",
|
":pagination:exposed",
|
||||||
@@ -16,6 +18,8 @@ String[] includes = [
|
|||||||
":language_codes",
|
":language_codes",
|
||||||
":language_codes:generator",
|
":language_codes:generator",
|
||||||
":repos:common",
|
":repos:common",
|
||||||
|
":repos:generator",
|
||||||
|
":repos:generator:test",
|
||||||
":repos:cache",
|
":repos:cache",
|
||||||
":repos:exposed",
|
":repos:exposed",
|
||||||
":repos:inmemory",
|
":repos:inmemory",
|
||||||
|
92
startup/launcher/README.md
Normal file
92
startup/launcher/README.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Startup Plugin Launcher
|
||||||
|
|
||||||
|
This module contains tools to start your plugin system.
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
Base config is pretty simple:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
So, `"dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin"` is the fully qualified name of plugin you wish to be
|
||||||
|
included in the server.
|
||||||
|
|
||||||
|
> JS note: In JS there are no opportunity to determine object type by its full name. Because of it, in JS developers
|
||||||
|
> should prefer to use `Config` in their kotlin code directly instead of json config passing. More info see in [JS](#js)
|
||||||
|
> section
|
||||||
|
|
||||||
|
## JVM
|
||||||
|
|
||||||
|
For JVM target you may use main class by path: `dev.inmo.micro_utils.startup.launcher.MainKt`
|
||||||
|
|
||||||
|
It is expected, that you will pass the main ONE argument with path to the config json. Sample of launching:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew run --args="sample.config.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
Content of `sample.config.json` described in [Config](#config) section.
|
||||||
|
|
||||||
|
You may build runnable app using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew assembleDist
|
||||||
|
```
|
||||||
|
|
||||||
|
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`
|
||||||
|
|
||||||
|
## JS
|
||||||
|
|
||||||
|
In JS for starting of your plugins app, you should use `PluginsStarter` in your code:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
PluginsStarter.startPlugins(
|
||||||
|
Config(HelloWorldPlugin)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
`Config` here is deserialized variant from [Config](#config) section. As was said in [Config](#config) section, in JS
|
||||||
|
there is no way to find classes/objects by their full qualifiers. Because of it you should use some way to register your
|
||||||
|
plugins in `StartPluginSerializer` or use the code like in the snippet above: there plugins will be registered
|
||||||
|
automatically.
|
||||||
|
|
||||||
|
In case you wish to register your plugins manually and run server from config, you should use one of the ways to register
|
||||||
|
plugin on start.
|
||||||
|
|
||||||
|
Sample with `EagerInitialization`: [Kotlin JS doc about lazy initialization](https://kotlinlang.org/docs/js-ir-compiler.html#incremental-compilation-for-development-binaries),
|
||||||
|
[@EagerInitialization docs](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.js/-eager-initialization/):
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@ExperimentalStdlibApi
|
||||||
|
@EagerInitialization
|
||||||
|
val plugin = createStartupPluginAndRegister("PluginNameToUseInConfig") {
|
||||||
|
// Your plugin creation. For example:
|
||||||
|
HelloWorldPlugin
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
So, in that case you will be able to load plugins list as `JsonObject` from anywhere and start plugins app with it:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
PluginsStarter.startPlugins(
|
||||||
|
jsonObject
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
It will load `HelloWorldPlugin` if `jsonObject` have next content:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"PluginNameToUseInConfig"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "application"
|
id "application"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppJavaProjectPresetPath"
|
apply from: "$mppJsAndJavaProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -13,7 +13,7 @@ kotlin {
|
|||||||
api internalProject("micro_utils.startup.plugin")
|
api internalProject("micro_utils.startup.plugin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmTest {
|
commonTest {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation libs.kt.coroutines.test
|
implementation libs.kt.coroutines.test
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user