Compare commits

..

35 Commits

Author SHA1 Message Date
fc48446ec4 temporarily remove fallback repo 2023-01-29 13:15:56 +06:00
3644b83ac6 fill changelog 2023-01-29 13:08:41 +06:00
cd73791b6f add docs to the MutableState.asState 2023-01-29 12:46:27 +06:00
03de71df2e add docs to as compose state 2023-01-29 12:43:51 +06:00
83d5d3faf4 improve flow as state functionality 2023-01-29 12:28:22 +06:00
0c8bec4c89 add extension actualizeAll 2023-01-27 15:15:27 +06:00
7fc93817c1 start to add fallback repos 2023-01-27 14:45:31 +06:00
d0a00031a1 Update CHANGELOG.md 2023-01-26 22:54:17 +06:00
6ebc5aa0c2 potential fix of state value change in asMutableComposeState 2023-01-23 15:21:47 +06:00
8a6b4bb49e add alsoIfTrue/alsoIfFalse/letIfTrue/letIfFalse 2023-01-22 23:01:06 +06:00
20799b9a3e add repeat on failure with callback 2023-01-22 22:43:26 +06:00
ec3afc615c add serializable diff 2023-01-20 13:29:45 +06:00
da692ccfc3 add ifTrue/ifFalse 2023-01-19 20:21:35 +06:00
53b89f3a18 start 0.16.7 2023-01-19 20:19:21 +06:00
58cded28d3 Merge pull request #217 from InsanusMokrassar/0.16.6
0.16.6
2023-01-18 22:28:44 +06:00
592c5f3732 Column extensions eqOrIsNull and neqOrIsNotNull 2023-01-14 20:17:57 +06:00
f44a78a5f5 small improvement of openBaseWebSocketFlow 2023-01-14 15:20:52 +06:00
e0bdd5dfdc improvements in microutils 2023-01-14 13:22:07 +06:00
99c0f06b72 repos update 2023-01-14 13:15:59 +06:00
66fc6df3d7 Improvements in 'StartLauncherPlugin#start' methods 2023-01-08 13:52:53 +06:00
a36425a905 start 0.16.6 2023-01-08 13:51:34 +06:00
d920fee6d4 Merge pull request #216 from InsanusMokrassar/0.16.5
0.16.5
2023-01-04 20:21:15 +06:00
23590be5de Update CHANGELOG.md 2023-01-04 20:20:25 +06:00
94acc3c93b Update libs.versions.toml 2023-01-04 08:56:46 +06:00
5616326a3b start 0.16.5 2023-01-04 08:50:19 +06:00
7601860c5c Merge pull request #214 from InsanusMokrassar/0.16.4
0.16.4
2022-12-27 19:03:35 +06:00
8b43d785cc update changelog 2022-12-27 18:51:01 +06:00
b62d3a0b7d launchInCurrentThread 2022-12-27 18:50:41 +06:00
fad73c7213 start 0.16.4 2022-12-27 18:49:04 +06:00
2403c7c2b0 Merge pull request #213 from InsanusMokrassar/0.16.3
0.16.3
2022-12-25 13:24:37 +06:00
fa090bf920 Update StartLauncherPlugin.kt 2022-12-25 10:29:16 +06:00
a83ee86340 improvements in startup launcher 2022-12-25 10:26:44 +06:00
204955bcce add template for startup and readme 2022-12-19 17:51:05 +06:00
ee56e9543a start 0.16.3 2022-12-17 17:27:15 +06:00
96fdff6ffd Merge pull request #212 from InsanusMokrassar/0.16.2
0.16.2
2022-12-16 13:59:47 +06:00
57 changed files with 1018 additions and 131 deletions

View File

@@ -1,5 +1,55 @@
# Changelog # Changelog
## 0.16.7
* `Common`:
* New extensions `ifTrue`/`ifFalse`/`alsoIfTrue`/`alsoIfFalse`/`letIfTrue`/`letIfFalse`
* `Diff` now is serializable
* Add `IndexedValue` serializer
* `repeatOnFailure` extending: now you may pass any lambda to check if continue to try/do something
* `Compose`:
* New extension `MutableState.asState`
* `Coroutines`:
* `Compose`:
* All the `Flow` conversations to compose `State`/`MutableState`/`SnapshotStateList`/`List` got several new
parameters
* `Flow.toMutableState` now is deprecated in favor to `asMutableComposeState`
* `Repos`:
* `Cache`:
* New type `FullCacheRepo`
* New type `CommonCacheRepo`
* `CacheRepo` got `invalidate` method. It will fully reload `FullCacheRepo` and just clear `CommonCacheRepo`
* New extensions `KVCache.actualizeAll`
## 0.16.6
* `Startup`:
* `Launcher`:
* Improvements in `StartLauncherPlugin#start` methods
* Add opportunity to pass second argument on `JVM` platform as log level
* `Repos`:
* `Ktor`:
* `Client`:
* All clients repos got opportunity to customize their flows
* `Exposed`:
* Extensions `eqOrIsNull` and `neqOrIsNotNull` for `Column`
## 0.16.5
* `Versions`:
* `Ktor`: `2.2.1` -> `2.2.2`
## 0.16.4
* `Coroutines`:
* Create `launchInCurrentThread`
## 0.16.3
* `Startup`:
* `Launcher`:
* All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now
## 0.16.2 ## 0.16.2
* `Versions`: * `Versions`:

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.common.compose
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
/**
* Converts current [MutableState] to immutable [State] using [derivedStateOf]
*/
fun <T> MutableState<T>.asState(): State<T> = derivedStateOf { this.value }

View File

