mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-18 14:59:24 +00:00
Compare commits
101 Commits
Author | SHA1 | Date | |
---|---|---|---|
23bcb26a58 | |||
e55f60c30b | |||
0d0c16e16d | |||
540d5cce7c | |||
a548b00979 | |||
4b7ca6d565 | |||
0473fa238c | |||
cfc7119697 | |||
22a6520d3e | |||
fb25e91191 | |||
c116b270b6 | |||
aa2d598689 | |||
5ef3bb746b | |||
037616e271 | |||
abbea906f1 | |||
9132e216c9 | |||
12a7e3c4af | |||
b40c093917 | |||
7ac12455c8 | |||
5043eec7a2 | |||
cf31f53e01 | |||
cd22d76fa7 | |||
c8759843f7 | |||
781bbcc012 | |||
6da29c0686 | |||
fcdb6fc45a | |||
e785a99bd7 | |||
121e513fdd | |||
9cf01ab54f | |||
c655107681 | |||
91a5af6a9a | |||
86e74c0a6f | |||
d8f01f21a0 | |||
9fb8626d8c | |||
1c52e04cdb | |||
798128256e | |||
72c2df47fd | |||
c9c6d4c0c1 | |||
2f4f9f3003 | |||
22e8f8e5d6 | |||
04cf8c3d9a | |||
52198be543 | |||
80fd5a489b | |||
b187043ee1 | |||
8965752055 | |||
b796620267 | |||
62df81bb4e | |||
0a8e0f6178 | |||
f705020aaa | |||
78bd3b9853 | |||
992b283597 | |||
8fc1ff1d59 | |||
3bbde61f39 | |||
8d955c4b9d | |||
18593c530b | |||
78903cd4eb | |||
eaa143f7d7 | |||
bcb0e42fa2 | |||
8eed435302 | |||
0e4a63057f | |||
1af5faa440 | |||
7412217b0c | |||
3e749d75b7 | |||
846b5c87c9 | |||
7b5e84f80b | |||
27822a8a66 | |||
36f4e7ec37 | |||
187e84ad65 | |||
195fe221c4 | |||
6f9c19bbf6 | |||
65bdab4f7e | |||
c4d5fcfc22 | |||
43232afa62 | |||
18908a01d7 | |||
b22fbfb3bc | |||
57aaea88b6 | |||
140949c5ea | |||
639241e0d3 | |||
e0eb42bc2d | |||
7990b21cc5 | |||
2ba5c97709 | |||
33b7c85fc2 | |||
7f6c02ffdf | |||
f368616e6f | |||
dd632f4203 | |||
a8f3ae501b | |||
ea76963ac2 | |||
99dfa97958 | |||
f68270a5b3 | |||
542ed81034 | |||
404a11f5e7 | |||
411221070e | |||
25d35d0c76 | |||
c72904d61c | |||
b94c9acd26 | |||
bdb4988569 | |||
1dafd1352a | |||
31fbd3cad7 | |||
848bc5ec10 | |||
4d0ad826a0 | |||
6955820fcc |
134
CHANGELOG.md
134
CHANGELOG.md
@@ -1,5 +1,139 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.11.10
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Cache`:
|
||||||
|
* `KVCache` has been replaced to the package `dev.inmo.micro_utils.repos.cache`
|
||||||
|
* `SimpleKVCache` has been replaced to the package `dev.inmo.micro_utils.repos.cache`
|
||||||
|
* New `KVCache` subtype - `FullKVCache`
|
||||||
|
* Add `Full*` variants of standard repos
|
||||||
|
* Add `cached`/`caching` (for write repos) extensions for all standard types of repos
|
||||||
|
|
||||||
|
## 0.11.9
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Coroutines`: `1.6.1` -> `1.6.3`
|
||||||
|
* `Ktor`: `2.0.2` -> `2.0.3`
|
||||||
|
* `Compose`: `1.2.0-alpha01-dev686` -> `1.2.0-alpha01-dev729`
|
||||||
|
|
||||||
|
## 0.11.8
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Common`:
|
||||||
|
* Fixes in `FileKeyValueRepo`
|
||||||
|
|
||||||
|
## 0.11.7
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* New abstractions `SimpleMapper` and `SimpleSuspendableMapper`
|
||||||
|
* `Repos`:
|
||||||
|
* `Common`:
|
||||||
|
* Add mappers for `CRUDRepo`
|
||||||
|
|
||||||
|
## 0.11.6
|
||||||
|
|
||||||
|
* `FSM`:
|
||||||
|
* `Common`
|
||||||
|
* Several fixes related to the jobs handling
|
||||||
|
|
||||||
|
## 0.11.5
|
||||||
|
|
||||||
|
* `Coroutines`:
|
||||||
|
* `Compose`:
|
||||||
|
* Add extension `StateFlow#asMutableComposeListState` and `StateFlow#asComposeList`
|
||||||
|
* Add extension `StateFlow#asMutableComposeState`/`StateFlow#asComposeState`
|
||||||
|
|
||||||
|
## 0.11.4
|
||||||
|
|
||||||
|
**THIS VERSION HAS BEEN BROKEN, DO NOT USE IT**
|
||||||
|
|
||||||
|
## 0.11.3
|
||||||
|
|
||||||
|
* `Ktor`:
|
||||||
|
* Support of `WebSockets` has been improved
|
||||||
|
* `Client`:
|
||||||
|
* New extensions: `HttpClient#openBaseWebSocketFlow`, `HttpClient#openWebSocketFlow`, `HttpClient#openSecureWebSocketFlow`
|
||||||
|
|
||||||
|
## 0.11.2
|
||||||
|
|
||||||
|
* `Ktor`:
|
||||||
|
* Support of `WebSockets` has been improved and added fixes inside of clients
|
||||||
|
|
||||||
|
## 0.11.1
|
||||||
|
|
||||||
|
* `Repos`
|
||||||
|
* `Ktor`
|
||||||
|
* In `configureReadKeyValueRepoRoutes` and `configureReadKeyValuesRepoRoutes` configurators fixed requiring of `reversed` property
|
||||||
|
|
||||||
|
## 0.11.0
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `UUID`: `0.4.0` -> `0.4.1`
|
||||||
|
* `Ktor`
|
||||||
|
* `Client`:
|
||||||
|
* New extension fun `HttpResponse#throwOnUnsuccess`
|
||||||
|
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
|
||||||
|
* `Server`:
|
||||||
|
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
|
||||||
|
* `Repos`
|
||||||
|
* `Ktor`:
|
||||||
|
* Fully rewritten work with all declared repositories
|
||||||
|
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
|
||||||
|
|
||||||
|
## 0.10.8
|
||||||
|
|
||||||
|
* `Common`
|
||||||
|
* Add `Element.isOverflow*` extension properties
|
||||||
|
|
||||||
|
## 0.10.7
|
||||||
|
|
||||||
|
* `Pagination`:
|
||||||
|
* Now it is possible to use `doForAll*` and `getForAll` functions in non suspend places
|
||||||
|
|
||||||
|
## 0.10.6
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Ktor`: `2.0.1` -> `2.0.2`
|
||||||
|
* `Common`
|
||||||
|
* `JS`:
|
||||||
|
* Add `ResizeObserver` functionality
|
||||||
|
|
||||||
|
## 0.10.5
|
||||||
|
|
||||||
|
* `Versions`
|
||||||
|
* `Compose`: `1.2.0-alpha01-dev683` -> `1.2.0-alpha01-dev686`
|
||||||
|
* `Repos`
|
||||||
|
* `Android`:
|
||||||
|
* New function `SharedPreferencesKeyValueRepo`
|
||||||
|
* `FSM`
|
||||||
|
* Add `StateHandlingErrorHandler` and opportunity to handle states handling errors
|
||||||
|
|
||||||
|
## 0.10.4
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Serialization`: `1.3.2` -> `1.3.3`
|
||||||
|
|
||||||
|
## 0.10.3
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Compose`: `1.2.0-alpha01-dev682` -> `1.2.0-alpha01-dev683`
|
||||||
|
* `Coroutines`:
|
||||||
|
* Fixes in `AccumulatorFlow`
|
||||||
|
|
||||||
|
## 0.10.2
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Compose`: `1.2.0-alpha01-dev675` -> `1.2.0-alpha01-dev682`
|
||||||
|
|
||||||
|
## 0.10.1
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Ktor`: `2.0.0` -> `2.0.1`
|
||||||
|
* `Crypto`:
|
||||||
|
* Add `hmacSha256`
|
||||||
|
* Add `hex`
|
||||||
|
|
||||||
## 0.10.0
|
## 0.10.0
|
||||||
|
|
||||||
* `Versions`:
|
* `Versions`:
|
||||||
|
@@ -0,0 +1,53 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
interface SimpleMapper<T1, T2> {
|
||||||
|
fun convertToT1(from: T2): T1
|
||||||
|
fun convertToT2(from: T1): T2
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("convertFromT2")
|
||||||
|
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T2) = convertToT1(from)
|
||||||
|
@JvmName("convertFromT1")
|
||||||
|
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T1) = convertToT2(from)
|
||||||
|
|
||||||
|
class SimpleMapperImpl<T1, T2>(
|
||||||
|
private val t1: (T2) -> T1,
|
||||||
|
private val t2: (T1) -> T2,
|
||||||
|
) : SimpleMapper<T1, T2> {
|
||||||
|
override fun convertToT1(from: T2): T1 = t1.invoke(from)
|
||||||
|
|
||||||
|
override fun convertToT2(from: T1): T2 = t2.invoke(from)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <T1, T2> simpleMapper(
|
||||||
|
noinline t1: (T2) -> T1,
|
||||||
|
noinline t2: (T1) -> T2,
|
||||||
|
) = SimpleMapperImpl(t1, t2)
|
||||||
|
|
||||||
|
interface SimpleSuspendableMapper<T1, T2> {
|
||||||
|
suspend fun convertToT1(from: T2): T1
|
||||||
|
suspend fun convertToT2(from: T1): T2
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmName("convertFromT2")
|
||||||
|
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T2) = convertToT1(from)
|
||||||
|
@JvmName("convertFromT1")
|
||||||
|
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T1) = convertToT2(from)
|
||||||
|
|
||||||
|
class SimpleSuspendableMapperImpl<T1, T2>(
|
||||||
|
private val t1: suspend (T2) -> T1,
|
||||||
|
private val t2: suspend (T1) -> T2,
|
||||||
|
) : SimpleSuspendableMapper<T1, T2> {
|
||||||
|
override suspend fun convertToT1(from: T2): T1 = t1.invoke(from)
|
||||||
|
|
||||||
|
override suspend fun convertToT2(from: T1): T2 = t2.invoke(from)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <T1, T2> simpleSuspendableMapper(
|
||||||
|
noinline t1: suspend (T2) -> T1,
|
||||||
|
noinline t2: suspend (T1) -> T2,
|
||||||
|
) = SimpleSuspendableMapperImpl(t1, t2)
|
@@ -41,6 +41,13 @@ data class Optional<T> internal constructor(
|
|||||||
inline val <T> T.optional
|
inline val <T> T.optional
|
||||||
get() = Optional.presented(this)
|
get() = Optional.presented(this)
|
||||||
|
|
||||||
|
inline val <T : Any> T?.optionalOrAbsentIfNull
|
||||||
|
get() = if (this == null) {
|
||||||
|
Optional.absent<T>()
|
||||||
|
} else {
|
||||||
|
Optional.presented(this)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||||
*/
|
*/
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
|
inline val Element.isOverflowWidth
|
||||||
|
get() = scrollWidth > clientWidth
|
||||||
|
|
||||||
|
inline val Element.isOverflowHeight
|
||||||
|
get() = scrollHeight > clientHeight
|
||||||
|
|
||||||
|
inline val Element.isOverflow
|
||||||
|
get() = isOverflowHeight || isOverflowWidth
|
@@ -0,0 +1,58 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import org.w3c.dom.*
|
||||||
|
import kotlin.js.Json
|
||||||
|
import kotlin.js.json
|
||||||
|
|
||||||
|
external class ResizeObserver(
|
||||||
|
callback: (Array<ResizeObserverEntry>, ResizeObserver) -> Unit
|
||||||
|
) {
|
||||||
|
fun observe(target: Element, options: Json = definedExternally)
|
||||||
|
|
||||||
|
fun unobserve(target: Element)
|
||||||
|
|
||||||
|
fun disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ResizeObserverSize {
|
||||||
|
val blockSize: Float
|
||||||
|
val inlineSize: Float
|
||||||
|
}
|
||||||
|
|
||||||
|
external interface ResizeObserverEntry {
|
||||||
|
val borderBoxSize: Array<ResizeObserverSize>
|
||||||
|
val contentBoxSize: Array<ResizeObserverSize>
|
||||||
|
val devicePixelContentBoxSize: Array<ResizeObserverSize>
|
||||||
|
val contentRect: DOMRectReadOnly
|
||||||
|
val target: Element
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ResizeObserver.observe(target: Element, options: ResizeObserverObserveOptions) = observe(
|
||||||
|
target,
|
||||||
|
json(
|
||||||
|
"box" to options.box ?.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
class ResizeObserverObserveOptions(
|
||||||
|
val box: Box? = null
|
||||||
|
) {
|
||||||
|
sealed interface Box {
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
object Content : Box {
|
||||||
|
override val name: String
|
||||||
|
get() = "content-box"
|
||||||
|
}
|
||||||
|
|
||||||
|
object Border : Box {
|
||||||
|
override val name: String
|
||||||
|
get() = "border-box"
|
||||||
|
}
|
||||||
|
|
||||||
|
object DevicePixelContent : Box {
|
||||||
|
override val name: String
|
||||||
|
get() = "device-pixel-content-box"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,26 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
|
import dev.inmo.micro_utils.common.applyDiff
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
|
||||||
|
scope: CoroutineScope
|
||||||
|
): SnapshotStateList<T> {
|
||||||
|
val state = mutableStateListOf<T>()
|
||||||
|
subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
state.applyDiff(it)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified T> Flow<List<T>>.asComposeList(
|
||||||
|
scope: CoroutineScope
|
||||||
|
): List<T> = asMutableComposeListState(scope)
|
||||||
|
|
@@ -0,0 +1,35 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines.compose
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
fun <T> Flow<T>.asMutableComposeState(
|
||||||
|
initial: T,
|
||||||
|
scope: CoroutineScope
|
||||||
|
): MutableState<T> {
|
||||||
|
val state = mutableStateOf(initial)
|
||||||
|
subscribeSafelyWithoutExceptions(scope) { state.value = it }
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <T> StateFlow<T>.asMutableComposeState(
|
||||||
|
scope: CoroutineScope
|
||||||
|
): MutableState<T> = asMutableComposeState(value, scope)
|
||||||
|
|
||||||
|
fun <T> Flow<T>.asComposeState(
|
||||||
|
initial: T,
|
||||||
|
scope: CoroutineScope
|
||||||
|
): State<T> {
|
||||||
|
val state = asMutableComposeState(initial, scope)
|
||||||
|
return derivedStateOf { state.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <T> StateFlow<T>.asComposeState(
|
||||||
|
scope: CoroutineScope
|
||||||
|
): State<T> = asComposeState(value, scope)
|
||||||
|
|
@@ -6,11 +6,12 @@ import kotlinx.coroutines.channels.Channel
|
|||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
|
||||||
private sealed interface AccumulatorFlowStep
|
private sealed interface AccumulatorFlowStep<T>
|
||||||
private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep
|
private data class DataRetrievedAccumulatorFlowStep<T>(val data: T) : AccumulatorFlowStep<T>
|
||||||
private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
|
private data class SubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
|
||||||
private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
|
private data class UnsubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences:
|
* This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences:
|
||||||
@@ -26,12 +27,12 @@ class AccumulatorFlow<T>(
|
|||||||
private val subscope = scope.LinkedSupervisorScope()
|
private val subscope = scope.LinkedSupervisorScope()
|
||||||
private val activeData = ArrayDeque<T>()
|
private val activeData = ArrayDeque<T>()
|
||||||
private val dataMutex = Mutex()
|
private val dataMutex = Mutex()
|
||||||
private val channelsForBroadcast = mutableListOf<Channel<Any>>()
|
private val channelsForBroadcast = mutableListOf<Channel<T>>()
|
||||||
private val channelsMutex = Mutex()
|
private val channelsMutex = Mutex()
|
||||||
private val steps = subscope.actor<AccumulatorFlowStep> { step ->
|
private val steps = subscope.actor<AccumulatorFlowStep<T>> { step ->
|
||||||
when (step) {
|
when (step) {
|
||||||
is DataRetrievedAccumulatorFlowStep -> {
|
is DataRetrievedAccumulatorFlowStep -> {
|
||||||
if (activeData.first() === step.data) {
|
if (activeData.firstOrNull() === step.data) {
|
||||||
dataMutex.withLock {
|
dataMutex.withLock {
|
||||||
activeData.removeFirst()
|
activeData.removeFirst()
|
||||||
}
|
}
|
||||||
@@ -42,7 +43,7 @@ class AccumulatorFlow<T>(
|
|||||||
dataMutex.withLock {
|
dataMutex.withLock {
|
||||||
val dataToSend = activeData.toList()
|
val dataToSend = activeData.toList()
|
||||||
safelyWithoutExceptions {
|
safelyWithoutExceptions {
|
||||||
dataToSend.forEach { step.channel.send(it as Any) }
|
dataToSend.forEach { step.channel.send(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,24 +59,29 @@ class AccumulatorFlow<T>(
|
|||||||
channelsMutex.withLock {
|
channelsMutex.withLock {
|
||||||
channelsForBroadcast.forEach { channel ->
|
channelsForBroadcast.forEach { channel ->
|
||||||
safelyWithResult {
|
safelyWithResult {
|
||||||
channel.send(it as Any)
|
channel.send(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun collectSafely(collector: FlowCollector<T>) {
|
override suspend fun collectSafely(collector: FlowCollector<T>) {
|
||||||
val channel = Channel<Any>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
|
val channel = Channel<T>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
|
||||||
steps.send(SubscribeAccumulatorFlowStep(channel))
|
steps.send(SubscribeAccumulatorFlowStep(channel))
|
||||||
for (data in channel) {
|
val result = runCatchingSafely {
|
||||||
try {
|
for (data in channel) {
|
||||||
collector.emit(data as T)
|
val emitResult = runCatchingSafely {
|
||||||
steps.send(DataRetrievedAccumulatorFlowStep(data))
|
collector.emit(data)
|
||||||
} finally {
|
}
|
||||||
channel.cancel()
|
if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) {
|
||||||
steps.send(UnsubscribeAccumulatorFlowStep(channel))
|
steps.send(DataRetrievedAccumulatorFlowStep(data))
|
||||||
|
}
|
||||||
|
emitResult.getOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
channel.cancel()
|
||||||
|
steps.send(UnsubscribeAccumulatorFlowStep(channel))
|
||||||
|
result.getOrThrow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.inmo.micro_utils.crypto
|
||||||
|
|
||||||
|
val HEX_ARRAY = "0123456789abcdef".toCharArray()
|
||||||
|
|
||||||
|
fun SourceBytes.hex(): String {
|
||||||
|
val hexChars = CharArray(size * 2)
|
||||||
|
for (j in indices) {
|
||||||
|
val v: Int = this[j].toInt() and 0xFF
|
||||||
|
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
|
||||||
|
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
|
||||||
|
}
|
||||||
|
return hexChars.concatToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SourceString.hex(): String = encodeToByteArray().hex()
|
@@ -0,0 +1,3 @@
|
|||||||
|
package dev.inmo.micro_utils.crypto
|
||||||
|
|
||||||
|
expect fun SourceString.hmacSha256(key: String): String
|
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.inmo.micro_utils.crypto
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class Hex {
|
||||||
|
@Test
|
||||||
|
fun testSimpleHmacSHA256Message() {
|
||||||
|
val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||||
|
val resultHex = text.hex()
|
||||||
|
assertEquals(
|
||||||
|
"4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742c2073656420646f20656975736d6f642074656d706f7220696e6369646964756e74207574206c61626f726520657420646f6c6f7265206d61676e6120616c697175612e20557420656e696d206164206d696e696d2076656e69616d2c2071756973206e6f737472756420657865726369746174696f6e20756c6c616d636f206c61626f726973206e69736920757420616c697175697020657820656120636f6d6d6f646f20636f6e7365717561742e2044756973206175746520697275726520646f6c6f7220696e20726570726568656e646572697420696e20766f6c7570746174652076656c697420657373652063696c6c756d20646f6c6f726520657520667567696174206e756c6c612070617269617475722e204578636570746575722073696e74206f6363616563617420637570696461746174206e6f6e2070726f6964656e742c2073756e7420696e2063756c706120717569206f666669636961206465736572756e74206d6f6c6c697420616e696d20696420657374206c61626f72756d2e",
|
||||||
|
resultHex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
package dev.inmo.micro_utils.crypto
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class HmacSHA256 {
|
||||||
|
@Test
|
||||||
|
fun testSimpleHmacSHA256Message() {
|
||||||
|
val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||||
|
val resultSha = text.hmacSha256("Example")
|
||||||
|
assertEquals("5a481d59329ef862b158eedc95392ebb22492ba3014661a3379d8201db992484", resultSha)
|
||||||
|
}
|
||||||
|
}
|
@@ -7,3 +7,7 @@ external interface CryptoJs {
|
|||||||
@JsModule("crypto-js")
|
@JsModule("crypto-js")
|
||||||
@JsNonModule
|
@JsNonModule
|
||||||
external val CryptoJS: CryptoJs
|
external val CryptoJS: CryptoJs
|
||||||
|
|
||||||
|
actual fun SourceString.hmacSha256(key: String): String {
|
||||||
|
return CryptoJS.asDynamic().HmacSHA256(this, key).toString().unsafeCast<String>()
|
||||||
|
}
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
package dev.inmo.micro_utils.crypto
|
||||||
|
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
actual fun SourceString.hmacSha256(key: String): String {
|
||||||
|
val mac = Mac.getInstance("HmacSHA256")
|
||||||
|
|
||||||
|
val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
|
||||||
|
mac.init(secretKey)
|
||||||
|
|
||||||
|
return mac.doFinal(toByteArray()).hex()
|
||||||
|
}
|
@@ -3,7 +3,10 @@ package dev.inmo.micro_utils.fsm.common
|
|||||||
import dev.inmo.micro_utils.common.Optional
|
import dev.inmo.micro_utils.common.Optional
|
||||||
import dev.inmo.micro_utils.common.onPresented
|
import dev.inmo.micro_utils.common.onPresented
|
||||||
import dev.inmo.micro_utils.coroutines.*
|
import dev.inmo.micro_utils.coroutines.*
|
||||||
|
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
|
||||||
|
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
@@ -13,13 +16,24 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
* handling until [start] method will be called
|
* handling until [start] method will be called
|
||||||
*/
|
*/
|
||||||
interface StatesMachine<T : State> : StatesHandler<T, T> {
|
interface StatesMachine<T : State> : StatesHandler<T, T> {
|
||||||
|
suspend fun launchStateHandling(
|
||||||
|
state: T,
|
||||||
|
handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||||
|
onStateHandlingErrorHandler: StateHandlingErrorHandler<T>
|
||||||
|
): T? {
|
||||||
|
return runCatchingSafely {
|
||||||
|
handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
||||||
|
handleState(state)
|
||||||
|
}
|
||||||
|
}.getOrElse {
|
||||||
|
onStateHandlingErrorHandler(state, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
suspend fun launchStateHandling(
|
suspend fun launchStateHandling(
|
||||||
state: T,
|
state: T,
|
||||||
handlers: List<CheckableHandlerHolder<in T, T>>
|
handlers: List<CheckableHandlerHolder<in T, T>>
|
||||||
): T? {
|
): T? {
|
||||||
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
return launchStateHandling(state, handlers, defaultStateHandlingErrorHandler())
|
||||||
handleState(state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,8 +52,9 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
|
|||||||
*/
|
*/
|
||||||
operator fun <T: State> invoke(
|
operator fun <T: State> invoke(
|
||||||
statesManager: StatesManager<T>,
|
statesManager: StatesManager<T>,
|
||||||
handlers: List<CheckableHandlerHolder<in T, T>>
|
handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||||
) = DefaultStatesMachine(statesManager, handlers)
|
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
|
||||||
|
) = DefaultStatesMachine(statesManager, handlers, onStateHandlingErrorHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,12 +67,17 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
|
|||||||
open class DefaultStatesMachine <T: State>(
|
open class DefaultStatesMachine <T: State>(
|
||||||
protected val statesManager: StatesManager<T>,
|
protected val statesManager: StatesManager<T>,
|
||||||
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
|
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||||
|
protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
|
||||||
) : StatesMachine<T> {
|
) : StatesMachine<T> {
|
||||||
/**
|
/**
|
||||||
* Will call [launchStateHandling] for state handling
|
* Will call [launchStateHandling] for state handling
|
||||||
*/
|
*/
|
||||||
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
|
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
|
||||||
|
|
||||||
|
override suspend fun launchStateHandling(state: T, handlers: List<CheckableHandlerHolder<in T, T>>): T? {
|
||||||
|
return launchStateHandling(state, handlers, onStateHandlingErrorHandler)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This
|
* This
|
||||||
*/
|
*/
|
||||||
@@ -66,10 +86,10 @@ open class DefaultStatesMachine <T: State>(
|
|||||||
|
|
||||||
protected open suspend fun performUpdate(state: T) {
|
protected open suspend fun performUpdate(state: T) {
|
||||||
val newState = launchStateHandling(state, handlers)
|
val newState = launchStateHandling(state, handlers)
|
||||||
if (newState != null) {
|
if (newState == null) {
|
||||||
statesManager.update(state, newState)
|
|
||||||
} else {
|
|
||||||
statesManager.endChain(state)
|
statesManager.endChain(state)
|
||||||
|
} else {
|
||||||
|
statesManager.update(state, newState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +119,7 @@ open class DefaultStatesMachine <T: State>(
|
|||||||
* [StatesManager.endChain].
|
* [StatesManager.endChain].
|
||||||
*/
|
*/
|
||||||
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||||
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
|
(statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) {
|
||||||
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
||||||
}
|
}
|
||||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
||||||
@@ -115,10 +135,6 @@ open class DefaultStatesMachine <T: State>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statesManager.getActiveStates().forEach {
|
|
||||||
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
import dev.inmo.micro_utils.common.*
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
|
||||||
|
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
@@ -20,9 +22,11 @@ interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
|
|||||||
open class DefaultUpdatableStatesMachine<T : State>(
|
open class DefaultUpdatableStatesMachine<T : State>(
|
||||||
statesManager: StatesManager<T>,
|
statesManager: StatesManager<T>,
|
||||||
handlers: List<CheckableHandlerHolder<in T, T>>,
|
handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||||
|
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
|
||||||
) : DefaultStatesMachine<T>(
|
) : DefaultStatesMachine<T>(
|
||||||
statesManager,
|
statesManager,
|
||||||
handlers
|
handlers,
|
||||||
|
onStateHandlingErrorHandler
|
||||||
), UpdatableStatesMachine<T> {
|
), UpdatableStatesMachine<T> {
|
||||||
protected val jobsStates = mutableMapOf<Job, T>()
|
protected val jobsStates = mutableMapOf<Job, T>()
|
||||||
|
|
||||||
@@ -34,7 +38,7 @@ open class DefaultUpdatableStatesMachine<T : State>(
|
|||||||
*/
|
*/
|
||||||
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
|
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
|
||||||
statesJobsMutex.withLock {
|
statesJobsMutex.withLock {
|
||||||
if (compare(previousState, actualState)) {
|
if (shouldReplaceJob(previousState, actualState)) {
|
||||||
statesJobs[actualState] ?.cancel()
|
statesJobs[actualState] ?.cancel()
|
||||||
}
|
}
|
||||||
val job = previousState.mapOnPresented {
|
val job = previousState.mapOnPresented {
|
||||||
@@ -48,6 +52,7 @@ open class DefaultUpdatableStatesMachine<T : State>(
|
|||||||
statesJobs.remove(
|
statesJobs.remove(
|
||||||
jobsStates[job] ?: return@withLock
|
jobsStates[job] ?: return@withLock
|
||||||
)
|
)
|
||||||
|
jobsStates.remove(job)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common.utils
|
||||||
|
|
||||||
|
typealias StateHandlingErrorHandler<T> = suspend (T, Throwable) -> T?
|
||||||
|
val DefaultStateHandlingErrorHandler: StateHandlingErrorHandler<*> = { _, _ -> null }
|
||||||
|
inline fun <T> defaultStateHandlingErrorHandler(): StateHandlingErrorHandler<T> = DefaultStateHandlingErrorHandler as StateHandlingErrorHandler<T>
|
||||||
|
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.10.0
|
version=0.11.10
|
||||||
android_code_version=115
|
android_code_version=134
|
||||||
|
@@ -1,22 +1,22 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kt = "1.6.21"
|
kt = "1.6.21"
|
||||||
kt-serialization = "1.3.2"
|
kt-serialization = "1.3.3"
|
||||||
kt-coroutines = "1.6.1"
|
kt-coroutines = "1.6.3"
|
||||||
|
|
||||||
jb-compose = "1.2.0-alpha01-dev675"
|
jb-compose = "1.2.0-alpha01-dev729"
|
||||||
jb-exposed = "0.38.2"
|
jb-exposed = "0.38.2"
|
||||||
jb-dokka = "1.6.21"
|
jb-dokka = "1.6.21"
|
||||||
|
|
||||||
klock = "2.7.0"
|
klock = "2.7.0"
|
||||||
uuid = "0.4.0"
|
uuid = "0.4.1"
|
||||||
|
|
||||||
ktor = "2.0.0"
|
ktor = "2.0.3"
|
||||||
|
|
||||||
gh-release = "2.3.7"
|
gh-release = "2.4.1"
|
||||||
|
|
||||||
android-gradle = "7.0.4"
|
android-gradle = "7.0.4"
|
||||||
dexcount = "3.0.1"
|
dexcount = "3.1.0"
|
||||||
|
|
||||||
android-coreKtx = "1.7.0"
|
android-coreKtx = "1.7.0"
|
||||||
android-recyclerView = "1.2.1"
|
android-recyclerView = "1.2.1"
|
||||||
@@ -37,17 +37,24 @@ kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-
|
|||||||
|
|
||||||
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
|
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
|
||||||
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
|
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
|
||||||
|
kt-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kt-coroutines" }
|
||||||
|
|
||||||
|
|
||||||
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
|
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
|
||||||
|
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
||||||
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||||
|
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
||||||
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
|
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
|
||||||
|
ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" }
|
||||||
|
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
||||||
|
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
||||||
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
|
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
|
||||||
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
|
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
|
||||||
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" }
|
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" }
|
||||||
ktor-websockets = { module = "io.ktor:ktor-websockets", version.ref = "ktor" }
|
ktor-websockets = { module = "io.ktor:ktor-websockets", version.ref = "ktor" }
|
||||||
ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" }
|
ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" }
|
||||||
ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
|
ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
|
||||||
|
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
|
||||||
|
|
||||||
|
|
||||||
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
|
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import io.ktor.client.plugins.ClientRequestException
|
||||||
|
import io.ktor.client.statement.HttpResponse
|
||||||
|
import io.ktor.http.isSuccess
|
||||||
|
|
||||||
|
inline fun HttpResponse.throwOnUnsuccess(
|
||||||
|
unsuccessMessage: () -> String
|
||||||
|
) {
|
||||||
|
if (status.isSuccess()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ClientRequestException(this, unsuccessMessage())
|
||||||
|
}
|
@@ -0,0 +1,103 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.Warning
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.coroutines.safely
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.plugins.pluginOrNull
|
||||||
|
import io.ktor.client.plugins.websocket.*
|
||||||
|
import io.ktor.client.request.HttpRequestBuilder
|
||||||
|
import io.ktor.http.URLProtocol
|
||||||
|
import kotlinx.coroutines.channels.SendChannel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||||
|
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||||
|
*/
|
||||||
|
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
|
||||||
|
inline fun <reified T : Any> openBaseWebSocketFlow(
|
||||||
|
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||||
|
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
|
||||||
|
): Flow<T> {
|
||||||
|
return channelFlow {
|
||||||
|
do {
|
||||||
|
val reconnect = runCatchingSafely {
|
||||||
|
webSocketSessionRequest()
|
||||||
|
checkReconnection(null)
|
||||||
|
}.getOrElse { e ->
|
||||||
|
checkReconnection(e).also {
|
||||||
|
if (!it) {
|
||||||
|
close(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (reconnect && isActive)
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
safely {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||||
|
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> HttpClient.openWebSocketFlow(
|
||||||
|
url: String,
|
||||||
|
useSecureConnection: Boolean,
|
||||||
|
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||||
|
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||||
|
): Flow<T> {
|
||||||
|
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
||||||
|
|
||||||
|
return openBaseWebSocketFlow<T>(checkReconnection) {
|
||||||
|
val block: suspend DefaultClientWebSocketSession.() -> Unit = {
|
||||||
|
while (isActive) {
|
||||||
|
send(receiveDeserialized<T>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useSecureConnection) {
|
||||||
|
wss(url, requestBuilder, block)
|
||||||
|
} else {
|
||||||
|
ws(url, requestBuilder, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||||
|
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> HttpClient.openWebSocketFlow(
|
||||||
|
url: String,
|
||||||
|
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||||
|
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||||
|
): Flow<T> = openWebSocketFlow(url, false, checkReconnection, requestBuilder)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||||
|
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> HttpClient.openSecureWebSocketFlow(
|
||||||
|
url: String,
|
||||||
|
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||||
|
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||||
|
): Flow<T> = openWebSocketFlow(url, true, checkReconnection, requestBuilder)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||||
|
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> HttpClient.createStandardWebsocketFlow(
|
||||||
|
url: String,
|
||||||
|
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||||
|
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||||
|
): Flow<T> = openWebSocketFlow(url, checkReconnection, requestBuilder)
|
@@ -4,7 +4,10 @@ import dev.inmo.micro_utils.common.MPPFile
|
|||||||
import dev.inmo.micro_utils.ktor.common.TemporalFileId
|
import dev.inmo.micro_utils.ktor.common.TemporalFileId
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import org.w3c.dom.mediasource.ENDED
|
||||||
|
import org.w3c.dom.mediasource.ReadyState
|
||||||
import org.w3c.xhr.*
|
import org.w3c.xhr.*
|
||||||
|
import org.w3c.xhr.XMLHttpRequest.Companion.DONE
|
||||||
|
|
||||||
suspend fun tempUpload(
|
suspend fun tempUpload(
|
||||||
fullTempUploadDraftPath: String,
|
fullTempUploadDraftPath: String,
|
||||||
@@ -12,7 +15,7 @@ suspend fun tempUpload(
|
|||||||
onUpload: (Long, Long) -> Unit
|
onUpload: (Long, Long) -> Unit
|
||||||
): TemporalFileId {
|
): TemporalFileId {
|
||||||
val formData = FormData()
|
val formData = FormData()
|
||||||
val answer = CompletableDeferred<TemporalFileId>()
|
val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
|
||||||
|
|
||||||
formData.append(
|
formData.append(
|
||||||
"data",
|
"data",
|
||||||
@@ -37,17 +40,15 @@ suspend fun tempUpload(
|
|||||||
request.open("POST", fullTempUploadDraftPath, true)
|
request.open("POST", fullTempUploadDraftPath, true)
|
||||||
request.send(formData)
|
request.send(formData)
|
||||||
|
|
||||||
val handle = currentCoroutineContext().job.invokeOnCompletion {
|
answer.invokeOnCompletion {
|
||||||
runCatching {
|
runCatching {
|
||||||
request.abort()
|
if (request.readyState != DONE) {
|
||||||
|
request.abort()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return runCatching {
|
return answer.await()
|
||||||
answer.await()
|
|
||||||
}.also {
|
|
||||||
handle.dispose()
|
|
||||||
}.getOrThrow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.server
|
||||||
|
|
||||||
|
import io.ktor.server.application.ApplicationCall
|
||||||
|
import io.ktor.server.response.responseType
|
||||||
|
import io.ktor.util.InternalAPI
|
||||||
|
import io.ktor.util.reflect.TypeInfo
|
||||||
|
|
||||||
|
@InternalAPI
|
||||||
|
suspend fun <T : Any> ApplicationCall.respond(
|
||||||
|
message: T,
|
||||||
|
typeInfo: TypeInfo
|
||||||
|
) {
|
||||||
|
response.responseType = typeInfo
|
||||||
|
response.pipeline.execute(this, message as Any)
|
||||||
|
}
|
@@ -12,6 +12,7 @@ import io.ktor.websocket.send
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.SerializationStrategy
|
import kotlinx.serialization.SerializationStrategy
|
||||||
|
|
||||||
|
@Deprecated("This method will be removed soon")
|
||||||
fun <T> Route.includeWebsocketHandling(
|
fun <T> Route.includeWebsocketHandling(
|
||||||
suburl: String,
|
suburl: String,
|
||||||
flow: Flow<T>,
|
flow: Flow<T>,
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.server
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.safely
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import io.ktor.http.URLProtocol
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.application.pluginOrNull
|
||||||
|
import io.ktor.server.routing.Route
|
||||||
|
import io.ktor.server.routing.application
|
||||||
|
import io.ktor.server.websocket.*
|
||||||
|
import io.ktor.websocket.send
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.serialization.SerializationStrategy
|
||||||
|
|
||||||
|
inline fun <reified T : Any> Route.includeWebsocketHandling(
|
||||||
|
suburl: String,
|
||||||
|
flow: Flow<T>,
|
||||||
|
protocol: URLProtocol? = null,
|
||||||
|
noinline dataMapper: suspend WebSocketServerSession.(T) -> T? = { it }
|
||||||
|
) {
|
||||||
|
application.apply {
|
||||||
|
pluginOrNull(WebSockets) ?: install(WebSockets)
|
||||||
|
}
|
||||||
|
webSocket(suburl, protocol ?.name) {
|
||||||
|
safely {
|
||||||
|
flow.collect {
|
||||||
|
sendSerialized(dataMapper(it) ?: return@collect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -13,6 +13,7 @@ import io.ktor.http.content.streamProvider
|
|||||||
import io.ktor.server.application.call
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.request.receiveMultipart
|
import io.ktor.server.request.receiveMultipart
|
||||||
import io.ktor.server.response.respond
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.response.respondText
|
||||||
import io.ktor.server.routing.Route
|
import io.ktor.server.routing.Route
|
||||||
import io.ktor.server.routing.post
|
import io.ktor.server.routing.post
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@@ -111,7 +112,7 @@ class TemporalFilesRoutingConfigurator(
|
|||||||
temporalFilesMutex.withLock {
|
temporalFilesMutex.withLock {
|
||||||
temporalFilesMap[fileId] = file
|
temporalFilesMap[fileId] = file
|
||||||
}
|
}
|
||||||
call.respond(fileId.string)
|
call.respondText(fileId.string)
|
||||||
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
|
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
|
||||||
} ?: call.respond(HttpStatusCode.BadRequest)
|
} ?: call.respond(HttpStatusCode.BadRequest)
|
||||||
}
|
}
|
||||||
|
@@ -2,19 +2,19 @@ package dev.inmo.micro_utils.pagination.utils
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
|
||||||
suspend fun <T> doForAll(
|
inline fun <T> doForAll(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
paginationMapper: (PaginationResult<T>) -> Pagination?,
|
paginationMapper: (PaginationResult<T>) -> Pagination?,
|
||||||
block: suspend (Pagination) -> PaginationResult<T>
|
block: (Pagination) -> PaginationResult<T>
|
||||||
) {
|
) {
|
||||||
doWithPagination(initialPagination) {
|
doWithPagination(initialPagination) {
|
||||||
block(it).let(paginationMapper)
|
block(it).let(paginationMapper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T> doForAllWithNextPaging(
|
inline fun <T> doForAllWithNextPaging(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
block: suspend (Pagination) -> PaginationResult<T>
|
block: (Pagination) -> PaginationResult<T>
|
||||||
) {
|
) {
|
||||||
doForAll(
|
doForAll(
|
||||||
initialPagination,
|
initialPagination,
|
||||||
@@ -23,9 +23,9 @@ suspend fun <T> doForAllWithNextPaging(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T> doAllWithCurrentPaging(
|
inline fun <T> doAllWithCurrentPaging(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
block: suspend (Pagination) -> PaginationResult<T>
|
block: (Pagination) -> PaginationResult<T>
|
||||||
) {
|
) {
|
||||||
doForAll(
|
doForAll(
|
||||||
initialPagination,
|
initialPagination,
|
||||||
@@ -34,7 +34,7 @@ suspend fun <T> doAllWithCurrentPaging(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T> doForAllWithCurrentPaging(
|
inline fun <T> doForAllWithCurrentPaging(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
block: suspend (Pagination) -> PaginationResult<T>
|
block: (Pagination) -> PaginationResult<T>
|
||||||
) = doAllWithCurrentPaging(initialPagination, block)
|
) = doAllWithCurrentPaging(initialPagination, block)
|
||||||
|
@@ -2,10 +2,10 @@ package dev.inmo.micro_utils.pagination.utils
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
|
||||||
suspend fun <T> getAll(
|
inline fun <T> getAll(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
paginationMapper: (PaginationResult<T>) -> Pagination?,
|
paginationMapper: (PaginationResult<T>) -> Pagination?,
|
||||||
block: suspend (Pagination) -> PaginationResult<T>
|
block: (Pagination) -> PaginationResult<T>
|
||||||
): List<T> {
|
): List<T> {
|
||||||
val results = mutableListOf<T>()
|
val results = mutableListOf<T>()
|
||||||
doForAll(initialPagination, paginationMapper) {
|
doForAll(initialPagination, paginationMapper) {
|
||||||
@@ -16,46 +16,45 @@ suspend fun <T> getAll(
|
|||||||
return results.toList()
|
return results.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T, R> R.getAllBy(
|
inline fun <T, R> R.getAllBy(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
paginationMapper: R.(PaginationResult<T>) -> Pagination?,
|
paginationMapper: R.(PaginationResult<T>) -> Pagination?,
|
||||||
block: suspend R.(Pagination) -> PaginationResult<T>
|
block: R.(Pagination) -> PaginationResult<T>
|
||||||
): List<T> = getAll(
|
): List<T> = getAll(
|
||||||
initialPagination,
|
initialPagination,
|
||||||
{ paginationMapper(it) },
|
{ paginationMapper(it) },
|
||||||
{ block(it) }
|
{ block(it) }
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun <T> getAllWithNextPaging(
|
inline fun <T> getAllWithNextPaging(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
block: suspend (Pagination) -> PaginationResult<T>
|
block: (Pagination) -> PaginationResult<T>
|
||||||
): List<T> = getAll(
|
): List<T> = getAll(
|
||||||
initialPagination,
|
initialPagination,
|
||||||
{ it.nextPageIfNotEmpty() },
|
{ it.nextPageIfNotEmpty() },
|
||||||
block
|
block
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun <T, R> R.getAllByWithNextPaging(
|
inline fun <T, R> R.getAllByWithNextPaging(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
block: suspend R.(Pagination) -> PaginationResult<T>
|
block: R.(Pagination) -> PaginationResult<T>
|
||||||
): List<T> = getAllWithNextPaging(
|
): List<T> = getAllWithNextPaging(
|
||||||
initialPagination,
|
initialPagination,
|
||||||
{ block(it) }
|
{ block(it) }
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun <T> getAllWithCurrentPaging(
|
inline fun <T> getAllWithCurrentPaging(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
block: suspend (Pagination) -> PaginationResult<T>
|
block: (Pagination) -> PaginationResult<T>
|
||||||
): List<T> = getAll(
|
): List<T> = getAll(
|
||||||
initialPagination,
|
initialPagination,
|
||||||
{ it.currentPageIfNotEmpty() },
|
{ it.currentPageIfNotEmpty() },
|
||||||
block
|
block
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun <T, R> R.getAllByWithCurrentPaging(
|
inline fun <T, R> R.getAllByWithCurrentPaging(
|
||||||
initialPagination: Pagination = FirstPagePagination(),
|
initialPagination: Pagination = FirstPagePagination(),
|
||||||
block: suspend R.(Pagination) -> PaginationResult<T>
|
block: R.(Pagination) -> PaginationResult<T>
|
||||||
): List<T> = getAllWithCurrentPaging(
|
): List<T> = getAllWithCurrentPaging(
|
||||||
initialPagination,
|
initialPagination
|
||||||
{ block(it) }
|
) { block(it) }
|
||||||
)
|
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
|
|
||||||
open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||||
protected val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||||
protected val kvCache: KVCache<IdType, ObjectType>,
|
protected open val kvCache: KVCache<IdType, ObjectType>,
|
||||||
protected val idGetter: (ObjectType) -> IdType
|
protected open val idGetter: (ObjectType) -> IdType
|
||||||
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo {
|
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo {
|
||||||
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)
|
||||||
@@ -18,8 +18,63 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
||||||
|
kvCache: KVCache<IdType, ObjectType>,
|
||||||
|
idGetter: (ObjectType) -> IdType
|
||||||
|
) = ReadCRUDCacheRepo(this, kvCache, idGetter)
|
||||||
|
|
||||||
|
open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||||
|
protected open val parentRepo: WriteCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
|
protected open val kvCache: KVCache<IdType, ObjectType>,
|
||||||
|
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
|
protected open val idGetter: (ObjectType) -> IdType
|
||||||
|
) : WriteCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
|
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
|
||||||
|
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
|
||||||
|
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
|
||||||
|
|
||||||
|
val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}.launchIn(scope)
|
||||||
|
|
||||||
|
override suspend fun deleteById(ids: List<IdType>) = parentRepo.deleteById(ids)
|
||||||
|
|
||||||
|
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
|
||||||
|
val updated = parentRepo.update(values)
|
||||||
|
|
||||||
|
kvCache.unset(values.map { it.id })
|
||||||
|
kvCache.set(updated.associateBy { idGetter(it) })
|
||||||
|
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
||||||
|
return parentRepo.update(id, value) ?.also {
|
||||||
|
kvCache.unset(id)
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
||||||
|
val created = parentRepo.create(values)
|
||||||
|
|
||||||
|
kvCache.set(
|
||||||
|
created.associateBy { idGetter(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
return created
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
|
||||||
|
kvCache: KVCache<IdType, ObjectType>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
idGetter: (ObjectType) -> IdType
|
||||||
|
) = WriteCRUDCacheRepo(this, kvCache, scope, idGetter)
|
||||||
|
|
||||||
|
|
||||||
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||||
parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
kvCache: KVCache<IdType, ObjectType>,
|
kvCache: KVCache<IdType, ObjectType>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
idGetter: (ObjectType) -> IdType
|
idGetter: (ObjectType) -> IdType
|
||||||
@@ -27,8 +82,17 @@ open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
|||||||
parentRepo,
|
parentRepo,
|
||||||
kvCache,
|
kvCache,
|
||||||
idGetter
|
idGetter
|
||||||
), CRUDRepo<ObjectType, IdType, InputValueType>, WriteCRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
|
),
|
||||||
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
|
||||||
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
parentRepo,
|
||||||
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
|
kvCache,
|
||||||
}
|
scope,
|
||||||
|
idGetter
|
||||||
|
),
|
||||||
|
CRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
|
|
||||||
|
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
||||||
|
kvCache: KVCache<IdType, ObjectType>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
idGetter: (ObjectType) -> IdType
|
||||||
|
) = CRUDCacheRepo(this, kvCache, scope, idGetter)
|
||||||
|
@@ -1,41 +1,6 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.*
|
@Deprecated("Replaced", ReplaceWith("KVCache", "dev.inmo.micro_utils.repos.cache.cache.KVCache"))
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
typealias KVCache<K, V> = dev.inmo.micro_utils.repos.cache.cache.KVCache<K, V>
|
||||||
import kotlinx.coroutines.sync.Mutex
|
@Deprecated("Replaced", ReplaceWith("SimpleKVCache", "dev.inmo.micro_utils.repos.cache.cache.SimpleKVCache"))
|
||||||
import kotlinx.coroutines.sync.withLock
|
typealias SimpleKVCache<K, V> = dev.inmo.micro_utils.repos.cache.cache.SimpleKVCache<K, V>
|
||||||
|
|
||||||
interface KVCache<K, V> : KeyValueRepo<K, V>
|
|
||||||
|
|
||||||
open class SimpleKVCache<K, V>(
|
|
||||||
protected val cachedValuesCount: Int,
|
|
||||||
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
|
||||||
) : KVCache<K, V>, KeyValueRepo<K, V> by kvParent {
|
|
||||||
protected open val cacheStack = ArrayList<K>(cachedValuesCount)
|
|
||||||
protected val syncMutex = Mutex()
|
|
||||||
|
|
||||||
protected suspend fun makeUnset(toUnset: List<K>) {
|
|
||||||
cacheStack.removeAll(toUnset)
|
|
||||||
kvParent.unset(toUnset)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun set(toSet: Map<K, V>) {
|
|
||||||
syncMutex.withLock {
|
|
||||||
if (toSet.size > cachedValuesCount) {
|
|
||||||
cacheStack.clear()
|
|
||||||
|
|
||||||
kvParent.unset(getAllWithNextPaging { kvParent.keys(it) })
|
|
||||||
val keysToInclude = toSet.keys.drop(toSet.size - cachedValuesCount)
|
|
||||||
|
|
||||||
cacheStack.addAll(keysToInclude)
|
|
||||||
kvParent.set(keysToInclude.associateWith { toSet.getValue(it) })
|
|
||||||
} else {
|
|
||||||
makeUnset(cacheStack.take(toSet.size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun unset(toUnset: List<K>) {
|
|
||||||
syncMutex.withLock { makeUnset(toUnset) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,20 +1,25 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
|
|
||||||
open class ReadKeyValueCacheRepo<Key,Value>(
|
open class ReadKeyValueCacheRepo<Key,Value>(
|
||||||
protected val parentRepo: ReadKeyValueRepo<Key, Value>,
|
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||||
protected val kvCache: KVCache<Key, Value>,
|
protected open val kvCache: KVCache<Key, Value>,
|
||||||
) : ReadKeyValueRepo<Key,Value> by parentRepo {
|
) : ReadKeyValueRepo<Key,Value> by parentRepo {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
||||||
|
kvCache: KVCache<Key, Value>
|
||||||
|
) = ReadKeyValueCacheRepo(this, kvCache)
|
||||||
|
|
||||||
open class KeyValueCacheRepo<Key,Value>(
|
open class KeyValueCacheRepo<Key,Value>(
|
||||||
parentRepo: KeyValueRepo<Key, Value>,
|
parentRepo: KeyValueRepo<Key, Value>,
|
||||||
kvCache: KVCache<Key, Value>,
|
kvCache: KVCache<Key, Value>,
|
||||||
@@ -23,3 +28,8 @@ open class KeyValueCacheRepo<Key,Value>(
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
||||||
|
kvCache: KVCache<Key, Value>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
) = KeyValueCacheRepo(this, kvCache, scope)
|
||||||
|
@@ -5,15 +5,14 @@ import dev.inmo.micro_utils.pagination.PaginationResult
|
|||||||
import dev.inmo.micro_utils.pagination.utils.paginate
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
|
|
||||||
open class ReadKeyValuesCacheRepo<Key,Value>(
|
open class ReadKeyValuesCacheRepo<Key,Value>(
|
||||||
protected val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||||
protected val kvCache: KVCache<Key, List<Value>>
|
protected open val kvCache: KVCache<Key, List<Value>>
|
||||||
) : ReadKeyValuesRepo<Key,Value> by parentRepo {
|
) : ReadKeyValuesRepo<Key,Value> by parentRepo {
|
||||||
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 kvCache.get(k) ?.paginate(
|
return kvCache.get(k) ?.paginate(
|
||||||
@@ -31,6 +30,10 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||||
|
kvCache: KVCache<Key, List<Value>>
|
||||||
|
) = ReadKeyValuesCacheRepo(this, kvCache)
|
||||||
|
|
||||||
open class KeyValuesCacheRepo<Key,Value>(
|
open class KeyValuesCacheRepo<Key,Value>(
|
||||||
parentRepo: KeyValuesRepo<Key, Value>,
|
parentRepo: KeyValuesRepo<Key, Value>,
|
||||||
kvCache: KVCache<Key, List<Value>>,
|
kvCache: KVCache<Key, List<Value>>,
|
||||||
@@ -40,3 +43,8 @@ open class KeyValuesCacheRepo<Key,Value>(
|
|||||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
|
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
|
||||||
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
|
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(
|
||||||
|
kvCache: KVCache<Key, List<Value>>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
) = KeyValuesCacheRepo(this, kvCache, scope)
|
||||||
|
8
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/FullKVCache.kt
vendored
Normal file
8
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/FullKVCache.kt
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.cache
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface declares that current type of [KVCache] will contains all the data all the time of its life
|
||||||
|
*/
|
||||||
|
interface FullKVCache<K, V> : KVCache<K, V> {
|
||||||
|
companion object
|
||||||
|
}
|
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/KVCache.kt
vendored
Normal file
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/KVCache.kt
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.cache
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
|
||||||
|
interface KVCache<K, V> : KeyValueRepo<K, V> {
|
||||||
|
companion object
|
||||||
|
}
|
28
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleFullKVCache.kt
vendored
Normal file
28
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleFullKVCache.kt
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.cache
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.MapKeyValueRepo
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
open class SimpleFullKVCache<K, V>(
|
||||||
|
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||||
|
) : FullKVCache<K, V>, KeyValueRepo<K, V> by kvParent {
|
||||||
|
protected val syncMutex = Mutex()
|
||||||
|
|
||||||
|
override suspend fun set(toSet: Map<K, V>) {
|
||||||
|
syncMutex.withLock {
|
||||||
|
kvParent.set(toSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unset(toUnset: List<K>) {
|
||||||
|
syncMutex.withLock {
|
||||||
|
kvParent.unset(toUnset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <K, V> FullKVCache(
|
||||||
|
kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||||
|
) = SimpleFullKVCache<K, V>(kvParent)
|
42
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleKVCache.kt
vendored
Normal file
42
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleKVCache.kt
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.cache
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
open class SimpleKVCache<K, V>(
|
||||||
|
protected val cachedValuesCount: Int,
|
||||||
|
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||||
|
) : KVCache<K, V>, KeyValueRepo<K, V> by kvParent {
|
||||||
|
protected open val cacheQueue = ArrayDeque<K>(cachedValuesCount)
|
||||||
|
protected val syncMutex = Mutex()
|
||||||
|
|
||||||
|
protected suspend fun makeUnset(toUnset: List<K>) {
|
||||||
|
cacheQueue.removeAll(toUnset)
|
||||||
|
kvParent.unset(toUnset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun set(toSet: Map<K, V>) {
|
||||||
|
syncMutex.withLock {
|
||||||
|
for ((k, v) in toSet) {
|
||||||
|
if (cacheQueue.size >= cachedValuesCount) {
|
||||||
|
cacheQueue.removeFirstOrNull() ?.let {
|
||||||
|
kvParent.unset(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cacheQueue.addLast(k)
|
||||||
|
kvParent.set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unset(toUnset: List<K>) {
|
||||||
|
syncMutex.withLock { makeUnset(toUnset) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <K, V> KVCache(
|
||||||
|
cachedValuesCount: Int,
|
||||||
|
kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||||
|
) = SimpleKVCache<K, V>(cachedValuesCount, kvParent)
|
95
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullCRUDCacheRepo.kt
vendored
Normal file
95
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullCRUDCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.full
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
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.cache.*
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
|
open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
||||||
|
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||||
|
protected open val kvCache: FullKVCache<IdType, ObjectType>,
|
||||||
|
protected open val idGetter: (ObjectType) -> IdType
|
||||||
|
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||||
|
protected inline fun <T> doOrTakeAndActualize(
|
||||||
|
action: FullKVCache<IdType, ObjectType>.() -> Optional<T>,
|
||||||
|
actionElse: ReadCRUDRepo<ObjectType, IdType>.() -> T,
|
||||||
|
actualize: FullKVCache<IdType, ObjectType>.(T) -> Unit
|
||||||
|
): T {
|
||||||
|
kvCache.action().onPresented {
|
||||||
|
return it
|
||||||
|
}.onAbsent {
|
||||||
|
return parentRepo.actionElse().also {
|
||||||
|
kvCache.actualize(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error("The result should be returned above")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected suspend fun actualizeAll() {
|
||||||
|
kvCache.clear()
|
||||||
|
doForAllWithNextPaging {
|
||||||
|
parentRepo.getByPagination(it).also {
|
||||||
|
kvCache.set(it.results.associateBy { idGetter(it) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
|
||||||
|
{ values(pagination).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
|
{ getByPagination(pagination) },
|
||||||
|
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun count(): Long = doOrTakeAndActualize(
|
||||||
|
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
|
||||||
|
{ count() },
|
||||||
|
{ if (it != 0L) actualizeAll() }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun contains(id: IdType): Boolean = doOrTakeAndActualize(
|
||||||
|
{ contains(id).takeIf { it }.optionalOrAbsentIfNull },
|
||||||
|
{ contains(id) },
|
||||||
|
{ if (it) parentRepo.getById(id) ?.let { set(id, it) } }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize(
|
||||||
|
{ get(id) ?.optional ?: Optional.absent() },
|
||||||
|
{ getById(id) },
|
||||||
|
{ it ?.let { set(idGetter(it), it) } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
||||||
|
kvCache: FullKVCache<IdType, ObjectType>,
|
||||||
|
idGetter: (ObjectType) -> IdType
|
||||||
|
) = FullReadCRUDCacheRepo(this, kvCache, idGetter)
|
||||||
|
|
||||||
|
open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||||
|
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
|
kvCache: FullKVCache<IdType, ObjectType>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
|
idGetter: (ObjectType) -> IdType
|
||||||
|
) : FullReadCRUDCacheRepo<ObjectType, IdType>(
|
||||||
|
parentRepo,
|
||||||
|
kvCache,
|
||||||
|
idGetter
|
||||||
|
),
|
||||||
|
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
|
||||||
|
parentRepo,
|
||||||
|
kvCache,
|
||||||
|
scope,
|
||||||
|
idGetter
|
||||||
|
),
|
||||||
|
CRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
|
|
||||||
|
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
||||||
|
kvCache: FullKVCache<IdType, ObjectType>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
|
idGetter: (ObjectType) -> IdType
|
||||||
|
) = FullCRUDCacheRepo(this, kvCache, scope, idGetter)
|
104
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt
vendored
Normal file
104
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.full
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
open class FullReadKeyValueCacheRepo<Key,Value>(
|
||||||
|
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||||
|
protected open val kvCache: FullKVCache<Key, Value>,
|
||||||
|
) : ReadKeyValueRepo<Key, Value> {
|
||||||
|
protected inline fun <T> doOrTakeAndActualize(
|
||||||
|
action: FullKVCache<Key, Value>.() -> Optional<T>,
|
||||||
|
actionElse: ReadKeyValueRepo<Key, Value>.() -> T,
|
||||||
|
actualize: FullKVCache<Key, Value>.(T) -> Unit
|
||||||
|
): T {
|
||||||
|
kvCache.action().onPresented {
|
||||||
|
return it
|
||||||
|
}.onAbsent {
|
||||||
|
return parentRepo.actionElse().also {
|
||||||
|
kvCache.actualize(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error("The result should be returned above")
|
||||||
|
}
|
||||||
|
protected suspend fun actualizeAll() {
|
||||||
|
kvCache.clear()
|
||||||
|
kvCache.set(parentRepo.getAll { keys(it) }.toMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(k: Key): Value? = doOrTakeAndActualize(
|
||||||
|
{ get(k) ?.optional ?: Optional.absent() },
|
||||||
|
{ get(k) },
|
||||||
|
{ set(k, it ?: return@doOrTakeAndActualize) }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = doOrTakeAndActualize(
|
||||||
|
{ values(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
|
{ values(pagination, reversed) },
|
||||||
|
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun count(): Long = doOrTakeAndActualize(
|
||||||
|
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
|
||||||
|
{ count() },
|
||||||
|
{ if (it != 0L) actualizeAll() }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun contains(key: Key): Boolean = doOrTakeAndActualize(
|
||||||
|
{ contains(key).takeIf { it }.optionalOrAbsentIfNull },
|
||||||
|
{ contains(key) },
|
||||||
|
{ if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
|
||||||
|
{ keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
|
{ keys(pagination, reversed) },
|
||||||
|
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
|
||||||
|
{ keys(v, pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
|
{ parentRepo.keys(v, pagination, reversed) },
|
||||||
|
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
||||||
|
kvCache: FullKVCache<Key, Value>
|
||||||
|
) = FullReadKeyValueCacheRepo(this, kvCache)
|
||||||
|
|
||||||
|
open class FullWriteKeyValueCacheRepo<Key,Value>(
|
||||||
|
protected open val parentRepo: WriteKeyValueRepo<Key, Value>,
|
||||||
|
protected open val kvCache: FullKVCache<Key, Value>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
) : WriteKeyValueRepo<Key, Value> by parentRepo {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
|
||||||
|
kvCache: FullKVCache<Key, Value>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
|
||||||
|
|
||||||
|
open class FullKeyValueCacheRepo<Key,Value>(
|
||||||
|
parentRepo: KeyValueRepo<Key, Value>,
|
||||||
|
kvCache: FullKVCache<Key, Value>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
|
||||||
|
KeyValueRepo<Key,Value>,
|
||||||
|
ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(parentRepo, kvCache) {
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
||||||
|
kvCache: FullKVCache<Key, Value>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
) = FullKeyValueCacheRepo(this, kvCache, scope)
|
154
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt
vendored
Normal file
154
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.full
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.*
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
open class FullReadKeyValuesCacheRepo<Key,Value>(
|
||||||
|
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||||
|
protected open val kvCache: FullKVCache<Key, List<Value>>,
|
||||||
|
) : ReadKeyValuesRepo<Key, Value> {
|
||||||
|
protected inline fun <T> doOrTakeAndActualize(
|
||||||
|
action: FullKVCache<Key, List<Value>>.() -> Optional<T>,
|
||||||
|
actionElse: ReadKeyValuesRepo<Key, Value>.() -> T,
|
||||||
|
actualize: FullKVCache<Key, List<Value>>.(T) -> Unit
|
||||||
|
): T {
|
||||||
|
kvCache.action().onPresented {
|
||||||
|
return it
|
||||||
|
}.onAbsent {
|
||||||
|
return parentRepo.actionElse().also {
|
||||||
|
kvCache.actualize(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error("The result should be returned above")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected suspend fun actualizeKey(k: Key) {
|
||||||
|
kvCache.set(k, parentRepo.getAll(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected suspend fun actualizeAll() {
|
||||||
|
doAllWithCurrentPaging { kvCache.keys(it).also { kvCache.unset(it.results) } }
|
||||||
|
kvCache.set(parentRepo.getAll())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||||
|
return doOrTakeAndActualize(
|
||||||
|
{
|
||||||
|
get(k) ?.paginate(
|
||||||
|
pagination.let { if (reversed) it.reverse(count(k)) else it }
|
||||||
|
) ?.let {
|
||||||
|
if (reversed) it.copy(results = it.results.reversed()) else it
|
||||||
|
}.optionalOrAbsentIfNull
|
||||||
|
},
|
||||||
|
{ get(k, pagination, reversed) },
|
||||||
|
{ actualizeKey(k) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||||
|
return doOrTakeAndActualize(
|
||||||
|
{
|
||||||
|
kvCache.keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull
|
||||||
|
},
|
||||||
|
{ parentRepo.keys(pagination, reversed) },
|
||||||
|
{ actualizeAll() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = doOrTakeAndActualize(
|
||||||
|
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
|
||||||
|
{ count() },
|
||||||
|
{ if (it != 0L) actualizeAll() }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun count(k: Key): Long = doOrTakeAndActualize(
|
||||||
|
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
|
||||||
|
{ count() },
|
||||||
|
{ if (it != 0L) actualizeKey(k) }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun contains(k: Key, v: Value): Boolean = doOrTakeAndActualize(
|
||||||
|
{ get(k) ?.contains(v).takeIf { it == true }.optionalOrAbsentIfNull },
|
||||||
|
{ contains(k, v) },
|
||||||
|
{ if (it) actualizeKey(k) }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun contains(k: Key): Boolean = doOrTakeAndActualize(
|
||||||
|
{ contains(k).takeIf { it }.optionalOrAbsentIfNull },
|
||||||
|
{ contains(k) },
|
||||||
|
{ if (it) actualizeKey(k) }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: Value,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> = doOrTakeAndActualize(
|
||||||
|
{
|
||||||
|
val keys = getAllWithNextPaging { keys(it) }.filter { get(it) ?.contains(v) == true }.optionallyReverse(reversed)
|
||||||
|
if (keys.isNotEmpty()) {
|
||||||
|
keys.paginate(pagination.optionallyReverse(keys.size, reversed)).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull
|
||||||
|
} else {
|
||||||
|
Optional.absent()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ parentRepo.keys(v, pagination, reversed) },
|
||||||
|
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||||
|
kvCache: FullKVCache<Key, List<Value>>
|
||||||
|
) = FullReadKeyValuesCacheRepo(this, kvCache)
|
||||||
|
|
||||||
|
open class FullWriteKeyValuesCacheRepo<Key,Value>(
|
||||||
|
protected open val parentRepo: WriteKeyValuesRepo<Key, Value>,
|
||||||
|
protected open val kvCache: FullKVCache<Key, List<Value>>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
) : WriteKeyValuesRepo<Key, Value> by parentRepo {
|
||||||
|
protected val onNewJob = parentRepo.onNewValue.onEach {
|
||||||
|
kvCache.set(
|
||||||
|
it.first,
|
||||||
|
kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)
|
||||||
|
)
|
||||||
|
}.launchIn(scope)
|
||||||
|
protected val onRemoveJob = parentRepo.onValueRemoved.onEach {
|
||||||
|
kvCache.set(
|
||||||
|
it.first,
|
||||||
|
kvCache.get(it.first) ?.minus(it.second) ?: return@onEach
|
||||||
|
)
|
||||||
|
}.launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
|
||||||
|
kvCache: FullKVCache<Key, List<Value>>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
|
||||||
|
|
||||||
|
open class FullKeyValuesCacheRepo<Key,Value>(
|
||||||
|
parentRepo: KeyValuesRepo<Key, Value>,
|
||||||
|
kvCache: FullKVCache<Key, List<Value>>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),
|
||||||
|
KeyValuesRepo<Key, Value>,
|
||||||
|
ReadKeyValuesRepo<Key, Value> by FullReadKeyValuesCacheRepo(parentRepo, kvCache) {
|
||||||
|
override suspend fun clearWithValue(v: Value) {
|
||||||
|
doAllWithCurrentPaging {
|
||||||
|
keys(v, it).also {
|
||||||
|
remove(it.results.associateWith { listOf(v) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <Key, Value> KeyValuesRepo<Key, Value>.caching(
|
||||||
|
kvCache: FullKVCache<Key, List<Value>>,
|
||||||
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
) = FullKeyValuesCacheRepo(this, kvCache, scope)
|
@@ -1,11 +1,10 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
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.doForAllWithCurrentPaging
|
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
interface ReadKeyValuesRepo<Key, Value> : Repo {
|
||||||
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||||
@@ -36,9 +35,9 @@ interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
|
typealias ReadOneToManyKeyValueRepo<Key,Value> = ReadKeyValuesRepo<Key, Value>
|
||||||
|
|
||||||
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
interface WriteKeyValuesRepo<Key, Value> : Repo {
|
||||||
val onNewValue: Flow<Pair<Key, Value>>
|
val onNewValue: Flow<Pair<Key, Value>>
|
||||||
val onValueRemoved: Flow<Pair<Key, Value>>
|
val onValueRemoved: Flow<Pair<Key, Value>>
|
||||||
val onDataCleared: Flow<Key>
|
val onDataCleared: Flow<Key>
|
||||||
@@ -55,41 +54,41 @@ interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
|||||||
add(toSet)
|
add(toSet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
|
typealias WriteOneToManyKeyValueRepo<Key,Value> = WriteKeyValuesRepo<Key, Value>
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add(
|
||||||
keysAndValues: List<Pair<Key, List<Value>>>
|
keysAndValues: List<Pair<Key, List<Value>>>
|
||||||
) = add(keysAndValues.toMap())
|
) = add(keysAndValues.toMap())
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add(
|
||||||
vararg keysAndValues: Pair<Key, List<Value>>
|
vararg keysAndValues: Pair<Key, List<Value>>
|
||||||
) = add(keysAndValues.toMap())
|
) = add(keysAndValues.toMap())
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
|
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.add(
|
||||||
k: Key, v: List<Value>
|
k: Key, v: List<Value>
|
||||||
) = add(mapOf(k to v))
|
) = add(mapOf(k to v))
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
|
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.add(
|
||||||
k: Key, vararg v: Value
|
k: Key, vararg v: Value
|
||||||
) = add(k, v.toList())
|
) = add(k, v.toList())
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
|
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.set(
|
||||||
keysAndValues: List<Pair<Key, List<Value>>>
|
keysAndValues: List<Pair<Key, List<Value>>>
|
||||||
) = set(keysAndValues.toMap())
|
) = set(keysAndValues.toMap())
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
|
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.set(
|
||||||
vararg keysAndValues: Pair<Key, List<Value>>
|
vararg keysAndValues: Pair<Key, List<Value>>
|
||||||
) = set(keysAndValues.toMap())
|
) = set(keysAndValues.toMap())
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.set(
|
||||||
k: Key, v: List<Value>
|
k: Key, v: List<Value>
|
||||||
) = set(mapOf(k to v))
|
) = set(mapOf(k to v))
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.set(
|
||||||
k: Key, vararg v: Value
|
k: Key, vararg v: Value
|
||||||
) = set(k, v.toList())
|
) = set(k, v.toList())
|
||||||
|
|
||||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value> {
|
interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyValuesRepo<Key, Value> {
|
||||||
override suspend fun clearWithValue(v: Value) {
|
override suspend fun clearWithValue(v: Value) {
|
||||||
doWithPagination {
|
doWithPagination {
|
||||||
val keysResult = keys(v, it)
|
val keysResult = keys(v, it)
|
||||||
@@ -102,22 +101,29 @@ interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
|
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value>
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
class DelegateBasedKeyValuesRepo<Key, Value>(
|
||||||
|
readDelegate: ReadKeyValuesRepo<Key, Value>,
|
||||||
|
writeDelegate: WriteKeyValuesRepo<Key, Value>
|
||||||
|
) : KeyValuesRepo<Key, Value>,
|
||||||
|
ReadKeyValuesRepo<Key, Value> by readDelegate,
|
||||||
|
WriteKeyValuesRepo<Key, Value> by writeDelegate
|
||||||
|
|
||||||
|
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
|
||||||
keysAndValues: List<Pair<Key, List<Value>>>
|
keysAndValues: List<Pair<Key, List<Value>>>
|
||||||
) = remove(keysAndValues.toMap())
|
) = remove(keysAndValues.toMap())
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
|
||||||
vararg keysAndValues: Pair<Key, List<Value>>
|
vararg keysAndValues: Pair<Key, List<Value>>
|
||||||
) = remove(keysAndValues.toMap())
|
) = remove(keysAndValues.toMap())
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
|
||||||
k: Key,
|
k: Key,
|
||||||
v: List<Value>
|
v: List<Value>
|
||||||
) = remove(mapOf(k to v))
|
) = remove(mapOf(k to v))
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
|
||||||
k: Key,
|
k: Key,
|
||||||
vararg v: Value
|
vararg v: Value
|
||||||
) = remove(k, v.toList())
|
) = remove(k, v.toList())
|
@@ -1,22 +1,51 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||||
|
val keyMapper: SimpleSuspendableMapper<FromKey, ToKey>
|
||||||
|
get() = simpleSuspendableMapper(
|
||||||
|
{ it.toInnerKey() },
|
||||||
|
{ it.toOutKey() }
|
||||||
|
)
|
||||||
|
val valueMapper: SimpleSuspendableMapper<FromValue, ToValue>
|
||||||
|
get() = simpleSuspendableMapper(
|
||||||
|
{ it.toInnerValue() },
|
||||||
|
{ it.toOutValue() }
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun FromKey.toOutKey() = this as ToKey
|
suspend fun FromKey.toOutKey() = this as ToKey
|
||||||
suspend fun FromValue.toOutValue() = this as ToValue
|
suspend fun FromValue.toOutValue() = this as ToValue
|
||||||
|
|
||||||
suspend fun ToKey.toInnerKey() = this as FromKey
|
suspend fun ToKey.toInnerKey() = this as FromKey
|
||||||
suspend fun ToValue.toInnerValue() = this as FromValue
|
suspend fun ToValue.toInnerValue() = this as FromValue
|
||||||
|
|
||||||
|
companion object
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
|
class SimpleMapperRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
private val keyFromToTo: suspend FromKey.() -> ToKey,
|
||||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
private val valueFromToTo: suspend FromValue.() -> ToValue,
|
||||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
private val keyToToFrom: suspend ToKey.() -> FromKey,
|
||||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
private val valueToToFrom: suspend ToValue.() -> FromValue
|
||||||
) = object : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
) : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||||
override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo()
|
override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo()
|
||||||
override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo()
|
override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo()
|
||||||
override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom()
|
override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom()
|
||||||
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
|
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
operator fun <FromKey, FromValue, ToKey, ToValue> MapperRepo.Companion.invoke(
|
||||||
|
keyFromToTo: suspend FromKey.() -> ToKey,
|
||||||
|
valueFromToTo: suspend FromValue.() -> ToValue,
|
||||||
|
keyToToFrom: suspend ToKey.() -> FromKey,
|
||||||
|
valueToToFrom: suspend ToValue.() -> FromValue
|
||||||
|
) = SimpleMapperRepo(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
|
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
|
||||||
|
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
) = MapperRepo(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
|
@@ -4,13 +4,13 @@ import dev.inmo.micro_utils.pagination.Pagination
|
|||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
|
interface ReadCRUDRepo<ObjectType, IdType> : Repo {
|
||||||
suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType>
|
suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType>
|
||||||
suspend fun getById(id: IdType): ObjectType?
|
suspend fun getById(id: IdType): ObjectType?
|
||||||
suspend fun contains(id: IdType): Boolean
|
suspend fun contains(id: IdType): Boolean
|
||||||
suspend fun count(): Long
|
suspend fun count(): Long
|
||||||
}
|
}
|
||||||
typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<ObjectType, IdType>
|
typealias ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType>
|
||||||
|
|
||||||
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
|
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
|
||||||
val <IdType> UpdatedValuePair<IdType, *>.id
|
val <IdType> UpdatedValuePair<IdType, *>.id
|
||||||
@@ -18,7 +18,7 @@ val <IdType> UpdatedValuePair<IdType, *>.id
|
|||||||
val <ValueType> UpdatedValuePair<*, ValueType>.value
|
val <ValueType> UpdatedValuePair<*, ValueType>.value
|
||||||
get() = second
|
get() = second
|
||||||
|
|
||||||
interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
||||||
val newObjectsFlow: Flow<ObjectType>
|
val newObjectsFlow: Flow<ObjectType>
|
||||||
val updatedObjectsFlow: Flow<ObjectType>
|
val updatedObjectsFlow: Flow<ObjectType>
|
||||||
val deletedObjectsIdsFlow: Flow<IdType>
|
val deletedObjectsIdsFlow: Flow<IdType>
|
||||||
@@ -28,18 +28,25 @@ interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
|||||||
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
|
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
|
||||||
suspend fun deleteById(ids: List<IdType>)
|
suspend fun deleteById(ids: List<IdType>)
|
||||||
}
|
}
|
||||||
typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
typealias WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> = WriteCRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
|
|
||||||
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
|
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.create(
|
||||||
vararg values: InputValueType
|
vararg values: InputValueType
|
||||||
): List<ObjectType> = create(values.toList())
|
): List<ObjectType> = create(values.toList())
|
||||||
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.update(
|
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.update(
|
||||||
vararg values: UpdatedValuePair<IdType, InputValueType>
|
vararg values: UpdatedValuePair<IdType, InputValueType>
|
||||||
): List<ObjectType> = update(values.toList())
|
): List<ObjectType> = update(values.toList())
|
||||||
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.deleteById(
|
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.deleteById(
|
||||||
vararg ids: IdType
|
vararg ids: IdType
|
||||||
) = deleteById(ids.toList())
|
) = deleteById(ids.toList())
|
||||||
|
|
||||||
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
|
interface CRUDRepo<ObjectType, IdType, InputValueType> : ReadCRUDRepo<ObjectType, IdType>,
|
||||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
WriteCRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>
|
typealias StandardCRUDRepo<ObjectType, IdType, InputValueType> = CRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
|
|
||||||
|
class DelegateBasedCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
|
readDelegate: ReadCRUDRepo<ObjectType, IdType>,
|
||||||
|
writeDelegate: WriteCRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
|
) : CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
|
ReadCRUDRepo<ObjectType, IdType> by readDelegate,
|
||||||
|
WriteCRUDRepo<ObjectType, IdType, InputValueType> by writeDelegate
|
||||||
|
@@ -4,7 +4,7 @@ import dev.inmo.micro_utils.pagination.*
|
|||||||
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
interface ReadKeyValueRepo<Key, Value> : Repo {
|
||||||
suspend fun get(k: Key): Value?
|
suspend fun get(k: Key): Value?
|
||||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||||
@@ -12,9 +12,9 @@ interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
|||||||
suspend fun contains(key: Key): Boolean
|
suspend fun contains(key: Key): Boolean
|
||||||
suspend fun count(): Long
|
suspend fun count(): Long
|
||||||
}
|
}
|
||||||
typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
|
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value>
|
||||||
|
|
||||||
interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
interface WriteKeyValueRepo<Key, Value> : Repo {
|
||||||
val onNewValue: Flow<Pair<Key, Value>>
|
val onNewValue: Flow<Pair<Key, Value>>
|
||||||
val onValueRemoved: Flow<Key>
|
val onValueRemoved: Flow<Key>
|
||||||
|
|
||||||
@@ -22,25 +22,25 @@ interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
|||||||
suspend fun unset(toUnset: List<Key>)
|
suspend fun unset(toUnset: List<Key>)
|
||||||
suspend fun unsetWithValues(toUnset: List<Value>)
|
suspend fun unsetWithValues(toUnset: List<Value>)
|
||||||
}
|
}
|
||||||
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
|
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value>
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
|
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> WriteStandardKeyValueRepo<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)
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
|
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset(
|
||||||
vararg k: Key
|
vararg k: Key
|
||||||
) = unset(k.toList())
|
) = unset(k.toList())
|
||||||
|
|
||||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unsetWithValues(
|
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
|
||||||
vararg v: Value
|
vararg v: Value
|
||||||
) = unsetWithValues(v.toList())
|
) = unsetWithValues(v.toList())
|
||||||
|
|
||||||
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value> {
|
interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> {
|
||||||
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
|
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
|
||||||
doAllWithCurrentPaging {
|
doAllWithCurrentPaging {
|
||||||
keys(v, it).also {
|
keys(v, it).also {
|
||||||
@@ -48,5 +48,16 @@ interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun clear() {
|
||||||
|
doAllWithCurrentPaging { keys(it).also { unset(it.results) } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>
|
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>
|
||||||
|
|
||||||
|
class DelegateBasedKeyValueRepo<Key, Value>(
|
||||||
|
readDelegate: ReadKeyValueRepo<Key, Value>,
|
||||||
|
writeDelegate: WriteKeyValueRepo<Key, Value>
|
||||||
|
) : KeyValueRepo<Key, Value>,
|
||||||
|
ReadKeyValueRepo<Key, Value> by readDelegate,
|
||||||
|
WriteKeyValueRepo<Key, Value> by writeDelegate
|
||||||
|
@@ -0,0 +1,130 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.mappers
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>(
|
||||||
|
private val to: ReadCRUDRepo<ToRegistered, ToId>,
|
||||||
|
mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>
|
||||||
|
) : ReadCRUDRepo<FromRegistered, FromId>, MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper {
|
||||||
|
override suspend fun getByPagination(
|
||||||
|
pagination: Pagination
|
||||||
|
): PaginationResult<FromRegistered> = to.getByPagination(
|
||||||
|
pagination
|
||||||
|
).let {
|
||||||
|
it.changeResultsUnchecked(
|
||||||
|
it.results.map { it.toInnerValue() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = to.count()
|
||||||
|
|
||||||
|
override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey())
|
||||||
|
|
||||||
|
override suspend fun getById(id: FromId): FromRegistered? = to.getById(
|
||||||
|
id.toOutKey()
|
||||||
|
) ?.toInnerValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <FromKey, FromValue, ToKey, ToValue> ReadCRUDRepo<ToValue, ToKey>.withMapper(
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
): ReadCRUDRepo<FromValue, FromKey> = MapperReadCRUDRepo(this, mapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadCRUDRepo<ToValue, ToKey>.withMapper(
|
||||||
|
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
): ReadCRUDRepo<FromValue, FromKey> = withMapper(
|
||||||
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
|
)
|
||||||
|
|
||||||
|
open class MapperWriteCRUDRepo<FromId, FromRegistered, FromInput, ToId, ToRegistered, ToInput>(
|
||||||
|
private val to: WriteCRUDRepo<ToRegistered, ToId, ToInput>,
|
||||||
|
mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>,
|
||||||
|
inputMapper: SimpleSuspendableMapper<FromInput, ToInput>
|
||||||
|
) : WriteCRUDRepo<FromRegistered, FromId, FromInput>,
|
||||||
|
MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper,
|
||||||
|
SimpleSuspendableMapper<FromInput, ToInput> by inputMapper {
|
||||||
|
override val newObjectsFlow: Flow<FromRegistered> = to.newObjectsFlow.map { it.toInnerValue() }
|
||||||
|
override val updatedObjectsFlow: Flow<FromRegistered> = to.updatedObjectsFlow.map { it.toInnerValue() }
|
||||||
|
override val deletedObjectsIdsFlow: Flow<FromId> = to.deletedObjectsIdsFlow.map { it.toInnerKey() }
|
||||||
|
|
||||||
|
override suspend fun deleteById(ids: List<FromId>) = to.deleteById(ids.map { it.toOutKey() })
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
values: List<UpdatedValuePair<FromId, FromInput>>
|
||||||
|
): List<FromRegistered> = to.update(
|
||||||
|
values.map {
|
||||||
|
it.first.toOutKey() to convert(it.second)
|
||||||
|
}
|
||||||
|
).map { it.toInnerValue() }
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
id: FromId,
|
||||||
|
value: FromInput
|
||||||
|
): FromRegistered? = to.update(id.toOutKey(), convert(value)) ?.toInnerValue()
|
||||||
|
|
||||||
|
override suspend fun create(values: List<FromInput>): List<FromRegistered> = to.create(
|
||||||
|
values.map {
|
||||||
|
convert(it)
|
||||||
|
}
|
||||||
|
).map {
|
||||||
|
it.toInnerValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <FromKey, FromValue, FromInput, ToKey, ToValue, ToInput> WriteCRUDRepo<ToValue, ToKey, ToInput>.withMapper(
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>,
|
||||||
|
simpleSuspendableMapper: SimpleSuspendableMapper<FromInput, ToInput>
|
||||||
|
): WriteCRUDRepo<FromValue, FromKey, FromInput> = MapperWriteCRUDRepo(this, mapper, simpleSuspendableMapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified FromInput, reified ToKey, reified ToValue, reified ToInput> WriteCRUDRepo<ToValue, ToKey, ToInput>.withMapper(
|
||||||
|
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
noinline inputFromToTo: suspend FromInput.() -> ToInput = { this as ToInput },
|
||||||
|
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
noinline inputToToFrom: suspend ToInput.() -> FromInput = { this as FromInput },
|
||||||
|
): WriteCRUDRepo<FromValue, FromKey, FromInput> = withMapper(
|
||||||
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom),
|
||||||
|
simpleSuspendableMapper({ inputToToFrom(it) }, { inputFromToTo(it) })
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||||
|
open class MapperCRUDRepo<FromId, FromRegistered, FromInput, ToId, ToRegistered, ToInput>(
|
||||||
|
private val to: CRUDRepo<ToRegistered, ToId, ToInput>,
|
||||||
|
private val mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>,
|
||||||
|
private val inputMapper: SimpleSuspendableMapper<FromInput, ToInput>
|
||||||
|
) : CRUDRepo<FromRegistered, FromId, FromInput>,
|
||||||
|
MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper,
|
||||||
|
ReadCRUDRepo<FromRegistered, FromId> by MapperReadCRUDRepo(to, mapper),
|
||||||
|
WriteCRUDRepo<FromRegistered, FromId, FromInput> by MapperWriteCRUDRepo(to, mapper, inputMapper)
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <FromKey, FromValue, FromInput, ToKey, ToValue, ToInput> CRUDRepo<ToValue, ToKey, ToInput>.withMapper(
|
||||||
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>,
|
||||||
|
simpleSuspendableMapper: SimpleSuspendableMapper<FromInput, ToInput>
|
||||||
|
): CRUDRepo<FromValue, FromKey, FromInput> = MapperCRUDRepo(this, mapper, simpleSuspendableMapper)
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun <reified FromKey, reified FromValue, reified FromInput, reified ToKey, reified ToValue, reified ToInput> CRUDRepo<ToValue, ToKey, ToInput>.withMapper(
|
||||||
|
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
|
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
|
noinline inputFromToTo: suspend FromInput.() -> ToInput = { this as ToInput },
|
||||||
|
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
|
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
|
noinline inputToToFrom: suspend ToInput.() -> FromInput = { this as FromInput },
|
||||||
|
): CRUDRepo<FromValue, FromKey, FromInput> = withMapper(
|
||||||
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom),
|
||||||
|
simpleSuspendableMapper({ inputToToFrom(it) }, { inputFromToTo(it) })
|
||||||
|
)
|
@@ -6,10 +6,12 @@ import dev.inmo.micro_utils.repos.*
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
@Deprecated("Renamed", ReplaceWith("MapperReadKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperReadKeyValueRepo"))
|
||||||
private val to: ReadStandardKeyValueRepo<ToKey, ToValue>,
|
typealias MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
|
private val to: ReadKeyValueRepo<ToKey, ToValue>,
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
) : ReadStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
) : ReadKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||||
override suspend fun get(k: FromKey): FromValue? = to.get(
|
override suspend fun get(k: FromKey): FromValue? = to.get(
|
||||||
k.toOutKey()
|
k.toOutKey()
|
||||||
) ?.toInnerValue()
|
) ?.toInnerValue()
|
||||||
@@ -69,24 +71,26 @@ open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
|
): ReadKeyValueRepo<FromKey, FromValue> = MapperReadKeyValueRepo(this, mapper)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
): ReadKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
)
|
)
|
||||||
|
|
||||||
open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
@Deprecated("Renamed", ReplaceWith("MapperWriteKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperWriteKeyValueRepo"))
|
||||||
private val to: WriteStandardKeyValueRepo<ToKey, ToValue>,
|
typealias MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
|
private val to: WriteKeyValueRepo<ToKey, ToValue>,
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
) : WriteStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
) : WriteKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||||
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
|
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
|
||||||
k.toInnerKey() to v.toInnerValue()
|
k.toInnerKey() to v.toInnerValue()
|
||||||
}
|
}
|
||||||
@@ -112,40 +116,42 @@ open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <FromKey, FromValue, ToKey, ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
|
): WriteKeyValueRepo<FromKey, FromValue> = MapperWriteKeyValueRepo(this, mapper)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
): WriteKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Deprecated("Renamed", ReplaceWith("MapperKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperKeyValueRepo"))
|
||||||
|
typealias MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||||
open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
open class MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
private val to: StandardKeyValueRepo<ToKey, ToValue>,
|
private val to: KeyValueRepo<ToKey, ToValue>,
|
||||||
private val mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
private val mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
) : StandardKeyValueRepo<FromKey, FromValue>,
|
) : KeyValueRepo<FromKey, FromValue>,
|
||||||
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||||
ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
|
ReadKeyValueRepo<FromKey, FromValue> by MapperReadKeyValueRepo(to, mapper),
|
||||||
WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
|
WriteKeyValueRepo<FromKey, FromValue> by MapperWriteKeyValueRepo(to, mapper)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <FromKey, FromValue, ToKey, ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
|
): KeyValueRepo<FromKey, FromValue> = MapperKeyValueRepo(this, mapper)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValueRepo<ToKey, ToValue>.withMapper(
|
||||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
): KeyValueRepo<FromKey, FromValue> = withMapper(
|
||||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
)
|
)
|
||||||
|
@@ -6,10 +6,12 @@ import dev.inmo.micro_utils.repos.*
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
@Deprecated("Renamed", ReplaceWith("MapperReadKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperReadKeyValuesRepo"))
|
||||||
private val to: ReadOneToManyKeyValueRepo<ToKey, ToValue>,
|
typealias MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
|
private val to: ReadKeyValuesRepo<ToKey, ToValue>,
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
) : ReadOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
) : ReadKeyValuesRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||||
override suspend fun get(
|
override suspend fun get(
|
||||||
k: FromKey,
|
k: FromKey,
|
||||||
pagination: Pagination,
|
pagination: Pagination,
|
||||||
@@ -67,24 +69,26 @@ open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
|
): ReadKeyValuesRepo<FromKey, FromValue> = MapperReadKeyValuesRepo(this, mapper)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
): ReadKeyValuesRepo<FromKey, FromValue> = withMapper(
|
||||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
)
|
)
|
||||||
|
|
||||||
open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
@Deprecated("Renamed", ReplaceWith("MapperWriteKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperWriteKeyValuesRepo"))
|
||||||
private val to: WriteOneToManyKeyValueRepo<ToKey, ToValue>,
|
typealias MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
|
open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
|
private val to: WriteKeyValuesRepo<ToKey, ToValue>,
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
) : WriteOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
) : WriteKeyValuesRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||||
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
|
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
|
||||||
k.toInnerKey() to v.toInnerValue()
|
k.toInnerKey() to v.toInnerValue()
|
||||||
}
|
}
|
||||||
@@ -118,40 +122,42 @@ open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <FromKey, FromValue, ToKey, ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
|
): WriteKeyValuesRepo<FromKey, FromValue> = MapperWriteKeyValuesRepo(this, mapper)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
): WriteKeyValuesRepo<FromKey, FromValue> = withMapper(
|
||||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Deprecated("Renamed", ReplaceWith("MapperKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperKeyValuesRepo"))
|
||||||
|
typealias MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||||
open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
open class MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||||
private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
|
private val to: KeyValuesRepo<ToKey, ToValue>,
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
) : OneToManyKeyValueRepo<FromKey, FromValue>,
|
) : KeyValuesRepo<FromKey, FromValue>,
|
||||||
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||||
ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
|
ReadKeyValuesRepo<FromKey, FromValue> by MapperReadKeyValuesRepo(to, mapper),
|
||||||
WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
|
WriteKeyValuesRepo<FromKey, FromValue> by MapperWriteKeyValuesRepo(to, mapper)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <FromKey, FromValue, ToKey, ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||||
): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
|
): KeyValuesRepo<FromKey, FromValue> = MapperKeyValuesRepo(this, mapper)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||||
): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
): KeyValuesRepo<FromKey, FromValue> = withMapper(
|
||||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||||
)
|
)
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
package dev.inmo.micro_utils.repos.pagination
|
package dev.inmo.micro_utils.repos.pagination
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
|
||||||
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll(
|
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
||||||
): List<T> = getAllWithNextPaging {
|
): List<T> = getAllWithNextPaging {
|
||||||
|
@@ -2,9 +2,9 @@ package dev.inmo.micro_utils.repos.pagination
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.getAll(
|
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||||
): List<Pair<Key, Value>> = getAllWithNextPaging {
|
): List<Pair<Key, Value>> = getAllWithNextPaging {
|
||||||
|
@@ -2,9 +2,9 @@ package dev.inmo.micro_utils.repos.pagination
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.getAll(
|
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll(
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||||
): List<Pair<Key, List<Value>>> = getAllWithNextPaging {
|
): List<Pair<Key, List<Value>>> = getAllWithNextPaging {
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
package dev.inmo.micro_utils.repos.versions
|
package dev.inmo.micro_utils.repos.versions
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.set
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
|
||||||
class KeyValueBasedVersionsRepoProxy<T>(
|
class KeyValueBasedVersionsRepoProxy<T>(
|
||||||
private val keyValueStore: StandardKeyValueRepo<String, Int>,
|
private val keyValueStore: KeyValueRepo<String, Int>,
|
||||||
override val database: T
|
override val database: T
|
||||||
) : StandardVersionsRepoProxy<T> {
|
) : StandardVersionsRepoProxy<T> {
|
||||||
override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName)
|
override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName)
|
||||||
|
@@ -13,9 +13,12 @@ import java.nio.file.StandardWatchEventKinds.*
|
|||||||
private inline val String.isAbsolute
|
private inline val String.isAbsolute
|
||||||
get() = startsWith(File.separator)
|
get() = startsWith(File.separator)
|
||||||
|
|
||||||
class FileReadStandardKeyValueRepo(
|
@Deprecated("Renamed", ReplaceWith("FileReadKeyValueRepo", "dev.inmo.micro_utils.repos.FileReadKeyValueRepo"))
|
||||||
|
typealias FileReadStandardKeyValueRepo = FileReadKeyValueRepo
|
||||||
|
|
||||||
|
class FileReadKeyValueRepo(
|
||||||
private val folder: File
|
private val folder: File
|
||||||
) : ReadStandardKeyValueRepo<String, File> {
|
) : ReadKeyValueRepo<String, File> {
|
||||||
init {
|
init {
|
||||||
folder.mkdirs()
|
folder.mkdirs()
|
||||||
}
|
}
|
||||||
@@ -79,21 +82,26 @@ class FileReadStandardKeyValueRepo(
|
|||||||
override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L
|
override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Renamed", ReplaceWith("FileWriteKeyValueRepo", "dev.inmo.micro_utils.repos.FileWriteKeyValueRepo"))
|
||||||
|
typealias FileWriteStandardKeyValueRepo = FileWriteKeyValueRepo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Files watching will not correctly works on Android with version of API lower than API 26
|
* Files watching will not correctly works on Android with version of API lower than API 26
|
||||||
*/
|
*/
|
||||||
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
|
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
|
||||||
class FileWriteStandardKeyValueRepo(
|
class FileWriteKeyValueRepo(
|
||||||
private val folder: File,
|
private val folder: File,
|
||||||
filesChangedProcessingScope: CoroutineScope? = null
|
filesChangedProcessingScope: CoroutineScope? = null
|
||||||
) : WriteStandardKeyValueRepo<String, File> {
|
) : WriteKeyValueRepo<String, File> {
|
||||||
private val _onNewValue = MutableSharedFlow<Pair<String, File>>()
|
private val _onNewValue = MutableSharedFlow<Pair<String, File>>()
|
||||||
override val onNewValue: Flow<Pair<String, File>> = _onNewValue.asSharedFlow()
|
override val onNewValue: Flow<Pair<String, File>> = _onNewValue.asSharedFlow()
|
||||||
private val _onValueRemoved = MutableSharedFlow<String>()
|
private val _onValueRemoved = MutableSharedFlow<String>()
|
||||||
override val onValueRemoved: Flow<String> = _onValueRemoved.asSharedFlow()
|
override val onValueRemoved: Flow<String> = _onValueRemoved.asSharedFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
folder.mkdirs()
|
if (!folder.mkdirs() && !folder.exists()) {
|
||||||
|
error("Unable to create folder ${folder.absolutePath}")
|
||||||
|
}
|
||||||
filesChangedProcessingScope ?.let {
|
filesChangedProcessingScope ?.let {
|
||||||
it.launch {
|
it.launch {
|
||||||
try {
|
try {
|
||||||
@@ -138,15 +146,17 @@ class FileWriteStandardKeyValueRepo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun set(toSet: Map<String, File>) {
|
override suspend fun set(toSet: Map<String, File>) {
|
||||||
supervisorScope {
|
val scope = CoroutineScope(currentCoroutineContext())
|
||||||
toSet.map { (filename, fileSource) ->
|
toSet.map { (filename, fileSource) ->
|
||||||
launch {
|
scope.launch {
|
||||||
val file = File(folder, filename)
|
val file = File(folder, filename)
|
||||||
|
|
||||||
file.delete()
|
file.delete()
|
||||||
fileSource.copyTo(file, overwrite = true)
|
fileSource.copyTo(file, overwrite = true)
|
||||||
_onNewValue.emit(filename to file)
|
if (!file.exists()) {
|
||||||
|
error("Can't create file $file with new content")
|
||||||
}
|
}
|
||||||
|
_onNewValue.emit(filename to file)
|
||||||
}
|
}
|
||||||
}.joinAll()
|
}.joinAll()
|
||||||
}
|
}
|
||||||
@@ -174,12 +184,15 @@ class FileWriteStandardKeyValueRepo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Renamed", ReplaceWith("FileKeyValueRepo", "dev.inmo.micro_utils.repos.FileKeyValueRepo"))
|
||||||
|
typealias FileStandardKeyValueRepo = FileKeyValueRepo
|
||||||
|
|
||||||
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
|
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
|
||||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||||
class FileStandardKeyValueRepo(
|
class FileKeyValueRepo(
|
||||||
folder: File,
|
folder: File,
|
||||||
filesChangedProcessingScope: CoroutineScope? = null
|
filesChangedProcessingScope: CoroutineScope? = null
|
||||||
) : StandardKeyValueRepo<String, File>,
|
) : KeyValueRepo<String, File>,
|
||||||
WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
|
WriteKeyValueRepo<String, File> by FileWriteKeyValueRepo(folder, filesChangedProcessingScope),
|
||||||
ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder) {
|
ReadKeyValueRepo<String, File> by FileReadKeyValueRepo(folder) {
|
||||||
}
|
}
|
@@ -12,7 +12,7 @@ val <T> T.asId: String
|
|||||||
|
|
||||||
abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
|
abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
|
||||||
protected val helper: StandardSQLHelper
|
protected val helper: StandardSQLHelper
|
||||||
) : ReadStandardCRUDRepo<ObjectType, IdType> {
|
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||||
protected abstract val tableName: String
|
protected abstract val tableName: String
|
||||||
protected abstract val idColumnName: String
|
protected abstract val idColumnName: String
|
||||||
protected abstract suspend fun Cursor.toObject(): ObjectType
|
protected abstract suspend fun Cursor.toObject(): ObjectType
|
||||||
|
@@ -9,9 +9,9 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
|||||||
helper: StandardSQLHelper,
|
helper: StandardSQLHelper,
|
||||||
replyInFlows: Int = 0,
|
replyInFlows: Int = 0,
|
||||||
extraBufferCapacityInFlows: Int = 64
|
extraBufferCapacityInFlows: Int = 64
|
||||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
|
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
|
||||||
StandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
CRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
|
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
|
||||||
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
|
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
|
||||||
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyInFlows, extraBufferCapacityInFlows)
|
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyInFlows, extraBufferCapacityInFlows)
|
||||||
|
@@ -7,7 +7,7 @@ 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.paginate
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||||
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
private val cache = HashMap<String, KeyValueStore<*>>()
|
private val cache = HashMap<String, KeyValueStore<*>>()
|
||||||
@@ -15,7 +15,7 @@ private val cache = HashMap<String, KeyValueStore<*>>()
|
|||||||
fun <T : Any> Context.keyValueStore(
|
fun <T : Any> Context.keyValueStore(
|
||||||
name: String = "default",
|
name: String = "default",
|
||||||
cacheValues: Boolean = false
|
cacheValues: Boolean = false
|
||||||
): StandardKeyValueRepo<String, T> {
|
): KeyValueRepo<String, T> {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return cache.getOrPut(name) {
|
return cache.getOrPut(name) {
|
||||||
KeyValueStore<T>(this, name, cacheValues)
|
KeyValueStore<T>(this, name, cacheValues)
|
||||||
@@ -26,7 +26,7 @@ class KeyValueStore<T : Any> internal constructor (
|
|||||||
c: Context,
|
c: Context,
|
||||||
preferencesName: String,
|
preferencesName: String,
|
||||||
useCache: Boolean = false
|
useCache: Boolean = false
|
||||||
) : SharedPreferences.OnSharedPreferenceChangeListener, StandardKeyValueRepo<String, T> {
|
) : SharedPreferences.OnSharedPreferenceChangeListener, KeyValueRepo<String, T> {
|
||||||
private val sharedPreferences = c.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
|
private val sharedPreferences = c.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
private val cachedData = if (useCache) {
|
private val cachedData = if (useCache) {
|
||||||
@@ -160,3 +160,9 @@ class KeyValueStore<T : Any> internal constructor (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <T : Any> SharedPreferencesKeyValueRepo(
|
||||||
|
context: Context,
|
||||||
|
name: String = "default",
|
||||||
|
cacheValues: Boolean = false
|
||||||
|
) = context.keyValueStore<T>(name, cacheValues)
|
||||||
|
@@ -9,7 +9,6 @@ import dev.inmo.micro_utils.repos.*
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
private val keyFromString: String.() -> Key,
|
private val keyFromString: String.() -> Key,
|
||||||
private val valueFromString: String.() -> Value,
|
private val valueFromString: String.() -> Value,
|
||||||
private val helper: SQLiteOpenHelper
|
private val helper: SQLiteOpenHelper
|
||||||
) : OneToManyKeyValueRepo<Key, Value> {
|
) : KeyValuesRepo<Key, Value> {
|
||||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||||
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
|
@@ -4,13 +4,11 @@ package dev.inmo.micro_utils.repos.versions
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
import androidx.core.content.contentValuesOf
|
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.keyvalue.keyValueStore
|
import dev.inmo.micro_utils.repos.keyvalue.keyValueStore
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will create [VersionsRepo] based on [T], but versions will be stored in [StandardKeyValueRepo]
|
* Will create [VersionsRepo] based on [T], but versions will be stored in [KeyValueRepo]
|
||||||
*
|
*
|
||||||
* @receiver Will be used to create [KeyValueBasedVersionsRepoProxy] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
* @receiver Will be used to create [KeyValueBasedVersionsRepoProxy] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
||||||
*
|
*
|
||||||
@@ -26,9 +24,9 @@ inline fun <T> Context.versionsKeyValueRepo(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
/**
|
/**
|
||||||
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
|
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [KeyValueRepo]
|
||||||
*
|
*
|
||||||
* @receiver Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
* @receiver Will be used to create [KeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
||||||
*
|
*
|
||||||
* @see [keyValueStore]
|
* @see [keyValueStore]
|
||||||
*/
|
*/
|
||||||
@@ -37,9 +35,9 @@ inline fun Context.versionsKeyValueRepoForSQL(
|
|||||||
) = versionsKeyValueRepo(database)
|
) = versionsKeyValueRepo(database)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
|
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [KeyValueRepo]
|
||||||
*
|
*
|
||||||
* @param context Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
* @param context Will be used to create [KeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
||||||
*
|
*
|
||||||
* @see [keyValueStore]
|
* @see [keyValueStore]
|
||||||
*/
|
*/
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed
|
package dev.inmo.micro_utils.repos.exposed
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.StandardCRUDRepo
|
import dev.inmo.micro_utils.repos.CRUDRepo
|
||||||
|
|
||||||
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
flowsChannelsSize: Int = 0,
|
flowsChannelsSize: Int = 0,
|
||||||
@@ -11,4 +11,4 @@ abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
tableName
|
tableName
|
||||||
),
|
),
|
||||||
ExposedCRUDRepo<ObjectType, IdType>,
|
ExposedCRUDRepo<ObjectType, IdType>,
|
||||||
StandardCRUDRepo<ObjectType, IdType, InputValueType>
|
CRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed
|
package dev.inmo.micro_utils.repos.exposed
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
|
abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
|
||||||
tableName: String
|
tableName: String
|
||||||
) :
|
) :
|
||||||
ReadStandardCRUDRepo<ObjectType, IdType>,
|
ReadCRUDRepo<ObjectType, IdType>,
|
||||||
ExposedCRUDRepo<ObjectType, IdType>,
|
ExposedCRUDRepo<ObjectType, IdType>,
|
||||||
Table(tableName)
|
Table(tableName)
|
||||||
{
|
{
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed
|
package dev.inmo.micro_utils.repos.exposed
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||||
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||||
@@ -15,7 +15,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
) :
|
) :
|
||||||
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
|
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
|
||||||
ExposedCRUDRepo<ObjectType, IdType>,
|
ExposedCRUDRepo<ObjectType, IdType>,
|
||||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
WriteCRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
{
|
{
|
||||||
protected val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
protected val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
||||||
protected val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
protected val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed.keyvalue
|
package dev.inmo.micro_utils.repos.exposed.keyvalue
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
||||||
import dev.inmo.micro_utils.repos.exposed.initTable
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
@@ -13,7 +11,7 @@ open class ExposedKeyValueRepo<Key, Value>(
|
|||||||
keyColumnAllocator: ColumnAllocator<Key>,
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
valueColumnAllocator: ColumnAllocator<Value>,
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
tableName: String? = null
|
tableName: String? = null
|
||||||
) : StandardKeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>(
|
) : KeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>(
|
||||||
database,
|
database,
|
||||||
keyColumnAllocator,
|
keyColumnAllocator,
|
||||||
valueColumnAllocator,
|
valueColumnAllocator,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed.keyvalue
|
package dev.inmo.micro_utils.repos.exposed.keyvalue
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.exposed.*
|
import dev.inmo.micro_utils.repos.exposed.*
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
@@ -11,7 +11,7 @@ open class ExposedReadKeyValueRepo<Key, Value>(
|
|||||||
keyColumnAllocator: ColumnAllocator<Key>,
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
valueColumnAllocator: ColumnAllocator<Value>,
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
tableName: String? = null
|
tableName: String? = null
|
||||||
) : ReadStandardKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||||
val keyColumn: Column<Key> = keyColumnAllocator()
|
val keyColumn: Column<Key> = keyColumnAllocator()
|
||||||
val valueColumn: Column<Value> = valueColumnAllocator()
|
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
|
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.OneToManyKeyValueRepo
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
typealias ExposedKeyValuesRepo<Key, Value> = ExposedOneToManyKeyValueRepo<Key, Value>
|
typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value>
|
||||||
open class ExposedOneToManyKeyValueRepo<Key, Value>(
|
open class ExposedKeyValuesRepo<Key, Value>(
|
||||||
database: Database,
|
database: Database,
|
||||||
keyColumnAllocator: ColumnAllocator<Key>,
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
valueColumnAllocator: ColumnAllocator<Value>,
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
tableName: String? = null
|
tableName: String? = null
|
||||||
) : OneToManyKeyValueRepo<Key, Value>, ExposedReadOneToManyKeyValueRepo<Key, Value>(
|
) : KeyValuesRepo<Key, Value>, ExposedReadKeyValuesRepo<Key, Value>(
|
||||||
database,
|
database,
|
||||||
keyColumnAllocator,
|
keyColumnAllocator,
|
||||||
valueColumnAllocator,
|
valueColumnAllocator,
|
@@ -1,20 +1,19 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
import dev.inmo.micro_utils.repos.exposed.*
|
import dev.inmo.micro_utils.repos.exposed.*
|
||||||
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedReadKeyValueRepo
|
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
typealias ExposedReadKeyValuesRepo<Key, Value> = ExposedReadOneToManyKeyValueRepo<Key, Value>
|
typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value>
|
||||||
|
|
||||||
open class ExposedReadOneToManyKeyValueRepo<Key, Value>(
|
open class ExposedReadKeyValuesRepo<Key, Value>(
|
||||||
override val database: Database,
|
override val database: Database,
|
||||||
keyColumnAllocator: ColumnAllocator<Key>,
|
keyColumnAllocator: ColumnAllocator<Key>,
|
||||||
valueColumnAllocator: ColumnAllocator<Value>,
|
valueColumnAllocator: ColumnAllocator<Value>,
|
||||||
tableName: String? = null
|
tableName: String? = null
|
||||||
) : ReadOneToManyKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||||
val keyColumn: Column<Key> = keyColumnAllocator()
|
val keyColumn: Column<Key> = keyColumnAllocator()
|
||||||
val valueColumn: Column<Value> = valueColumnAllocator()
|
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||||
|
|
@@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.*
|
|||||||
|
|
||||||
class ReadMapCRUDRepo<ObjectType, IdType>(
|
class ReadMapCRUDRepo<ObjectType, IdType>(
|
||||||
private val map: Map<IdType, ObjectType> = emptyMap()
|
private val map: Map<IdType, ObjectType> = emptyMap()
|
||||||
) : ReadStandardCRUDRepo<ObjectType, IdType> {
|
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> {
|
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> {
|
||||||
return map.keys.drop(pagination.firstIndex).take(pagination.size).mapNotNull {
|
return map.keys.drop(pagination.firstIndex).take(pagination.size).mapNotNull {
|
||||||
map[it]
|
map[it]
|
||||||
@@ -24,7 +24,7 @@ class ReadMapCRUDRepo<ObjectType, IdType>(
|
|||||||
|
|
||||||
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
protected val map: MutableMap<IdType, ObjectType> = mutableMapOf()
|
protected val map: MutableMap<IdType, ObjectType> = mutableMapOf()
|
||||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
) : WriteCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
protected val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
protected val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
||||||
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
||||||
protected val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
protected val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
||||||
@@ -68,25 +68,30 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
|
|
||||||
abstract class MapCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class MapCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
map: MutableMap<IdType, ObjectType>
|
map: MutableMap<IdType, ObjectType>
|
||||||
) : StandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
) : CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
ReadStandardCRUDRepo<ObjectType, IdType> by ReadMapCRUDRepo(map),
|
ReadCRUDRepo<ObjectType, IdType> by ReadMapCRUDRepo(map),
|
||||||
WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(map)
|
WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(map)
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
|
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
|
||||||
map: MutableMap<IdType, ObjectType>,
|
map: MutableMap<IdType, ObjectType>,
|
||||||
updateCallback: suspend (newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||||
createCallback: suspend (newValue: InputValueType) -> Pair<IdType, ObjectType>
|
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||||
) = object : MapCRUDRepo<ObjectType, IdType, InputValueType>(map) {
|
) = object : MapCRUDRepo<ObjectType, IdType, InputValueType>(map) {
|
||||||
override suspend fun updateObject(
|
override suspend fun updateObject(
|
||||||
newValue: InputValueType,
|
newValue: InputValueType,
|
||||||
id: IdType,
|
id: IdType,
|
||||||
old: ObjectType
|
old: ObjectType
|
||||||
): ObjectType = updateCallback(newValue, id, old)
|
): ObjectType = map.updateCallback(newValue, id, old)
|
||||||
|
|
||||||
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = createCallback(newValue)
|
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = map.createCallback(newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
|
||||||
|
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||||
|
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||||
|
) = MapCRUDRepo(mutableMapOf(), updateCallback, createCallback)
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputValueType> MutableMap<IdType, ObjectType>.asCrudRepo(
|
fun <ObjectType, IdType, InputValueType> MutableMap<IdType, ObjectType>.asCrudRepo(
|
||||||
updateCallback: suspend (newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||||
createCallback: suspend (newValue: InputValueType) -> Pair<IdType, ObjectType>
|
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||||
) = MapCRUDRepo(this, updateCallback, createCallback)
|
) = MapCRUDRepo(this, updateCallback, createCallback)
|
||||||
|
@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||||||
|
|
||||||
class ReadMapKeyValueRepo<Key, Value>(
|
class ReadMapKeyValueRepo<Key, Value>(
|
||||||
protected val map: Map<Key, Value> = emptyMap()
|
protected val map: Map<Key, Value> = emptyMap()
|
||||||
) : ReadStandardKeyValueRepo<Key, Value> {
|
) : ReadKeyValueRepo<Key, Value> {
|
||||||
override suspend fun get(k: Key): Value? = map[k]
|
override suspend fun get(k: Key): Value? = map[k]
|
||||||
|
|
||||||
override suspend fun values(
|
override suspend fun values(
|
||||||
@@ -58,7 +58,7 @@ class ReadMapKeyValueRepo<Key, Value>(
|
|||||||
|
|
||||||
class WriteMapKeyValueRepo<Key, Value>(
|
class WriteMapKeyValueRepo<Key, Value>(
|
||||||
private val map: MutableMap<Key, Value> = mutableMapOf()
|
private val map: MutableMap<Key, Value> = mutableMapOf()
|
||||||
) : WriteStandardKeyValueRepo<Key, Value> {
|
) : WriteKeyValueRepo<Key, Value> {
|
||||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
override val onNewValue: Flow<Pair<Key, Value>>
|
override val onNewValue: Flow<Pair<Key, Value>>
|
||||||
get() = _onNewValue
|
get() = _onNewValue
|
||||||
@@ -78,19 +78,19 @@ class WriteMapKeyValueRepo<Key, Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
||||||
map.forEach {
|
map.mapNotNull { (k, v) ->
|
||||||
if (it.value in toUnset) {
|
k.takeIf { v in toUnset }
|
||||||
map.remove(it.key)
|
}.forEach {
|
||||||
_onValueRemoved.emit(it.key)
|
map.remove(it)
|
||||||
}
|
_onValueRemoved.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapKeyValueRepo<Key, Value>(
|
class MapKeyValueRepo<Key, Value>(
|
||||||
private val map: MutableMap<Key, Value> = mutableMapOf()
|
private val map: MutableMap<Key, Value> = mutableMapOf()
|
||||||
) : StandardKeyValueRepo<Key, Value>,
|
) : KeyValueRepo<Key, Value>,
|
||||||
ReadStandardKeyValueRepo<Key, Value> by ReadMapKeyValueRepo(map),
|
ReadKeyValueRepo<Key, Value> by ReadMapKeyValueRepo(map),
|
||||||
WriteStandardKeyValueRepo<Key, Value> by WriteMapKeyValueRepo(map)
|
WriteKeyValueRepo<Key, Value> by WriteMapKeyValueRepo(map)
|
||||||
|
|
||||||
fun <K, V> MutableMap<K, V>.asKeyValueRepo(): StandardKeyValueRepo<K, V> = MapKeyValueRepo(this)
|
fun <K, V> MutableMap<K, V>.asKeyValueRepo(): KeyValueRepo<K, V> = MapKeyValueRepo(this)
|
||||||
|
@@ -5,9 +5,11 @@ import dev.inmo.micro_utils.pagination.utils.paginate
|
|||||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
class MapReadOneToManyKeyValueRepo<Key, Value>(
|
@Deprecated("Renamed", ReplaceWith("MapReadKeyValuesRepo", "dev.inmo.micro_utils.repos.MapReadKeyValuesRepo"))
|
||||||
|
typealias MapReadOneToManyKeyValueRepo<Key, Value> = MapReadKeyValuesRepo<Key, Value>
|
||||||
|
class MapReadKeyValuesRepo<Key, Value>(
|
||||||
private val map: Map<Key, List<Value>> = emptyMap()
|
private val map: Map<Key, List<Value>> = emptyMap()
|
||||||
) : ReadOneToManyKeyValueRepo<Key, Value> {
|
) : ReadKeyValuesRepo<Key, Value> {
|
||||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||||
val list = map[k] ?: return emptyPaginationResult()
|
val list = map[k] ?: return emptyPaginationResult()
|
||||||
|
|
||||||
@@ -53,9 +55,11 @@ class MapReadOneToManyKeyValueRepo<Key, Value>(
|
|||||||
override suspend fun count(): Long = map.size.toLong()
|
override suspend fun count(): Long = map.size.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
@Deprecated("Renamed", ReplaceWith("MapWriteKeyValuesRepo", "dev.inmo.micro_utils.repos.MapWriteKeyValuesRepo"))
|
||||||
|
typealias MapWriteOneToManyKeyValueRepo<Key, Value> = MapWriteKeyValuesRepo<Key, Value>
|
||||||
|
class MapWriteKeyValuesRepo<Key, Value>(
|
||||||
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
|
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
|
||||||
) : WriteOneToManyKeyValueRepo<Key, Value> {
|
) : WriteKeyValuesRepo<Key, Value> {
|
||||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||||
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||||
@@ -80,6 +84,10 @@ class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
|||||||
_onValueRemoved.emit(k to v)
|
_onValueRemoved.emit(k to v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (map[k] ?.isEmpty() == true) {
|
||||||
|
map.remove(k)
|
||||||
|
_onDataCleared.emit(k)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,12 +102,17 @@ class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapOneToManyKeyValueRepo<Key, Value>(
|
@Deprecated("Renamed", ReplaceWith("MapKeyValuesRepo", "dev.inmo.micro_utils.repos.MapKeyValuesRepo"))
|
||||||
|
typealias MapOneToManyKeyValueRepo1<Key, Value> = MapKeyValuesRepo<Key, Value>
|
||||||
|
class MapKeyValuesRepo<Key, Value>(
|
||||||
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
|
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
|
||||||
) : OneToManyKeyValueRepo<Key, Value>,
|
) : KeyValuesRepo<Key, Value>,
|
||||||
ReadOneToManyKeyValueRepo<Key, Value> by MapReadOneToManyKeyValueRepo(map),
|
ReadKeyValuesRepo<Key, Value> by MapReadKeyValuesRepo(map),
|
||||||
WriteOneToManyKeyValueRepo<Key, Value> by MapWriteOneToManyKeyValueRepo(map)
|
WriteKeyValuesRepo<Key, Value> by MapWriteKeyValuesRepo(map)
|
||||||
|
|
||||||
fun <K, V> MutableMap<K, List<V>>.asOneToManyKeyValueRepo(): OneToManyKeyValueRepo<K, V> = MapOneToManyKeyValueRepo(
|
fun <K, V> MutableMap<K, List<V>>.asKeyValuesRepo(): KeyValuesRepo<K, V> = MapKeyValuesRepo(
|
||||||
map { (k, v) -> k to v.toMutableList() }.toMap().toMutableMap()
|
map { (k, v) -> k to v.toMutableList() }.toMap().toMutableMap()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Deprecated("Renamed", ReplaceWith("asKeyValuesRepo", "dev.inmo.micro_utils.repos.asKeyValuesRepo"))
|
||||||
|
fun <K, V> MutableMap<K, List<V>>.asOneToManyKeyValueRepo(): KeyValuesRepo<K, V> = asKeyValuesRepo()
|
@@ -0,0 +1,106 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.client.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.util.reflect.TypeInfo
|
||||||
|
import io.ktor.util.reflect.typeInfo
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
||||||
|
readDelegate: ReadCRUDRepo<ObjectType, IdType>,
|
||||||
|
writeDelegate: WriteCRUDRepo<ObjectType, IdType, InputValue>
|
||||||
|
) : CRUDRepo<ObjectType, IdType, InputValue> by DelegateBasedCRUDRepo(
|
||||||
|
readDelegate,
|
||||||
|
writeDelegate
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
noinline idSerializer: suspend (IdType) -> String
|
||||||
|
) = KtorCRUDRepoClient(
|
||||||
|
KtorReadCRUDRepoClient(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
typeInfo<ObjectType>(),
|
||||||
|
typeInfo<PaginationResult<ObjectType>>(),
|
||||||
|
contentType,
|
||||||
|
idSerializer
|
||||||
|
),
|
||||||
|
KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
|
||||||
|
baseUrl: String,
|
||||||
|
subpart: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
noinline idSerializer: suspend (IdType) -> String
|
||||||
|
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
|
||||||
|
buildStandardUrl(baseUrl, subpart),
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
idSerializer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
idsSerializer: KSerializer<IdType>,
|
||||||
|
serialFormat: StringFormat,
|
||||||
|
contentType: ContentType,
|
||||||
|
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(baseUrl, httpClient, contentType) {
|
||||||
|
serialFormat.encodeToString(idsSerializer, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
idsSerializer: KSerializer<IdType>,
|
||||||
|
serialFormat: BinaryFormat,
|
||||||
|
contentType: ContentType,
|
||||||
|
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(baseUrl, httpClient, contentType) {
|
||||||
|
serialFormat.encodeHex(idsSerializer, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
subpart: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
noinline idSerializer: suspend (IdType) -> String
|
||||||
|
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
|
||||||
|
buildStandardUrl(baseUrl, subpart),
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
idSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
subpart: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
idsSerializer: KSerializer<IdType>,
|
||||||
|
serialFormat: StringFormat,
|
||||||
|
contentType: ContentType,
|
||||||
|
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(buildStandardUrl(baseUrl, subpart), httpClient, idsSerializer, serialFormat, contentType)
|
||||||
|
|
||||||
|
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
subpart: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
idsSerializer: KSerializer<IdType>,
|
||||||
|
serialFormat: BinaryFormat,
|
||||||
|
contentType: ContentType,
|
||||||
|
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(buildStandardUrl(baseUrl, subpart), httpClient, idsSerializer, serialFormat, contentType)
|
@@ -0,0 +1,97 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.client.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.countRouting
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.idParameterName
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.call.body
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.util.reflect.TypeInfo
|
||||||
|
import io.ktor.util.reflect.typeInfo
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
class KtorReadCRUDRepoClient<ObjectType, IdType> (
|
||||||
|
private val baseUrl: String,
|
||||||
|
private val httpClient: HttpClient,
|
||||||
|
private val objectType: TypeInfo,
|
||||||
|
private val paginationObjectType: TypeInfo,
|
||||||
|
private val contentType: ContentType,
|
||||||
|
private val idSerializer: suspend (IdType) -> String
|
||||||
|
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||||
|
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = httpClient.get(
|
||||||
|
buildStandardUrl(baseUrl, getByPaginationRouting, pagination.asUrlQueryParts)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body(paginationObjectType)
|
||||||
|
|
||||||
|
override suspend fun getById(id: IdType): ObjectType? = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
getByIdRouting,
|
||||||
|
mapOf(
|
||||||
|
idParameterName to idSerializer(id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.takeIf { it.status != HttpStatusCode.NoContent } ?.body<ObjectType>(objectType)
|
||||||
|
|
||||||
|
override suspend fun contains(id: IdType): Boolean = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
containsRouting,
|
||||||
|
mapOf(
|
||||||
|
idParameterName to idSerializer(id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body()
|
||||||
|
|
||||||
|
override suspend fun count(): Long = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
countRouting
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified ObjectType, IdType> KtorReadCRUDRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
noinline idSerializer: suspend (IdType) -> String
|
||||||
|
) = KtorReadCRUDRepoClient<ObjectType, IdType>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
typeInfo<ObjectType>(),
|
||||||
|
typeInfo<PaginationResult<ObjectType>>(),
|
||||||
|
contentType,
|
||||||
|
idSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun <reified ObjectType, IdType> KtorReadCRUDRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
idsSerializer: KSerializer<IdType>,
|
||||||
|
serialFormat: StringFormat,
|
||||||
|
contentType: ContentType,
|
||||||
|
) = KtorReadCRUDRepoClient<ObjectType, IdType>(baseUrl, httpClient, contentType) {
|
||||||
|
serialFormat.encodeToString(idsSerializer, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified ObjectType, IdType> KtorReadCRUDRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
idsSerializer: KSerializer<IdType>,
|
||||||
|
serialFormat: BinaryFormat,
|
||||||
|
contentType: ContentType,
|
||||||
|
) = KtorReadCRUDRepoClient<ObjectType, IdType>(baseUrl, httpClient, contentType) {
|
||||||
|
serialFormat.encodeHex(idsSerializer, it)
|
||||||
|
}
|
@@ -3,19 +3,22 @@ package dev.inmo.micro_utils.repos.ktor.client.crud
|
|||||||
import dev.inmo.micro_utils.ktor.client.*
|
import dev.inmo.micro_utils.ktor.client.*
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.countRouting
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.idParameterName
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
|
||||||
|
@Deprecated("Use KtorReadCRUDRepoClient instead")
|
||||||
class KtorReadStandardCrudRepo<ObjectType, IdType> (
|
class KtorReadStandardCrudRepo<ObjectType, IdType> (
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
private val unifiedRequester: UnifiedRequester,
|
private val unifiedRequester: UnifiedRequester,
|
||||||
private val objectsSerializer: KSerializer<ObjectType>,
|
private val objectsSerializer: KSerializer<ObjectType>,
|
||||||
private val objectsSerializerNullable: KSerializer<ObjectType?>,
|
private val objectsSerializerNullable: KSerializer<ObjectType?>,
|
||||||
private val idsSerializer: KSerializer<IdType>
|
private val idsSerializer: KSerializer<IdType>
|
||||||
) : ReadStandardCRUDRepo<ObjectType, IdType> {
|
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||||
private val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
|
private val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -38,9 +41,7 @@ class KtorReadStandardCrudRepo<ObjectType, IdType> (
|
|||||||
buildStandardUrl(
|
buildStandardUrl(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
getByIdRouting,
|
getByIdRouting,
|
||||||
mapOf(
|
idParameterName to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
|
||||||
"id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
objectsSerializerNullable
|
objectsSerializerNullable
|
||||||
)
|
)
|
||||||
@@ -49,9 +50,7 @@ class KtorReadStandardCrudRepo<ObjectType, IdType> (
|
|||||||
buildStandardUrl(
|
buildStandardUrl(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
containsRouting,
|
containsRouting,
|
||||||
mapOf(
|
idParameterName to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
|
||||||
"id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
Boolean.serializer()
|
Boolean.serializer()
|
||||||
)
|
)
|
||||||
|
@@ -7,6 +7,7 @@ import dev.inmo.micro_utils.repos.*
|
|||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
|
|
||||||
|
@Deprecated("Use KtorCRUDRepoClient instead")
|
||||||
class KtorStandardCrudRepo<ObjectType, IdType, InputValue> (
|
class KtorStandardCrudRepo<ObjectType, IdType, InputValue> (
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
baseSubpart: String,
|
baseSubpart: String,
|
||||||
@@ -15,15 +16,15 @@ class KtorStandardCrudRepo<ObjectType, IdType, InputValue> (
|
|||||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||||
inputsSerializer: KSerializer<InputValue>,
|
inputsSerializer: KSerializer<InputValue>,
|
||||||
idsSerializer: KSerializer<IdType>
|
idsSerializer: KSerializer<IdType>
|
||||||
) : StandardCRUDRepo<ObjectType, IdType, InputValue>,
|
) : CRUDRepo<ObjectType, IdType, InputValue>,
|
||||||
ReadStandardCRUDRepo<ObjectType, IdType> by KtorReadStandardCrudRepo(
|
ReadCRUDRepo<ObjectType, IdType> by KtorReadStandardCrudRepo(
|
||||||
"$baseUrl/$baseSubpart",
|
"$baseUrl/$baseSubpart",
|
||||||
unifiedRequester,
|
unifiedRequester,
|
||||||
objectsSerializer,
|
objectsSerializer,
|
||||||
objectsNullableSerializer,
|
objectsNullableSerializer,
|
||||||
idsSerializer
|
idsSerializer
|
||||||
),
|
),
|
||||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValue> by KtorWriteStandardCrudRepo(
|
WriteCRUDRepo<ObjectType, IdType, InputValue> by KtorWriteStandardCrudRepo(
|
||||||
"$baseUrl/$baseSubpart",
|
"$baseUrl/$baseSubpart",
|
||||||
unifiedRequester,
|
unifiedRequester,
|
||||||
objectsSerializer,
|
objectsSerializer,
|
||||||
|
@@ -0,0 +1,85 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.client.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.client.*
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||||
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.call.body
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.statement.HttpResponse
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.contentType
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class KtorWriteCrudRepoClient<ObjectType, IdType, InputValue> (
|
||||||
|
private val baseUrl: String,
|
||||||
|
private val httpClient: HttpClient,
|
||||||
|
override val newObjectsFlow: Flow<ObjectType>,
|
||||||
|
override val updatedObjectsFlow: Flow<ObjectType>,
|
||||||
|
override val deletedObjectsIdsFlow: Flow<IdType>,
|
||||||
|
private val createSetup: suspend HttpRequestBuilder.(List<InputValue>) -> Unit,
|
||||||
|
private val updateSetup: suspend HttpRequestBuilder.(List<UpdatedValuePair<IdType, InputValue>>) -> Unit,
|
||||||
|
private val deleteByIdSetup: suspend HttpRequestBuilder.(List<IdType>) -> Unit,
|
||||||
|
private val createBodyGetter: suspend HttpResponse.() -> List<ObjectType>,
|
||||||
|
private val updateBodyGetter: suspend HttpResponse.() -> List<ObjectType>
|
||||||
|
) : WriteCRUDRepo<ObjectType, IdType, InputValue> {
|
||||||
|
override suspend fun create(values: List<InputValue>): List<ObjectType> = httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, createRouting)
|
||||||
|
) {
|
||||||
|
createSetup(values)
|
||||||
|
}.createBodyGetter()
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
values: List<UpdatedValuePair<IdType, InputValue>>
|
||||||
|
): List<ObjectType> = httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, updateRouting)
|
||||||
|
) {
|
||||||
|
updateSetup(values)
|
||||||
|
}.updateBodyGetter()
|
||||||
|
|
||||||
|
override suspend fun update(id: IdType, value: InputValue): ObjectType? = update(listOf(id to value)).firstOrNull()
|
||||||
|
|
||||||
|
override suspend fun deleteById(ids: List<IdType>) {
|
||||||
|
httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, deleteByIdRouting)
|
||||||
|
) {
|
||||||
|
deleteByIdSetup(ids)
|
||||||
|
}.throwOnUnsuccess { "Unable to delete $ids" }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType
|
||||||
|
) = KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
httpClient.createStandardWebsocketFlow(
|
||||||
|
buildStandardUrl(baseUrl, newObjectsFlowRouting),
|
||||||
|
),
|
||||||
|
httpClient.createStandardWebsocketFlow(
|
||||||
|
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
|
||||||
|
),
|
||||||
|
httpClient.createStandardWebsocketFlow(
|
||||||
|
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
|
||||||
|
),
|
||||||
|
{
|
||||||
|
contentType(contentType)
|
||||||
|
setBody(it)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contentType(contentType)
|
||||||
|
setBody(it)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contentType(contentType)
|
||||||
|
setBody(it)
|
||||||
|
},
|
||||||
|
{ body() },
|
||||||
|
{ body() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -3,13 +3,14 @@ package dev.inmo.micro_utils.repos.ktor.client.crud
|
|||||||
import dev.inmo.micro_utils.ktor.client.*
|
import dev.inmo.micro_utils.ktor.client.*
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||||
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.builtins.*
|
import kotlinx.serialization.builtins.*
|
||||||
|
|
||||||
|
@Deprecated("Use KtorWriteCRUDRepoClient instead")
|
||||||
class KtorWriteStandardCrudRepo<ObjectType, IdType, InputValue> (
|
class KtorWriteStandardCrudRepo<ObjectType, IdType, InputValue> (
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
private val unifiedRequester: UnifiedRequester,
|
private val unifiedRequester: UnifiedRequester,
|
||||||
@@ -17,7 +18,7 @@ class KtorWriteStandardCrudRepo<ObjectType, IdType, InputValue> (
|
|||||||
private val objectsNullableSerializer: KSerializer<ObjectType?>,
|
private val objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||||
private val inputsSerializer: KSerializer<InputValue>,
|
private val inputsSerializer: KSerializer<InputValue>,
|
||||||
private val idsSerializer: KSerializer<IdType>
|
private val idsSerializer: KSerializer<IdType>
|
||||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValue> {
|
) : WriteCRUDRepo<ObjectType, IdType, InputValue> {
|
||||||
private val listObjectsSerializer = ListSerializer(objectsSerializer)
|
private val listObjectsSerializer = ListSerializer(objectsSerializer)
|
||||||
private val listInputSerializer = ListSerializer(inputsSerializer)
|
private val listInputSerializer = ListSerializer(inputsSerializer)
|
||||||
private val listIdsSerializer = ListSerializer(idsSerializer)
|
private val listIdsSerializer = ListSerializer(idsSerializer)
|
||||||
|
@@ -0,0 +1,85 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.client.key.value
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.encodeURLQueryComponent
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
class KtorKeyValueRepoClient<Key, Value> (
|
||||||
|
readDelegate: ReadKeyValueRepo<Key, Value>,
|
||||||
|
writeDelegate: WriteKeyValueRepo<Key, Value>
|
||||||
|
) : KeyValueRepo<Key, Value> by DelegateBasedKeyValueRepo(
|
||||||
|
readDelegate,
|
||||||
|
writeDelegate
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
inline operator fun <reified Key, reified Value> invoke(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
noinline idSerializer: suspend (Key) -> String,
|
||||||
|
noinline valueSerializer: suspend (Value) -> String
|
||||||
|
) = KtorKeyValueRepoClient(
|
||||||
|
KtorReadKeyValueRepoClient(
|
||||||
|
baseUrl, httpClient, contentType, idSerializer, valueSerializer
|
||||||
|
),
|
||||||
|
KtorWriteKeyValueRepoClient(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
inline operator fun <reified Key, reified Value> invoke(
|
||||||
|
baseUrl: String,
|
||||||
|
subpart: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
noinline idSerializer: suspend (Key) -> String,
|
||||||
|
noinline valueSerializer: suspend (Value) -> String
|
||||||
|
) = KtorKeyValueRepoClient(
|
||||||
|
buildStandardUrl(baseUrl, subpart),
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
idSerializer,
|
||||||
|
valueSerializer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified Key, reified Value> KtorKeyValueRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
idSerializer: SerializationStrategy<Key>,
|
||||||
|
valueSerializer: SerializationStrategy<Value>,
|
||||||
|
serialFormat: StringFormat,
|
||||||
|
) = KtorKeyValueRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
{
|
||||||
|
serialFormat.encodeToString(idSerializer, it).encodeURLQueryComponent()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified Key, reified Value> KtorKeyValueRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
idSerializer: SerializationStrategy<Key>,
|
||||||
|
valueSerializer: SerializationStrategy<Value>,
|
||||||
|
serialFormat: BinaryFormat,
|
||||||
|
) = KtorKeyValueRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
{
|
||||||
|
serialFormat.encodeHex(idSerializer, it)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
serialFormat.encodeHex(valueSerializer, it)
|
||||||
|
}
|
@@ -0,0 +1,144 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.client.key.value
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.containsRoute
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.call.body
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.util.reflect.TypeInfo
|
||||||
|
import io.ktor.util.reflect.typeInfo
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
class KtorReadKeyValueRepoClient<Key, Value>(
|
||||||
|
private val baseUrl: String,
|
||||||
|
private val httpClient: HttpClient,
|
||||||
|
private val contentType: ContentType,
|
||||||
|
private val objectType: TypeInfo,
|
||||||
|
private val paginationResultObjectsTypeInfo: TypeInfo,
|
||||||
|
private val paginationResultIdsTypeInfo: TypeInfo,
|
||||||
|
private val idSerializer: suspend (Key) -> String,
|
||||||
|
private val valueSerializer: suspend (Value) -> String
|
||||||
|
) : ReadKeyValueRepo<Key, Value> {
|
||||||
|
override suspend fun get(k: Key): Value? = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
getRoute,
|
||||||
|
mapOf(
|
||||||
|
keyParameterName to idSerializer(k)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.takeIf { it.status != HttpStatusCode.NoContent } ?.body<Value>(objectType)
|
||||||
|
|
||||||
|
override suspend fun contains(key: Key): Boolean = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
containsRoute,
|
||||||
|
keyParameterName to idSerializer(key)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body()
|
||||||
|
|
||||||
|
override suspend fun values(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Value> = httpClient.get(
|
||||||
|
buildStandardUrl(baseUrl, valuesRoute, pagination.asUrlQueryParts + (reversedParameterName to reversed.toString()))
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body(paginationResultObjectsTypeInfo)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> = httpClient.get(
|
||||||
|
buildStandardUrl(baseUrl, keysRoute, pagination.asUrlQueryParts + (reversedParameterName to reversed.toString()))
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body(paginationResultIdsTypeInfo)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: Value,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
keysRoute,
|
||||||
|
pagination.asUrlQueryParts + (reversedParameterName to reversed.toString()) + (valueParameterName to valueSerializer(v))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body(paginationResultIdsTypeInfo)
|
||||||
|
|
||||||
|
override suspend fun count(): Long = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
dev.inmo.micro_utils.repos.ktor.common.countRoute
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified Key, reified Value> KtorReadKeyValueRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
noinline idSerializer: suspend (Key) -> String,
|
||||||
|
noinline valueSerializer: suspend (Value) -> String
|
||||||
|
) = KtorReadKeyValueRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
typeInfo<Value>(),
|
||||||
|
typeInfo<PaginationResult<Value>>(),
|
||||||
|
typeInfo<PaginationResult<Key>>(),
|
||||||
|
idSerializer,
|
||||||
|
valueSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun <reified Key, reified Value> KtorReadKeyValueRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
idsSerializer: KSerializer<Key>,
|
||||||
|
valueSerializer: KSerializer<Value>,
|
||||||
|
serialFormat: StringFormat,
|
||||||
|
contentType: ContentType,
|
||||||
|
) = KtorReadKeyValueRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
{
|
||||||
|
serialFormat.encodeToString(idsSerializer, it).encodeURLQueryComponent()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified Key, reified Value> KtorReadKeyValueRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
idsSerializer: KSerializer<Key>,
|
||||||
|
valuesSerializer: KSerializer<Value>,
|
||||||
|
serialFormat: BinaryFormat,
|
||||||
|
contentType: ContentType,
|
||||||
|
) = KtorReadKeyValueRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
{
|
||||||
|
serialFormat.encodeHex(idsSerializer, it)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
serialFormat.encodeHex(valuesSerializer, it)
|
||||||
|
}
|
@@ -0,0 +1,79 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.client.key.value
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.client.createStandardWebsocketFlow
|
||||||
|
import dev.inmo.micro_utils.ktor.client.throwOnUnsuccess
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.request.post
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.util.InternalAPI
|
||||||
|
import io.ktor.util.reflect.TypeInfo
|
||||||
|
import io.ktor.util.reflect.typeInfo
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class KtorWriteKeyValueRepoClient<Key, Value>(
|
||||||
|
private val baseUrl: String,
|
||||||
|
private val httpClient: HttpClient,
|
||||||
|
private val contentType: ContentType,
|
||||||
|
override val onNewValue: Flow<Pair<Key, Value>>,
|
||||||
|
override val onValueRemoved: Flow<Key>,
|
||||||
|
private val idsListTypeInfo: TypeInfo,
|
||||||
|
private val objectsListTypeInfo: TypeInfo,
|
||||||
|
private val idsToObjectsMapTypeInfo: TypeInfo
|
||||||
|
) : WriteKeyValueRepo<Key, Value> {
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
||||||
|
httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, unsetWithValuesRoute)
|
||||||
|
) {
|
||||||
|
body = toUnset
|
||||||
|
bodyType = objectsListTypeInfo
|
||||||
|
contentType(contentType)
|
||||||
|
}.throwOnUnsuccess { "Unable to unset data with values $toUnset" }
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
|
override suspend fun unset(toUnset: List<Key>) {
|
||||||
|
httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, unsetRoute)
|
||||||
|
) {
|
||||||
|
body = toUnset
|
||||||
|
bodyType = idsListTypeInfo
|
||||||
|
contentType(contentType)
|
||||||
|
}.throwOnUnsuccess { "Unable to unset $toUnset" }
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
|
override suspend fun set(toSet: Map<Key, Value>) {
|
||||||
|
httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, setRoute)
|
||||||
|
) {
|
||||||
|
body = toSet
|
||||||
|
bodyType = idsToObjectsMapTypeInfo
|
||||||
|
contentType(contentType)
|
||||||
|
}.throwOnUnsuccess { "Unable to set $toSet" }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
inline operator fun <reified Key, reified Value> invoke(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType
|
||||||
|
) = KtorWriteKeyValueRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
httpClient.createStandardWebsocketFlow(
|
||||||
|
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||||
|
),
|
||||||
|
httpClient.createStandardWebsocketFlow(
|
||||||
|
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||||
|
),
|
||||||
|
typeInfo<List<Key>>(),
|
||||||
|
typeInfo<List<Value>>(),
|
||||||
|
typeInfo<Map<Key, Value>>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,89 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.client.key.values
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.http.encodeURLQueryComponent
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
class KtorKeyValuesRepoClient<Key, Value> (
|
||||||
|
readDelegate: ReadKeyValuesRepo<Key, Value>,
|
||||||
|
writeDelegate: WriteKeyValuesRepo<Key, Value>
|
||||||
|
) : KeyValuesRepo<Key, Value> by DelegateBasedKeyValuesRepo(
|
||||||
|
readDelegate,
|
||||||
|
writeDelegate
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
inline operator fun <reified Key : Any, reified Value : Any> invoke(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
noinline keySerializer: suspend (Key) -> String,
|
||||||
|
noinline valueSerializer: suspend (Value) -> String
|
||||||
|
) = KtorKeyValuesRepoClient(
|
||||||
|
KtorReadKeyValuesRepoClient(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
keySerializer,
|
||||||
|
valueSerializer
|
||||||
|
),
|
||||||
|
KtorWriteKeyValuesRepoClient(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType
|
||||||
|
)
|
||||||
|
)
|
||||||
|
inline operator fun <reified Key : Any, reified Value : Any> invoke(
|
||||||
|
baseUrl: String,
|
||||||
|
subpart: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
noinline keySerializer: suspend (Key) -> String,
|
||||||
|
noinline valueSerializer: suspend (Value) -> String
|
||||||
|
) = KtorKeyValuesRepoClient(
|
||||||
|
buildStandardUrl(baseUrl, subpart),
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
keySerializer,
|
||||||
|
valueSerializer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified Key : Any, reified Value : Any> KtorKeyValuesRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
keySerializer: SerializationStrategy<Key>,
|
||||||
|
valueSerializer: SerializationStrategy<Value>,
|
||||||
|
serialFormat: StringFormat,
|
||||||
|
) = KtorKeyValuesRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
{
|
||||||
|
serialFormat.encodeToString(keySerializer, it).encodeURLQueryComponent()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified Key : Any, reified Value : Any> KtorKeyValuesRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
keySerializer: SerializationStrategy<Key>,
|
||||||
|
valueSerializer: SerializationStrategy<Value>,
|
||||||
|
serialFormat: BinaryFormat,
|
||||||
|
) = KtorKeyValuesRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
{
|
||||||
|
serialFormat.encodeHex(keySerializer, it)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
serialFormat.encodeHex(valueSerializer, it)
|
||||||
|
}
|
@@ -0,0 +1,158 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.client.key.values
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.pagination.*
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.call.body
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.util.reflect.TypeInfo
|
||||||
|
import io.ktor.util.reflect.typeInfo
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
class KtorReadKeyValuesRepoClient<Key, Value>(
|
||||||
|
private val baseUrl: String,
|
||||||
|
private val httpClient: HttpClient,
|
||||||
|
private val contentType: ContentType,
|
||||||
|
private val paginationResultValuesTypeInfo: TypeInfo,
|
||||||
|
private val paginationResultKeysTypeInfo: TypeInfo,
|
||||||
|
private val keySerializer: suspend (Key) -> String,
|
||||||
|
private val valueSerializer: suspend (Value) -> String
|
||||||
|
) : ReadKeyValuesRepo<Key, Value> {
|
||||||
|
override suspend fun get(
|
||||||
|
k: Key,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Value> = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
getRoute,
|
||||||
|
pagination.asUrlQueryParts + (reversedParameterName to reversed.toString()) + (keyParameterName to keySerializer(k))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body(paginationResultValuesTypeInfo)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
keysRoute,
|
||||||
|
pagination.asUrlQueryParts + (reversedParameterName to reversed.toString())
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body(paginationResultKeysTypeInfo)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: Value,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
keysRoute,
|
||||||
|
pagination.asUrlQueryParts + (reversedParameterName to reversed.toString()) + (valueParameterName to valueSerializer(v))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body(paginationResultKeysTypeInfo)
|
||||||
|
|
||||||
|
override suspend fun contains(k: Key): Boolean = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
containsRoute,
|
||||||
|
keyParameterName to keySerializer(k)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body()
|
||||||
|
|
||||||
|
override suspend fun contains(k: Key, v: Value): Boolean = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
containsRoute,
|
||||||
|
keyParameterName to keySerializer(k),
|
||||||
|
valueParameterName to valueSerializer(v)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body()
|
||||||
|
|
||||||
|
override suspend fun count(): Long = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
countRouting
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body()
|
||||||
|
|
||||||
|
override suspend fun count(k: Key): Long = httpClient.get(
|
||||||
|
buildStandardUrl(
|
||||||
|
baseUrl,
|
||||||
|
countRouting,
|
||||||
|
keyParameterName to keySerializer(k),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
contentType(contentType)
|
||||||
|
}.body()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified Key, reified Value> KtorReadKeyValuesRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType,
|
||||||
|
noinline keySerializer: suspend (Key) -> String,
|
||||||
|
noinline valueSerializer: suspend (Value) -> String
|
||||||
|
) = KtorReadKeyValuesRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
typeInfo<PaginationResult<Value>>(),
|
||||||
|
typeInfo<PaginationResult<Key>>(),
|
||||||
|
keySerializer,
|
||||||
|
valueSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
inline fun <reified Key, reified Value> KtorReadKeyValuesRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
idsSerializer: KSerializer<Key>,
|
||||||
|
valueSerializer: KSerializer<Value>,
|
||||||
|
serialFormat: StringFormat,
|
||||||
|
contentType: ContentType,
|
||||||
|
) = KtorReadKeyValuesRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
{
|
||||||
|
serialFormat.encodeToString(idsSerializer, it).encodeURLQueryComponent()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified Key, reified Value> KtorReadKeyValuesRepoClient(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
idsSerializer: KSerializer<Key>,
|
||||||
|
valuesSerializer: KSerializer<Value>,
|
||||||
|
serialFormat: BinaryFormat,
|
||||||
|
contentType: ContentType,
|
||||||
|
) = KtorReadKeyValuesRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
{
|
||||||
|
serialFormat.encodeHex(idsSerializer, it)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
serialFormat.encodeHex(valuesSerializer, it)
|
||||||
|
}
|
@@ -0,0 +1,106 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.client.key.values
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.ktor.client.createStandardWebsocketFlow
|
||||||
|
import dev.inmo.micro_utils.ktor.client.throwOnUnsuccess
|
||||||
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.request.post
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.util.InternalAPI
|
||||||
|
import io.ktor.util.reflect.TypeInfo
|
||||||
|
import io.ktor.util.reflect.typeInfo
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class KtorWriteKeyValuesRepoClient<Key : Any, Value : Any>(
|
||||||
|
private val baseUrl: String,
|
||||||
|
private val httpClient: HttpClient,
|
||||||
|
private val contentType: ContentType,
|
||||||
|
override val onNewValue: Flow<Pair<Key, Value>>,
|
||||||
|
override val onValueRemoved: Flow<Pair<Key, Value>>,
|
||||||
|
override val onDataCleared: Flow<Key>,
|
||||||
|
private val keyTypeInfo: TypeInfo,
|
||||||
|
private val valueTypeInfo: TypeInfo,
|
||||||
|
private val keyToValuesMapTypeInfo: TypeInfo
|
||||||
|
) : WriteKeyValuesRepo<Key, Value> {
|
||||||
|
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
|
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||||
|
httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, addRoute)
|
||||||
|
) {
|
||||||
|
body = toAdd
|
||||||
|
bodyType = keyToValuesMapTypeInfo
|
||||||
|
contentType(contentType)
|
||||||
|
}.throwOnUnsuccess { "Unable to add $toAdd" }
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
|
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
||||||
|
httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, removeRoute)
|
||||||
|
) {
|
||||||
|
body = toRemove
|
||||||
|
bodyType = keyToValuesMapTypeInfo
|
||||||
|
contentType(contentType)
|
||||||
|
}.throwOnUnsuccess { "Unable to remove $toRemove" }
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
|
override suspend fun clear(k: Key) {
|
||||||
|
httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, clearRoute)
|
||||||
|
) {
|
||||||
|
body = k
|
||||||
|
bodyType = keyTypeInfo
|
||||||
|
contentType(contentType)
|
||||||
|
}.throwOnUnsuccess { "Unable to clear data with key $k" }
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
|
override suspend fun clearWithValue(v: Value) {
|
||||||
|
httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, clearWithValueRoute)
|
||||||
|
) {
|
||||||
|
body = v
|
||||||
|
bodyType = valueTypeInfo
|
||||||
|
contentType(contentType)
|
||||||
|
}.throwOnUnsuccess { "Unable to clear data with value $v" }
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalAPI::class)
|
||||||
|
override suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||||
|
httpClient.post(
|
||||||
|
buildStandardUrl(baseUrl, setRoute)
|
||||||
|
) {
|
||||||
|
body = toSet
|
||||||
|
bodyType = keyToValuesMapTypeInfo
|
||||||
|
contentType(contentType)
|
||||||
|
}.throwOnUnsuccess { "Unable to set data $toSet" }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
inline operator fun <reified Key : Any, reified Value : Any> invoke(
|
||||||
|
baseUrl: String,
|
||||||
|
httpClient: HttpClient,
|
||||||
|
contentType: ContentType
|
||||||
|
) = KtorWriteKeyValuesRepoClient<Key, Value>(
|
||||||
|
baseUrl,
|
||||||
|
httpClient,
|
||||||
|
contentType,
|
||||||
|
httpClient.createStandardWebsocketFlow(
|
||||||
|
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||||
|
),
|
||||||
|
httpClient.createStandardWebsocketFlow(
|
||||||
|
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||||
|
),
|
||||||
|
httpClient.createStandardWebsocketFlow(
|
||||||
|
buildStandardUrl(baseUrl, onDataClearedRoute),
|
||||||
|
),
|
||||||
|
typeInfo<Key>(),
|
||||||
|
typeInfo<Value>(),
|
||||||
|
typeInfo<Map<Key, List<Value>>>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -3,20 +3,25 @@ package dev.inmo.micro_utils.repos.ktor.client.key_value
|
|||||||
import dev.inmo.micro_utils.ktor.client.*
|
import dev.inmo.micro_utils.ktor.client.*
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.*
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.containsRoute
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.countRoute
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
import dev.inmo.micro_utils.repos.ktor.common.key_value.keyParameterName
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.common.key_value.reversedParameterName
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
|
||||||
|
@Deprecated("Replaced with KtorReadKeyValueRepoClient")
|
||||||
class KtorReadStandardKeyValueRepo<Key, Value> (
|
class KtorReadStandardKeyValueRepo<Key, Value> (
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
private val unifiedRequester: UnifiedRequester,
|
private val unifiedRequester: UnifiedRequester,
|
||||||
private val keySerializer: KSerializer<Key>,
|
private val keySerializer: KSerializer<Key>,
|
||||||
private val valueSerializer: KSerializer<Value>,
|
private val valueSerializer: KSerializer<Value>,
|
||||||
private val valueNullableSerializer: KSerializer<Value?>
|
private val valueNullableSerializer: KSerializer<Value?>
|
||||||
) : ReadStandardKeyValueRepo<Key, Value> {
|
) : ReadKeyValueRepo<Key, Value> {
|
||||||
constructor(
|
constructor(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
client: HttpClient,
|
client: HttpClient,
|
||||||
|
@@ -7,6 +7,7 @@ import dev.inmo.micro_utils.repos.*
|
|||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
@Deprecated("Replaced with KtorKeyValueRepoClient")
|
||||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||||
class KtorStandartKeyValueRepo<K, V> (
|
class KtorStandartKeyValueRepo<K, V> (
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
@@ -15,15 +16,15 @@ class KtorStandartKeyValueRepo<K, V> (
|
|||||||
keySerializer: KSerializer<K>,
|
keySerializer: KSerializer<K>,
|
||||||
valueSerializer: KSerializer<V>,
|
valueSerializer: KSerializer<V>,
|
||||||
valueNullableSerializer: KSerializer<V?>
|
valueNullableSerializer: KSerializer<V?>
|
||||||
) : StandardKeyValueRepo<K, V>,
|
) : KeyValueRepo<K, V>,
|
||||||
ReadStandardKeyValueRepo<K, V> by KtorReadStandardKeyValueRepo(
|
ReadKeyValueRepo<K, V> by KtorReadStandardKeyValueRepo(
|
||||||
"$baseUrl/$baseSubpart",
|
"$baseUrl/$baseSubpart",
|
||||||
unifiedRequester,
|
unifiedRequester,
|
||||||
keySerializer,
|
keySerializer,
|
||||||
valueSerializer,
|
valueSerializer,
|
||||||
valueNullableSerializer
|
valueNullableSerializer
|
||||||
),
|
),
|
||||||
WriteStandardKeyValueRepo<K, V> by KtorWriteStandardKeyValueRepo(
|
WriteKeyValueRepo<K, V> by KtorWriteStandardKeyValueRepo(
|
||||||
"$baseUrl/$baseSubpart",
|
"$baseUrl/$baseSubpart",
|
||||||
unifiedRequester,
|
unifiedRequester,
|
||||||
keySerializer,
|
keySerializer,
|
||||||
|
@@ -2,19 +2,20 @@ package dev.inmo.micro_utils.repos.ktor.client.key_value
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.client.*
|
import dev.inmo.micro_utils.ktor.client.*
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
import dev.inmo.micro_utils.repos.WriteStandardKeyValueRepo
|
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.builtins.*
|
import kotlinx.serialization.builtins.*
|
||||||
|
|
||||||
|
@Deprecated("Replaced with KtorWriteKeyValueRepoClient")
|
||||||
class KtorWriteStandardKeyValueRepo<K, V> (
|
class KtorWriteStandardKeyValueRepo<K, V> (
|
||||||
private var baseUrl: String,
|
private var baseUrl: String,
|
||||||
private var unifiedRequester: UnifiedRequester,
|
private var unifiedRequester: UnifiedRequester,
|
||||||
private var keySerializer: KSerializer<K>,
|
private var keySerializer: KSerializer<K>,
|
||||||
private var valueSerializer: KSerializer<V>,
|
private var valueSerializer: KSerializer<V>,
|
||||||
) : WriteStandardKeyValueRepo<K, V> {
|
) : WriteKeyValueRepo<K, V> {
|
||||||
private val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
private val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
||||||
private val keysListSerializer = ListSerializer(keySerializer)
|
private val keysListSerializer = ListSerializer(keySerializer)
|
||||||
private val valuesListSerializer = ListSerializer(valueSerializer)
|
private val valuesListSerializer = ListSerializer(valueSerializer)
|
||||||
|
@@ -7,20 +7,21 @@ import dev.inmo.micro_utils.repos.*
|
|||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
|
|
||||||
|
@Deprecated("Should be replaced with KtorKeyValuesRepoClient")
|
||||||
class KtorOneToManyKeyValueRepo<Key, Value>(
|
class KtorOneToManyKeyValueRepo<Key, Value>(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
baseSubpart: String,
|
baseSubpart: String,
|
||||||
unifiedRequester: UnifiedRequester,
|
unifiedRequester: UnifiedRequester,
|
||||||
keySerializer: KSerializer<Key>,
|
keySerializer: KSerializer<Key>,
|
||||||
valueSerializer: KSerializer<Value>,
|
valueSerializer: KSerializer<Value>,
|
||||||
) : OneToManyKeyValueRepo<Key, Value>,
|
) : KeyValuesRepo<Key, Value>,
|
||||||
ReadOneToManyKeyValueRepo<Key, Value> by KtorReadOneToManyKeyValueRepo<Key, Value> (
|
ReadKeyValuesRepo<Key, Value> by KtorReadOneToManyKeyValueRepo<Key, Value> (
|
||||||
"$baseUrl/$baseSubpart",
|
"$baseUrl/$baseSubpart",
|
||||||
unifiedRequester,
|
unifiedRequester,
|
||||||
keySerializer,
|
keySerializer,
|
||||||
valueSerializer,
|
valueSerializer,
|
||||||
),
|
),
|
||||||
WriteOneToManyKeyValueRepo<Key, Value> by KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
WriteKeyValuesRepo<Key, Value> by KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||||
"$baseUrl/$baseSubpart",
|
"$baseUrl/$baseSubpart",
|
||||||
unifiedRequester,
|
unifiedRequester,
|
||||||
keySerializer,
|
keySerializer,
|
||||||
|
@@ -3,7 +3,7 @@ package dev.inmo.micro_utils.repos.ktor.client.one_to_many
|
|||||||
import dev.inmo.micro_utils.ktor.client.*
|
import dev.inmo.micro_utils.ktor.client.*
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
||||||
@@ -12,12 +12,13 @@ import io.ktor.client.HttpClient
|
|||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
|
||||||
|
@Deprecated("Should be replaced with KtorReadKeyValuesRepoClient")
|
||||||
class KtorReadOneToManyKeyValueRepo<Key, Value> (
|
class KtorReadOneToManyKeyValueRepo<Key, Value> (
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
private val unifiedRequester: UnifiedRequester,
|
private val unifiedRequester: UnifiedRequester,
|
||||||
private val keySerializer: KSerializer<Key>,
|
private val keySerializer: KSerializer<Key>,
|
||||||
private val valueSerializer: KSerializer<Value>
|
private val valueSerializer: KSerializer<Value>
|
||||||
) : ReadOneToManyKeyValueRepo<Key, Value> {
|
) : ReadKeyValuesRepo<Key, Value> {
|
||||||
private val paginationValueResultSerializer = PaginationResult.serializer(valueSerializer)
|
private val paginationValueResultSerializer = PaginationResult.serializer(valueSerializer)
|
||||||
private val paginationKeyResultSerializer = PaginationResult.serializer(keySerializer)
|
private val paginationKeyResultSerializer = PaginationResult.serializer(keySerializer)
|
||||||
|
|
||||||
|
@@ -2,19 +2,20 @@ package dev.inmo.micro_utils.repos.ktor.client.one_to_many
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.ktor.client.*
|
import dev.inmo.micro_utils.ktor.client.*
|
||||||
import dev.inmo.micro_utils.ktor.common.*
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
import dev.inmo.micro_utils.repos.WriteOneToManyKeyValueRepo
|
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
||||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.builtins.*
|
import kotlinx.serialization.builtins.*
|
||||||
|
|
||||||
|
@Deprecated("Should be replaced with KtorWriteKeyValuesRepoClient")
|
||||||
class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||||
private val baseUrl: String,
|
private val baseUrl: String,
|
||||||
private val unifiedRequester: UnifiedRequester,
|
private val unifiedRequester: UnifiedRequester,
|
||||||
private val keySerializer: KSerializer<Key>,
|
private val keySerializer: KSerializer<Key>,
|
||||||
private val valueSerializer: KSerializer<Value>
|
private val valueSerializer: KSerializer<Value>
|
||||||
) : WriteOneToManyKeyValueRepo<Key, Value> {
|
) : WriteKeyValuesRepo<Key, Value> {
|
||||||
private val keyValueSerializer = PairSerializer(keySerializer, valueSerializer)
|
private val keyValueSerializer = PairSerializer(keySerializer, valueSerializer)
|
||||||
private val keyValueMapSerializer = MapSerializer(keySerializer, ListSerializer(valueSerializer))
|
private val keyValueMapSerializer = MapSerializer(keySerializer, ListSerializer(valueSerializer))
|
||||||
|
|
||||||
|
@@ -13,5 +13,22 @@ kotlin {
|
|||||||
api internalProject("micro_utils.repos.common")
|
api internalProject("micro_utils.repos.common")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
jvmTest {
|
||||||
|
dependencies {
|
||||||
|
implementation internalProject("micro_utils.repos.common")
|
||||||
|
implementation internalProject("micro_utils.repos.ktor.client")
|
||||||
|
implementation internalProject("micro_utils.repos.ktor.server")
|
||||||
|
implementation internalProject("micro_utils.repos.inmemory")
|
||||||
|
implementation libs.kt.coroutines.test
|
||||||
|
|
||||||
|
implementation libs.ktor.server.cio
|
||||||
|
implementation libs.ktor.client.cio
|
||||||
|
implementation libs.ktor.server.content.negotiation
|
||||||
|
implementation libs.ktor.serialization.kotlinx.json
|
||||||
|
implementation libs.ktor.client.content.negotiation
|
||||||
|
implementation libs.ktor.client.logging
|
||||||
|
implementation libs.ktor.client.websockets
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.common
|
||||||
|
|
||||||
|
const val containsRoute = "contains"
|
@@ -0,0 +1,3 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.common
|
||||||
|
|
||||||
|
const val countRoute = "count"
|
@@ -0,0 +1,3 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.ktor.common
|
||||||
|
|
||||||
|
const val countRouting = "count"
|
@@ -1,5 +1,6 @@
|
|||||||
package dev.inmo.micro_utils.repos.ktor.common
|
package dev.inmo.micro_utils.repos.ktor.common
|
||||||
|
|
||||||
|
const val idParameterName = "id"
|
||||||
const val keyParameterName = "key"
|
const val keyParameterName = "key"
|
||||||
const val valueParameterName = "value"
|
const val valueParameterName = "value"
|
||||||
const val reversedParameterName = "reversed"
|
const val reversedParameterName = "reversed"
|
@@ -3,4 +3,3 @@ package dev.inmo.micro_utils.repos.ktor.common.crud
|
|||||||
const val getByPaginationRouting = "getByPagination"
|
const val getByPaginationRouting = "getByPagination"
|
||||||
const val getByIdRouting = "getById"
|
const val getByIdRouting = "getById"
|
||||||
const val containsRouting = "contains"
|
const val containsRouting = "contains"
|
||||||
const val countRouting = "count"
|
|
||||||
|
@@ -3,8 +3,8 @@ package dev.inmo.micro_utils.repos.ktor.common.key_value
|
|||||||
const val getRoute = "get"
|
const val getRoute = "get"
|
||||||
const val valuesRoute = "values"
|
const val valuesRoute = "values"
|
||||||
const val keysRoute = "keys"
|
const val keysRoute = "keys"
|
||||||
const val containsRoute = "contains"
|
const val containsRoute = dev.inmo.micro_utils.repos.ktor.common.containsRoute
|
||||||
const val countRoute = "count"
|
const val countRoute = dev.inmo.micro_utils.repos.ktor.common.countRoute
|
||||||
|
|
||||||
const val onNewValueRoute = "onNewValue"
|
const val onNewValueRoute = "onNewValue"
|
||||||
const val onValueRemovedRoute = "onValueRemoved"
|
const val onValueRemovedRoute = "onValueRemoved"
|
||||||
|
@@ -5,7 +5,7 @@ const val keysRoute = "keys"
|
|||||||
const val containsByKeyRoute = "containsByKey"
|
const val containsByKeyRoute = "containsByKey"
|
||||||
const val containsByKeyValueRoute = "containsByKeyValue"
|
const val containsByKeyValueRoute = "containsByKeyValue"
|
||||||
const val countByKeyRoute = "countByKey"
|
const val countByKeyRoute = "countByKey"
|
||||||
const val countRoute = "count"
|
const val countRoute = dev.inmo.micro_utils.repos.ktor.common.countRoute
|
||||||
|
|
||||||
const val onNewValueRoute = "onNewValue"
|
const val onNewValueRoute = "onNewValue"
|
||||||
const val onValueRemovedRoute = "onValueRemoved"
|
const val onValueRemovedRoute = "onValueRemoved"
|
||||||
|
89
repos/ktor/common/src/jvmTest/kotlin/CRUDTests.kt
Normal file
89
repos/ktor/common/src/jvmTest/kotlin/CRUDTests.kt
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.client.crud.KtorCRUDRepoClient
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.server.crud.configureCRUDRepoRoutes
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.plugins.logging.Logging
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.serialization.kotlinx.KotlinxWebsocketSerializationConverter
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.cio.CIO
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.server.routing.routing
|
||||||
|
import io.ktor.server.websocket.WebSockets
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class CRUDTests {
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@Test
|
||||||
|
fun testCRUDFunctions() {
|
||||||
|
runTest {
|
||||||
|
val map = mutableMapOf<Int, ComplexData>()
|
||||||
|
val repo = MapCRUDRepo<ComplexData, Int, SimpleData>(
|
||||||
|
map,
|
||||||
|
{ newValue, id, oldValue ->
|
||||||
|
oldValue.copy(title = newValue.title)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
size to ComplexData(size, title = it.title)
|
||||||
|
}
|
||||||
|
val server = io.ktor.server.engine.embeddedServer(
|
||||||
|
CIO,
|
||||||
|
23456,
|
||||||
|
"127.0.0.1"
|
||||||
|
) {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json()
|
||||||
|
}
|
||||||
|
install(WebSockets) {
|
||||||
|
contentConverter = KotlinxWebsocketSerializationConverter(Json)
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
configureCRUDRepoRoutes(
|
||||||
|
repo
|
||||||
|
) {
|
||||||
|
it.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start(false)
|
||||||
|
val client = HttpClient {
|
||||||
|
install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) {
|
||||||
|
json()
|
||||||
|
}
|
||||||
|
install(Logging)
|
||||||
|
install(io.ktor.client.plugins.websocket.WebSockets) {
|
||||||
|
contentConverter = KotlinxWebsocketSerializationConverter(Json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val crudClient = KtorCRUDRepoClient<ComplexData, Int, SimpleData>(
|
||||||
|
"http://127.0.0.1:23456",
|
||||||
|
client,
|
||||||
|
ContentType.Application.Json
|
||||||
|
) {
|
||||||
|
it.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val created = crudClient.create(SimpleData("Example")).single()
|
||||||
|
assertEquals(map.size, 1)
|
||||||
|
assertEquals(map.size.toLong(), crudClient.count())
|
||||||
|
assertEquals(1, crudClient.count())
|
||||||
|
assertEquals(map.getValue(map.keys.first()), created)
|
||||||
|
|
||||||
|
val updated = crudClient.update(created.id, SimpleData("Example2"))
|
||||||
|
assertEquals(map.size, 1)
|
||||||
|
assertEquals(map.size.toLong(), crudClient.count())
|
||||||
|
assertEquals(1, crudClient.count())
|
||||||
|
assertEquals(map.getValue(map.keys.first()), updated)
|
||||||
|
|
||||||
|
crudClient.deleteById(created.id)
|
||||||
|
assertEquals(map.size, 0)
|
||||||
|
assertEquals(map.size.toLong(), crudClient.count())
|
||||||
|
assertEquals(0, crudClient.count())
|
||||||
|
server.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
repos/ktor/common/src/jvmTest/kotlin/ComplexData.kt
Normal file
10
repos/ktor/common/src/jvmTest/kotlin/ComplexData.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import com.benasher44.uuid.uuid4
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ComplexData(
|
||||||
|
val id: Int,
|
||||||
|
val simple: SimpleData = SimpleData(),
|
||||||
|
val simples: List<SimpleData> = (0 until 100).map { SimpleData(("$it")) },
|
||||||
|
val title: String = uuid4().toString()
|
||||||
|
)
|
139
repos/ktor/common/src/jvmTest/kotlin/KVTests.kt
Normal file
139
repos/ktor/common/src/jvmTest/kotlin/KVTests.kt
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.client.key.value.KtorKeyValueRepoClient
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.server.key.value.configureKeyValueRepoRoutes
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.plugins.logging.Logging
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.serialization.kotlinx.KotlinxWebsocketSerializationConverter
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.cio.CIO
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.server.routing.routing
|
||||||
|
import io.ktor.server.websocket.WebSockets
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class KVTests {
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@Test
|
||||||
|
fun testKVFunctions() {
|
||||||
|
runTest {
|
||||||
|
val map = mutableMapOf<Int, ComplexData>()
|
||||||
|
val repo = MapKeyValueRepo<Int, ComplexData>(map)
|
||||||
|
val server = io.ktor.server.engine.embeddedServer(
|
||||||
|
CIO,
|
||||||
|
23456,
|
||||||
|
"127.0.0.1"
|
||||||
|
) {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json()
|
||||||
|
}
|
||||||
|
install(WebSockets) {
|
||||||
|
contentConverter = KotlinxWebsocketSerializationConverter(Json)
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
configureKeyValueRepoRoutes(
|
||||||
|
repo,
|
||||||
|
Int.serializer(),
|
||||||
|
ComplexData.serializer(),
|
||||||
|
Json {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.start(false)
|
||||||
|
val client = HttpClient {
|
||||||
|
install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) {
|
||||||
|
json()
|
||||||
|
}
|
||||||
|
install(Logging)
|
||||||
|
install(io.ktor.client.plugins.websocket.WebSockets) {
|
||||||
|
contentConverter = KotlinxWebsocketSerializationConverter(Json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val crudClient = KtorKeyValueRepoClient<Int, ComplexData>(
|
||||||
|
"http://127.0.0.1:23456",
|
||||||
|
client,
|
||||||
|
ContentType.Application.Json,
|
||||||
|
Int.serializer(),
|
||||||
|
ComplexData.serializer(),
|
||||||
|
Json
|
||||||
|
)
|
||||||
|
|
||||||
|
val dataInOneKey = ComplexData(1, title = "Example1")
|
||||||
|
val dataInMultipleKeys = ComplexData(2, title = "Example2")
|
||||||
|
val repeatCount = 3
|
||||||
|
|
||||||
|
val dataList = listOf(
|
||||||
|
1 to dataInOneKey
|
||||||
|
) + (0 until repeatCount).map {
|
||||||
|
(it + 2) to dataInMultipleKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
dataList.forEachIndexed { i, (id, data) ->
|
||||||
|
crudClient.set(id, data)
|
||||||
|
assertEquals(map.size, i + 1)
|
||||||
|
assertEquals(map.size.toLong(), crudClient.count())
|
||||||
|
assertEquals(i + 1L, crudClient.count())
|
||||||
|
dataList.take(i + 1).forEach { (id, data) ->
|
||||||
|
assertEquals(data, map[id])
|
||||||
|
assertEquals(data, crudClient.get(id))
|
||||||
|
assertEquals(map[id], crudClient.get(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataList.forEach { (id, data) ->
|
||||||
|
assertTrue(crudClient.contains(id))
|
||||||
|
assertEquals(data, crudClient.get(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
dataList.mapNotNull { if (it.second == dataInMultipleKeys) it.first else null },
|
||||||
|
getAllWithNextPaging(firstPageWithOneElementPagination) {
|
||||||
|
crudClient.keys(dataInMultipleKeys, it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
dataList.mapNotNull { if (it.second == dataInOneKey) it.first else null },
|
||||||
|
getAllWithNextPaging(firstPageWithOneElementPagination) {
|
||||||
|
crudClient.keys(dataInOneKey, it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
dataList.map { it.first },
|
||||||
|
getAllWithNextPaging(firstPageWithOneElementPagination) {
|
||||||
|
crudClient.keys(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
dataList.map { it.second },
|
||||||
|
getAllWithNextPaging(firstPageWithOneElementPagination) {
|
||||||
|
crudClient.values(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(dataList.size.toLong(), crudClient.count())
|
||||||
|
|
||||||
|
crudClient.unsetWithValues(dataInMultipleKeys)
|
||||||
|
assertEquals(
|
||||||
|
dataList.filter { it.second == dataInOneKey }.size.toLong(),
|
||||||
|
crudClient.count()
|
||||||
|
)
|
||||||
|
|
||||||
|
crudClient.unset(dataList.first { it.second == dataInOneKey }.first)
|
||||||
|
assertEquals(
|
||||||
|
0,
|
||||||
|
crudClient.count()
|
||||||
|
)
|
||||||
|
|
||||||
|
server.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
repos/ktor/common/src/jvmTest/kotlin/KVsTests.kt
Normal file
140
repos/ktor/common/src/jvmTest/kotlin/KVsTests.kt
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.client.key.values.KtorKeyValuesRepoClient
|
||||||
|
import dev.inmo.micro_utils.repos.ktor.server.key.values.configureKeyValuesRepoRoutes
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.plugins.logging.Logging
|
||||||
|
import io.ktor.http.ContentType
|
||||||
|
import io.ktor.serialization.kotlinx.KotlinxWebsocketSerializationConverter
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import io.ktor.server.application.install
|
||||||
|
import io.ktor.server.cio.CIO
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
import io.ktor.server.routing.routing
|
||||||
|
import io.ktor.server.websocket.WebSockets
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class KVsTests {
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@Test
|
||||||
|
fun testKVsFunctions() {
|
||||||
|
runTest {
|
||||||
|
val map = mutableMapOf<Int, MutableList<ComplexData>>()
|
||||||
|
val repo = MapKeyValuesRepo(map)
|
||||||
|
val server = io.ktor.server.engine.embeddedServer(
|
||||||
|
CIO,
|
||||||
|
23456,
|
||||||
|
"127.0.0.1"
|
||||||
|
) {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json()
|
||||||
|
}
|
||||||
|
install(WebSockets) {
|
||||||
|
contentConverter = KotlinxWebsocketSerializationConverter(Json)
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
configureKeyValuesRepoRoutes(
|
||||||
|
repo,
|
||||||
|
Int.serializer(),
|
||||||
|
ComplexData.serializer(),
|
||||||
|
Json {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.start(false)
|
||||||
|
val client = HttpClient {
|
||||||
|
install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) {
|
||||||
|
json()
|
||||||
|
}
|
||||||
|
install(Logging)
|
||||||
|
install(io.ktor.client.plugins.websocket.WebSockets) {
|
||||||
|
contentConverter = KotlinxWebsocketSerializationConverter(Json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val crudClient = KtorKeyValuesRepoClient(
|
||||||
|
"http://127.0.0.1:23456",
|
||||||
|
client,
|
||||||
|
ContentType.Application.Json,
|
||||||
|
Int.serializer(),
|
||||||
|
ComplexData.serializer(),
|
||||||
|
Json
|
||||||
|
)
|
||||||
|
|
||||||
|
val dataInOneKey = ComplexData(1, title = "Example1")
|
||||||
|
val dataInMultipleKeys = ComplexData(2, title = "Example2")
|
||||||
|
val repeatCount = 3
|
||||||
|
|
||||||
|
val dataList = listOf(
|
||||||
|
1 to listOf(dataInOneKey)
|
||||||
|
) + (0 until repeatCount).map {
|
||||||
|
(it + 2) to listOf(dataInMultipleKeys)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataList.forEachIndexed { i, (id, data) ->
|
||||||
|
crudClient.set(id, data)
|
||||||
|
assertEquals(i + 1, map.size)
|
||||||
|
assertEquals(map.size.toLong(), crudClient.count())
|
||||||
|
assertEquals(i + 1L, crudClient.count())
|
||||||
|
dataList.take(i + 1).forEach { (id, data) ->
|
||||||
|
assertContentEquals(data, map[id])
|
||||||
|
assertContentEquals(data, crudClient.getAll(id))
|
||||||
|
assertContentEquals(map[id], crudClient.getAll(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataList.forEach { (key, data) ->
|
||||||
|
assertTrue(crudClient.contains(key))
|
||||||
|
assertContentEquals(data, crudClient.getAll(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
dataList.mapNotNull { if (it.second.contains(dataInMultipleKeys)) it.first else null },
|
||||||
|
getAllWithNextPaging(firstPageWithOneElementPagination) {
|
||||||
|
crudClient.keys(dataInMultipleKeys, it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
dataList.mapNotNull { if (it.second.contains(dataInOneKey)) it.first else null },
|
||||||
|
getAllWithNextPaging(firstPageWithOneElementPagination) {
|
||||||
|
crudClient.keys(dataInOneKey, it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
dataList.map { it.first },
|
||||||
|
getAllWithNextPaging(firstPageWithOneElementPagination) {
|
||||||
|
crudClient.keys(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
dataList.map { it.first },
|
||||||
|
getAllWithNextPaging(firstPageWithOneElementPagination) {
|
||||||
|
crudClient.keys(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(dataList.size.toLong(), crudClient.count())
|
||||||
|
|
||||||
|
crudClient.remove(dataList.filter { it.second.contains(dataInMultipleKeys) })
|
||||||
|
println(map)
|
||||||
|
assertEquals(
|
||||||
|
dataList.filter { it.second.contains(dataInOneKey) }.size.toLong(),
|
||||||
|
crudClient.count()
|
||||||
|
)
|
||||||
|
|
||||||
|
crudClient.remove(dataList.filter { it.second.contains(dataInOneKey) })
|
||||||
|
assertEquals(
|
||||||
|
0,
|
||||||
|
crudClient.count()
|
||||||
|
)
|
||||||
|
|
||||||
|
server.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
repos/ktor/common/src/jvmTest/kotlin/SimpleData.kt
Normal file
7
repos/ktor/common/src/jvmTest/kotlin/SimpleData.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import com.benasher44.uuid.uuid4
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SimpleData(
|
||||||
|
val title: String = uuid4().toString()
|
||||||
|
)
|
198
repos/ktor/crud.yml
Normal file
198
repos/ktor/crud.yml
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
openapi: "3.0.0"
|
||||||
|
info:
|
||||||
|
description: "This is a template for the CRUD repositories from [microutils](https://github.com/InsanusMokrassar/MicroUtils/tree/master/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud)"
|
||||||
|
version: "0.11.3"
|
||||||
|
title: "CRUD Repo"
|
||||||
|
contact:
|
||||||
|
email: "ovsyannikov.alexey95@gmail.com"
|
||||||
|
tags:
|
||||||
|
- name: "Read"
|
||||||
|
description: "Operations with `get` request in most cases"
|
||||||
|
- name: "Write"
|
||||||
|
description: "Operations with `post` request in most cases"
|
||||||
|
|
||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
IdInQuery:
|
||||||
|
in: "query"
|
||||||
|
name: "id"
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Key"
|
||||||
|
PaginationInQueryPage:
|
||||||
|
in: "query"
|
||||||
|
name: "ppage"
|
||||||
|
description: "Page of pagination"
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: false
|
||||||
|
PaginationInQuerySize:
|
||||||
|
in: "query"
|
||||||
|
name: "psize"
|
||||||
|
description: "Size of each page in pagination"
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: false
|
||||||
|
schemas:
|
||||||
|
|
||||||
|
Key:
|
||||||
|
type: integer
|
||||||
|
description: "REWRITE THIS TYPE AS KEY IN SWAGGER FILE"
|
||||||
|
Value:
|
||||||
|
type: integer
|
||||||
|
description: "REWRITE THIS TYPE AS VALUE IN SWAGGER FILE"
|
||||||
|
NewValue:
|
||||||
|
type: integer
|
||||||
|
description: "REWRITE THIS TYPE AS NEW VALUE IN SWAGGER FILE"
|
||||||
|
Pair:
|
||||||
|
type: object
|
||||||
|
description: "Pair of objects"
|
||||||
|
properties:
|
||||||
|
first:
|
||||||
|
second:
|
||||||
|
PaginationResult:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
page:
|
||||||
|
type: integer
|
||||||
|
description: "Page of pagination"
|
||||||
|
pagesNumber:
|
||||||
|
type: integer
|
||||||
|
description: "Count of pages with the size from this pagination"
|
||||||
|
size:
|
||||||
|
type: integer
|
||||||
|
description: "Size of each page in pagination"
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
description: "Array of all elements on that page. Size of pagination and size of array can be different and it can be interpreted like current page is the last one"
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/getByPagination:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "Read"
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/PaginationInQueryPage"
|
||||||
|
- $ref: "#/components/parameters/PaginationInQuerySize"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Pagination with elements"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/PaginationResult"
|
||||||
|
- properties:
|
||||||
|
results:
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Value"
|
||||||
|
/getById:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "Read"
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/IdInQuery"
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Result object"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Value"
|
||||||
|
"204":
|
||||||
|
description: "No value by id"
|
||||||
|
/contains:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "Read"
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/IdInQuery"
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Object with id availability in repo"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
/count:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "Read"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Amount of objects in repo"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
|
||||||
|
|
||||||
|
/create:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- "Write"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/NewValue"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Objects has been created and saved"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Value"
|
||||||
|
/update:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- "Write"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/Pair"
|
||||||
|
- properties:
|
||||||
|
first:
|
||||||
|
$ref: "#/components/schemas/Key"
|
||||||
|
second:
|
||||||
|
$ref: "#/components/schemas/NewValue"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Objects has been updated"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Value"
|
||||||
|
/deleteById:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- "Write"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Key"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Objects has been updated"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Value"
|
201
repos/ktor/kv.yml
Normal file
201
repos/ktor/kv.yml
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
openapi: "3.0.0"
|
||||||
|
info:
|
||||||
|
description: "This is a template for the KeyValue repositories from [microutils](https://github.com/InsanusMokrassar/MicroUtils/tree/master/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/key/value)"
|
||||||
|
version: "0.11.1"
|
||||||
|
title: "KeyValue Repo"
|
||||||
|
contact:
|
||||||
|
email: "ovsyannikov.alexey95@gmail.com"
|
||||||
|
tags:
|
||||||
|
- name: "Read"
|
||||||
|
description: "Operations with `get` request in most cases"
|
||||||
|
- name: "Write"
|
||||||
|
description: "Operations with `post` request in most cases"
|
||||||
|
|
||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
KeyInQuery:
|
||||||
|
in: "query"
|
||||||
|
name: "key"
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Key"
|
||||||
|
PaginationInQueryPage:
|
||||||
|
in: "query"
|
||||||
|
name: "ppage"
|
||||||
|
description: "Page of pagination"
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: false
|
||||||
|
PaginationInQuerySize:
|
||||||
|
in: "query"
|
||||||
|
name: "psize"
|
||||||
|
description: "Size of each page in pagination"
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: false
|
||||||
|
ReversedInQuery:
|
||||||
|
in: "query"
|
||||||
|
name: "reversed"
|
||||||
|
description: "If passed, will tell to reverse the result pages"
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
required: false
|
||||||
|
ValueInQuery:
|
||||||
|
in: "query"
|
||||||
|
name: "value"
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Value"
|
||||||
|
schemas:
|
||||||
|
Key:
|
||||||
|
type: integer
|
||||||
|
description: "REWRITE THIS TYPE AS KEY IN SWAGGER FILE"
|
||||||
|
Value:
|
||||||
|
type: integer
|
||||||
|
description: "REWRITE THIS TYPE AS VALUE IN SWAGGER FILE"
|
||||||
|
PaginationResult:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
page:
|
||||||
|
type: integer
|
||||||
|
description: "Page of pagination"
|
||||||
|
pagesNumber:
|
||||||
|
type: integer
|
||||||
|
description: "Count of pages with the size from this pagination"
|
||||||
|
size:
|
||||||
|
type: integer
|
||||||
|
description: "Size of each page in pagination"
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
description: "Array of all elements on that page. Size of pagination and size of array can be different and it can be interpreted like current page is the last one"
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/get:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "Read"
|
||||||
|
parameters:
|
||||||
|
- allOf:
|
||||||
|
- $ref: "#/components/parameters/KeyInQuery"
|
||||||
|
- required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Element by key"
|
||||||
|
content:
|
||||||
|
"*/*":
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Value"
|
||||||
|
"204":
|
||||||
|
description: "No value by id"
|
||||||
|
/values:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "Read"
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/PaginationInQueryPage"
|
||||||
|
- $ref: "#/components/parameters/PaginationInQuerySize"
|
||||||
|
- $ref: "#/components/parameters/ReversedInQuery"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Pagination with elements"
|
||||||
|
content:
|
||||||
|
"*/*":
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/PaginationResult"
|
||||||
|
- properties:
|
||||||
|
results:
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Value"
|
||||||
|
/keys:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "Read"
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/PaginationInQueryPage"
|
||||||
|
- $ref: "#/components/parameters/PaginationInQuerySize"
|
||||||
|
- $ref: "#/components/parameters/ReversedInQuery"
|
||||||
|
- $ref: "#/components/parameters/ValueInQuery"
|
||||||
|
required: false
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Pagination with elements"
|
||||||
|
content:
|
||||||
|
"*/*":
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/PaginationResult"
|
||||||
|
- properties:
|
||||||
|
results:
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Key"
|
||||||
|
/contains:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "Read"
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/KeyInQuery"
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Object with id availability in repo"
|
||||||
|
content:
|
||||||
|
"*/*":
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
/count:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- "Read"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Amount of objects in repo"
|
||||||
|
content:
|
||||||
|
"*/*":
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
|
||||||
|
|
||||||
|
/set:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- "Write"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
"*/*":
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
$ref: "#/components/schemas/Value"
|
||||||
|
description: "Map with new elements to set. Use keys as a keys of this map"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Will return 200 if everything has been completed ok"
|
||||||
|
/unset:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- "Write"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
"*/*":
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Key"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Objects with keys from body has been unset"
|
||||||
|
/unsetWithValues:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- "Write"
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
"*/*":
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Value"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "Objects with values from body has been unset"
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user