Compare commits

...

15 Commits

18 changed files with 574 additions and 26 deletions

View File

@@ -1,5 +1,25 @@
# Changelog
## 0.11.0
## 0.10.8
* `Common`
* Add `Element.isOverflow*` extension properties
## 0.10.7
* `Pagination`:
* Now it is possible to use `doForAll*` and `getForAll` functions in non suspend places
## 0.10.6
* `Versions`
* `Ktor`: `2.0.1` -> `2.0.2`
* `Common`
* `JS`:
* Add `ResizeObserver` functionality
## 0.10.5
* `Versions`

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.Element
inline val Element.isOverflowWidth
get() = scrollWidth > clientWidth
inline val Element.isOverflowHeight
get() = scrollHeight > clientHeight
inline val Element.isOverflow
get() = isOverflowHeight || isOverflowWidth

View File

@@ -0,0 +1,58 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.*
import kotlin.js.Json
import kotlin.js.json
external class ResizeObserver(
callback: (Array<ResizeObserverEntry>, ResizeObserver) -> Unit
) {
fun observe(target: Element, options: Json = definedExternally)
fun unobserve(target: Element)
fun disconnect()
}
external interface ResizeObserverSize {
val blockSize: Float
val inlineSize: Float
}
external interface ResizeObserverEntry {
val borderBoxSize: Array<ResizeObserverSize>
val contentBoxSize: Array<ResizeObserverSize>
val devicePixelContentBoxSize: Array<ResizeObserverSize>
val contentRect: DOMRectReadOnly
val target: Element
}
fun ResizeObserver.observe(target: Element, options: ResizeObserverObserveOptions) = observe(
target,
json(
"box" to options.box ?.name
)
)
class ResizeObserverObserveOptions(
val box: Box? = null
) {
sealed interface Box {
val name: String
object Content : Box {
override val name: String
get() = "content-box"
}
object Border : Box {
override val name: String
get() = "border-box"
}
object DevicePixelContent : Box {
override val name: String
get() = "device-pixel-content-box"
}
}
}

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.10.5
android_code_version=120
version=0.11.0
android_code_version=124

View File

@@ -11,7 +11,7 @@ jb-dokka = "1.6.21"
klock = "2.7.0"
uuid = "0.4.0"
ktor = "2.0.1"
ktor = "2.0.2"
gh-release = "2.3.7"

View File

@@ -0,0 +1,54 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.plugins.pluginOrNull
import io.ktor.client.plugins.websocket.*
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.websocket.Frame
import io.ktor.websocket.readBytes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
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
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow {
do {
val reconnect = runCatchingSafely {
ws(correctedUrl, requestBuilder) {
for (received in incoming) {
sendSerialized(received.data)
}
}
checkReconnection(null)
}.getOrElse { e ->
checkReconnection(e).also {
if (!it) {
close(e)
}
}
}
} while (reconnect && isActive)
if (isActive) {
safely {
close()
}
}
}
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.http.URLProtocol
import io.ktor.server.application.install
import io.ktor.server.application.pluginOrNull
import io.ktor.server.routing.Route
import io.ktor.server.routing.application
import io.ktor.server.websocket.*
import io.ktor.websocket.send
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerializationStrategy
inline fun <reified T : Any> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
protocol: URLProtocol? = null
) {
application.apply {
pluginOrNull(WebSockets) ?: install(WebSockets)
}
webSocket(suburl, protocol ?.name) {
safely {
flow.collect {
sendSerialized(it)
}
}
}
}

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ import io.ktor.client.HttpClient
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
@Deprecated("Use KtorReadStandardCrudRepoClient instead")
class KtorReadStandardCrudRepo<ObjectType, IdType> (
private val baseUrl: String,
private val unifiedRequester: UnifiedRequester,

View File

@@ -0,0 +1,80 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.http.HttpStatusCode
import io.ktor.util.reflect.TypeInfo
import io.ktor.util.reflect.typeInfo
import kotlinx.serialization.*
class KtorReadStandardCrudRepoClient<ObjectType, IdType> (
private val baseUrl: String,
private val httpClient: HttpClient,
private val objectType: TypeInfo,
private val idSerializer: suspend (IdType) -> String
) : ReadStandardCRUDRepo<ObjectType, IdType> {
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = httpClient.get(
buildStandardUrl(baseUrl, getByPaginationRouting, pagination.asUrlQueryParts)
).body()
override suspend fun getById(id: IdType): ObjectType? = httpClient.get(
buildStandardUrl(
baseUrl,
getByIdRouting,
mapOf(
"id" to idSerializer(id)
)
)
).takeIf { it.status != HttpStatusCode.NoContent } ?.body<ObjectType>(objectType)
override suspend fun contains(id: IdType): Boolean = httpClient.get(
buildStandardUrl(
baseUrl,
containsRouting,
mapOf(
"id" to idSerializer(id)
)
)
).body()
override suspend fun count(): Long = httpClient.get(
buildStandardUrl(
baseUrl,
countRouting
)
).body()
}
inline fun <reified ObjectType, IdType> KtorReadStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
noinline idSerializer: suspend (IdType) -> String
) = KtorReadStandardCrudRepoClient<ObjectType, IdType>(
baseUrl,
httpClient,
typeInfo<ObjectType>(),
idSerializer
)
inline fun <reified ObjectType, IdType> KtorReadStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat
) = KtorReadStandardCrudRepoClient<ObjectType, IdType>(baseUrl, httpClient) {
serialFormat.encodeToString(idsSerializer, it)
}
inline fun <reified ObjectType, IdType> KtorReadStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat
) = KtorReadStandardCrudRepoClient<ObjectType, IdType>(baseUrl, httpClient) {
serialFormat.encodeHex(idsSerializer, it)
}