@@ -2,6 +2,8 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
import kotlinx.serialization.Serializable
private inline fun <T> getObject( private inline fun <T> getObject(
additional: MutableList<T>, additional: MutableList<T>,
iterator: Iterator<T> iterator: Iterator<T>
@@ -24,13 +26,14 @@ private inline fun <T> getObject(
* *
* @see calculateDiff * @see calculateDiff
*/ */
@Serializable
data class Diff<T> internal constructor( data class Diff<T> internal constructor(
val removed: List<IndexedValue<T>>, val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
/** /**
* Old-New values pairs * Old-New values pairs
*/ */
val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>, val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
val added: List<IndexedValue<T>> val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
) )
private inline fun <T> performChanges( private inline fun <T> performChanges(

View File

@@ -0,0 +1,43 @@
package dev.inmo.micro_utils.common
inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
return if (this) {
block()
} else {
null
}
}
inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
return if (this) {
null
} else {
block()
}
}
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean {
letIfTrue(block)
return this
}
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean {
letIfFalse(block)
return this
}
inline fun <T> Boolean.ifTrue(block: () -> T): T? {
return if (this) {
block()
} else {
null
}
}
inline fun <T> Boolean.ifFalse(block: () -> T): T? {
return if (this) {
null
} else {
block()
}
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.common
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.builtins.PairSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
class IndexedValueSerializer<T>(private val subSerializer: KSerializer<T>) : KSerializer<IndexedValue<T>> {
private val originalSerializer = PairSerializer(Int.serializer(), subSerializer)
override val descriptor: SerialDescriptor
get() = originalSerializer.descriptor
override fun deserialize(decoder: Decoder): IndexedValue<T> {
val pair = originalSerializer.deserialize(decoder)
return IndexedValue(
pair.first,
pair.second
)
}
override fun serialize(encoder: Encoder, value: IndexedValue<T>) {
originalSerializer.serialize(
encoder,
Pair(value.index, value.value)
)
}
}

View File

@@ -1,5 +1,27 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
/**
* Executes the given [action] until getting of successful result specified number of [times].
*
* A zero-based index of current iteration is passed as a parameter to [action].
*/
inline fun <R> repeatOnFailure(
onFailure: (Throwable) -> Boolean,
action: () -> R
): Result<R> {
do {
runCatching {
action()
}.onFailure {
if (!onFailure(it)) {
return Result.failure(it)
}
}.onSuccess {
return Result.success(it)
}
} while (true)
}
/** /**
* Executes the given [action] until getting of successful result specified number of [times]. * Executes the given [action] until getting of successful result specified number of [times].
* *
@@ -10,12 +32,23 @@ inline fun <R> repeatOnFailure(
onEachFailure: (Throwable) -> Unit = {}, onEachFailure: (Throwable) -> Unit = {},
action: (Int) -> R action: (Int) -> R
): Optional<R> { ): Optional<R> {
repeat(times) { var i = 0
runCatching { val result = repeatOnFailure(
action(it) {
}.onFailure(onEachFailure).onSuccess { onEachFailure(it)
return Optional.presented(it) if (i < times) {
i++
true
} else {
false
} }
} }
return Optional.absent() ) {
action(i)
}
return if (result.isSuccess) {
Optional.presented(result.getOrThrow())
} else {
Optional.absent()
}
} }

View File

@@ -22,6 +22,7 @@ kotlin {
dependencies { dependencies {
api libs.kt.coroutines.android api libs.kt.coroutines.android
} }
dependsOn(jvmMain)
} }
} }
} }

View File

@@ -3,24 +3,58 @@ package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import dev.inmo.micro_utils.common.applyDiff import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.coroutines.ExceptionHandler
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
/**
* Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList]
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asMutableComposeListState( inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
scope: CoroutineScope scope: CoroutineScope,
useContextOnChange: CoroutineContext? = Dispatchers.Main,
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
): SnapshotStateList<T> { ): SnapshotStateList<T> {
val state = mutableStateListOf<T>() val state = mutableStateListOf<T>()
subscribeSafelyWithoutExceptions(scope) { val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let {
{
withContext(useContextOnChange) {
state.applyDiff(it) state.applyDiff(it)
} }
}
} ?: {
state.applyDiff(it)
}
subscribeSafelyWithoutExceptions(scope, onException, changeBlock)
return state return state
} }
/**
* In fact, it is just classcast of [asMutableComposeListState] to [List]
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [List]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*
* @return Changing in time [List] which follow [Flow] values
*/
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asComposeList( inline fun <reified T> Flow<List<T>>.asComposeList(
scope: CoroutineScope scope: CoroutineScope,
): List<T> = asMutableComposeListState(scope) useContextOnChange: CoroutineContext? = Dispatchers.Main,
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
): List<T> = asMutableComposeListState(scope, useContextOnChange, onException)

View File

@@ -1,35 +1,94 @@
package dev.inmo.micro_utils.coroutines.compose package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.inmo.micro_utils.common.compose.asState
import dev.inmo.micro_utils.coroutines.ExceptionHandler
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
import dev.inmo.micro_utils.coroutines.doInUI
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
/**
* Will map [this] [Flow] as [MutableState]. Returned [MutableState] WILL NOT change source [Flow]
*
* @param initial First value which will be passed to the result [MutableState]
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
fun <T> Flow<T>.asMutableComposeState( fun <T> Flow<T>.asMutableComposeState(
initial: T, initial: T,
scope: CoroutineScope scope: CoroutineScope,
useContextOnChange: CoroutineContext? = Dispatchers.Main,
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): MutableState<T> { ): MutableState<T> {
val state = mutableStateOf(initial) val state = mutableStateOf(initial)
subscribeSafelyWithoutExceptions(scope) { state.value = it } val changeBlock: suspend (T) -> Unit = useContextOnChange ?.let {
{
withContext(useContextOnChange) {
state.value = it
}
}
} ?: {
state.value = it
}
subscribeSafelyWithoutExceptions(scope, onException, block = changeBlock)
return state return state
} }
/**
* Will map [this] [StateFlow] as [MutableState]. Returned [MutableState] WILL NOT change source [StateFlow].
* This conversation will pass its [StateFlow.value] as the first value
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asMutableComposeState( inline fun <T> StateFlow<T>.asMutableComposeState(
scope: CoroutineScope scope: CoroutineScope,
): MutableState<T> = asMutableComposeState(value, scope) useContextOnChange: CoroutineContext? = Dispatchers.Main,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): MutableState<T> = asMutableComposeState(value, scope, useContextOnChange, onException)
/**
* Will create [MutableState] using [asMutableComposeState] and use [asState] to convert it as immutable state
*
* @param initial First value which will be passed to the result [State]
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
fun <T> Flow<T>.asComposeState( fun <T> Flow<T>.asComposeState(
initial: T, initial: T,
scope: CoroutineScope scope: CoroutineScope,
useContextOnChange: CoroutineContext? = Dispatchers.Main,
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): State<T> { ): State<T> {
val state = asMutableComposeState(initial, scope) val state = asMutableComposeState(initial, scope, useContextOnChange, onException)
return derivedStateOf { state.value } return state.asState()
} }
/**
* Will map [this] [StateFlow] as [State]. This conversation will pass its [StateFlow.value] as the first value
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asComposeState( inline fun <T> StateFlow<T>.asComposeState(
scope: CoroutineScope scope: CoroutineScope,
): State<T> = asComposeState(value, scope) useContextOnChange: CoroutineContext? = Dispatchers.Main,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): State<T> = asComposeState(value, scope, useContextOnChange, onException)

View File

@@ -7,17 +7,15 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(initial, scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
fun <T> Flow<T>.toMutableState( fun <T> Flow<T>.toMutableState(
initial: T, initial: T,
scope: CoroutineScope scope: CoroutineScope
): MutableState<T> { ): MutableState<T> = asMutableComposeState(initial, scope)
val state = mutableStateOf(initial)
subscribeSafelyWithoutExceptions(scope) { state.value = it }
return state
}
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.toMutableState( inline fun <T> StateFlow<T>.toMutableState(
scope: CoroutineScope scope: CoroutineScope
): MutableState<T> = toMutableState(value, scope) ): MutableState<T> = asMutableComposeState(scope)

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T {
val scope = CoroutineScope(Dispatchers.Unconfined)
return scope.launchSynchronously(block)
}

View File

@@ -6,7 +6,7 @@ fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T
var result: Result<T>? = null var result: Result<T>? = null
val objectToSynchronize = Object() val objectToSynchronize = Object()
synchronized(objectToSynchronize) { synchronized(objectToSynchronize) {
launch { launch(start = CoroutineStart.UNDISPATCHED) {
result = safelyWithResult(block) result = safelyWithResult(block)
}.invokeOnCompletion { }.invokeOnCompletion {
synchronized(objectToSynchronize) { synchronized(objectToSynchronize) {

View File

@@ -0,0 +1,47 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlin.test.Test
import kotlin.test.assertEquals
class LaunchInCurrentThreadTests {
@Test
fun simpleTestThatLaunchInCurrentThreadWorks() {
val expectedResult = 10
val result = launchInCurrentThread {
expectedResult
}
assertEquals(expectedResult, result)
}
@Test
fun simpleTestThatSeveralLaunchInCurrentThreadWorks() {
val testData = 0 until 100
testData.forEach {
val result = launchInCurrentThread {
it
}
assertEquals(it, result)
}
}
@Test
fun simpleTestThatLaunchInCurrentThreadWillCorrectlyHandleSuspensionsWorks() {
val testData = 0 until 100
suspend fun test(data: Any): Any {
return withContext(Dispatchers.Default) {
delay(1)
data
}
}
testData.forEach {
val result = launchInCurrentThread {
test(it)
}
assertEquals(it, result)
}
}
}

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.16.2 version=0.16.7
android_code_version=170 android_code_version=175

View File

@@ -13,7 +13,7 @@ jb-dokka = "1.7.20"
klock = "3.4.0" klock = "3.4.0"
uuid = "0.6.0" uuid = "0.6.0"
ktor = "2.2.1" ktor = "2.2.2"
gh-release = "2.4.1" gh-release = "2.4.1"

View File

@@ -19,7 +19,7 @@ import kotlinx.coroutines.isActive
* connection. Must return true in case if must be reconnected. By default always reconnecting * connection. Must return true in case if must be reconnected. By default always reconnecting
*/ */
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties") @Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
inline fun <reified T : Any> openBaseWebSocketFlow( inline fun <T : Any> openBaseWebSocketFlow(
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true }, noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
): Flow<T> { ): Flow<T> {
@@ -57,7 +57,7 @@ inline fun <reified T : Any> HttpClient.openWebSocketFlow(
): Flow<T> { ): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow") pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
return openBaseWebSocketFlow<T>(checkReconnection) { return openBaseWebSocketFlow(checkReconnection) {
val block: suspend DefaultClientWebSocketSession.() -> Unit = { val block: suspend DefaultClientWebSocketSession.() -> Unit = {
while (isActive) { while (isActive) {
send(receiveDeserialized<T>()) send(receiveDeserialized<T>())

View File

@@ -10,12 +10,14 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>, protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
protected open val kvCache: KVCache<IdType, ObjectType>, protected open val kvCache: KVCache<IdType, ObjectType>,
protected open val idGetter: (ObjectType) -> IdType protected open val idGetter: (ObjectType) -> IdType
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CacheRepo { ) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CommonCacheRepo {
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also { override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
kvCache.set(id, it) kvCache.set(id, it)
}) })
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id) override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
override suspend fun invalidate() = kvCache.clear()
} }
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached( fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
@@ -28,7 +30,7 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
protected open val kvCache: KVCache<IdType, ObjectType>, protected open val kvCache: KVCache<IdType, ObjectType>,
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default), protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
protected open val idGetter: (ObjectType) -> IdType protected open val idGetter: (ObjectType) -> IdType
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CacheRepo { ) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CommonCacheRepo {
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
@@ -72,6 +74,8 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
return created return created
} }
override suspend fun invalidate() = kvCache.clear()
} }
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching( fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(

View File

@@ -1,3 +1,5 @@
package dev.inmo.micro_utils.repos.cache package dev.inmo.micro_utils.repos.cache
interface CacheRepo interface CacheRepo {
suspend fun invalidate()
}

View File

@@ -0,0 +1,7 @@
package dev.inmo.micro_utils.repos.cache
/**
* Any inheritor of this should work with next logic: try to take data from some [dev.inmo.micro_utils.repos.cache.cache.KVCache] and,
* if not exists, take from origin and save to the cache for future reuse
*/
interface CommonCacheRepo : CacheRepo

View File

@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.*
open class ReadKeyValueCacheRepo<Key,Value>( open class ReadKeyValueCacheRepo<Key,Value>(
protected open val parentRepo: ReadKeyValueRepo<Key, Value>, protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
protected open val kvCache: KVCache<Key, Value>, protected open val kvCache: KVCache<Key, Value>,
) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo { ) : ReadKeyValueRepo<Key,Value> by parentRepo, CommonCacheRepo {
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) } override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key) override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
@@ -23,6 +23,8 @@ open class ReadKeyValueCacheRepo<Key,Value>(
) )
} }
} }
override suspend fun invalidate() = kvCache.clear()
} }
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
@@ -33,9 +35,11 @@ open class KeyValueCacheRepo<Key,Value>(
parentRepo: KeyValueRepo<Key, Value>, parentRepo: KeyValueRepo<Key, Value>,
kvCache: KVCache<Key, Value>, kvCache: KVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CacheRepo { ) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CommonCacheRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope) protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope) protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
override suspend fun invalidate() = kvCache.clear()
} }
fun <Key, Value> KeyValueRepo<Key, Value>.cached( fun <Key, Value> KeyValueRepo<Key, Value>.cached(

View File

@@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.*
open class ReadKeyValuesCacheRepo<Key,Value>( open class ReadKeyValuesCacheRepo<Key,Value>(
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>, protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
protected open val kvCache: KVCache<Key, List<Value>> protected open val kvCache: KVCache<Key, List<Value>>
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CacheRepo { ) : ReadKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> { override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return getAll(k, reversed).paginate( return getAll(k, reversed).paginate(
pagination pagination
@@ -30,6 +30,8 @@ open class ReadKeyValuesCacheRepo<Key,Value>(
} }
}) })
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k) override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
override suspend fun invalidate() = kvCache.clear()
} }
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
@@ -40,7 +42,7 @@ open class KeyValuesCacheRepo<Key,Value>(
parentRepo: KeyValuesRepo<Key, Value>, parentRepo: KeyValuesRepo<Key, Value>,
kvCache: KVCache<Key, List<Value>>, kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo { ) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) -> protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) ->
kvCache.set( kvCache.set(
k, k,
@@ -56,6 +58,8 @@ open class KeyValuesCacheRepo<Key,Value>(
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { protected val onDataClearedJob = parentRepo.onDataCleared.onEach {
kvCache.unset(it) kvCache.unset(it)
}.launchIn(scope) }.launchIn(scope)
override suspend fun invalidate() = kvCache.clear()
} }
fun <Key, Value> KeyValuesRepo<Key, Value>.cached( fun <Key, Value> KeyValuesRepo<Key, Value>.cached(

View File

@@ -8,6 +8,7 @@ import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.* import dev.inmo.micro_utils.repos.cache.*
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.cache.KVCache import dev.inmo.micro_utils.repos.cache.cache.KVCache
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -32,12 +33,7 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
} }
protected open suspend fun actualizeAll() { protected open suspend fun actualizeAll() {
kvCache.clear() kvCache.actualizeAll(parentRepo)
doForAllWithNextPaging {
parentRepo.getByPagination(it).also {
kvCache.set(it.results.associateBy { idGetter(it) })
}
}
} }
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize( override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
@@ -69,6 +65,10 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
{ getById(id) }, { getById(id) },
{ it ?.let { set(idGetter(it), it) } } { it ?.let { set(idGetter(it), it) } }
) )
override suspend fun invalidate() {
actualizeAll()
}
} }
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached( fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
@@ -92,7 +92,11 @@ open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
scope, scope,
idGetter idGetter
), ),
CRUDRepo<ObjectType, IdType, InputValueType> CRUDRepo<ObjectType, IdType, InputValueType> {
override suspend fun invalidate() {
actualizeAll()
}
}
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached( fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
kvCache: FullKVCache<IdType, ObjectType>, kvCache: FullKVCache<IdType, ObjectType>,

View File

@@ -5,6 +5,7 @@ import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import dev.inmo.micro_utils.repos.pagination.getAll import dev.inmo.micro_utils.repos.pagination.getAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -68,6 +69,10 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
{ parentRepo.keys(v, pagination, reversed) }, { parentRepo.keys(v, pagination, reversed) },
{ if (it.results.isNotEmpty()) actualizeAll() } { if (it.results.isNotEmpty()) actualizeAll() }
) )
override suspend fun invalidate() {
actualizeAll()
}
} }
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
@@ -81,6 +86,10 @@ open class FullWriteKeyValueCacheRepo<Key,Value>(
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo { ) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope) protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope) protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
override suspend fun invalidate() {
kvCache.clear()
}
} }
fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching( fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
@@ -89,13 +98,17 @@ fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
) = FullWriteKeyValueCacheRepo(this, kvCache, scope) ) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
open class FullKeyValueCacheRepo<Key,Value>( open class FullKeyValueCacheRepo<Key,Value>(
parentRepo: KeyValueRepo<Key, Value>, override val parentRepo: KeyValueRepo<Key, Value>,
kvCache: FullKVCache<Key, Value>, kvCache: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope), ) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
KeyValueRepo<Key,Value>, KeyValueRepo<Key,Value>,
ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(parentRepo, kvCache) { ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(parentRepo, kvCache) {
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset) override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
override suspend fun invalidate() {
kvCache.actualizeAll(parentRepo)
}
} }
fun <Key, Value> KeyValueRepo<Key, Value>.cached( fun <Key, Value> KeyValueRepo<Key, Value>.cached(

View File

@@ -5,6 +5,7 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.* import dev.inmo.micro_utils.pagination.utils.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@@ -33,8 +34,7 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
} }
protected open suspend fun actualizeAll() { protected open suspend fun actualizeAll() {
doAllWithCurrentPaging { kvCache.keys(it).also { kvCache.unset(it.results) } } kvCache.actualizeAll(parentRepo)
kvCache.set(parentRepo.getAll())
} }
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> { override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
@@ -102,6 +102,9 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
{ if (it.results.isNotEmpty()) actualizeAll() } { if (it.results.isNotEmpty()) actualizeAll() }
) )
override suspend fun invalidate() {
actualizeAll()
}
} }
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
@@ -125,6 +128,10 @@ open class FullWriteKeyValuesCacheRepo<Key,Value>(
kvCache.get(it.first) ?.minus(it.second) ?: return@onEach kvCache.get(it.first) ?.minus(it.second) ?: return@onEach
) )
}.launchIn(scope) }.launchIn(scope)
override suspend fun invalidate() {
kvCache.clear()
}
} }
fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching( fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
@@ -133,7 +140,7 @@ fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope) ) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
open class FullKeyValuesCacheRepo<Key,Value>( open class FullKeyValuesCacheRepo<Key,Value>(
parentRepo: KeyValuesRepo<Key, Value>, override val parentRepo: KeyValuesRepo<Key, Value>,
kvCache: FullKVCache<Key, List<Value>>, kvCache: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope), ) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),
@@ -146,6 +153,10 @@ open class FullKeyValuesCacheRepo<Key,Value>(
} }
} }
} }
override suspend fun invalidate() {
kvCache.actualizeAll(parentRepo)
}
} }
fun <Key, Value> KeyValuesRepo<Key, Value>.caching( fun <Key, Value> KeyValuesRepo<Key, Value>.caching(

View File

@@ -0,0 +1,61 @@
package dev.inmo.micro_utils.repos.cache.util
import dev.inmo.micro_utils.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import dev.inmo.micro_utils.repos.cache.cache.KVCache
import dev.inmo.micro_utils.repos.set
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
getAll: () -> Map<K, V>
) {
clear()
set(getAll())
}
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
repo: ReadKeyValueRepo<K, V>
) {
clear()
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
val initPagination = FirstPagePagination(count)
doForAllWithNextPaging(initPagination) {
keys(it).also {
set(
it.results.mapNotNull { k -> repo.get(k) ?.let { k to it } }
)
}
}
}
suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
repo: ReadKeyValuesRepo<K, V>
) {
clear()
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
val initPagination = FirstPagePagination(count)
doForAllWithNextPaging(initPagination) {
keys(it).also {
set(
it.results.associateWith { k -> repo.getAll(k) }
)
}
}
}
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
repo: ReadCRUDRepo<V, K>
) {
clear()
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
val initPagination = FirstPagePagination(count)
doForAllWithNextPaging(initPagination) {
keys(it).also {
set(
it.results.mapNotNull { k -> repo.getById(k) ?.let { k to it } }
)
}
}
}

