Compare commits

..

31 Commits

Author SHA1 Message Date
cfc7119697 update dependencies 2022-06-28 23:06:09 +06:00
22a6520d3e start 0.11.9 2022-06-28 23:01:32 +06:00
fb25e91191 start 0.12.0 2022-06-28 22:57:21 +06:00
c116b270b6 Merge pull request #170 from InsanusMokrassar/0.11.8
0.11.8
2022-06-28 14:18:11 +06:00
aa2d598689 fixes in FileKeyValueRepo 2022-06-28 14:13:04 +06:00
5ef3bb746b start 0.11.8 2022-06-28 14:12:12 +06:00
037616e271 Merge pull request #169 from InsanusMokrassar/0.11.7
0.11.7
2022-06-28 03:07:54 +06:00
abbea906f1 fixes 2022-06-28 01:46:24 +06:00
9132e216c9 add several extensions for MapperRepo 2022-06-28 01:43:57 +06:00
12a7e3c4af fixes 2022-06-28 01:06:46 +06:00
b40c093917 SimpleMapper, SimpleSuspendableMapper, mappers for CRUDRepo 2022-06-28 00:56:51 +06:00
7ac12455c8 add ReadCRUDRepo mappers 2022-06-28 00:19:25 +06:00
5043eec7a2 start 0.11.7 2022-06-27 23:35:10 +06:00
cf31f53e01 Merge pull request #168 from InsanusMokrassar/0.11.6
0.11.6
2022-06-24 00:19:34 +06:00
cd22d76fa7 fsms fixes 2022-06-23 18:47:59 +06:00
c8759843f7 start 0.11.6 2022-06-23 17:19:07 +06:00
781bbcc012 Merge pull request #167 from InsanusMokrassar/0.11.5
0.11.5
2022-06-23 12:50:31 +06:00
6da29c0686 add note about 0.11.4 2022-06-23 12:49:09 +06:00
fcdb6fc45a start and finish 0.11.5 -.- 2022-06-23 12:48:24 +06:00
e785a99bd7 Merge pull request #166 from InsanusMokrassar/0.11.4
0.11.4
2022-06-22 22:37:30 +06:00
121e513fdd fixes in tempUpload of js part 2022-06-22 22:36:06 +06:00
9cf01ab54f improve compose list state creation 2022-06-22 16:56:51 +06:00
c655107681 renames in compose states creation 2022-06-22 16:52:19 +06:00
91a5af6a9a fix of changelog 2022-06-22 16:10:58 +06:00
86e74c0a6f add extension toMutableListState 2022-06-22 16:09:52 +06:00
d8f01f21a0 start 0.11.4 2022-06-22 16:09:35 +06:00
9fb8626d8c Rewrite crud.yml 2022-06-16 15:51:22 +06:00
1c52e04cdb Merge pull request #164 from InsanusMokrassar/0.11.3
0.11.3
2022-06-14 23:59:49 +06:00
798128256e fixes in websockets 2022-06-14 23:48:10 +06:00
72c2df47fd start 0.11.3 2022-06-14 19:35:17 +06:00
c9c6d4c0c1 Merge pull request #163 from InsanusMokrassar/0.11.2
0.11.2
2022-06-13 23:21:14 +06:00
17 changed files with 571 additions and 191 deletions

View File

@@ -1,5 +1,50 @@
# Changelog # Changelog
## 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 ## 0.11.2
* `Ktor`: * `Ktor`:

View File

