Compare commits

..

7 Commits

Author SHA1 Message Date
216c03205c Revert "add Handlers subproject"
This reverts commit d1021d283a.
2022-04-26 13:23:46 +06:00
ab112aa7a4 Revert "HandlersRegistrar now is open"
This reverts commit 3ac56dcfd3.
2022-04-25 22:04:36 +06:00
d85b3d0da9 Revert "now HandlersRegisrar properties are open"
This reverts commit 67b9a03366.
2022-04-25 22:04:33 +06:00
67b9a03366 now HandlersRegisrar properties are open 2022-04-25 22:01:04 +06:00
3ac56dcfd3 HandlersRegistrar now is open 2022-04-25 21:55:38 +06:00
d1021d283a add Handlers subproject 2022-04-25 21:51:42 +06:00
97ed973cb5 start 0.9.25 2022-04-25 21:50:41 +06:00
147 changed files with 1289 additions and 4529 deletions

View File

@@ -15,7 +15,7 @@ jobs:
continue-on-error: true continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Build - name: Build
run: ./gradlew build && ./gradlew dokkaHtml run: ./gradlew dokkaHtml
- name: Publish KDocs - name: Publish KDocs
uses: peaceiris/actions-gh-pages@v3 uses: peaceiris/actions-gh-pages@v3
with: with:

View File

@@ -1,8 +1,8 @@
name: Build name: Publish package to GitHub Packages
on: [push] on: [push]
jobs: jobs:
build: publishing:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -20,9 +20,9 @@ jobs:
mv gradle.properties.tmp gradle.properties mv gradle.properties.tmp gradle.properties
- name: Build - name: Build
run: ./gradlew build run: ./gradlew build
# - name: Publish - name: Publish
# continue-on-error: true continue-on-error: true
# run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
# env: env:
# GITHUBPACKAGES_USER: ${{ github.actor }} GITHUBPACKAGES_USER: ${{ github.actor }}
# GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,201 +1,6 @@
# Changelog # Changelog
## 0.12.1 ## 0.9.25
* `Versions`:
* `Ktor`: `2.0.3` -> `2.1.0`
## 0.12.0
**OLD DEPRECATIONS HAVE BEEN REMOVED**
**MINIMAL ANDROID API HAS BEEN ENLARGED UP TO API 21 (Android 5.0)**
* `Versions`
* `Kotlin`: `1.6.21` -> `1.7.0`
* `Coroutines`: `1.6.3` -> `1.6.4`
* `Exposed`: `0.38.2` -> `0.39.2`
* `Compose`: `1.2.0-alpha01-dev729` -> `1.2.0-alpha01-dev753`
* `Klock`: `2.7.0` -> `3.0.0`
* `uuid`: `0.4.1` -> `0.5.0`
* `Android Core KTX`: `1.7.0` -> `1.8.0`
* `Android AppCompat`: `1.4.1` -> `1.4.2`
* `Ktor`:
* All previously standard functions related to work with binary data by default have been deprecated
## 0.11.14
* `Pagination`:
* `PaginationResult` got new field `objectsNumber` which by default is a times between `pagesNumber` and `size`
## 0.11.13
* `Versions`:
* `Coroutines`: `1.6.3` -> `1.6.4`
* `Compose`: `1.2.0-alpha01-dev629` -> `1.2.0-alpha01-dev731`
## 0.11.12
* `Repos`:
* `Common`:
* `JVM`:
* Fixes in `ReadFileKeyValueRepo` methods (`values`/`keys`)
## 0.11.11
* `Crypto`:
* `hmacSha256` has been deprecated
* `Ktor`:
* `Client`:
* `BodyPair` has been deprecated
* `Repos`:
* `Cache`:
* New interface `CacheRepo`
* New interface `FullCacheRepo`
* `actualize*` methods inside of full cache repos now open for overriding
## 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
* `Versions`:
* `Kotlin`: `1.6.10` -> `1.6.21`
* `Compose`: `1.1.1` -> `1.2.0-alpha01-dev675`
* `Exposed`: `0.37.3` -> `0.38.2`
* `Ktor`: `1.6.8` -> `2.0.0`
* `Dokka`: `1.6.10` -> `1.6.21`
## 0.9.24 ## 0.9.24

View File

@@ -21,7 +21,6 @@ allprojects {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
google() google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
} }
// temporal crutch until legacy tests will be stabled or legacy target will be removed // temporal crutch until legacy tests will be stabled or legacy target will be removed

View File

