Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 24 additions & 39 deletions kotlin-js-store/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# yarn lockfile v1


"@colors/[email protected].1":
"@colors/[email protected].0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
Expand Down Expand Up @@ -63,28 +63,12 @@
dependencies:
"@types/node" "*"

"@types/eslint-scope@^3.7.3":
version "3.7.7"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5"
integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==
dependencies:
"@types/eslint" "*"
"@types/estree" "*"

"@types/eslint@*":
version "9.6.1"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584"
integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==
dependencies:
"@types/estree" "*"
"@types/json-schema" "*"

"@types/estree@*", "@types/estree@^1.0.5":
"@types/estree@^1.0.5":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==

"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
Expand Down Expand Up @@ -665,7 +649,7 @@ engine.io@~6.6.0:
engine.io-parser "~5.2.1"
ws "~8.17.1"

enhanced-resolve@^5.17.0:
enhanced-resolve@^5.17.1:
version "5.18.2"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz#7903c5b32ffd4b2143eeb4b92472bd68effd5464"
integrity sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==
Expand Down Expand Up @@ -1194,10 +1178,10 @@ [email protected]:
minimatch "^9.0.3"
webpack-merge "^4.1.5"

[email protected].3:
version "6.4.3"
resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.3.tgz#763e500f99597218bbb536de1a14acc4ceea7ce8"
integrity sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==
[email protected].4:
version "6.4.4"
resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.4.tgz#dfa5a426cf5a8b53b43cd54ef0d0d09742351492"
integrity sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==
dependencies:
"@colors/colors" "1.5.0"
body-parser "^1.19.0"
Expand Down Expand Up @@ -1229,6 +1213,13 @@ kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==

[email protected]:
version "2.0.0"
resolved "https://registry.yarnpkg.com/kotlin-web-helpers/-/kotlin-web-helpers-2.0.0.tgz#b112096b273c1e733e0b86560998235c09a19286"
integrity sha512-xkVGl60Ygn/zuLkDPx+oHj7jeLR7hCvoNF99nhwXMn8a3ApB4lLiC9pk4ol4NHPjyoCbvQctBqvzUcp8pkqyWw==
dependencies:
format-util "^1.0.5"

loader-runner@^4.2.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
Expand Down Expand Up @@ -1337,10 +1328,10 @@ mkdirp@^0.5.5:
dependencies:
minimist "^1.2.6"

[email protected].0:
version "10.7.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.0.tgz#9e5cbed8fa9b37537a25bd1f7fb4f6fc45458b9a"
integrity sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==
[email protected].3:
version "10.7.3"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.3.tgz#ae32003cabbd52b59aece17846056a68eb4b0752"
integrity sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==
dependencies:
ansi-colors "^4.1.3"
browser-stdout "^1.3.1"
Expand Down Expand Up @@ -1879,11 +1870,6 @@ type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"

[email protected]:
version "5.5.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==

ua-parser-js@^0.7.30:
version "0.7.40"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.40.tgz#c87d83b7bb25822ecfa6397a0da5903934ea1562"
Expand Down Expand Up @@ -1982,12 +1968,11 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723"
integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==

webpack@5.93.0:
version "5.93.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.93.0.tgz#2e89ec7035579bdfba9760d26c63ac5c3462a5e5"
integrity sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==
webpack@5.94.0:
version "5.94.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f"
integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^1.0.5"
"@webassemblyjs/ast" "^1.12.1"
"@webassemblyjs/wasm-edit" "^1.12.1"
Expand All @@ -1996,7 +1981,7 @@ [email protected]:
acorn-import-attributes "^1.9.5"
browserslist "^4.21.10"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.17.0"
enhanced-resolve "^5.17.1"
es-module-lexer "^1.2.1"
eslint-scope "5.1.1"
events "^3.2.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.lightningkite.reactive.core.Listenable
import com.lightningkite.reactive.core.Reactive
import com.lightningkite.reactive.core.ReactiveState
import com.lightningkite.reactive.core.reactiveState
import com.lightningkite.reactive.lensing.validation.IssueNode
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ fun <T> Reactive<T>.withWrite(action: suspend Reactive<T>.(T) -> Unit): MutableR
}
}