View File

@@ -93,6 +93,10 @@ suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
vararg toSet: Pair<Key, Value> vararg toSet: Pair<Key, Value>
) = set(toSet.toMap()) ) = set(toSet.toMap())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
toSet: List<Pair<Key, Value>>
) = set(toSet.toMap())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set( suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
k: Key, v: Value k: Key, v: Value
) = set(k to v) ) = set(k to v)
@@ -125,7 +129,11 @@ interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValue
* By default, will remove all the data of current repo using [doAllWithCurrentPaging], [keys] and [unset] * By default, will remove all the data of current repo using [doAllWithCurrentPaging], [keys] and [unset]
*/ */
suspend fun clear() { suspend fun clear() {
doAllWithCurrentPaging { keys(it).also { unset(it.results) } } var count: Int
do {
count = count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
keys(FirstPagePagination(count)).also { unset(it.results) }
} while(count > 0)
} }
} }
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value> typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>

View File

@@ -0,0 +1,23 @@
package dev.inmo.micro_utils.repos.exposed
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNotNull
import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNull
import org.jetbrains.exposed.sql.SqlExpressionBuilder.neq
fun <T> Column<T?>.eqOrIsNull(
value: T?
) = if (value == null) {
isNull()
} else {
eq(value)
}
fun <T> Column<T?>.neqOrIsNotNull(
value: T?
) = if (value == null) {
isNotNull()
} else {
neq(value)
}