@@ -1,5 +1,3 @@
@file:Suppress("OPT_IN_IS_NOT_ENABLED")
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
@RequiresOptIn( @RequiresOptIn(

View File

@@ -43,7 +43,6 @@ private inline fun <T> performChanges(
if (oldOneEqualToNewObject || newOneEqualToOldObject) { if (oldOneEqualToNewObject || newOneEqualToOldObject) {
changedList.addAll( changedList.addAll(
potentialChanges.take(i).mapNotNull { potentialChanges.take(i).mapNotNull {
@Suppress("UNCHECKED_CAST")
if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null
} }
) )
@@ -122,10 +121,7 @@ fun <T> Iterable<T>.calculateDiff(
when { when {
oldObject === newObject || (oldObject == newObject && !strictComparison) -> { oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
changedObjects.addAll(potentiallyChangedObjects.map { changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> })
@Suppress("UNCHECKED_CAST")
it as Pair<IndexedValue<T>, IndexedValue<T>>
})
potentiallyChangedObjects.clear() potentiallyChangedObjects.clear()
} }
else -> { else -> {

View File

@@ -1,5 +1,3 @@
@file:Suppress("unused", "NOTHING_TO_INLINE")
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
import kotlinx.serialization.* import kotlinx.serialization.*
@@ -23,18 +21,26 @@ import kotlinx.serialization.encoding.*
sealed interface Either<T1, T2> { sealed interface Either<T1, T2> {
val optionalT1: Optional<T1> val optionalT1: Optional<T1>
val optionalT2: Optional<T2> val optionalT2: Optional<T2>
@Deprecated("Use optionalT1 instead", ReplaceWith("optionalT1"))
val t1OrNull: T1? val t1: T1?
get() = optionalT1.dataOrNull() get() = optionalT1.dataOrNull()
val t2OrNull: T2? @Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2"))
val t2: T2?
get() = optionalT2.dataOrNull() get() = optionalT2.dataOrNull()
companion object {
fun <T1, T2> serializer(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
): KSerializer<Either<T1, T2>> = EitherSerializer(t1Serializer, t2Serializer)
}
} }
class EitherSerializer<T1, T2>( class EitherSerializer<T1, T2>(
t1Serializer: KSerializer<T1>, t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>, t2Serializer: KSerializer<T2>,
) : KSerializer<Either<T1, T2>> { ) : KSerializer<Either<T1, T2>> {
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
override val descriptor: SerialDescriptor = buildSerialDescriptor( override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer", "TypedSerializer",
SerialKind.CONTEXTUAL SerialKind.CONTEXTUAL
@@ -97,7 +103,7 @@ class EitherSerializer<T1, T2>(
*/ */
@Serializable @Serializable
data class EitherFirst<T1, T2>( data class EitherFirst<T1, T2>(
val t1: T1 override val t1: T1
) : Either<T1, T2> { ) : Either<T1, T2> {
override val optionalT1: Optional<T1> = t1.optional override val optionalT1: Optional<T1> = t1.optional
override val optionalT2: Optional<T2> = Optional.absent() override val optionalT2: Optional<T2> = Optional.absent()
@@ -108,7 +114,7 @@ data class EitherFirst<T1, T2>(
*/ */
@Serializable @Serializable
data class EitherSecond<T1, T2>( data class EitherSecond<T1, T2>(
val t2: T2 override val t2: T2
) : Either<T1, T2> { ) : Either<T1, T2> {
override val optionalT1: Optional<T1> = Optional.absent() override val optionalT1: Optional<T1> = Optional.absent()
override val optionalT2: Optional<T2> = t2.optional override val optionalT2: Optional<T2> = t2.optional

View File

@@ -1,53 +0,0 @@
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)

View File

@@ -41,18 +41,10 @@ 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)
*/ */
inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply { inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
@OptIn(Warning::class)
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
} }
@@ -60,7 +52,6 @@ inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply
* Will call [block] when data presented ([Optional.dataPresented] == true) * Will call [block] when data presented ([Optional.dataPresented] == true)
*/ */
inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run { inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
@OptIn(Warning::class)
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
} }
@@ -68,7 +59,6 @@ inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
* Will call [block] when data absent ([Optional.dataPresented] == false) * Will call [block] when data absent ([Optional.dataPresented] == false)
*/ */
inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply { inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
@OptIn(Warning::class)
if (!dataPresented) { block() } if (!dataPresented) { block() }
} }
@@ -76,22 +66,27 @@ inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
* Will call [block] when data presented ([Optional.dataPresented] == true) * Will call [block] when data presented ([Optional.dataPresented] == true)
*/ */
inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run { inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
@OptIn(Warning::class) if (!dataPresented) { @Suppress("UNCHECKED_CAST") block() } else null
if (!dataPresented) { block() } else null
} }
/** /**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
*/ */
fun <T> Optional<T>.dataOrNull() = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null fun <T> Optional<T>.dataOrNull() = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
/** /**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise
*/ */
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
/** /**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it * Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/ */
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block() inline fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/
@Deprecated("dataOrElse now is inline", ReplaceWith("dataOrElse", "dev.inmo.micro_utils.common.dataOrElse"))
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()

View File

@@ -32,7 +32,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) { for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) { for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) { if (i + count > oldList.lastIndex) {
continue continue
} }
@@ -55,7 +55,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (step in oldList.indices) { for (step in oldList.indices) {
for ((i, _) in withIndex) { for ((i, v) in withIndex) {
val mutable = oldList.toMutableList() val mutable = oldList.toMutableList()
val changes = ( val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step) if (step == 0) i until oldList.size else (i until oldList.size step step)
@@ -104,7 +104,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) { for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) { for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) { if (i + count > oldList.lastIndex) {
continue continue
} }
@@ -129,20 +129,15 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (step in oldList.indices) { for (step in oldList.indices) {
for ((i, _) in withIndex) { for ((i, v) in withIndex) {
val mutable = oldList.toMutableList() val mutable = oldList.toMutableList()
val changes = (
val newList = if (step == 0) { if (step == 0) i until oldList.size else (i until oldList.size step step)
i until oldList.size ).map { index ->
} else {
i until oldList.size step step
}
newList.forEach { index ->
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also { IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
mutable[index] = it.value mutable[index] = it.value
} }
} }
val mutableOldList = oldList.toMutableList() val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable) mutableOldList.applyDiff(mutable)
assertEquals( assertEquals(

View File

@@ -1,12 +0,0 @@
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

View File

@@ -1,58 +0,0 @@
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"
}
}
}

View File

@@ -1,26 +0,0 @@
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)

View File

@@ -1,35 +0,0 @@
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)

View File

@@ -16,7 +16,6 @@ fun <T> Flow<T>.toMutableState(
return state return state
} }
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.toMutableState( inline fun <T> StateFlow<T>.toMutableState(
scope: CoroutineScope scope: CoroutineScope
): MutableState<T> = toMutableState(value, scope) ): MutableState<T> = toMutableState(value, scope)

View File

@@ -6,12 +6,11 @@ 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<T> private sealed interface AccumulatorFlowStep
private data class DataRetrievedAccumulatorFlowStep<T>(val data: T) : AccumulatorFlowStep<T> private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep
private data class SubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T> private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
private data class UnsubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T> private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
/** /**
* 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:
@@ -27,12 +26,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<T>>() private val channelsForBroadcast = mutableListOf<Channel<Any>>()
private val channelsMutex = Mutex() private val channelsMutex = Mutex()
private val steps = subscope.actor<AccumulatorFlowStep<T>> { step -> private val steps = subscope.actor<AccumulatorFlowStep> { step ->
when (step) { when (step) {
is DataRetrievedAccumulatorFlowStep -> { is DataRetrievedAccumulatorFlowStep -> {
if (activeData.firstOrNull() === step.data) { if (activeData.first() === step.data) {
dataMutex.withLock { dataMutex.withLock {
activeData.removeFirst() activeData.removeFirst()
} }
@@ -43,7 +42,7 @@ class AccumulatorFlow<T>(
dataMutex.withLock { dataMutex.withLock {
val dataToSend = activeData.toList() val dataToSend = activeData.toList()
safelyWithoutExceptions { safelyWithoutExceptions {
dataToSend.forEach { step.channel.send(it) } dataToSend.forEach { step.channel.send(it as Any) }
} }
} }
} }
@@ -59,29 +58,24 @@ class AccumulatorFlow<T>(
channelsMutex.withLock { channelsMutex.withLock {
channelsForBroadcast.forEach { channel -> channelsForBroadcast.forEach { channel ->
safelyWithResult { safelyWithResult {
channel.send(it) channel.send(it as Any)
} }
} }
} }
} }
override suspend fun collectSafely(collector: FlowCollector<T>) { override suspend fun collectSafely(collector: FlowCollector<T>) {
val channel = Channel<T>(Channel.UNLIMITED, BufferOverflow.SUSPEND) val channel = Channel<Any>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
steps.send(SubscribeAccumulatorFlowStep(channel)) steps.send(SubscribeAccumulatorFlowStep(channel))
val result = runCatchingSafely { for (data in channel) {
for (data in channel) { try {
val emitResult = runCatchingSafely { collector.emit(data as T)
collector.emit(data) steps.send(DataRetrievedAccumulatorFlowStep(data))
} } finally {
if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) { channel.cancel()
steps.send(DataRetrievedAccumulatorFlowStep(data)) steps.send(UnsubscribeAccumulatorFlowStep(channel))
}
emitResult.getOrThrow()
} }
} }
channel.cancel()
steps.send(UnsubscribeAccumulatorFlowStep(channel))
result.getOrThrow()
} }
} }

View File

@@ -1,15 +0,0 @@
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()

View File

@@ -1,15 +0,0 @@
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
)
}
}

View File

@@ -47,11 +47,24 @@ fun <I : O, O : State> CheckableHandlerHolder(
} }
) )
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
fun <I : O, O : State> StateHandlerHolder(
inputKlass: KClass<I>,
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(inputKlass, strict, delegateTo)
inline fun <reified I : O, O : State> CheckableHandlerHolder( inline fun <reified I : O, O : State> CheckableHandlerHolder(
strict: Boolean = false, strict: Boolean = false,
delegateTo: StatesHandler<I, O> delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(I::class, strict, delegateTo) ) = CheckableHandlerHolder(I::class, strict, delegateTo)
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
inline fun <reified I : O, O : State> StateHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(strict, delegateTo)
inline fun <reified I : O, O: State> StatesHandler<I, O>.holder( inline fun <reified I : O, O: State> StatesHandler<I, O>.holder(
strict: Boolean = true strict: Boolean = true
) = CheckableHandlerHolder<I, O>( ) = CheckableHandlerHolder<I, O>(

View File

@@ -3,10 +3,7 @@ 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
@@ -16,24 +13,13 @@ 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 launchStateHandling(state, handlers, defaultStateHandlingErrorHandler()) return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
} }
/** /**
@@ -52,9 +38,8 @@ 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>>
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler() ) = DefaultStatesMachine(statesManager, handlers)
) = DefaultStatesMachine(statesManager, handlers, onStateHandlingErrorHandler)
} }
} }
@@ -67,17 +52,12 @@ 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
*/ */
@@ -86,10 +66,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.endChain(state)
} else {
statesManager.update(state, newState) statesManager.update(state, newState)
} else {
statesManager.endChain(state)
} }
} }
@@ -119,7 +99,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.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) { 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) {
@@ -135,6 +115,10 @@ open class DefaultStatesMachine <T: State>(
} }
} }
} }
statesManager.getActiveStates().forEach {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
}
} }
/** /**

View File

@@ -1,8 +1,6 @@
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
@@ -22,11 +20,9 @@ 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>()
@@ -38,7 +34,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 (shouldReplaceJob(previousState, actualState)) { if (compare(previousState, actualState)) {
statesJobs[actualState] ?.cancel() statesJobs[actualState] ?.cancel()
} }
val job = previousState.mapOnPresented { val job = previousState.mapOnPresented {
@@ -52,7 +48,6 @@ open class DefaultUpdatableStatesMachine<T : State>(
statesJobs.remove( statesJobs.remove(
jobsStates[job] ?: return@withLock jobsStates[job] ?: return@withLock
) )
jobsStates.remove(job)
} }
} }
} }
@@ -68,6 +63,9 @@ open class DefaultUpdatableStatesMachine<T : State>(
*/ */
protected open suspend fun shouldReplaceJob(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new protected open suspend fun shouldReplaceJob(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
@Deprecated("Overwrite shouldReplaceJob instead")
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = shouldReplaceJob(previous, new)
override suspend fun updateChain(currentState: T, newState: T) { override suspend fun updateChain(currentState: T, newState: T) {
statesManager.update(currentState, newState) statesManager.update(currentState, newState)
} }

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.fsm.common.State
import kotlinx.coroutines.flow.*
/**
* Creates [DefaultStatesManager] with [InMemoryDefaultStatesManagerRepo]
*
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state
*/
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
fun <T: State> InMemoryStatesManager(
onStartContextsConflictResolver: suspend (old: T, new: T) -> Boolean = { _, _ -> true },
onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
) = DefaultStatesManager(onStartContextsConflictResolver = onStartContextsConflictResolver, onUpdateContextsConflictResolver = onUpdateContextsConflictResolver)

View File

@@ -1,6 +0,0 @@
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>

View File

@@ -1,6 +1,7 @@
import dev.inmo.micro_utils.fsm.common.* import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
import kotlinx.coroutines.* import kotlinx.coroutines.*
sealed interface TrafficLightState : State { sealed interface TrafficLightState : State {

View File

@@ -21,7 +21,7 @@ if (new File(projectDir, "secret.gradle").exists()) {
owner "InsanusMokrassar" owner "InsanusMokrassar"
repo "MicroUtils" repo "MicroUtils"
tagName "v${project.version}" tagName "${project.version}"
releaseName "${project.version}" releaseName "${project.version}"
targetCommitish "${project.version}" targetCommitish "${project.version}"

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.12.1 version=0.9.25
android_code_version=140 android_code_version=115

View File

@@ -1,30 +1,30 @@
[versions] [versions]
kt = "1.7.0" kt = "1.6.10"
kt-serialization = "1.4.0-RC" kt-serialization = "1.3.2"
kt-coroutines = "1.6.4" kt-coroutines = "1.6.1"
jb-compose = "1.2.0-alpha01-dev753" jb-compose = "1.1.1"
jb-exposed = "0.39.2" jb-exposed = "0.37.3"
jb-dokka = "1.7.0" jb-dokka = "1.6.10"
klock = "3.0.0" klock = "2.7.0"
uuid = "0.5.0" uuid = "0.4.0"
ktor = "2.1.0" ktor = "1.6.8"
gh-release = "2.4.1" gh-release = "2.2.12"
android-gradle = "7.2.2" android-gradle = "7.0.4"
dexcount = "3.1.0" dexcount = "3.0.1"
android-coreKtx = "1.8.0" android-coreKtx = "1.7.0"
android-recyclerView = "1.2.1" android-recyclerView = "1.2.1"
android-appCompat = "1.4.2" android-appCompat = "1.4.1"
android-espresso = "3.4.0" android-espresso = "3.3.0"
android-test = "1.1.3" android-test = "1.1.2"
android-props-minSdk = "21" android-props-minSdk = "19"
android-props-compileSdk = "32" android-props-compileSdk = "32"
android-props-buildTools = "32.0.0" android-props-buildTools = "32.0.0"
@@ -37,24 +37,15 @@ 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-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" }

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -1,62 +1,59 @@
package dev.inmo.micro_utils.ktor.client package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.plugins.pluginOrNull import io.ktor.client.features.websocket.ws
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.plugins.websocket.ws
import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.HttpRequestBuilder
import io.ktor.websocket.Frame import io.ktor.http.cio.websocket.Frame
import io.ktor.websocket.readBytes import io.ktor.http.cio.websocket.readBytes
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
/** /**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish * @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 * connection. Must return true in case if must be reconnected. By default always reconnecting
*/ */
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
inline fun <T> HttpClient.createStandardWebsocketFlow( inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String, url: String,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true }, crossinline checkReconnection: (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}, noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
crossinline conversation: suspend (StandardKtorSerialInputData) -> T crossinline conversation: suspend (StandardKtorSerialInputData) -> T
): Flow<T> { ): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
val correctedUrl = url.asCorrectWebSocketUrl val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow { return channelFlow {
val producerScope = this@channelFlow
do { do {
val reconnect = runCatchingSafely { val reconnect = try {
ws(correctedUrl, requestBuilder) { safely {
for (received in incoming) { ws(correctedUrl, requestBuilder) {
when (received) { for (received in incoming) {
is Frame.Binary -> send(conversation(received.data)) when (received) {
else -> { is Frame.Binary -> producerScope.send(conversation(received.readBytes()))
close() else -> {
return@ws producerScope.close()
return@ws
}
} }
} }
} }
} }
checkReconnection(null) checkReconnection(null)
}.getOrElse { e -> } catch (e: Throwable) {
checkReconnection(e).also { checkReconnection(e).also {
if (!it) { if (!it) {
close(e) producerScope.close(e)
} }
} }
} }
} while (reconnect && isActive) } while (reconnect)
if (!producerScope.isClosedForSend) {
if (isActive) { safely(
safely { { it.printStackTrace() }
close() ) {
producerScope.close()
} }
} }
} }
@@ -66,11 +63,10 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish * @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 * connection. Must return true in case if must be reconnected. By default always reconnecting
*/ */
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
inline fun <T> HttpClient.createStandardWebsocketFlow( inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String, url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}, noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow( ) = createStandardWebsocketFlow(

View File

@@ -1,15 +0,0 @@
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())
}

View File

@@ -1,103 +0,0 @@
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)

View File

@@ -4,15 +4,14 @@ import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.request.forms.* import io.ktor.client.request.forms.*
import io.ktor.client.statement.readBytes
import io.ktor.http.* import io.ktor.http.*
import io.ktor.utils.io.core.ByteReadPacket import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.serialization.* import kotlinx.serialization.*
@Deprecated("This class will be removed soon. It is now recommended to use built-in ktor features instead") typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
class UnifiedRequester( class UnifiedRequester(
val client: HttpClient = HttpClient(), val client: HttpClient = HttpClient(),
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
@@ -32,7 +31,7 @@ class UnifiedRequester(
suspend fun <BodyType, ResultType> unipost( suspend fun <BodyType, ResultType> unipost(
url: String, url: String,
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>, bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType> resultDeserializer: DeserializationStrategy<ResultType>
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat) ) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
@@ -51,7 +50,7 @@ class UnifiedRequester(
url: String, url: String,
filename: String, filename: String,
inputProvider: InputProvider, inputProvider: InputProvider,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>, otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>, resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*", mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {}, additionalParametersBuilder: FormBuilder.() -> Unit = {},
@@ -74,7 +73,7 @@ class UnifiedRequester(
suspend fun <BodyType, ResultType> unimultipart( suspend fun <BodyType, ResultType> unimultipart(
url: String, url: String,
mppFile: MPPFile, mppFile: MPPFile,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>, otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>, resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*", mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {}, additionalParametersBuilder: FormBuilder.() -> Unit = {},
@@ -86,32 +85,31 @@ class UnifiedRequester(
fun <T> createStandardWebsocketFlow( fun <T> createStandardWebsocketFlow(
url: String, url: String,
checkReconnection: suspend (Throwable?) -> Boolean, checkReconnection: (Throwable?) -> Boolean,
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {}, requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder) ) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat, requestBuilder)
fun <T> createStandardWebsocketFlow( fun <T> createStandardWebsocketFlow(
url: String, url: String,
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {}, requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder) ) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
} }
@Deprecated("This property will be removed soon. It is now recommended to use built-in ktor features instead")
val defaultRequester = UnifiedRequester() val defaultRequester = UnifiedRequester()
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.uniget( suspend fun <ResultType> HttpClient.uniget(
url: String, url: String,
resultDeserializer: DeserializationStrategy<ResultType>, resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = get(url).let { ) = get<StandardKtorSerialInputData>(
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) url
).let {
serialFormat.decodeDefault(resultDeserializer, it)
} }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
fun <T> SerializationStrategy<T>.encodeUrlQueryValue( fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
value: T, value: T,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
@@ -120,21 +118,17 @@ fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
value value
) )
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unipost( suspend fun <BodyType, ResultType> HttpClient.unipost(
url: String, url: String,
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>, bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>, resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = post(url) { ) = post<StandardKtorSerialInputData>(url) {
setBody( body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
)
}.let { }.let {
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) serialFormat.decodeDefault(resultDeserializer, it)
} }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.unimultipart( suspend fun <ResultType> HttpClient.unimultipart(
url: String, url: String,
filename: String, filename: String,
@@ -145,7 +139,7 @@ suspend fun <ResultType> HttpClient.unimultipart(
dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}, requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = submitFormWithBinaryData( ): ResultType = submitFormWithBinaryData<StandardKtorSerialInputData>(
url, url,
formData = formData { formData = formData {
append( append(
@@ -161,13 +155,12 @@ suspend fun <ResultType> HttpClient.unimultipart(
} }
) { ) {
requestBuilder() requestBuilder()
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) } }.let { serialFormat.decodeDefault(resultDeserializer, it) }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unimultipart( suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String, url: String,
filename: String, filename: String,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>, otherData: BodyPair<BodyType>,
inputProvider: InputProvider, inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>, resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*", mimetype: String = "*/*",
@@ -201,7 +194,6 @@ suspend fun <BodyType, ResultType> HttpClient.unimultipart(
serialFormat serialFormat
) )
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.unimultipart( suspend fun <ResultType> HttpClient.unimultipart(
url: String, url: String,
mppFile: MPPFile, mppFile: MPPFile,
@@ -223,11 +215,10 @@ suspend fun <ResultType> HttpClient.unimultipart(
serialFormat serialFormat
) )
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unimultipart( suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String, url: String,
mppFile: MPPFile, mppFile: MPPFile,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>, otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>, resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*", mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {}, additionalParametersBuilder: FormBuilder.() -> Unit = {},

View File

@@ -4,10 +4,7 @@ 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,
@@ -15,7 +12,7 @@ suspend fun tempUpload(
onUpload: (Long, Long) -> Unit onUpload: (Long, Long) -> Unit
): TemporalFileId { ): TemporalFileId {
val formData = FormData() val formData = FormData()
val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job) val answer = CompletableDeferred<TemporalFileId>()
formData.append( formData.append(
"data", "data",
@@ -40,15 +37,17 @@ suspend fun tempUpload(
request.open("POST", fullTempUploadDraftPath, true) request.open("POST", fullTempUploadDraftPath, true)
request.send(formData) request.send(formData)
answer.invokeOnCompletion { val handle = currentCoroutineContext().job.invokeOnCompletion {
runCatching { runCatching {
if (request.readyState != DONE) { request.abort()
request.abort()
}
} }
} }
return answer.await() return runCatching {
answer.await()
}.also {
handle.dispose()
}.getOrThrow()
} }

View File

@@ -4,10 +4,9 @@ import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename import dev.inmo.micro_utils.common.filename
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 io.ktor.client.plugins.onUpload import io.ktor.client.features.onUpload
import io.ktor.client.request.forms.formData import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitFormWithBinaryData import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers import io.ktor.http.Headers
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
import java.net.URLConnection import java.net.URLConnection
@@ -21,7 +20,7 @@ actual suspend fun HttpClient.tempUpload(
onUpload: (Long, Long) -> Unit onUpload: (Long, Long) -> Unit
): TemporalFileId { ): TemporalFileId {
val inputProvider = file.inputProvider() val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData( val fileId = submitFormWithBinaryData<String>(
fullTempUploadDraftPath, fullTempUploadDraftPath,
formData = formData { formData = formData {
append( append(
@@ -35,6 +34,6 @@ actual suspend fun HttpClient.tempUpload(
} }
) { ) {
onUpload(onUpload) onUpload(onUpload)
}.bodyAsText() }
return TemporalFileId(fileId) return TemporalFileId(fileId)
} }

View File

@@ -1,5 +1,3 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.ktor.common package dev.inmo.micro_utils.ktor.common
import kotlinx.serialization.* import kotlinx.serialization.*

View File

@@ -19,8 +19,7 @@ kotlin {
api libs.ktor.server api libs.ktor.server
api libs.ktor.server.cio api libs.ktor.server.cio
api libs.ktor.server.host.common api libs.ktor.server.host.common
api libs.ktor.server.websockets api libs.ktor.websockets
api libs.ktor.server.statusPages
} }
} }
} }

View File

@@ -1,15 +0,0 @@
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)
}

View File

@@ -2,27 +2,26 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.featureOrNull
import io.ktor.application.install
import io.ktor.http.URLProtocol import io.ktor.http.URLProtocol
import io.ktor.server.application.install import io.ktor.http.cio.websocket.*
import io.ktor.server.application.pluginOrNull import io.ktor.routing.Route
import io.ktor.server.routing.Route import io.ktor.routing.application
import io.ktor.server.routing.application import io.ktor.websocket.*
import io.ktor.server.websocket.*
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>,
protocol: URLProtocol? = null, protocol: URLProtocol = URLProtocol.WS,
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData? converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
) { ) {
application.apply { application.apply {
pluginOrNull(WebSockets) ?: install(WebSockets) featureOrNull(io.ktor.websocket.WebSockets) ?: install(io.ktor.websocket.WebSockets)
} }
webSocket(suburl, protocol ?.name) { webSocket(suburl, protocol.name) {
safely { safely {
flow.collect { flow.collect {
converter(it) ?.let { data -> converter(it) ?.let { data ->
@@ -33,13 +32,12 @@ fun <T> Route.includeWebsocketHandling(
} }
} }
@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>,
serializer: SerializationStrategy<T>, serializer: SerializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
protocol: URLProtocol? = null, protocol: URLProtocol = URLProtocol.WS,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling( ) = includeWebsocketHandling(
suburl, suburl,

View File

@@ -1,31 +0,0 @@
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)
}
}
}
}

View File

@@ -3,23 +3,26 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.* import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.* import io.ktor.http.*
import io.ktor.http.content.* import io.ktor.http.content.PartData
import io.ktor.server.application.ApplicationCall import io.ktor.http.content.forEachPart
import io.ktor.server.application.call import io.ktor.request.receive
import io.ktor.server.request.receive import io.ktor.request.receiveMultipart
import io.ktor.server.request.receiveMultipart import io.ktor.response.respond
import io.ktor.server.response.respond import io.ktor.response.respondBytes
import io.ktor.server.response.respondBytes import io.ktor.routing.Route
import io.ktor.server.routing.Route import io.ktor.util.asStream
import io.ktor.server.websocket.WebSocketServerSession import io.ktor.util.cio.writeChannel
import io.ktor.util.pipeline.PipelineContext import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.* import io.ktor.utils.io.core.*
import io.ktor.websocket.WebSocketServerSession
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.*
import kotlinx.serialization.SerializationStrategy import java.io.File
import java.io.File.createTempFile
@Deprecated("This class method will be removed soon. It is now recommended to use built-in ktor features instead")
class UnifiedRouter( class UnifiedRouter(
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
@@ -28,7 +31,7 @@ class UnifiedRouter(
suburl: String, suburl: String,
flow: Flow<T>, flow: Flow<T>,
serializer: SerializationStrategy<T>, serializer: SerializationStrategy<T>,
protocol: URLProtocol? = null, protocol: URLProtocol = URLProtocol.WS,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter) ) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
@@ -98,7 +101,6 @@ class UnifiedRouter(
val defaultUnifiedRouter = UnifiedRouter() val defaultUnifiedRouter = UnifiedRouter()
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.unianswer( suspend fun <T> ApplicationCall.unianswer(
answerSerializer: SerializationStrategy<T>, answerSerializer: SerializationStrategy<T>,
answer: T answer: T
@@ -109,7 +111,6 @@ suspend fun <T> ApplicationCall.unianswer(
) )
} }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniload( suspend fun <T> ApplicationCall.uniload(
deserializer: DeserializationStrategy<T> deserializer: DeserializationStrategy<T>
) = safely { ) = safely {
@@ -122,7 +123,6 @@ suspend fun <T> ApplicationCall.uniload(
suspend fun ApplicationCall.uniloadMultipart( suspend fun ApplicationCall.uniloadMultipart(
onFormItem: (PartData.FormItem) -> Unit = {}, onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {}, onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {} onBinaryContent: (PartData.BinaryItem) -> Unit = {}
) = safely { ) = safely {
val multipartData = receiveMultipart() val multipartData = receiveMultipart()
@@ -139,19 +139,16 @@ suspend fun ApplicationCall.uniloadMultipart(
} }
} }
is PartData.BinaryItem -> onBinaryContent(it) is PartData.BinaryItem -> onBinaryContent(it)
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
} }
} }
resultInput ?: error("Bytes has not been received") resultInput ?: error("Bytes has not been received")
} }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniloadMultipart( suspend fun <T> ApplicationCall.uniloadMultipart(
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {}, onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {}, onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {} onBinaryContent: (PartData.BinaryItem) -> Unit = {}
): Pair<Input, T> { ): Pair<Input, T> {
var data: Optional<T>? = null var data: Optional<T>? = null
@@ -164,7 +161,6 @@ suspend fun <T> ApplicationCall.uniloadMultipart(
onCustomFileItem(it) onCustomFileItem(it)
} }
}, },
onBinaryChannelItem,
onBinaryContent onBinaryContent
) )
@@ -172,12 +168,10 @@ suspend fun <T> ApplicationCall.uniloadMultipart(
return resultInput to (completeData.dataOrNull().let { it as T }) return resultInput to (completeData.dataOrNull().let { it as T })
} }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniloadMultipartFile( suspend fun <T> ApplicationCall.uniloadMultipartFile(
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {}, onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {}, onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}, onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely { ) = safely {
val multipartData = receiveMultipart() val multipartData = receiveMultipart()
@@ -203,9 +197,7 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
".${name.extension}" ".${name.extension}"
).apply { ).apply {
outputStream().use { fileStream -> outputStream().use { fileStream ->
it.streamProvider().use { it.provider().asStream().copyTo(fileStream)
it.copyTo(fileStream)
}
} }
} }
} }
@@ -214,7 +206,6 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
} }
} }
is PartData.BinaryItem -> onBinaryContent(it) is PartData.BinaryItem -> onBinaryContent(it)
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
} }
} }
@@ -225,7 +216,6 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
suspend fun ApplicationCall.uniloadMultipartFile( suspend fun ApplicationCall.uniloadMultipartFile(
onFormItem: (PartData.FormItem) -> Unit = {}, onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {}, onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}, onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely { ) = safely {
val multipartData = receiveMultipart() val multipartData = receiveMultipart()
@@ -249,9 +239,7 @@ suspend fun ApplicationCall.uniloadMultipartFile(
".${name.extension}" ".${name.extension}"
).apply { ).apply {
outputStream().use { fileStream -> outputStream().use { fileStream ->
it.streamProvider().use { it.provider().asStream().copyTo(fileStream)
it.copyTo(fileStream)
}
} }
} }
} else { } else {
@@ -259,7 +247,6 @@ suspend fun ApplicationCall.uniloadMultipartFile(
} }
} }
is PartData.BinaryItem -> onBinaryContent(it) is PartData.BinaryItem -> onBinaryContent(it)
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
} }
} }
@@ -286,7 +273,6 @@ suspend fun ApplicationCall.getQueryParameterOrSendError(
} }
} }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
fun <T> ApplicationCall.decodeUrlQueryValue( fun <T> ApplicationCall.decodeUrlQueryValue(
field: String, field: String,
deserializer: DeserializationStrategy<T> deserializer: DeserializationStrategy<T>
@@ -297,7 +283,6 @@ fun <T> ApplicationCall.decodeUrlQueryValue(
) )
} }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError( suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError(
field: String, field: String,
deserializer: DeserializationStrategy<T> deserializer: DeserializationStrategy<T>

View File

@@ -1,7 +1,7 @@
package dev.inmo.micro_utils.ktor.server package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
import io.ktor.server.application.Application import io.ktor.application.Application
import io.ktor.server.cio.CIO import io.ktor.server.cio.CIO
import io.ktor.server.cio.CIOApplicationEngine import io.ktor.server.cio.CIOApplicationEngine
import io.ktor.server.engine.* import io.ktor.server.engine.*

View File

@@ -7,15 +7,14 @@ import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.ktor.common.DefaultTemporalFilesSubPath import dev.inmo.micro_utils.ktor.common.DefaultTemporalFilesSubPath
import dev.inmo.micro_utils.ktor.common.TemporalFileId import dev.inmo.micro_utils.ktor.common.TemporalFileId
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
import io.ktor.application.call
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData import io.ktor.http.content.PartData
import io.ktor.http.content.streamProvider import io.ktor.http.content.streamProvider
import io.ktor.server.application.call import io.ktor.request.receiveMultipart
import io.ktor.server.request.receiveMultipart import io.ktor.response.respond
import io.ktor.server.response.respond import io.ktor.routing.Route
import io.ktor.server.response.respondText import io.ktor.routing.post
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@@ -112,7 +111,7 @@ class TemporalFilesRoutingConfigurator(
temporalFilesMutex.withLock { temporalFilesMutex.withLock {
temporalFilesMap[fileId] = file temporalFilesMap[fileId] = file
} }
call.respondText(fileId.string) call.respond(fileId.string)
launchSafelyWithoutExceptions { filesFlow.emit(fileId) } launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
} ?: call.respond(HttpStatusCode.BadRequest) } ?: call.respond(HttpStatusCode.BadRequest)
} }

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application import io.ktor.application.Application
import io.ktor.server.application.install import io.ktor.application.install
import io.ktor.server.plugins.cachingheaders.CachingHeaders import io.ktor.features.CachingHeaders
import io.ktor.server.plugins.cachingheaders.CachingHeadersConfig
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
data class ApplicationCachingHeadersConfigurator( data class ApplicationCachingHeadersConfigurator(
private val elements: List<@Contextual Element> private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator { ) : KtorApplicationConfigurator {
fun interface Element { operator fun CachingHeadersConfig.invoke() } fun interface Element { operator fun CachingHeaders.Configuration.invoke() }
override fun Application.configure() { override fun Application.configure() {
install(CachingHeaders) { install(CachingHeaders) {

View File

@@ -1,9 +1,8 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator.Element import io.ktor.application.*
import io.ktor.server.application.* import io.ktor.routing.Route
import io.ktor.server.routing.Route import io.ktor.routing.Routing
import io.ktor.server.routing.Routing
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -19,7 +18,7 @@ class ApplicationRoutingConfigurator(
} }
override fun Application.configure() { override fun Application.configure() {
pluginOrNull(Routing) ?.apply { featureOrNull(Routing) ?.apply {
rootInstaller.apply { invoke() } rootInstaller.apply { invoke() }
} ?: install(Routing) { } ?: install(Routing) {
rootInstaller.apply { invoke() } rootInstaller.apply { invoke() }

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application import io.ktor.application.Application
import io.ktor.server.application.install import io.ktor.application.install
import io.ktor.server.sessions.Sessions import io.ktor.sessions.Sessions
import io.ktor.server.sessions.SessionsConfig
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
class ApplicationSessionsConfigurator( class ApplicationSessionsConfigurator(
private val elements: List<@Contextual Element> private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator { ) : KtorApplicationConfigurator {
fun interface Element { operator fun SessionsConfig.invoke() } fun interface Element { operator fun Sessions.Configuration.invoke() }
override fun Application.configure() { override fun Application.configure() {
install(Sessions) { install(Sessions) {

View File

@@ -1,6 +1,6 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application import io.ktor.application.Application
interface KtorApplicationConfigurator { interface KtorApplicationConfigurator {
fun Application.configure() fun Application.configure()

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application import io.ktor.application.Application
import io.ktor.server.application.install import io.ktor.application.install
import io.ktor.server.plugins.statuspages.StatusPages import io.ktor.features.StatusPages
import io.ktor.server.plugins.statuspages.StatusPagesConfig
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
class StatusPagesConfigurator( class StatusPagesConfigurator(
private val elements: List<@Contextual Element> private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator { ) : KtorApplicationConfigurator {
fun interface Element { operator fun StatusPagesConfig.invoke() } fun interface Element { operator fun StatusPages.Configuration.invoke() }
override fun Application.configure() { override fun Application.configure() {
install(StatusPages) { install(StatusPages) {

View File

@@ -1,6 +1,5 @@
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.ListSerializer
@@ -165,7 +164,7 @@ suspend fun main(vararg args: String) {
val ietfLanguageCodes = json.decodeFromString( val ietfLanguageCodes = json.decodeFromString(
ListSerializer(LanguageCode.serializer()), ListSerializer(LanguageCode.serializer()),
client.get(ietfLanguageCodesLink).bodyAsText() client.get(ietfLanguageCodesLink)
).map { ).map {
it.copy( it.copy(
title = it.title title = it.title
@@ -176,7 +175,7 @@ suspend fun main(vararg args: String) {
} }
val ietfLanguageCodesWithTagsMap = json.decodeFromString( val ietfLanguageCodesWithTagsMap = json.decodeFromString(
ListSerializer(LanguageCodeWithTag.serializer()), ListSerializer(LanguageCodeWithTag.serializer()),
client.get(ietfLanguageCodesAdditionalTagsLink).bodyAsText() client.get(ietfLanguageCodesAdditionalTagsLink)
).filter { it.withSubtag != it.tag }.groupBy { it.tag } ).filter { it.withSubtag != it.tag }.groupBy { it.tag }
val tags = ietfLanguageCodes.map { val tags = ietfLanguageCodes.map {

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.mime_types package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.* import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.* import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
@@ -15,7 +16,6 @@ fun mimeType(raw: String) = mimesCache.getOrPut(raw) {
internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw) internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw)
@Suppress("OPT_IN_USAGE")
@Serializer(MimeType::class) @Serializer(MimeType::class)
object MimeTypeSerializer : KSerializer<MimeType> { object MimeTypeSerializer : KSerializer<MimeType> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING) override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)

View File

@@ -1,60 +1,23 @@
package dev.inmo.micro_utils.pagination package dev.inmo.micro_utils.pagination
import kotlinx.serialization.* import kotlinx.serialization.Serializable
import kotlin.math.ceil
/**
* @param page Current page number
* @param size Current page size. It can be greater than size of [results]
* @param results Result objects
* @param objectsNumber Count of all objects across all pages
*/
@Serializable @Serializable
data class PaginationResult<T>( data class PaginationResult<T>(
override val page: Int, override val page: Int,
override val size: Int, val pagesNumber: Int,
val results: List<T>, val results: List<T>,
val objectsNumber: Long override val size: Int
) : Pagination { ) : Pagination
/**
* Amount of pages for current pagination
*/
@EncodeDefault
val pagesNumber: Int = ceil(objectsNumber / size.toFloat()).toInt()
constructor( fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0)
page: Int,
results: List<T>,
pagesNumber: Int,
size: Int
) : this(
page,
size,
results,
(pagesNumber * size).toLong()
)
@Deprecated("Replace with The other order of incoming parameters or objectsCount parameter")
constructor(
page: Int,
pagesNumber: Int,
results: List<T>,
size: Int
) : this(
page,
results,
pagesNumber,
size
)
}
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L)
/** /**
* @return New [PaginationResult] with [data] without checking of data sizes equality * @return New [PaginationResult] with [data] without checking of data sizes equality
*/ */
fun <I, O> PaginationResult<I>.changeResultsUnchecked( fun <I, O> PaginationResult<I>.changeResultsUnchecked(
data: List<O> data: List<O>
): PaginationResult<O> = PaginationResult(page, size, data, objectsNumber) ): PaginationResult<O> = PaginationResult(page, pagesNumber, data, size)
/** /**
* @return New [PaginationResult] with [data] <b>with</b> checking of data sizes equality * @return New [PaginationResult] with [data] <b>with</b> checking of data sizes equality
*/ */
@@ -70,9 +33,12 @@ fun <T> List<T>.createPaginationResult(
commonObjectsNumber: Long commonObjectsNumber: Long
) = PaginationResult( ) = PaginationResult(
pagination.page, pagination.page,
pagination.size, calculatePagesNumber(
commonObjectsNumber,
pagination.size
),
this, this,
commonObjectsNumber pagination.size
) )
fun <T> List<T>.createPaginationResult( fun <T> List<T>.createPaginationResult(
@@ -80,9 +46,12 @@ fun <T> List<T>.createPaginationResult(
commonObjectsNumber: Long commonObjectsNumber: Long
) = PaginationResult( ) = PaginationResult(
calculatePage(firstIndex, size), calculatePage(firstIndex, size),
size, calculatePagesNumber(
commonObjectsNumber,
size
),
this, this,
commonObjectsNumber size
) )
fun <T> Pair<Long, List<T>>.createPaginationResult( fun <T> Pair<Long, List<T>>.createPaginationResult(

View File

@@ -26,10 +26,6 @@ inline fun Pagination.nextPage() =
size size
) )
/**
* @param page Current page number
* @param size Current page size
*/
@Serializable @Serializable
data class SimplePagination( data class SimplePagination(
override val page: Int, override val page: Int,

View File

@@ -27,5 +27,4 @@ inline fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? =
null null
} }
@Suppress("NOTHING_TO_INLINE")
inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty() inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()

View File

@@ -2,19 +2,19 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
inline fun <T> doForAll( suspend fun <T> doForAll(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?, paginationMapper: (PaginationResult<T>) -> Pagination?,
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
) { ) {
doWithPagination(initialPagination) { doWithPagination(initialPagination) {
block(it).let(paginationMapper) block(it).let(paginationMapper)
} }
} }
inline fun <T> doForAllWithNextPaging( suspend fun <T> doForAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
) { ) {
doForAll( doForAll(
initialPagination, initialPagination,
@@ -23,9 +23,9 @@ inline fun <T> doForAllWithNextPaging(
) )
} }
inline fun <T> doAllWithCurrentPaging( suspend fun <T> doAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
) { ) {
doForAll( doForAll(
initialPagination, initialPagination,
@@ -34,7 +34,7 @@ inline fun <T> doAllWithCurrentPaging(
) )
} }
inline fun <T> doForAllWithCurrentPaging( suspend fun <T> doForAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
) = doAllWithCurrentPaging(initialPagination, block) ) = doAllWithCurrentPaging(initialPagination, block)

View File

@@ -2,10 +2,10 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
inline fun <T> getAll( suspend fun <T> getAll(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?, paginationMapper: (PaginationResult<T>) -> Pagination?,
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
): List<T> { ): List<T> {
val results = mutableListOf<T>() val results = mutableListOf<T>()
doForAll(initialPagination, paginationMapper) { doForAll(initialPagination, paginationMapper) {
@@ -16,45 +16,46 @@ inline fun <T> getAll(
return results.toList() return results.toList()
} }
inline fun <T, R> R.getAllBy( suspend fun <T, R> R.getAllBy(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
paginationMapper: R.(PaginationResult<T>) -> Pagination?, paginationMapper: R.(PaginationResult<T>) -> Pagination?,
block: R.(Pagination) -> PaginationResult<T> block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAll( ): List<T> = getAll(
initialPagination, initialPagination,
{ paginationMapper(it) }, { paginationMapper(it) },
{ block(it) } { block(it) }
) )
inline fun <T> getAllWithNextPaging( suspend fun <T> getAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
): List<T> = getAll( ): List<T> = getAll(
initialPagination, initialPagination,
{ it.nextPageIfNotEmpty() }, { it.nextPageIfNotEmpty() },
block block
) )
inline fun <T, R> R.getAllByWithNextPaging( suspend fun <T, R> R.getAllByWithNextPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: R.(Pagination) -> PaginationResult<T> block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithNextPaging( ): List<T> = getAllWithNextPaging(
initialPagination, initialPagination,
{ block(it) } { block(it) }
) )
inline fun <T> getAllWithCurrentPaging( suspend fun <T> getAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
): List<T> = getAll( ): List<T> = getAll(
initialPagination, initialPagination,
{ it.currentPageIfNotEmpty() }, { it.currentPageIfNotEmpty() },
block block
) )
inline fun <T, R> R.getAllByWithCurrentPaging( suspend fun <T, R> R.getAllByWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: R.(Pagination) -> PaginationResult<T> block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithCurrentPaging( ): List<T> = getAllWithCurrentPaging(
initialPagination initialPagination,
) { block(it) } { block(it) }
)

View File

@@ -1,7 +1,7 @@
package dev.inmo.micro_utils.pagination package dev.inmo.micro_utils.pagination
import io.ktor.application.ApplicationCall
import io.ktor.http.Parameters import io.ktor.http.Parameters
import io.ktor.server.application.ApplicationCall
val Parameters.extractPagination: Pagination val Parameters.extractPagination: Pagination
get() = SimplePagination( get() = SimplePagination(

View File

@@ -15,4 +15,4 @@ kotlin {
} }
} }
} }
} }

View File

@@ -1,16 +1,16 @@
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.* import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
open class ReadCRUDCacheRepo<ObjectType, IdType>( open class ReadCRUDCacheRepo<ObjectType, IdType>(
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>, protected val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
protected open val kvCache: KVCache<IdType, ObjectType>, protected val kvCache: KVCache<IdType, ObjectType>,
protected open val idGetter: (ObjectType) -> IdType protected val idGetter: (ObjectType) -> IdType
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CacheRepo { ) : 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,63 +18,8 @@ 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>, CacheRepo {
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>(
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>, 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
@@ -82,17 +27,8 @@ open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
parentRepo, parentRepo,
kvCache, kvCache,
idGetter idGetter
), ), CRUDRepo<ObjectType, IdType, InputValueType>, WriteCRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo( protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
parentRepo, protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
kvCache, protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
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)

View File

@@ -1,3 +0,0 @@
package dev.inmo.micro_utils.repos.cache
interface CacheRepo

View File

@@ -1,32 +1,36 @@
package dev.inmo.micro_utils.repos.cache.cache package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
interface KVCache<K, V> : KeyValueRepo<K, V>
open class SimpleKVCache<K, V>( open class SimpleKVCache<K, V>(
protected val cachedValuesCount: Int, protected val cachedValuesCount: Int,
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>() private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
) : KVCache<K, V>, KeyValueRepo<K, V> by kvParent { ) : KVCache<K, V>, KeyValueRepo<K, V> by kvParent {
protected open val cacheQueue = ArrayDeque<K>(cachedValuesCount) protected open val cacheStack = ArrayList<K>(cachedValuesCount)
protected val syncMutex = Mutex() protected val syncMutex = Mutex()
protected suspend fun makeUnset(toUnset: List<K>) { protected suspend fun makeUnset(toUnset: List<K>) {
cacheQueue.removeAll(toUnset) cacheStack.removeAll(toUnset)
kvParent.unset(toUnset) kvParent.unset(toUnset)
} }
override suspend fun set(toSet: Map<K, V>) { override suspend fun set(toSet: Map<K, V>) {
syncMutex.withLock { syncMutex.withLock {
for ((k, v) in toSet) { if (toSet.size > cachedValuesCount) {
if (cacheQueue.size >= cachedValuesCount) { cacheStack.clear()
cacheQueue.removeFirstOrNull() ?.let {
kvParent.unset(it) kvParent.unset(getAllWithNextPaging { kvParent.keys(it) })
} val keysToInclude = toSet.keys.drop(toSet.size - cachedValuesCount)
}
cacheQueue.addLast(k) cacheStack.addAll(keysToInclude)
kvParent.set(k, v) kvParent.set(keysToInclude.associateWith { toSet.getValue(it) })
} else {
makeUnset(cacheStack.take(toSet.size))
} }
} }
} }
@@ -35,8 +39,3 @@ open class SimpleKVCache<K, V>(
syncMutex.withLock { makeUnset(toUnset) } syncMutex.withLock { makeUnset(toUnset) }
} }
} }
inline fun <K, V> KVCache(
cachedValuesCount: Int,
kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
) = SimpleKVCache<K, V>(cachedValuesCount, kvParent)

View File

@@ -1,35 +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 open val parentRepo: ReadKeyValueRepo<Key, Value>, protected val parentRepo: ReadKeyValueRepo<Key, Value>,
protected open val kvCache: KVCache<Key, Value>, protected val kvCache: KVCache<Key, Value>,
) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo { ) : 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>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CacheRepo { ) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo {
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)

View File

@@ -5,15 +5,16 @@ 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 open val parentRepo: ReadKeyValuesRepo<Key, Value>, protected val parentRepo: ReadKeyValuesRepo<Key, Value>,
protected open val kvCache: KVCache<Key, List<Value>> protected val kvCache: KVCache<Key, List<Value>>
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CacheRepo { ) : 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(
pagination.let { if (reversed) it.reverse(count(k)) else it } pagination.let { if (reversed) it.reverse(count(k)) else it }
@@ -30,21 +31,12 @@ 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>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo { ) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope) 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) 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)

View File

@@ -1,8 +0,0 @@
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
}

View File

@@ -1,7 +0,0 @@
package dev.inmo.micro_utils.repos.cache.cache
import dev.inmo.micro_utils.repos.*
interface KVCache<K, V> : KeyValueRepo<K, V> {
companion object
}

View File

@@ -1,28 +0,0 @@
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)

