Compare commits

...

28 Commits

Author SHA1 Message Date
aefaf4a8cc update klock (again) 2022-03-06 16:08:02 +06:00
b29f37a251 update klock 2022-03-04 20:20:17 +06:00
88854020ac extend functionality of websockets 2022-03-04 20:16:59 +06:00
14ebe01fc6 improvements in includeWebsocketHandling 2022-03-04 15:31:32 +06:00
771aed0f0f UnifiedRequester#createStandardWebsocketFlow(String,DeserializationStrategy) 2022-03-04 15:20:16 +06:00
16c57fcd6a start 0.9.10 2022-03-04 15:17:36 +06:00
75397a7ccb Merge pull request #131 from InsanusMokrassar/0.9.9
0.9.9
2022-02-24 14:44:13 +06:00
b05404f828 IntersectionObserver 2022-02-24 00:26:07 +06:00
edea942874 small refactor of operatons in applyDiff 2022-02-23 23:02:31 +06:00
8a8f568b9a add applyDiff in Diff Utils and update korlibs klock version 2022-02-23 22:55:56 +06:00
4dc8d30c52 start 0.9.9 2022-02-23 22:32:05 +06:00
cb4e08e823 Merge pull request #130 from InsanusMokrassar/0.9.8
0.9.8
2022-02-19 21:44:56 +06:00
c443bf4fa0 update dependencies 2022-02-19 16:15:36 +06:00
a6982de822 start 0.9.8 2022-02-19 16:06:45 +06:00
4f1a663e75 fixes in ExposedOneToManyKeyValueRepo and includeWebsocketHandling 2022-02-19 16:06:33 +06:00
dab262d626 start 0.9.7 2022-02-19 16:06:33 +06:00
87a3f61ca6 fix in ExposedOneToManyKeyValueRepo 2022-02-07 20:43:43 +06:00
506e937a68 start 0.9.6 2022-02-07 20:39:44 +06:00
5a037c76dd Merge pull request #126 from InsanusMokrassar/0.9.5
0.9.5
2022-02-01 23:30:26 +06:00
313f622f7e Update CHANGELOG.md 2022-02-01 14:27:07 +06:00
6cba1fe1a2 Update klock 2022-02-01 14:25:24 +06:00
fd2d0e80b7 start 0.9.5 2022-02-01 14:24:12 +06:00
96ab2e8aca Merge pull request #125 from InsanusMokrassar/0.9.4
0.9.4
2022-01-18 18:58:49 +06:00
0202988cae several fixes in optionallyReverse 2022-01-17 16:11:55 +06:00
d619d59947 Either updates 2022-01-17 15:56:48 +06:00
85b3e48d18 add optionallyReverse extensions 2022-01-17 15:38:23 +06:00
7a9b7d98a1 start 0.9.4 2022-01-14 13:22:04 +06:00
b212acfcaf Merge pull request #124 from InsanusMokrassar/0.9.3
0.9.3
2022-01-14 00:46:20 +06:00
14 changed files with 420 additions and 51 deletions

View File