View File

@@ -1,12 +1,17 @@
package dev.inmo.micro_utils.repos.ktor.client.crud package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.client.createStandardWebsocketFlow
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.ktor.common.crud.deletedObjectsIdsFlowRouting
import dev.inmo.micro_utils.repos.ktor.common.crud.newObjectsFlowRouting
import dev.inmo.micro_utils.repos.ktor.common.crud.updatedObjectsFlowRouting
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.util.reflect.TypeInfo import io.ktor.util.reflect.TypeInfo
import io.ktor.util.reflect.typeInfo import io.ktor.util.reflect.typeInfo
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.* import kotlinx.serialization.*
class KtorCRUDRepoClient<ObjectType, IdType, InputValue> ( class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
@@ -21,6 +26,15 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
baseUrl: String, baseUrl: String,
httpClient: HttpClient, httpClient: HttpClient,
contentType: ContentType, contentType: ContentType,
newObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, newObjectsFlowRouting),
),
updatedObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
),
deletedObjectsIdsFlow: Flow<IdType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
),
noinline idSerializer: suspend (IdType) -> String noinline idSerializer: suspend (IdType) -> String
) = KtorCRUDRepoClient( ) = KtorCRUDRepoClient(
KtorReadCRUDRepoClient( KtorReadCRUDRepoClient(
@@ -35,7 +49,10 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>( KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
baseUrl, baseUrl,
httpClient, httpClient,
contentType contentType,
newObjectsFlow,
updatedObjectsFlow,
deletedObjectsIdsFlow
) )
) )
@@ -44,11 +61,23 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
subpart: String, subpart: String,
httpClient: HttpClient, httpClient: HttpClient,
contentType: ContentType, contentType: ContentType,
newObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, newObjectsFlowRouting),
),
updatedObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
),
deletedObjectsIdsFlow: Flow<IdType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
),
noinline idSerializer: suspend (IdType) -> String noinline idSerializer: suspend (IdType) -> String
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>( ) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
buildStandardUrl(baseUrl, subpart), buildStandardUrl(baseUrl, subpart),
httpClient, httpClient,
contentType, contentType,
newObjectsFlow,
updatedObjectsFlow,
deletedObjectsIdsFlow,
idSerializer idSerializer
) )
} }
@@ -80,11 +109,23 @@ inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepo
subpart: String, subpart: String,
httpClient: HttpClient, httpClient: HttpClient,
contentType: ContentType, contentType: ContentType,
newObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, newObjectsFlowRouting),
),
updatedObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
),
deletedObjectsIdsFlow: Flow<IdType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
),
noinline idSerializer: suspend (IdType) -> String noinline idSerializer: suspend (IdType) -> String
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>( ) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
buildStandardUrl(baseUrl, subpart), buildStandardUrl(baseUrl, subpart),
httpClient, httpClient,
contentType, contentType,
newObjectsFlow,
updatedObjectsFlow,
deletedObjectsIdsFlow,
idSerializer idSerializer
) )