View File

@@ -1,95 +0,0 @@
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>, FullCacheRepo {
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 open 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)

View File

@@ -1,5 +0,0 @@
package dev.inmo.micro_utils.repos.cache.full
import dev.inmo.micro_utils.repos.cache.CacheRepo
interface FullCacheRepo : CacheRepo

View File

@@ -1,104 +0,0 @@
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>, FullCacheRepo {
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 open 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, FullCacheRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
}
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)

View File

@@ -1,154 +0,0 @@
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>, FullCacheRepo {
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 open suspend fun actualizeKey(k: Key) {
kvCache.set(k, parentRepo.getAll(k))
}
protected open 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, FullCacheRepo {
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)

View File

@@ -1,51 +1,21 @@
package dev.inmo.micro_utils.repos package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.common.*
@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
} }
class SimpleMapperRepo<FromKey, FromValue, ToKey, ToValue>( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
private val keyFromToTo: suspend FromKey.() -> ToKey, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
private val valueFromToTo: suspend FromValue.() -> ToValue, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
private val keyToToFrom: suspend ToKey.() -> FromKey, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
private val valueToToFrom: suspend ToValue.() -> FromValue crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
) : MapperRepo<FromKey, FromValue, ToKey, ToValue> { ) = object : 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)

View File

@@ -1,10 +1,11 @@
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 ReadKeyValuesRepo<Key, Value> : Repo { interface ReadOneToManyKeyValueRepo<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>
@@ -35,9 +36,9 @@ interface ReadKeyValuesRepo<Key, Value> : Repo {
} }
} }
} }
typealias ReadOneToManyKeyValueRepo<Key,Value> = ReadKeyValuesRepo<Key, Value> typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
interface WriteKeyValuesRepo<Key, Value> : Repo { interface WriteOneToManyKeyValueRepo<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>
@@ -54,41 +55,41 @@ interface WriteKeyValuesRepo<Key, Value> : Repo {
add(toSet) add(toSet)
} }
} }
typealias WriteOneToManyKeyValueRepo<Key,Value> = WriteKeyValuesRepo<Key, Value> typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add( suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<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 : WriteKeyValuesRepo<Key, Value>> REPO.add( suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<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> WriteKeyValuesRepo<Key, Value>.add( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<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> WriteKeyValuesRepo<Key, Value>.add( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<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 : WriteKeyValuesRepo<Key, Value>> REPO.set( suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<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 : WriteKeyValuesRepo<Key, Value>> REPO.set( suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<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> WriteKeyValuesRepo<Key, Value>.set( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<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> WriteKeyValuesRepo<Key, Value>.set( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, vararg v: Value k: Key, vararg v: Value
) = set(k, v.toList()) ) = set(k, v.toList())
interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyValuesRepo<Key, Value> { interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<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)
@@ -101,29 +102,22 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal
} }
} }
} }
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value> typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
class DelegateBasedKeyValuesRepo<Key, Value>( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
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> WriteKeyValuesRepo<Key, Value>.remove( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<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> WriteKeyValuesRepo<Key, Value>.remove( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<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> WriteKeyValuesRepo<Key, Value>.remove( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
k: Key, k: Key,
vararg v: Value vararg v: Value
) = remove(k, v.toList()) ) = remove(k, v.toList())

View File

@@ -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 ReadCRUDRepo<ObjectType, IdType> : Repo { interface ReadStandardCRUDRepo<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 ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType> typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<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 WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo { interface WriteStandardCRUDRepo<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,25 +28,18 @@ interface WriteCRUDRepo<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 WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> = WriteCRUDRepo<ObjectType, IdType, InputValueType> typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.create( suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<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> WriteCRUDRepo<ObjectType, IdType, InputValueType>.update( suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<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> WriteCRUDRepo<ObjectType, IdType, InputValueType>.deleteById( suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.deleteById(
vararg ids: IdType vararg ids: IdType
) = deleteById(ids.toList()) ) = deleteById(ids.toList())
interface CRUDRepo<ObjectType, IdType, InputValueType> : ReadCRUDRepo<ObjectType, IdType>, interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
WriteCRUDRepo<ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
typealias StandardCRUDRepo<ObjectType, IdType, InputValueType> = CRUDRepo<ObjectType, IdType, InputValueType> typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<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

View File

@@ -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 ReadKeyValueRepo<Key, Value> : Repo { interface ReadStandardKeyValueRepo<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 ReadKeyValueRepo<Key, Value> : Repo {
suspend fun contains(key: Key): Boolean suspend fun contains(key: Key): Boolean
suspend fun count(): Long suspend fun count(): Long
} }
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value> typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
interface WriteKeyValueRepo<Key, Value> : Repo { interface WriteStandardKeyValueRepo<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 WriteKeyValueRepo<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 WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value> typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set( suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
vararg toSet: Pair<Key, Value> vararg toSet: Pair<Key, Value>
) = set(toSet.toMap()) ) = set(toSet.toMap())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set( suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
k: Key, v: Value k: Key, v: Value
) = set(k to v) ) = set(k to v)
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset( suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
vararg k: Key vararg k: Key
) = unset(k.toList()) ) = unset(k.toList())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues( suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unsetWithValues(
vararg v: Value vararg v: Value
) = unsetWithValues(v.toList()) ) = unsetWithValues(v.toList())
interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> { interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<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,16 +48,5 @@ interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValue
} }
} }
} }
suspend fun clear() {
doAllWithCurrentPaging { keys(it).also { unset(it.results) } }
}
} }
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value> typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<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