@@ -0,0 +1,53 @@
package dev.inmo.micro_utils.common
import kotlin.jvm.JvmName
interface SimpleMapper<T1, T2> {
fun convertToT1(from: T2): T1
fun convertToT2(from: T1): T2
}
@JvmName("convertFromT2")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T2) = convertToT1(from)
@JvmName("convertFromT1")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T1) = convertToT2(from)
class SimpleMapperImpl<T1, T2>(
private val t1: (T2) -> T1,
private val t2: (T1) -> T2,
) : SimpleMapper<T1, T2> {
override fun convertToT1(from: T2): T1 = t1.invoke(from)
override fun convertToT2(from: T1): T2 = t2.invoke(from)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleMapper(
noinline t1: (T2) -> T1,
noinline t2: (T1) -> T2,
) = SimpleMapperImpl(t1, t2)
interface SimpleSuspendableMapper<T1, T2> {
suspend fun convertToT1(from: T2): T1
suspend fun convertToT2(from: T1): T2
}
@JvmName("convertFromT2")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T2) = convertToT1(from)
@JvmName("convertFromT1")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T1) = convertToT2(from)
class SimpleSuspendableMapperImpl<T1, T2>(
private val t1: suspend (T2) -> T1,
private val t2: suspend (T1) -> T2,
) : SimpleSuspendableMapper<T1, T2> {
override suspend fun convertToT1(from: T2): T1 = t1.invoke(from)
override suspend fun convertToT2(from: T1): T2 = t2.invoke(from)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleSuspendableMapper(
noinline t1: suspend (T2) -> T1,
noinline t2: suspend (T1) -> T2,
) = SimpleSuspendableMapperImpl(t1, t2)

View File

@@ -0,0 +1,26 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
scope: CoroutineScope
): SnapshotStateList<T> {
val state = mutableStateListOf<T>()
subscribeSafelyWithoutExceptions(scope) {
state.applyDiff(it)
}
return state
}
@Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asComposeList(
scope: CoroutineScope
): List<T> = asMutableComposeListState(scope)

View File