View File

@@ -53,19 +53,22 @@ class KtorWriteCrudRepoClient<ObjectType, IdType, InputValue> (
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke( inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
baseUrl: String, baseUrl: String,
httpClient: HttpClient, httpClient: HttpClient,
contentType: ContentType contentType: ContentType,
newObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, newObjectsFlowRouting),
),
updatedObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
),
deletedObjectsIdsFlow: Flow<IdType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
),
) = KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>( ) = KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
baseUrl, baseUrl,
httpClient, httpClient,
httpClient.createStandardWebsocketFlow( newObjectsFlow,
buildStandardUrl(baseUrl, newObjectsFlowRouting), updatedObjectsFlow,
), deletedObjectsIdsFlow,
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
),
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
),
{ {
contentType(contentType) contentType(contentType)
setBody(it) setBody(it)

View File

@@ -1,10 +1,14 @@
package dev.inmo.micro_utils.repos.ktor.client.key.value package dev.inmo.micro_utils.repos.ktor.client.key.value
import dev.inmo.micro_utils.ktor.client.createStandardWebsocketFlow
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.ktor.common.key_value.onNewValueRoute
import dev.inmo.micro_utils.repos.ktor.common.key_value.onValueRemovedRoute
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.encodeURLQueryComponent import io.ktor.http.encodeURLQueryComponent
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.* import kotlinx.serialization.*
class KtorKeyValueRepoClient<Key, Value> ( class KtorKeyValueRepoClient<Key, Value> (
@@ -20,6 +24,12 @@ class KtorKeyValueRepoClient<Key, Value> (
httpClient: HttpClient, httpClient: HttpClient,
contentType: ContentType, contentType: ContentType,
noinline idSerializer: suspend (Key) -> String, noinline idSerializer: suspend (Key) -> String,
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onNewValueRoute),
),
onValueRemoved: Flow<Key> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
noinline valueSerializer: suspend (Value) -> String noinline valueSerializer: suspend (Value) -> String
) = KtorKeyValueRepoClient( ) = KtorKeyValueRepoClient(
KtorReadKeyValueRepoClient( KtorReadKeyValueRepoClient(
@@ -28,7 +38,9 @@ class KtorKeyValueRepoClient<Key, Value> (
KtorWriteKeyValueRepoClient( KtorWriteKeyValueRepoClient(
baseUrl, baseUrl,
httpClient, httpClient,
contentType contentType,
onNewValue,
onValueRemoved
) )
) )
inline operator fun <reified Key, reified Value> invoke( inline operator fun <reified Key, reified Value> invoke(
@@ -37,12 +49,20 @@ class KtorKeyValueRepoClient<Key, Value> (
httpClient: HttpClient, httpClient: HttpClient,
contentType: ContentType, contentType: ContentType,
noinline idSerializer: suspend (Key) -> String, noinline idSerializer: suspend (Key) -> String,
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onNewValueRoute),
),
onValueRemoved: Flow<Key> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
noinline valueSerializer: suspend (Value) -> String noinline valueSerializer: suspend (Value) -> String
) = KtorKeyValueRepoClient( ) = KtorKeyValueRepoClient(
buildStandardUrl(baseUrl, subpart), buildStandardUrl(baseUrl, subpart),
httpClient, httpClient,
contentType, contentType,
idSerializer, idSerializer,
onNewValue,
onValueRemoved,
valueSerializer valueSerializer
) )
} }

View File

@@ -60,17 +60,19 @@ class KtorWriteKeyValueRepoClient<Key, Value>(
inline operator fun <reified Key, reified Value> invoke( inline operator fun <reified Key, reified Value> invoke(
baseUrl: String, baseUrl: String,
httpClient: HttpClient, httpClient: HttpClient,
contentType: ContentType contentType: ContentType,
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onNewValueRoute),
),
onValueRemoved: Flow<Key> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
) = KtorWriteKeyValueRepoClient<Key, Value>( ) = KtorWriteKeyValueRepoClient<Key, Value>(
baseUrl, baseUrl,
httpClient, httpClient,
contentType, contentType,
httpClient.createStandardWebsocketFlow( onNewValue,
buildStandardUrl(baseUrl, onNewValueRoute), onValueRemoved,
),
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
typeInfo<List<Key>>(), typeInfo<List<Key>>(),
typeInfo<List<Value>>(), typeInfo<List<Value>>(),
typeInfo<Map<Key, Value>>() typeInfo<Map<Key, Value>>()

View File

@@ -1,10 +1,15 @@
package dev.inmo.micro_utils.repos.ktor.client.key.values package dev.inmo.micro_utils.repos.ktor.client.key.values
import dev.inmo.micro_utils.ktor.client.createStandardWebsocketFlow
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.onDataClearedRoute
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.onNewValueRoute
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.onValueRemovedRoute
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.encodeURLQueryComponent import io.ktor.http.encodeURLQueryComponent
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.* import kotlinx.serialization.*
class KtorKeyValuesRepoClient<Key, Value> ( class KtorKeyValuesRepoClient<Key, Value> (
@@ -20,6 +25,15 @@ class KtorKeyValuesRepoClient<Key, Value> (
httpClient: HttpClient, httpClient: HttpClient,
contentType: ContentType, contentType: ContentType,
noinline keySerializer: suspend (Key) -> String, noinline keySerializer: suspend (Key) -> String,
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onNewValueRoute),
),
onValueRemoved: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
onDataCleared: Flow<Key> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onDataClearedRoute),
),
noinline valueSerializer: suspend (Value) -> String noinline valueSerializer: suspend (Value) -> String
) = KtorKeyValuesRepoClient( ) = KtorKeyValuesRepoClient(
KtorReadKeyValuesRepoClient( KtorReadKeyValuesRepoClient(
@@ -32,7 +46,10 @@ class KtorKeyValuesRepoClient<Key, Value> (
KtorWriteKeyValuesRepoClient( KtorWriteKeyValuesRepoClient(
baseUrl, baseUrl,
httpClient, httpClient,
contentType contentType,
onNewValue,
onValueRemoved,
onDataCleared
) )
) )
inline operator fun <reified Key : Any, reified Value : Any> invoke( inline operator fun <reified Key : Any, reified Value : Any> invoke(
@@ -41,12 +58,24 @@ class KtorKeyValuesRepoClient<Key, Value> (
httpClient: HttpClient, httpClient: HttpClient,
contentType: ContentType, contentType: ContentType,
noinline keySerializer: suspend (Key) -> String, noinline keySerializer: suspend (Key) -> String,
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onNewValueRoute),
),
onValueRemoved: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
onDataCleared: Flow<Key> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onDataClearedRoute),
),
noinline valueSerializer: suspend (Value) -> String noinline valueSerializer: suspend (Value) -> String
) = KtorKeyValuesRepoClient( ) = KtorKeyValuesRepoClient(
buildStandardUrl(baseUrl, subpart), buildStandardUrl(baseUrl, subpart),
httpClient, httpClient,
contentType, contentType,
keySerializer, keySerializer,
onNewValue,
onValueRemoved,
onDataCleared,
valueSerializer valueSerializer
) )
} }
@@ -59,13 +88,25 @@ inline fun <reified Key : Any, reified Value : Any> KtorKeyValuesRepoClient(
keySerializer: SerializationStrategy<Key>, keySerializer: SerializationStrategy<Key>,
valueSerializer: SerializationStrategy<Value>, valueSerializer: SerializationStrategy<Value>,
serialFormat: StringFormat, serialFormat: StringFormat,
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onNewValueRoute),
),
onValueRemoved: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
onDataCleared: Flow<Key> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onDataClearedRoute),
),
) = KtorKeyValuesRepoClient<Key, Value>( ) = KtorKeyValuesRepoClient<Key, Value>(
baseUrl, baseUrl,
httpClient, httpClient,
contentType, contentType,
{ {
serialFormat.encodeToString(keySerializer, it).encodeURLQueryComponent() serialFormat.encodeToString(keySerializer, it).encodeURLQueryComponent()
} },
onNewValue,
onValueRemoved,
onDataCleared
) { ) {
serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent() serialFormat.encodeToString(valueSerializer, it).encodeURLQueryComponent()
} }
@@ -77,13 +118,25 @@ inline fun <reified Key : Any, reified Value : Any> KtorKeyValuesRepoClient(
keySerializer: SerializationStrategy<Key>, keySerializer: SerializationStrategy<Key>,
valueSerializer: SerializationStrategy<Value>, valueSerializer: SerializationStrategy<Value>,
serialFormat: BinaryFormat, serialFormat: BinaryFormat,
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onNewValueRoute),
),
onValueRemoved: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
onDataCleared: Flow<Key> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onDataClearedRoute),
),
) = KtorKeyValuesRepoClient<Key, Value>( ) = KtorKeyValuesRepoClient<Key, Value>(
baseUrl, baseUrl,
httpClient, httpClient,
contentType, contentType,
{ {
serialFormat.encodeHex(keySerializer, it) serialFormat.encodeHex(keySerializer, it)
} },
onNewValue,
onValueRemoved,
onDataCleared
) { ) {
serialFormat.encodeHex(valueSerializer, it) serialFormat.encodeHex(valueSerializer, it)
} }