View File

@@ -1,130 +0,0 @@
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) })
)

View File

@@ -1,14 +1,15 @@
package dev.inmo.micro_utils.repos.mappers package dev.inmo.micro_utils.repos.mappers
import dev.inmo.micro_utils.pagination.* 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 kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>( open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadKeyValueRepo<ToKey, ToValue>, private val to: ReadStandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : ReadKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper { ) : ReadStandardKeyValueRepo<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()
@@ -20,8 +21,11 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
pagination, pagination,
reversed reversed
).let { ).let {
it.changeResultsUnchecked( PaginationResult(
it.results.map { it.toInnerValue() } it.page,
it.pagesNumber,
it.results.map { it.toInnerValue() },
it.size
) )
} }
@@ -32,8 +36,11 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
pagination, pagination,
reversed reversed
).let { ).let {
it.changeResultsUnchecked( PaginationResult(
it.results.map { it.toInnerKey() } it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
) )
} }
@@ -46,8 +53,11 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
pagination, pagination,
reversed reversed
).let { ).let {
it.changeResultsUnchecked( PaginationResult(
it.results.map { it.toInnerKey() } it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
) )
} }
@@ -59,24 +69,24 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadKeyValueRepo<FromKey, FromValue> = MapperReadKeyValueRepo(this, mapper) ): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadKeyValueRepo<FromKey, FromValue> = withMapper( ): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>( open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteKeyValueRepo<ToKey, ToValue>, private val to: WriteStandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : WriteKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper { ) : WriteStandardKeyValueRepo<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()
} }
@@ -102,40 +112,39 @@ open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteKeyValueRepo<FromKey, FromValue> = MapperWriteKeyValueRepo(this, mapper) ): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteKeyValueRepo<FromKey, FromValue> = withMapper( ): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@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>, 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,
ReadKeyValueRepo<FromKey, FromValue> by MapperReadKeyValueRepo(to, mapper), ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
WriteKeyValueRepo<FromKey, FromValue> by MapperWriteKeyValueRepo(to, mapper) WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValueRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): KeyValueRepo<FromKey, FromValue> = MapperKeyValueRepo(this, mapper) ): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValueRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): KeyValueRepo<FromKey, FromValue> = withMapper( ): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )

View File

@@ -1,14 +1,15 @@
package dev.inmo.micro_utils.repos.mappers package dev.inmo.micro_utils.repos.mappers
import dev.inmo.micro_utils.pagination.* 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 kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>( open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadKeyValuesRepo<ToKey, ToValue>, private val to: ReadOneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : ReadKeyValuesRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper { ) : ReadOneToManyKeyValueRepo<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,
@@ -18,8 +19,11 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
pagination, pagination,
reversed reversed
).let { ).let {
it.changeResultsUnchecked( PaginationResult(
it.results.map { it.toInnerValue() } it.page,
it.pagesNumber,
it.results.map { it.toInnerValue() },
it.size
) )
} }
@@ -30,8 +34,11 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
pagination, pagination,
reversed reversed
).let { ).let {
it.changeResultsUnchecked( PaginationResult(
it.results.map { it.toInnerKey() } it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
) )
} }
@@ -44,8 +51,11 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
pagination, pagination,
reversed reversed
).let { ).let {
it.changeResultsUnchecked( PaginationResult(
it.results.map { it.toInnerKey() } it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
) )
} }
@@ -57,24 +67,24 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadKeyValuesRepo<FromKey, FromValue> = MapperReadKeyValuesRepo(this, mapper) ): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadKeyValuesRepo<FromKey, FromValue> = withMapper( ): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>( open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteKeyValuesRepo<ToKey, ToValue>, private val to: WriteOneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : WriteKeyValuesRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper { ) : WriteOneToManyKeyValueRepo<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()
} }
@@ -108,40 +118,39 @@ open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteKeyValuesRepo<FromKey, FromValue> = MapperWriteKeyValuesRepo(this, mapper) ): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteKeyValuesRepo<FromKey, FromValue> = withMapper( ): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@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>
) : KeyValuesRepo<FromKey, FromValue>, ) : OneToManyKeyValueRepo<FromKey, FromValue>,
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
ReadKeyValuesRepo<FromKey, FromValue> by MapperReadKeyValuesRepo(to, mapper), ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
WriteKeyValuesRepo<FromKey, FromValue> by MapperWriteKeyValuesRepo(to, mapper) WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): KeyValuesRepo<FromKey, FromValue> = MapperKeyValuesRepo(this, mapper) ): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): KeyValuesRepo<FromKey, FromValue> = withMapper( ): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )

View File

@@ -1,10 +1,11 @@
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.ReadCRUDRepo import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll( suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<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 {

View File

@@ -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.ReadKeyValueRepo import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll( suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<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 {

View File

@@ -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.ReadKeyValuesRepo import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll( suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<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 {

View File

@@ -1,10 +1,10 @@
package dev.inmo.micro_utils.repos.versions package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import dev.inmo.micro_utils.repos.set import dev.inmo.micro_utils.repos.set
class KeyValueBasedVersionsRepoProxy<T>( class KeyValueBasedVersionsRepoProxy<T>(
private val keyValueStore: KeyValueRepo<String, Int>, private val keyValueStore: StandardKeyValueRepo<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)

View File

@@ -13,9 +13,9 @@ import java.nio.file.StandardWatchEventKinds.*
private inline val String.isAbsolute private inline val String.isAbsolute
get() = startsWith(File.separator) get() = startsWith(File.separator)
class FileReadKeyValueRepo( class FileReadStandardKeyValueRepo(
private val folder: File private val folder: File
) : ReadKeyValueRepo<String, File> { ) : ReadStandardKeyValueRepo<String, File> {
init { init {
folder.mkdirs() folder.mkdirs()
} }
@@ -31,7 +31,7 @@ class FileReadKeyValueRepo(
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> { override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
val count = count() val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult() val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
if (reversed) { if (reversed) {
filesPaths.reverse() filesPaths.reverse()
} }
@@ -44,7 +44,7 @@ class FileReadKeyValueRepo(
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> { override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
val count = count() val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult() val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
if (reversed) { if (reversed) {
filesPaths.reverse() filesPaths.reverse()
} }
@@ -83,19 +83,17 @@ class FileReadKeyValueRepo(
* 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 FileWriteKeyValueRepo( class FileWriteStandardKeyValueRepo(
private val folder: File, private val folder: File,
filesChangedProcessingScope: CoroutineScope? = null filesChangedProcessingScope: CoroutineScope? = null
) : WriteKeyValueRepo<String, File> { ) : WriteStandardKeyValueRepo<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 {
if (!folder.mkdirs() && !folder.exists()) { folder.mkdirs()
error("Unable to create folder ${folder.absolutePath}")
}
filesChangedProcessingScope ?.let { filesChangedProcessingScope ?.let {
it.launch { it.launch {
try { try {
@@ -140,17 +138,15 @@ class FileWriteKeyValueRepo(
} }
override suspend fun set(toSet: Map<String, File>) { override suspend fun set(toSet: Map<String, File>) {
val scope = CoroutineScope(currentCoroutineContext()) supervisorScope {
toSet.map { (filename, fileSource) -> toSet.map { (filename, fileSource) ->
scope.launch { 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)
if (!file.exists()) { _onNewValue.emit(filename to file)
error("Can't create file $file with new content")
} }
_onNewValue.emit(filename to file)
} }
}.joinAll() }.joinAll()
} }
@@ -179,11 +175,9 @@ class FileWriteKeyValueRepo(
} }
@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") class FileStandardKeyValueRepo(
class FileKeyValueRepo(
folder: File, folder: File,
filesChangedProcessingScope: CoroutineScope? = null filesChangedProcessingScope: CoroutineScope? = null
) : KeyValueRepo<String, File>, ) : StandardKeyValueRepo<String, File>,
WriteKeyValueRepo<String, File> by FileWriteKeyValueRepo(folder, filesChangedProcessingScope), WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
ReadKeyValueRepo<String, File> by FileReadKeyValueRepo(folder) { ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder)
}

View File

@@ -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
) : ReadCRUDRepo<ObjectType, IdType> { ) : ReadStandardCRUDRepo<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
@@ -64,4 +64,4 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
} }
} }
} }
} }

View File

@@ -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
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, ) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper), AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
CRUDRepo<ObjectType, IdType, InputValueType> { StandardCRUDRepo<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)

View File

@@ -3,10 +3,11 @@ package dev.inmo.micro_utils.repos.keyvalue
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.Pagination
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.KeyValueRepo import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
private val cache = HashMap<String, KeyValueStore<*>>() private val cache = HashMap<String, KeyValueStore<*>>()
@@ -14,8 +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
): KeyValueRepo<String, T> { ): StandardKeyValueRepo<String, T> {
@Suppress("UNCHECKED_CAST")
return cache.getOrPut(name) { return cache.getOrPut(name) {
KeyValueStore<T>(this, name, cacheValues) KeyValueStore<T>(this, name, cacheValues)
} as KeyValueStore<T> } as KeyValueStore<T>
@@ -25,7 +25,7 @@ class KeyValueStore<T : Any> internal constructor (
c: Context, c: Context,
preferencesName: String, preferencesName: String,
useCache: Boolean = false useCache: Boolean = false
) : SharedPreferences.OnSharedPreferenceChangeListener, KeyValueRepo<String, T> { ) : SharedPreferences.OnSharedPreferenceChangeListener, StandardKeyValueRepo<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) {
@@ -62,7 +62,6 @@ class KeyValueStore<T : Any> internal constructor (
} }
override suspend fun get(k: String): T? { override suspend fun get(k: String): T? {
@Suppress("UNCHECKED_CAST")
return (cachedData ?. get(k) ?: sharedPreferences.all[k]) as? T return (cachedData ?. get(k) ?: sharedPreferences.all[k]) as? T
} }
@@ -71,11 +70,11 @@ class KeyValueStore<T : Any> internal constructor (
return sharedPreferences.all.values.paginate( return sharedPreferences.all.values.paginate(
resultPagination resultPagination
).let { ).let {
it.changeResultsUnchecked( PaginationResult(
it.results.map { it.page,
@Suppress("UNCHECKED_CAST") it.pagesNumber,
it as T it.results.map { it as T }.let { if (reversed) it.reversed() else it },
}.let { if (reversed) it.reversed() else it } it.size
) )
} }
} }
@@ -85,8 +84,11 @@ class KeyValueStore<T : Any> internal constructor (
return sharedPreferences.all.keys.paginate( return sharedPreferences.all.keys.paginate(
resultPagination resultPagination
).let { ).let {
it.changeResultsUnchecked( PaginationResult(
it.results.let { if (reversed) it.reversed() else it } it.page,
it.pagesNumber,
it.results.let { if (reversed) it.reversed() else it },
it.size
) )
} }
} }
@@ -96,8 +98,11 @@ class KeyValueStore<T : Any> internal constructor (
return sharedPreferences.all.mapNotNull { (k, value) -> if (value == v) k else null }.paginate( return sharedPreferences.all.mapNotNull { (k, value) -> if (value == v) k else null }.paginate(
resultPagination resultPagination
).let { ).let {
it.changeResultsUnchecked( PaginationResult(
it.results.let { if (reversed) it.reversed() else it } it.page,
it.pagesNumber,
it.results.let { if (reversed) it.reversed() else it },
it.size
) )
} }
} }
@@ -150,9 +155,3 @@ class KeyValueStore<T : Any> internal constructor (
} }
} }
} }
inline fun <T : Any> SharedPreferencesKeyValueRepo(
context: Context,
name: String = "default",
cacheValues: Boolean = false
) = context.keyValueStore<T>(name, cacheValues)