@@ -1,5 +1,63 @@
# Changelog
## 0.9.10
* `Versions`:
* `Klock`: `2.5.2` -> `2.6.1`
* Ktor:
* Client:
* New function `UnifiedRequester#createStandardWebsocketFlow` without `checkReconnection` arg
* Server:
* Now it is possible to filter data in `Route#includeWebsocketHandling`
* Callback in `Route#includeWebsocketHandling` and dependent methods is `suspend` since now
* Add `URLProtocol` support in `Route#includeWebsocketHandling` and dependent methods
## 0.9.9
* `Versions`:
* `Klock`: `2.5.1` -> `2.5.2`
* `Common`:
* Add new diff tool - `applyDiff`
* Implementation of `IntersectionObserver` in JS part (copypaste of [this](https://youtrack.jetbrains.com/issue/KT-43157#focus=Comments-27-4498582.0-0) comment)
## 0.9.8
* `Versions`:
* `Exposed`: `0.37.2` -> `0.37.3`
* `Klock`: `2.4.13` -> `2.5.1`
* `AppCompat`: `1.4.0` -> `1.4.1`
## 0.9.7
* `Repos`:
* `Exposed`:
* Fix in `ExposedOneToManyKeyValueRepo` - now it will not use `insertIgnore`
* `Ktor`:
* `Server`:
* `Route#includeWebsocketHandling` now will check that `WebSockets` feature and install it if not
## 0.9.6
* `Repos`:
* `Exposed`:
* Fix in `ExposedOneToManyKeyValueRepo` - now it will not use `deleteIgnoreWhere`
## 0.9.5
* `Versions`:
* `Klock`: `2.4.12` -> `2.4.13`
## 0.9.4
* `Pagination`:
* `Common`:
* Add several `optionallyReverse` functions
* `Common`:
* Changes in `Either`:
* Now `Either` uses `optionalT1` and `optionalT2` as main properties
* `Either#t1` and `Either#t2` are deprecated
* New extensions `Either#mapOnFirst` and `Either#mapOnSecond`
## 0.9.3
* `Versions`:

View File

@@ -153,3 +153,22 @@ inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDif
inline fun <T> Iterable<T>.calculateStrictDiff(
other: Iterable<T>
) = calculateDiff(other, strictComparison = true)
/**
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
* mutable list
*/
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
strictComparison: Boolean = false
) = calculateDiff(source, strictComparison).let {
for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}
it.added.forEach { (i, t) ->
add(i, t)
}
it.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
}

View File

@@ -6,7 +6,7 @@ import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
/**
* Realization of this interface will contains at least one not null - [t1] or [t2]
* Realization of this interface will contains at least one not null - [optionalT1] or [optionalT2]
*
* @see EitherFirst
* @see EitherSecond
@@ -14,11 +14,19 @@ import kotlinx.serialization.encoding.*
* @see Either.Companion.second
* @see Either.onFirst
* @see Either.onSecond
* @see Either.mapOnFirst
* @see Either.mapOnSecond
*/
@Serializable(EitherSerializer::class)
sealed interface Either<T1, T2> {
val optionalT1: Optional<T1>
val optionalT2: Optional<T2>
@Deprecated("Use optionalT1 instead", ReplaceWith("optionalT1"))
val t1: T1?
get() = optionalT1.dataOrNull()
@Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2"))
val t2: T2?
get() = optionalT2.dataOrNull()
companion object {
fun <T1, T2> serializer(
@@ -93,25 +101,25 @@ class EitherSerializer<T1, T2>(
}
/**
* This type [Either] will always have not nullable [t1]
* This type [Either] will always have not nullable [optionalT1]
*/
@Serializable
data class EitherFirst<T1, T2>(
override val t1: T1
) : Either<T1, T2> {
override val t2: T2?
get() = null
override val optionalT1: Optional<T1> = t1.optional
override val optionalT2: Optional<T2> = Optional.absent()
}
/**
* This type [Either] will always have not nullable [t2]
* This type [Either] will always have not nullable [optionalT2]
*/
@Serializable
data class EitherSecond<T1, T2>(
override val t2: T2
) : Either<T1, T2> {
override val t1: T1?
get() = null
override val optionalT1: Optional<T1> = Optional.absent()
override val optionalT2: Optional<T2> = t2.optional
}
/**
@@ -124,23 +132,35 @@ inline fun <T1, T2> Either.Companion.first(t1: T1): Either<T1, T2> = EitherFirst
inline fun <T1, T2> Either.Companion.second(t2: T2): Either<T1, T2> = EitherSecond(t2)
/**
* Will call [block] in case when [Either.t1] of [this] is not null
* Will call [block] in case when [this] is [EitherFirst]
*/
inline fun <T1, T2, E : Either<T1, T2>> E.onFirst(block: (T1) -> Unit): E {
val t1 = t1
t1 ?.let(block)
optionalT1.onPresented(block)
return this
}
/**
* Will call [block] in case when [Either.t2] of [this] is not null
* Will call [block] in case when [this] is [EitherSecond]
*/
inline fun <T1, T2, E : Either<T1, T2>> E.onSecond(block: (T2) -> Unit): E {
val t2 = t2
t2 ?.let(block)
optionalT2.onPresented(block)
return this
}
/**
* @return Result of [block] if [this] is [EitherFirst]
*/
inline fun <T1, R> Either<T1, *>.mapOnFirst(block: (T1) -> R): R? {
return optionalT1.mapOnPresented(block)
}
/**
* @return Result of [block] if [this] is [EitherSecond]
*/
inline fun <T2, R> Either<*, T2>.mapOnSecond(block: (T2) -> R): R? {
return optionalT2.mapOnPresented(block)
}
inline fun <reified T1, reified T2> Any.either() = when (this) {
is T1 -> Either.first<T1, T2>(this)
is T2 -> Either.second<T1, T2>(this)

View File

@@ -54,7 +54,7 @@ class DiffUtilsTests {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (step in 0 until oldList.size) {
for (step in oldList.indices) {
for ((i, v) in withIndex) {
val mutable = oldList.toMutableList()
val changes = (
@@ -73,4 +73,78 @@ class DiffUtilsTests {
}
}
}
@Test
fun testThatSimpleRemoveApplyWorks() {
val oldList = (0 until 10).toList()
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
val removedSublist = oldList.subList(i, i + count)
val mutableOldList = oldList.toMutableList()
val targetList = oldList - removedSublist
mutableOldList.applyDiff(targetList)
assertEquals(
targetList,
mutableOldList
)
}
}
}
@Test
fun testThatSimpleAddApplyWorks() {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
val addedSublist = oldList.subList(i, i + count).map { "added$it" }
val mutable = oldList.toMutableList()
mutable.addAll(i, addedSublist)
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(
mutable,
mutableOldList
)
}
}
}
@Test
fun testThatSimpleChangesApplyWorks() {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (step in oldList.indices) {
for ((i, v) in withIndex) {
val mutable = oldList.toMutableList()
val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step)
).map { index ->
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
mutable[index] = it.value
}
}
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(
mutable,
mutableOldList
)
}
}
}
}