View File

@@ -84,20 +84,23 @@ class KtorWriteKeyValuesRepoClient<Key : Any, Value : Any>(
inline operator fun <reified Key : Any, reified Value : Any> invoke( inline operator fun <reified Key : Any, reified Value : Any> invoke(
baseUrl: String, baseUrl: String,
httpClient: HttpClient, httpClient: HttpClient,
contentType: ContentType contentType: ContentType,
onNewValue: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onNewValueRoute),
),
onValueRemoved: Flow<Pair<Key, Value>> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
onDataCleared: Flow<Key> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onDataClearedRoute),
),
) = KtorWriteKeyValuesRepoClient<Key, Value>( ) = KtorWriteKeyValuesRepoClient<Key, Value>(
baseUrl, baseUrl,
httpClient, httpClient,
contentType, contentType,
httpClient.createStandardWebsocketFlow( onNewValue,
buildStandardUrl(baseUrl, onNewValueRoute), onValueRemoved,
), onDataCleared,
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onValueRemovedRoute),
),
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, onDataClearedRoute),
),
typeInfo<Key>(), typeInfo<Key>(),
typeInfo<Value>(), typeInfo<Value>(),
typeInfo<Map<Key, List<Value>>>() typeInfo<Map<Key, List<Value>>>()

View File

@@ -1,10 +1,8 @@
package dev.inmo.micro_utils.startup.launcher package dev.inmo.micro_utils.startup.launcher
import dev.inmo.kslog.common.i import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.setupDI
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import org.koin.core.KoinApplication import org.koin.core.KoinApplication
import org.koin.core.context.startKoin
import org.koin.dsl.module
/** /**
* Will create [KoinApplication], init, load modules using [StartLauncherPlugin] and start plugins using the same base * Will create [KoinApplication], init, load modules using [StartLauncherPlugin] and start plugins using the same base
@@ -13,19 +11,7 @@ import org.koin.dsl.module
* @param rawConfig It is expected that this [JsonObject] will contain serialized [Config] ([StartLauncherPlugin] will * @param rawConfig It is expected that this [JsonObject] will contain serialized [Config] ([StartLauncherPlugin] will
* deserialize it in its [StartLauncherPlugin.setupDI] * deserialize it in its [StartLauncherPlugin.setupDI]
*/ */
@Deprecated("Fully replaced with StartLauncherPlugin#start", ReplaceWith("StartLauncherPlugin.start(rawConfig)", "dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin"))
suspend fun start(rawConfig: JsonObject) { suspend fun start(rawConfig: JsonObject) {
with(StartLauncherPlugin) { StartLauncherPlugin.start(rawConfig)
logger.i("Start initialization")
val koinApp = KoinApplication.init()
koinApp.modules(
module {
setupDI(rawConfig)
}
)
logger.i("Modules loaded")
startKoin(koinApp)
logger.i("Koin started")
startPlugin(koinApp.koin)
logger.i("App has been setup")
}
} }

View File