@@ -0,0 +1,35 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
fun <T> Flow<T>.asMutableComposeState(
initial: T,
scope: CoroutineScope
): MutableState<T> {
val state = mutableStateOf(initial)
subscribeSafelyWithoutExceptions(scope) { state.value = it }
return state
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asMutableComposeState(
scope: CoroutineScope
): MutableState<T> = asMutableComposeState(value, scope)
fun <T> Flow<T>.asComposeState(
initial: T,
scope: CoroutineScope
): State<T> {
val state = asMutableComposeState(initial, scope)
return derivedStateOf { state.value }
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asComposeState(
scope: CoroutineScope
): State<T> = asComposeState(value, scope)

View File

@@ -6,6 +6,7 @@ import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler 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
@@ -85,10 +86,10 @@ open class DefaultStatesMachine <T: State>(
protected open suspend fun performUpdate(state: T) { protected open suspend fun performUpdate(state: T) {
val newState = launchStateHandling(state, handlers) val newState = launchStateHandling(state, handlers)
if (newState != null) { if (newState == null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state) statesManager.endChain(state)
} else {
statesManager.update(state, newState)
} }
} }
@@ -118,7 +119,7 @@ open class DefaultStatesMachine <T: State>(
* [StatesManager.endChain]. * [StatesManager.endChain].
*/ */
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) { (statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) } launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
} }
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) { statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
@@ -134,10 +135,6 @@ open class DefaultStatesMachine <T: State>(
} }
} }
} }
statesManager.getActiveStates().forEach {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
}
} }
/** /**

View File

@@ -52,6 +52,7 @@ open class DefaultUpdatableStatesMachine<T : State>(
statesJobs.remove( statesJobs.remove(
jobsStates[job] ?: return@withLock jobsStates[job] ?: return@withLock
) )
jobsStates.remove(job)
} }
} }
} }

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.11.2 version=0.11.9
android_code_version=126 android_code_version=133

View File

@@ -2,18 +2,18 @@
kt = "1.6.21" kt = "1.6.21"
kt-serialization = "1.3.3" kt-serialization = "1.3.3"
kt-coroutines = "1.6.1" kt-coroutines = "1.6.3"
jb-compose = "1.2.0-alpha01-dev686" jb-compose = "1.2.0-alpha01-dev729"
jb-exposed = "0.38.2" jb-exposed = "0.38.2"
jb-dokka = "1.6.21" jb-dokka = "1.6.21"
klock = "2.7.0" klock = "2.7.0"
uuid = "0.4.1" uuid = "0.4.1"
ktor = "2.0.2" ktor = "2.0.3"
gh-release = "2.3.7" gh-release = "2.4.1"
android-gradle = "7.0.4" android-gradle = "7.0.4"
dexcount = "3.1.0" dexcount = "3.1.0"

View File

@@ -1,5 +1,6 @@
package dev.inmo.micro_utils.ktor.client 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.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.*
@@ -7,35 +8,25 @@ import io.ktor.client.HttpClient
import io.ktor.client.plugins.pluginOrNull import io.ktor.client.plugins.pluginOrNull
import io.ktor.client.plugins.websocket.* import io.ktor.client.plugins.websocket.*
import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.HttpRequestBuilder
import io.ktor.websocket.Frame import io.ktor.http.URLProtocol
import io.ktor.websocket.readBytes import kotlinx.coroutines.channels.SendChannel
import io.ktor.websocket.serialization.sendSerializedBase
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.coroutines.isActive
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
*/ */
inline fun <reified T : Any> HttpClient.createStandardWebsocketFlow( @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")
url: String, inline fun <reified T : Any> openBaseWebSocketFlow(
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true }, noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {} noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
): Flow<T> { ): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow { return channelFlow {
do { do {
val reconnect = runCatchingSafely { val reconnect = runCatchingSafely {
ws(correctedUrl, requestBuilder) { webSocketSessionRequest()
while (isActive) {
send(receiveDeserialized<T>())
}
}
checkReconnection(null) checkReconnection(null)
}.getOrElse { e -> }.getOrElse { e ->
checkReconnection(e).also { checkReconnection(e).also {
@@ -53,3 +44,60 @@ inline fun <reified T : Any> HttpClient.createStandardWebsocketFlow(
} }
} }
} }
/**
* @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,7 +4,10 @@ import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.ktor.common.TemporalFileId import dev.inmo.micro_utils.ktor.common.TemporalFileId
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.w3c.dom.mediasource.ENDED
import org.w3c.dom.mediasource.ReadyState
import org.w3c.xhr.* import org.w3c.xhr.*
import org.w3c.xhr.XMLHttpRequest.Companion.DONE
suspend fun tempUpload( suspend fun tempUpload(
fullTempUploadDraftPath: String, fullTempUploadDraftPath: String,
@@ -12,7 +15,7 @@ suspend fun tempUpload(
onUpload: (Long, Long) -> Unit onUpload: (Long, Long) -> Unit
): TemporalFileId { ): TemporalFileId {
val formData = FormData() val formData = FormData()
val answer = CompletableDeferred<TemporalFileId>() val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
formData.append( formData.append(
"data", "data",
@@ -37,17 +40,15 @@ suspend fun tempUpload(
request.open("POST", fullTempUploadDraftPath, true) request.open("POST", fullTempUploadDraftPath, true)
request.send(formData) request.send(formData)
val handle = currentCoroutineContext().job.invokeOnCompletion { answer.invokeOnCompletion {
runCatching { runCatching {
request.abort() if (request.readyState != DONE) {
request.abort()
}
} }
} }
return runCatching { return answer.await()
answer.await()
}.also {
handle.dispose()
}.getOrThrow()
} }

View File

@@ -1,22 +1,51 @@
package dev.inmo.micro_utils.repos package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.common.*
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> { interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
val keyMapper: SimpleSuspendableMapper<FromKey, ToKey>
get() = simpleSuspendableMapper(
{ it.toInnerKey() },
{ it.toOutKey() }
)
val valueMapper: SimpleSuspendableMapper<FromValue, ToValue>
get() = simpleSuspendableMapper(
{ it.toInnerValue() },
{ it.toOutValue() }
)
suspend fun FromKey.toOutKey() = this as ToKey suspend fun FromKey.toOutKey() = this as ToKey
suspend fun FromValue.toOutValue() = this as ToValue suspend fun FromValue.toOutValue() = this as ToValue
suspend fun ToKey.toInnerKey() = this as FromKey suspend fun ToKey.toInnerKey() = this as FromKey
suspend fun ToValue.toInnerValue() = this as FromValue suspend fun ToValue.toInnerValue() = this as FromValue
companion object
} }
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper( class SimpleMapperRepo<FromKey, FromValue, ToKey, ToValue>(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, private val keyFromToTo: suspend FromKey.() -> ToKey,
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, private val valueFromToTo: suspend FromValue.() -> ToValue,
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, private val keyToToFrom: suspend ToKey.() -> FromKey,
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, private val valueToToFrom: suspend ToValue.() -> FromValue
) = object : MapperRepo<FromKey, FromValue, ToKey, ToValue> { ) : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo() override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo()
override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo() override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo()
override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom() override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom()
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom() override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
} }
operator fun <FromKey, FromValue, ToKey, ToValue> MapperRepo.Companion.invoke(
keyFromToTo: suspend FromKey.() -> ToKey,
valueFromToTo: suspend FromValue.() -> ToValue,
keyToToFrom: suspend ToKey.() -> FromKey,
valueToToFrom: suspend ToValue.() -> FromValue
) = SimpleMapperRepo(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
) = MapperRepo(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)

View File

@@ -0,0 +1,130 @@
package dev.inmo.micro_utils.repos.mappers
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>(
private val to: ReadCRUDRepo<ToRegistered, ToId>,
mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>
) : ReadCRUDRepo<FromRegistered, FromId>, MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper {
override suspend fun getByPagination(
pagination: Pagination
): PaginationResult<FromRegistered> = to.getByPagination(
pagination
).let {
it.changeResultsUnchecked(
it.results.map { it.toInnerValue() }
)
}
override suspend fun count(): Long = to.count()
override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey())
override suspend fun getById(id: FromId): FromRegistered? = to.getById(
id.toOutKey()
) ?.toInnerValue()
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadCRUDRepo<ToValue, ToKey>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadCRUDRepo<FromValue, FromKey> = MapperReadCRUDRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadCRUDRepo<ToValue, ToKey>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadCRUDRepo<FromValue, FromKey> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperWriteCRUDRepo<FromId, FromRegistered, FromInput, ToId, ToRegistered, ToInput>(
private val to: WriteCRUDRepo<ToRegistered, ToId, ToInput>,
mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>,
inputMapper: SimpleSuspendableMapper<FromInput, ToInput>
) : WriteCRUDRepo<FromRegistered, FromId, FromInput>,
MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper,
SimpleSuspendableMapper<FromInput, ToInput> by inputMapper {
override val newObjectsFlow: Flow<FromRegistered> = to.newObjectsFlow.map { it.toInnerValue() }
override val updatedObjectsFlow: Flow<FromRegistered> = to.updatedObjectsFlow.map { it.toInnerValue() }
override val deletedObjectsIdsFlow: Flow<FromId> = to.deletedObjectsIdsFlow.map { it.toInnerKey() }
override suspend fun deleteById(ids: List<FromId>) = to.deleteById(ids.map { it.toOutKey() })
override suspend fun update(
values: List<UpdatedValuePair<FromId, FromInput>>
): List<FromRegistered> = to.update(
values.map {
it.first.toOutKey() to convert(it.second)
}
).map { it.toInnerValue() }
override suspend fun update(
id: FromId,
value: FromInput
): FromRegistered? = to.update(id.toOutKey(), convert(value)) ?.toInnerValue()
override suspend fun create(values: List<FromInput>): List<FromRegistered> = to.create(
values.map {
convert(it)
}
).map {
it.toInnerValue()
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, FromInput, ToKey, ToValue, ToInput> WriteCRUDRepo<ToValue, ToKey, ToInput>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>,
simpleSuspendableMapper: SimpleSuspendableMapper<FromInput, ToInput>
): WriteCRUDRepo<FromValue, FromKey, FromInput> = MapperWriteCRUDRepo(this, mapper, simpleSuspendableMapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified FromInput, reified ToKey, reified ToValue, reified ToInput> WriteCRUDRepo<ToValue, ToKey, ToInput>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline inputFromToTo: suspend FromInput.() -> ToInput = { this as ToInput },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
noinline inputToToFrom: suspend ToInput.() -> FromInput = { this as FromInput },
): WriteCRUDRepo<FromValue, FromKey, FromInput> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom),
simpleSuspendableMapper({ inputToToFrom(it) }, { inputFromToTo(it) })
)
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
open class MapperCRUDRepo<FromId, FromRegistered, FromInput, ToId, ToRegistered, ToInput>(
private val to: CRUDRepo<ToRegistered, ToId, ToInput>,
private val mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>,
private val inputMapper: SimpleSuspendableMapper<FromInput, ToInput>
) : CRUDRepo<FromRegistered, FromId, FromInput>,
MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper,
ReadCRUDRepo<FromRegistered, FromId> by MapperReadCRUDRepo(to, mapper),
WriteCRUDRepo<FromRegistered, FromId, FromInput> by MapperWriteCRUDRepo(to, mapper, inputMapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, FromInput, ToKey, ToValue, ToInput> CRUDRepo<ToValue, ToKey, ToInput>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>,
simpleSuspendableMapper: SimpleSuspendableMapper<FromInput, ToInput>
): CRUDRepo<FromValue, FromKey, FromInput> = MapperCRUDRepo(this, mapper, simpleSuspendableMapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified FromInput, reified ToKey, reified ToValue, reified ToInput> CRUDRepo<ToValue, ToKey, ToInput>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline inputFromToTo: suspend FromInput.() -> ToInput = { this as ToInput },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
noinline inputToToFrom: suspend ToInput.() -> FromInput = { this as FromInput },
): CRUDRepo<FromValue, FromKey, FromInput> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom),
simpleSuspendableMapper({ inputToToFrom(it) }, { inputFromToTo(it) })
)

View File

@@ -77,10 +77,10 @@ inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValueRepo<ToKey, ToValue>
@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> ReadKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadKeyValueRepo<FromKey, FromValue> = withMapper( ): ReadKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@@ -122,10 +122,10 @@ inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValueRepo<ToKey, ToValue
@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> WriteKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteKeyValueRepo<FromKey, FromValue> = withMapper( ): WriteKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@@ -148,10 +148,10 @@ inline fun <FromKey, FromValue, ToKey, ToValue> KeyValueRepo<ToKey, ToValue>.wit
@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> KeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): KeyValueRepo<FromKey, FromValue> = withMapper( ): KeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )

View File

@@ -75,10 +75,10 @@ inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValuesRepo<ToKey, ToValue
@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> ReadKeyValuesRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadKeyValuesRepo<FromKey, FromValue> = withMapper( ): ReadKeyValuesRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@@ -128,10 +128,10 @@ inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValuesRepo<ToKey, ToValu
@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> WriteKeyValuesRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteKeyValuesRepo<FromKey, FromValue> = withMapper( ): WriteKeyValuesRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@@ -154,10 +154,10 @@ inline fun <FromKey, FromValue, ToKey, ToValue> KeyValuesRepo<ToKey, ToValue>.wi
@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> KeyValuesRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): KeyValuesRepo<FromKey, FromValue> = withMapper( ): KeyValuesRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )

View File

@@ -99,7 +99,9 @@ class FileWriteKeyValueRepo(
override val onValueRemoved: Flow<String> = _onValueRemoved.asSharedFlow() override val onValueRemoved: Flow<String> = _onValueRemoved.asSharedFlow()
init { init {
folder.mkdirs() if (!folder.mkdirs() && !folder.exists()) {
error("Unable to create folder ${folder.absolutePath}")
}
filesChangedProcessingScope ?.let { filesChangedProcessingScope ?.let {
it.launch { it.launch {
try { try {
@@ -144,15 +146,17 @@ class FileWriteKeyValueRepo(
} }
override suspend fun set(toSet: Map<String, File>) { override suspend fun set(toSet: Map<String, File>) {
supervisorScope { val scope = CoroutineScope(currentCoroutineContext())
toSet.map { (filename, fileSource) -> toSet.map { (filename, fileSource) ->
launch { scope.launch {
val file = File(folder, filename) val file = File(folder, filename)
file.delete() file.delete()
fileSource.copyTo(file, overwrite = true) fileSource.copyTo(file, overwrite = true)
_onNewValue.emit(filename to file) if (!file.exists()) {
error("Can't create file $file with new content")
} }
_onNewValue.emit(filename to file)
} }
}.joinAll() }.joinAll()
} }

View File

@@ -1,7 +1,7 @@
swagger: "2.0" openapi: "3.0.0"
info: info:
description: "This is a template for the CRUD repositories from [microutils](https://github.com/InsanusMokrassar/MicroUtils/tree/master/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud)" description: "This is a template for the CRUD repositories from [microutils](https://github.com/InsanusMokrassar/MicroUtils/tree/master/repos/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/ktor/server/crud)"
version: "0.11.0" version: "0.11.3"
title: "CRUD Repo" title: "CRUD Repo"
contact: contact:
email: "ovsyannikov.alexey95@gmail.com" email: "ovsyannikov.alexey95@gmail.com"
@@ -11,91 +11,61 @@ tags:
- name: "Write" - name: "Write"
description: "Operations with `post` request in most cases" description: "Operations with `post` request in most cases"
parameters: components:
IdInQuery: parameters:
in: "query" IdInQuery:
name: "id" in: "query"
allOf: name: "id"
- $ref: "#/definitions/Key" schema:
IdsInBody: $ref: "#/components/schemas/Key"
in: "body" PaginationInQueryPage:
name: "body" in: "query"
type: array name: "ppage"
items: description: "Page of pagination"
$ref: "#/definitions/Key" schema:
NewValuesInBody: type: integer
in: "body" required: false
name: "body" PaginationInQuerySize:
type: array in: "query"
allOf: name: "psize"
- $ref: "#/definitions/NewValues" description: "Size of each page in pagination"
NewValuesWithIdsInBody: schema:
in: "body" type: integer
name: "body" required: false
type: array schemas:
items:
allOf:
- $ref: "#/definitions/Pair"
- properties:
first:
$ref: "#/definitions/Key"
second:
$ref: "#/definitions/NewValue"
PaginationInQueryPage:
in: "query"
type: integer
name: "ppage"
description: "Page of pagination"
required: false
PaginationInQuerySize:
in: "query"
type: integer
name: "psize"
description: "Size of each page in pagination"
required: false
Key:
definitions: type: integer
Key: description: "REWRITE THIS TYPE AS KEY IN SWAGGER FILE"
type: integer Value:
description: "REWRITE THIS TYPE AS KEY IN SWAGGER FILE" type: integer
Value: description: "REWRITE THIS TYPE AS VALUE IN SWAGGER FILE"
type: integer NewValue:
description: "REWRITE THIS TYPE AS VALUE IN SWAGGER FILE" type: integer
Values: description: "REWRITE THIS TYPE AS NEW VALUE IN SWAGGER FILE"
type: array Pair:
items: type: object
$ref: "#/definitions/Value" description: "Pair of objects"
NewValue: properties:
type: integer first:
description: "REWRITE THIS TYPE AS NEW VALUE IN SWAGGER FILE" second:
Pair: PaginationResult:
type: object type: object
description: "Pair of objects" properties:
properties: page:
first: type: integer
second: description: "Page of pagination"
NewValues: pagesNumber:
type: array type: integer
items: description: "Count of pages with the size from this pagination"
$ref: "#/definitions/NewValue" size:
PaginationResult: type: integer
type: object description: "Size of each page in pagination"
properties: results:
page: type: array
type: integer description: "Array of all elements on that page. Size of pagination and size of array can be different and it can be interpreted like current page is the last one"
description: "Page of pagination" items:
pagesNumber: type: object
type: integer
description: "Count of pages with the size from this pagination"
size:
type: integer
description: "Size of each page in pagination"
results:
type: array
description: "Array of all elements on that page. Size of pagination and size of array can be different and it can be interpreted like current page is the last one"
items:
type: object
paths: paths:
/getByPagination: /getByPagination:
@@ -103,30 +73,34 @@ paths:
tags: tags:
- "Read" - "Read"
parameters: parameters:
- $ref: "#/parameters/PaginationInQueryPage" - $ref: "#/components/parameters/PaginationInQueryPage"
- $ref: "#/parameters/PaginationInQuerySize" - $ref: "#/components/parameters/PaginationInQuerySize"
responses: responses:
"200": "200":
description: "Pagination with elements" description: "Pagination with elements"
schema: content:
allOf: application/json:
- $ref: "#/definitions/PaginationResult" schema:
- properties: allOf:
results: - $ref: "#/components/schemas/PaginationResult"
items: - properties:
$ref: "#/definitions/Value" results:
items:
$ref: "#/components/schemas/Value"
/getById: /getById:
get: get:
tags: tags:
- "Read" - "Read"
parameters: parameters:
- $ref: "#/parameters/IdInQuery" - $ref: "#/components/parameters/IdInQuery"
required: true required: true
responses: responses:
"200": "200":
description: "Result object" description: "Result object"
schema: content:
$ref: "#/definitions/Value" application/json:
schema:
$ref: "#/components/schemas/Value"
"204": "204":
description: "No value by id" description: "No value by id"
/contains: /contains:
@@ -134,13 +108,15 @@ paths:
tags: tags:
- "Read" - "Read"
parameters: parameters:
- $ref: "#/parameters/IdInQuery" - $ref: "#/components/parameters/IdInQuery"
required: true required: true
responses: responses:
"200": "200":
description: "Object with id availability in repo" description: "Object with id availability in repo"
schema: content:
type: boolean application/json:
schema:
type: boolean
/count: /count:
get: get:
tags: tags:
@@ -148,40 +124,75 @@ paths:
responses: responses:
"200": "200":
description: "Amount of objects in repo" description: "Amount of objects in repo"
schema: content:
type: integer application/json:
schema:
type: integer
/create: /create:
post: post:
tags: tags:
- "Write" - "Write"
parameters: requestBody:
- $ref: "#/parameters/NewValuesInBody" content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/NewValue"
responses: responses:
"200": "200":
description: "Objects has been created and saved" description: "Objects has been created and saved"
schema: content:
$ref: "#/definitions/Values" application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Value"
/update: /update:
post: post:
tags: tags:
- "Write" - "Write"
parameters: requestBody:
- $ref: "#/parameters/NewValuesWithIdsInBody" content:
application/json:
schema:
type: array
items:
allOf:
- $ref: "#/components/schemas/Pair"
- properties:
first:
$ref: "#/components/schemas/Key"
second:
$ref: "#/components/schemas/NewValue"
responses: responses:
"200": "200":
description: "Objects has been updated" description: "Objects has been updated"
schema: content:
$ref: "#/definitions/Values" application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Value"
/deleteById: /deleteById:
post: post:
tags: tags:
- "Write" - "Write"
parameters: requestBody:
- $ref: "#/parameters/IdsInBody" content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Key"
responses: responses:
"200": "200":
description: "Objects has been updated" description: "Objects has been updated"
schema: content:
$ref: "#/definitions/Values" application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Value"