View File

@@ -0,0 +1,124 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.DOMRectReadOnly
import org.w3c.dom.Element
external interface IntersectionObserverOptions {
/**
* An Element or Document object which is an ancestor of the intended target, whose bounding rectangle will be
* considered the viewport. Any part of the target not visible in the visible area of the root is not considered
* visible.
*/
var root: Element?
/**
* A string which specifies a set of offsets to add to the root's bounding_box when calculating intersections,
* effectively shrinking or growing the root for calculation purposes. The syntax is approximately the same as that
* for the CSS margin property; see The root element and root margin in Intersection Observer API for more
* information on how the margin works and the syntax. The default is "0px 0px 0px 0px".
*/
var rootMargin: String?
/**
* Either a single number or an array of numbers between 0.0 and 1.0, specifying a ratio of intersection area to
* total bounding box area for the observed target. A value of 0.0 means that even a single visible pixel counts as
* the target being visible. 1.0 means that the entire target element is visible. See Thresholds in Intersection
* Observer API for a more in-depth description of how thresholds are used. The default is a threshold of 0.0.
*/
var threshold: Array<Number>?
}
fun IntersectionObserverOptions(
block: IntersectionObserverOptions.() -> Unit = {}
): IntersectionObserverOptions = js("{}").unsafeCast<IntersectionObserverOptions>().apply(block)
external interface IntersectionObserverEntry {
/**
* Returns the bounds rectangle of the target element as a DOMRectReadOnly. The bounds are computed as described in
* the documentation for Element.getBoundingClientRect().
*/
val boundingClientRect: DOMRectReadOnly
/**
* Returns the ratio of the intersectionRect to the boundingClientRect.
*/
val intersectionRatio: Number
/**
* Returns a DOMRectReadOnly representing the target's visible area.
*/
val intersectionRect: DOMRectReadOnly
/**
* A Boolean value which is true if the target element intersects with the intersection observer's root. If this is
* true, then, the IntersectionObserverEntry describes a transition into a state of intersection; if it's false,
* then you know the transition is from intersecting to not-intersecting.
*/
val isIntersecting: Boolean
/**
* Returns a DOMRectReadOnly for the intersection observer's root.
*/
val rootBounds: DOMRectReadOnly
/**
* The Element whose intersection with the root changed.
*/
val target: Element
/**
* A DOMHighResTimeStamp indicating the time at which the intersection was recorded, relative to the
* IntersectionObserver's time origin.
*/
val time: Double
}
typealias IntersectionObserverCallback = (entries: Array<IntersectionObserverEntry>, observer: IntersectionObserver) -> Unit
/**
* This is just an implementation from [this commentary](https://youtrack.jetbrains.com/issue/KT-43157#focus=Comments-27-4498582.0-0)
* of Kotlin JS issue related to the absence of [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver)
*/
external class IntersectionObserver(callback: IntersectionObserverCallback) {
constructor(callback: IntersectionObserverCallback, options: IntersectionObserverOptions)
/**
* The Element or Document whose bounds are used as the bounding box when testing for intersection. If no root value
* was passed to the constructor or its value is null, the top-level document's viewport is used.
*/
val root: Element
/**
* An offset rectangle applied to the root's bounding box when calculating intersections, effectively shrinking or
* growing the root for calculation purposes. The value returned by this property may not be the same as the one
* specified when calling the constructor as it may be changed to match internal requirements. Each offset can be
* expressed in pixels (px) or as a percentage (%). The default is "0px 0px 0px 0px".
*/
val rootMargin: String
/**
* A list of thresholds, sorted in increasing numeric order, where each threshold is a ratio of intersection area to
* bounding box area of an observed target. Notifications for a target are generated when any of the thresholds are
* crossed for that target. If no value was passed to the constructor, 0 is used.
*/
val thresholds: Array<Number>
/**
* Stops the IntersectionObserver object from observing any target.
*/
fun disconnect()
/**
* Tells the IntersectionObserver a target element to observe.
*/
fun observe(targetElement: Element)
/**
* Returns an array of IntersectionObserverEntry objects for all observed targets.
*/
fun takeRecords(): Array<IntersectionObserverEntry>
/**
* Tells the IntersectionObserver to stop observing a particular target element.
*/
fun unobserve(targetElement: Element)
}