View File

@@ -7,6 +7,7 @@ import dev.inmo.micro_utils.repos.*
import io.ktor.client.HttpClient
import kotlinx.serialization.KSerializer
@Deprecated("Use KtorStandardCrudRepoClient instead")
class KtorStandardCrudRepo<ObjectType, IdType, InputValue> (
baseUrl: String,
baseSubpart: String,

View File

@@ -0,0 +1,93 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.repos.*
import io.ktor.client.HttpClient
import io.ktor.util.reflect.TypeInfo
import io.ktor.util.reflect.typeInfo
import kotlinx.serialization.*
class KtorStandardCrudRepoClient<ObjectType, IdType, InputValue> (
baseUrl: String,
httpClient: HttpClient,
objectTypeInfo: TypeInfo,
idSerializer: suspend (IdType) -> String
) : StandardCRUDRepo<ObjectType, IdType, InputValue>,
ReadStandardCRUDRepo<ObjectType, IdType> by KtorReadStandardCrudRepoClient(
baseUrl,
httpClient,
objectTypeInfo,
idSerializer
),
WriteStandardCRUDRepo<ObjectType, IdType, InputValue> by KtorWriteStandardCrudRepoClient(
baseUrl,
httpClient
) {
constructor(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
objectTypeInfo: TypeInfo,
idSerializer: suspend (IdType) -> String
) : this(
buildStandardUrl(baseUrl, subpart), httpClient, objectTypeInfo, idSerializer
)
}
inline fun <reified ObjectType, IdType, InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
noinline idSerializer: suspend (IdType) -> String
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(
baseUrl,
httpClient,
typeInfo<ObjectType>(),
idSerializer
)
inline fun <reified ObjectType, IdType, InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(baseUrl, httpClient) {
serialFormat.encodeToString(idsSerializer, it)
}
inline fun <reified ObjectType, IdType, InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(baseUrl, httpClient) {
serialFormat.encodeHex(idsSerializer, it)
}
inline fun <reified ObjectType, IdType, InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
noinline idSerializer: suspend (IdType) -> String
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(
buildStandardUrl(baseUrl, subpart),
httpClient,
idSerializer
)
inline fun <reified ObjectType, IdType, InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(buildStandardUrl(baseUrl, subpart), httpClient, idsSerializer, serialFormat)
inline fun <reified ObjectType, IdType, InputValue> KtorStandardCrudRepoClient(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat
) = KtorStandardCrudRepoClient<ObjectType, IdType, InputValue>(buildStandardUrl(baseUrl, subpart), httpClient, idsSerializer, serialFormat)

View File

@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.*
@Deprecated("Use KtorWriteStandardCrudRepoClient instead")
class KtorWriteStandardCrudRepo<ObjectType, IdType, InputValue> (
private val baseUrl: String,
private val unifiedRequester: UnifiedRequester,

View File

@@ -0,0 +1,51 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.client.*
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.*
import io.ktor.util.reflect.TypeInfo
import io.ktor.util.reflect.typeInfo
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.*
class KtorWriteStandardCrudRepoClient<ObjectType, IdType, InputValue> (
private val baseUrl: String,
private val httpClient: HttpClient
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValue> {
override val newObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, newObjectsFlowRouting),
)
override val updatedObjectsFlow: Flow<ObjectType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, updatedObjectsFlowRouting)
)
override val deletedObjectsIdsFlow: Flow<IdType> = httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting)
)
override suspend fun create(values: List<InputValue>): List<ObjectType> = httpClient.post {
url(buildStandardUrl(baseUrl, createRouting))
setBody(values)
}.body()
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValue>>): List<ObjectType> = httpClient.post {
url(buildStandardUrl(baseUrl, updateManyRouting))
setBody(values)
}.body()
override suspend fun update(id: IdType, value: InputValue): ObjectType? = update(listOf(id to value)).firstOrNull()
override suspend fun deleteById(ids: List<IdType>) {
httpClient.post {
url(buildStandardUrl(baseUrl, deleteByIdRouting))
setBody(ids)
}
}
}