fun <T> Reactive<T>.onNextSuccess(action: (T) -> Unit): () -> Unit {
fun <T> Reactive<T>.onNextSuccess(action: (T) -> Unit): (() -> Unit)? {
if (state.success) {
state.onSuccess(action)
return {}
return null
}

var remover: (() -> Unit)? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import com.lightningkite.reactive.core.MutableWithReactiveValue
import com.lightningkite.reactive.core.Reactive
import com.lightningkite.reactive.core.ReactiveState
import com.lightningkite.reactive.extensions.invokeAllSafe
import com.lightningkite.reactive.lensing.validation.IssueNode
import com.lightningkite.reactive.lensing.validation.MutableValidated
import com.lightningkite.reactive.lensing.validation.Validated
import com.lightningkite.reactive.lensing.validation.ValidatedValue
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
Expand Down Expand Up @@ -44,7 +48,11 @@ class LensByElement<E, ID, T>(
val identity: (E) -> ID,
val elementLens: (LensByElement<E, ID, T>.Element) -> T
) : Reactive<List<T>> {
inner class Element internal constructor(valueInit: E) : MutableWithReactiveValue<E>, CalculationContext {
private val node = IssueNode(parent = (source as? MutableValidated)?.node).apply { connect() }

inner class Element internal constructor(valueInit: E) : MutableWithReactiveValue<E>, MutableValidated<E>, CalculationContext {
override val node: IssueNode = [email protected]()

private var job = Job()
private val restOfContext = Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable ->
if (throwable !is CancellationException) {
Expand All @@ -56,17 +64,16 @@ class LensByElement<E, ID, T>(
internal var dead = false
set(value) {
field = value
if (value) node.disconnect() else node.connect()
listeners.invokeAllSafe()
job.cancel()
job = Job()
}
var id: ID = identity(valueInit)
private set
private val listeners = ArrayList<() -> Unit>()
override val state: ReactiveState<E>
get() = ReactiveState(value)
override var value: E = valueInit
set(value) {
internal set(value) {
if (field != value) {
id = identity(value)
field = value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ open class SetLens<O, T>(
val set: (T) -> O
) : Lens<MutableReactive<O>, O, T>(source, get), MutableReactive<T> {
override suspend fun set(value: T) {
source.set(set.invoke(value))
val transformed = set.invoke(value)
state = ReactiveState(get(transformed)) // using get(set(value)) so that echos are filtered out
source.set(transformed)
}
}

Expand All @@ -52,7 +54,9 @@ open class ModifyLens<O, T>(
val modify: (O, T) -> O
) : Lens<MutableReactive<O>, O, T>(source, get), MutableReactive<T> {
override suspend fun set(value: T) {
source.set(modify(source.awaitOnce(), value))
val transformed = modify(source.awaitOnce(), value)
state = ReactiveState(get(transformed)) // using get(modify(this, value)) so that echos are filtered out
source.set(transformed)
}
}

Expand Down Expand Up @@ -90,7 +94,9 @@ open class SetValueLens<O, T>(source: MutableReactiveValue<O>, get: (O) -> T, va
override var value: T
get() = super.value
set(value) {
source.value = set.invoke(value)
val transformed = set.invoke(value)
super.value = get(transformed)
source.value = transformed
}
}

Expand All @@ -99,7 +105,9 @@ open class ModifyValueLens<O, T>(source: MutableReactiveValue<O>, get: (O) -> T,
override var value: T
get() = super.value
set(value) {
source.value = modify(source.value, value)
val transformed = modify(source.value, value)
super.value = get(transformed)
source.value = transformed
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.lightningkite.reactive.lensing.validation

import com.lightningkite.reactive.context.ReactiveContext
import com.lightningkite.reactive.core.Constant
import com.lightningkite.reactive.core.Reactive
import com.lightningkite.reactive.core.ReactiveMutableList
import com.lightningkite.reactive.core.ReactiveValue
import com.lightningkite.reactive.core.ResourceUse
import com.lightningkite.reactive.core.Signal
import com.lightningkite.reactive.core.remember

Expand All @@ -24,19 +28,69 @@ import com.lightningkite.reactive.core.remember
* Reporting an issue on any node will cause that issue to be included in the parent's [issues] list.
* Removing a child node will remove its issues from the parent's [issues] list.
*
* Note: When created, IssueNodes are not by-default connected to their parent's validation tree.
* This is done to help manage dependencies, and avoid deadlocks in validation. If this wasn't done
* there would be some cases where a validation lens would be discarded, but it's validation issues
* would still propagate up the tree, even though there's no way to clear the issues. To connect an
* IssueNode
*
* @property parent The parent node in the validation tree, or null if this is the root.
*/
class IssueNode(val parent: IssueNode?) {
private val nodeIssue = Signal<Issue?>(null)
class IssueNode(val parent: IssueNode?) : ResourceUse {
private val nodeIssue = Signal<Reactive<Issue?>>(Constant(null))

fun report(issue: Issue?) { nodeIssue.value = issue }
fun report(issue: Issue?) { nodeIssue.value = Constant(issue) }
fun reactiveReport(issue: ReactiveContext.() -> Issue?) {
nodeIssue.value = remember(action = issue)
}

private val children = ReactiveMutableList<IssueNode>()
fun child() = IssueNode(this).also { children.add(it) }
fun removeChild(child: IssueNode) = children.remove(child)

/**
* Creates a child of this [IssueNode] and immediately connects it to the validation tree.
*
* Note: This function is **NOT** safe to use in a [ReactiveContext], unless you plan to manage
* the lifetime of the node manually. Using this function in a [ReactiveContext] will create a new
* child every time the context reruns, and will probably leave orphaned nodes that you are unable
* to clear the issues on.
*
* Instead, consider using [child] outside of the [ReactiveContext], and then report to that outside
* node inside any reactive code.
* */
fun child() = IssueNode(this).apply { connect() }

private var connected = false

/**
* Grafts this node and its children to its parent's validation tree.
* This means that this node's issues will propagate to its parent.
*
* Useful for establishing validation dependencies once a set of data has become relevant.
* */
fun connect() {
if (connected || parent == null) return
connected = true
parent.children.add(this)
}
/**
* Prunes this node and its children from its parent's validation tree.
* This means that this node's issues will no longer propagate to its parent.
*
* Useful for removing validation dependencies on data that is no longer relevant.
* */
fun disconnect() {
if (!connected || parent == null) return
connected = false
parent.children.remove(this)
}

override fun beginUse(): () -> Unit {
connect()
return ::disconnect
}

val issues : Reactive<List<Issue>> = remember {
listOfNotNull(nodeIssue()) + children().flatMap { it.issues() }
listOfNotNull(nodeIssue()()) + children().flatMap { it.issues() }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ val IssueTracking.issues get() = node.issues
*
* Note: Reporting or clearing issues in this node does not affect issues in child nodes.
*
* @param issue The issue to report, or null to clear this node's issues.
* @param issue The issue to report, or null to clear this node's issue.
*/
fun IssueTracking.report(issue: Issue?) = node.report(issue)

Expand Down
Loading
Loading