@@ -4,6 +4,8 @@ import dev.inmo.kslog.common.i
import dev.inmo.kslog.common.taggedLogger import dev.inmo.kslog.common.taggedLogger
import dev.inmo.kslog.common.w import dev.inmo.kslog.common.w
import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.setupDI
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.startPlugin
import dev.inmo.micro_utils.startup.plugin.StartPlugin import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -12,7 +14,11 @@ import kotlinx.coroutines.launch
import kotlinx.serialization.SerialFormat import kotlinx.serialization.SerialFormat
import kotlinx.serialization.StringFormat import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonObject
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.KoinApplication
import org.koin.core.context.startKoin
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.dsl.binds import org.koin.dsl.binds
import org.koin.dsl.module import org.koin.dsl.module
@@ -23,26 +29,20 @@ import org.koin.dsl.module
object StartLauncherPlugin : StartPlugin { object StartLauncherPlugin : StartPlugin {
internal val logger = taggedLogger(this) internal val logger = taggedLogger(this)
/** fun Module.setupDI(config: Config, rawJsonObject: JsonObject? = null) {
* Will deserialize [Config] from [config], register it in receiver [Module] (as well as [CoroutineScope] and val rawJsonObject = rawJsonObject ?: defaultJson.encodeToJsonElement(Config.serializer(), config).jsonObject
* [kotlinx.serialization.json.Json])
*
* Besides, in this method will be called [StartPlugin.setupDI] on each plugin from [Config.plugins]. In case when
* some plugin will not be loaded correctly it will be reported throw the [logger]
*/
override fun Module.setupDI(config: JsonObject) {
val pluginsConfig = defaultJson.decodeFromJsonElement(Config.serializer(), config)
single { pluginsConfig } single { rawJsonObject }
single { config }
single { CoroutineScope(Dispatchers.Default) } single { CoroutineScope(Dispatchers.Default) }
single { defaultJson } binds arrayOf(StringFormat::class, SerialFormat::class) single { defaultJson } binds arrayOf(StringFormat::class, SerialFormat::class)
includes( includes(
pluginsConfig.plugins.mapNotNull { config.plugins.mapNotNull {
runCatching { runCatching {
module { module {
with(it) { with(it) {
setupDI(config) setupDI(rawJsonObject)
} }
} }
}.onFailure { e -> }.onFailure { e ->
@@ -52,11 +52,28 @@ object StartLauncherPlugin : StartPlugin {
) )
} }
/**
* Will deserialize [Config] from [config], register it in receiver [Module] (as well as [CoroutineScope] and
* [kotlinx.serialization.json.Json])
*
* Besides, in this method will be called [StartPlugin.setupDI] on each plugin from [Config.plugins]. In case when
* some plugin will not be loaded correctly it will be reported throw the [logger]
*/
override fun Module.setupDI(config: JsonObject) {
logger.i("Koin for current module has started setup")
setupDI(
defaultJson.decodeFromJsonElement(Config.serializer(), config),
config
)
logger.i("Koin for current module has been setup")
}
/** /**
* Takes [CoroutineScope] and [Config] from the [koin], and call starting of each plugin from [Config.plugins] * Takes [CoroutineScope] and [Config] from the [koin], and call starting of each plugin from [Config.plugins]
* ASYNCHRONOUSLY. Just like in [setupDI], in case of fail in some plugin it will be reported using [logger] * ASYNCHRONOUSLY. Just like in [setupDI], in case of fail in some plugin it will be reported using [logger]
*/ */
override suspend fun startPlugin(koin: Koin) { override suspend fun startPlugin(koin: Koin) {
logger.i("Start starting of subplugins")
val scope = koin.get<CoroutineScope>() val scope = koin.get<CoroutineScope>()
koin.get<Config>().plugins.map { plugin -> koin.get<Config>().plugins.map { plugin ->
scope.launch { scope.launch {
@@ -66,11 +83,61 @@ object StartLauncherPlugin : StartPlugin {
startPlugin(koin) startPlugin(koin)
} }
}.onFailure { e -> }.onFailure { e ->
logger.w("Unable to load bot part of $plugin", e) logger.w("Unable to start plugin $plugin", e)
}.onSuccess { }.onSuccess {
logger.i("Complete loading of $plugin") logger.i("Complete loading of $plugin")
} }
} }
}.joinAll() }.joinAll()
logger.i("Complete subplugins start")
}
/**
* Will create [KoinApplication], init, load modules using [StartLauncherPlugin] and start plugins using the same base
* plugin. It is basic [start] method which accepts both [config] and [rawConfig] which suppose to be the same or
* at least [rawConfig] must contain serialized variant of [config]
*
* @param rawConfig It is expected that this [JsonObject] will contain serialized [Config] ([StartLauncherPlugin] will
* deserialize it in its [StartLauncherPlugin.setupDI]
*/
suspend fun start(config: Config, rawConfig: JsonObject) {
logger.i("Start initialization")
val koinApp = KoinApplication.init()
koinApp.modules(
module {
setupDI(config, rawConfig)
}
)
logger.i("Modules loaded")
startKoin(koinApp)
logger.i("Koin started")
startPlugin(koinApp.koin)
logger.i("App has been setup")
}
/**
* Call [start] with deserialized [Config] as config and [rawConfig] as is
*
* @param rawConfig It is expected that this [JsonObject] will contain serialized [Config]
*/
suspend fun start(rawConfig: JsonObject) {
start(defaultJson.decodeFromJsonElement(Config.serializer(), rawConfig), rawConfig)
}
/**
* Call [start] with deserialized [Config] as is and serialize it to [JsonObject] to pass as the first parameter
* to the basic [start] method
*
* @param config Will be converted to [JsonObject] as raw config. That means that all plugins from [config] will
* receive serialized version of [config] in [StartPlugin.setupDI] method
*/
suspend fun start(config: Config) {
start(config, defaultJson.encodeToJsonElement(Config.serializer(), config).jsonObject)
} }
} }

View File

@@ -1,7 +1,7 @@
import dev.inmo.micro_utils.startup.launcher.Config import dev.inmo.micro_utils.startup.launcher.Config
import dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin import dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin
import dev.inmo.micro_utils.startup.launcher.defaultJson import dev.inmo.micro_utils.startup.launcher.defaultJson
import dev.inmo.micro_utils.startup.launcher.start
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
@@ -23,7 +23,7 @@ class StartupLaunchingTests {
runTest { runTest {
val job = launch { val job = launch {
start(emptyJson) StartLauncherPlugin.start(emptyJson)
} }
job.join() job.join()
} }
@@ -37,7 +37,7 @@ class StartupLaunchingTests {
runTest { runTest {
val job = launch { val job = launch {
start(emptyJson) StartLauncherPlugin.start(emptyJson)
} }
job.join() job.join()
} }

View File

@@ -5,6 +5,7 @@ import dev.inmo.kslog.common.i
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
@Deprecated("Useless due to including of the same functionality in StrtLauncherPlugin")
object PluginsStarter { object PluginsStarter {
init { init {
KSLog.default = KSLog("Launcher") KSLog.default = KSLog("Launcher")
@@ -15,19 +16,10 @@ object PluginsStarter {
* app inside of [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer] using its * app inside of [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer] using its
* [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer.registerPlugin] method * [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer.registerPlugin] method
*/ */
suspend fun startPlugins(json: JsonObject) { suspend fun startPlugins(json: JsonObject) = StartLauncherPlugin.start(json)
start(json)
}
/** /**
* Will convert [config] to [JsonObject] with auto registration of [dev.inmo.micro_utils.startup.plugin.StartPlugin]s * Will convert [config] to [JsonObject] with auto registration of [dev.inmo.micro_utils.startup.plugin.StartPlugin]s
* in [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer] * in [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer]
*/ */
suspend fun startPlugins(config: Config) { suspend fun startPlugins(config: Config) = StartLauncherPlugin.start(config)
KSLog.i("Start convert config to JSON")
val json = defaultJson.encodeToJsonElement(Config.serializer(), config).jsonObject
KSLog.i("Config has been read")
start(json)
}
} }

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.startup.launcher package dev.inmo.micro_utils.startup.launcher
import dev.inmo.kslog.common.KSLog import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.i import dev.inmo.kslog.common.i
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import java.io.File import java.io.File
@@ -23,15 +24,30 @@ import java.io.File
* In that case in `build/distributions` folder you will be able to find zip and tar files with all required * In that case in `build/distributions` folder you will be able to find zip and tar files with all required
* tools for application running (via their `bin/app_name` binary). In that case yoy will not need to pass * tools for application running (via their `bin/app_name` binary). In that case yoy will not need to pass
* `--args=...` and launch will look like `./bin/app_name sample.config.json` * `--args=...` and launch will look like `./bin/app_name sample.config.json`
*
* ## Debug mode
*
* You may pass the second parameter, one of [LogLevel] enum variants to setup [KSLog] minimal logging level. Sample:
*
* ```bash
* ./gradlew run --args="sample.config.json DEBUG" // enable debugging output
* ```
*
* OR
* ```bash
* ./gradlew run --args="sample.config.json WARNING" // enable logging since WARNING
* ```
*
* **Default level is [LogLevel.INFO]**
*/ */
suspend fun main(args: Array<String>) { suspend fun main(args: Array<String>) {
KSLog.default = KSLog("Launcher") KSLog.default = KSLog("Launcher", args.getOrNull(1) ?.let { LogLevel.valueOf(it) } ?: LogLevel.INFO)
val (configPath) = args val (configPath) = args
val file = File(configPath) val file = File(configPath)
KSLog.i("Start read config from ${file.absolutePath}") KSLog.i("Start read config from ${file.absolutePath}")
val json = defaultJson.parseToJsonElement(file.readText()).jsonObject val json = defaultJson.parseToJsonElement(file.readText()).jsonObject
KSLog.i("Config has been read") KSLog.i("Config has been read")
start(json) StartLauncherPlugin.start(json)
} }

View File

@@ -0,0 +1,10 @@
# How to use
In case you have multiplatform project and wish to use startup plugin, this template may help you to create new modules.
1. Copy-paste whole template folder (you may clone this folder to your project and actualize some data to copy your prepared template)
2. Replace `group_name` by your project (or root module) group name
3. Replace `module_name` by the name of your new module name
You may read about the `build.gradle` structure in these templates in project
[KotlinMultiplatformProjectTemplate](https://github.com/InsanusMokrassar/KotlinMultiplatformProjectTemplate).

View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.compose)
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":${rootProject.name}.module_name.common")
}
}
}
}

View File

@@ -0,0 +1,15 @@
package group_name.module_name.client
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.serialization.json.JsonObject
import org.koin.core.Koin
import org.koin.core.module.Module
object ClientPlugin : StartPlugin {
override fun Module.setupDI(config: JsonObject) {
}
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
}
}