View File

@@ -9,6 +9,7 @@ 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
@@ -25,7 +26,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
) : KeyValuesRepo<Key, Value> { ) : OneToManyKeyValueRepo<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()

View File

@@ -4,11 +4,13 @@ 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 [KeyValueRepo] * Will create [VersionsRepo] based on [T], but versions will be stored in [StandardKeyValueRepo]
* *
* @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]
* *
@@ -24,9 +26,9 @@ inline fun <T> Context.versionsKeyValueRepo(
) )
) )
/** /**
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [KeyValueRepo] * Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
* *
* @receiver Will be used to create [KeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo] * @receiver Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
* *
* @see [keyValueStore] * @see [keyValueStore]
*/ */
@@ -35,9 +37,9 @@ inline fun Context.versionsKeyValueRepoForSQL(
) = versionsKeyValueRepo(database) ) = versionsKeyValueRepo(database)
/** /**
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [KeyValueRepo] * Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
* *
* @param context Will be used to create [KeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo] * @param context Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
* *
* @see [keyValueStore] * @see [keyValueStore]
*/ */

View File

@@ -1,6 +1,6 @@
package dev.inmo.micro_utils.repos.exposed package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.repos.CRUDRepo import dev.inmo.micro_utils.repos.StandardCRUDRepo
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>,
CRUDRepo<ObjectType, IdType, InputValueType> StandardCRUDRepo<ObjectType, IdType, InputValueType>

View File

@@ -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.ReadCRUDRepo import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
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
) : ) :
ReadCRUDRepo<ObjectType, IdType>, ReadStandardCRUDRepo<ObjectType, IdType>,
ExposedCRUDRepo<ObjectType, IdType>, ExposedCRUDRepo<ObjectType, IdType>,
Table(tableName) Table(tableName)
{ {

View File

@@ -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.WriteCRUDRepo import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
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,11 +15,17 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
) : ) :
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName), AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
ExposedCRUDRepo<ObjectType, IdType>, ExposedCRUDRepo<ObjectType, IdType>,
WriteCRUDRepo<ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<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)
protected val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize) protected val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize)
@Deprecated("Renamed", ReplaceWith("_newObjectsFlow"))
protected val newObjectsChannel = _newObjectsFlow
@Deprecated("Renamed", ReplaceWith("_updatedObjectsFlow"))
protected val updateObjectsChannel = _updatedObjectsFlow
@Deprecated("Renamed", ReplaceWith("_deletedObjectsIdsFlow"))
protected val deleteObjectsIdsChannel = _deletedObjectsIdsFlow
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow() override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow() override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()