View File

@@ -10,11 +10,11 @@ org.gradle.jvmargs=-Xmx2g
kotlin_version=1.6.10
kotlin_coroutines_version=1.6.0
kotlin_serialisation_core_version=1.3.2
kotlin_exposed_version=0.37.2
kotlin_exposed_version=0.37.3
ktor_version=1.6.7
klockVersion=2.4.12
klockVersion=2.6.1
github_release_plugin_version=2.2.12
@@ -24,7 +24,7 @@ uuidVersion=0.4.0
core_ktx_version=1.7.0
androidx_recycler_version=1.2.1
appcompat_version=1.4.0
appcompat_version=1.4.1
android_minSdkVersion=19
android_compileSdkVersion=32
@@ -40,10 +40,10 @@ crypto_js_version=4.1.1
# Dokka
dokka_version=1.6.0
dokka_version=1.6.10
# Project data
group=dev.inmo
version=0.9.3
android_code_version=93
version=0.9.10
android_code_version=100

View File

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

View File

@@ -4,6 +4,7 @@ import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.features.websocket.ws
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readBytes
import kotlinx.coroutines.flow.Flow
@@ -17,6 +18,7 @@ import kotlinx.serialization.DeserializationStrategy
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
): Flow<T> {
val correctedUrl = url.asCorrectWebSocketUrl
@@ -26,7 +28,7 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
do {
val reconnect = try {
safely {
ws(correctedUrl) {
ws(correctedUrl, requestBuilder) {
for (received in incoming) {
when (received) {
is Frame.Binary -> producerScope.send(conversation(received.readBytes()))
@@ -65,10 +67,12 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(
url,
checkReconnection
checkReconnection,
requestBuilder
) {
serialFormat.decodeDefault(deserializer, it)
}

View File

@@ -85,9 +85,16 @@ class UnifiedRequester(
fun <T> createStandardWebsocketFlow(
url: String,
checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat)
checkReconnection: (Throwable?) -> Boolean,
deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat, requestBuilder)
fun <T> createStandardWebsocketFlow(
url: String,
deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
}
val defaultRequester = UnifiedRequester()

View File

@@ -2,29 +2,31 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.featureOrNull
import io.ktor.application.install
import io.ktor.http.URLProtocol
import io.ktor.http.cio.websocket.*
import io.ktor.routing.Route
import io.ktor.websocket.webSocket
import io.ktor.routing.application
import io.ktor.websocket.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.serialization.SerializationStrategy
private suspend fun DefaultWebSocketSession.checkReceivedAndCloseIfExists() {
if (incoming.tryReceive() != null) {
close()
throw CorrectCloseException
}
}
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
converter: (T) -> StandardKtorSerialInputData
protocol: URLProtocol = URLProtocol.WS,
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
) {
webSocket(suburl) {
application.apply {
featureOrNull(io.ktor.websocket.WebSockets) ?: install(io.ktor.websocket.WebSockets)
}
webSocket(suburl, protocol.name) {
safely {
flow.collect {
send(converter(it))
converter(it) ?.let { data ->
send(data)
}
}
}
}
@@ -34,10 +36,24 @@ fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
protocol: URLProtocol = URLProtocol.WS,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(
suburl,
flow
) {
serialFormat.encodeDefault(serializer, it)
}
flow,
protocol,
converter = if (filter == null) {
{
serialFormat.encodeDefault(serializer, it)
}
} else {
{
if (filter(it)) {
serialFormat.encodeDefault(serializer, it)
} else {
null
}
}
}
)

View File

@@ -5,8 +5,7 @@ import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.*
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.request.receive
@@ -18,6 +17,7 @@ import io.ktor.util.asStream
import io.ktor.util.cio.writeChannel
import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.*
import io.ktor.websocket.WebSocketServerSession
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.*
import java.io.File
@@ -30,8 +30,10 @@ class UnifiedRouter(
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat)
serializer: SerializationStrategy<T>,
protocol: URLProtocol = URLProtocol.WS,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
answerSerializer: SerializationStrategy<T>,

View File

@@ -38,3 +38,31 @@ fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
size.toLong()
)
}
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
is List<T> -> optionallyReverse(reverse)
is Set<T> -> optionallyReverse(reverse)
else -> if (reverse) {
reversed()
} else {
this
}
}
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
reversed()
} else {
this
}
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
reversed().toSet()
} else {
this
}
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
Array(size) {
get(lastIndex - it)
}
} else {
this
}

View File

@@ -26,3 +26,15 @@ fun Pagination.reverse(datasetSize: Long): SimplePagination {
* Shortcut for [reverse]
*/
fun Pagination.reverse(objectsCount: Int) = reverse(objectsCount.toLong())
fun Pagination.optionallyReverse(objectsCount: Int, reverse: Boolean) = if (reverse) {
reverse(objectsCount)
} else {
this
}
fun Pagination.optionallyReverse(objectsCount: Long, reverse: Boolean) = if (reverse) {
reverse(objectsCount)
} else {
this
}

View File

@@ -35,10 +35,15 @@ open class ExposedOneToManyKeyValueRepo<Key, Value>(
if (select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) {
return@mapNotNull null
}
insertIgnore {
val insertResult = insert {
it[keyColumn] = k
it[valueColumn] = v
}.getOrNull(keyColumn) ?.let { k to v }
}
if (insertResult.insertedCount > 0) {
k to v
} else {
null
}
} ?: emptyList()
}
}.forEach { _onNewValue.emit(it) }
@@ -48,7 +53,7 @@ open class ExposedOneToManyKeyValueRepo<Key, Value>(
transaction(database) {
toRemove.keys.flatMap { k ->
toRemove[k] ?.mapNotNull { v ->
if (deleteIgnoreWhere { keyColumn.eq(k).and(valueColumn.eq(v)) } > 0 ) {
if (deleteWhere { keyColumn.eq(k).and(valueColumn.eq(v)) } > 0 ) {
k to v
} else {
null