View File

@@ -0,0 +1,20 @@
package group_name.module_name.client
import group_name.module_name.common.CommonJSPlugin
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.serialization.json.JsonObject
import org.koin.core.Koin
import org.koin.core.module.Module
object ClientJSPlugin : StartPlugin {
override fun Module.setupDI(config: JsonObject) {
with(CommonJSPlugin) { setupDI(config) }
with(ClientPlugin) { setupDI(config) }
}
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
CommonJSPlugin.startPlugin(koin)
ClientPlugin.startPlugin(koin)
}
}

View File

@@ -0,0 +1,9 @@
package group_name.module_name.client
import dev.inmo.micro_utils.startup.plugin.createStartupPluginAndRegister
@ExperimentalStdlibApi
@EagerInitialization
@JsExport
@ExperimentalJsExport
private val jsModuleLoader = createStartupPluginAndRegister("template.ClientJSPlugin") { ClientJSPlugin }

View File

@@ -0,0 +1,21 @@
package group_name.module_name.client
import group_name.module_name.common.CommonJVMPlugin
import group_name.module_name.common.CommonJVMPlugin.setupDI
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.serialization.json.JsonObject
import org.koin.core.Koin
import org.koin.core.module.Module
object ClientJVMPlugin : StartPlugin {
override fun Module.setupDI(config: JsonObject) {
with(CommonJVMPlugin) { setupDI(config) }
with(ClientPlugin) { setupDI(config) }
}
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
CommonJVMPlugin.startPlugin(koin)
ClientPlugin.startPlugin(koin)
}
}

View File

@@ -0,0 +1 @@
<manifest package="group_name.module_name.client"/>

View File

@@ -0,0 +1,21 @@
package group_name.module_name.client
import group_name.module_name.common.CommonAndroidPlugin
import group_name.module_name.common.CommonAndroidPlugin.setupDI
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.serialization.json.JsonObject
import org.koin.core.Koin
import org.koin.core.module.Module
object ClientAndroidPlugin : StartPlugin {
override fun Module.setupDI(config: JsonObject) {
with(CommonAndroidPlugin) { setupDI(config) }
with(ClientPlugin) { setupDI(config) }
}
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
CommonAndroidPlugin.startPlugin(koin)
ClientPlugin.startPlugin(koin)
}
}

View File

@@ -0,0 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1,11 @@
package group_name.module_name.common
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.serialization.json.JsonObject
import org.koin.core.module.Module
object CommonPlugin : StartPlugin {
override fun Module.setupDI(config: JsonObject) {
}
}

View File

@@ -0,0 +1 @@
package group_name.module_name.common

View File

@@ -0,0 +1,17 @@
package group_name.module_name.common
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.serialization.json.JsonObject
import org.koin.core.Koin
import org.koin.core.module.Module
object CommonJSPlugin : StartPlugin {
override fun Module.setupDI(config: JsonObject) {
with (CommonPlugin) { setupDI(config) }
}
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
CommonPlugin.startPlugin(koin)
}
}

View File

@@ -0,0 +1,9 @@
package group_name.module_name.common
import dev.inmo.micro_utils.startup.plugin.createStartupPluginAndRegister
@ExperimentalStdlibApi
@EagerInitialization
@JsExport
@ExperimentalJsExport
private val jsModuleLoader = createStartupPluginAndRegister("template.CommonJSPlugin") { CommonJSPlugin }

View File

@@ -0,0 +1,17 @@
package group_name.module_name.common
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.serialization.json.JsonObject
import org.koin.core.Koin
import org.koin.core.module.Module
object CommonJVMPlugin : StartPlugin {
override fun Module.setupDI(config: JsonObject) {
with (CommonPlugin) { setupDI(config) }
}
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
CommonPlugin.startPlugin(koin)
}
}

View File

@@ -0,0 +1 @@
<manifest package="group_name.module_name.common"/>

View File

@@ -0,0 +1,17 @@
package group_name.module_name.common
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.serialization.json.JsonObject
import org.koin.core.Koin
import org.koin.core.module.Module
object CommonAndroidPlugin : StartPlugin {
override fun Module.setupDI(config: JsonObject) {
with (CommonPlugin) { setupDI(config) }
}
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
CommonPlugin.startPlugin(koin)
}
}

View File

@@ -0,0 +1,16 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
}
apply from: "$mppJavaProjectPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":${rootProject.name}.module_name.common")
}
}
}
}

View File

@@ -0,0 +1,15 @@
package group_name.module_name.server
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.serialization.json.JsonObject
import org.koin.core.Koin
import org.koin.core.module.Module
object ServerPlugin : StartPlugin {
override fun Module.setupDI(config: JsonObject) {
}
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
}
}

View File

@@ -0,0 +1,20 @@
package group_name.module_name.server
import group_name.module_name.common.CommonJVMPlugin
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import kotlinx.serialization.json.JsonObject
import org.koin.core.Koin
import org.koin.core.module.Module
object ServerJVMPlugin : StartPlugin {
override fun Module.setupDI(config: JsonObject) {
with(CommonJVMPlugin) { setupDI(config) }
with(ServerPlugin) { setupDI(config) }
}
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
CommonJVMPlugin.startPlugin(koin)
ServerPlugin.startPlugin(koin)
}
}