View File

@@ -1,7 +1,9 @@
package dev.inmo.micro_utils.repos.exposed.keyvalue package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.StandardKeyValueRepo
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
@@ -11,7 +13,7 @@ open class ExposedKeyValueRepo<Key, Value>(
keyColumnAllocator: ColumnAllocator<Key>, keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>, valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null tableName: String? = null
) : KeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>( ) : StandardKeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>(
database, database,
keyColumnAllocator, keyColumnAllocator,
valueColumnAllocator, valueColumnAllocator,

View File

@@ -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.ReadKeyValueRepo import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
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
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") { ) : ReadStandardKeyValueRepo<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)

View File

@@ -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.KeyValuesRepo import dev.inmo.micro_utils.repos.OneToManyKeyValueRepo
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 ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value> typealias ExposedKeyValuesRepo<Key, Value> = ExposedOneToManyKeyValueRepo<Key, Value>
open class ExposedKeyValuesRepo<Key, Value>( open class ExposedOneToManyKeyValueRepo<Key, Value>(
database: Database, database: Database,
keyColumnAllocator: ColumnAllocator<Key>, keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>, valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null tableName: String? = null
) : KeyValuesRepo<Key, Value>, ExposedReadKeyValuesRepo<Key, Value>( ) : OneToManyKeyValueRepo<Key, Value>, ExposedReadOneToManyKeyValueRepo<Key, Value>(
database, database,
keyColumnAllocator, keyColumnAllocator,
valueColumnAllocator, valueColumnAllocator,

View File

@@ -1,19 +1,20 @@
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.ReadKeyValuesRepo import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
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 ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value> typealias ExposedReadKeyValuesRepo<Key, Value> = ExposedReadOneToManyKeyValueRepo<Key, Value>
open class ExposedReadKeyValuesRepo<Key, Value>( open class ExposedReadOneToManyKeyValueRepo<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
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") { ) : ReadOneToManyKeyValueRepo<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()

View File

@@ -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()
) : ReadCRUDRepo<ObjectType, IdType> { ) : ReadStandardCRUDRepo<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()
) : WriteCRUDRepo<ObjectType, IdType, InputValueType> { ) : WriteStandardCRUDRepo<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,30 +68,25 @@ 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>
) : CRUDRepo<ObjectType, IdType, InputValueType>, ) : StandardCRUDRepo<ObjectType, IdType, InputValueType>,
ReadCRUDRepo<ObjectType, IdType> by ReadMapCRUDRepo(map), ReadStandardCRUDRepo<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 MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType, updateCallback: suspend (newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType> createCallback: suspend (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 = map.updateCallback(newValue, id, old) ): ObjectType = updateCallback(newValue, id, old)
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = map.createCallback(newValue) override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = 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 MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType, updateCallback: suspend (newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType> createCallback: suspend (newValue: InputValueType) -> Pair<IdType, ObjectType>
) = MapCRUDRepo(this, updateCallback, createCallback) ) = MapCRUDRepo(this, updateCallback, createCallback)

View File

@@ -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()
) : ReadKeyValueRepo<Key, Value> { ) : ReadStandardKeyValueRepo<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()
) : WriteKeyValueRepo<Key, Value> { ) : WriteStandardKeyValueRepo<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,20 +78,19 @@ class WriteMapKeyValueRepo<Key, Value>(
} }
override suspend fun unsetWithValues(toUnset: List<Value>) { override suspend fun unsetWithValues(toUnset: List<Value>) {
map.mapNotNull { (k, v) -> map.forEach {
k.takeIf { v in toUnset } if (it.value in toUnset) {
}.forEach { map.remove(it.key)
map.remove(it) _onValueRemoved.emit(it.key)
_onValueRemoved.emit(it) }
} }
} }
} }
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
class MapKeyValueRepo<Key, Value>( class MapKeyValueRepo<Key, Value>(
private val map: MutableMap<Key, Value> = mutableMapOf() private val map: MutableMap<Key, Value> = mutableMapOf()
) : KeyValueRepo<Key, Value>, ) : StandardKeyValueRepo<Key, Value>,
ReadKeyValueRepo<Key, Value> by ReadMapKeyValueRepo(map), ReadStandardKeyValueRepo<Key, Value> by ReadMapKeyValueRepo(map),
WriteKeyValueRepo<Key, Value> by WriteMapKeyValueRepo(map) WriteStandardKeyValueRepo<Key, Value> by WriteMapKeyValueRepo(map)
fun <K, V> MutableMap<K, V>.asKeyValueRepo(): KeyValueRepo<K, V> = MapKeyValueRepo(this) fun <K, V> MutableMap<K, V>.asKeyValueRepo(): StandardKeyValueRepo<K, V> = MapKeyValueRepo(this)

View File

@@ -5,9 +5,9 @@ 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 MapReadKeyValuesRepo<Key, Value>( class MapReadOneToManyKeyValueRepo<Key, Value>(
private val map: Map<Key, List<Value>> = emptyMap() private val map: Map<Key, List<Value>> = emptyMap()
) : ReadKeyValuesRepo<Key, Value> { ) : ReadOneToManyKeyValueRepo<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 +53,9 @@ class MapReadKeyValuesRepo<Key, Value>(
override suspend fun count(): Long = map.size.toLong() override suspend fun count(): Long = map.size.toLong()
} }
class MapWriteKeyValuesRepo<Key, Value>( class MapWriteOneToManyKeyValueRepo<Key, Value>(
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf() private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
) : WriteKeyValuesRepo<Key, Value> { ) : WriteOneToManyKeyValueRepo<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,10 +80,6 @@ class MapWriteKeyValuesRepo<Key, Value>(
_onValueRemoved.emit(k to v) _onValueRemoved.emit(k to v)
} }
} }
if (map[k] ?.isEmpty() == true) {
map.remove(k)
_onDataCleared.emit(k)
}
} }
} }
@@ -98,13 +94,12 @@ class MapWriteKeyValuesRepo<Key, Value>(
} }
} }
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") class MapOneToManyKeyValueRepo<Key, Value>(
class MapKeyValuesRepo<Key, Value>(
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf() private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
) : KeyValuesRepo<Key, Value>, ) : OneToManyKeyValueRepo<Key, Value>,
ReadKeyValuesRepo<Key, Value> by MapReadKeyValuesRepo(map), ReadOneToManyKeyValueRepo<Key, Value> by MapReadOneToManyKeyValueRepo(map),
WriteKeyValuesRepo<Key, Value> by MapWriteKeyValuesRepo(map) WriteOneToManyKeyValueRepo<Key, Value> by MapWriteOneToManyKeyValueRepo(map)
fun <K, V> MutableMap<K, List<V>>.asKeyValuesRepo(): KeyValuesRepo<K, V> = MapKeyValuesRepo( fun <K, V> MutableMap<K, List<V>>.asOneToManyKeyValueRepo(): OneToManyKeyValueRepo<K, V> = MapOneToManyKeyValueRepo(
map { (k, v) -> k to v.toMutableList() }.toMap().toMutableMap() map { (k, v) -> k to v.toMutableList() }.toMap().toMutableMap()
) )

View File

@@ -1,106 +0,0 @@
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)

View File

@@ -1,97 +0,0 @@
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)
}

View File

@@ -0,0 +1,66 @@
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.pagination.*
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.client.HttpClient
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
class KtorReadStandardCrudRepo<ObjectType, IdType> (
private val baseUrl: String,
private val unifiedRequester: UnifiedRequester,
private val objectsSerializer: KSerializer<ObjectType>,
private val objectsSerializerNullable: KSerializer<ObjectType?>,
private val idsSerializer: KSerializer<IdType>
) : ReadStandardCRUDRepo<ObjectType, IdType> {
private val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
constructor(
baseUrl: String,
client: HttpClient,
objectsSerializer: KSerializer<ObjectType>,
objectsSerializerNullable: KSerializer<ObjectType?>,
idsSerializer: KSerializer<IdType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) : this (
baseUrl, UnifiedRequester(client, serialFormat), objectsSerializer, objectsSerializerNullable, idsSerializer
)
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = unifiedRequester.uniget(
buildStandardUrl(baseUrl, getByPaginationRouting, pagination.asUrlQueryParts),
paginationResultSerializer
)
override suspend fun getById(id: IdType): ObjectType? = unifiedRequester.uniget(
buildStandardUrl(
baseUrl,
getByIdRouting,
mapOf(
"id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
)
),
objectsSerializerNullable
)
override suspend fun contains(id: IdType): Boolean = unifiedRequester.uniget(
buildStandardUrl(
baseUrl,
containsRouting,
mapOf(
"id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
)
),
Boolean.serializer()
)
override suspend fun count(): Long = unifiedRequester.uniget(
buildStandardUrl(
baseUrl,
countRouting
),
Long.serializer()
)
}

View File

@@ -0,0 +1,46 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.repos.*
import io.ktor.client.HttpClient
import kotlinx.serialization.KSerializer
class KtorStandardCrudRepo<ObjectType, IdType, InputValue> (
baseUrl: String,
baseSubpart: String,
unifiedRequester: UnifiedRequester,
objectsSerializer: KSerializer<ObjectType>,
objectsNullableSerializer: KSerializer<ObjectType?>,
inputsSerializer: KSerializer<InputValue>,
idsSerializer: KSerializer<IdType>
) : StandardCRUDRepo<ObjectType, IdType, InputValue>,
ReadStandardCRUDRepo<ObjectType, IdType> by KtorReadStandardCrudRepo(
"$baseUrl/$baseSubpart",
unifiedRequester,
objectsSerializer,
objectsNullableSerializer,
idsSerializer
),
WriteStandardCRUDRepo<ObjectType, IdType, InputValue> by KtorWriteStandardCrudRepo(
"$baseUrl/$baseSubpart",
unifiedRequester,
objectsSerializer,
objectsNullableSerializer,
inputsSerializer,
idsSerializer
) {
constructor(
baseUrl: String,
baseSubpart: String,
client: HttpClient,
objectsSerializer: KSerializer<ObjectType>,
objectsNullableSerializer: KSerializer<ObjectType?>,
inputsSerializer: KSerializer<InputValue>,
idsSerializer: KSerializer<IdType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) : this(
baseUrl, baseSubpart, UnifiedRequester(client, serialFormat), objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer
)
}

View File

@@ -1,85 +0,0 @@
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() }
)
}
}

Some files were not shown because too many files have changed in this diff Show More