View File

@@ -0,0 +1,71 @@
package dev.inmo.micro_utils.repos.ktor.server.crud
import dev.inmo.micro_utils.ktor.common.decodeHex
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.pagination.extractPagination
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.serialization.*
inline fun <reified ObjectType, reified IdType> Route.configureReadStandardCrudRepoRoutes(
originalRepo: ReadStandardCRUDRepo<ObjectType, IdType>,
noinline idDeserializer: suspend (String) -> IdType
) {
get(getByPaginationRouting) {
val pagination = call.request.queryParameters.extractPagination
call.respond(originalRepo.getByPagination(pagination))
}
get(getByIdRouting) {
val id = idDeserializer(
call.getQueryParameterOrSendError("id") ?: return@get
)
val result = originalRepo.getById(id)
if (result == null) {
call.respond(HttpStatusCode.NoContent)
} else {
call.respond(result)
}
}
get(containsRouting) {
val id = idDeserializer(
call.getQueryParameterOrSendError("id") ?: return@get
)
call.respond(
originalRepo.contains(id)
)
}
get(countRouting) {
call.respond(
originalRepo.count()
)
}
}
inline fun <reified ObjectType, reified IdType> Route.configureReadStandardCrudRepoRoutes(
originalRepo: ReadStandardCRUDRepo<ObjectType, IdType>,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat
) = configureReadStandardCrudRepoRoutes(originalRepo) {
serialFormat.decodeFromString(idsSerializer, it)
}
inline fun <reified ObjectType, reified IdType> Route.configureReadStandardCrudRepoRoutes(
originalRepo: ReadStandardCRUDRepo<ObjectType, IdType>,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat
) = configureReadStandardCrudRepoRoutes(originalRepo) {
serialFormat.decodeHex(idsSerializer, it)
}

View File

@@ -0,0 +1,34 @@
package dev.inmo.micro_utils.repos.ktor.server.crud
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
import dev.inmo.micro_utils.repos.StandardCRUDRepo
import io.ktor.http.ContentType
import io.ktor.server.routing.Route
import io.ktor.server.routing.route
import kotlinx.serialization.*
inline fun <reified ObjectType : Any, reified IdType : Any, reified InputValue : Any> Route.configureStandardCrudRepoRoutes(
originalRepo: StandardCRUDRepo<ObjectType, IdType, InputValue>,
noinline idDeserializer: suspend (String) -> IdType
) {
configureReadStandardCrudRepoRoutes(originalRepo, idDeserializer)
configureWriteStandardCrudRepoRoutes(originalRepo)
}
inline fun <reified ObjectType : Any, reified IdType : Any, reified InputValue : Any> Route.configureStandardCrudRepoRoutes(
originalRepo: StandardCRUDRepo<ObjectType, IdType, InputValue>,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat
) = configureStandardCrudRepoRoutes(originalRepo) {
serialFormat.decodeFromString(idsSerializer, it)
}
inline fun <reified ObjectType : Any, reified IdType : Any, reified InputValue : Any> Route.configureStandardCrudRepoRoutes(
originalRepo: StandardCRUDRepo<ObjectType, IdType, InputValue>,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat
) = configureStandardCrudRepoRoutes(originalRepo) {
serialFormat.decodeHex(idsSerializer, it)
}

View File

@@ -0,0 +1,43 @@
package dev.inmo.micro_utils.repos.ktor.server.crud
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
inline fun <reified ObjectType : Any, reified IdType : Any, reified InputValue : Any> Route.configureWriteStandardCrudRepoRoutes(
originalRepo: WriteStandardCRUDRepo<ObjectType, IdType, InputValue>
) {
includeWebsocketHandling(
newObjectsFlowRouting,
originalRepo.newObjectsFlow,
)
includeWebsocketHandling(
updatedObjectsFlowRouting,
originalRepo.updatedObjectsFlow
)
includeWebsocketHandling(
deletedObjectsIdsFlowRouting,
originalRepo.deletedObjectsIdsFlow
)
post(createRouting) {
call.respond(
originalRepo.create(
call.receive()
)
)
}
post(updateManyRouting) {
call.respond(originalRepo.update(call.receive()))
}
post(deleteByIdRouting) {
call.respond(originalRepo.deleteById(call.receive()))
}
}